HarmonyOS 鸿蒙Next中scene node directional light方向设置无效

HarmonyOS 鸿蒙Next中scene node directional light方向设置无效 文档链接

文档中directionallight继承于Light类,Light类继承于Node类,Node存在成员rotation,修改该成员无法实现方向光的旋转,应该如何控制方向光的方向?

9 回复

尊敬的开发者,您好
关于您反馈的问题

如下代码,设置DirectionalLight的rotation属性可以明显观察到立方体的阴影变化。

import {
  Scene,
  RenderContext,
  SceneResourceParameters,
  CustomGeometry,
  RenderResourceFactory,
  LightType,
  MaterialType,
  Material,
  PrimitiveTopology,
  SceneResourceFactory,
  Camera,
  Node,
  Container,
  MeshResource,
  Quaternion,
  DirectionalLight,
  Geometry
} from '@kit.ArkGraphics3D';

@Entry
@Component
struct ContainerPage {
  @State sceneOpt: SceneOptions | null = null;
  @State hierarchy: string = '';
  scene: Scene | null = null;
  cam: Camera | null = null;
  dirLight: DirectionalLight | null = null;
  geometryNode: Geometry | null = null;

  traversal(node: Node | null): void {
    if (!node) {
      return;
    }
    this.hierarchy += node.path + (node.name ? ` [${node.name}]` : '') + '\n';
    const container: Container<Node> = node.children;
    const 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.dirLight = null;
    this.geometryNode = null;
  }

  init(): void {
    if (this.scene === null) {
      const renderContext: RenderContext | null = Scene.getDefaultRenderContext();
      if (!renderContext) {
        console.error('获取默认渲染上下文失败');
        return;
      }
      const renderResourceFactory: RenderResourceFactory = renderContext.getRenderResourceFactory();
      renderResourceFactory.createScene().then(async (result: Scene) => {
        if (!result || !result.root) {
          console.error('创建场景失败');
          return;
        }
        this.scene = result;
        const rf: SceneResourceFactory = this.scene.getResourceFactory();
        this.cam = await rf.createCamera({ name: 'MainCamera' });
        this.cam.enabled = true;
        this.cam.position = { x: 0, y: 2, z: 5 };
        this.cam.fov = 60 * Math.PI / 180;
        this.cam.nearPlane = 0.1;
        this.cam.farPlane = 100;
        this.scene.root?.children.append(this.cam);

        this.dirLight = await rf.createLight(
          { name: 'Sun' },
          LightType.DIRECTIONAL
        ) as DirectionalLight;
        this.dirLight.enabled = true;
        this.dirLight.color = {
          r: 1.0,
          g: 1.0,
          b: 1.0,
          a: 1.0
        };
        this.dirLight.intensity = 2.0;
        this.dirLight.shadowEnabled = true;
        this.dirLight.rotation = eulerToQuaternion(45, 0, 0);
        this.scene.root?.children.append(this.dirLight);

        const meshResource = await createMeshResource();
        this.geometryNode = await rf.createGeometry(
          { name: 'cubeGeometry' },
          meshResource
        );
        const pbrMat: Material = await rf.createMaterial(
          { name: 'pbrMat' },
          MaterialType.METALLIC_ROUGHNESS
        );
        this.geometryNode.mesh.subMeshes[0].material = pbrMat;
        this.scene.root?.children.append(this.geometryNode);

        this.hierarchy = '';
        this.traversal(this.scene.root);
        this.sceneOpt = {
          scene: this.scene,
          modelType: ModelType.SURFACE
        } as SceneOptions;
      }).catch((reason: string) => {
        console.error(`场景初始化失败: ${reason}`);
      });
    }
  }

  rotateLight(degrees: number): void {
    if (!this.dirLight) {
      return;
    }
    const deltaRot = eulerToQuaternion(0, degrees, 0); // 绕Y轴旋转
    this.dirLight.rotation = multiplyQuaternions(this.dirLight.rotation, deltaRot);
  }

  // 重置平行光
  resetLight(): void {
    if (!this.dirLight) {
      return;
    }
    this.dirLight.rotation = eulerToQuaternion(45, 0, 0); // 恢复初始方向
  }

