第 12 章练习:Noise 的基本原理
对应教程:12 Noise 的基本原理
起始示例:从格点到 value noise,修改 12-value-noise.js 和需要时的 12-value-noise.html。
练习 1:手算一个单元
当前单元四角的值分别为:左下 a = 0.0、右下 b = 0.2、左上 c = 0.6、右上 d = 1.0。格内位置为 local = (0.25, 0.75)。
**目标:**分别算出双线性插值结果,以及先用三次平滑权重 t²(3-2t) 后的插值结果。
**完成标准:**写出 x、y 的权重和先横后纵的中间值;解释为什么两个结果不同,但四个角上的值仍相同。
练习 2:把明暗图变成柔边斑块
**目标:**在示例中新增“柔边斑块”显示模式,使用已算出的 valueNoise(p) 作为遮罩来源;阈值可由滑块调整,边缘宽度固定为 0.06。
**要求:**保留原有三个计算模式与格线开关;新显示模式只改变最后的颜色映射,不修改 hash21 或插值。阈值滑块范围可设为 0.2~0.8。
**完成标准:**提高阈值时暖色区域减少;平滑插值模式的斑块边缘柔和,格点值模式仍是跳变色块。
练习 3:让图案平滑滑动
**目标:**给 p.x 增加由 uTime 驱动的连续偏移,使图案向左或向右匀速滑动,并能暂停。
**要求:**偏移必须在 floor / fract 之前加入;保留手动偏移滑块。用 THREE.Clock 和动画循环更新 uTime。
**完成标准:**图案不会每隔一个格子整幅跳变;暂停后画面保持不动,继续播放仍从暂停位置接上,而不是突然跳到当前时钟秒数。
<details><summary>提示</summary>
记录自己的动画时间累积量;暂停时不要增加它。查询坐标向右移动时,画面内容视觉上向左移动。
</details>
<a id=“reference-answers”></a>
第 12 章练习参考解答
对应练习:Noise 的基本原理。
练习 1:手算一个单元
线性权重 wx = 0.25、wy = 0.75。下边 mix(0.0, 0.2, 0.25) = 0.05,上边 mix(0.6, 1.0, 0.25) = 0.70,最终 mix(0.05, 0.70, 0.75) = 0.5375。
平滑权重 wx = 0.25² × (3 - 2 × 0.25) = 0.15625;wy = 0.75² × (3 - 2 × 0.75) = 0.84375。下边变为 0.03125,上边变为 0.6625,最终约为 0.5638671875。插值权重中间发生变化,所以内部值不同;在 0 和 1 两端,平滑权重仍恰好为 0 或 1,因此四角值不变。
练习 2:把明暗图变成柔边斑块
在 HTML 新增一个显示方式下拉框,例如 id="display",其中 0 为连续颜色、1 为柔边斑块;再新增 id="threshold" 的滑块。在 JS 的 uniforms 中加入:
uDisplay: { value: 0 },
uThreshold: { value: 0.5 }
在片元 shader 中声明 uniform int uDisplay; uniform float uThreshold;。在 float value = valueNoise(p); 后、原有格线绘制前,计算:
vec3 color = mix( vec3( 0.025, 0.055, 0.10 ),
vec3( 0.97, 0.72, 0.34 ), value );
if ( uDisplay == 1 ) {
float mask = smoothstep( uThreshold - 0.03,
uThreshold + 0.03, value );
color = mix( vec3( 0.025, 0.055, 0.10 ),
vec3( 0.97, 0.72, 0.34 ), mask );
}
这使阈值两边总宽度为 0.06。把两个新控件的值读取进 updateControls() 并更新对应 uniform;原有 uMode 与 uGrid 逻辑不变。改变显示模式只消费 valueNoise 的输出,所以可与三种计算模式自由组合。提高阈值通常让暖色变少;实际面积比例取决于格点值分布。
练习 3:让图案平滑滑动
在 HTML 添加 id="pause" 的复选框。在 JS 的 uniforms 中增加 uTime: { value: 0.0 },片元 shader 也声明 uniform float uTime;,然后把原有的 p.x += uOffset; 改为:
p.x += uOffset + 0.7 * uTime;
把原有 render() 留给控件与窗口变化;另外启动动画循环:
const clock = new THREE.Clock();
let animationTime = 0;
renderer.setAnimationLoop( () => {
const delta = Math.min( clock.getDelta(), 0.1 );
if ( ! document.querySelector( '#pause' ).checked ) animationTime += delta;
material.uniforms.uTime.value = animationTime;
render();
} );
暂停时 animationTime 不变;clock.getDelta() 仍每帧调用,恢复时不会把整个暂停时间一次补上。p.x 向右查询,图案视觉上向左滑。若要向右滑,把 + 0.7 * uTime 改为减号。p 在进入 valueNoise() 之前已平移,因此 floor 与 fract 一直处理同一个连续坐标。