13 综合练习
Raymarching:只用片元 shader 画 3D
没有三角形。每个像素发出一条射线,沿 SDF 一步步前进,碰到表面就上色。
4128
0.0010.6
-0.61.2
-3.143.14
习题
每个像素运行一次拖动数字,或直接改代码
#define STEPS 64 float sdBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } float smin(float a, float b, float k) { float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); return mix(b, a, h) - k * h * (1.0 - h); } float scene(vec3 p) { float sphere = length(p - vec3(-0.45, 0.0, 0.0)) - 0.6; float box = sdBox(p - vec3(0.450, 0.0, 0.0), vec3(0.4)); float ground = p.y + 0.7; return min(smin(sphere, box, 0.250), ground); } vec3 sceneNormal(vec3 p) { vec2 e = vec2(0.001, 0.0); return normalize(vec3(scene(p + e.xyy) - scene(p - e.xyy), scene(p + e.yxy) - scene(p - e.yxy), scene(p + e.yyx) - scene(p - e.yyx))); } void main() { vec2 p = (gl_FragCoord.xy - 0.5 * uResolution) / min(uResolution.x, uResolution.y); float a = 0.500 + uTime * 0.15; vec3 ro = vec3(3.2 * sin(a), 1.0, 3.2 * cos(a)); // 射线起点:相机 vec3 forward = normalize(-ro); vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0))); vec3 up = cross(right, forward); vec3 rd = normalize(forward * 1.6 + right * p.x + up * p.y); // 这个像素的射线方向 float t = 0.0; float hit = 0.0; for (int i = 0; i < 128; i++) { if (i >= STEPS) break; float d = scene(ro + rd * t); // 离最近的表面有多远 if (d < 0.001) { hit = 1.0; break; } t += d; // 安全地前进这么远 if (t > 20.0) break; } vec3 pos = ro + rd * t; vec3 N = sceneNormal(pos); float diffuse = max(dot(N, normalize(vec3(0.6, 0.8, 0.4))), 0.0); float tiles = mod(floor(pos.x * 2.0) + floor(pos.z * 2.0), 2.0); vec3 surface = pos.y < -0.69 ? mix(vec3(0.16, 0.22, 0.25), vec3(0.24, 0.36, 0.34), tiles) : vec3(0.96, 0.43, 0.30); vec3 color = mix(vec3(0.07, 0.11, 0.15), surface * (0.2 + 0.8 * diffuse), hit); gl_FragColor = vec4(color, 1.0); }
JS 这一侧
gl.uniform2f(uResolution, …, …)
gl.uniform1f(uTime, 0.00)每个像素发出一条射线,用 SDF 安全地往前走,直到碰到表面——不用三角形也能画 3D。
试一试
- 把步数降到 8,边缘出现破洞
- 调大平滑融合,球和方块熔在一起
- 在 scene() 里再加一个球