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

avatar
作者
猴君
阅读量:0

介绍

本实例主要介绍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)   } } 

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!