赞
踩
本实例主要介绍3D引擎提供的接口功能。提供了@ohos.graphics.scene中接口的功能演示。 3D引擎渲染的画面会被显示在Component3D这一控件中。点击按钮触发不同的功能,用户可以观察渲染画面的改变。
使用说明
添加、移除、遍历节点的功能接口参考:ContainerPage.ets
/* * 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
/* * 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
/* * 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
/* * 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
/* * 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
/* * 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
/* * 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) } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。