GameTech/URP 渲染实战
源码 / shaders/Ch21/Portal.glsl
shaders/Ch21/Portal.glsl
// 案例 21.4:极坐标漩涡传送门——角度 + 半径当 UV,扭转、旋转、向内吸入;顺带处理 atan2 接缝(GLSL 版,对应 Ch21/Portal.shader)
Shader "Cookbook/Ch21/Portal"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" "IgnoreProjector"="True" }

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="UniversalForward" }
            Blend One One          // 叠加发光:越叠越亮,不需要排序
            ZWrite Off
            Cull Off
            GLSLPROGRAM
            #pragma multi_compile_instancing
            #include "Ch21/UVAnim.glsl"

            uniform vec4 _ColorInner, _ColorOuter;
            uniform float _Arms, _Twist, _Spin, _Inward, _RadialTiling;
            uniform float _Contrast;
            uniform float _Phase;
            uniform float _FixSeam, _Debug;
            uniform sampler2D _NoiseTex;

            VARYING vec2 uv;

            #ifdef VERTEX
            void main()
            {
                gl_Position = TransformObjectToHClip(positionOS.xyz);
                uv = texcoord0.xy;
            }
            #endif

            #ifdef FRAGMENT
            vec2 SwirlUV(vec2 polar, float t, float spin)
            {
                // x:角度 × 臂数(必须是整数才能首尾相接)+ 半径 × 扭转(越往里转得越多)+ 时间旋转
                // y:半径 × 平铺 − 时间(图案不断向中心流)
                return vec2(polar.x * _Arms + polar.y * _Twist + t * spin,
                            polar.y * _RadialTiling - t * _Inward);
            }

            // 带接缝修正的采样:atan2 在 ±π 处从 1 跳回 0,硬件按 2×2 像素块求导会得到巨大的 ddx,
            // 于是选中最低一级 mip,在接缝上画出一条线。修正:另算一份“接缝挪到对面”的角度,取较小的导数
            float SampleSwirl(vec2 polar, float t, float spin)
            {
                vec2 suv = SwirlUV(polar, t, spin);
                vec2 dx = dFdx(suv), dy = dFdy(suv);
                if (_FixSeam > 0.5)
                {
                    vec2 uvAlt = SwirlUV(vec2(fract(polar.x + 0.5), polar.y), t, spin);
                    vec2 dxAlt = dFdx(uvAlt), dyAlt = dFdy(uvAlt);
                    dx.x = abs(dx.x) < abs(dxAlt.x) ? dx.x : dxAlt.x;
                    dy.x = abs(dy.x) < abs(dyAlt.x) ? dy.x : dyAlt.x;
                }
                return textureGrad(_NoiseTex, suv, dx, dy).r;
            }

            vec4 frag()
            {
                vec2 polar = ToPolar(uv, vec2(0.5, 0.5));
                float t = _Time.y + _Phase;

                if (_Debug > 0.5 && _Debug < 1.5) return vec4(polar.x, polar.y, 0.0, 1.0);
                if (_Debug > 1.5 && _Debug < 2.5) return vec4(fract(SwirlUV(polar, t, _Spin)), 0.0, 1.0);
                // 只看一层漩涡噪声并二值化:接缝处取到最低级 mip(≈ 平均灰),在黑白图案里显出一道灰线
                if (_Debug > 3.5) { vec2 u = SwirlUV(polar, t, _Spin); float d = max(length(dFdx(u)), length(dFdy(u))); return vec4(vec3(saturate(log2(d * 512.0) / 10.0)), 1.0); }
                if (_Debug > 2.5) return vec4(vec3(smoothstep(0.5, 0.7, SampleSwirl(polar, t, _Spin))), 1.0);

                // 两层速度不同的漩涡相乘,图案更“活”
                float n1 = SampleSwirl(polar, t, _Spin);
                float n2 = SampleSwirl(polar * vec2(1, 0.7), t * 1.3, -_Spin * 0.6);
                float swirl = pow(saturate(n1 * n2 * 2.0), _Contrast);

                float r = polar.y;
                float disc = smoothstep(1.0, 0.8, r);                     // 外缘淡出
                float rim  = exp(-pow((r - 0.86) * 9.0, 2.0));            // 外圈一道亮环
                float core = exp(-r * r * 18.0);                          // 中心光核
                vec3 col = mix(_ColorInner.rgb, _ColorOuter.rgb, saturate(r * 1.2));
                vec3 result = col * (swirl * disc + rim * 0.8) + _ColorInner.rgb * core;
                return vec4(result, 1.0);
            }
            void main() { fragColor = frag(); }
            #endif
            ENDGLSL
        }
    }
}