《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》04:材质、纹理与灯光如何决定3D场景质感【鸿蒙心迹】

东西都放好了,为什么看起来像塑料?

前言

前几篇把工程骨架、坐标体系、模型加载都搞定了。房间里有桌子、椅子、沙发、台灯,结构上看起来像那么回事了。

但跑起来一看,总觉得哪里不对:东西都有,就是看起来特别假,像塑料玩具。

我一开始以为是模型质量不好,换了几个模型还是一样。后来调了半天灯光,才发现问题不在模型,也不在灯光,而是材质和灯光配合的方式不对。

3D 渲染里有个基本常识:你看到的颜色,不是物体本身的颜色,而是物体表面反射出来的光。材质决定了表面怎么反射光,灯光决定了有多少光打在表面上。这两个东西是配合工作的,单独调哪个都没用。

这一篇就解决这个问题:为什么同一个沙发模型,别人做出来像真的布料,我做出来像塑料?材质、纹理、灯光之间到底是怎么配合的。


一、先搞清楚:你看到的颜色是怎么来的

很多人刚开始做 3D 的时候,以为材质就是"给物体上个色":沙发是灰色的,桌子是木色的,墙是白色的。

但真实的渲染不是这么回事。打个比方:

  • 晚上关灯的时候,你看不到任何东西,不是因为物体没有颜色,是没有光
  • 用红色灯光照白墙,墙就变成红色的
  • 粗糙的墙看起来是哑光的,光滑的金属看起来是亮的

这就是材质和灯光配合的结果。材质描述的是"这个表面怎么反射光",不是"这个表面是什么颜色"。

先把基础概念列清楚:

概念 作用 举个例子
漫反射(Diffuse) 光线打到表面后,向各个方向反射 墙面、布料,看起来哑光
高光(Specular) 光线像镜子一样反射出去 金属、玻璃,有亮斑
粗糙度(Roughness) 表面有多粗糙 粗糙的漫反射多,光滑的高光强
环境光(Ambient) 整个场景的基础亮度 阴影里也不会全黑
主光源(Key Light) 主要的照明方向 太阳、主台灯

很多初学者只加一个方向光,强度拉满,结果整个场景平得像一张纸,没有明暗层次。就是因为只考虑了主光源,没考虑环境光和材质的反射特性。


二、第一版:只有颜色的材质

先写第一版的材质设置,看看为什么看起来像塑料:

typescript 复制代码
import { scene } from '@kit.ArkGraphics3DKit';

export class MaterialManager {
  /**
   * 给家具设置基础材质
   */
  setupBasicMaterial(node: scene.Node, color: {r: number, g: number, b: number}): void {
    const material = node.createComponent(scene.NodeComponentType.MATERIAL) as scene.Material;
    // 只设置了基础颜色
    material.albedo = color;
    console.info('MaterialManager: basic material set', color);
  }
}

然后调用它:

typescript 复制代码
// 沙发设成灰色
materialMgr.setupBasicMaterial(sofaNode, {r: 0.6, g: 0.6, b: 0.6});
// 桌子设成木色
materialMgr.setupBasicMaterial(deskNode, {r: 0.5, g: 0.35, b: 0.2});

这个代码跑起来,东西是有颜色的,但看起来就是塑料感。为什么?因为只有漫反射颜色,没有粗糙度,没有高光,没有纹理。所有东西都像塑料球一样,表面光滑均匀。

这就是塑料感的来源:表面太"完美"了,没有任何细节,没有粗糙度变化,没有纹理。


三、加纹理和粗糙度,马上就不一样了

改进一下:给材质加上纹理和粗糙度参数。

typescript 复制代码
export class MaterialManager {
  /**
   * 设置布料材质
   */
  setupFabricMaterial(node: scene.Node, texturePath: string): void {
    const material = node.createComponent(scene.NodeComponentType.MATERIAL) as scene.Material;

    // 基础颜色
    material.albedo = {r: 0.6, g: 0.6, b: 0.6};

    // 加载纹理贴图
    material.loadAlbedoTexture(texturePath);

    // 布料是粗糙的,粗糙度设高一点
    material.roughness = 0.8;

    // 布料没有金属感,金属度设0
    material.metallic = 0.0;

    console.info('MaterialManager: fabric material set');
  }

