欢迎光临
我们一直在努力

HarmonyOS 6 ArkGraphics 3D 精讲:几何体、材质与纹理

HarmonyOS 6 ArkGraphics 3D 精讲:几何体、材质与纹理

    • 一、理解ArkGraphics 3D 资源链路,模型、材质、贴图的关系。
    • 二、几何体和 Mesh:形状是怎么被造出来的
      • 在ArkGraphics 3D里创建几何体
      • 什么时候适合运行时创建?
    • 三、材质:同一个形状,为什么看起来完全不同
      • 材质:给几何体穿上“外衣”
      • 封装一个统一的创建方法
    • 四、纹理与Image:图片到底是怎么贴到模型上的
      • 加载图片资源
      • UV:模型表面的坐标系统
      • 纹理的成本
    • 五、glTF:资源进入工程之前,先把规范说清楚
      • glTF 里有什么
    • 六、案例实操:几何、材质、纹理如何协同工作
    • 七、常见坑点与本篇小结

做 3D 商品展示的时候,最常见的一个需求是“换皮肤”。 在这里插入图片描述 比如一个售卖T恤的3D可视化网站,用户可以自由选择T恤的配色,更换logo,这里面涉及的是几何体、材质、纹理、图片资源、Mesh 和场景节点之间的关系。 在这里插入图片描述 很多开发者在实际开发中可能会遇到类似下面这种材质、贴图的问题:

  • 模型能显示,但换材质没反应。
  • 图片路径没错,但贴图没有正常显示。
  • 同一张贴图换到不同节点,效果被拉伸。
  • glTF 在编辑器里正常,放到工程里就变成灰白色。

这一篇我们就讲这件事:几何体、材质、纹理、Geometry之间的关系。

我们计划做一个小案例,可以切换 Cube / Sphere,切换 PBR / Unlit,切换无贴图、立方体贴图、头盔 albedo 贴图,也可以切换颜色预设,来观察下面的资源链路:

Image -> Material -> Mesh -> Geometry -> Scene

一、理解ArkGraphics 3D 资源链路,模型、材质、贴图的关系。

在这里插入图片描述

在 2D 页面里,一张图片通常就是一个完整资源。路径对了,图片就显示。

3D 场景不太一样。

屏幕上有一个球,其实背后至少有几层东西在协作:

层级作用项目里的表现
顶点 决定有哪些点 模型的基础数据
三角面 决定点怎么连成面 模型轮廓和表面
法线 决定面朝哪里 影响受光效果
UV 决定贴图怎么铺 影响纹理方向和拉伸
Mesh 保存网格资源 可以被 Geometry 使用
Material 描述表面 颜色、贴图、金属度、粗糙度
Geometry 场景里的可见节点 有位置、旋转、缩放

所以“模型渲染”不是一步完成的,它更像下面这条链路:

几何描述 -> Mesh 资源 -> Geometry 节点 -> Material 材质 -> Scene 场景

如果还有贴图,细分下大概是这样的:

Image -> Material.baseColor.image -> Geometry.mesh.materialOverride

在 ArkGraphics 3D 里,这个过程非常直观。比如我们创建一个立方体:

let cube = new CubeGeometry();
cube.size = { x: 1.25, y: 1.25, z: 1.25 };

let mesh = await rf.createMesh({ name: 'Article05CubeMesh' }, cube);
let geometry = await rf.createGeometry({ name: 'Article05Cube' }, mesh);

geometry.mesh.materialOverride = material;
root.children.append(geometry);

这几行代码里, CubeGeometry、Mesh 和 Geometry容易混淆。

CubeGeometry 只是一个几何描述,告诉引擎“我要一个多大的立方体”。

Mesh 是资源,保存网格数据。它也不是直接挂在场景树里的对象。

Geometry 才是可见节点。它能设置 position、rotation、scale,也能挂到 root.children 下面。

