跳到主要内容

创建并使用材质资源

材质(Material):材质是用于定义物体表面视觉效果的重要资源。材质决定了物体如何与光线交互,从而影响其最终的渲染效果,如颜色、金属感、粗糙度等外观属性。

ArkGraphics 3D采用基于物理的渲染(PBR, Physically-Based Rendering)模型,其材质实现遵循通用的PBR原理。开发者既可以使用标准材质快速实现真实感效果,也可以通过自定义Shader材质灵活控制渲染逻辑。

基本概念

着色器(Shader):着色器是GPU上可以执行的一段程序,可以控制GPU执行哪些并行计算操作。AGP引擎提供的默认着色器实现了PBR渲染,开发者只需要指定对应的参数就可以完成不同的PBR渲染。

ArkGraphics 3D支持开发者创建自定义的着色器,开发者可以通过自定义着色器自定义渲染计算过程,完全控制渲染计算流程,比如控制某物体不受某光源的影响、自定义边缘描边、高亮效果等个性化视觉呈现。

着色器通常配合MaterialType.SHADER材质使用,是实现个性化渲染的重要手段。其创建依赖名称及沙箱路径,创建后可绑定至材质上,替代默认的渲染行为。

材质类型(MaterialType)

ArkGraphics 3D中的材质类型通过MaterialType枚举指定,目前支持以下两种类型:

  • MaterialType.SHADER:基于Shader(着色器)的材质类型,支持绑定自定义着色器,开发者可通过自定义渲染程序实现个性化的视觉表现,适用于高级图形渲染需求。
  • MaterialType.METALLIC_ROUGHNESS:基于金属-粗糙度模型的标准PBR材质类型,符合glTF材质规范,适合快速构建真实感渲染效果,支持设置基础色、金属度、粗糙度、法线贴图等常见属性。

材质的创建一般通过SceneResourceFactory.createMaterial()方法完成,需指定材质名称和类型。不同类型的材质支持不同的参数配置,开发者可按需选择以实现预期的渲染效果。

材质的属性

材质的属性定义了其在渲染时的视觉表现行为,例如颜色、金属感、粗糙度、光照响应以及透明度控制等。通过设置这些属性,开发者可以精准控制物体在场景中的外观效果。

在ArkGraphics 3D中,材质属性设计既提供了统一的基础能力,也根据材质类型提供了差异化扩展,满足从基础场景搭建到高级视觉定制的多样需求。

通用属性

所有材质均具备以下基础属性(如materialType、shadowReceiver、blend等)可通过设置Material类型对象实现,用于控制材质的类型及其渲染基础行为:

  • materialType:材质类型,标识该材质是标准PBR材质还是基于Shader的自定义材质。

    适用场景:当需要快速使用预设真实感材质时选择PBR材质,需实现个性化或特殊渲染效果时选择Shader材质。

  • shadowReceiver:材质是否可以接收场景中的阴影投射,true表示可以接收,false表示不能接收,默认为false。

    适用场景:需要表现阴影效果的物体,如地面、墙体、角色等开启;纯发光物体或不参与阴影计算的可关闭以优化性能。

  • cullMode:剔除模式,决定是否剔除背面几何体,默认值为BACK,即剔除背面。

    适用场景:普通实体模型一般开启剔除背面提升渲染效率;透明或双面材质(如树叶、布料)需要禁用剔除以显示完整模型。

  • blend:是否启用材质的透明效果模式。true表示开启透明,false表示关闭透明,默认值为false。

    适用场景:表现透明或半透明材质时开启,如玻璃、水面、烟雾、透明塑料等。

  • alphaCutoff:透明度阈值,取值范围[0,1],默认值为1。像素的alpha值低于该阈值时不进行渲染,用于实现透明裁剪效果。

    适用场景:需要硬透明裁剪的材质,比如树叶、铁丝网、布料边缘等带有透明区域但无半透明渐变的模型。通过设置阈值,可以快速裁剪掉透明部分像素。

  • renderSort:渲染排序设置,用于控制材质在渲染队列中的渲染顺序,确保透明或特殊效果材质正确叠加显示。

    适用场景:多重透明材质、叠加特效、UI元素等需要严格渲染顺序的场景。

PBR材质属性

