【Unity Shader】实现基础光照模型

写在前面

现在问你:什么是漫反射?什么是高光反射?Phong和BlinnPhong模型的区别在哪儿?漫反射有哪几个参数?高光反射有几个参数?公式分别是啥?计算出来的结果指的是什么?...如果这种超基础的理论知识都回答不上来的话,建议再去恶补一遍理论!!一切都搞清楚了再上手代码也不迟~! 

参考书籍

仍旧是冯乐乐那本《Unity Shader 入门精要》


1 实现漫反射光照模型

1.1逐顶点光照:Shader代码

Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level"
{
    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 {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert(a2v v) {
                v2f o; //定义返回值o
                o.pos = UnityObjectToClipPos(v.vertex);  //顶点着色器的基本任务
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  //获得环境光

                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));  //法向量由物体->世界
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, worldLight));
                o.color = diffuse + ambient;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Diffuse"
}

效果会跟后面的逐像素光照模型一起放出来做一个对比。 

1.2 逐像素光照:Shader代码

Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level"
{
    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 {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            //顶点着色器输出的
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;  //自定义的一些参数就用texcoord0 
            };

            v2f vert(a2v v) {
                v2f o; //定义返回值o
                o.pos = UnityObjectToClipPos(v.vertex);  //顶点着色器的基本任务
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);  //就传递一个世界空间的发现就行了
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  //获得环境光
                fixed3 worldNormal = normalize(i.worldNormal);  //法向量由物体->世界
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, worldLight));
                fixed3 color = diffuse + ambient;

                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Diffuse"
}

1.3 半兰伯特模型:Shader代码

简单谈一谈半兰伯特(Half Lambert)光照模型为什么会被提出来?

兰伯特(Lambert)光照模型使用了max把法向量和指向光源方向的向量点积限制在了[0, 1]之间,这样做就会导致原本为负的区域直接给全部映射到了0,这就意味着背面光的效果直接没有了!

而半兰伯特做了一个这样的操作,就像之前做法线[-1, 1]->像素颜色值[0, 1]映射的做法一样,它把原始点积结果映射到了[0, 1]上,那么背面光不就也有个缓慢过渡的效果了!但我们需要明白,半兰伯特是没有任何物理依据的,只是为了加强视觉效果而提出的一个方法而已。

Shader "Unity Shaders Book/Chapter 6/Diffuse HalfLambert"
{
    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 {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            //顶点着色器输出的
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;  //自定义的一些参数就用texcoord0 
            };

            v2f vert(a2v v) {
                v2f o; //定义返回值o
                o.pos = UnityObjectToClipPos(v.vertex);  //顶点着色器的基本任务
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);  //就传递一个世界空间的发现就行了
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  //获得环境光
                fixed3 worldNormal = normalize(i.worldNormal);  //法向量由物体->世界
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                //computer diffuse
                fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * halfLambert;
                fixed3 color = diffuse + ambient;

                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Diffuse"
}

1.4 三者效果动态对比

1.4.1 逐顶点和逐像素的对比

这里我直接做了一个动图,左边是逐顶点,右边是逐像素,效果可能不是特别明显但凑合,大概可以看得出来左边变化的会有锯齿一样的不匀称的感觉,而右边相对来说就很平滑:

扫描二维码关注公众号,回复: 14526166 查看本文章

1.4.2 逐像素和半兰伯特的对比

二者对比如下:可以看到左边的逐像素不如右边的过渡效果好,右边有个背光过渡的感觉。

2 实现高光反射光照模型

2.1 逐顶点光照:Shader代码 

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'


Shader "Unity Shaders Book/Chapter 6/Specular Vertex-Level"
{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular("Specular", Color) = (1, 1, 1, 1)
        _Gloss("Gloss", Range(8.0, 256)) = 20  //这是高光项
    }
    SubShader 
    {
        Pass 
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "lighting.cginc"

            //properties
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            //逐顶点基本在顶点着色器中实现
            v2f vert(a2v v) {
                v2f o;
                //pos2clip space
                o.pos = UnityObjectToClipPos(v.vertex);
                //ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //normal:object -> world & normalize
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                //lightDir
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //reflectDir
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //viewDir =camera.pos - vertex.pos 
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

                //the diffuse:
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                //the specular:
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                //all:
                o.color = diffuse + specular + ambient;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return fixed4(i.color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Specular"
}

2.2 逐像素光照:Shader代码

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level"
{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular("Specular", Color) = (1, 1, 1, 1)
        _Gloss("Gloss", Range(8.0, 256)) = 20  //这是高光项
    }
    SubShader 
    {
        Pass 
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "lighting.cginc"

            //properties
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1; 
            };


            v2f vert(a2v v) {
                v2f o;
                //pos2clip space
                o.pos = UnityObjectToClipPos(v.vertex);
                //worldnormal
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                //worldPos
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                //ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //lightDir
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //worldNormal
                fixed3 worldNormal = normalize(i.worldNormal);
                //viewDir
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                //reflectDir
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLightDir));
                
                fixed3 specular = _LightColor0 * _Specular * pow(saturate(dot(viewDir, reflectDir)), _Gloss);

                //plus 3:
                fixed3 color = specular + ambient + diffuse;

                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Specular"
}

2.3 二者动态效果对比

 我就简单录了个随着光照方向改变,逐顶点(左)和逐像素(右)的高光区域的变化,可以很明显看到左边是有锯齿的感觉的,右边明显顺滑很多!

3 实现Blinn-Phong光照模型

 Blinn-Phong就是在2.3中实现的逐像素(高光+漫反射+环境光)的基础上,把高光项的max()里的viewDir和LightDir替换成了更易计算的halfDir和normal向量,相比于Phong计算的更加快了。

3.1 Shader代码

Shader "Unity Shaders Book/Chapter 6/BlinnPhong"
{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular("Specular", Color) = (1, 1, 1, 1)
        _Gloss("Gloss", Range(8.0, 256)) = 20  //这是高光项
    }
    SubShader 
    {
        Pass 
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "lighting.cginc"

            //properties
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1; 
            };


            v2f vert(a2v v) {
                v2f o;
                //pos2clip space
                o.pos = UnityObjectToClipPos(v.vertex);
                //worldnormal
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                //IF using UnityObjectToWorldNormal:
                //o.worldNormal = UnityObjectToWorldNormal(v.normal);
                
                //worldPos
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                //ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //lightDir
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //IF using UnityWorldSpaceLightDir:
                //fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                //worldNormal
                fixed3 worldNormal = normalize(i.worldNormal);
                
                //viewDir
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                //IF using UnityWorldSpaceViewDir:
                //fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                // //reflectDir
                // fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                // h:
                fixed3 hDir = normalize(viewDir + worldLightDir);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLightDir));
                
                fixed3 specular = _LightColor0 * _Specular * pow(saturate(dot(worldNormal, hDir)), _Gloss);

                //plus 3:
                fixed3 color = specular + ambient + diffuse;

                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Specular"
}

3.2 Blinn-Phong与Phong效果对比 

可以看到Blinn-Phong(右)的高光范围比Phong(左)的更大、更亮一些,实际渲染中也多选择Blinn-Phong模型来计算。

猜你喜欢

转载自blog.csdn.net/qq_41835314/article/details/127027830