其他分享
首页 > 其他分享> > 【Unity】【Shader】基础光照原理及代码实践

【Unity】【Shader】基础光照原理及代码实践

作者:互联网

《Unity+Shader入门精要》学习
Unity版本:2019.4.23f1c1

模拟真实的光照环境来生成一张图像,需要考虑3种物理现象。

  1. 首先,光线从光源种被发射出来。
  2. 然后,光线和场景种的一些物体相交:一些光线被物体吸收了,而另一些光线被散射到其他方向。
  3. 最后,摄像机吸收了一些光,产生了一张图像。

光源

在实时渲染中,我们通常把**光源(light source)**当成一个没有体积的点,用 l 来表示它的方向。

在光学里,我们使用**辐射度(irradiance)**来量化光(测量一个光源发射出多少光)。

对于平行光来说,它的辐射度可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到。在计算光照模型时,我们需要知道一个物体表面的辐射度,而物体表面往往是和l不垂直的。计算表面的辐射度时,可以使用光源方向l和表面法线n之间的夹角的余弦cos值来得到。需要注意的是,这里默认方向矢量的模都为1。
示例图
左图中,光是垂直照射到物体表面,因此光线之间的垂直距离不变;
右图中,光是斜着照射到物体表面,在物体光线之间的距离是d/cosθ,因此单位面积接收到光线数目要少于左图。

吸收和散射

光线由光源发射出来后,就会与一些物体相交。通常,相交的结果有两个:

光线在物体表面经过散射后,有两种方向:

为了区分两种不同的散射方向,在光照模型中使用了不同的部分来计算:

此处,只假设漫反射部分是没有方向性的,即光线在所有方向上是平均分布的。同时,我们也只考虑某一个特定方向上的高光反射。

着色

着色(shading):根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型(Lighting Model)

BRDF光照模型

Bidirectional Reflectance Distribution Function
当给定模型表面上的一个点时,BRDF包含了对该点外观的完整的描述。在图形学中,BRDF大多使用一个数学公式来表示,并且提供了一些参数来调整材质属性。即当给定入射光线的方向和辐照度后,BRDF可以给出在某个出射方向上的光照能量分布。

	计算机图形学的第一定律:如果它看起来是对的,那么它就是对的。——《3D数学基础:图形与游戏开发》邓恩

标准光照模型

在BRDF理论被提出之前,标准光照模型已被广泛使用。

标准光照模型只关心直接光照(direct light),也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。

基本方法:把进入到摄像机内的光线分为4个部分,每个部分使用一种方法来计算它的贡献度。

  1. 自发光(emissive):用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照(global illumination)技术,这些自发光的表面并不会真的照亮周围的物体,二十它本身看起来更亮了而已。
  2. 高光反射(specular):用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
  3. 漫反射(diffuse):用于描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
  4. 环境光(ambient):用于描述其他多有的间接光照。

环境光

标准光照模型的重点在于描述直接光照,而现实中,物体也可以被间接光照(indirect light)所照亮。

间接光照:光线通常会在多个物体之间反射,最后进入摄像机,即在光线进入摄像机之前,经过了不止一次的物体反射。

在标准光照模型中,我们使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它通常是一个全局变量,即场景中的所有物体都使用这个环境光。
cambient=gambient

自发光

光线也可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度。它的激素那就是直接使用了该材质的自发光颜色。
cemissive=memissive

通常在实时渲染中,自发光的表面往往不会照亮周围表面,即这个物体并不会被当成一个光源。

漫反射

漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是,入射光线的角度很重要。

漫反射光照符合兰伯特定律:反射光线的强度与表面法线和光源之间的夹角的余弦值成正比。因此,漫反射的计算为:

cdiffuse=(clight·mdiffuse)max(0,n·I)

漫反射=(光源颜色·材质的漫反射颜色)max(0,表面法线·光源的单位矢量)

其中,n是表面法线,I是指向光源的单位矢量,Mdiffuse是材质的漫反射颜色,Clight是光源颜色。需要注意的是,我们需要防止法线和光源方向点乘的结果为负值,为此,我们使用取最大值的函数来将其截取到0,这可以放置物体被从后面来的光源照亮。

高光反射

这里的高光反射是一种经验模型,即它并不完全符合真实世界中的高光反射现象。它可用于计算那些沿着完全镜面方向被反射的光线,这可以让物体看起来是有光泽的,例如金属材质。

