Unity shader程序化天空盒与水面

发布于 2023-04-28  105 次阅读


引言

程序化天空盒是我一直想复现的技术,因为自己做的很多场景都是使用固定的skybox,这使得天空整体死气沉沉的,因此决定做一个半卡通风格的程序化昼夜交替天空盒。

效果参考:程序化天空盒实现昼夜变换 - 知乎 (zhihu.com) 以及 Skybox tutorial part 1 | Kelvin van Hoorn,不过我的各个算法与之有很大出入,所以最终效果和这些教程的差别也比较大。

Part1. 日月模拟

首先我们复现一下日月。在分析日月逻辑之前先创建一个基础Unlit Shader,Shader Type和Light Model都设置成Unlit,而Render Type和Render Queue都设置成Background。然后写入以下预设内容:

Shader "Unlit/mySkybox"
{
    SubShader
    {
        Tags { "RenderType"="Background" "PreviewType"="Skybox" "Queue"="Background"}
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 uv : TEXCOORD0;
            };
            struct v2f
            {
                float3 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return distance(i.uv.xyz, _WorldSpaceLightPos0.xyz);
            }
            ENDCG
        }
    }
}

前半部分和默认的Unlit Shader无差别,唯独需要注意我们天空盒材质的uv是三维的(这三个维度恰好是视线向量的xyz。这个是cubemap的固有定义),然后在片元着色器我们直接返回一个distance(i.uv.xyz, _WorldSpaceLightPos0.xyz),它计算了天空盒某个方位向量与主光源方向向量的距离(作差),当重合时取到0,当反向时取到最大值:

这里就是我们日月的雏形了,当主光源旋转的时候,黑球的部分就代表了日月所在的位置。现在我们要把黑球区域改变成太阳的样子,如果直接用1-颜色的话,会有一层很柔和的过渡,导致太阳效果不是很好: