目录

创建并使用材质资源

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

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

基本概念

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

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

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

材质类型(MaterialType)

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

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

材质的属性

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

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

[h2]通用属性

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

[h2]PBR材质属性

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

创建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) {
      // 从 .gltf 切换为 .glb;内容相同,格式不同
      Scene.load($rawfile('gltf/CubeWithFloor/glTF/AnimatedCube.glb'))
        .then(async (result: Scene) => {
          // 将加载的场景赋值给globalScene,以便统一创建资源
          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;
    
    // 记录原始材质
    this.originalMat = this.geom.mesh.subMeshes[0].material;
  5. 创建Shader材质(空白)。

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

    function createMaterialPromise(): Promise<Material> {
      return new Promise((resolve, reject) => {
        // 确保在访问sceneFactory之前场景已加载
        if (globalScene) {
          let sceneFactory: SceneResourceFactory = globalScene.getResourceFactory();
          let sceneMaterialParameter: SceneResourceParameters = { name: '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资源可在后续步骤中绑定到Shader材质上,实现自定义渲染逻辑。

    function createShaderPromise(): Promise<Shader> {
      return new Promise((resolve, reject) => {
        // 确保在访问sceneFactory之前场景已加载
        if (globalScene) {
          let sceneFactory: SceneResourceFactory = globalScene.getResourceFactory();
    
          // 创建一个SceneResourceParameters对象,并用它来创建着色器
          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材质,再将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';
    import { BusinessError } from '@kit.BasicServicesKit';
  2. 加载场景资源。

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

    if (this.scene == null) {
      // 从 .gltf 切换为 .glb;内容相同,格式不同
      Scene.load($rawfile('gltf/CompareClearcoat/CompareClearcoat.glb'))
        .then(async (scene: Scene) => {
          this.scene = scene;
          if (!this.scene.root) {
            return;
          }
          let rf: SceneResourceFactory = scene.getResourceFactory();
    
          // ...
        })
        .catch((err: BusinessError): void => {
          console.error('Failed to load scene, code is %{public}d, message is %{public}s', err.code, err.message);
        });
    }
  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;
      }
    }