符合glTF标准的基于物理渲染(PBR)的金属-粗糙度材质,通过设置MetallicRoughnessMaterial实现,其中各项属性采用MaterialProperty类型封装,支持绑定纹理和设置因子(factor)值。具体属性包括:

  • baseColor:基础颜色和透明度,包含纹理及对应因子,用于定义材质表面的主色调。

    适用场景:所有材质的基本颜色设置,适用于任何物体表面,尤其是需要表现颜色和透明度的模型。

  • normal:法线贴图,用于模拟表面细节凹凸,增强光照效果的真实感。

    适用场景:需要表现表面细节如石材纹理、皮肤毛孔、木纹凹凸等,提升真实感的模型。

  • material:金属度、粗糙度与反射率参数,描述材质表面的光学特性。

    适用场景:区分金属和非金属表面,控制材质的光滑或粗糙程度,常用于金属件、塑料、橡胶等多种材质。

  • ambientOcclusion:环境光遮蔽贴图,提升材质细节处的阴影层次和真实感。

    适用场景:增强模型细节阴影效果,适合复杂结构或细节丰富的物体,如建筑物裂缝、机械零件缝隙。

  • emissive:自发光颜色及纹理,表达材质自发光的效果。

    适用场景:灯光、屏幕、发光标志、荧光材料等需要表现光源或自发光效果的材质。

  • clearCoat:清漆层强度,模拟车漆等具有透明反光层的材质。

    适用场景:汽车车身、家具表面等有光泽涂层的材质,表现透明光泽和反射。

  • clearCoatRoughness:清漆层的粗糙度,控制清漆层的反光细节。

    适用场景:配合clearCoat使用,调节涂层表面的光滑度或粗糙感。

  • clearCoatNormal:清漆层法线贴图,增强清漆层的光照变化。

    适用场景:清漆层带有细节纹理时使用,增加涂层的真实光照反馈。

  • sheen:微纤维漫反射层,用于表现布料、织物的光泽感。

    适用场景:衣物、窗帘、沙发等纺织品,表现柔和的光泽和细腻质感。

  • specular:镜面反射属性,控制非金属材质的高光反射强度。

    适用场景:玻璃、水面、塑料等非金属材质的高光表现,增强材质的镜面反射效果。

创建Shader材质并设置属性

在需要自定义渲染逻辑或实现特殊视觉效果时,可以通过MaterialType.SHADER类型创建Shader材质。Shader材质支持绑定自定义的.shader文件,开发者可以在其中编写自定义的渲染计算逻辑,灵活控制模型的外观表现,实现如描边、高光、发光等个性化视觉效果。以下示例展示了Shader材质的创建与使用流程,包括场景加载、Shader资源创建与绑定,以及将Shader材质应用到目标几何体节点的过程。

  1. 导入相关模块。

    在页面脚本中导入ArkGraphics 3D提供的核心类型,用于创建Shader材质及绑定Shader资源。

    import { Camera, Environment, Geometry, Image, Material, MaterialType, Scene, SceneResourceFactory,
    SceneResourceParameters, Shader, ShaderMaterial, EnvironmentBackgroundType } from '@kit.ArkGraphics3D';
  2. 加载场景并设置渲染参数。

    调用Scene.load()方法加载.glb或.gltf格式的模型文件,并在加载完成后获取Scene对象。随后构建SceneOptions对象,指定场景及渲染模式,用于后续通过Component3D将场景内容渲染到界面中。

    if (this.scene === null) {
    // Switched from .gltf to .glb; same content, different format
    Scene.load($rawfile('gltf/CubeWithFloor/glTF/AnimatedCube.glb'))
    .then(async (result: Scene) => {
    // Assign loaded scene to globalScene for unified resource creation
    globalScene = result;
    this.scene = result;
    this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
    this.rf = this.scene.getResourceFactory();
    // ...
    })
    .catch((error: string) => {
    console.error('init error: ' + error + '.');
    });
    }
  3. 初始化相机。

    创建相机对象并设置相机启用状态与观察位置,用于后续展示模型。

    this.cam = await this.rf.createCamera({ 'name': 'Camera1' });
    this.cam.enabled = true;
    this.cam.position.z = 5;
  4. 获取几何体节点。

    通过Scene.getNodeByPath()方法获取目标模型的几何体(Geometry)节点,并记录其原始材质,以便在后续修改材质后可进行回退或恢复操作。

    this.geom = this.scene.getNodeByPath('rootNode_/Unnamed Node 1/AnimatedCube') as Geometry;

    // record original material
    this.originalMat = this.geom.mesh.subMeshes[0].material;
  5. 创建Shader材质(空白)。

    调用SceneResourceFactory.createMaterial()创建Shader类型的空白材质,为后续绑定自定义Shader做准备。

    function createMaterialPromise(): Promise<Material> {
    return new Promise((resolve, reject) => {
    // Ensure the scene is loaded before accessing sceneFactory
    if (globalScene) {
    let sceneFactory: SceneResourceFactory = globalScene.getResourceFactory();
    let sceneMaterialParameter: SceneResourceParameters = { name: 'material' };
    // Create Material
    let material: Promise<Material> = sceneFactory.createMaterial(sceneMaterialParameter, MaterialType.SHADER);
    material.then(resolve)
    .catch((err: string) => {
    console.error('Blank material create failed: ' + err);
    reject(err);
    });
    } else {
    reject('Scene is not loaded yet.');
    }
    });
    }
  6. 创建并绑定Shader资源。

    通过SceneResourceFactory.createShader()创建自定义着色器资源,并将其绑定到Shader材质上,实现自定义渲染逻辑。

    function createShaderPromise(): Promise<Shader> {
    return new Promise((resolve, reject) => {
    // Ensure the scene is loaded before accessing sceneFactory
    if (globalScene) {
    let sceneFactory: SceneResourceFactory = globalScene.getResourceFactory();

    // Create a SceneResourceParameters object and use it to create a shader
    let sceneResourceParameter: SceneResourceParameters = {
    name: 'shaderResource',
    uri: $rawfile('shaders/custom_shader/custom_material_sample.shader')
    };

    let shader: Promise<Shader> = sceneFactory.createShader(sceneResourceParameter);
    shader.then((shaderEntity: Shader) => {
    resolve(shaderEntity);
    }).catch((err: string) => {
    console.error('Shader load failed: ' + err + '.');
    reject(err);
    });
    } else {
    reject('Scene is not loaded yet.');
    }
    });
    }
  7. 应用Shader材质到几何体节点。

    通过按钮点击事件调用不同的函数,可在运行时动态切换模型的材质,实现从默认材质到Shader材质的过渡效果。

    Button('Replace with a blank material')
    // ...
    .onClick(async (): Promise<void> => {
    console.info('Start to replace with a blank material');

    if (!this.blankMat) {
    this.blankMat = await createMaterialPromise();
    }

    if (!this.scene || !this.rf) {
    return;
    }

    this.geom = this.scene.getNodeByPath('rootNode_/Unnamed Node 1/AnimatedCube') as Geometry;

    this.geom.mesh.materialOverride = undefined;
    if (this.blankMat) {
    this.geom.mesh.subMeshes[0].material = this.blankMat;
    }

    });

    Button('Replace with a Shader material')
    // ...
    .onClick(async (): Promise<void> => {
    console.info('Start to replace with a shader material');

    if (!this.shader) {
    this.shader = await createShaderPromise();
    }

    if (!this.scene || !this.rf) {
    return;
    }

    if (!this.shaderMat) {
    let rf = this.scene.getResourceFactory();
    this.shaderMat = await rf.createMaterial({ name: 'shaderMat' }, MaterialType.SHADER);
    }

    if (this.shader) {
    this.shaderMat.colorShader = this.shader;
    }

    this.geom = this.scene.getNodeByPath('rootNode_/Unnamed Node 1/AnimatedCube') as Geometry;

    this.geom.mesh.materialOverride = undefined;

    if (this.shaderMat) {
    this.geom.mesh.subMeshes[0].material = this.shaderMat;
    }
    })

创建PBR材质并设置属性

