当前位置: 首页 > news >正文

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

介绍

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

相关文章:

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

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

C#实现数据采集系统-系统优化服务封装

系统优化-服务封装 现在我们调用modbustcp和mqtt都直接在Program,所有加载和功能都混合在一起,比较难以维护 类似asp.net core项目的Program.cs代码如下,构建服务配置和启动 要实现的效果,Main方法中就是一个服务启动,只需要几行代码 分析代码 这里分成两部分,一…...

数据结构与算法--栈、队列篇

一、计算机领域的地位 在计算机科学的广袤领域中&#xff0c;数据结构犹如一座精巧的大厦&#xff0c;为信息的存储和处理提供了坚实的框架。而在众多的数据结构中&#xff0c;栈和队列宛如两颗璀璨的明珠&#xff0c;各自闪耀着独特的光芒。 栈和队列虽然看似简单&…...

【程序、游戏、人生】致敬飞逝的3年和新的开始

人&#xff0c;总要向前看。 感谢之前关注的朋友&#xff0c;感谢各位朋友的私信、感谢关心的评论。 不要停下 20年&#xff1a;某银行业务三方开发。 21年&#xff1a;移动内部业务平台开发移动物联网商城开发储备TPL。 22年-至今&#xff1a;手游发行技术综合北漂 经历了行…...

第三届人工智能、人机交互与机器人国际会议

国际人工智能、人机交互和机器人会议是一项年度活动&#xff0c;汇集了来自世界各地的研究人员、从业者和行业专业人士&#xff0c;分享他们在人工智能、人际交互和机器人领域的知识和专业知识。在过去的几十年里&#xff0c;这些领域在计算能力、数据分析和机器学习技术的进步…...

AWS生成式AI项目的全生命周期管理

随着人工智能技术的迅速发展&#xff0c;生成式 AI 已成为当今最具创新性和影响力的领域之一。生成式 AI 能够创建新的内容&#xff0c;如文本、图像、音频等&#xff0c;具有广泛的应用前景&#xff0c;如自然语言处理、计算机视觉、创意设计等。然而&#xff0c;构建一个成功…...

windows go grpc

windows环境安装go grpc 的工具和插件 在Windows环境下&#xff0c;安装Protocol Buffers&#xff08;proto&#xff09;和gRPC相关的工具和插件&#xff0c;可以通过以下几个步骤进行 1.安装protoc 在git 仓库下载tag 包 https://github.com/protocolbuffers/protobuf/rele…...

Leetcode 第 135 场双周赛题解

Leetcode 第 135 场双周赛题解 Leetcode 第 135 场双周赛题解题目1&#xff1a;3222. 求出硬币游戏的赢家思路代码复杂度分析 题目2&#xff1a;3223. 操作后字符串的最短长度思路代码复杂度分析 题目3&#xff1a;3224. 使差值相等的最少数组改动次数思路代码复杂度分析 题目4…...

rpc的原理

RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;是一种编程模型&#xff0c;它允许开发者像调用本地函数一样调用位于不同进程或者不同机器上的函数或服务。这种抽象简化了分布式系统的开发&#xff0c;使得开发人员无需关注底层网络通信细节&#…...

【无线通信发展史-第二篇】,带你走进查利·奥古斯丁·库仑的世界,了解(库伦定律)-(扭秤实验)-(如何测量出静电力常量)

前言&#xff1a;用这几个问答形式来解读下我这个系列的来龙去脉。如果大家觉得本篇文章不水的话希望帮忙点赞收藏加关注&#xff0c;你们的鼓舞是我继续更新的动力。 我为什么会写这个系列呢&#xff1f; 首先肯定是因为我本身就是一名从业通信者&#xff0c;想着更加了解自…...

CAPL使用结构体的方式组装一条DoIP车辆声明消息(方法2)

在文章CAPL使用结构体的方式组装一条DoIP车辆声明消息(方法1)中,我们声明一个结构体DoIPMessage表示完整的DoIP车辆声明消息: 上半部分是DoIP报头通用部分(也就是所有类型的DoIP消息都有的),而payload是每个类型的DoIP消息独有的部分,对于车辆声明消息来说,用另一个结…...

基于Matlab的车牌识别系统设计与实现

基于Matlab的车牌识别系统设计与实现 摘要 随着智能交通系统的不断演进&#xff0c;车牌识别技术已成为提升交通管理效率与准确性的关键。本文深入探讨了基于Matlab平台的车牌识别系统设计与实现&#xff0c;该系统通过精细的图像预处理、高效的车牌定位算法、精准的字符分割…...

使用Cisco进行模拟RIP路由协议配置

实验四 RIP路由协议配置 文章目录 实验四 RIP路由协议配置1.实验目的2.实验流程3.RIPv1实验步骤4.RIPv2实验步骤 1.实验目的 1&#xff09;理解RIP路由的原理 2&#xff09;掌握RIP路由的配置方法 2.实验流程 开始→布置拓扑→配置IP地址→配置并验证RIPv1→配置并验证RIPv2…...

段页式存储-系统架构师(三十七)

1、一个完整的系统需要从不同的角度进行描述&#xff0c;下图属于软件架构设计中的&#xff08;&#xff09;&#xff0c;用于&#xff08;&#xff09;视图来描述软件系统。 问题1 A对象图 B时序图 C构件图 D类图 问题2 A进程 B开发 C物理 D逻辑 解析&#xff1a; 从…...

