鸿蒙图形开发【3D引擎接口示例】
介绍
本实例主要介绍3D引擎提供的接口功能。提供了@ohos.graphics.scene中接口的功能演示。 3D引擎渲染的画面会被显示在Component3D这一控件中。点击按钮触发不同的功能,用户可以观察渲染画面的改变。
效果预览
使用说明
- 在主界面,可以点击按钮进入不同的子页面,每一个子页面分别测试了一类3D引擎的接口功能,在子页面点击back返回主界面。
- 在container界面,点击按钮,可以添加、移除子节点,节点的结构信息已打印在界面上。在本示例中操作的子节点是一个头盔模型。
- 在node_base界面,点击按钮对节点的基础属性如位置、旋转、大小、可见性等进行操作。在本示例中操作的子节点是一个头盔模型。
- 在node_camera界面,点击按钮对相机的属性如投影、后处理等进行操作。
- 在node_light界面,点击按钮对灯光的类型、颜色、强度、阴影等进行操作。
- 在scene_environment界面,点击按钮对背景进行操作。
- 在scene_animation界面,点击按钮进行动画的播放、暂停等操作的功能。
- 在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)}
}
相关文章:

鸿蒙图形开发【3D引擎接口示例】
介绍 本实例主要介绍3D引擎提供的接口功能。提供了ohos.graphics.scene中接口的功能演示。 3D引擎渲染的画面会被显示在Component3D这一控件中。点击按钮触发不同的功能,用户可以观察渲染画面的改变。 效果预览 使用说明 在主界面,可以点击按钮进入不…...

C#实现数据采集系统-系统优化服务封装
系统优化-服务封装 现在我们调用modbustcp和mqtt都直接在Program,所有加载和功能都混合在一起,比较难以维护 类似asp.net core项目的Program.cs代码如下,构建服务配置和启动 要实现的效果,Main方法中就是一个服务启动,只需要几行代码 分析代码 这里分成两部分,一…...
数据结构与算法--栈、队列篇
一、计算机领域的地位 在计算机科学的广袤领域中,数据结构犹如一座精巧的大厦,为信息的存储和处理提供了坚实的框架。而在众多的数据结构中,栈和队列宛如两颗璀璨的明珠,各自闪耀着独特的光芒。 栈和队列虽然看似简单&…...
【程序、游戏、人生】致敬飞逝的3年和新的开始
人,总要向前看。 感谢之前关注的朋友,感谢各位朋友的私信、感谢关心的评论。 不要停下 20年:某银行业务三方开发。 21年:移动内部业务平台开发移动物联网商城开发储备TPL。 22年-至今:手游发行技术综合北漂 经历了行…...
第三届人工智能、人机交互与机器人国际会议
国际人工智能、人机交互和机器人会议是一项年度活动,汇集了来自世界各地的研究人员、从业者和行业专业人士,分享他们在人工智能、人际交互和机器人领域的知识和专业知识。在过去的几十年里,这些领域在计算能力、数据分析和机器学习技术的进步…...

AWS生成式AI项目的全生命周期管理
随着人工智能技术的迅速发展,生成式 AI 已成为当今最具创新性和影响力的领域之一。生成式 AI 能够创建新的内容,如文本、图像、音频等,具有广泛的应用前景,如自然语言处理、计算机视觉、创意设计等。然而,构建一个成功…...
windows go grpc
windows环境安装go grpc 的工具和插件 在Windows环境下,安装Protocol Buffers(proto)和gRPC相关的工具和插件,可以通过以下几个步骤进行 1.安装protoc 在git 仓库下载tag 包 https://github.com/protocolbuffers/protobuf/rele…...

Leetcode 第 135 场双周赛题解
Leetcode 第 135 场双周赛题解 Leetcode 第 135 场双周赛题解题目1:3222. 求出硬币游戏的赢家思路代码复杂度分析 题目2:3223. 操作后字符串的最短长度思路代码复杂度分析 题目3:3224. 使差值相等的最少数组改动次数思路代码复杂度分析 题目4…...
rpc的原理
RPC(Remote Procedure Call,远程过程调用)是一种编程模型,它允许开发者像调用本地函数一样调用位于不同进程或者不同机器上的函数或服务。这种抽象简化了分布式系统的开发,使得开发人员无需关注底层网络通信细节&#…...

