☰
《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》04:材质、纹理与灯光如何决定3D场景质感【鸿蒙心迹】
2026/9/27 1:55:51 网站建设 项目流程

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

前言

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

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

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

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

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


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

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

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

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

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

先把基础概念列清楚:

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

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


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

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

import{scene}from'@kit.ArkGraphics3DKit';exportclassMaterialManager{/** * 给家具设置基础材质 */setupBasicMaterial(node:scene.Node,color:{r:number,g:number,b:number}):void{constmaterial=node.createComponent(scene.NodeComponentType.MATERIAL)asscene.Material;// 只设置了基础颜色material.albedo=color;console.info('MaterialManager: basic material set',color);}}

然后调用它:

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

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

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


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

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

exportclassMaterialManager{/** * 设置布料材质 */setupFabricMaterial(node:scene.Node,texturePath:string):void{constmaterial=node.createComponent(scene.NodeComponentType.MATERIAL)asscene.Material;// 基础颜色material.albedo={r:0.6,g:0.6,b:0.6};// 加载纹理贴图material.loadAlbedoTexture(texturePath);// 布料是粗糙的,粗糙度设高一点material.roughness=0.8;// 布料没有金属感,金属度设0material.metallic=0.0;console.info('MaterialManager: fabric material set');}/** * 设置木质材质 */setupWoodMaterial(node:scene.Node,texturePath:string):void{constmaterial=node.createComponent(scene.NodeComponentType.MATERIAL)asscene.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{constmaterial=node.createComponent(scene.NodeComponentType.MATERIAL)asscene.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:

exportclassLightManager{setupSceneLights(scene:scene.Scene):void{// 1. 主光源:从斜上方照下来constkeyLight=scene.createNode('KeyLight');constkeyLightComp=keyLight.createComponent(scene.NodeComponentType.LIGHT)asscene.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. 补光:从另一侧补一点constfillLight=scene.createNode('FillLight');constfillLightComp=fillLight.createComponent(scene.NodeComponentType.LIGHT)asscene.Light;fillLightComp.lightType=scene.LightType.DIRECTIONAL;fillLightComp.intensity=0.4;// 补光弱一点fillLight.position={x:-3,y:4,z:-2};scene.getRoot().addChild(fillLight);// 3. 环境光:整体基础亮度constambientLight=scene.createNode('AmbientLight');constambientComp=ambientLight.createComponent(scene.NodeComponentType.LIGHT)asscene.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,提供几个预设:

exportclassSceneThemeManager{// 日间模式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 房间里看看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询