第 23 章练习:三个综合项目
对应教程:23 综合项目。
起始示例:动态图案海报、流动水面、程序化徽标。需要改动时分别使用海报源码、水面源码、徽标源码。
练习 1:让海报圆环从中心向外传递
**目标:**把各格原本由格子编号决定的相位,改为“格子离海报中心越远,相位越滞后”的径向波。保留每格的局部 fract 坐标、圆环宽度、配色及暂停开关。
**要求:**用 cell、tileCount 计算格子中心到整个网格中心的距离;时间项仍用 uTime*uSpeed。不要把用于圆环形状的 local 改成全局坐标。
**完成标准:**播放时相邻距离层的圆环半径依次变化;暂停后所有半径不再变化;调格数后仍以网格中心为传播中心。
练习 2:给水面增加第三组细波
**目标:**给 heightAt 增加一组与前两组方向不同、幅度较小的正弦波,使轮廓和明暗同时反映新波形。
**要求:**只在 heightAt 内组合新高度,不另写一套法线公式;新波幅不超过 uAmplitude 的 0.2 倍。比较高分段和 8 × 5 左右低分段下的结果,并在修正法线、原法线模式之间切换。
**完成标准:**高分段时可看到新增的细小起伏与高光变化;低分段时细波的轮廓表现变差;原法线模式的明暗不能准确跟随新的起伏。
练习 3:在徽标横杠中挖一个圆形缺口
**目标:**只在字母横杠中间挖出一个小圆形缺口,外环和斜边保持完整。旋转和缩放徽标时,缺口应与横杠一起运动。
**要求:**在 sdLogo 的局部坐标 q 中建立孔的 SDF,使用差集表达式从已有的并集场中减去;保留最终乘回 uScale。通过等距线模式检查孔边界。
**完成标准:**完整徽标中的横杠有清晰缺口;旋转后缺口仍在横杠中央;仅填充模式和等距线模式都显示相同的零边界。
练习 4:按症状选择最先检查的层
**目标:**对下列现象分别指出第一项应检查什么、可用哪个示例查看模式确认,并说明它属于坐标、几何、法线还是颜色问题。
- 水面只有
8个横向分段,亮纹很多,但外轮廓呈大块折线。 - 徽标旋转正常,放大后光晕看起来相对变窄,等距线也不像固定宽度。
- 海报圆环的轮廓正确,但青色与珊瑚色的区域和预想不同。
**完成标准:**能先定位产生症状的中间量,再决定是否需要改 shader;不会用增加片元颜色层去修复网格轮廓,也不会用增加网格分段去修复格子配色。
<a id=“reference-answers”></a>
第 23 章练习参考解答
对应练习:三个综合项目。
练习 1:让海报圆环从中心向外传递
在海报片元 shader 中,把原来的 phase 一行替换为:
// 片元阶段;cell 与 tileCount 都属于海报格子坐标。
vec2 cellCenter = cell + vec2( 0.5 );
float radialDistance = length( cellCenter - 0.5 * tileCount );
float phase = uTime * uSpeed - 1.2 * radialDistance;
保持后面的 radius、ringD、ring 不变。相位相同的格子大致位于同一同心距离带;公式 ωt-kr 的等相位位置随时间向外移动。tileCount 随密度控件更新,因此中心不必手写为 (3,2)。原配色仍由 cell 决定,不会因为半径传播变成单色。另一种合理做法是用平面高度单位中的格子中心位置计算距离,但须同时修正横纵格距。
练习 2:给水面增加第三组细波
在水面顶点 shader 的 heightAt 中、原 return 之前加入:
// 顶点阶段;localXY 为水面局部平面坐标。
vec2 dir3 = normalize( vec2( 0.7, -0.7 ) );
float fineWave = uAmplitude * 0.18 *
sin( dot( localXY, dir3 ) * 14.0 - uTime * 2.3 );
把原返回值改为 return primary + secondary + detail + fineWave;。现有 hx、hy 都调用 heightAt,所以新波自动进入局部斜率;随后 normalMatrix 将新法线送到观察空间。新增波的波长约为 2π/14≈0.45 个局部单位。横向仅 8 段时相邻顶点间距 3/8=0.375,每个周期只有约一个采样间隔,不能可靠表示该波形;提高分段才能让轮廓与顶点插值跟上。原法线模式仍使用旧平面法线,因此高光不能正确追随细波。也可用另一方向与频率,但应使波幅较小且避免远高于网格采样能力。
练习 3:在徽标横杠中挖一个圆形缺口
在徽标片元 shader 的 sdLogo 末尾,用下面的代码替换原来的 return min(...) * uScale;:
// 片元阶段;q 是已逆向变换的徽标局部坐标。
float combined = min( ring, min( min( left, right ), bar ) );
float hole = length( q - vec2( 0.0, -0.07 ) ) - 0.038;
return max( combined, -hole ) * uScale;
hole<0 的局部区域被 -hole>0 排出徽标,故 max 是差集。缺口中心与原横杠中心的 y 坐标相同,半径 0.038 大于横杠约 0.44*uStroke 的局部半宽,能切断横杠,而又远离两侧斜边。由于孔和笔画都使用同一个 q,旋转与缩放会一起生效。新布尔场的零边界适合填充与轮廓检查;其内部距离幅值未必是到最终边界的精确欧氏距离。
练习 4:按症状选择最先检查的层
| 现象 | 最先检查 | 示例查看方式 | 原因 |
|---|---|---|---|
| 水面低分段轮廓呈折线 | 网格分段与波长 | “网格线与修正明暗” | 轮廓由顶点决定,增加片元颜色细节不能添加顶点 |
| 徽标放大后光晕相对变窄 | sdLogo 的缩放与距离单位,再看 uGlow |
“距离等值线” | uGlow 是固定的画面距离,徽标放大后相对尺寸自然变小;若等距线数值也错误,检查返回值是否乘回 uScale |
| 海报配色不符合预想 | cell 的编号与 hueChoice 公式 |
“局部重复坐标”后看“圆环遮罩” | 遮罩正确说明形状链条正常;颜色选择由格子编号决定,分段数不影响它 |
第二项不必一看到“相对变窄”就改 shader:固定世界/画面单位的光晕在更大的徽标旁本来会显得窄。如果目标是让光晕与徽标等比变宽,可把 uGlow 乘以 uScale,同时保留距离返回值的乘回系数;这改变了设计目标,而不是修复距离计算。