文章

shader入门基础-复杂光照二

shader入门基础-复杂光照二

光照衰减

Unity 使用一张纹理作为查找表来在片原着色器中计算逐像素光照的衰减。

优点

  • 计算衰减不依赖数学公式的复杂性,只需要使用一个参数做纹理采样。

缺点

  • 需要预处理得到采样纹理,而且纹理的大小也会影响到衰减的精度。
  • 不直观,不方便,一旦把数据存储到查找表中,无法使用其他数学公式计算衰减。

用于光照衰减的纹理

Unity 在内部使用了一张名为_LightTexture0 的纹理来计算光照衰减,如果对该光源使用了 cookie,则对应_LightTextureB0,通常我们只关心_LightTexture0 对角线上的纹理颜色值,(0,0)表示与光源重合位置的衰减值,(1,1)点表明了光源空间中所关心距离最远的点的衰减。 顶点在光源空间中的坐标,参数是顶点在世界空间中的坐标。

1
2
 float3 lightCoord=mul(_LightMatrix0,float4(i.worldPosition,1)).xyz
 fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL

阴影

实时渲染中,最常使用的是一种名为 Shadow Map 的技术。把摄像机的位置放在光源重合位置上,阴影便是摄像机看不到的地方,在前向渲染中,如果 场景中最重要的平行光开启了阴影,Unity 就会为该光源计算它的阴影映射纹理(shadowmap),shandowmap 本质是一张深度图,记录了该光源的位置出发 能看到场景中距离它最近的表面位置(深度信息)。Unity 会将 LightModel 设置为 ShadowCaster 的 Pass 专门用来更新光源的阴影映射纹理。

让物体投射阴影

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    Pass {
        Name "ShadowCaster"
        Tags { "LightMode" = "ShadowCaster" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_shadowcaster
#pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
#include "UnityCG.cginc"

struct v2f {
    V2F_SHADOW_CASTER;
    UNITY_VERTEX_OUTPUT_STEREO
};

v2f vert( appdata_base v )
{
    v2f o;
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    return o;
}

float4 frag( v2f i ) : SV_Target
{
    SHADOW_CASTER_FRAGMENT(i)
}
ENDCG

让物体接收阴影

Cast Shadows 设置为 On,该物体就会加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。
Recieve Shadows则可以选择是否让物体接受来自其他物体的阴影。
SHADOW_COORDS 在顶点着色器的输出结构体 v2f 中添加,该宏用于声明一个用于对阴影纹理采样的坐标。该宏的参数需要是下一个可用的插值寄存器的索引值。
TRANSFER_SHADOW 在顶点着色器返回之前添加,用于在顶点着色器中计算上一步中声明的阴影纹理坐标。
SHADOW_ATTENUATION 在片元着色器中计算阴影值。
UNITY_LIGHT_ATTENUATION 该宏用于计算光照衰减和阴影的宏, 第一个参数 atten,宏会声明,我们不需要声明,第二个参数是结构体 v2f,第三个参数是世界空间的坐标。

1
2
//传递shadow coordinates
fixed shadow = SHADOW_ATTENUATION(i)

注意

这些宏中会使用上下文变量来进行相关计算,例如 TRANSFER_SHADOW 会使用 v.vertex 或 a.pos 来计算坐标,因此为了能够让这些宏正确工作,我们需要保证自定义 的变量名和这些宏中使用的变量名匹配。我们需要保证 a2v 结构体的顶点坐标变量名必须是 vertex,顶点着色器的输入结构体必 a2v 必须命名为 v,且 v2f 中的顶点位置变量必须命名为 pos。
对于大多数不透明物体来说,把 Fallback 设为 VertexLit 就可以得到正确的阴影。对于使用了透明度测试的物体,要得到正确的阴影效果,我们会把 Fallback 设置 为 Transparent/Cutout/VertexLit。

实践

透明度测试的阴影

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Practice/AlphaTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color Tint",Color) = (1,1,1,1)
        _Cutoff("Alpha Cutoff",Range(0,1))=0.5
    }
    SubShader
    {
        //RenderType 可以让unity把这个shader归入到提“TransparentCutout”
        //"IgnoreProjector"="True" 不受投影器影响
        //渲染序列设置为 AlphaTest
        Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
        Pass
        {
            //LightModel 是 pass标签的一种,正确定义LightModel 才能正确得到unity的内置光照变量
            Tags {"LightModel"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            //包含内置变量的 _LightColor0
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            //C_diffuse = (C_light·m_diffuse)max(0,N·L) //漫反射计算公式
            //都在unity 世界坐标计算,需要知道顶点在世界的位置,顶点法线,顶点视角位置
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord :TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float3 worldNormal:NORMAL;
                float3 worldPos:TEXCOORD2;
                //下一个插值寄存器 3
                SHADOW_COORDS(3)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            fixed _Cutoff;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //把顶点坐标转换到世界坐标,为后续取 L做准备
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                //把法线转换到 世界空间中
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                //纹理映射坐标,该顶点在纹理中对应的2D坐标
                o.uv=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //逐像素 光照
                //C_diffuse = (C_light·m_diffuse)max(0,N·L)
                //获取公式中的各个变量,m_diffuse =此时材质面板给定的_Color*顶点的color
                fixed4 col;
                fixed4 texColor = tex2D(_MainTex,i.uv);
                fixed3 N = normalize(i.worldNormal);
                //UnityWorldSpaceLightDir输入世界空间中的坐标点,WorldSpaceLightDir 输入模型空间中的坐标顶点
                fixed3 L = normalize(UnityWorldSpaceLightDir(i.worldPos));
                clip(texColor.a-_Cutoff);
                //equal to
                //if(m_diffuse.a-_Cutoff)<0{
                    //discard;
                //}
                //计算漫反射部分

                fixed3 c_light = _LightColor0.rgb;
                fixed3 m_diffuse =texColor.rgb*_Color.rgb;
                fixed3 c_diffuse = c_light*m_diffuse*max(0,dot(N,L));
                //计算环境光产生的影响,环境光反射的也会受材质本身颜色影响
                fixed3 ambient = m_diffuse*UNITY_LIGHTMODEL_AMBIENT.rgb;
                //计算光照衰减和阴影
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                //环境光和漫反射光
                col = fixed4(ambient+c_diffuse*atten,1.0);
                return col;
            }
            ENDCG
        }
    }
    FallBack "Transparent/Cutout/VertexLit"
}

本文由作者按照 CC BY 4.0 进行授权