  /**
   * 设置木质材质
   */
  setupWoodMaterial(node: scene.Node, texturePath: string): void {
    const material = node.createComponent(scene.NodeComponentType.MATERIAL) as scene.Material;

    material.albedo = {r: 0.5, g: 0.35, b: 0.2};
    material.loadAlbedoTexture(texturePath);

    // 木头有一点光泽,但不是金属
    material.roughness = 0.6;
    material.metallic = 0.0;

    console.info('MaterialManager: wood material set');
  }

  /**
   * 设置金属材质
   */
  setupMetalMaterial(node: scene.Node): void {
    const material = node.createComponent(scene.NodeComponentType.MATERIAL) as scene.Material;

    material.albedo = {r: 0.8, g: 0.8, b: 0.8};

    // 金属很光滑,金属度高
    material.roughness = 0.2;
    material.metallic = 0.9;

    console.info('MaterialManager: metal material set');
  }
}

现在沙发是布料材质,粗糙度 0.8,看起来就哑光了;桌子是木头,粗糙度 0.6,有一点自然的光泽;台灯支架是金属,粗糙度 0.2,高光就出来了。

这时候再看场景,塑料感就少了很多。因为不同材质有不同的反射特性,不再是所有东西都一个样子。


四、灯光不是越多越好

材质调好了,接下来调灯光。很多人以为灯光越多越亮越好,结果加了五六个方向光,场景更假了。

灯光太多有两个问题:

  1. 所有阴影都被照亮了,没有明暗层次,像个白板
  2. 渲染性能下降,每个光源都要算一遍光照

正确的做法是分层次布光:

灯光类型 作用 数量 例子
主光源(Key Light) 主要照明,决定方向和阴影 1个 从窗户照进来的自然光
补光(Fill Light) 照亮阴影区域,不要全黑 1个 环境反射的光
轮廓光(Rim Light) 从背后打光,突出物体边缘 可选 让物体从背景里分离出来
环境光(Ambient) 整体基础亮度 1个 天空和地面反射的环境光

写个 LightManager:

typescript 复制代码
export class LightManager {
  setupSceneLights(scene: scene.Scene): void {
    // 1. 主光源:从斜上方照下来
    const keyLight = scene.createNode('KeyLight');
    const keyLightComp = keyLight.createComponent(scene.NodeComponentType.LIGHT) as scene.Light;
    keyLightComp.lightType = scene.LightType.DIRECTIONAL;
    keyLightComp.intensity = 1.2;  // 主光源最亮
    keyLight.position = {x: 5, y: 8, z: 3};
    keyLight.rotation = {x: -45, y: 30, z: 0};
    scene.getRoot().addChild(keyLight);

    // 2. 补光:从另一侧补一点
    const fillLight = scene.createNode('FillLight');
    const fillLightComp = fillLight.createComponent(scene.NodeComponentType.LIGHT) as scene.Light;
    fillLightComp.lightType = scene.LightType.DIRECTIONAL;
    fillLightComp.intensity = 0.4;  // 补光弱一点
    fillLight.position = {x: -3, y: 4, z: -2};
    scene.getRoot().addChild(fillLight);

    // 3. 环境光:整体基础亮度
    const ambientLight = scene.createNode('AmbientLight');
    const ambientComp = ambientLight.createComponent(scene.NodeComponentType.LIGHT) as scene.Light;
    ambientComp.lightType = scene.LightType.AMBIENT;
    ambientComp.intensity = 0.3;  // 环境光不要太亮,不然阴影没了
    scene.getRoot().addChild(ambientLight);

    console.info('LightManager: 3 lights setup done');
  }
}

主光源 1.2,补光 0.4,环境光 0.3。这个比例大概能做出自然的明暗层次:主光面亮,背光面有补光不会全黑,环境光保证最暗的地方也能看到细节。


五、做几个预设模式:日间、暖光、展示

真实应用里,用户可能想要不同的灯光氛围:白天模式、晚上暖光模式、展示模式。

做一个 SceneThemeManager,提供几个预设:

typescript 复制代码
export class SceneThemeManager {
  // 日间模式
  applyDaylight(scene: scene.Scene): void {
    // 冷色调,亮度高
    this.setLightColor(scene, {r: 1.0, g: 1.0, b: 1.0});
    this.setLightIntensity(scene, {key: 1.5, fill: 0.5, ambient: 0.4});
  }

  // 暖光模式(晚上)
  applyWarmLight(scene: scene.Scene): void {
    // 暖黄色,亮度低
    this.setLightColor(scene, {r: 1.0, g: 0.9, b: 0.7});
    this.setLightIntensity(scene, {key: 0.8, fill: 0.3, ambient: 0.2});
  }

  // 展示模式(产品展示用)
  applyShowcase(scene: scene.Scene): void {
    // 均匀照亮,突出产品
    this.setLightColor(scene, {r: 1.0, g: 1.0, b: 1.0});
    this.setLightIntensity(scene, {key: 1.0, fill: 0.8, ambient: 0.5});
  }
}

这样用户可以一键切换场景氛围,不用手动调每个灯光参数。


六、几个最容易踩的质感坑

把这一篇遇到的坑总结一下:

坑 现象 解决办法
只有基础颜色,没有粗糙度 所有东西都像塑料 不同材质设不同粗糙度,布料高、金属低
灯光越多越好 场景平得像白板,没有阴影 分层布光:1主光+1补光+1环境光就够
环境光太亮 阴影全没了,没有立体感 环境光别太亮,0.2~0.4差不多
纹理分辨率太高 内存涨,加载慢 家具纹理不用4K,1024足够
白色家具高光过曝 白家具一块死白 降低曝光,或者把粗糙度调高一点
所有材质都一个样 整个场景材质没有区分 布料、木头、金属、玻璃分别设参数

这些问题都不是 API 用错了,是对"材质+灯光"怎么配合没有概念。3D 渲染本质上是在模拟物理世界的光传播,不是简单地给物体上个颜色。


总结

第四篇的核心就一句话:质感不是靠模型好,是靠材质和灯光配合出来的。

  1. 材质描述的是表面怎么反射光,不是表面是什么颜色
  2. 不同材质(布料、木头、金属)有不同的粗糙度和金属度,质感就不一样
  3. 灯光不是越多越好,分层布光(主光+补光+环境光)就够了
  4. 可以做几个灯光预设模式,让用户一键切换氛围
  5. 塑料感通常是因为所有材质都一样光滑,加上灯光太平

SpaceRoom 现在看起来像个真实房间了。下一篇开始做交互:让用户能拖动视角、缩放、平移,真正走进这个 3D 房间里看看。

相关推荐
李游Leo2 小时前
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》07:复杂3D场景的帧率、内存与资源性能优化【鸿蒙心迹】
android·3d·性能优化·harmonyos
行者-全栈开发3 小时前
华为云码道 CodeArts 实测:让 AI 独立开发一个鸿蒙原生专注计时应用「刻循」
harmonyos·arkts·鸿蒙·ai 编程·华为云码道·codearts 代码智能体·专注计时
resh_people3 小时前
开源鸿蒙平台 KMP/CMP 三方库「kotlinx-datetime」适配全流程
华为·开源·harmonyos
可爱系程序猿14 小时前
Unity 材质预览才弹 d3dcompiler_47.dll?先分清编辑器 SDK、着色器缓存和玩家包
程序人生·3d·unity·电脑·材质
威哥爱编程17 小时前
HarmonyOS 7 自由多窗实战:supportWindowMode + 断点布局,大屏多任务主动接管
harmonyos·arkts
威哥爱编程17 小时前
HarmonyOS 7 应用接续实战:continuable + onContinue 跨设备无缝流转,手机编辑平板接着搞
harmonyos·arkts
威哥爱编程18 小时前
HarmonyOS 7 视觉 AI 进阶实战:人脸检测 + 通用文字识别(OCR)两步接入
harmonyos
Zldaisy3d20 小时前
从英特尔Connection苏州大会揭示算力狂飙下的散热范式革命,洞悉增材制造潜力重塑液冷散热版图
3d·制造
李游Leo1 天前
HarmonyOS 7 API 26 升级适配实战:API Change Assistant、targetSDKVersion 与兼容性回归
harmonyos