【Unity Shader学习笔记】Unity光照基础-漫反射光照


本代码只适用于平行光。

1、逐顶点漫反射光照

1.1漫反射光照原理

1.2代码实现

在Properties语义块中声明一个漫反射颜色属性

Properties
    {
        //漫反射参数,用于调整漫反射效果
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }

在SubShader语义块中定义一个Pass语义块。
在Pass的第一行指明光照模式。

	Tags{"LightMode" = "ForwardBase"}

在Pass中,使用CGPROGRAM与ENDCG包围Cg代码片,并告诉Unity顶点着色器与片元着色器的名字。
使用#include包含Unity的内置文件。

	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	//包含内置文件
	#include "Lighting.cginc"
	ENGCG

接着在CGPROGRAM与ENDCG中定义此前在Properties中定义的属性。
fixed4 _Diffuse;
定义输入与输出结构体

            struct a2v
            {
                //模型变换
                float4 vertex_position : POSITION;
                //法线方向(计算)
                float3 normal : NORMAL;
            };

            struct v2f
            {
                //输出顶点位置
                float4 pos : SV_POSITION;
                //存储顶点着色器输出的颜色
                fixed3 color_in : COLOR;
            };

主要计算都集中在顶点着色器。在顶点着色器中,我们有两个目标:把坐标从模型空间转换至裁剪空间中,以及输出漫反射计算之后的color。
坐标变换很简单。代码如下:

                //pos变换
                o.pos = UnityObjectToClipPos(v.vertex_position);

计算颜色要复杂一些。根据最上方的公式,我们一共需要四个变量。_Diffuse变量我们已经定义好,光源颜色不需要运算(_LightColor0.rgb可直接得到),接下来需要获取世界坐标下的光源方向世界坐标下的法线方向

fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//法线原本是模型空间下的,需要变换至世界坐标,因此需要mul()
//光源方向本来就是世界坐标下的,因此不需要mul()
//使用normalize()进行归一化

随后可以进行最重要的漫反射运算

防止法线和光源方向点乘的结果为负值,将其截取到0,这可以防止物体被从后面来的光源照亮。

saturate函数是Cg提供的函数,可以将参数截取在[0,1]的范围内。
dot()函数点乘两个向量。

//漫反射部分计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

加上环境光输出

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
return o;

别忘了在shader最后Fallback。FallBack "Diffuse"
最终写成如下代码:

Shader "Practise/DiffuseVertexShader"
{
    Properties
    {
        //漫反射参数,用于调整漫反射效果
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            //包含内置文件
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            fixed4 _Diffuse;

            struct a2v
            {
                //模型变换
                float4 vertex_position : POSITION;
                //法线方向(计算)
                float3 normal : NORMAL;
            };

            struct v2f
            {
                //输出顶点位置
                float4 pos : SV_POSITION;
                //存储顶点着色器输出的颜色
                fixed3 color_in : COLOR;
            };

            v2f vert(a2v v)
            {
                //两个目标:输出变换后的pos坐标、输出计算后的color
                v2f o;
                //pos变换
                o.pos = UnityObjectToClipPos(v.vertex_position);

                //color计算
                //变量准备
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //法线原本是模型空间下的,需要变换至世界坐标
                //环境光部分
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //漫反射部分计算
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
                //整理输出
                o.color_in = ambient + diffuse;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(i.color_in, 1.0);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

效果如下:

2、逐像素漫反射光照

逐像素光照中,主要的计算过程需要放到片元着色器中。顶点着色器只需要将变换后的位置坐标、变换后的世界空间中的发现坐标传到片元着色器中即可。
在逐顶点漫反射的基础上修改:

//输入顶点着色器
struct a2v{
	float3 pos : POSITION;
	float3 normal : NORMAL;
};
//顶点着色器输出
struct v2f{
	float4 worldpos : SV_POSITION;
	fixed3 worldnormal : TEXCOORD0;
};

TEXCOORD0、TEXCOORD1 等语义用于指示任意高精度数据,如纹理坐标和位置。

把计算挪到片元着色器:

            v2f vert(a2v v)
            {
                // 返回
                v2f o;
                //变换pos
                o.worldpos = UnityObjectToClipPos(v.pos);
                //变换法线
                o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET
            {
                fixed3 worldNormal = normalize(i.worldnormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //计算漫反射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                //环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }

得到代码:

Shader "Prcatice/DiffusePixelShader"
{
    Properties
    {
       _Diffuse("Diffuse", Color) = (1, 1, 1, 1)

    }
    SubShader
    {
        Pass{
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            //输入顶点着色器
            struct a2v{
                float3 pos : POSITION;
                float3 normal : NORMAL;
            };
            //顶点着色器输出
            struct v2f{
                float4 worldpos : SV_POSITION;
                fixed3 worldnormal : TEXCOORD0;
            };

            v2f vert(a2v v)
            {
                // 返回
                v2f o;
                //变换pos
                o.worldpos = UnityObjectToClipPos(v.pos);
                //变换法线
                o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET
            {
                fixed3 worldNormal = normalize(i.worldnormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //计算漫反射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                //环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }
            ENDCG
            }
    }
    FallBack "Diffuse"
}

最终效果:

3、对比

逐顶点漫反射可能会在向光面与背光面的之间形成一些锯齿。
(如下图,左侧为逐像素漫反射,右侧为逐顶点漫反射,右侧阴影明显有菱形锯齿)