鸿蒙开发-想给3D模型换材质?Resources材质与贴图设置
ArkGraphics 3D资源系统详解:本文介绍了3D开发中的核心资源类型,包括材质、着色器、图片和采样器等。重点解析了四种材质类型:着色器材质(完全自定义)、PBR金属-粗糙度材质(真实物理效果)、Unlit材质(不受光照影响)和遮挡材质(AR场景专用)。通过SceneResourceFactory可创建各类资源,其中材质决定了物体表面质感,着色器控制渲染逻辑。文章还提供了资源创建代码示例和系
给 3D 物体换皮肤:材质、图片、着色器这些资源怎么用
你有没有在游戏里见过那种金属质感的盔甲、布料质感的衣服、或者玻璃质感的酒杯?这些不同的"皮肤"效果,在 3D 开发里叫做"材质(Material)"。材质决定了一个物体看起来是什么质感——是光滑的还是粗糙的,是金属的还是塑料的,是会发光的还是暗淡的。
这篇文章就来聊聊 ArkGraphics 3D 里的资源系统,包括材质、图片、着色器、采样器这些核心资源类型,以及怎么创建和使用它们。
ArkGraphics 3D 资源系统架构
下面是 ArkGraphics 3D 资源系统的整体架构:
资源系统的整体结构
在 ArkGraphics 3D 里,所有资源都继承自 SceneResource 这个基类。它有几个通用属性:
- name:资源名称,你自己起的,用来标识这个资源
- resourceType:资源类型,只读,是一个枚举值
- uri:资源文件路径,只读
资源类型用 SceneResourceType 枚举来区分:NODE(节点)、ENVIRONMENT(环境)、MATERIAL(材质)、MESH(网格)、ANIMATION(动画)、SHADER(着色器)、IMAGE(图片)、MESH_RESOURCE(网格资源)、EFFECT(特效)。
所有资源都可以通过 destroy() 方法来释放。记住,一旦释放就不能再用了。
材质:物体的"皮肤"
材质是最核心的资源类型之一。ArkGraphics 3D 支持四种材质类型,用 MaterialType 枚举来区分:
- SHADER(1):着色器材质,完全由自定义着色器控制渲染效果
- METALLIC_ROUGHNESS(2):金属-粗糙度材质,基于物理渲染(PBR)模型,最常用的材质类型
- UNLIT(3):不受光照影响的材质,适合做 UI 元素或特效
- OCCLUSION(4):遮挡材质,能遮挡其他物体但不遮挡环境
创建材质
通过 SceneResourceFactory 的 createMaterial 方法来创建材质:
import { MaterialType, Material, SceneResourceParameters, SceneResourceFactory, Scene } from '@kit.ArkGraphics3D';
function createMaterialPromise() : Promise<Material> {
return new Promise((resolve, reject) => {
// 加载场景资源,支持.gltf和.glb格式,路径和文件名可根据项目实际资源自定义
let scene: Promise<Scene> = Scene.load($rawfile("gltf/CubeWithFloor/glTF/AnimatedCube.glb"));
scene.then(async (result: Scene) => {
let sceneFactory: SceneResourceFactory = result.getResourceFactory();
let sceneMaterialParameter: SceneResourceParameters = { name: "material" };
// 创建材质
let material: Material = await sceneFactory.createMaterial(sceneMaterialParameter, MaterialType.SHADER);
resolve(material);
}).catch((error: Error) => {
console.error('Scene load failed:', error);
reject(error);
});
});
}
这里创建的是一个着色器材质(MaterialType.SHADER)。SceneResourceParameters 只需要一个 name,用来标识这个材质资源。
Material 的通用属性
不管什么类型的材质,都有一些通用属性:
- materialType:材质类型,只读
- shadowReceiver:是否接收阴影,默认 false
- cullMode:剔除模式,控制是否剔除正面或背面的面片。默认是
BACK(剔除背面),这在大多数情况下是正确的——你从外面看一个立方体,不需要渲染它内部的面 - blend:透明效果,
{ enabled: true }开启透明 - alphaCutoff:透明通道阈值,低于这个阈值的像素不渲染
- renderSort:渲染排序,控制不同物体的绘制先后顺序
- polygonMode:多边形绘制模式,
FILL(填充)、LINE(线框)、POINT(只画顶点)
PBR 金属-粗糙度材质
这是最常用的材质类型,能模拟金属、塑料、布料等各种真实质感。它继承自 Material,有以下属性:
- baseColor:基础颜色,决定物体在没有光照时的颜色
- normal:法线贴图,让物体表面看起来有凹凸细节,但实际几何形状不变
- material:金属材质参数,包含粗糙度、金属度、反射度
- ambientOcclusion:环境光遮蔽贴图,模拟凹陷处的阴影
- emissive:自发光颜色
- clearCoat:透明图层,类似车漆效果
- clearCoatRoughness:透明图层粗糙度
- clearCoatNormal:透明图层法线贴图
- sheen:微纤维光泽,适合布料材质
- specular:非金属的高光反射
每个属性都是 MaterialProperty 类型,包含三个字段:
- image:纹理贴图(Image 类型或 null)
- factor:属性因子(Vec4 类型),当没有纹理贴图时,用这个值作为默认属性
- sampler:采样器,控制纹理的采样方式
简单说,factor 是"纯色模式",image 是"贴图模式"。比如你要做一个纯红色的塑料球,只需要把 baseColor 的 factor 设为红色就行;如果你要做一个有木纹的桌面,就需要给 baseColor 的 image 指定一张木纹贴图。
着色器材质
着色器材质给你最大的自由度——你可以完全控制渲染逻辑。它只有一个属性:
- colorShader:着色器对象
Unlit 材质和遮挡材质
UnlitMaterial(API 23+)不受光照影响,只有一个 baseColor 属性。适合做 UI 上的 3D 元素,或者你想让某个物体始终保持恒定亮度的场景。
OcclusionMaterial(API 23+)是一个特殊材质,它能遮挡场景中的其他物体,但不会遮挡环境背景。典型的用途是做 AR 场景中的"遮挡体"——比如虚拟家具放在真实地面上,地面需要用遮挡材质来挡住虚拟家具的底部。
着色器:渲染的灵魂
着色器(Shader)是一段运行在 GPU 上的程序,它决定了物体最终呈现的样子。在 ArkGraphics 3D 里,着色器通过 .shader 文件来定义。
创建着色器
import { Shader, SceneResourceParameters, SceneResourceFactory, Scene } from '@kit.ArkGraphics3D';
function createShaderPromise(): Promise<Shader> {
return new Promise((resolve, reject) => {
// 加载场景资源,支持.gltf和.glb格式,路径和文件名可根据项目实际资源自定义
let scene: Promise<Scene> = Scene.load($rawfile("gltf/CubeWithFloor/glTF/AnimatedCube.glb"));
scene.then(async (result: Scene) => {
let sceneFactory: SceneResourceFactory = result.getResourceFactory();
// 创建shader资源(通过SceneResourceParameters配置),路径和文件名可根据项目实际资源自定义
let sceneResourceParameter: SceneResourceParameters = { name: "shaderResource",
uri: $rawfile("shaders/custom_shader/custom_material_sample.shader") };
let shader: Shader = await sceneFactory.createShader(sceneResourceParameter);
resolve(shader);
}).catch((error: Error) => {
console.error('Scene load failed:', error);
reject(error);
});
});
}
这次 SceneResourceParameters 需要指定 uri,指向你的 .shader 文件。
设置着色器输入
着色器需要外部传入一些参数(uniform),比如时间、颜色、纹理等。你可以通过 inputs 属性或 setShaderInputs 方法来设置:
import { Image, MaterialType, Scene, SceneResourceFactory, Shader, ShaderMaterial } from '@kit.ArkGraphics3D';
function setinputs(): void {
// 加载场景资源,支持.gltf和.glb格式,路径和文件名可根据项目实际资源自定义
let scene: Promise<Scene> = Scene.load($rawfile("gltf/CubeWithFloor/glTF/AnimatedCube.glb"));
scene.then(async (result: Scene) => {
if (result) {
let rf : SceneResourceFactory | null = await result.getResourceFactory();
if (!rf) {
return;
}
// 创建材质和shader
let material: ShaderMaterial | null = await rf.createMaterial({name: "CustomMaterial"}, MaterialType.SHADER);
let shader : Shader | null = await rf.createShader(
{name: "CustomShader", uri: $rawfile("shaders/custom_shader/custom_material_sample.shader")});
if (!material || !shader) {
return;
}
// 加载纹理资源
let image : Image | null = await rf.createImage({name: "envImg", uri: $rawfile("custom_image.jpg")});
if (!image) {
return;
}
// 绑定shader到纹理上
material.colorShader = shader;
// 设置shader输入
material.colorShader.setShaderInputs({
"uTime": 1.0,
"uVelocity": {x: 1.0, y: 1.0, z:-1.0, w:-1.0},
"uTexture": image
})
}
});
}
这段代码展示了一个完整的流程:创建着色器材质、创建着色器、加载图片纹理、把着色器绑定到材质上、设置着色器输入。
setShaderInputs 接受一个键值对对象,值可以是 number、Vec2、Vec3、Vec4 或 Image。注意 setShaderInputs 方法(API 23+)比直接设置 inputs 属性性能更好,推荐使用。
图片资源
图片(Image)在 3D 场景中用途广泛——贴图、环境贴图、UI 纹理等。
import { Image, SceneResourceParameters, Scene, RenderContext, RenderResourceFactory } from '@kit.ArkGraphics3D';
function createImageResource(): Promise<Image> {
const renderContext: RenderContext | null = Scene.getDefaultRenderContext();
if (!renderContext) {
return Promise.reject(new Error("RenderContext is null"));
}
const renderResourceFactory: RenderResourceFactory = renderContext.getRenderResourceFactory();
// 加载图片资源,路径和文件名可根据项目实际资源自定义
let imageParams: SceneResourceParameters = {
name: "sampleImage",
uri: $rawfile("image/Cube_BaseColor.png")
};
return renderResourceFactory.createImage(imageParams);
}
Image 有两个只读属性:width(宽度)和 height(高度),单位都是像素。
注意这里用的是 RenderResourceFactory 而不是 SceneResourceFactory。两者都可以创建图片资源,区别是 RenderResourceFactory 创建的资源可以在多个场景之间共享。
采样器:控制纹理的采样方式
采样器(Sampler)决定了纹理在被放大或缩小时怎么处理像素。
import { SceneResourceParameters, Scene, RenderContext, RenderResourceFactory, Sampler } from '@kit.ArkGraphics3D';
function createSamplerResource(): Promise<Sampler> {
const renderContext: RenderContext | null = Scene.getDefaultRenderContext();
if (!renderContext) {
return Promise.reject(new Error("RenderContext is null"));
}
const renderResourceFactory: RenderResourceFactory = renderContext.getRenderResourceFactory();
// 加载图片资源,路径和文件名可根据项目实际资源自定义
let samplerParams: SceneResourceParameters = {
name: "sampler1",
uri: $rawfile("image/Cube_BaseColor.png")
};
return renderResourceFactory.createSampler(samplerParams);
}
Sampler 的属性包括:
- magFilter:放大过滤模式,纹理被放大时怎么采样
- minFilter:缩小过滤模式,纹理被缩小时怎么采样
- mipMapMode:mipmap 过滤模式,多级纹理之间的采样方式
- addressModeU:U 方向(水平)的寻址模式
- addressModeV:V 方向(垂直)的寻址模式
过滤模式有两种:
- NEAREST(0):最近邻插值,速度快但可能有锯齿
- LINEAR(1):线性插值,效果平滑但性能略低
寻址模式有三种:
- REPEAT(0):重复平铺,坐标超出 [0,1] 时纹理会重复
- MIRRORED_REPEAT(1):镜像重复
- CLAMP_TO_EDGE(2):边缘拉伸
默认值都是 LINEAR 和 REPEAT,对大多数场景来说够用了。
网格资源
网格(MeshResource)定义了物体的几何形状。你可以通过代码来创建自定义网格:
import { SceneResourceParameters, Scene, CustomGeometry, PrimitiveTopology, RenderContext, RenderResourceFactory,
MeshResource } from '@kit.ArkGraphics3D';
function createMeshResource(): Promise<MeshResource> {
const renderContext: RenderContext | null = Scene.getDefaultRenderContext();
if (!renderContext) {
return Promise.reject(new Error("RenderContext is null"));
}
const renderResourceFactory: RenderResourceFactory = renderContext.getRenderResourceFactory();
const geometry = new CustomGeometry();
geometry.vertices = [
{ x: 0, y: 0, z: 0 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 1, z: 0 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 1 },
{ x: 0, y: 1, z: 1 }
];
geometry.indices = [
0, 1, 2, 2, 3, 0, // front
4, 5, 6, 6, 7, 4, // back
0, 4, 5, 5, 1, 0, // bottom
1, 5, 6, 6, 2, 1, // right
3, 2, 6, 6, 7, 3, // top
3, 7, 4, 4, 0, 3 // left
];
geometry.topology = PrimitiveTopology.TRIANGLE_LIST;
geometry.normals = [
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: 1 }
];
geometry.uvs = [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 1, y: 1 },
{ x: 0, y: 1 },
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 1, y: 1 },
{ x: 0, y: 1 }
];
geometry.colors = [
{ r: 1, g: 0, b: 0, a: 1 },
{ r: 0, g: 1, b: 0, a: 1 },
{ r: 0, g: 0, b: 1, a: 1 },
{ r: 1, g: 1, b: 0, a: 1 },
{ r: 1, g: 0, b: 1, a: 1 },
{ r: 0, g: 1, b: 1, a: 1 },
{ r: 1, g: 1, b: 1, a: 1 },
{ r: 0, g: 0, b: 0, a: 1 }
];
// 加载图片资源,路径和文件名可根据项目实际资源自定义
let sceneResourceParameter: SceneResourceParameters = {
name: "cubeMesh",
uri: $rawfile("image/Cube_BaseColor.png")
};
return renderResourceFactory.createMesh(sceneResourceParameter, geometry);
}
这段代码用 CustomGeometry 手动定义了一个立方体的几何数据:
- vertices:8 个顶点的坐标
- indices:顶点索引,每 3 个一组构成一个三角形(一个立方体 6 个面,每个面 2 个三角形,共 12 个三角形,36 个索引)
- topology:图元拓扑,
TRIANGLE_LIST表示每 3 个顶点构成一个独立的三角形 - normals:法线向量,用于光照计算
- uvs:UV 坐标,用于纹理映射
- colors:顶点颜色
除了 CustomGeometry,ArkGraphics 3D 还提供了几种内置几何体:
- CubeGeometry:立方体,只需指定
size(宽高深) - PlaneGeometry:平面,只需指定
size(宽高) - SphereGeometry:球体,需要指定
radius(半径)和segmentCount(分段数) - CylinderGeometry(API 23+):圆柱体,需要指定
radius、height和segmentCount
用内置几何体比手动定义顶点简单得多:
import { SceneResourceFactory, Scene, Geometry, CubeGeometry } from '@kit.ArkGraphics3D';
function createGeometryPromise() : Promise<Geometry> {
return new Promise((resolve, reject) => {
let scene: Promise<Scene> = Scene.load();
scene.then(async (result: Scene | undefined) => {
if (!result) {
return;
}
let sceneFactory: SceneResourceFactory = result.getResourceFactory();
// 创建立方体几何数据
let cubeGeom = new CubeGeometry();
cubeGeom.size = { x: 1, y: 1, z: 1 };
// 根据立方体几何数据创建网格资源
let meshRes = await sceneFactory.createMesh({ name: "MeshName" }, cubeGeom);
console.info("TEST createGeometryPromise");
// 根据场景节点参数和网格资源创建几何对象
let geometry: Geometry = await sceneFactory.createGeometry({ name: "GeometryName" }, meshRes);
resolve(geometry);
}).catch((error: Error) => {
console.error('Scene load failed:', error);
reject(error);
});
});
}
这里先创建一个 1x1x1 的立方体几何定义,然后用它创建网格资源,最后创建几何节点。几何节点是可以直接放到场景树里的。
环境:场景的光照和背景
环境(Environment)控制场景的整体光照和背景。
import { Environment, SceneResourceParameters, SceneResourceFactory, Scene } from '@kit.ArkGraphics3D';
function createEnvironmentPromise(): Promise<Environment> {
return new Promise((resolve, reject) => {
// 加载场景资源,支持.gltf和.glb格式,路径和文件名可根据项目实际资源自定义
let scene: Promise<Scene> = Scene.load($rawfile("gltf/CubeWithFloor/glTF/AnimatedCube.glb"));
scene.then(async (result: Scene) => {
let sceneFactory: SceneResourceFactory = result.getResourceFactory();
// 加载环境贴图资源,路径和文件名可根据项目实际资源自定义
let sceneEnvironmentParameter: SceneResourceParameters = { name: "env", uri: $rawfile("KTX/quarry_02_2k_radiance.ktx") };
// 创建Environment
let env: Environment = await sceneFactory.createEnvironment(sceneEnvironmentParameter);
resolve(env);
}).catch((error: Error) => {
console.error('Scene load failed:', error);
reject(error);
});
});
}
Environment 的属性包括:
- backgroundType:背景类型,可以是无背景、图片背景、立方体贴图背景或等距柱状投影背景
- indirectDiffuseFactor:间接散射系数
- indirectSpecularFactor:间接反射系数
- environmentMapFactor:环境贴图系数
- environmentImage:环境图片
- radianceImage:辐射图片
- irradianceCoefficients:辐射系数
- environmentRotation(API 23+):环境光旋转,接收归一化四元数
环境贴图通常用 .ktx 格式的 HDR 图片,它包含了场景周围的光照信息,PBR 材质会用这些信息来做反射和全局光照计算。
动画资源
如果 glTF 模型里带有动画数据,加载后会自动解析到 Scene.animations 数组里。每个 Animation 对象有以下属性:
- enabled:是否启用
- speed:播放速度,默认 1.0,负值表示反向播放
- duration:动画时长(秒)
- running:是否正在播放
- progress:播放进度 [0, 1]
控制动画的方法有:start()、stop()、pause()、restart()、seek(position)、finish()。还有两个回调:onStarted 和 onFinished,分别在动画开始和结束时触发。
形变器(Morpher)
形变器(API 20+)用于控制模型的形变效果,比如让人脸做出不同的表情。它有一个 targets 属性,是一个键值对,键是形变目标的名称,值是权重(通常在 0 到 1 之间)。
PBR 材质属性配置流程
下面是 PBR 金属-粗糙度材质的属性配置流程:
小结
ArkGraphics 3D 的资源系统层次清晰:
- 材质决定物体的外观质感,PBR 材质最常用,着色器材质最灵活
- 着色器是 GPU 上运行的程序,控制渲染的每一个像素
- 图片是纹理的载体,可以给材质贴上各种图案
- 采样器控制纹理的采样精度和重复方式
- 网格定义物体的几何形状,可以用内置几何体或自定义顶点
- 环境控制场景的整体光照和背景
这些资源通过 SceneResourceFactory 或 RenderResourceFactory 来创建,通过 SceneResourceParameters 来配置参数。
下一篇文章我们来聊聊 3D 开发中最基础的数据类型——向量和四元数。搞懂这些,你才能真正理解 3D 空间中的位置、方向和旋转。
更多推荐


所有评论(0)