同一只音箱,换成暖白塑料,表面呈现柔和、均匀的哑光;换成银色金属,机壳上出现集中的高光和细密横纹;换成灰棕织物,光线落在交错的经纬纱线上。模型轮廓没变,表面却有了三种不同的观感。
这一篇把音箱放进一间可以动手调整的材质实验室:切换三套外观、拖动材质参数、改变光照方向,再把机壳、顶盖和网罩单独拿出来观察。还可以打开网罩剖面,从开口看到内侧和边缘厚度。
先看材质变化
下面从左到右是暖白哑光塑料、银色拉丝金属、灰棕编织。三张真机画面使用同一视角和灯光:先比较高光是铺开还是集中,再看表面有没有方向纹和经纬起伏。


三套预设使用相同的几何结构和节点名称,每套都在 GLB 内嵌基础色、金属粗糙度和法线贴图。点击预设会加载对应的本地场景,因此无需运行时下载模型或图片。
一、从模型准备开始
模型来自项目中的参数化生成脚本,没有使用设计图自动生成模型。设计图用于确定界面和质感方向,真正加载到 ArkGraphics 3D 的资源是由顶点、法线、UV、三角形和材质组成的 GLB。
基础模型沿用结构探索台的音箱:机壳是有内外壁的圆筒,顶盖带浅槽,灯环由圆环构成,底座用旋转曲面生成。网罩孔壁和暗色衬层分别保留,亮面与孔内暗部共同表达孔洞深度。
资源制作按三个步骤执行:
node tools/prepare-material-lab.js
node tools/finish-material-models.js
node tools/validate-material-models.js
第一步从 orbitdesk_structure.glb 读取几何、生成 UV 与九张 PNG,输出塑料、金属、织物三套模型。第二步补切线并加入网罩截段。第三步检查数据范围、属性数量、切线正交性和内嵌图片。
输出文件位于 entry/src/main/resources/rawfile/material_lab/。每套模型有十个节点、九个网格:八个原有零件和一个额外的 GrilleSection 网罩截段,整机状态隐藏截段。
这是一套教学示意模型,不对应某款商品的真实电子或声学结构。
二、让贴图找到表面:UV 与切线
贴图是一张平面图片,需要 UV 坐标告诉渲染器:模型上的每个顶点对应图片的哪个位置。
圆筒侧壁适合圆柱映射。以模型局部坐标为输入,用绕轴角度计算 U,用高度计算 V:
const u = Math.atan2(z, x) / (2 * Math.PI) + 0.5;
const v = y;
U 绕一圈回到起点,V 随高度变化。采样器设置重复寻址,使图片能沿表面重复铺排。跨越 U 接缝的三角形需要单独展开:如果同一三角形的 U 最大值与最小值相差超过 0.5,就把接近 0 的坐标加 1,避免纹理在接缝处被拉过整张图片。
顶盖端面不能继续按圆柱方式铺图,否则中心附近会挤成一束。生成器对法线接近竖直的表面改用 X/Z 平面映射:
if (Math.abs(normalY) > 0.9) {
uv.push(x * 0.5 + 0.5, z * 0.5 + 0.5);
} else {
uv.push(u, y);
}
法线贴图还需要切线空间。处理脚本根据每个三角形的位置差和 UV 差计算切线,将它与法线正交化、归一化,并保存副切线方向的符号。退化 UV 使用与法线垂直的备用方向,避免出现无穷大或无效数值。
这里为接缝和切线展开了三角形顶点,换来直接、容易检查的数据布局;单个文件约 13 MB。用于更大产品库时,可以进一步合并具有相同位置、法线和 UV 的顶点,减少内存和包体。
三、九张贴图如何制作
每种材料有三张 512 × 512 PNG,全部由本地脚本生成。没有外部照片、下载素材或运行时图像服务。
| 贴图 | 用途 | 数据约定 |
|---|---|---|
| baseColor | 表面颜色和细微明暗纹路 | sRGB 颜色 |
| metallicRoughness | 控制反光形态与金属属性 | G 通道为粗糙度,B 通道为金属度,线性数据 |
| normal | 改变微小表面朝向 | 切线空间法线,线性数据 |
金属高度函数沿纵向变化,铺到圆柱上形成浅细横纹;塑料只保留很弱的微小变化。织物则以8像素为一格,根据格子奇偶交替选择经线和纬线,用纱线截面起伏、交织处压低的高度和细纤维纹理表达编织结构:
const weave = (x, y) => {
const period = 8;
const cx = Math.floor(x / period), cy = Math.floor(y / period);
const fx = ((x % period) + period) % period / period;
const fy = ((y % period) + period) % period / period;
const vertical = ((cx + cy) % 2 + 2) % 2 === 0;
const across = vertical ? fx : fy, along = vertical ? fy : fx;
const crown = Math.pow(Math.max(0, Math.sin(Math.PI * across)), 0.55);
return crown * (0.72 + 0.28 * Math.sin(Math.PI * along))
+ 0.035 * Math.sin(across * Math.PI * 12);
};
const fabricHeight = (x, y) => weave(x, y) * 0.65;
对高度函数取水平、竖直差分,可以得到局部坡度。把坡度转换为单位法线,再从 −1~1 编码到 0~255,就得到蓝紫色的法线图片。
基础粗糙度分别约为塑料 0.85、金属 0.18、织物 0.89~0.94。金属的金属度通道为 1,另外两种为 0。三套材料同时改变基础色、反光宽度和微表面纹理:暖白塑料几乎没有可见纹路,银色金属保留浅拉丝,灰棕织物有清晰的经纬交错。比较时相机、灯光方向和几何保持一致,颜色用于辅助辨认,表面质感仍由PBR参数与贴图共同决定。
拉丝纹路采用常规金属粗糙度材质,不是各向异性反射模型。织物是表面凹凸示意,没有模拟独立纤维。
PNG 写入 GLB 的二进制区后,通过 images[].bufferView 和 mimeType: image/png 引用,再由材质关联纹理索引。这样每个 GLB 都能独立携带本身所需的贴图。
四、在 ArkGraphics 3D 中加载并调整材质
页面先检测 Graphics3D 系统能力,再通过资源对象加载本地文件:
if (!canIUse('SystemCapability.ArkUi.Graphics3D')) {
throw new Error('设备不支持 Graphics3D');
}
const scene = await Scene.load($rawfile('material_lab/speaker-metal.glb'));
沿节点树找到 SpeakerBody、TopInset、GrilleDots 和 GrilleSection,读取几何节点子网格上的 MetallicRoughnessMaterial。直接修改导入材质的参数,基础色和法线图片会继续保留:
material.material.factor = {
x: 1,
y: this.roughness,
z: this.metallic,
w: 0.5
};
在本机 SDK 中,factor.y 对应粗糙度、factor.z 对应金属度,factor.w 对应反射率。不要把 GLB 纹理通道编号和这个向量的位置混淆。


