From b154ef099262178c3d66a055993ff65778c680c6 Mon Sep 17 00:00:00 2001 From: z60068389 Date: Mon, 23 Jun 2025 12:32:55 +0800 Subject: [PATCH] Added a demo of material interface properties Signed-off-by: zhulei --- .../src/main/ets/arkgraphic/material.ets | 94 +++++++++ .../entry/src/main/ets/common/utils.ets | 79 +++++++ .../entry/src/main/ets/material/matbase.ets | 198 ++++++++++++++++++ .../entry/src/main/ets/material/pbr.ets | 0 .../src/main/ets/material/pbradvanced.ets | 0 .../entry/src/main/ets/pages/Index.ets | 32 ++- .../resources/base/profile/main_pages.json | 6 +- 7 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/arkgraphic/material.ets create mode 100644 code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/common/utils.ets create mode 100644 code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/matbase.ets create mode 100644 code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbr.ets create mode 100644 code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbradvanced.ets diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/arkgraphic/material.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/arkgraphic/material.ets new file mode 100644 index 0000000000..0b78c70543 --- /dev/null +++ b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/arkgraphic/material.ets @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 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 logger from '../utils/Logger'; + +@Entry +@Component +struct materialPage { + build() { + Column({ space: 20 }) { + Button('Material general property settings') + .id('matbase') + .fontSize(16) + .fontWeight(500) + .margin({ top: 200, left: 6 }) + .onClick((): void => { + router.pushUrl({ + url: 'material/matbase' + }, router.RouterMode.Standard, (err) => { + if (err) { + logger.error('Invoke replaceUrl failed, code is %{public}s, message is %{public}s'); + return; + } + logger.info('Invoke replaceUrl succeeded.'); + }); + }) + .width('80%') + .height(40); + + Button('Create and configure PBR materials') + .id('pbr') + .fontSize(16) + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .onClick((): void => { + router.pushUrl({ + url: 'material/pbr' + }, router.RouterMode.Standard, (err) => { + if (err) { + logger.error('Invoke replaceUrl failed, code is %{public}s, message is %{public}s'); + return; + } + logger.info('Invoke replaceUrl succeeded.'); + }); + }) + .width('80%') + .height(40) + + Button('Configure advanced PBR material properties') + .id('pbradvanced') + .fontSize(16) + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .onClick((): void => { + router.pushUrl({ + url: 'material/pbradvanced' + }, router.RouterMode.Standard, (err) => { + if (err) { + logger.error('Invoke replaceUrl failed, code is %{public}s, message is %{public}s'); + return; + } + logger.info('Invoke replaceUrl succeeded.'); + }); + }) + .width('80%') + .height(40) + + Button('back') + .id('back') + .fontSize(16) + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .onClick(() => { + router.back(); + }) + .width('80%') + .height(40); + } + .width('100%'); + } +} \ No newline at end of file diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/common/utils.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/common/utils.ets new file mode 100644 index 0000000000..11f956ad28 --- /dev/null +++ b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/common/utils.ets @@ -0,0 +1,79 @@ +import scene3d from '@ohos.graphics.scene' + +function Sub(l: scene3d.Vec3, r: scene3d.Vec3): scene3d.Vec3 { + return { x: l.x - r.x, y: l.y - r.y, z: l.z - r.z }; +} +function Dot(l: scene3d.Vec3, r: scene3d.Vec3): number { + return l.x * r.x + l.y * r.y + r.z * l.z; +} +function Normalize(l: scene3d.Vec3): scene3d.Vec3 { + let d = Math.sqrt(Dot(l, l)); + return { x: l.x / d, y: l.y / d, z: l.z / d }; +} +function Cross(l: scene3d.Vec3, r: scene3d.Vec3): scene3d.Vec3 { + return { x: (l.y * r.z - l.z * r.y), y: (l.z * r.x - l.x * r.z), z: (l.x * r.y - l.y * r.x) }; +} +function Mul(l: scene3d.Quaternion, d: number): scene3d.Quaternion { + return { + x: l.x * d, + y: l.y * d, + z: l.z * d, + w: l.w * d + }; +} +function lookAt(node: scene3d.Node, eye: scene3d.Vec3, center: scene3d.Vec3, up: scene3d.Vec3) { + + let t: number; + + let q: scene3d.Quaternion = { + x: 0.0, + y: 0.0, + z: 0.0, + w: 0.0 + }; + let f = Normalize(Sub(center, eye)); + let m0 = Normalize(Cross(f, up)); + let m1 = Cross(m0, f); + let m2: scene3d.Vec3 = { x: -f.x, y: -f.y, z: -f.z }; + if (m2.z < 0) { + if (m0.x > m1.y) { + t = 1.0 + m0.x - m1.y - m2.z; + q = { + x: t, + y: m0.y + m1.x, + z: m2.x + m0.z, + w: m1.z - m2.y + }; + } else { + t = 1.0 - m0.x + m1.y - m2.z; + q = { + x: m0.y + m1.x, + y: t, + z: m1.z + m2.y, + w: m2.x - m0.z + }; + } + } else { + if (m0.x < -m1.y) { + t = 1.0 - m0.x - m1.y + m2.z; + q = { + x: m2.x + m0.z, + y: m1.z + m2.y, + z: t, + w: m0.y - m1.x + }; + } else { + t = 1.0 + m0.x + m1.y + m2.z; + q = { + x: m1.z - m2.y, + y: m2.x - m0.z, + z: m0.y - m1.x, + w: t + } + } + } + node.position = eye; + node.rotation = Mul(q, 0.5 / Math.sqrt(t)); +} + +export { lookAt }; \ No newline at end of file diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/matbase.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/matbase.ets new file mode 100644 index 0000000000..53611e8f16 --- /dev/null +++ b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/matbase.ets @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025 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 { Image, Shader, MaterialType, Material, ShaderMaterial, Animation, Environment, Container, SceneNodeParameters, + LightType, Light, Camera, SceneResourceParameters, SceneResourceFactory, Scene, Node, MetallicRoughnessMaterial, + Geometry, Vec3} from '@kit.ArkGraphics3D'; +import * as scene3d from '@ohos.graphics.scene' + +import { router } from '@kit.ArkUI'; +import logger from '../utils/Logger'; +import { lookAt } from '../common/utils'; + +@Entry +@Component +struct Model { + scene: Scene | null = null; + @State sceneOpt: SceneOptions | null = null; + cam: Camera | null = null; + @State shadowEnabled: boolean = false; // 默认关闭阴影 + @State planeMat: Material | null = null; // 用于按钮控制材质 + @State lightY: number = 3.5; + @State lightZ: number = 2.0; + @State camY: number = 2.0; + light: Light | null = null; + + onPageShow(): void { + this.init(); + } + + onPageHide(): void { + if (this.scene) { + this.scene.destroy(); + } + this.scene = null; + this.sceneOpt = null; + this.cam = null; + this.planeMat = null; + this.light = null; + } + + init(): void { + if (this.scene == null) { + // Load the model and place the gltf file in the related path. Use the actual path during loading. + // Switched from .gltf to .glb; same content, different format + Scene.load($rawfile('gltf/BrainStem/glTF/BrainStem.glb')) + .then(async (result: Scene) => { + if (!result.root) { + return; + } + this.scene = result; + let rf: SceneResourceFactory = result.getResourceFactory(); + // 创建一个灯光 + let light: Light = await rf.createLight({name: "MySptLight"}, LightType.SPOT); + light.color = {r:1, g:0, b:0, a:1}; + light.intensity = 1000; + light.shadowEnabled = true; + let pos: Vec3 = {x:0, y:this.lightY, z:this.lightZ}; + let targ: Vec3 = {x:0, y:0, z:0.0}; + let up: Vec3 = {x:0, y:1, z:0}; + lookAt(light, pos, targ, up); + this.light = light; + + // 创建一个平面接收阴影 + let planeGeom: scene3d.PlaneGeometry = new scene3d.PlaneGeometry(); + planeGeom.size = {x:10, y:10}; + let meshRes: scene3d.MeshResource = await rf.createMesh({name: "asdf"}, planeGeom); + let plane: Geometry = await rf.createGeometry({name: "shadowPlane"}, meshRes); + + let material = await rf.createMaterial({name: "asd"}, MaterialType.SHADER); + + // 设置阴影 + material.shadowReceiver = this.shadowEnabled; + + // 关闭阴影 + plane.mesh.subMeshes[0].material = material; + this.planeMat = material; + // Create a Camera. + this.cam = await rf.createCamera({ 'name': 'Camera' }); + // Set proper camera parameters. + this.cam.enabled = true; + lookAt(this.cam,{x:0, y:this.camY, z:-5}, {x:0, y:0, z:0}, {x:0, y:1, z:0}); + + this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions; + logger.info('initialization completely.'); + }) + .catch((reason: string) => { + console.log(reason); + }); + } + } + + build() { + Row() { + Column() { + if (this.sceneOpt) { + // Use Component3D to display the 3D scenario. + Component3D(this.sceneOpt); + } else { + Text('Loading···'); + } + Button(this.shadowEnabled ? 'Shadow: OFF' : 'Shadow: ON') + .id('shadow') + .margin({ top: 20, left: 6 }) + .width('60%') + .height(40) + .onClick(async (): Promise => { + this.shadowEnabled = !this.shadowEnabled; + if (this.planeMat) { + this.planeMat.shadowReceiver = this.shadowEnabled; + logger.info('Shadow toggled: ' + this.shadowEnabled); + } + }); + + // 灯光Y轴滑动条 + Text('Light height: ' + this.lightY.toFixed(2)).fontSize(12); + Slider({ + value: this.lightY, + min: 0, + max: 5, + step: 0.1, + style: SliderStyle.OutSet + }) + .showTips(false) + .onChange((value: number, mode: SliderChangeMode) => { + this.lightY = value; + if (mode === SliderChangeMode.End && this.light) { + let pos: Vec3 = {x:0, y:this.lightY, z:this.lightZ}; + lookAt(this.light, pos, {x:0, y:0, z:0}, {x:0, y:1, z:0}); + } + }) + .width('90%'); + + // 灯光Z轴滑动条 + Text('Light direction: ' + this.lightZ.toFixed(2)).fontSize(12); + Slider({ + value: this.lightZ, + min: -3, + max: 3, + step: 0.1, + style: SliderStyle.OutSet + }) + .showTips(false) + .onChange((value: number, mode: SliderChangeMode) => { + if (value === 0) { + value = 0.01; + } + this.lightZ = value; + if (mode === SliderChangeMode.End && this.light) { + let pos: Vec3 = {x:0, y:this.lightY, z:this.lightZ}; + lookAt(this.light, pos, {x:0, y:0, z:0}, {x:0, y:1, z:0}); + } + }) + .width('90%'); + + // 相机Y轴滑动条 + Text('Camera height: ' + this.camY.toFixed(2)).fontSize(12); + Slider({ + value: this.camY, + min: 0.1, + max: 10, + step: 0.1, + style: SliderStyle.OutSet + }) + .showTips(false) + .onChange((value: number, mode: SliderChangeMode) => { + this.camY = value; + if (mode === SliderChangeMode.End && this.cam) { + lookAt(this.cam, {x:0, y:this.camY, z:-5}, {x:0, y:0, z:0}, {x:0, y:1, z:0}); + } + }) + .width('90%'); + + Button('back') + .id('back') + .fontSize(16) + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .onClick(() => { + router.back(); + }) + .width('80%') + .height(40) + }.width('100%') + }.height('60%') + } +} diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbr.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbr.ets new file mode 100644 index 0000000000..e69de29bb2 diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbradvanced.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/material/pbradvanced.ets new file mode 100644 index 0000000000..e69de29bb2 diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/pages/Index.ets b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/pages/Index.ets index c1ce392bc1..94132c5873 100644 --- a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/pages/Index.ets +++ b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/ets/pages/Index.ets @@ -79,8 +79,36 @@ struct Index { } ); }) + + Button('Creating and Using ArkGraphics 3D Material') + .id('animation') + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .width('80%') + .height(40) + .onClick((): void => { + router.pushUrl({ url: 'arkgraphic/material' }, + router.RouterMode.Standard, + (err) => { + if (err) { + logger.error('Invoke replaceUrl failed, code is ' + err.code + ', message is ' + err.message); + return; + } + logger.info('Invoke replaceUrl succeeded.'); + } + ); + }) + + Button('back') + .id('back') + .fontWeight(500) + .margin({ top: 20, left: 6 }) + .width('80%') + .height(40) + .onClick((): void => { + router.back(); + }); } .width('100%'); } -} - +} \ No newline at end of file diff --git a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/resources/base/profile/main_pages.json b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/resources/base/profile/main_pages.json index 4158b4f8eb..2cdb1608eb 100644 --- a/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/resources/base/profile/main_pages.json +++ b/code/DocsSample/graphic/ArkGraphics3D/entry/src/main/resources/base/profile/main_pages.json @@ -4,8 +4,12 @@ "arkgraphic/scene", "arkgraphic/resource", "arkgraphic/animation", + "arkgraphic/material", "scene/init", "scene/camera", - "scene/light" + "scene/light", + "material/matbase", + "material/pbr", + "material/pbradvanced" ] } -- Gitee