用最直白的方式来比喻,可以把这三者的关系理解成 “房子的骨架、皮肤和实物”:

  • Mesh(网格)—— 形状数据

    • 通俗说: 就像是3D模型的“骨架”或“轮廓图”。
    • 理解: 它决定了这个东西是方的、圆的、还是凹凸不平的。它只记录“位置”和“形状”,比如一个杯子 Mesh,就是杯子外形的无数个小三角片拼成的网。它本身是看不见的“空壳”。
  • Material(材质)—— 表面表现

    • 通俗说: 就像是物体的“皮肤”或“衣服”。
    • 理解: 它决定了这个东西看起来是什么感觉。比如同一个球体(Mesh),加上红塑料材质就是皮球,加上不锈钢材质就是钢珠,加上玻璃材质就是水晶球。它管颜色、反不反光、粗糙还是光滑、有没有纹理。
  • Geometry(几何体)—— 场景里的可见对象

    • 通俗说: 就像摆在你面前的那个“实实在在的成品”。
    • 理解: 它是把上面的**Mesh(形状)和Material(材质)合在一起,放到场景里的一个具体东西。比如“桌上的那个红色茶杯”。Geometry 才是你最终在屏幕上能看见、能摸到(交互)**的那个物体。
  • 二、几何体和 Mesh:形状是怎么被造出来的

    先说几何体

    在实时渲染里,再复杂的模型最终都会被拆成三角形。你看到的那个光滑球体,本质上也是一堆三角面拼出来的。三角面越多,球就越圆;少了,就是那种低多边形的风格。

    每个顶点不止记录位置,通常还带着这些信息:

    • position:坐标
    • normal:法线,控制光照怎么算
    • uv:贴图坐标
    • color:顶点颜色
    • tangent:切线,做法线贴图时用
    • 骨骼权重:角色动画里会用到

    所以说,几何体不只是个外轮廓,它还存着材质和贴图需要的数据。

    举个例子:贴图为什么对不准?很多时候不是图片的问题,是UV没展好。光照看起来别扭?多半不是灯光的问题,是法线算错了。


    在ArkGraphics 3D里创建几何体

    在这里插入图片描述

    实验台那段代码,就是在球体和立方体之间切换:

    if (this.geometryMode === 'Sphere') {
    let sphere = new SphereGeometry();
    sphere.radius = 0.72;
    sphere.segmentCount = 36;
    let sphereMesh = await rf.createMesh({ name: this.nextResourceName('Article05SphereMesh') }, sphere);
    previewGeometry = await rf.createGeometry({ name: this.nextResourceName('Article05Sphere') }, sphereMesh);
    } else {
    let cube = new CubeGeometry();
    cube.size = { x: 1.25, y: 1.25, z: 1.25 };
    let cubeMesh = await rf.createMesh({ name: this.nextResourceName('Article05CubeMesh') }, cube);
    previewGeometry = await rf.createGeometry({ name: this.nextResourceName('Article05Cube') }, cubeMesh);
    }

    这段代码其实很适合理解 createMesh 和 createGeometry 的区别:

    • createMesh:把几何描述转成网格资源
    • createGeometry:把网格塞进一个可见节点

    let cubeMesh = await rf.createMesh({ name: 'Article05CubeMesh' }, cube);
    let geometry = await rf.createGeometry({ name: 'Article05Cube' }, cubeMesh);


    什么时候适合运行时创建?

    如果你要做辅助线、调试点、标记面板、或者简单的图形,这种运行时生成的方式就很方便。比如实验台里的XYZ坐标轴,其实就是三个被拉长的立方体:

    async createBox(rf: SceneResourceFactory, parent: Node, name: string, size: Vec3, position: Vec3,
    material: Material): Promise<Geometry> {
    let box = new CubeGeometry();
    box.size = size;
    let mesh = await rf.createMesh({ name: `${name}Mesh` }, box);
    let node: Geometry = await rf.createGeometry({ name: name }, mesh);
    node.position = position;
    node.mesh.materialOverride = material;
    parent.children.append(node);
    return node;
    }

    运行时创建的几何体,适合简单、规则、能用参数描述的东西。复杂的模型,还是得交给Blender、Maya这类工具去建模,然后导出glTF。

    比如这些就不适合手写代码来搓:

    • 设备外壳
    • 汽车内饰
    • 角色模型
    • 商品细节
    • 带骨骼的动画模型

    这些东西需要正确的拓扑、UV、法线、材质槽和贴图关系。代码的任务是加载和控制,不是替代美术的生产流程。


    两类来源,各司其职

    来源适合做什么
    运行时创建 坐标轴、调试线、标记点、简单球体或立方体
    glTF导入 商品模型、角色、设备、复杂场景、动画模型

    这样分清楚,用起来心里就有数了。

    三、材质:同一个形状,为什么看起来完全不同

    在这里插入图片描述

    材质:给几何体穿上“外衣”

    简单来说,几何体决定了“这是什么形状”,材质决定了“这看起来像什么”。

    同一个球体,可以看起来像塑料球,也可以像金属球,还能变成一个不受光照影响的UI按钮。形状没变,但视觉感受完全不同——差别全在材质上。

    在ArkGraphics 3D的这篇案例里,我们主要用了两类材质:

    类型特点适用场景
    MaterialType.METALLIC_ROUGHNESS PBR金属粗糙度材质,受光照影响 商品、设备、写实模型
    MaterialType.UNLIT 不受光照影响,颜色稳定 坐标轴、标签、辅助线、UI类元素

    先看Unlit材质

    let material = await rf.createMaterial({ name: name }, MaterialType.UNLIT) as UnlitMaterial;
    material.baseColor = {
    image: null,
    factor: { x: r, y: g, z: b, w: 1 }
    };

    Unlit特别适合做辅助元素。实验台里的坐标轴就是这么处理的——原因很简单:坐标轴是给人看的参照物,不应该因为灯光角度变化就变暗。

    如果X轴时亮时暗,读者反而会怀疑坐标方向是不是出了问题。

    再看PBR材质

    let material: MetallicRoughnessMaterial =
    await rf.createMaterial(
    { name: this.nextResourceName(`${name}Pbr`) },
    MaterialType.METALLIC_ROUGHNESS
    ) as MetallicRoughnessMaterial;

    material.baseColor = {
    image: image,
    factor: { x: color.r, y: color.g, z: color.b, w: 1 }
    };

    PBR更适合表现真实物体。它会综合光源、环境光、表面粗糙度和金属度来决定最终效果。一个电商商品、一个车载模型、或者一个设备可视化对象,一般都不应该只用Unlit来应付。

    封装一个统一的创建方法

    实验台里封装了这样一个createMaterial:

    async createMaterial(rf: SceneResourceFactory, name: string, mode: string, textureMode: string): Promise<Material> {
    const color: ColorPreset = this.getColorPreset();
    const image: Image | null = textureMode === 'None' ? null : this.getTextureImage();
    if (mode === 'Unlit') {
    let material: UnlitMaterial =
    await rf.createMaterial({ name: this.nextResourceName(`${name}Unlit`) }, MaterialType.UNLIT) as UnlitMaterial;
    material.baseColor = {
    image: image,
    factor: { x: color.r, y: color.g, z: color.b, w: 1 }
    };
    return material;
    }

    let material: MetallicRoughnessMaterial =
    await rf.createMaterial({ name: this.nextResourceName(`${name}Pbr`) },
    MaterialType.METALLIC_ROUGHNESS) as MetallicRoughnessMaterial;
    material.baseColor = {
    image: image,
    factor: { x: color.r, y: color.g, z: color.b, w: 1 }
    };
    return material;
    }

    这段代码的核心不在于“封装了一个函数”,而是把材质的几个关键要素放在了一起:

    • 材质类型:PBR还是Unlit
    • 图片资源:有贴图还是没有
    • 颜色因子:baseColor.factor
    • 最终挂载:geometry.mesh.materialOverride

    运行时换材质

    用户点击按钮时,不需要重新加载整个场景,只需要创建一个新材质,然后替换当前Geometry的材质:

    this.previewGeometry.mesh.materialOverride = material;

    这个API值得留意。

    materialOverride可以理解成“运行时给几何体换一层皮肤”。在商品展示里,切换颜色、更换贴图、选中高亮,很多交互都可以从这里下手。

    如果每次换颜色都重新调用Scene.load(),用户体验会非常糟糕——页面闪烁、资源重复加载、内存也越来越难控制。用materialOverride就轻松得多。

    四、纹理与Image:图片到底是怎么贴到模型上的

    在这里插入图片描述

    说到贴图,很多人第一反应就是“图片绑定到模型上”。一张图片要真正变成模型表面的纹理,至少需要经过三步:

    createImage 创建图片资源
    -> material.baseColor.image 绑定到材质
    -> 模型通过 UV 把图片映射到表面

    加载图片资源

    本篇的 demo 里加载了两张图片:

    this.cubeImage = await rf.createImage({
    name: 'Article05CubeImage',
    uri: $rawfile('gltf/Cube/glTF/Cube_BaseColor.png')
    });

    this.helmetImage = await rf.createImage({
    name: 'Article05HelmetAlbedo',
    uri: $rawfile('gltf/DamagedHelmet/glTF/Default_albedo.jpg')
    });

    然后根据按钮状态决定用哪一张:

    getTextureImage(): Image | null {
    if (this.textureMode === 'CubeTexture') {
    return this.cubeImage;
    }
    if (this.textureMode === 'HelmetAlbedo') {
    return this.helmetImage;
    }
    return null;
    }

    真正把图片挂到材质上的是这个地方:

    material.baseColor = {
    image: image,
    factor: { x: color.r, y: color.g, z: color.b, w: 1 }
    };

    逻辑很简单:image 为 null 时,只显示 factor 对应的颜色;image 有值时,图片会作为 baseColor 贴图参与显示。

    UV:模型表面的坐标系统

    这里要提一个很实际的问题:贴图能不能正常显示,不只看图片有没有加载成功,还要看模型有没有合适的 UV。

    UV 可以理解成模型表面的二维坐标。GPU 要知道图片的哪一块对应模型的哪一个三角形,全靠 UV 来告诉它。

    所以遇到贴图问题的时候,可以先对照一下这个表:

    现象可能原因
    贴图完全没有出现 没有绑定 baseColor.image,或 Image 创建失败
    贴图出现但方向不对 UV 方向或导出设置有问题
    贴图被拉伸 UV 展开不合理
    球体贴图有接缝 球面 UV 天然容易出现接缝
    模型发黑 法线、光照、材质参数或颜色空间问题

    这也是为什么同一张头盔贴图贴到球体上,效果不一定好看。那张图片本来就是为 DamagedHelmet 模型的 UV 展开准备的。换到球体或立方体上,只能说明“材质绑定成功了”,但不能说明它适合做这个几何体的贴图。

    同一张图片,多处复用

    实验台里还做了一个贴图板:

    let boardMaterial: Material = await this.createMaterial(rf, 'TextureBoardMaterial', 'Unlit', 'CubeTexture');
    let boardGeometry = new CubeGeometry();
    boardGeometry.size = { x: 1.35, y: 0.05, z: 1.35 };
    let boardMesh = await rf.createMesh({ name: 'TextureBoardMesh' }, boardGeometry);
    let textureBoard: Geometry = await rf.createGeometry({ name: 'TextureBoard' }, boardMesh);
    textureBoard.mesh.materialOverride = boardMaterial;
    root.children.append(textureBoard);

    这个板子的作用是帮我们观察图片资源。它说明了一个道理:同一张 Image 可以喂给不同的 Material,也可以被不同的 Geometry 共用。

    这在真实业务里很常见。比如一个鞋子模型,鞋面、鞋带、鞋底可能是不同的 Geometry。你可以只换鞋面的贴图,不动鞋底;也可以让多个零件共用一张贴图集。

    纹理的成本

    纹理还有一个绕不开的问题:它吃内存。

    一张 2048 x 2048 的 RGBA 图片,未压缩时大约 16MB。一个模型如果带十几张大图,加载速度、内存、功耗都会受影响。移动端尤其要克制。

    几点实用建议:

    • 不要所有贴图都上 2K 或 4K
    • 同一张图不要重复创建资源
    • 不需要透明就别用透明图
    • 能用贴图集就尽量减少散图
    • 商品展示优先关注用户能看见的细节,背面隐藏区域没必要做高精度贴图

    性能优化后面会有专门的文章来讲,这里先记住一句话:纹理不是免费的,图片越大,渲染链路越重。

    五、glTF:资源进入工程之前,先把规范说清楚

    运行时创建 Cube 和 Sphere 用来教学很合适,但真实项目不可能只有基本几何体。

    商品、设备、车模、角色、场景,绝大多数都来自外部建模工具。对 ArkGraphics 3D 来说,glTF 是一条非常重要的资源入口。

    glTF 里有什么

    一个 glTF 文件通常包含这些内容:

    内容作用
    Scene 场景入口
    Node 节点层级
    Mesh 网格
    Material 材质
    Texture 纹理引用
    Image 图片
    Animation 动画
    Skin 骨骼蒙皮

    加载方式

    加载一个 glTF 场景,代码很简单:

    Scene.load($rawfile('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'))
    .then((scene: Scene) => {
    this.scene = scene;
    });

    但这篇的 demo 并没有直接把 DamagedHelmet 整个加载进来,而是单独拿走了它的 albedo 图片:

    this.helmetImage = await rf.createImage({
    name: 'Article05HelmetAlbedo',
    uri: $rawfile('gltf/DamagedHelmet/glTF/Default_albedo.jpg')
    });

    这样做的目的是把两个概念区分清楚:

    • Scene.load:整体加载场景、节点、网格、材质、贴图关系
    • createImage:单独创建图片资源,再手动绑定到材质

    真实项目里,两种方式都会用到。

    举个例子:一个电商 3D 商品页,美术交付的是完整 glTF。你可以先整体加载,让默认材质和贴图正常显示;用户选择不同颜色时,再找到对应部件,动态替换它的材质或贴图。

    推荐的工作流

    比较靠谱的流程大致是这样的:

    Blender / Maya 建模
    -> 展 UV
    -> 制作贴图
    -> 配置材质
    -> 导出 glTF / glb
    -> 放入 rawfile
    -> Scene.load 加载
    -> 代码查找节点
    -> 替换材质 / 播放动画 / 绑定交互

    开发和美术的协作规范

    这个流程里最怕的,是开发和美术之间没有约定好规范。

    比如美术说“模型好了”,开发一加载发现:

    • 模型太大或太小,根本看不见
    • 材质全丢了
    • 贴图路径还是美术电脑上的绝对路径
    • 节点命名全是 Cube.001、Sphere.002
    • 想单独换颜色的部件和其他部件合并成了一个网格

    这些问题不是 ArkGraphics 3D 独有的,所有实时 3D 项目都会遇到。

    建议提前约定好以下几件事:

    项目建议
    坐标轴 统一模型朝向,比如 Y 轴向上
    单位 统一尺寸单位,比如米或厘米
    节点命名 需要交互的部件用可读的英文命名
    材质命名 需要动态替换的材质单独命名
    贴图尺寸 不要无脑上 4K
    贴图路径 确保随 glTF 一起进入工程,不要用绝对路径
    部件拆分 需要单独替换材质的部件不要合并

    很多所谓的“代码问题”,查到最后发现其实是资源交付的问题。也是为了给后面讲工具链和性能打一个基础。

    六、案例实操:几何、材质、纹理如何协同工作

    回到 demo 本身。

    初始化场景

    我们从空场景开始:

    Scene.load()
    .then(async (result: Scene) => {
    this.scene = result;
    this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
    this.scene.environment.indirectDiffuseFactor = this.makeVec4(0.20, 0.20, 0.20, 1);
    this.scene.environment.indirectSpecularFactor = this.makeVec4(0.12, 0.12, 0.12, 1);

    let rf: SceneResourceFactory = this.scene.getResourceFactory();
    this.resourceFactory = rf;

    await this.createCameraAndLight(rf);
    await this.loadImages(rf);
    await this.createSceneContent(rf);
    await this.rebuildPreview();

    this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
    });

    这里没有直接加载 glTF,因为我们要手动搭建一条完整的资源链路。

    场景树结构的延续

    预览模型挂在 previewRoot 下面:

    this.previewRoot = await rf.createNode({ name: 'Article05PreviewRoot' });
    root.children.append(this.previewRoot);

    自动旋转时,旋转的是 previewRoot:

    this.animator.onFrame = () => {
    if (!this.running || !this.previewRoot) {
    return;
    }
    this.rotationDegree = (this.rotationDegree + 0.8) % 360;
    this.previewRoot.rotation = this.makeYRotation(this.rotationDegree);
    };

    这延续了第四篇的场景树思路:父节点负责稳定的控制逻辑,子节点可以随时替换。立方体换成球体,旋转逻辑完全不用动。

    几何体切换

    几何体的切换通过 rebuildPreview 实现:

    let oldPreviewGeometry: Geometry | null = this.previewGeometry;
    let material: Material = await this.createMaterial(rf, 'Article05PreviewMaterial', this.materialMode, this.textureMode);
    let previewGeometry: Geometry;

    if (this.geometryMode === 'Sphere') {
    let sphere = new SphereGeometry();
    sphere.radius = 0.72;
    sphere.segmentCount = 36;
    let sphereMesh = await rf.createMesh({ name: this.nextResourceName('Article05SphereMesh') }, sphere);
    previewGeometry = await rf.createGeometry({ name: this.nextResourceName('Article05Sphere') }, sphereMesh);
    } else {
    let cube = new CubeGeometry();
    cube.size = { x: 1.25, y: 1.25, z: 1.25 };
    let cubeMesh = await rf.createMesh({ name: this.nextResourceName('Article05CubeMesh') }, cube);
    previewGeometry = await rf.createGeometry({ name: this.nextResourceName('Article05Cube') }, cubeMesh);
    }

    this.previewGeometry = previewGeometry;
    previewGeometry.position = { x: 0.45, y: 0.05, z: 0 };
    previewGeometry.mesh.materialOverride = material;
    previewRoot.children.append(previewGeometry);

    if (oldPreviewGeometry) {
    previewRoot.children.remove(oldPreviewGeometry);
    }

    这里有一个值得注意的细节:先添加新节点,再删除旧节点。

    如果反过来,先删旧节点再异步创建新节点,中间会出现短暂的空窗期,画面可能会闪一下。实验台里按钮点得频繁,这种体验问题会被放大。

    材质切换

    材质切换则不需要重建几何体:

    async refreshPreviewMaterial(): Promise<void> {
    let rf: SceneResourceFactory | null = this.resourceFactory;
    if (!rf || !this.previewGeometry) {
    return;
    }
    let material: Material = await this.createMaterial(rf, 'Article05PreviewMaterial', this.materialMode, this.textureMode);
    this.previewGeometry.mesh.materialOverride = material;
    }

    按钮逻辑的分工

    按钮的处理逻辑也按照这个原则分开:

    setChoice(group: string, value: string): void {
    if (this.isChoiceSelected(group, value)) {
    return;
    }

    if (group === 'geometry') {
    this.geometryMode = value;
    this.updateResourceText();
    this.rebuildPreviewLater();
    return;
    }

    if (group === 'material') {
    this.materialMode = value;
    } else {
    this.textureMode = value;
    }
    this.updateResourceText();
    this.refreshPreviewMaterialLater();
    }

    这段代码反映的不是 UI 技巧,而是资源操作的判断原则:

    变化的内容操作方式
    几何体变了 重建 Geometry
    材质类型变了 替换 Material
    贴图变了 替换 Material
    颜色变了 替换 Material

    状态展示

    实验台还会把当前状态直接打印出来:

    this.resourceText =
    `Geometry: ${this.geometryMode}Geometry -> createMesh -> createGeometry\\n` +
    `Material: MaterialType.${this.materialMode === 'Unlit' ? 'UNLIT' : 'METALLIC_ROUGHNESS'}\\n` +
    `Texture: ${textureText}\\n` +
    `Override: geometry.mesh.materialOverride`;

    举个例子,点击球体按钮,你应该看到:

    Geometry: SphereGeometry -> createMesh -> createGeometry

    点击 Unlit 材质:

    Material: MaterialType.UNLIT

    点击头盔贴图:

    Texture: baseColor.image = HelmetAlbedo

    通过这种方式,你可以清楚地看到每一步操作对应的是哪一层资源的变化,而不是被一个黑盒框架包在里头。

    七、常见坑点与本篇小结

    这一篇聊的是资源体系,最后把几个容易踩的坑汇总一下。

    Mesh 本质上是个资源,Geometry 才是能挂到场景树里的可见节点。两者的关系要分清楚:

    let mesh = await rf.createMesh({ name: 'mesh' }, cube);
    let node = await rf.createGeometry({ name: 'node' }, mesh);
    root.children.append(node);

    颜色和贴图的变化,绝大多数情况都属于材质层面的变化,直接替换 materialOverride 就好,不需要重建几何体:

    geometry.mesh.materialOverride = newMaterial;

    本篇小结

    这一篇真正需要记住的东西,其实不多:

    • Geometry:负责进入场景树,成为可见节点
    • Mesh:负责几何形状数据
    • Material:负责表面表现
    • Image:负责图片资源
    • baseColor.image:负责贴图
    • baseColor.factor:负责颜色因子
    • materialOverride:运行时换材质的关键入口

    走到这里,这个系列已经从“让立方体跑起来”,推进到了“能拆清楚一个 3D 对象到底由哪些资源组成”。

    因为当几何、材质、纹理都能被正确组织之后,下一个自然的问题就是:这些对象怎么动起来,动画数据又是如何驱动节点属性变化的。

    赞(0)
    未经允许不得转载:171主机测评 » HarmonyOS 6 ArkGraphics 3D 精讲:几何体、材质与纹理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址