关于 3d-wood-lighting-scorecard
这是面向 Three.js 场景制作者的木材照明与材质技能,涵盖木纹、窗光光束、尘埃和最终色调映射。它还提供评分脚本,用固定渲染条件衡量画面并检查各层效果。
主要功能
- 01程序化生成木纹材质
- 02配置单窗主光与环境补光
- 03添加光束和尘埃效果
- 04执行 ACES 最终色调映射
- 05运行画面评分与逐层消融
适用场景
- 01制作暗室中的木工作品场景
- 02评估木纹照明和阴影表现
- 03比较光束、尘埃等图层影响
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill 3d-wood-lighting-scorecard安装后在 Claude Code 中输入/3d-wood-lighting-scorecard确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
demo.htmlPROMPT.mdagents/openai.yamlreferences/last-scorecard.jsonmetrics.jsrubric.mdscorecard.mjs详细介绍
3D 木材光照评分卡
木材只有在黑暗的房间里被一道掠射的定向主光照亮时,才看起来像木材。因此,木纹、阴影接缝和光束都由同一扇窗户照亮,在最后统一进行色调映射,并在最终画面上逐项测量,而不是凭感觉评判。
已验证的技术栈:Three.js r169(unpkg)、WebGL2、通过 MeshPhysicalMaterial.onBeforeCompile 打补丁的 GLSL、半浮点 MSAA 渲染目标,以及手写的 ACES 阶段。演示位于 demo.html;数据位于 references/。
适用范围。这是用于单个受光室内场景的光照与材质层。若需要让阳光光束被真实树木和屋顶轮廓遮挡,请使用 3d-sky-rays(屏幕空间遮挡);这里的光束是用平面制作的。若要使用扫描或绘制的 PBR 贴图、管理纹素预算并进行交付,请使用 3d-high-resolution-textures;这里的木材则由一个共享的 1024 噪声场程序化生成。若要在深色金属上添加明亮的边缘光,请使用 3d-specular-rim-glow;玻璃应使用影棚盒阶梯方案,而不是本方案。若只需要评分方法,请使用 workflow-score-to-target;本技能提供木材评分标准和测量工具。
内容提取自 Kibori(木彫),一个由滚动驱动的 WebGL 木工页面:移植了其中的 woodMat、buildEnvironment、主光装置、光束平面、尘埃和最终阶段,并将窗户移到了光束所需的位置。
构建顺序
- 渲染器:
NoToneMapping、LinearSRGBColorSpace、使用samples: 4的HalfFloatType渲染目标、autoClear = false。将所有内容(包括叠加光束和尘埃)绘制到该渲染目标中。在最后一个全屏阶段统一进行色调映射。 - 一扇窗户。让发光窗纸、主光方向、光束平面和 PMREM 环境中的窗户卡片处于同一条线上。不要添加其他光源。
- 主光使用
DirectionalLight0xfff2df,强度 2.0,4096 阴影贴图(低于 820 px 时使用 2048),normalBias0.0045(2048 时使用 0.0085),bias-0.00025,静态房间中设置shadowMap.autoUpdate = false。让真实墙面和真实开口投射阴影:工作台上的光斑便会呈现窗户的形状。 - 从相机一侧添加 0xaec7df、强度 0.60-0.76 的补光;从后方添加 0xffdeb4、强度 0.72 的轮廓光;半球光使用 0xb5c5d7 / 0x35281c、强度 0.19。这些光只负责让阴影面保留细节,并非画面风格的来源。
- 木材:
MeshPhysicalMaterial,ior 1.43、specularIntensity 0.72,粗糙度为clamp(base * W.rough, 0.35, 1);从单一周期性噪声场生成程序化反照率、粗糙度和凹凸贴图,并烘焙一次(1024x1024 RGBA8、64 个单元、三个八度、带 mipmap、重复)。 - 光束、尘埃、最终阶段,然后运行
references/scorecard.mjs。
规则及各自防止的问题
平光会让木纹消失。 只要半球光或环境光亮到足以看清房间,就会填满每个孔隙,木板看起来就像涂了漆。将主光保持在 2.0,并降低环境光项;在此处测得,关闭影棚反射后总分提高了 0.14,因为反射会抬高阴影。各材质的 envMapIntensity 分别设置为:工作台 0.25、雕刻木板 0.45、默认值 0.40。
envMapIntensity 没有效果,调出的参数只在纸面上成立。 在 Three.js r163 及更高版本中,只有设置了 material.envMap,材质值才会被读取;仅设置 scene.environment 会忽略它。为每个木材材质设置 mat.envMap = env,并通过强度控制图层开关,不要将贴图设为 null(这样会触发重新编译)。
光泽反射出一扇并不存在的窗户。 Kibori 将环境窗户放在左前方;如果场景中的窗户位于左后方,反射出的光便不存在。将环境中的窗格组放在真实窗户所在的位置(此处为 (-2.3, 4.3, -3.2),缩放 0.5,包含 12 块 1.24x1.38 的窗格,辐射度为 3.8/3.55/3.05)。倾斜木板,让镜面反射方向略微偏离窗户(此处为 0.27 rad;若恰好处于镜面角度,整块木板会泛白)。
体积光会冲淡黑位和木纹。 加法混合的平面会给其后的所有内容增加光线,包括它们穿过的木材顶部。按世界高度淡出每束光线(smoothstep(0.02, 0.34, y)),将增益保持在 0.13(Kibori 为 0.12),并将参考条上方的亮度控制在 35 级以下,同时检查黑位:p1 必须低于 7。在高度淡出之前,增益设为 0.14 会让牌匾蒙上一层乳白色雾气,木纹也随之消失。
光束平面与实体相交时会形成一道硬边。 平面与长凳或立柱相交,便会画出一条亮线。Kibori 将平面放在长凳后方;上面的高度淡出方法无需移动平面即可解决这个问题。另外,要在 pow 之前使用 clamp:如果边缘坐标略微为负,pow(1.0 - across, 2.2) 就会产生 NaN,而 NaN 经加法混合后会形成黑色楔形。
上帝光线会发生裁切。 如果渲染器执行色调映射,或者光束通道在 ACES 之后才叠加,光束要么会重复映射,要么会直接达到 1.0。先在线性 HDR 中合成它们,再运行一次 ACES(a=2.51 b=0.03 c=2.43 d=0.59 e=0.14)。窗纸采用 toneMapped:false 线性值(1.15, 0.86, 0.52);在 (3.9, 3.35, 2.55) 时,它被裁切成了白纸。
加法混合的 Alpha。 AdditiveBlending 也会累加 Alpha。在不透明画布上看不出来;如果场景要绘制到 Alpha 合成图层中,请使用 CustomBlending,并设置 blendSrc One, blendDst One, blendSrcAlpha Zero, blendDstAlpha One。
长凳上出现阴影痘斑,或牌匾下方出现空隙。 痘斑仅由较小的负 bias 引起;应使用按阴影贴图比例缩放的 normalBias(4096 时为 0.0045,2048 时为 0.0085)。物体与阴影之间出现空隙(阴影漂浮)也是 normalBias 过高所致。评分卡会将其记为 contactGap。在每个物体下方烘焙一块接触阴影(径向 Alpha 为 0.78,r = 0.30..1.0,使用 smoothstep,y 设为 0.0012);仅靠阴影贴图会让接缝显得发亮。
单频纹理在掠射角下闪烁。 精细纹理带(孔洞为每米 96/88 个周期,纤维为 230 个周期,光线为 13/115 个周期)必须根据自身坐标的 fwidth 淡出;凹凸效果还必须比颜色更早消退(1 - smoothstep(0.05, 0.18, vesselPx)),否则 dFdx(h) 会产生摩尔纹。评分卡中的 shimmer 表示半像素视角偏移;这里的数值为 1.1,达到 3+ 就属于混叠。
雕刻浮雕变成噪点。 周期低于 12 px 的凹槽会在凹凸贴图中产生混叠,并在明亮环境下闪烁。这里的青海波雕刻使用 0.15 m 的单元格,每个圆有两道圆环(1440 宽时约为 16 px);缩小到 0.062 m 后,在 1x 时会变成噪点,在 2x 时则很细腻。雕刻尺寸应根据像素预算确定,而不是根据模型确定。
木纹中出现砖块状图案。 平切面上的髓射线斑点(rays)看起来会像铺好的方格。Kibori 的 0.8 默认值适用于径切面;牌匾上设为 0,其他部位设为 0.15。
暗墙上出现色带。 暗角渐变中出现 8 位色阶。sRGB 编码后添加 +-0.0015 的哈希抖动((hash - 0.5) * 0.003)即可消除色带;评分卡会统计连续 6 个及以上相同像素的情况(flatRun 为 0.019)。
摄像机移动时,指标也会变化。 冻结摆动和时间(KiboriScore.freeze(true)),并在同一任务中渲染和读取画布(不使用 preserveDrawingBuffer),以固定尺寸和 DPR 截取图像,并使用无头 Chromium;应用内面板会自行绘制背景,且与应用共享 GPU。
常量
| 项目 | 值 |
|---|---|
| 摄像机 | 垂直视场角 34 度,位置 (0.60, 1.25, 2.55),朝向 (0.05, 0.50, -0.30);宽高比小于 1 时使用更宽的视场角 |
| 光线方向 | 归一化后为 (0.40, -0.75, 0.45),窗户中心 (-0.60, 1.40, -1.0),开口尺寸 1.5 x 1.5 |
| 渲染目标 | HalfFloat,MSAA 4,DPR 上限为 2 |
| 主光 / 补光 / 轮廓光 / 半球光 | 2.0 / 0.60 / 0.72 / 0.19(Kibori 补光为 0.76) |
| 光束 | 5 个平面,宽 0.44-0.52,长 2.75,增益 0.13,底部渐隐 0.22(Kibori 为 0.30),fbm 3 个八度 |
| 墙面光线 | 10 个平面,宽 0.20-0.90,长 2.2,增益 0.08 |
| 灰尘 | 1900 个点,尺寸为 17 / depth px,限制在 3.4-46;散景效果为 1 - smoothstep,仅显示在距光束轴约 0.33 m 的范围内 |
| 场 | 1024^2 RGBA8,64 个周期单元(n、f、第二个 n),带 mipmap;启用“low”开关时为 16 px |
| 最终处理 | 锐化 0.10,暗角 0.42 + 0.58 v,ACES,sRGB,抖动 0.003 |
性能开销(在 Apple Silicon 上使用无头 Chromium 和 Metal 测得)
完整管线,每帧加一次 1 px 回读:1440x900 x1 时为 7.4-7.9 ms,2880x1800(DPR 2)时为 16.5 ms,390x844 DPR 2 时为 7.2 ms。光束和墙面光线平面是开销最大的层(每个叠加像素都要执行 3 个八度的 fbm:x1 时约 1 ms,x2 时约 5 ms);阴影贴图在首帧后无需额外开销,因为它是静态的;颗粒、环境光、主光和灰尘各约为 0.2-0.7 ms。最经济的优化方式是减少光束的 fbm 八度数(或以半分辨率绘制),而不是减少平面数量。不要根据一次开关测试就下结论:在 2x 下,不同运行之间的噪声约为 1 ms。
评分流程
- 提供页面服务,执行
npm i playwright-core,运行node references/scorecard.mjs(参数为--url、--size 1440x900、--dpr 1、--target 8、--no-ablate、--sync)。该脚本会进行无头捕获、冻结时间、依据references/rubric.md对八个项目评分、逐层消融并写入 JSON;任一项目未达标时退出码为 1。 - 修改前先评估基线(此处所有层均关闭时为 3.7)并保留该文件。消融用于核验指标:移除某一层后总分反而上升,说明测量有误,或该层的收益不值得其开销。
- 先修复最低分项目中成本最低的子指标,在相同尺寸和 DPR 下重新运行,并只保留画面效果证明确实有用的改动。每次根据指标进行更改后,都要查看 2x 裁剪图;这个演示中有三项“改进”虽然提高了数值,却让木板外观变差(整块木板加清漆、各向异性 0.3、逐像素色调增益 2.4),因此都已撤回。
- 如果分数要代表“看起来正确”,就把捕获图和评分标准交给一位没看过这些数字的新评审,并采用两个分数中较低的那个。
- 如实报告。演示版,1440x900 x1:平均分 8.8;颗粒 8.1,主光/补光 10,阴影 9.6,光束 8.0,光线 8.8,色调 9.8,纹理 9.7,高光 6.5(未达 8 分:光泽范围较宽,没有形成明显高光;而能形成明显高光的改动又损害了颗粒效果)。DPR 2 时,高光项目为 7.4,光束为 7.3(灰尘斑点比测量窗口中的细节更小)。
无障碍与生命周期
prefers-reduced-motion:显示经过设计的静态画面(时间固定为 5.2,无摆动、灰尘不漂移),按需渲染;图层开关和评分仍可正常工作。- 当
document.hidden为真,或画布离开视口时暂停;dt限制为最大 1/30 s,暂停后的首帧dt = 0。 - 使用
ResizeObserver调整尺寸;DPR 上限为 2;渲染目标和灰尘点大小随之调整。 - 图层开关使用真实复选框,并显示焦点状态;分数区域使用
aria-live。 - 小于 820 px 时,面板移至画布下方,分数标签显示在画布上。
验证
demo.html中的指标区块与references/metrics.js一致(node scorecard.mjs --sync)。- 在 1440x900 和 390x844 尺寸下,控制台无错误;减少动态效果时显示完整构图;按 Tab 可访问每个开关。
- 存在一份包含基线、全部开启、消融和性能开销的评分卡 JSON,且与上述结论一致。