鸿蒙图形开发【3D引擎接口示例】

介绍

本实例主要介绍3D引擎提供的接口功能。提供了@ohos.graphics.scene中接口的功能演示。 3D引擎渲染的画面会被显示在Component3D这一控件中。点击按钮触发不同的功能,用户可以观察渲染画面的改变。

效果预览

使用说明

  1. 在主界面,可以点击按钮进入不同的子页面,每一个子页面分别测试了一类3D引擎的接口功能,在子页面点击back返回主界面。
  2. 在container界面,点击按钮,可以添加、移除子节点,节点的结构信息已打印在界面上。在本示例中操作的子节点是一个头盔模型。
  3. 在node_base界面,点击按钮对节点的基础属性如位置、旋转、大小、可见性等进行操作。在本示例中操作的子节点是一个头盔模型。
  4. 在node_camera界面,点击按钮对相机的属性如投影、后处理等进行操作。
  5. 在node_light界面,点击按钮对灯光的类型、颜色、强度、阴影等进行操作。
  6. 在scene_environment界面,点击按钮对背景进行操作。
  7. 在scene_animation界面,点击按钮进行动画的播放、暂停等操作的功能。
  8. 在scene_shader界面,点击按钮进行纹理材质的操作。

具体实现

  • 添加、移除、遍历节点的功能接口参考:ContainerPage.ets

    • 初始时会使用深度优先的方式遍历并打印场景中每一个节点的信息,从场景的root节点开始;

    • 删除节点:调用remove方法删除指定节点,不会重复删除,在本示例中删除了头盔节点;

    • 添加节点:调用append方法在子节点列表的末尾添加指定节点,不会重复添加,在本示例中添加了头盔节点;

    • 添加节点:调用insertAfter方法在子节点列表的指定位置添加指定节点,不会重复添加,在本示例中添加了头盔节点;

    • 清除子节点:调用clear方法清除子节点列表的所有节点,本示例中清除了root的子节点。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import { router } from '@kit.ArkUI';
      import { Scene, Camera, Node, Container, SceneResourceFactory, EnvironmentBackgroundType } from '@kit.ArkGraphics3D';
      import { Constants } from '../constants/Constants';
      import Logger from '../utils/Logger';

      const TAG: string = '[ContainerPage]';

      @Entry
      @Component
      struct ContainerPage {
      @State sceneOpt: SceneOptions | null = null;
      @State hierarchy: string = '';
      scene: Scene | null = null;
      cam: Camera | null = null;
      node: Node | null | undefined = undefined;
      sceneNode: Node | null = null;

      traversal(node: Node | null): void {
      if (!node) {
      return;
      }

      this.hierarchy += node.path + node.name + '\n';
      let container: Container<Node> = node.children;
      let count: number = container.count();
      
      this.hierarchy += '  ';
      for (let i = 0; i < count; i++) {
        this.traversal(container.get(i));
      }
      

      }

      aboutToAppear(): void {
      this.init();
      }

      aboutToDisappear(): void {
      if (this.scene) {
      this.scene.destroy();
      this.scene = null;
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      if (this.scene === null) {
      Scene.load($rawfile('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'))
      .then(async (result: Scene) => {
      if (!result) {
      return;
      }
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      let rf: SceneResourceFactory = this.scene.getResourceFactory();
      this.cam = await rf.createCamera({ 'name': 'Camera1' });
      this.cam.enabled = true;
      this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;

            this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
            this.cam.clearColor = Constants.CLEAR_COLOR;
            this.node = this.scene.getNodeByPath(Constants.HELMET_NODE_PATH);
            this.traversal(this.scene.root);
            this.sceneNode = this.scene.getNodeByPath(Constants.SCENE_NODE_PATH);
          })
          .catch((reason: string) => {
            Logger.error(TAG, `init error: ${reason}`);
          });
      }
      

      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      } else {
      Text(r('app.string.loading')) .fontSize(r('app.float.text_font_size'))
      .fontWeight(Constants.FONT_WEIGHT_FIVE_HUNDRED)
      }
      }
      .height(Constants.THIRTY_PERCENT)
      .width(Constants.FULL_PERCENT)
      .backgroundColor(Color.White)
      .borderRadius($r('app.float.board_radius_normal'))

        Column() {
          Text(this.hierarchy)
            .borderRadius($r('app.float.board_radius_normal'))
            .fontWeight(FontWeight.Normal)
        }
        .height(Constants.TWENTY_PERCENT)
        .width(Constants.FULL_PERCENT)
        .borderRadius($r('app.float.board_radius_normal'))
        .backgroundColor(Color.White)
        .alignItems(HorizontalAlign.Start)
        .padding($r('app.float.text_area_padding'))
      
        Blank()
          .layoutWeight(1)
      
        Button($r('app.string.remove_node'))
          .onClick(() => {
            if (this?.scene?.root) {
              this.scene.root.children.get(0)?.children.remove(this.node);
              this.hierarchy = '';
              this.traversal(this.scene.root);
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.append_node'))
          .onClick(() => {
            if (this?.scene?.root) {
              this.scene.root.children.get(0)?.children.append(this.node);
              this.hierarchy = '';
              this.traversal(this.scene.root);
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.insert_node'))
          .onClick(() => {
            if (this?.scene?.root) {
              this.scene.root.children.get(0)?.children.insertAfter(this.node, null);
              this.hierarchy = '';
              this.traversal(this.scene.root);
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.clear'))
          .onClick(() => {
            if (this?.scene?.root) {
              this.scene.root.children.clear();
              this.hierarchy = '';
              this.traversal(this.scene.root);
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.back'))
          .onClick(() => {
            router.back();
          })
          .width(Constants.FULL_PERCENT)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      

      }
      }

  • 对节点的基础属性如位置、旋转、大小等操作参考:NodeBase.ets

    • 修改scale属性改变节点的大小,本示例中改变了头盔的大小;

    • 修改position属性改变节点的位置,本示例中改变了头盔的x轴坐标;

    • 修改rotation属性改变节点的旋转方向,改变子节点的父节点的rotation同样会改变子节点的旋转方向(position同理),本示例中改变了头盔的旋转方向;

    • 修改节点的visible属性改变节点的可见性,本示例中改变了头盔的可见性;

    • 使用getEnabled和setEnabled操作节点的layerMask,本示例中将layerMask的信息打印在界面上。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import { Scene, Camera, Node, Container, SceneResourceFactory, EnvironmentBackgroundType } from '@kit.ArkGraphics3D';
      import { router } from '@kit.ArkUI';
      import { Constants } from '../constants/Constants';
      import Logger from '../utils/Logger';

      const TAG: string = '[NodeBase]';

      @Entry
      @Component
      struct NodeBase {
      @State sceneOpt: SceneOptions | null = null;
      @State xAxis: number = 0;
      @State layerMaskInfo: string = '';
      scene: Scene | null = null;
      cam: Camera | null = null;
      node: Node | null | undefined = null;
      scaled: boolean = false;
      step: number = 0;
      value: number = 0;
      layerMaskIndex: number = 0x1;

      traversalChild(node: Node | null): void {
      if (!node) {
      return;
      }

      let container: Container<Node> = node.children;
      let count: number = container.count();
      
      for (let i = 0; i < count; i++) {
        this.traversalChild(container.get(i));
      }
      

      }

      aboutToAppear(): void {
      this.init();
      }

      aboutToDisappear(): void {
      if (this.scene) {
      this.scene.destroy();
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      if (this.scene === null) {
      Scene.load($rawfile('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'))
      .then(async (result: Scene) => {
      if (!result) {
      return;
      }
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      let rf: SceneResourceFactory = this.scene.getResourceFactory();
      this.cam = await rf.createCamera({ 'name': 'Camera1' });
      this.cam.enabled = true;
      this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;

            this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
            this.cam.clearColor = Constants.CLEAR_COLOR;
            this.node = this.scene.getNodeByPath(Constants.HELMET_NODE_PATH);
            if (this.node) {
              this.xAxis = this.node.position.x;
              this.value = this.xAxis;
            }
          }).catch((reason: string) => {
          Logger.error(TAG, `init error: ${reason}`);
        });
      }
      

      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      } else {
      Text(r('app.string.loading')); } } .height(Constants.THIRTY_PERCENT) .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .borderRadius(r('app.float.board_radius_normal'))

        Column() {
          Text('layer mask info:')
            .fontWeight(FontWeight.Normal)
      
          Text(this.layerMaskInfo)
            .fontWeight(FontWeight.Normal)
        }
        .height(Constants.THIRTEEN_PERCENT)
        .width(Constants.FULL_PERCENT)
        .borderRadius($r('app.float.board_radius_normal'))
        .backgroundColor(Color.White)
        .alignItems(HorizontalAlign.Start)
        .padding($r('app.float.text_area_padding'))
      
        Column({ space: Constants.LIST_SPACE }) {
          Text($r('app.string.x_axis', this.xAxis?.toFixed(1).toString()))
            .fontSize($r('app.float.text_font_size'))
            .fontWeight(Constants.FONT_WEIGHT_FIVE_HUNDRED)
            .margin({ left: $r('app.float.text_area_padding') })
      
          Slider({
            value: this.value,
            min: this.value - Constants.XAXIS_VALUE,
            max: this.value + Constants.XAXIS_VALUE,
            step: Constants.XAXIS_STEP,
            style: SliderStyle.OutSet
          })
            .showTips(false)
            .onChange((value: number, mode: SliderChangeMode) => {
              this.xAxis = value;
              if (mode === SliderChangeMode.End) {
                if (!this.node) {
                  return;
                }
                this.node.position.x = this.xAxis;
              }
            })
            .width(Constants.FULL_PERCENT)
            .height($r('app.float.slider_height'))
        }
        .alignItems(HorizontalAlign.Start)
        .width(Constants.FULL_PERCENT)
      
        Column({ space: Constants.LIST_SPACE }) {
          Button($r('app.string.layer_mask'))
            .onClick(() => {
              if (!this.scene) {
                return;
              }
              let node: Node | null | undefined = this.scene.getNodeByPath(Constants.HELMET_NODE_PATH);
              if (!node) {
                return;
              }
              let enabled: boolean = node.layerMask.getEnabled(this.layerMaskIndex);
              node.layerMask.setEnabled(1, !enabled);
              this.layerMaskInfo = 'node name: ' + node.name + '\n' + 'layer mask index: ' + this.layerMaskIndex + '\n' +
                'layer mask enabled: ' + enabled;
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.scale_helmet'))
            .onClick(() => {
              if (!this.scene) {
                return;
              }
              let node: Node | null | undefined = this.scene.root?.children.get(0)?.getNodeByPath(Constants.HELMET_PATH);
              if (!node) {
                return;
              }
      
              if (this.scaled) {
                node.scale = { x: 1.0, y: 1.0, z: 1.0 };
                this.scaled = false;
              } else {
                node.scale = { x: 0.5, y: 0.5, z: 0.5 };
                this.scaled = true;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.rotate_helmet'))
            .onClick(() => {
              if (!this.scene) {
                return;
              }
              let node: Node | null | undefined = this.scene.getNodeByPath(Constants.HELMET_NODE_PATH);
              if (!node) {
                return;
              }
              let c = Math.cos(-this.step * 0.7 * 0.1);
              let s = Math.sin(-this.step * 0.7 * 0.1);
              node.rotation = {
                x: s,
                y: 0.0,
                z: 0.0,
                w: c
              };
              this.step++;
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.rotate_parent'))
            .onClick(() => {
              if (!this.scene) {
                return;
              }
              let child: Node | null | undefined = this.scene.root?.getNodeByPath(Constants.HELMET_PARENT_PATH);
              if (!child) {
                return;
              }
              let node: Node | null = child.parent;
              if (!node) {
                return;
              }
              let c = Math.cos(-this.step * 0.7 * 0.1);
              let s = Math.sin(-this.step * 0.7 * 0.1);
              node.rotation = {
                x: 0.0,
                y: s,
                z: 0.0,
                w: c
              };
              this.step++;
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.root_visible'))
            .onClick(() => {
              if (this.scene?.root) {
                this.scene.root.visible = !this.scene.root?.visible;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.back'))
            .onClick(() => {
              if (this.scene) {
                this.scene.destroy();
              }
              router.back();
            })
            .width(Constants.FULL_PERCENT)
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.End)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

  • 对相机的属性如投影、后处理等进行操作的功能接口参考:NodeCamera.ets

    • 修改fov属性改变投影的视场角,本示例中设置了45/60/90三种;

    • 修改nearPlane和farPlane属性投影的近平面和远平面;

    • 修改enabled属性改变相机是否启用,设为false之后控件中的画面将不再刷新;

    • 修改postProcess.toneMapping.type属性可以改变用于色调映射的方法,目前有ACES/ACES_2020/FILMIC三种;

    • 修改postProcess.toneMapping.exposure属性可以改变用于色调映射的曝光参数。

    • 修改clearColor属性可以设置每一帧的刷新背景色,设置a通道为零可以获得一个透明的背景,设置为null时不会刷新全部背景像素。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import {
      Scene,
      Camera,
      SceneResourceFactory,
      EnvironmentBackgroundType,
      ToneMappingType,
      ToneMappingSettings
      } from '@kit.ArkGraphics3D';
      import { router } from '@kit.ArkUI';
      import { Constants } from '../constants/Constants';
      import Logger from '../utils/Logger';

      let fovFlag: number = 0;
      let TonemapTypeFlag: number = 0;
      let clearColorFlag: number = 0;

      @Extend(Text)
      function textEffect() {
      .fontSize($r('app.float.text_font_size'))
      .fontWeight(Constants.FONT_WEIGHT_FIVE_HUNDRED)
      .margin({ left: $r('app.float.text_area_padding') })
      }

      @Entry
      @Component
      struct NodeCamera {
      @State sceneOpt: SceneOptions | null = null;
      @State nearPlaneValue: number = 0.1;
      @State farPlaneValue: number = 100;
      @State tonemapExposure: number = 1;
      @State enable: boolean = true;
      scene: Scene | null = null;
      cam: Camera | null = null;

      aboutToAppear(): void {
      this.init();
      }

      aboutToDisappear(): void {
      if (this.scene) {
      this.scene.destroy();
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      if (this.scene === null) {
      Scene.load($rawfile('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'))
      .then(async (result: Scene) => {
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      let rf: SceneResourceFactory = this.scene.getResourceFactory();
      this.cam = await rf.createCamera({ 'name': 'Camera1' });
      this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;
      this.cam.enabled = true;
      this.cam.postProcess = {
      toneMapping: {
      type: ToneMappingType.ACES,
      exposure: 1.0
      } as ToneMappingSettings
      };
      this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
      })
      .catch((reason: string) => {
      Logger.error(init error: ${reason});
      });
      }
      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      .backgroundColor(Color.Transparent)
      .width(Constants.NINETY_PERCENT)
      .height(Constants.FULL_PERCENT)
      } else {
      Text(r('app.string.loading')) } } .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .height(Constants.THIRTY_PERCENT) .borderRadius(r('app.float.board_radius_normal'))

        Column() {
          Text($r('app.string.near_plane', this.nearPlaneValue.toFixed(1).toString()))
            .textEffect()
          Slider({
            value: this.nearPlaneValue,
            min: 0.1,
            max: 10,
            step: 0.1,
            style: SliderStyle.OutSet
          })
            .showTips(false)
            .onChange((value: number, mode: SliderChangeMode) => {
              this.nearPlaneValue = value;
              if (mode === SliderChangeMode.End) {
                if (!this.scene || !this.cam) {
                  return;
                }
                this.cam.nearPlane = value;
              }
            })
            .width(Constants.FULL_PERCENT)
        }
        .alignItems(HorizontalAlign.Start)
        .width(Constants.FULL_PERCENT)
      
        Column() {
          Text($r('app.string.far_plane', this.farPlaneValue.toFixed(1).toString()))
            .textEffect()
          Slider({
            value: this.farPlaneValue,
            min: 0.1,
            max: 100,
            step: 1,
            style: SliderStyle.OutSet
          })
            .showTips(false)
            .onChange((value: number, mode: SliderChangeMode) => {
              this.farPlaneValue = value;
              if (mode === SliderChangeMode.End) {
                if (!this.scene || !this.cam) {
                  return;
                }
                this.cam.farPlane = this.farPlaneValue;
              }
            })
            .width(Constants.FULL_PERCENT)
        }
        .alignItems(HorizontalAlign.Start)
        .width(Constants.FULL_PERCENT)
      
        Column() {
          Text($r('app.string.tonemap_exposure', this.tonemapExposure.toFixed(1).toString()))
            .textEffect()
          Slider({
            value: this.tonemapExposure,
            min: 0,
            max: 10,
            step: 0.1,
            style: SliderStyle.OutSet
          })
            .showTips(false)
            .onChange((value: number, mode: SliderChangeMode) => {
              this.tonemapExposure = value;
              if (mode === SliderChangeMode.End) {
                if (!this.scene || !this.cam || !this.cam.postProcess || !this.cam.postProcess.toneMapping) {
                  return;
                }
                this.cam.postProcess = {
                  toneMapping: {
                    exposure: this.tonemapExposure,
                    type: this.cam.postProcess.toneMapping.type
                  }
                };
              }
            })
            .width(Constants.FULL_PERCENT)
        }
        .alignItems(HorizontalAlign.Start)
        .width(Constants.FULL_PERCENT)
      
        Column({ space: Constants.LIST_SPACE }) {
          Button(!this.enable ? $r('app.string.enabled') : $r('app.string.disabled'))
            .onClick(() => {
              if (!this.scene || !this.cam) {
                return;
              }
              this.enable = !this.enable;
              this.cam.enabled = this.enable;
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_fov'))
            .onClick(() => {
              if (!this.scene || !this.cam) {
                return;
              }
              const RADIAN: number = Math.PI / Constants.PI_RADIAN;
              const FOV_COUNT: number = 3;
              const FOV_0: number = 0;
              const FOV_1: number = 1;
              fovFlag = ++fovFlag % FOV_COUNT;
              if (fovFlag === FOV_0) {
                let degree = Constants.DEGREE_SIXTY;
                this.cam.fov = degree * RADIAN;
              } else if (fovFlag === FOV_1) {
                let degree = Constants.DEGREE_NINETY;
                this.cam.fov = degree * RADIAN;
              } else {
                let degree = Constants.DEGREE_FORTY_FIVE;
                this.cam.fov = degree * RADIAN;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_tonemap_type'))
            .onClick(() => {
              if (!this.scene || !this.cam || !this.cam.postProcess || !this.cam.postProcess.toneMapping) {
                return;
              }
              let type: ToneMappingType = ToneMappingType.ACES;
              const TONE_MAPPING_COUNT: number = 3;
              const TONE_MAPPING_0: number = 0;
              const TONE_MAPPING_1: number = 1;
              TonemapTypeFlag = ++TonemapTypeFlag % TONE_MAPPING_COUNT;
              if (TonemapTypeFlag === TONE_MAPPING_0) {
                type = ToneMappingType.ACES;
              } else if (TonemapTypeFlag === TONE_MAPPING_1) {
                type = ToneMappingType.ACES_2020;
              } else {
                type = ToneMappingType.FILMIC;
              }
              this.cam.postProcess = {
                toneMapping: {
                  exposure: this.cam.postProcess.toneMapping.exposure,
                  type: type
                }
              };
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.set_clear_color'))
            .onClick(() => {
              if (!this.scene || !this.cam) {
                return;
              }
              const CLEAR_COLOR_COUNT: number = 3;
              const CLEAR_COLOR_0: number = 0;
              const CLEAR_COLOR_1: number = 1;
      
              clearColorFlag = ++clearColorFlag % CLEAR_COLOR_COUNT;
              if (clearColorFlag === CLEAR_COLOR_0) {
                this.cam.clearColor = this.cam.clearColor = Constants.CLEAR_COLOR;
              } else if (clearColorFlag === CLEAR_COLOR_1) {
                this.cam.clearColor = Constants.CLEAR_COLOR_BLUE;
              } else {
                this.cam.clearColor = Constants.CLEAR_COLOR_RED;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.back'))
            .onClick(() => {
              router.back();
            })
            .width(Constants.FULL_PERCENT)
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.End)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

  • 对灯光的类型、颜色、强度、阴影等进行操作的功能接口参考:NodeLight.ets

    • lightType属性为只读,表示灯光的种类,目前有DIRECTIONAL和SPOT两种,分别为平行光和点光源;

    • 修改enabled属性改变灯光是否启用;

    • 修改color属性可以改变灯光的颜色,本示例中有三种可以变化;

    • 修改intensity属性可以改变灯光的强度。

    • 修改shadowEnabled属性可以设置灯光是否产生阴影。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import {
      Scene,
      Camera,
      DirectionalLight,
      Light,
      SpotLight,
      Image,
      LightType,
      SceneResourceFactory,
      EnvironmentBackgroundType
      } from '@kit.ArkGraphics3D';
      import { router } from '@kit.ArkUI';
      import { Constants } from '../constants/Constants';
      import Logger from '../utils/Logger';
      import { CalcUtils } from '../utils/CalcUtils';

      let colorFlag: number = 0;
      let intensityFlag: number = 0;
      let shadowFlag: boolean = true;

      @Entry
      @Component
      struct NodeLight {
      @State sceneOpt: SceneOptions | null = null;
      @State lgt: Light | null = null;
      scene: Scene | null = null;
      cam: Camera | null = null;
      directionalLight: DirectionalLight | null | undefined = null;
      spotLight: SpotLight | null = null;
      radianceImg1: Image | null = null;

      onPageShow(): void {
      this.init();
      }

      onPageHide(): void {
      if (this.scene) {
      this.scene.destroy();
      }
      this.cam = null;
      this.scene = null;
      }

      init(): void {
      if (this.scene !== null) {
      return;
      }

      Scene.load($rawfile('gltf/CubeWithFloor/glTF/AnimatedCube.gltf'))
        .then(async (result: Scene) => {
          this.scene = result;
          this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
          let rf: SceneResourceFactory = this.scene.getResourceFactory();
          this.cam = await rf.createCamera({ 'name': 'Camera1' });
          this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;
          this.cam.enabled = true;
          // Camera look at direction.
          CalcUtils.lookAt(this.cam, { x: 10, y: 5, z: 15 }, { x: 0, y: 0.0, z: 0.0 }, { x: 0, y: 1, z: 0 });
      
          this.radianceImg1 = await rf.createImage({
            name: 'radianceImg1',
            uri: $rawfile('gltf/Environment/glTF/images/quarry_02_2k_radiance.ktx')
          });
      
          this.scene.environment.radianceImage = this.radianceImg1;
          this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
      
          this.directionalLight = await this.scene?.getResourceFactory().createLight(
            { 'name': 'DirectionalLight1' }, LightType.DIRECTIONAL) as DirectionalLight;
      
          // Light look at direction.
          CalcUtils.lookAt(this.directionalLight, { x: 10.0, y: 10.0, z: 10.0 }, { x: 0.0, y: 0.0, z: 0.0 },
            { x: 0.0, y: 1.0, z: 0.0 });
      
          this.directionalLight.enabled = false;
          this.spotLight = await this.scene?.getResourceFactory().createLight(
            { 'name': 'SpotLight1' }, LightType.SPOT) as SpotLight;
          // Spot light look at direction.
          CalcUtils.lookAt(this.spotLight, { x: 6, y: 6, z: -6 }, { x: 0, y: 0.0, z: 0.0 }, { x: 0, y: 1, z: 0 });
      
          this.spotLight.enabled = true;
          this.lgt = this.spotLight;
          this.UpdateLights();
        })
        .catch((reason: string) => {
          Logger.error(`init error ${reason}`);
        })
      

      }

      UpdateLights(): void {
      if (this.lgt) {
      this.lgt.color = Constants.COLORS[colorFlag];
      this.lgt.intensity = Constants.INTENSITIES[intensityFlag];
      if (this.lgt.lightType === LightType.DIRECTIONAL) {
      // Just reduce some intensity when directional light.
      this.lgt.intensity = Constants.INTENSITIES[intensityFlag] / Constants.HALF_HUNDRED;
      }
      this.lgt.shadowEnabled = shadowFlag;
      }
      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      } else {
      Text(r('app.string.loading')); } } .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .height(Constants.THIRTY_PERCENT) .borderRadius(r('app.float.board_radius_normal'))

        Blank()
          .layoutWeight(1)
      
        if (this.lgt) {
          if (this.lgt.enabled) {
            Button(`Shadows (${!this.lgt.shadowEnabled ? 'enabled' : 'disabled'})`)
              .onClick(() => {
                if (!this.scene || !this.lgt) {
                  return;
                }
                shadowFlag = !shadowFlag;
                this.UpdateLights();
              })
              .width(Constants.FULL_PERCENT)
      
            Button($r('app.string.change_color'))
              .onClick(() => {
                if (!this.scene || !this.lgt) {
                  return;
                }
                colorFlag = ++colorFlag % Constants.COLORS.length;
                this.UpdateLights();
              })
              .width(Constants.FULL_PERCENT)
      
            Button(`Change intensity (${this.lgt.intensity})`)
              .onClick(() => {
                if (!this.scene || !this.lgt) {
                  return;
                }
                intensityFlag = (intensityFlag + 1) % Constants.INTENSITIES.length;
                this.UpdateLights();
              })
              .width(Constants.FULL_PERCENT)
          }
      
          Button(`Switch light type (${this.lgt.lightType === LightType.DIRECTIONAL ? 'DIRECTIONAL' : 'SPOT'})`)
            .onClick(() => {
              if (this.lgt) {
                this.lgt.enabled = false;
      
                if (this.lgt.lightType === LightType.DIRECTIONAL) {
                  this.lgt = this.spotLight;
                } else if (this.directionalLight) {
                  this.lgt = this.directionalLight;
                }
              }
      
              if (this.lgt) {
                this.lgt.enabled = true;
                this.UpdateLights();
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button(this.lgt.enabled ? $r('app.string.disabled') : $r('app.string.enabled'))
            .onClick(() => {
              if (!this.scene || !this.lgt) {
                return;
              }
              this.lgt.enabled = !this.lgt.enabled;
            })
            .width(Constants.FULL_PERCENT)
        }
      
        Button($r('app.string.back'))
          .onClick(() => {
            router.back();
          })
          .width(Constants.FULL_PERCENT)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

  • 对背景进行操作的功能接口考:SceneEnvironment.ets

    • 同时修改backgroundType和environmentImage可以设置背景图片,backgroundType为BACKGROUND_IMAGE或BACKGROUND_EQUIRECTANGULAR时对应png或者jpeg格式的图片;类型为BACKGROUND_CUBEMAP时对应ktx格式的图片;类型为BACKGROUND_NONE时不设置背景图片,需要同时将camera的clearColor的a通道设置为0以获得透明背景;

    • 修改environmentMapFactor属性改变背景图的相应参数。

    • 修改radianceImage属性改变PBR中的环境贴图;

    • 修改indirectDiffuseFactor属性改变PBR中的相应参数;

    • 修改indirectSpecularFactor属性改变PBR中的相应参数;

    • 修改irradianceCoefficients属性改变PBR中的相应参数;

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import { Animation, Scene, Camera, EnvironmentBackgroundType } from '@kit.ArkGraphics3D';
      import { Animator, router, AnimatorResult } from '@kit.ArkUI';
      import Logger from '../utils/Logger';
      import { Constants } from '../constants/Constants';

      @Entry
      @Component
      struct SceneAnimation {
      @State sceneOpt: SceneOptions | null = null;
      @State progressValue: number = 0;
      @State animationEnabled: Boolean = false;
      @State animationDuration: number = 0;
      @State animationIsRunning: Boolean = false;
      @State animationCallbackInvoked: string = Constants.STRING_NO;
      @State enable: boolean = true;
      scene: Scene | null = null;
      cam: Camera | null = null;
      backAnimator: AnimatorResult | undefined = undefined;

      onPageShow(): void {
      this.init();
      }

      onPageHide(): void {
      if (this.scene) {
      this.scene.destroy();
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      this.backAnimator = Animator.create(Constants.ANIMATION_OPTION);
      this.backAnimator.onFrame = () => {
      if (this.scene?.animations[0]) {
      this.animationEnabled = this.scene.animations[0].enabled;
      this.animationDuration = this.scene.animations[0].duration;
      this.animationIsRunning = this.scene.animations[0].running;
      this.progressValue = this.scene.animations[0].progress;
      }
      }
      if (this.scene === null) {
      Scene.load($rawfile('gltf/BrainStem/glTF/BrainStem.gltf'))
      .then(async (result: Scene) => {
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      let rf = this.scene.getResourceFactory();
      this.cam = await rf.createCamera({ 'name': 'Camera1' });
      this.cam.enabled = true;
      this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;
      this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
      })
      .catch((error: string) => {
      Logger.error(init error: ${error});
      });
      }
      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      .backgroundColor(Color.Transparent)
      .onAppear(() => {
      if (!this.scene || !this.scene.animations[0]) {
      return;
      }
      let anim: Animation = this.scene.animations[0];
      anim.onStarted(() => {
      this.animationCallbackInvoked = Constants.STRING_START;
      });
      anim.onFinished(() => {
      this.animationCallbackInvoked = Constants.STRING_FINISH;
      });
      this.backAnimator?.play();
      })
      } else {
      Text(r('app.string.loading')) } } .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .height(Constants.TWENTY_FIVE_PERCENT) .borderRadius(r('app.float.board_radius_normal'))

        Column() {
          Text($r('app.string.progress', (this.progressValue * 100).toFixed(2).toString()))
            .fontSize($r('app.float.text_font_size'))
          Text($r('app.string.duration', this.animationDuration.toFixed(2).toString()))
            .fontSize($r('app.float.text_font_size'))
          Text($r('app.string.running', this.animationIsRunning))
            .fontSize($r('app.float.text_font_size'))
          Text($r('app.string.animation_enabled', this.animationEnabled))
            .fontSize($r('app.float.text_font_size'))
          Text($r('app.string.animation_invoked_callback', this.animationCallbackInvoked))
            .fontSize($r('app.float.text_font_size'))
        }
        .alignItems(HorizontalAlign.Start)
        .width(Constants.FULL_PERCENT)
        .backgroundColor(Color.White)
        .borderRadius($r('app.float.board_radius_normal'))
        .padding($r('app.float.text_area_padding'))
      
        Column({ space: Constants.LIST_SPACE }) {
          Button(this.enable ? 'disable animation' : 'enable animation')
            .onClick(() => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              this.enable = !this.enable;
              this.scene.animations[0].enabled = this.enable;
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.start'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              anim.start();
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.pause'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              anim.pause();
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.stop'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              anim.stop();
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.finish'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              anim.finish();
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.restart'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              anim.restart();
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.seek'))
            .onClick(async () => {
              if (!this.scene || !this.scene.animations[0]) {
                return;
              }
              let anim: Animation = this.scene.animations[0];
              // Seek to 30%.
              anim.seek(0.3);
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.back'))
            .onClick(() => {
              router.back();
            })
            .width(Constants.FULL_PERCENT)
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.End)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

  • 对动画的播放、暂停等进行操作的功能接口参考:SceneAnimation.ets

    • 修改enabled属性改变动画是否启用;

    • 只读属性duration、running、progress为动画的时长、进行状态、已经进行的比例;

    • 调用start方法控制动画开启;

    • 调用pause方法控制动画暂停;

    • 调用stop方法控制动画停止,并将动画状态设置为开头;

    • 调用finish方法控制动画结束,并将动画状态设置为结尾;

    • 调用restart方法控制动画从头开始;

    • 调用seek方法控制动画设置到指定状态;

    • onStarted方法在动画开始时执行传入的回调;

    • onFinished方法在动画结束时执行传入的回调。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import {
      Scene,
      Camera,
      Image,
      SceneResourceFactory,
      EnvironmentBackgroundType,
      } from '@kit.ArkGraphics3D';
      import { router } from '@kit.ArkUI';
      import { Constants } from '../constants/Constants';
      import { CalcUtils } from '../utils/CalcUtils';
      import Logger from '../utils/Logger';

      let typeFlag: number = 0;
      let radianceImageFlag: boolean = true;
      let factorIndex: number = 0;

      @Entry
      @Component
      struct sceneEnvironment {
      @State sceneOpt: SceneOptions | null = null;
      scene: Scene | null = null;
      cam: Camera | null = null;
      env: Environment | null = null;
      envImg1: Image | null = null;
      envImg2: Image | null = null;
      envImg3: Image | null = null;
      radianceImg1: Image | null = null;

      onPageShow(): void {
      this.init();
      }

      onPageHide(): void {
      if (this.scene) {
      this.scene.destroy();
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      if (this.scene === null) {
      Scene.load($rawfile('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'))
      .then(async (result: Scene) => {
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      let rf: SceneResourceFactory = this.scene.getResourceFactory();
      this.cam = await rf.createCamera({ 'name': 'Camera1' });
      this.cam.enabled = true;
      this.cam.position.z = 5;
      this.env = await rf.createEnvironment({ 'name': 'Env' });
      this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;

            this.envImg1 = await rf.createImage({ name: 'envImg1', uri: $rawfile('gltf/Cube/glTF/Cube_BaseColor.png') });
            this.envImg2 = await rf.createImage({
              name: 'envImg2',
              uri: $rawfile('gltf/Environment/glTF/images/quarry_02_2k_skybox.ktx')
            });
            this.envImg3 = await rf.createImage({
              name: 'envImg3',
              uri: $rawfile('gltf/DamagedHelmet/glTF/Default_albedo.jpg')
            });
            this.radianceImg1 = await rf.createImage({
              name: 'radianceImg1',
              uri: $rawfile('gltf/Environment/glTF/images/quarry_02_2k_radiance.ktx')
            });
          })
          .catch((error: string) => {
            Logger.error(`init error: ${error}`);
          });
      }
      

      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      .backgroundColor(Color.Transparent)
      .width(Constants.NINETY_PERCENT)
      .height(Constants.FULL_PERCENT)
      } else {
      Text(r('app.string.loading')) } } .height(Constants.THIRTY_PERCENT) .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .borderRadius(r('app.float.board_radius_normal'))

        Column({ space: Constants.LIST_SPACE }) {
          Button($r('app.string.change_env_img_type'))
            .onClick(() => {
              if (!this.scene || !this.env || !this.cam) {
                return;
              }
              const ENV_TYPE_COUNT: number = 4;
              const ENV_TYPE_0: number = 0;
              const ENV_TYPE_1: number = 1;
              const ENV_TYPE_2: number = 2;
              typeFlag = ++typeFlag % ENV_TYPE_COUNT;
              if (typeFlag === ENV_TYPE_0) {
                this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
                this.cam.clearColor = Constants.CLEAR_COLOR;
              } else if (this.envImg1 && typeFlag === ENV_TYPE_1) {
                this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_IMAGE;
                this.scene.environment.environmentImage = this.envImg1;
              } else if (this.envImg2 && typeFlag === ENV_TYPE_2) {
                this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_CUBEMAP;
                this.scene.environment.environmentImage = this.envImg2;
              } else {
                this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_EQUIRECTANGULAR;
                this.scene.environment.environmentImage = this.envImg3;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_environment_map_factor'))
            .onClick(() => {
              if (!this.scene || !this.env) {
                return;
              }
              this.scene.environment.environmentMapFactor =
                Constants.ENVIRONMENT_FACTOR[++factorIndex % Constants.ENVIRONMENT_FACTOR.length];
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_radiance_mg'))
            .onClick(() => {
              if (!this.scene || !this.env) {
                return;
              }
              radianceImageFlag = !radianceImageFlag;
              if (radianceImageFlag) {
                this.scene.environment.radianceImage = null;
              }
              if (this.radianceImg1 && !radianceImageFlag) {
                this.scene.environment.radianceImage = this.radianceImg1;
              }
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_indirect_diffuse_factor'))
            .onClick(() => {
              if (!this.scene || !this.env) {
                return;
              }
              this.scene.environment.indirectDiffuseFactor =
                Constants.ENVIRONMENT_FACTOR[++factorIndex % Constants.ENVIRONMENT_FACTOR.length];
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_indirect_specular_factor'))
            .onClick(() => {
              if (!this.scene || !this.env) {
                return;
              }
              this.scene.environment.indirectSpecularFactor =
                Constants.ENVIRONMENT_FACTOR[++factorIndex % Constants.ENVIRONMENT_FACTOR.length];
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.change_irradiance_coefficients'))
            .onClick(() => {
              if (!this.scene || !this.env) {
                return;
              }
              this.scene.environment.irradianceCoefficients = [
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() },
                { x: CalcUtils.genRandom(), y: CalcUtils.genRandom(), z: CalcUtils.genRandom() }
              ];
            })
            .width(Constants.FULL_PERCENT)
      
          Button($r('app.string.back'))
            .onClick(() => {
              router.back();
            })
            .width(Constants.FULL_PERCENT)
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.End)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

  • 对纹理材质进行操作的功能接口参考:SceneShader.ets

    • 首先创建一个shader作为ShaderMaterial的colorShader,再创建一个material作为纹理的ShaderMaterial;

    • 使用Geometry获取相应的带有Material的Mesh节点;

    • 修改shader的input参数;

    • 修改subMesh的material属性,将其变为自定义的ShaderMaterial;

    • 修改materialOverride属性,将纹理覆盖为自定义的ShaderMaterial。

      /*

      • Copyright (c) 2024 Huawei Device Co., Ltd.
      • Licensed under the Apache License, Version 2.0 (the "License");
      • you may not use this file except in compliance with the License.
      • You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        */

      import {
      Aabb,
      Vec4,
      Scene,
      Camera,
      Shader,
      ShaderMaterial,
      Geometry,
      Material,
      Node,
      Image,
      Container,
      SceneResourceFactory,
      EnvironmentBackgroundType,
      MaterialType
      } from '@kit.ArkGraphics3D';
      import { Animator, AnimatorResult, router } from '@kit.ArkUI';
      import Logger from '../utils/Logger';
      import { Constants } from '../constants/Constants';

      @Entry
      @Component
      struct sceneShader {
      @State sceneOpt: SceneOptions | null = null;
      @State hierarchy: string = '';
      @State meshInfo: string = '';
      scene: Scene | null = null;
      rf: SceneResourceFactory | null = null;
      cam: Camera | null = null;
      shader: Shader | null = null;
      material: ShaderMaterial | null = null;
      geom: Geometry | null = null;
      image: Image | null = null;
      materialOrg: Material | null = null;
      backAnimator: AnimatorResult | undefined = undefined;
      step: number = 0;

      traversal(node: Node | null): void {
      if (!node) {
      return;
      }
      this.hierarchy += node.path + '/' + node.name + '\n';
      let container: Container<Node> = node.children;
      let count: number = container.count();
      this.hierarchy += ' ';
      for (let i = 0; i < count; i++) {
      this.traversal(container.get(i));
      }
      }

      onPageShow(): void {
      this.init();
      }

      printAabb(aabb: Aabb, append: string): string {
      let info: string = '';
      info += append + ' max aabb [ ' + aabb.aabbMax.x + ' ' + aabb.aabbMax.y + ' ' + aabb.aabbMax.z + ' ]';
      info += '\n' + append + ' min aabb [ ' + aabb.aabbMin.x + ' ' + aabb.aabbMin.y + ' ' + aabb.aabbMin.z + ' ]';
      return info;
      }

      onPageHide(): void {
      if (this.scene) {
      this.scene.destroy();
      }

      this.cam = null;
      this.scene = null;
      

      }

      init(): void {
      this.backAnimator = Animator.create(Constants.ANIMATION_OPTION);
      this.backAnimator.onFrame = () => {
      this.step++;
      if (this.material && this.material.colorShader) {
      // Just give a random effect.
      (this.material.colorShader.inputs['vec_1'] as Vec4) = {
      x: Math.abs(Math.sin(this.step) + 0.5),
      y: Math.abs(Math.sin(this.step * 0.86) + 0.5),
      z: Math.abs(Math.sin(this.step * 0.91) + 0.5),
      w: 1.0
      };
      (this.material.colorShader.inputs['time'] as number) = this.step;
      }
      };
      if (this.scene === null) {
      Scene.load($rawfile('gltf/Cube/glTF/Cube.gltf'))
      .then(async (result: Scene) => {
      this.scene = result;
      this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
      this.rf = this.scene.getResourceFactory();
      this.cam = await this.rf.createCamera({ 'name': 'Camera1' });
      this.cam.enabled = true;
      this.cam.position.z = Constants.CAMERA_POSITION_Z_INDEX;
      this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;

            this.image =
              await this.rf.createImage({ name: 'envImg3', uri: $rawfile('gltf/DamagedHelmet/glTF/Default_AO.jpg') });
            this.traversal(this.scene?.root);
            if (!this.geom) {
              this.geom = this.scene.getNodeByPath(Constants.CUBE_PATH) as Geometry;
              this.meshInfo += this.printAabb(this.geom.mesh.aabb, 'Mesh ');
      
              for (let i = 0; i < this.geom.mesh.subMeshes.length; i++) {
                this.meshInfo += '\n';
                this.meshInfo += this.printAabb(this.geom.mesh.aabb, 'Submesh[' + i + ']');
              }
            }
            this.materialOrg = this.geom.mesh.subMeshes[0].material;
          })
          .catch((error: string) => {
            Logger.error(`init error: ${error}`);
          });
      }
      

      }

      async createShader(): Promise<void> {
      if (!this.scene || !this.rf) {
      return;
      }
      if (!this.material) {
      this.material = await this.rf.createMaterial({ name: 'CustomMaterial' }, MaterialType.SHADER);
      }
      if (!this.shader) {
      this.shader = await this.rf.createShader({
      name: 'CustomShader',
      uri: $rawfile('shaders/custom_shader/custom_material_sample.shader')
      });
      }

      if (this.material) {
        this.material.colorShader = this.shader;
      }
      
      if (!this.geom) {
        this.geom = this.scene.getNodeByPath(Constants.CUBE_PATH) as Geometry;
      }
      
      this.geom.mesh.materialOverride = undefined;
      this.geom.mesh.subMeshes[0].material = this.material;
      
      if (this.material && this.material.colorShader && this.image) {
        (this.material.colorShader.inputs['BASE_COLOR_Image'] as Image) = this.image;
      }
      

      }

      build() {
      Column({ space: Constants.LIST_SPACE }) {
      Column() {
      if (this.sceneOpt) {
      Component3D(this.sceneOpt)
      .renderWidth(r('app.string.sixty_percent')) .renderHeight(r('app.string.sixty_percent'))
      .onAppear(() => {
      this.backAnimator?.play()
      })
      } else {
      Text(r('app.string.loading')) } } .height(Constants.THIRTY_PERCENT) .width(Constants.FULL_PERCENT) .backgroundColor(Color.White) .borderRadius(r('app.float.board_radius_normal'))

        Column() {
          Text(this.meshInfo)
            .fontSize($r('app.float.text_font_size'))
          Text(this.hierarchy)
            .fontSize($r('app.float.text_font_size'))
        }
        .borderRadius($r('app.float.board_radius_normal'))
        .backgroundColor(Color.White)
        .width(Constants.FULL_PERCENT)
        .padding($r('app.float.text_area_padding'))
        .alignItems(HorizontalAlign.Start)
      
        Blank()
          .layoutWeight(1)
      
        Button($r('app.string.create_shader'))
          .onClick(() => {
            this.createShader();
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.recovery_original_material'))
          .onClick(async () => {
            if (this.geom) {
              this.geom.mesh.materialOverride = undefined;
              this.geom.mesh.subMeshes[0].material = this.materialOrg as ShaderMaterial;
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.material_override'))
          .onClick(async () => {
            if (this.geom) {
              this.geom.mesh.subMeshes[0].material = this.materialOrg as ShaderMaterial;
            }
            if (this.geom && this.material) {
              this.geom.mesh.materialOverride = this.material as ShaderMaterial;
            }
          })
          .width(Constants.FULL_PERCENT)
      
        Button($r('app.string.back'))
          .onClick(() => {
            this.backAnimator?.cancel();
            router.back();
          })
          .width(Constants.FULL_PERCENT)
      }
      .width(Constants.FULL_PERCENT)
      .height(Constants.FULL_PERCENT)
      .padding($r('app.float.page_padding_left'))
      .justifyContent(FlexAlign.SpaceBetween)
      

      }
      }

相关推荐
脑极体1 小时前
应用开发“取经路”,华为应用市场送出全周期服务“助攻”
华为
Android技术栈1 小时前
鸿蒙(API 12 Beta6版)【ArkGraphics 3D场景搭建以及管理】方舟3D图形
3d·华为·harmonyos·鸿蒙·鸿蒙系统·openharmony·图形
若兰幽竹6 小时前
【鸿蒙HarmonyOS NEXT】UIAbility的生命周期
harmonyos
yiyiy111yiy9 小时前
关于HarmonyOS的学习
前端·javascript·学习·华为·harmonyos
谢道韫66610 小时前
HarmonyOs DevEco Studio小技巧8--string的用法
开发语言·harmonyos
嵌入式lx11 小时前
Deveco_studio js/Arkts sdk 安装失败
javascript·openharmony·deveco_studio
Verdure陌矣12 小时前
C++项目引入开源库bit7z
开发语言·c++·3d·github
jmoych13 小时前
解码数字化转型顶层规划(附236页PPT:xx企业数字化转型项目顶层规划方案)
安全·华为·制造
Android技术栈15 小时前
鸿蒙(API 12 Beta6版)GPU加速引擎服务【空域AI超分】
人工智能·华为·ai·harmonyos·鸿蒙·鸿蒙系统·openharmony
Android技术栈15 小时前
鸿蒙(API 12 Beta6版)超帧功能开发【ABR功能开发】
华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony·abr