  build() {
    Column() {
      Column() {
        if (this.sceneOpt) {
          Component3D(this.sceneOpt)
            .renderWidth('100%')
            .renderHeight('100%')
        } else {
          Text('3D场景加载中...')
            .fontSize(20)
            .fontColor(Color.Gray)
        }
      }
      .height('50%')
      .width('100%')
      .backgroundColor(Color.White)

      Row({ space: 16 }) {
        Button('向左旋转45°')
          .onClick(() => this.rotateLight(-45))
          .fontSize(14)
        Button('向右旋转45°')
          .onClick(() => this.rotateLight(45))
          .fontSize(14)
        Button('重置方向')
          .onClick(() => this.resetLight())
          .fontSize(14)
      }
      .padding(16)
      .justifyContent(FlexAlign.Center)

      Column() {
        Text('场景节点层次:')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 8 })
        Scroll() {
          Text(this.hierarchy)
            .fontSize(12)
            .fontColor(Color.Gray)
            .lineHeight(20)
        }
        .height('100%')
      }
      .height('30%')
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Gray)
  }
}

function eulerToQuaternion(x: number, y: number, z: number): Quaternion {
  const xRad = x * Math.PI / 180;
  const yRad = y * Math.PI / 180;
  const zRad = z * Math.PI / 180;
  const cx = Math.cos(xRad / 2);
  const sx = Math.sin(xRad / 2);
  const cy = Math.cos(yRad / 2);
  const sy = Math.sin(yRad / 2);
  const cz = Math.cos(zRad / 2);
  const sz = Math.sin(zRad / 2);
  return {
    x: sx * cy * cz + cx * sy * sz,
    y: cx * sy * cz - sx * cy * sz,
    z: cx * cy * sz + sx * sy * cz,
    w: cx * cy * cz - sx * sy * sz
  };
}

function multiplyQuaternions(q1: Quaternion, q2: Quaternion): Quaternion {
  return {
    x: q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
    y: q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
    z: q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
    w: q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
  };
}

function createMeshResource(): Promise<MeshResource> {
  const renderContext: RenderContext | null = Scene.getDefaultRenderContext();
  if (!renderContext) {
    return Promise.reject(new Error("RenderContext is null"));
  }
  const renderResourceFactory: RenderResourceFactory = renderContext.getRenderResourceFactory();
  const geometry = new CustomGeometry();
  geometry.vertices = [
    // 前面 (z=0.5)
    { x: -0.5, y: -0.5, z: 0.5 },
    { x: 0.5, y: -0.5, z: 0.5 },
    { x: 0.5, y: 0.5, z: 0.5 },
    { x: -0.5, y: 0.5, z: 0.5 },
    // 后面 (z=-0.5)
    { x: -0.5, y: -0.5, z: -0.5 },
    { x: 0.5, y: -0.5, z: -0.5 },
    { x: 0.5, y: 0.5, z: -0.5 },
    { x: -0.5, y: 0.5, z: -0.5 },
    // 右面 (x=0.5)
    { x: 0.5, y: -0.5, z: 0.5 },
    { x: 0.5, y: -0.5, z: -0.5 },
    { x: 0.5, y: 0.5, z: -0.5 },
    { x: 0.5, y: 0.5, z: 0.5 },
    // 左面 (x=-0.5)
    { x: -0.5, y: -0.5, z: 0.5 },
    { x: -0.5, y: -0.5, z: -0.5 },
    { x: -0.5, y: 0.5, z: -0.5 },
    { x: -0.5, y: 0.5, z: 0.5 },
    // 上面 (y=0.5)
    { x: -0.5, y: 0.5, z: 0.5 },
    { x: 0.5, y: 0.5, z: 0.5 },
    { x: 0.5, y: 0.5, z: -0.5 },
    { x: -0.5, y: 0.5, z: -0.5 },
    // 下面 (y=-0.5)
    { x: -0.5, y: -0.5, z: 0.5 },
    { x: 0.5, y: -0.5, z: 0.5 },
    { x: 0.5, y: -0.5, z: -0.5 },
    { x: -0.5, y: -0.5, z: -0.5 }
  ];
  geometry.indices = [
    0, 1, 2, 2, 3, 0, // 前面
    4, 5, 6, 6, 7, 4, // 后面
    8, 9, 10, 10, 11, 8, // 右面
    12, 13, 14, 14, 15, 12, // 左面
    16, 17, 18, 18, 19, 16, // 上面
    20, 21, 22, 22, 23, 20 // 下面
  ];
  geometry.topology = PrimitiveTopology.TRIANGLE_LIST;
  geometry.normals = [
    // 前面:法线(0,0,1)
    { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 },
    // 后面:法线(0,0,-1)
    { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: -1 },
    // 右面:法线(1,0,0)
    { x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 },
    // 左面:法线(-1,0,0)
    { x: -1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 },
    // 上面:法线(0,1,0)
    { x: 0, y: 1, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: 1, z: 0 },
    // 下面:法线(0,-1,0)
    { x: 0, y: -1, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: -1, z: 0 }
  ];
  geometry.uvs = [
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 },
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 },
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 },
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 },
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 },
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 }
  ];
  const sceneResourceParameter: SceneResourceParameters = { name: "cubeMesh" };
  return renderResourceFactory.createMesh(sceneResourceParameter, geometry);
}