在ArkGraphics 3D中,基于物理的渲染(PBR)材质允许开发者通过调整金属度、粗糙度、透明度等参数精确控制物体的外观效果,从而实现高度真实的渲染表现。由于不同模型在导出时所携带的PBR属性可能存在差异,因此在设置材质前建议根据模型内容进行适配。本示例选用CompareClearcoat模型,该模型自带清漆层(Clearcoat)相关的材质参数,适合用于演示清漆效果的调节。通过设置clearCoat与clearCoatRoughness等属性,可以直观观察清漆层的强度、光泽度与反射特性的变化。

  1. 导入相关模块。

    在页面脚本中导入ArkGraphics 3D提供的核心类型,用于创建PBR材质及绑定贴图资源。

    import { Scene, Camera, Material, Node, Image, SceneResourceFactory, Geometry, EnvironmentBackgroundType,
    PostProcessSettings, ToneMappingType, MetallicRoughnessMaterial, Vec4 } from '@kit.ArkGraphics3D';
    import {lookAt, OrbitCameraHelper } from '../common/utils';
  2. 加载场景资源。

    调用Scene.load()方法加载.glb或.gltf格式的模型文件,并在加载完成后获取Scene对象。场景加载完成后,可以访问场景的资源工厂以创建材质和其他资源。

    if (this.scene == null) {
    // Switched from .gltf to .glb; same content, different format
    Scene.load($rawfile('gltf/CompareClearcoat/CompareClearcoat.glb'))
    .then(async (scene: Scene) => {
    this.scene = scene;
    if (!this.scene.root) {
    return;
    }
    let rf: SceneResourceFactory = scene.getResourceFactory();

    // ...
    });
    }
  3. 获取几何体节点并预加载纹理。

    通过场景的节点路径获取目标几何体节点,并提取其材质,随后预加载清漆层(Clearcoat)相关的纹理资源。

    let pbrNode: Node | null | undefined = this.scene.root?.getNodeByPath('Unnamed Node 1/GeoSphere003');
    if (pbrNode) {
    this.material = (pbrNode as Geometry).mesh.subMeshes[0].material;
    let mrMaterial = (this.material as MetallicRoughnessMaterial);
    let original: Image | null = mrMaterial.clearCoat.image;
    const helmAlbedo: Resource = $rawfile('image/round_pattern.png');
    const irregularUri: Resource = $rawfile('image/irregular_pattern.png');
    let round: Image | null = await rf.createImage({name: 'round', uri: helmAlbedo });
    let irregular: Image | null = await rf.createImage({name: 'irregular', uri: irregularUri });
    if (original && round && irregular ) {
    this.textures.push(original);
    this.textures.push(round);
    this.textures.push(irregular);
    }
    }
  4. 配置环境光照。

    创建图像基础的光照(IBL)环境,配置环境贴图和辐射贴图,以实现真实的环境光照效果。

    scene.environment = await rf.createEnvironment({ name: 'env' });
    scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_CUBEMAP;
    scene.environment.environmentImage = await rf.createImage({ name: 'cube', uri: $rawfile('Environment/quarry_02_2k_skybox.ktx') });
    scene.environment.radianceImage = await rf.createImage({ name: 'rad', uri: $rawfile('Environment/quarry_02_2k_radiance.ktx') });
    scene.environment.irradianceCoefficients =
    [{ x: 1.080343842506409, y: 0.936282396316528, z: 0.665518164634705 },
    { x: 0.959947884082794, y: 0.828918874263763, z: 0.569704353809357 },
    { x: 0.848236382007599, y: 0.715092182159424, z: 0.473145037889481 },
    { x: -0.591795265674591, y: -0.501678705215454, z: -0.334018945693970 },
    { x: -0.775423347949982, y: -0.655484378337860, z: -0.437325984239578 },
    { x: 1.053589701652527, y: 0.887459456920624, z: 0.587381422519684 },
    { x: -0.018954016268253, y: -0.014871496707201, z: -0.008891185745597 },
    { x: -0.566255271434784, y: -0.476870059967041, z: -0.314557582139969 },
    { x: -0.239390164613724, y: -0.200478553771973, z: -0.132790848612785 }];
  5. 创建相机并设置视角。

    创建一个相机对象,并设置其位置和观察目标。然后启用轨道控制功能,让用户可以通过手势旋转和缩放视图。

    this.cam = await rf.createCamera({ 'name': 'ClearcoatCam' });
    this.cam.enabled = true;
    lookAt(this.cam,{x:0,y:0,z:-3},{x:0,y:0,z:0},{x:0,y:1,z:0});
    this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
    this.orbitCamera.SetOrbitFromEye(this.cam.position, this.scene.root.position, this.cam.rotation);
  6. 切换清漆层纹理。

    允许用户在不同的清漆纹理之间切换。通过按下按钮或触发事件来实现纹理的动态切换。

    changeClearcoatTex() {
    if (this.textures.length > 0) {
    let i = ++this.textureInUse % this.textures.length;
    (this.material as MetallicRoughnessMaterial).clearCoat.image = this.textures[i];
    }
    }
  7. 调整清漆层强度。

    通过滑动条调整清漆层的强度。这个方法通过更新材质的clearCoat.factor属性来实现。

    setClearcoat(v: number) {
    if (this.material) {
    const f: Vec4 = (this.material as MetallicRoughnessMaterial).clearCoat.factor;
    f.x = v / RESO;
    (this.material as MetallicRoughnessMaterial).clearCoat.factor = f;
    }
    }
  8. 切换粗糙度纹理。

    类似于清漆层纹理切换,用户也可以在不同的粗糙度纹理之间切换。

    changeClearcoatRoughTex() {
    if (this.textures.length > 0) {
    let i = ++this.textureInUse % this.textures.length;
    (this.material as MetallicRoughnessMaterial).clearCoatRoughness.image = this.textures[i];
    }
    }
  9. 调整清漆层粗糙度。

    通过滑动条调整清漆层的粗糙度,同样地,这通过更新clearCoatRoughness.factor来实现。

    setClearcoatRoughness(v: number) {
    if (this.material) {
    const f: Vec4 = (this.material as MetallicRoughnessMaterial).clearCoatRoughness.factor;
    f.y = v / RESO;
    (this.material as MetallicRoughnessMaterial).clearCoatRoughness.factor = f;
    }
    }