通过指令深入了解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 生成端&#xff0c;生成完…...

高中数学学科知识与教学能力

梳理...

Flink 实时数仓(七)【DWS 层搭建(一)流量域汇总表创建】

前言 今天开始 DWS 层的搭建&#xff0c;不知不觉又是周一&#xff0c;都忘了昨天是周末&#xff0c;近两年对我来说&#xff0c;周六日晚上八九点能打一小会篮球就算一周的休息了。不得不说自己真的是天生打工体质&#xff0c;每天不管多累&#xff0c;晚上十二点睡&#xff0…...

Python和PyCharm的安装激活及Python新手入门指南

一、软件介绍 Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。于 1989 年底由 Guido van Rossum 发明&#xff0c;第一个公开发行版发行于 1991 年。 当然也有很多小伙伴不清楚python与pycharm的区别和联系&#xff0c;接下来给大家简单介绍一下&#xff1…...

Apache Flink窗口机制解析:滚动窗口与滑动窗口的比较与应用

Apache Flink是一个开源的流处理框架&#xff0c;用于实现大规模数据流的处理和分析。在处理数据流时&#xff0c;窗口操作是一种常见的方法&#xff0c;它允许对数据流中连续的项目进行分组。Flink提供了多种窗口类型&#xff0c;其中滚动窗口&#xff08;Tumbling Window&…...

为什么《程序员修炼之道》评分能到 9.1?

大家好&#xff0c;我是 方圆。开始接触到《程序员修炼之道&#xff1a;通向务实的最高境界》这本书是在豆瓣图书的高分榜单上&#xff0c;它的评分高达 9.1&#xff0c;其中有条蛮有意思的书评非常吸引我&#xff1a;“这本书我读过 5 遍信不信&#xff0c;每个字都磨出了感情…...

接口自动化测试框架中动态参数接口,加密接口,签名接口你们是怎么处理的?

动态参数&#xff1a;可通过热加载形式&#xff08;在代码执行过中自动去yaml里面执行外部的函数&#xff09; 接口测试加密解密简介&#xff1a; 对称加密&#xff08;私钥加密&#xff0c;只有一个密钥&#xff09;AES,DES,BASE64 特点是&#xff1a;加密和解密有相同的密钥…...

【hadoop】常用命令

集群信息 查看hadoop版本 hadoop version查询hdfs系统中的namenode # 方式一 hdfs getconf -namenodes# 方式二 hdfs getconf -confKey dfs.namenode.http-address获取NameNode restful接口 hdfs getconf -confKey dfs.namenode.http-address hdfs getconf -confKey dfs.na…...

时间同步--- ntp与ptp

时间同步 1. 什么是NTP时间&#xff1f;什么是PTP时间&#xff1f; NTP时间&#xff08;Network Time Protocol 时间&#xff09;: NTP即网络时间协议&#xff08;Network Time Protocol&#xff09;&#xff0c;它是一种用于同步计算机时间的网络协议。NTP可以将所有参与的计…...

CSDN 僵尸粉 机器人

CSDN 僵尸粉 机器人 1. 前言 不知道什么时候开始每天创作2篇就有1500流量爆光&#xff0c;每次都能收获一些关注和收藏&#xff0c;感觉还是挻开心的感觉CSDN人气还是挻可以的以前各把月一个收藏和关注都没有写的动力了。 2. 正文 后面又连接做了2天的每日创建2篇任务&…...

【Material-UI】File Upload Button 组件详解

文章目录 一、基础实现1. component"label"2. 隐藏的输入元素 二、样式和交互增强1. 自定义按钮样式2. 交互提示 三、支持多文件上传四、无障碍性&#xff08;Accessibility&#xff09;1. 提供 aria-label 或 aria-labelledby2. 支持键盘导航 五、高级用法和集成1. …...

计算机组成原理 - 中央处理器

中央处理器 考纲内容 CPU的功能和基本结构指令执行过程数据通路的功能和基本结构控制器的功能和工作原理异常和中断机制 异常和终端的基本概念&#xff1b;异常和中断的分类&#xff1b;异常和中断的检测与响应指令流水线 指令流水线的基本概念&#xff1b;指令流水线的基本实…...

C++笔试练习笔记【5】:最小花费爬楼梯(有题目链接) 初识动态规划

文章目录 题目思路代码 动态规划简介**一、什么是动态规划****二、动态规划的应用场景****三、动态规划的基本步骤****四、动态规划的优缺点** 题目 题目链接&#xff1a;https://www.nowcoder.com/practice/9b969a3ec20149e3b870b256ad40844e?tpld230&tpld39751&ru/…...

数据结构----------贪心算法

什么是贪心算法&#xff1f; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在问题求解过程中&#xff0c;每一步都采取当前状态下最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致最终的全局最优解的算法策略。 贪心算法的核心思想是做选择时&…...

C++初学(11)

不知不觉就第11篇了QWQ 11.1、指针和自由存储空间 之前提到了计算机程序在存储数据时必须跟踪的3个基本属性&#xff1a; &#xff08;1&#xff09;信息存储在何处&#xff1b; &#xff08;2&#xff09;存储的值为多少&#xff1b; &#xff08;3&#xff09;存储的信息…...