源码 / shaders/Ch01/SpaceViz.glsl
shaders/Ch01/SpaceViz.glsl
// 第 1 章 案例 1.1:坐标空间可视化 // 同一个顶点,在不同空间里坐标不同。这个调试 Shader 把各空间的坐标“涂”在物体表面, // 用 _Mode 切换:物体空间 / 世界空间 / 观察空间 / NDC / 裁剪空间 w / 屏幕像素坐标。 Shader "Cookbook/Ch01/SpaceViz" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } Pass { Name "SpaceViz" Tags { "LightMode"="UniversalForward" } GLSLPROGRAM uniform float _Mode; uniform float _GridSize; uniform float _DepthScale; uniform float _PixelCell; // 交给光栅化器的裁剪空间坐标写进 gl_Position(到了片元里会变成像素坐标 gl_FragCoord) VARYING vec3 vPositionOS; // 物体空间 VARYING vec3 vPositionWS; // 世界空间 VARYING vec3 vPositionVS; // 观察空间(右手系,相机看向 -Z) VARYING vec4 vPositionHCS; // 裁剪空间的一份拷贝:gl_FragCoord 在片元阶段已被改写,想要原值得自己传 #ifdef VERTEX void main() { // URP 一次性算好各个空间的位置(ShaderVariablesFunctions.hlsl) VertexPositionInputs pos = GetVertexPositionInputs(positionOS.xyz); gl_Position = pos.positionCS; vPositionOS = positionOS.xyz; vPositionWS = pos.positionWS; vPositionVS = pos.positionVS; vPositionHCS = pos.positionCS; } #endif #ifdef FRAGMENT // 抗锯齿网格线:p 落在 size 的整数倍附近时返回 1。 // fwidth 给出 p 在相邻像素间的变化量,用它做线宽,线在远处也保持约 1 像素宽。 // 在某个表面上恒定不变的分量(如地面的 y)fwidth 为 0,要排除,否则整面都会被当成“线上”。 float GridLine(vec3 p, float size) { vec3 q = p / size; vec3 fw = fwidth(q); vec3 d = abs(fract(q - 0.5) - 0.5) / max(fw, vec3(1e-5)); d += step(fw, vec3(1e-6)) * 1e6; return 1.0 - saturate(min(min(d.x, d.y), d.z) - 0.5); } float GridLine2(vec2 p, float size) { vec2 q = p / size; vec2 d = abs(fract(q - 0.5) - 0.5) / max(fwidth(q), vec2(1e-5)); return 1.0 - saturate(min(d.x, d.y) - 0.5); } void main() { vec3 col = vec3(0.0); int mode = int(_Mode); if (mode == 0) // 物体空间:坐标跟着物体走,旋转缩放物体,图案跟着转 { col = saturate(vPositionOS + 0.5); col = mix(col, vec3(0.05), GridLine(vPositionOS, 0.25 * _GridSize)); } else if (mode == 1) // 世界空间:所有物体共用一套坐标,网格线在物体之间连续 { vec3 cell = fract(vPositionWS / _GridSize); col = 0.25 + 0.6 * cell; col = mix(col, vec3(0.05), GridLine(vPositionWS, _GridSize)); // 世界原点处的 X 轴(红)、Z 轴(蓝) float ax = 1.0 - saturate(abs(vPositionWS.z) / (2.0 * fwidth(vPositionWS.z))); float az = 1.0 - saturate(abs(vPositionWS.x) / (2.0 * fwidth(vPositionWS.x))); col = mix(col, vec3(1, 0.1, 0.1), ax); col = mix(col, vec3(0.1, 0.3, 1), az); } else if (mode == 2) // 观察空间:x 向右、y 向上,深度 = -z { float depth = -vPositionVS.z; col.rg = saturate(vPositionVS.xy * 0.25 + 0.5); col.b = saturate(depth / _DepthScale); // 等深线:每 1 米一条,它们都与相机的成像平面平行 float iso = abs(fract(depth - 0.5) - 0.5) / max(fwidth(depth), 1e-5); col = mix(col, vec3(1.0), 1.0 - saturate(iso - 0.5)); } else if (mode == 3) // NDC:透视除法之后,可见范围 x、y 都在 [-1, 1] { vec2 ndc = vPositionHCS.xy / vPositionHCS.w; ndc.y *= _ProjectionParams.x; // 渲染到 RT 时投影矩阵被上下翻转过,这里翻回来,让 +y 永远朝上(WebGL 里恒为 1) col = vec3(saturate(ndc.x), saturate(ndc.y), saturate(-ndc.x) * 0.7 + saturate(-ndc.y) * 0.3); col = mix(col, vec3(0.0), GridLine2(ndc, 0.25)); col = mix(col, vec3(1.0), GridLine2(ndc, 1.0)); // 中心十字(x=0、y=0) } else if (mode == 4) // 裁剪空间的 w:透视投影下它就是观察空间深度 { float w = vPositionHCS.w; col = vec3(saturate(w / _DepthScale)); float iso = abs(fract(w - 0.5) - 0.5) / max(fwidth(w), 1e-5); col = mix(col, vec3(1, 0.45, 0.2), 1.0 - saturate(iso - 0.5)); } else // 片元阶段的 gl_FragCoord.xy:像素中心坐标(如 100.5, 37.5) { // 注意:Unity(D3D)的像素坐标原点在左上角,WebGL 在左下角,棋盘格的起点会差半格 vec2 cell = floor(gl_FragCoord.xy / _PixelCell); float s = cell.x + cell.y; float k = s - 2.0 * trunc(s / 2.0); // fmod(s, 2) col = mix(vec3(0.9, 0.9, 0.92), vec3(0.25, 0.45, 0.6), k); } fragColor = vec4(col, 1); } #endif ENDGLSL } } }