计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。此处假设这些矢量都是单位矢量。
在这里插入图片描述
在这四个矢量中,我们实际只需要知道其中3个矢量即可,而第四个矢量——反射方向可以通过其他信息计算得到:
在这里插入图片描述
这样,我们就可以利用Phong模型来计算高光反射的部分:
在这里插入图片描述

其中,mgloss是材质的光泽度,也被称为反光度。用于控制高光区域的“亮点”有多宽,mgloss越大,亮点越小。mspscular是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。clight则是光源颜色和强度。同样,这里也需要防止v·r的结果为负数。

和上述的Phong模型相比,Blinn提出了一个简单的修改方法来得到类似的结果。它的基本思想是,避免计算反射方向r。为此,Blinn模型引入了一个新的矢量h,它是通过对v和I的取平均后再归一化得到的。即
在这里插入图片描述
然后,使用n和h之间的夹角进行计算,而非v和r之间的夹角,
总结,Blinn模型公式为
在这里插入图片描述
在硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,这是因为,此时可以认为v和I都是定值,因此h将是一个常量。但是,当v和L不是定值时,Phong模型可能反而更快一些。需要注意的是,这两种光照模型都是经验模型,即我们不应该认为Blinn模型是对“正确的”Phong模型的近似。实际上,在一些情况下,Blinn模型更符合实验结果。

逐像素还是逐顶点

通常在两个地方计算这些光照模型:

在逐像素光照中,我们会以每个像素为基础,得到它的法线(可以是对顶点法线插值得到的,也可以是从法线纹理中采样得到的),然后进行光照模型的计算。这种在面片之间对顶点法线进行插值的技术被称为**Phong着色(Phong shading)**也被称为Phong插值或法线插值着色技术。这不同于我们之前讲到的Phong光照模型。

与之相对的是逐顶点光照,也被称为高洛德着色(Gouraud shading)。在逐顶点光照中,我们在每个顶点上计算光照,然后会在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目往往远小于像素数目,因此逐顶点光照的计算量往往要小于逐像素光照。但是,由于逐顶点光照依赖于线性插值来得到像素光照,因此,当光照模型中有非线性的计算(例如计算高光反射时)时,逐顶点光照就会出问题。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象。

总结

虽然标准光照模型仅仅是一个经验模型,即它并不完全符合真实世界中的光照现象。但由于它的易用性、计算速度和得到的效果都比较好,因此仍然被广泛使用。而也是由于它的广泛使用性,这种标准光照模型有很多不同叫法。例如:Phong光照模型,因为裴祥风(Bui Tuong Phong)首先提出了使用漫反射和高光反射的和来对反射光照进行建模的基本思想,并且提出了基于经验的计算高光反射的方法(用于计算漫反射光照的兰伯特模型在那时已经被提出)。而后,由于Blinn的方法简化了计算而且在某些情况下计算更快,我们把这种模型称为Blinn-Phong光照模型。

但这种模型有很多局限性。首先,有很多重要的物理现象无法用Blinn-Phong模型表现出来,例如菲涅尔反射(Fresnel reflection)。其次,Blinn-Phong模型是各向同性(isotropic)的,也就是说,当我们固定视角和光源方向旋转这个表面时,反射不会发生任何变化。但有些表面是具有各向异性(anisotropic)反射性质的,例如拉丝金属、毛发等。

Unity中的环境光和自发光

照明设置

在Unity Shader中实现漫反射光照模型

基本光照模型中漫反射部分计算公式:
cdiffuse=(clight·mdiffuse)max(0,n·I)
可知,要计算漫反射需要知道4个参数:入射光线的颜色和强度clight,材质的漫反射系数mdiffuse,表面法线n以及光源方向I。
为了防止点积结果为负值,我们需要使用max操作,而CG提供了这样的函数(CG的saturate函数可以达到同样目的)

实践:逐顶点光照

新建场景,包含一个摄像机和一个平行光,去掉内置天空盒
去除环境
新建一个3d物体
新建一个材质,赋予物体
新建一个shader,赋予材质
编写shader:

Shader "MyShader/DiffuseVertexLevel"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)//初始值设为白色
    }
        SubShader
    {
        Pass//顶点/片元着色器的代码需要写在Pass语义块
        {
             Tags{
                "LightMode" = "ForwardBase"//指明该Pass的光照模式
            }

            CGPROGRAM//CGPROGRAM和ENDCG来包围CG代码片,以定义顶点着色器和片元着色器代码

            #pragma vertex vert//顶点着色器名字
            #pragma fragment frag//片元着色器名字

            #include "Lighting.cginc"//unity内置文件

            fixed4 _Diffuse;//为了使用properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
            //得到材质的漫反射属性,由于颜色属性的范围在0到1之间,因此可以使用fixed精度的变量来存储它

            struct a2v//定义顶点着色器的输入结构体
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f//定义顶点着色器的输出结构体  同时也是片元着色器的输入结构体
            {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            //顶点着色器
            v2f vert(a2v v)
            {
                v2f o;//定义返回值o
                //顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间。使用内置UnityObjectToClipPos来完成坐标转换,得到环境光
                o.pos = UnityObjectToClipPos(v.vertex);

                //计算漫反射(已知才知道漫反射颜色_Diffuse以及顶点法线v.normal,还需要知道光源的颜色和强度信息以及光源方向
                //内置变量_LightColor0可访问该Pass处理的光源的颜色和强度信息(需要定义合适的LightMode标签)
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //选择世界坐标空间(计算发现和光源方向之间的点积,两者需处于同一坐标空间
                //a2v得到的顶点法线是位于模型空间下,此处需要把法线转换到世界空间
                //首先得到模型空间到世界空间的变换举证的逆矩阵_World2Object,然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵惩罚。
                fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
                //光源方向可以由_WorldSpaceLightPos0来得到(只适用一个光源且为平行光
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //得到世界空间中的法线和光源方向后,对它们进行归一化。在得到点积结果,防止这个结果为负值,使用saturate函数。
                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
        }
    }
        //把这个UnityShader的回调shader设置为内置的Diffuse
        Fallback "Diffuse"
}

在这里插入图片描述

实践:逐像素光照

只需要对Shader进行一些更改就可以实现逐像素的漫反射效果。

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

Shader "MyShader/DiffusePixelLevel"
{
    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:TEXCOORDO;
            };

            //顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可
            v2f vert(a2v v)
            {
                v2f 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);

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

                fixed3 color = ambient + diffuse;

                return fixed4(color,1.0);
            }

            ENDCG
        }
    }
                Fallback "Diffuse"
}

在这里插入图片描述
逐像素光照可以得到更加平滑的光照效果。但是即便使用了逐像素漫反射光照,有一个问题仍然存在。在光照无法到达的趋于,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。实际上我们可以通过添加环境光来得到非全黑的效果,但即便这样仍然无法解决背光面明暗一样的缺点。为此,有一种改善技术被提出来,这就是半兰伯特(Half Lambert)光照模型。

半兰伯特模型

兰伯特定律——在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。(上面使用的漫反射光照模型也被称为兰伯特光照模型.

为了改善上述提出的问题,Valve公司在开发游戏《半条命》时提出了一种技术,由于该技术是在原兰伯特光照模型的基础上进行一个简单的修改,因此被称为半兰伯特光照模型。

广义的半兰伯特光照模型公式:
在这里插入图片描述
可以看出,与原兰伯特模型相比,半兰伯特光照模型没有使用max操作来防止n和I的点积为负值,而是对其结果进行了一个α倍的缩放再加上一个β大小的偏移。绝大多数情况下,α和β的值均为0.5,即公式为:

在这里插入图片描述
通过这样的方式,我们可以把n·I的结果范围从[-1,1]映射到[0,1]范围内。即对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。

需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。

Shader "MyShader/HalfLambert"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)//初始值设为白色
    }
        SubShader
    {
        Pass//顶点/片元着色器的代码需要写在Pass语义块
        {
             Tags{
                "LightMode" = "ForwardBase"//指明该Pass的光照模式
            }

           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:TEXCOORDO;
            };

            //顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可
            v2f vert(a2v v)
            {
                v2f 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);//获取光源的方向,
                fixed3 halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;//半兰伯特
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;//_LightColor0获取光源的颜色和强度
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
        Fallback "Diffuse"
}

在这里插入图片描述
背光:
在这里插入图片描述

在Unity Shader中实现高光反射光照模型