【无线通信发展史-第二篇】,带你走进查利·奥古斯丁·库仑的世界,了解(库伦定律)-(扭秤实验)-(如何测量出静电力常量)
前言:用这几个问答形式来解读下我这个系列的来龙去脉。如果大家觉得本篇文章不水的话希望帮忙点赞收藏加关注,你们的鼓舞是我继续更新的动力。 我为什么会写这个系列呢? 首先肯定是因为我本身就是一名从业通信者,想着更加了解自…...

CAPL使用结构体的方式组装一条DoIP车辆声明消息(方法2)
在文章CAPL使用结构体的方式组装一条DoIP车辆声明消息(方法1)中,我们声明一个结构体DoIPMessage表示完整的DoIP车辆声明消息: 上半部分是DoIP报头通用部分(也就是所有类型的DoIP消息都有的),而payload是每个类型的DoIP消息独有的部分,对于车辆声明消息来说,用另一个结…...
基于Matlab的车牌识别系统设计与实现
基于Matlab的车牌识别系统设计与实现 摘要 随着智能交通系统的不断演进,车牌识别技术已成为提升交通管理效率与准确性的关键。本文深入探讨了基于Matlab平台的车牌识别系统设计与实现,该系统通过精细的图像预处理、高效的车牌定位算法、精准的字符分割…...

使用Cisco进行模拟RIP路由协议配置
实验四 RIP路由协议配置 文章目录 实验四 RIP路由协议配置1.实验目的2.实验流程3.RIPv1实验步骤4.RIPv2实验步骤 1.实验目的 1)理解RIP路由的原理 2)掌握RIP路由的配置方法 2.实验流程 开始→布置拓扑→配置IP地址→配置并验证RIPv1→配置并验证RIPv2…...

段页式存储-系统架构师(三十七)
1、一个完整的系统需要从不同的角度进行描述,下图属于软件架构设计中的(),用于()视图来描述软件系统。 问题1 A对象图 B时序图 C构件图 D类图 问题2 A进程 B开发 C物理 D逻辑 解析: 从…...

通过指令深入了解Linux
文章目录 1.简单介绍XShell1.1下载安装XShell1.2 使用XShell登录主机1.3 XShell下的复制粘贴 2. Linux下的基本指令2.1 ls指令2.1.1 对文件的理解2.1.2 目录下的隐藏文件 2.2 pwd指令2.3 cd指令2.3.1 Linux下目录结构的认识 2.4 touch指令2.5 mkdir指令2.6 clear指令 1.简单介绍…...

IP探针双端源码
源码耗费两年半的制作过程 将源码上传至你的服务器或你的主机 可以对接其他东西或者网站其他语言 使用方法 1.参数使用 http://域名/sc.php?id这是生成端 http://域名/sc1.php?id这是生成端生成的链接可以跳转链接 http://域名/ck.php?id这是查看IP 生成端,生成完…...

高中数学学科知识与教学能力
梳理...

Flink 实时数仓(七)【DWS 层搭建(一)流量域汇总表创建】
前言 今天开始 DWS 层的搭建,不知不觉又是周一,都忘了昨天是周末,近两年对我来说,周六日晚上八九点能打一小会篮球就算一周的休息了。不得不说自己真的是天生打工体质,每天不管多累,晚上十二点睡࿰…...

Python和PyCharm的安装激活及Python新手入门指南
一、软件介绍 Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。于 1989 年底由 Guido van Rossum 发明,第一个公开发行版发行于 1991 年。 当然也有很多小伙伴不清楚python与pycharm的区别和联系,接下来给大家简单介绍一下࿱…...
Apache Flink窗口机制解析:滚动窗口与滑动窗口的比较与应用
Apache Flink是一个开源的流处理框架,用于实现大规模数据流的处理和分析。在处理数据流时,窗口操作是一种常见的方法,它允许对数据流中连续的项目进行分组。Flink提供了多种窗口类型,其中滚动窗口(Tumbling Window&…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...