学习进度
0/9 已勾只保存在这台浏览器;勾满 9 项即视为本题完成。
先把题目说成人话
这题其实问了前后相接的两件事:怎样让树干、枝条和叶片按照一组规则长出来;树长成以后,怎样避免远处的树冠看起来像许多互相闪烁的纸片。第二件事不是每棵树都必须做,而是游戏用简化叶片暴露出纸片结构时的一种补偿。
用节点和参数反复生成不同树形叫程序化制作(Procedural Generation),这套规则可以封装成数字资产(HDA)。游戏常用带透明图案的平面冒充一簇叶子,这种平面叫叶片卡。告诉光照“表面朝哪里”的箭头叫法线(Normal)。若需要让许多叶片共同呈现树冠的大形状,可以借用一层不可见外壳的方向;这层外壳叫树冠代理(Crown Proxy)。
这是虚构教学故事,不是个人项目经历。
场景:先让问题变得看得见
小明从一条主干曲线开始,让粗枝、细枝和叶片依次长出来。树被送进一间只有太阳灯和白色地面的摄影棚,所有变化都在同一棵树、同一盏灯下观察。
(右脑)画面:太阳从树的左边慢慢移向右边。树冠里有些叶片突然整块发白,旁边一些又立刻变黑,像许多绿色纸片轮流暴露自己。小红先关掉所有实时阴影:地上的树影消失了,叶片之间也不再互相投影,可那些碎亮仍然存在。画面暂停后,每张叶片上出现一支小箭头,原来每块平面都在用自己的朝向回答阳光。随后,一层看不见的柔软外皮贴着树冠鼓起。叶片没有转动,只借来附近外皮的方向,并保留一点原来的方向。太阳再次移动时,亮处开始沿整片树冠连续流动。最后重新打开阴影,叶片与地面的投影依然回来——外皮没有替树挡光,它只整理了叶片回应光线的方向。
(左脑)对表:关掉阴影以后碎亮仍在,是区分两种机制的关键证据。阴影回答“光能不能到达这里”,法线回答“光到达以后,表面有多朝向它”;树冠代理只修改后一项。需要检查的对象因此分成叶片几何、叶片原法线、代理法线、混合强度和独立的阴影结果。位置与代理必须处于同一 SOP 空间,法线和混合权重没有单位,混合后的方向必须重新归一化。一个球能否代表不规则树冠仍是假设:分叉明显的树可能需要更贴形的外壳或多个局部代理。
小明:先形成一个完整猜测
小明最初相信每张叶片卡都应该严格使用自己的几何法线。这个选择本身没有错:若模型真的做出了大量弯曲、朝向自然的叶片,它通常更接近真实表面。
问题在于当前一张大卡片冒充了许多小叶子。光照忠实照亮的是平面,而贴图试图让玩家相信那里是一簇叶子。小明随后又走向另一个极端,把球面法线直接传给整棵树;树冠连贯了,树干高光却像充气玩具。两次结果共同划出边界:这里修正的是简化叶片的着色方向,不是让所有树木数据服从一个球。
小红:把猜测放回现实检查
小红先把生成过程固定下来:主干和枝条由曲线决定,枝条上的点保存叶片位置、大小和朝向,再把叶片复制过去。同一个随机种子应长出同一棵树,冠幅、分枝和叶片密度则作为可调参数保存在 HDA 中。
接着她保持几何、相机和灯光不变,只处理叶片组。她用 xyzdist() 找到叶片点附近的代理表面,再用 primuv() 读取那个位置的法线,与叶片原法线按强度混合:
int prim = -1;
vector uvw;
xyzdist(1, @P, prim, uvw);
vector crown_n = normalize(primuv(1, "N", prim, uvw));
float strength = clamp(chf("wrap_strength"), 0.0, 1.0);
@N = normalize(lerp(normalize(@N), crown_n, strength));
这只是展示核心关系的最小 VEX。正式工具还要先确认代理存在、能找到最近面、原法线与代理法线都不是零向量;失败就阻止导出,不能让备用方向冒充成功结果。叶片与树干必须分组,若同一点的不同面需要不同方向,就写 Vertex N,不能让 Point N 把边界强行焊平。
她先在关闭阴影的情况下比较原法线与混合法线,确认变化确实来自受光方向;再用 Attribute Visualize 检查箭头是否连续、是否只改到叶片。这时才有证据说代理减少了叶片卡之间突兀的明暗跳变。
我:用检查结果修正模型
我把阴影重新打开,叶片之间的自阴影和地面树影仍然存在。这证明树冠代理没有把微观阴影换成宏观阴影,它只是把高频、离散的法线受光整理成低频、连贯的树冠明暗。若项目还要降低植被阴影成本,需要另外决定阴影距离、分辨率、叶片是否投影或是否使用简化 Shadow Caster,不能把这份性能收益记到法线修改头上。
随后我保留原法线与代理法线的混合强度:近处让叶片保有自己的转折,远处可以更强调树冠整体。新的法线还会影响切线空间,因此切线需要与最终法线保持一致。导出 FBX 时同时携带 Normals、Tangents、UV、Color 和 LOD 命名;各级 LOD 的 pivot、风锚点和相位也要相容。
进入 Unity 后,按顺序旋转太阳、开关阴影、切换 LOD,再播放风动画。若 Houdini 里平滑、Unity 中却突然翻亮,就继续检查双面叶片、切线、导入设置和目标 Shader,而不是继续加大代理权重。
(右脑)画面修正:现在摄影棚里有两只彼此独立的开关。第一只控制叶片与地面的真实投影;第二只控制叶片用自己的箭头,还是借一点树冠外皮的方向。只拨第二只时,地面影子完全不动,树冠明暗却从碎纸片变成连续大块面。只拨第一只时,谁挡住谁会改变,叶片朝向阳光的方式却没有改变。两只开关终于不再被误认为同一件事。
(左脑)严格对表:生成链是主干/枝条曲线 → 稳定采样点 → 生成枝干 → 写入叶片位置、朝向和尺度 → 复制叶片。着色链只处理叶片组:在统一 SOP 空间查找代理最近面,读取有效且非零的代理 N,与原 N 按 0 到 1 的强度混合并归一化;树干保留自身法线,共享点按连续性选择 Point 或 Vertex N。验证时先关阴影隔离法线效果,再开阴影确认两套机制共存,最后检查切线、LOD、风和 Unity 目标 Shader。只有另外简化 Shadow Caster 时,才能声称阴影成本发生变化。
老师解惑
- 何时不需要代理:真实叶片足够密、弯曲与朝向也可信时,尊重它们自己的法线通常更合理。代理主要补偿叶片卡暴露出的纸片结构。
- 法线与阴影:法线决定表面怎样回应已经到达的光;Shadow Map 决定光能否到达。修改前者不会自动省掉后者。
- 树冠代理:传递的是连续受光方向,不是把几何压成球,也不是替整棵树投影。
- 最近面采样:
xyzdist + primuv可以从任意代理外壳读取连续法线;不规则树冠可使用贴形外壳或多个局部代理。 - LOD 与风:各级 pivot、风锚点和相位不一致,会在切档或运动时跳动,即使静止截图正常。
工具分工
- Houdini SOP/HDA:生成骨架、枝干、叶片和可重复的树形参数。
- VEX / Attribute Visualize:采样、混合并显示原法线与树冠法线。
- Unity 光照与阴影开关:分别验证受光方向和遮挡,防止把两个结果混在一起。
- LOD/风回归场景:用同一镜头检查切档、旋转、阴影和动态光照。
记忆钩子
树冠代理不替叶片挡光;它只借给叶片一套更连贯的受光方向。
这是教学场景,不是目标项目的真实捕获或个人经历。
下一步:验证这条因果
球体代理法线怎样转移到叶片、按强度混合,并在 Unity 导入后保持不丢失。
教学边界:它不是完整 Houdini .hip 网络,也不证明真实项目的树种生成、LOD、风或性能结果。