基本光照模型中高光反射部分的计算公式:
在这里插入图片描述
可知,要计算高光反射需要知道4个参数:入射光线的颜色和强度clight,材质的高光反射系数mspecualr,视角方向v以及反射方向r。其中,反射方向r可以由表面法线n和光源方向I计算而得:
在这里插入图片描述
CG提供了计算反射方向的函数reflect(i,n)
函数:reflect(i,n)
参数:i,入射方向;n,法线方向。可以是float、float2、float3等类型。
描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。
参数和返回值之间的关系:在这里插入图片描述

实践:逐顶点光照

Shader "MyShader/SpecularVertexLevel"
{
    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"

        //由于颜色属性范围在0到1之间,因此_Diffuse和_Specular属性可以使用fixed精度变量来存储
        //_Gloss的范围很大,因此使用float精度来存储
            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);

                fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
            
            //高光反射
                //计算入射光线方向关于表面法线的反射方向reflectDir。CG的reflect函数的入射方向要求是由光源指向交点处的,因此需要对worldLightDir取反后再传给reflect函数
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //通过_WorldSpaceCameraPos得到世界空间中的摄像机位置,再把顶点位置从模型空间变换到世界空间下,再通过和_WorldSpaceCameraPos相减即可得到世界空间下的视角方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

                //已知4个参数,代入公式即可得到高光反射的光照部分。
                fixed3 specular = _LightColor0.rbg * _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
        }
    }
    //把这个Unity Shader的回调Shader设置为内置的Specular
    FallBack "Specular"
}

在这里插入图片描述
使用逐顶点的方法得到的高光效果有比较大的问题,可以看出高光部分明显不平滑。这主要是因为,高光反射部分的计算是非现行的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。因此我们需要使用逐像素方法来计算高光反射。

实践:逐像素光照

Shader "MyShader/SpecularPixelLevel"
{
    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"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
        //顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把他们传递给片元着色器
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
  
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;

            }
            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.zyx - i.worldPos.zyx);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                
                return fixed4(ambient + diffuse + specular,1.0);
            }
            ENDCG            
        }
    }
        FallBack "Specular"
}

在这里插入图片描述

Blinn-Phong光照模型

Blinn模型没有使用反射方向,而是引入一个新的矢量h,它是通过对视角方向v和光照方向I相加后再归一化得到的。即
在这里插入图片描述
而Blinn模型计算高光反射的公式:
在这里插入图片描述
代码中只需要修改片元着色器中对高光反射部分的计算:

Shader "MyShader/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"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
            //顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把他们传递给片元着色器
                v2f vert(a2v v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);

                    o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                    return o;

                }
                fixed4 frag(v2f i) :SV_Target
                {
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

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

                    fixed3 viewDir = normalize(_WorldSpaceCameraPos.zyx - i.worldPos.zyx);
                    fixed3 halfDir = normalize(worldLightDir + viewDir);
                    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)), _Gloss);

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

在这里插入图片描述

使用Unity内置函数

在这里插入图片描述类似UnityXXX的几个函数是Unity5中新添加的内置函数,使得我们不需要根各种变换矩阵、内置变量打交道,也不需要考虑各种不同的情况(例如使用了那些光源),而仅仅调用一个函数就可以得到需要的信息。

注意:这些函数都没有保证得到的方向矢量是单位矢量,因此,我们需要在使用前把它们归一化。

计算光源方向的3个函数:WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpaceLightDir,稍微复杂一些,这是因为,Unity帮我们处理了不同种类光源的情况。注意:这3个函数仅可用于前向渲染。这是因为只有在前向渲染时,这3个函数里使用的内置变量——WorldSpaceLightPos0等才会被正确赋值。

内置函数改写上面Blinn-Phong光照模型代码

Shader "MyShader/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"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
                v2f vert(a2v v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);

                    o.worldNormal = UnityObjectToWorldNormal(v.normal);//改写部分1
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                    return o;

                }
                fixed4 frag(v2f i) :SV_Target
                {
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));//改写部分2

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

                    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));//改写部分3
                    fixed3 halfDir = normalize(worldLightDir + viewDir);
                    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)), _Gloss);

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

标签:模型,Shader,fixed3,Unity,worldNormal,顶点,Diffuse,光照
来源: https://blog.csdn.net/Liuees/article/details/120208166