更多关于HarmonyOS 鸿蒙Next中scene node directional light方向设置无效的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


我最近也在做AR,DirectionalLight是平行光,其照射方向由节点在3D空间中的旋转决定。

cke_1560.jpeg

let sceneLight = result.root?.getNodeByPath("Directional Light") as Light;
if (sceneLight) {
    sceneLight.enabled = true;
    sceneLight.color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
    sceneLight.intensity = 1.5;
    sceneLight.rotation = {
        x: 0.0,
        y: 1.0,
        z: 0.0,
        w: 0.0
    };
}

可以先把问题拆成两步验证:rotation 是否真的改变了节点姿态,以及姿态改变后光照方向是否跟着变。如果 rotation/forward 都变化了,但光照结果不变,更像是当前 DirectionalLight 的光照方向没有按普通 Node transform 链路更新。

建议做最小 demo:一个固定模型、一个 DirectionalLight,分别测试直接改 light.rotation、把 light 挂到父 Node 后改 parent.rotation、以及重新创建 light 后设置初始姿态。如果只有父节点方案有效,就用父节点包一层作为临时方案;如果三种都无效,建议带最小 demo 反馈文档/API 口径,因为“继承 Node”不一定等于光照方向完整跟随可见节点旋转。

  1. 可以创建个node, 吧平行光节点加入node再做rotation,试试。
    node.children.append(light);
    node.rotation = 
    
  2. 对于平行光旋转的话,是什么场景需要旋转呢?因为它的direction才是影响较大的。它主要就是模拟环境光(太阳光)的。

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

从文档来看,DirectionalLight 确实继承自 Node,因此 rotation 属性本身是可以设置的。

不过需要注意一点:方向光没有“朝向某个位置”的概念,它的方向实际上来自节点自身的坐标系方向(Forward 向量)。也就是说,引擎最终使用的是节点变换后的方向向量参与光照计算,而不是单独维护一个 direction 属性。

因此控制方向光方向的正确方式就是修改:

directionalLight.rotation

或者修改父节点的变换:

parentNode.rotation

然后让方向光挂在该节点下面。

建议验证一下:

console.info(JSON.stringify(directionalLight.forward))

修改 rotation 前后查看 forward 是否发生变化。

如果:

rotation变化
↓
forward也变化
↓
光照方向没变化

那就不是 API 使用问题了,而更可能是:

  • 场景环境光过强,看不出方向光变化
  • 当前模型材质对光照不敏感
  • 阴影未开启
  • 当前版本存在 DirectionalLight 渲染侧问题

另外很多开发者容易把 DirectionalLight 当成 SpotLight 来理解,实际上方向光是无限远平行光:

DirectionalLight
    只关心方向
    不关心位置

SpotLight
    同时关心位置和方向

所以单纯修改 position 往往不会改变照射效果,而修改 rotation 才是正确思路。希望能帮到你~~~

从你描述看,DirectionalLight 虽然在类型层级上继承了 Node,但它的“光照方向”不一定等同于普通模型节点的可见旋转。很多 3D 引擎里方向光的方向由光源自身的 direction/朝向计算链路决定,普通 rotation 只改节点变换,未必会重新驱动光照方向。建议先确认当前 Scene Kit 是否有专门的方向/姿态接口或示例;如果没有,优先尝试把光源挂到父节点上旋转父节点,或用位置 + lookAt/目标点这类方式间接控制。若父节点旋转也无效,那更像是文档继承关系没有把 DirectionalLight 的方向控制讲清楚,建议带最小 demo 反馈文档/API 口径问题。

在 HarmonyOS 鸿蒙 Next 中,方向光方向无效通常源于以下两点:

  1. 方向向量未采用世界坐标系或归一化处理,需确保调用 setDirection 时传入的向量为单位向量且为世界空间坐标。
  2. 场景节点(SceneNode)的变换未正确应用,检查方向光是否挂在正确的节点下,且该节点未附加其他旋转影响。

请核查代码中方向设置语句与场景图结构是否匹配。

方向光不通过节点的 rotation 控制方向,它有自己的 direction 属性(Vector3 类型)。直接设置该向量即可改变光照方向,无需依赖节点变换。示例:

import { Vector3 } from '@ohos.math';

// 假设已创建 directionalLight : DirectionalLight
directionalLight.direction = new Vector3(1.0, -1.0, 0.0); // 从右上前方照射

若向量长度非零,引擎会自动归一化。确保光源节点已添加到场景中。

回到顶部