第 18 章练习:有符号距离的直觉
对应教程:18 有符号距离函数的直觉
起始示例:圆的有符号距离,需要修改时使用示例源码。
练习 1:先算出符号和数值
设 sdCircle(p, 0.25) = length(p) - 0.25,坐标和半径均以平面高度为单位。
**目标:**分别计算 p=(0,0)、p=(0.20,0)、p=(0.30,0.40) 和 p=(0.25,0) 的 d,指出点在圆内、边界还是圆外;再判断示例的 1.0-step(0.0,d) 对四点各输出什么。
**完成标准:**能区分距离的符号与大小,并注意硬边 step 在恰好 d=0 时的归属。
练习 2:把零边界变成有厚度的提示带
**目标:**在第 18 章示例中新增一个显示模式:d < -0.04 显示内部色,-0.04 <= d < 0.04 显示醒目的边界带,其他位置显示外部色。改变圆半径,观察整条带是否始终跟着圆周移动。
**要求:**只使用现有 d 和片元颜色分支,不改变 sdCircle、顶点 shader 或网格。提示带宽度使用与 d 相同的平面单位。
**完成标准:**边界带在圆周内外各占 0.04,不会固定在画布上的某一位置;能解释它与只画 d=0 的细线有何区别。
练习 3:只在形状内部描边
**目标:**再新增一个模式:宽 0.06 的描边只出现在圆内侧,也就是大致 -0.06 < d < 0。让描边的两条边都使用已有的 aa 做抗锯齿。
**要求:**保持外侧背景不变;不要使用 abs(d),因为它会把描边扩到圆外。
**完成标准:**放大观察时,外边缘仍位于原圆周 d=0,内边缘位于 d≈-0.06;圆外没有对称的另一半描边。
<a id=“reference-answers”></a>
第 18 章练习参考解答
对应练习:有符号距离的直觉。
练习 1:先算出符号和数值
点 p |
length(p) |
d=length(p)-0.25 |
位置 | 1.0-step(0.0,d) |
|---|---|---|---|---|
(0,0) |
0 | -0.25 | 圆内 | 1 |
(0.20,0) |
0.20 | -0.05 | 圆内 | 1 |
(0.30,0.40) |
0.50 | 0.25 | 圆外 | 0 |
(0.25,0) |
0.25 | 0 | 边界 | 0 |
step(0.0,d) 在 d=0 时返回 1,所以这个硬边写法把恰好位于数学边界的点归到外侧。实际边界通常穿过像素,像素抗锯齿模式会混合两侧颜色。
练习 2:把零边界变成有厚度的提示带
在 HTML 的 #mode 下拉框追加 <option value="5">边界提示带</option>。把片元 shader 最后处理描边的 else { ... } 整块替换为:
// 片元阶段;d 的单位是平面高度。
else {
if ( uMode == 5 ) {
if ( d < -0.04 ) color = insideColor;
else if ( d < 0.04 ) color = vec3( 1.0, 0.80, 0.30 );
else color = outsideColor;
} else {
float strokeDistance = abs( d ) - 0.5 * uStroke;
float stroke = 1.0 - smoothstep( -aa, aa, strokeDistance );
color = mix( outsideColor, vec3( 1.0, 0.72, 0.22 ), stroke );
}
}
保留前面的模式 0~3 分支及其结束的 };上面代码从 else { 开始,接在其后。模式 4 继续使用原描边算法。判断区间是 [-0.04,0.04),总宽 0.08 个平面高度单位。改变半径会改变 d=0 的位置,提示带随边界一起移动。它有固定的平面距离宽度;示例原有的金色零线则只在边界附近按 fwidth(d) 画约一像素宽。
练习 3:只在形状内部描边
在 HTML 的 #mode 下拉框追加 <option value="6">仅内部描边</option>。若已完成练习 2,将上面的最终 else { ... } 再替换为以下完整分支;只做练习 3 时可删除模式 5 的判断:
// 片元阶段;aa 已由 fwidth(d) 计算。
else {
if ( uMode == 5 ) {
if ( d < -0.04 ) color = insideColor;
else if ( d < 0.04 ) color = vec3( 1.0, 0.80, 0.30 );
else color = outsideColor;
} else if ( uMode == 6 ) {
float insideOfOuterEdge = 1.0 - smoothstep( -aa, aa, d );
float outsideOfInnerEdge = smoothstep( -0.06 - aa, -0.06 + aa, d );
float innerStroke = insideOfOuterEdge * outsideOfInnerEdge;
color = mix( outsideColor, vec3( 1.0, 0.72, 0.22 ), innerStroke );
} else {
float strokeDistance = abs( d ) - 0.5 * uStroke;
float stroke = 1.0 - smoothstep( -aa, aa, strokeDistance );
color = mix( outsideColor, vec3( 1.0, 0.72, 0.22 ), stroke );
}
}
外边缘以 d=0 为中心,内边缘以 d=-0.06 为中心;两个软边的重叠部分接近 1。模式 6 的背景始终用 outsideColor,因此圆内非描边区域也显示背景;若想保留圆内填充,可先用 insideColor 作底色,再叠加描边。模式 6 未取 abs(d),所以圆外不会出现对称描边。