页面显示的是"倍率"。例如金属粗糙度贴图约为 0.18,倍率调到 0.5 后,粗糙度约为 0.09。塑料和织物的金属度贴图本来就是 0,乘以任何倍率仍是 0;要观察金属度滑杆的明显变化,请选择金属预设。
五、用灯光揭示纹理

正面光便于看整体颜色,侧光有助于观察表面起伏,背面光用来检查边缘轮廓。三个按钮改变同一个方向光的旋转,另一个补光保持不变,因此对照时仍能看到暗部。
环境背景显式设为 BACKGROUND_NONE,相机使用不透明清屏色,使三维区域与页面的深色背景一致。光照对比采用按钮切换,同一时刻只有一个场景视图。
六、把零件单独拿出来看



近景只显示当前零件,其他产品节点暂时隐藏,灯光和相机继续工作。选择"整机"后恢复原有零件,剖面截段保持隐藏。相机围绕所选节点的中心转动,纵向角度限制在 −65°~65°。
this.productNodes.forEach((node: Node) => {
node.visible = selected ? node === selected : node.name !== 'GrilleSection';
});

剖面不是把截图裁掉一块,而是另一个真实网格。生成器从孔壁模型保留约 240° 的曲面,并在两个开口端加入径向封边。筛选单位是三角形,孔边界沿网格离散轮廓保留,因此它适合讲解剖面观察,不作为精密加工截面。
七、切换与退出的资源管理
每次切换预设先卸载旧场景,清空相机和材质引用,再异步加载新场景。加载操作保存代次编号;如果等待期间用户退出或重新加载,旧请求完成后立即销毁自身结果,避免把过期场景放回页面。
加载失败时显示真实错误并提供"重新加载",不以其他模型冒充当前预设。资源加载时禁用下方参数和选择控件,返回按钮始终可用。页面退出与组件销毁共用释放方法,允许重复调用。
八、材料实验室的复现入口
在项目中构建签名 HAP、安装到设备后,从"3D 实验空间"进入"材质实验室"。本例在 HUAWEI Mate 60 Pro、API 26 环境验证,运行无需相机或网络权限。
完整页面为 entry/src/main/ets/features/graphics3d/MaterialLabPage.ets,模型制作脚本与资源目录见前文。按照"生成 → 补切线和剖面 → 校验 → 构建"的顺序,可重做同一套材料并继续调整纹理密度、凹凸幅度和粗糙度分布。