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

news2024/11/25 18:36:52

介绍

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

效果预览

1

2

使用说明

  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)
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【书生大模型实战营第三期】基础岛 第1关 书生大模型全链路开源体系

欢迎大家参与第三期书生大模型实战营&#xff01;&#xff01;&#xff01; 1. 书生浦语开源历程 从23年7月开始&#xff0c;直到今年7月&#xff0c;书生浦语先后开源了 InternLM、InternLM2 核性能更好的 InternLM2.5。 2. InternLM2.5 的优势 其中&#xff0c;最新的 Intern…

计算机语言-CSP初赛知识点整理

历年真题 [2020-CSP-J-第2题] 编译器的主要功能( ) A. 将源程序翻译成机器指令代码 B. 将源程序重新组合 C. 将低级语言翻译成高级语言 D. 将一种高级语言翻译成另一种高级语言 [2021-CSP-J-第1题] 以下不属于面向对象程序设计语言的是&#xff08;&#xff09;。 A. C B. Pyt…

【读点论文】场景图像中文本检测和识别关键技术研究-博士学位论文

文本是人类获取信息及社会交流的重要手段&#xff0c;从图像准确读取文本对人类的生产生活至关重要。现有方法通常将文本读取细分为文本检测、文本识别、端到端文本识别三个子任务。其中文本检测的目的是定位出图像中文本的位置&#xff0c;文本识别旨在识别出文本区域的字符序…

高仲富:49岁搞AI,白天种菜卖菜,晚上学数学搞程序

这是《开发者说》的第13期&#xff0c;本期我们邀请的开发者是高仲富&#xff0c;曾是一位数学老师&#xff0c;自学成为一名程序员&#xff0c;在北京漂过&#xff0c;后逃回了成都&#xff0c;一边与病魔抗争&#xff0c;一边写代码&#xff0c;一写就是15年&#xff0c;制作…

Electron 集成SQlite FTS5 实现百万级数据的倒排索引

背景 在产品迭代时&#xff0c;个人版产品已经将联系人和消息实时备份到本地&#xff0c;而消息的备份的目的仍然是为了快速查询对自己有用的上下文&#xff0c;并能快速定位到这些用户以及这些有用的信息。另外包括未来喂给 chatgpt-4o 的数据也是需要调用搜索获取的&#xff…

39. 647. 回文子串,516.最长回文子序列, 动态规划总结

确定dp数组以及下标的含义。如果大家做了很多这种子序列相关的题目&#xff0c;在定义dp数组的时候 很自然就会想题目求什么&#xff0c;我们就如何定义dp数组。绝大多数题目确实是这样&#xff0c;不过本题如果我们定义&#xff0c;dp[i] 为 下标i结尾的字符串有 dp[i]个回文串…

Weblogic 漏洞(详细)

一.weblogic弱口令 访问一下默认用户名&#xff1a;weblogic 密码&#xff1a; Oracle123 然后点击安装 然后再点击上传文件 将jsp木马打包&#xff0c;改为war上传一直下一步&#xff0c;最后点完成 然后使用工具连接 二.CVE-2017-3506 使用工具检测&#xff0c;存在漏洞 …

【C++】模拟实现stack

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 ​ 目录 一.了解项目功能 &#x1f4cc;了解stack官方标准 &#x1f4cc;了解模拟实现stack 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现stack成员变量 &…

[pdf]240道《软件方法》强化自测题业务建模需求分析共201页(202408更新)

链接&#xff1a; http://www.umlchina.com/url/quizad.html 如果需要提取码&#xff1a;umlc 文件夹中的“潘加宇《软件方法》强化自测题业务建模需求分析共240题.pdf”

【MATLAB第107期】基于MATLAB的Morris全局敏感性分析模型(无目标函数)

【MATLAB第107期】基于MATLAB的Morris全局敏感性分析模型&#xff08;无目标函数&#xff09; 一、原理介绍 1.基本原理&#xff1a; Morris方法采用概率均匀抽样的方式估计每个模型输入因子在输出结果中的重要性&#xff0c;通过比较系统在不同输入参数值上的输出结果变化来…

智观察 | 行业赛道里的AI大模型

‍ “AI改变世界”被炒得热火朝天&#xff0c;结果就换来AI聊天&#xff1f; 实际上&#xff0c;在日常娱乐之下&#xff0c;AI正在暗暗“憋大招”&#xff0c;深入各行各业&#xff0c;发挥更专业的作用。 自动驾驶 最近“萝卜快跑”霸榜热搜长达一周&#xff0c;让无人驾…

ECMAScript 6 入门 学习 日志笔记 2024/8/6 13:59

就读书籍: ECMAScript 6 入门 作者&#xff1a;阮一峰https://www.ruanyifeng.com/ 个人理解笔记 { } 块级 函数不能先用后声明 Let 优先函数表达 不可重复声明同一变量 { letfunction (){ } } 不谈其他,只要在{ } 中即可 ,简单暴力理解 const 和 let 类似 …

语言模型-神经网络模型(二)

神经网络模型语言模型 神经网络模型神经网络的分类神经网络模型和Ngram对比应用一-话者分离对比优劣 应用二-数字归一化应用三-文本打标 神经网络模型 释义&#xff1a; 与ngram模型相似使用&#xff0c;前n个词预测下一个词&#xff0c;输出在字表上的概率分布&#xff1b;过…

【Playwright+Python】使用Playwright进行API接口测试

在当今的自动化测试领域&#xff0c;结合Web UI和API接口测试已成为提升测试覆盖率和效率的关键。Playwright作为一个强大的自动化测试工具&#xff0c;除了在Web UI测试中大放异彩&#xff0c;还能与Python结合&#xff0c;实现强大的API接口测试功能。本文将带你探索如何使用…

面试软件测试岗:经典面试题!全背下来,月薪10K起步...

背题是一个快速应付面试的方式&#xff0c;但如果你想在软件测试行业稳步前进、步步为营的话&#xff0c;建议大家还是有序学习软件测试知识&#xff0c;积累够了&#xff0c;转行、跳槽都是顺其自然的。 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; …

告别录屏难题:2024四大热门电脑录屏软件推荐

进行在线教学、游戏直播、制作教程视频&#xff0c;录屏已成为我们日常生活和工作的重要需求。电脑怎么录屏&#xff1f;一款好用的录屏软件十分重要。今天&#xff0c;我们就来为大家推荐四款实用的电脑录屏工具。 1. 福昕录屏大师&#xff1a;专业级录屏&#xff0c;满足多样…

深入理解接口测试:实用指南与最佳实践(四)IHRM管理系统实战-项目分析

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这一阶段是接口测试的学习&#xff0c;我们接下来的讲解都是使用Postman这款工具&#xff0c;当然呢Postman是现在一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用…

前端获取视频文件宽高信息和视频时长

安装 yarn add video-metadata-thumbnails | npm install video-metadata-thumbnails引入依赖包 import { getMetadata } from video-metadata-thumbnails使用 if (file.name.includes(mp4)) {if (file) {try {console.log(file)// 获取视频的元数据const metadata await …

Linux基础笔记分享(超详细~)

文章目录 Linux基础1.基础概念2.基础命令命令行快捷键自动补全: tab移动光标快速删除翻看历史命令终止程序退出登录清屏 查看命令帮助alias命令别名-快捷键pwd-类似于地图cd-类似于传送术mkdir-类似于合成装备touch-创建文件ls-类似于查看装备tree-打印目录层级结构cp-复制命令…