Unity Shader 基础光照(build)

发布时间 2023-07-11 18:30:20作者: CatSevenMillion

1.光学基础

  1.1 光源:  

    光线由光源发出,在实时渲染中,我们通常把光源当成一个没有体积的点。用l代表光的方向,用辐照度来量化光的强度。辐照度的意思表示垂直于l的单位面积上单位时间穿过的能量。如果光于平面不垂直,则辐照度为 cos S/l,S为光线与平面法线的夹角。

  1.2 吸收与散射:   

    光源与物体相交后通常会有两种结果:散射 和 吸收。

    散射:只改变光的方向,不改变光的密度和颜色; 吸收: 只改变光的方向和密度,不改变光的方向。散射又分为折射和反射两种。

    散射又分为两种,一种是直接被反射出来,另一种是经过内部折射或者内部吸收后翻出出来的光。我们把高光反射(specular)表示表面反射的光,漫反射(difuss)表示折射、吸收、散射出表面的光。

  1.3 着色:

    着色,指根据材质属性、光源信息、使用一个等式去计算沿着某个观察方向的初射度过程。我们把这个等式称为 光照模型(Lighting Model),不同的光照模型有不同的目的,例如一些描述粗糙物体表面,一些描述金属表面。

  1.4 BRDF 光照模型

    BRDF光照模型用来回答了当光线从某个方向照射到一个表面时,有多少光线被反射,反射的角度有哪些等问题。BRDF大多都是一个数学公式给定的。这些往往都是经验模型,你可以自己试一试,图形学第一定律“如果它看起来是对的,那么它就是对的”。

2.标准光照模型

  标准光照模型的基本方法是,把进入摄像机内的光线分成4个部分。每一个部分使用一种方法来计算它的贡献度:

    自发光(emissive),用于描述给定一个方向时,一个表面本身会向该方向发射多少辐射量。值得注意的是如果没有全局光照技术,这些自发光的表面并不会真的照亮周围的物体,而它本身看起来更亮了而已。

    高光反射(specular),这部分用于描述当前光线从光源照射到模型表面时,该表面完全镜面反射散射多少辐射量。

    漫反射(difuss),当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。

    环境光(ambient),描述其他所有的间接光线。

  逐顶点还是逐像素?

    通过基本光照模型,我们可以得到数学工作,那在哪开始计算这些光照模型呢?我们可以在片元着色器中计算,也叫做逐像素光照。或者是在顶点着色器中计算,也叫做逐顶点光照

    在逐像素光照中,我们会以每个像素为单位,得到它的法线,在片之间进行法线的差值计算,然后进行光照模型计算。逐顶点光照,我们会计算每个顶点的光照,然后会在渲染图元内部进行线性差值,最后输出成像素颜色。逐顶点光照计算量小于逐像素光照。

3.Unity中的环境光与自发光

  在光照模型中,环境光和自发光是最简单的,Unity中可以通过Window->Rendering->Lighting->Environment 中进行设置环境光。而由于绝大多数的物体是没有自发光特性的,因此大部分不用设置。如果需要的话只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可。

4.使用Unity Shader实现漫反射光照模型

  在基本光照模型中漫反射的计算公式是:

    Cdiffuse=(Clight*Mdiffuse)max(0,n*l);

  其中:Clight表示入射光颜色和强度,Mdiffuse为材质漫反射系数,n为法线,l为入射方向。

  1.逐顶点光照:

Shader "Untiy Book/Chapter6/DiffuseVertexLevel"
{
    Properties
    {
        //控制漫反射颜色
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            //只有定义了正确的LigtMode,才能得到后续的一些光照变量
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "Lighting.cginc"
            //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量
            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.pos = UnityObjectToClipPos(v.vertex);
                // 得到环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                // 将顶点的法线从模型空间转为世界空间   归一化处理,防止结果为负数
                fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
                // 得到光照强度和方向
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                // 光的颜色强度*材质漫反射颜色 得到漫散射光照  saturate函数是Cg提供的可以班参数截取到0-1之间。
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb*saturate(dot(worldNormal,worldLight));
                o.color = ambient+diffuse;
                return o;
            }

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

效果: 逐顶点的漫反射光照,对于细分程度高的物体可以有比较好的效果,对于细分程度低的模型,会看到背光面与向光面有一些齿轮。

   2.逐像素光照:

Shader "Untiy Book/Chapter6/DiffusePixelLevel"
{
    Properties
    {
        //控制漫反射颜色
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            //只有定义了正确的LigtMode,才能得到后续的一些光照变量
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "Lighting.cginc"
            //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量
            fixed4 _Diffuse;
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
            };

            v2f vert (a2v v)
            {
                v2f o;
                // 顶点着色器基本任务 将模型空间转为裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);
                // 顶点着色器不再计算,只需要将世界坐标法线给片元着色器
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                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 = _LightColor0.rgb * _Diffuse.rgb*saturate(dot(worldNormal,worldLight));
                fixed3 color = ambient+diffuse;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

效果: 这样光线过渡就有了更加平滑的效果了。但问题又来了,在光线无法到达的区域,模型没有任何的明暗变化。

   3.半兰伯特模型

  这种模型是在开发半条命时提出的一种技术。其实也就是更改了光照模型的公式,如下:

  Cdiffuse=(Clight*Mdiffuse)(alpha(n*l)+beta);

  其中对n*l 乘以一个alpha倍再加上了 beta偏移,绝大多数情况下两个值都为0.5,这样保证了值不为0,且原有的0值处,现在为0.5了所以保证了背光面也能有阴影变化

 

Shader "Untiy Book/Chapter6/HalfLambert"
{
    Properties
    {
        //控制漫反射颜色
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            //只有定义了正确的LigtMode,才能得到后续的一些光照变量
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "Lighting.cginc"
            //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量
            fixed4 _Diffuse;
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
            };

            v2f vert (a2v v)
            {
                v2f o;
                // 顶点着色器基本任务 将模型空间转为裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);
                // 顶点着色器不再计算,只需要将世界坐标法线给片元着色器
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                // 将顶点的法线从模型空间转为世界空间   归一化处理,防止结果为负数
                fixed3 worldNormal = normalize(i.worldNormal);
                // 得到光照强度和方向
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

                fixed halfLambert = dot(worldNormal,worldLight)*0.5 +0.5;
                // 得到物体散射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
                
                fixed3 color = ambient+diffuse;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

效果:

 三种漫反射光照对比效果:

5.使用Unity Shader实现高光反射模型

  高光反射的基本光照模型为:

   Cspecular=(Clight*Mspecular)max(0,(v*r))gloss;

  其中,入射光线的颜色和强度Clight,材质的高光反射系数Mspecular,视角方向v,反射方向r。其中r = l-2(n*l)n

  1.逐顶点光照

Shader "Untiy Book/Chapter6/SpecularVertex"
{
    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"
            #include "UnityCG.cginc"
            
            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;
                // 基本功能
                o.pos = UnityObjectToClipPos(v.vertex);
                // 漫反射部分
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0 * _Diffuse.rgb *saturate(dot(worldNormal,worldLight));
                // 高光反射部分
                fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(mul(unity_ObjectToWorld,v.vertex)));
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
                // 环境+漫反射+高光反射
                o.color = ambient + diffuse + specular;
                return o;
            }

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

  这种方式得到的高光部分明显不平滑,因为高光反射部分的计算是非线性的,但是顶点着色器中计算光照部分是线性的。

  2.逐像素光照

Shader "Untiy Book/Chapter6/SpecularPixel"
{
    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"
            #include "UnityCG.cginc"
            
            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;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 只需计算世界空间下的法线坐标和顶点方向
                o.worldNormal = UnityObjectToWorldNormal(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                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 = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
                // 计算高光
                fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);

                fixed3 specular = _LightColor0.rbg * _Specular.rbg* pow(saturate(dot(reflectDir,viewDir)),_Gloss);

                return fixed4(ambient+diffuse+specular,1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

效果:我们看到了,光照部分更加平滑了。

   3.Blinn-Phong 光照模型

  该公式比较复杂,它是一个经验模型,并不完全符合真实世界中的光照现象。具体公式为:

 

Shader "Untiy Book/Chapter6/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"
            #include "UnityCG.cginc"
            
            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;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 只需计算世界空间下的法线坐标和顶点方向
                o.worldNormal = UnityObjectToWorldNormal(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                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 = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
                // 计算高光
                //fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);
                // change
                fixed3 halfDir = normalize(worldLight+viewDir);
                fixed3 specular = _LightColor0.rbg * _Specular.rbg* pow(max(0,dot(worldNormal,halfDir)),_Gloss);
                return fixed4(ambient+diffuse+specular,1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

效果:我们可以看到高光反射部分看起来更大,更亮一些。实际渲染中我们更喜欢这种这种方式

 全部对比:从左到右,从上到下