GameTech/URP 渲染实战
源码 / shaders/Ch04/C04_01_FunctionPlot.glsl
shaders/Ch04/C04_01_FunctionPlot.glsl
// 案例 4.1:在 Shader 里画函数曲线 —— 调试数学函数最直观的办法。(GLSL 版)
Shader "Cookbook/Ch04/FunctionPlot"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="UniversalForward" }

            GLSLPROGRAM
            #include "Ch04/ProceduralCommon.glsl"

            uniform float _Mode;
            uniform vec4  _View;
            uniform float _LineWidth;
            uniform vec4  _ColorA, _ColorB, _ColorC, _Paper;

            #ifdef FRAGMENT
            // 第 k 条曲线 y = f(x)。改这里就能画任意函数
            float Curve(int mode, int k, float x)
            {
                if (mode == 0)          // 阈值类:step / smoothstep / 线性 step
                {
                    if (k == 0) return step(0.5, x);
                    if (k == 1) return smoothstep(0.2, 0.8, x);
                    return LinearStep(0.2, 0.8, x);
                }
                if (mode == 1)          // 重复类:floor 阶梯 / frac 锯齿 / 三角波
                {
                    if (k == 0) return floor(x * 4.0) / 4.0;
                    if (k == 1) return fract(x * 2.0);
                    return abs(fract(x) - 0.5) * 2.0;
                }
                // 形状类:脉冲(两个 smoothstep 相减)/ 正弦重映射 / 幂函数
                if (k == 0) return smoothstep(0.25, 0.35, x) - smoothstep(0.65, 0.75, x);
                if (k == 1) return 0.5 + 0.5 * sin(x * TWO_PI * 2.0);
                return x * x * x;
            }

            // 抗锯齿画线:把曲线在当前像素附近近似成一小段折线(左右各一个像素),
            // 求到这段线段的距离,再换算成“像素单位”。跳变处线段几乎竖直,竖线也能自然连上
            float PlotCurve(int mode, int k, vec2 q, float pixel)
            {
                vec2 a = vec2(q.x - pixel, Curve(mode, k, q.x - pixel));
                vec2 b = vec2(q.x + pixel, Curve(mode, k, q.x + pixel));
                float dist = SdSegment(q, a, b);
                return saturate(_LineWidth * 0.5 + 0.5 - dist / pixel);
            }

            // 到 d=0 处一条宽 widthPx 像素的线
            float LineAt(float d, float pixel, float widthPx)
            {
                return saturate(widthPx * 0.5 + 0.5 - abs(d) / pixel);
            }

            // 每隔 spacing 一条网格线:fract 把坐标折回到最近网格线附近
            float GridLine(float v, float spacing, float pixel, float widthPx)
            {
                float d = (fract(v / spacing + 0.5) - 0.5) * spacing;
                return LineAt(d, pixel, widthPx);
            }

            void main()
            {
                // UV [0,1] → 绘图坐标
                vec2 q = vec2(mix(_View.x, _View.y, uv.x), mix(_View.z, _View.w, uv.y));
                float pixel = fwidth(q.x);                 // 一个像素对应多少绘图单位

                vec3 col = _Paper.rgb;
                col = mix(col, vec3(0.82, 0.85, 0.88), max(GridLine(q.x, 0.25, pixel, 1.0), GridLine(q.y, 0.25, pixel, 1.0)));
                col = mix(col, vec3(0.35, 0.38, 0.42), max(LineAt(q.x, pixel, 1.5), LineAt(q.y, pixel, 1.5)));  // 坐标轴
                int mode = int(_Mode);
                col = mix(col, _ColorC.rgb, PlotCurve(mode, 2, q, pixel));
                col = mix(col, _ColorB.rgb, PlotCurve(mode, 1, q, pixel));
                col = mix(col, _ColorA.rgb, PlotCurve(mode, 0, q, pixel));
                fragColor = vec4(col, 1.0);
            }
            #endif
            ENDGLSL
        }
    }
}