【TA之路知识总结】shader学习笔记——入门篇——光照模型公式篇(含各种光照计算)


前言


对于数据类型的选择:
float:高精度类型,32位,通常用于世界坐标下的位置,纹理UV,或涉及到复杂函数的计算

half:中精度类型,16位,数值范围[-6000,+6000],通常用于本地坐标下的位置、方向向量、HDR颜色等。

fixed:低精度类型,11位,数值范围[-2,+2],通常用于常规的颜色贴图,以及一些低精度的运算等。


一、漫反射计算公式

1.基本光照模型中漫反射部分计算公式:

  1. 基本公式:
    在这里插入图片描述
    参数含义:
    1 、m diffuse 材质的漫反射系数
    2 、 c light 入射光的颜色和强度
    3 、 n 表面法线
    4 、 I 光源方向

  2. 有用的函数:
    函数:saturate(x)
    描述:把用于操作的标量或矢量x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量都进行这样的操作。
    提示:如果计算rgb过程中出现小于0的颜色,那么该顶点的颜色会变成0,使该公式变成非线形,导致插值错误,那么就会导致过渡不平滑或者出现锯齿。这个时候可以考虑使用max(0,x)同类函数来解决这个问题。

2.逐顶点漫反射光照:

  1. 关键代码(漫反射计算部分):
v2f vert(a2v v) {//顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间
		v2f o;//定义了返回值o

		o.pos = UnityObjectToClipPos(v.vertex);//利用UNITY内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成这样的坐标变换 访问模型空间的顶点坐标,将顶点坐标从模型空间转换到世界空间

		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//通过UNITY的内置变量UNITY_LIGHTMODEL_AMBIENT得到了环境光部分

		fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//Transform the normal from object space to world space  将法线从对象(模型)空间转换为世界空间 

		fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//光源方向
					
		fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));//漫反射部分

		o.color = ambient + diffuse;

		return o;
}
  1. 理解:
    顾名思义,逐顶点的计算应当要在顶点着色器中进行计算,每个顶点都计算出该点的颜色,直接作为顶点着色器的输出。比如一个三角形面片,计算了每个顶点的颜色值后再经过片元着色器的投影,最后会根据显示在屏幕上的像素的周围的顶点来插值计算像素的最终颜色。
    那么对于具体的漫反射的计算,我们可以根据第一点中的漫反射基本公式代入相应的量来计算。

    对于m diffuse 来说,它的具体含义就是材质的漫反射颜色,我们可以在Properties语义块中自定义它,它的值类型是Color。

    对于 c light 来说,它在这里代表了光源的颜色,我们可以通过Unity内置的变量——LightColor0来获取该Pass处理的光源颜色和强度。

    在公式中,比较麻烦的就是 n 表面法线 和 I 光源方向 的计算。但是只要掌握了基本的规律和方法,我们就可以处理绝大部分的n 表面法线 和 I 光源方向 的计算。

    在片元着色器中,要获得世界法线,我们就必须要先将顶点的法线信息从模型空间转换到世界空间。(至于为什么要获取世界法线,那是因为我们选定的计算空间就是世界空间)

    对于 I 光源方向 ,由于我们探究的是漫反射的计算,所以对于灯光的设置,我们就当作只有一个平行光,此时,我们就可以使用_WorldSpaceLightPos0(如果场景中有多个光源那么用该内置变量就会出现错误)
  2. 全部代码:
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/DiffuseVertexLevel"
{
	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 v2f {
					float4 pos : SV_POSITION;
					fixed3 color : COLOR;
				};

				struct a2v {
					float4 vertex : POSITION;
					float3 normal : NORMAL;
				};
				

				v2f vert(a2v v) {//顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间
					v2f o;//定义了返回值o

					o.pos = UnityObjectToClipPos(v.vertex);//利用UNITY内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成这样的坐标变换 访问模型空间的顶点坐标,将顶点坐标从模型空间转换到世界空间

					fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//通过UNITY的内置变量UNITY_LIGHTMODEL_AMBIENT得到了环境光部分

					fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//Transform the normal from object space to world space  将法线从对象(模型)空间转换为世界空间 

					fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//光源方向
					// Compute diffuse term
					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"
}



3.逐像素漫反射光照:

  1. 关键代码(漫反射计算部分):
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));
	//点积dot是一个浮点值,它等于 将两个向量的大小相乘,然后乘以向量之间角度的余弦值。对于 normalized 向量,如果它们指向完全相同的方向,dot 返回 1; 如果它们指向完全相反的方向 ,返回 - 1;如果向量彼此垂直,则 Dot 返回 0。
		fixed3 color = ambient + diffuse;
		return fixed4(color, 1);
}
  1. 理解:
    由于使用的公式原理和逐顶点漫反射光照计算一样,两者不同之处在于最后的效果(更加平滑)和计算的地方不同,其它的这里就不做解释。

    补充:在顶点着色器阶段只是进行了简单的顶点变化操作以及顶点的法线转换到世界空间的操作。

  2. 全部代码:

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

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'


Shader "Custom/DiffusePixelLevelMat"
{
	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 v2f {
					float4 pos : SV_POSITION;
					float3 worldNormal : TEXCOORD0;
				};

				struct a2v {
					float4 vertex : POSITION;
					float3 normal : NORMAL;
				};


				v2f vert(a2v v) {//顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间
					v2f o;//定义了返回值o

					o.pos = UnityObjectToClipPos(v.vertex);//利用UNITY内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成这样的坐标变换 访问模型空间的顶点坐标,将顶点坐标从模型空间转换到切线空间

					o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将法线从模型空间转换到世界空间 transform the normal from object space to world space

					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));
					//点积dot是一个浮点值,它等于 将两个向量的大小相乘,然后乘以向量之间角度的余弦值。对于 normalized 向量,如果它们指向完全相同的方向,dot 返回 1; 如果它们指向完全相反的方向 ,返回 - 1;如果向量彼此垂直,则 Dot 返回 0。
					fixed3 color = ambient + diffuse;
					return fixed4(color, 1);


				}

					ENDCG
	}
	}
		FallBack "Diffuse"
}




4.半兰伯特光照模型:

  1. 关键代码(漫反射计算部分):
//片元着色器计算漫反射光照
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(0.5*dot(worldNormal, worldLightDir)+0.5);//修改了这里,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术
			//点积dot是一个浮点值,它等于 将两个向量的大小相乘,然后乘以向量之间角度的余弦值。对于 normalized 向量,如果它们指向完全相同的方向,dot 返回 1; 如果它们指向完全相反的方向 ,返回 - 1;如果向量彼此垂直,则 Dot 返回 0。
			fixed3 color = ambient + diffuse;
			return fixed4(color, 1);


}
  1. 理解:

    广义的半兰伯特光照模型公式:
    在这里插入图片描述

    在原来的情况下,我们是使用saturate函数或max函数将nI的点积的结果映射到[0,1] 处,那么对于模型的背光面,该数值就会被全部映射为0此时背光面就会呈现出一片黑色,不会出现明暗变化,但是使用半兰伯特模型,背光面是有明暗变化的,不同的点积结果会映射到不同的值上。

  2. 全部代码:

Shader "Custom/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 v2f {
					float4 pos : SV_POSITION;
					float3 worldNormal : TEXCOORD0;
				};

				struct a2v {
					float4 vertex : POSITION;
					float3 normal : NORMAL;
				};


				v2f vert(a2v v) {//顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间
					v2f o;//定义了返回值o

					o.pos = UnityObjectToClipPos(v.vertex);//利用UNITY内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成这样的坐标变换 访问模型空间的顶点坐标,将顶点坐标从模型空间转换到世界空间

					o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将法线从模型空间转换到世界空间 transform the normal from object space to world space

					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(0.5*dot(worldNormal, worldLightDir)+0.5);//修改了这里,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术
					//点积dot是一个浮点值,它等于 将两个向量的大小相乘,然后乘以向量之间角度的余弦值。对于 normalized 向量,如果它们指向完全相同的方向,dot 返回 1; 如果它们指向完全相反的方向 ,返回 - 1;如果向量彼此垂直,则 Dot 返回 0。
					fixed3 color = ambient + diffuse;
					return fixed4(color, 1);


				}

					ENDCG
	}
	}
		FallBack "Diffuse"
}

二、高光反射光照模型

1.基本光照模型中高光反射部分计算公式:

1.基本公式:
在这里插入图片描述
2.有用的函数:
函数:reflect(i,n)
描述:给定入射方向i和法线方向,reflect函数可以返回反射方向。并且除了用于光照计算,该函数也可以用于给光滑表面和水面加上反射效果。
在这里插入图片描述

2.逐顶点高光反射光照:

  1. 关键代码:
v2f vert(a2v v) {
           v2f o;
           o.pos = UnityObjectToClipPos(v.vertex);//将模型的顶点从模型空间转换到裁剪空间 transform the vertex from object space to projection space 

           //计算漫反射部分
           //1.得到环境光部分
           fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
           //2.将物体的法线从模型空间转换到世界空间 transform the normal from object space to world space
           fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
           //3.得到在世界空间下光线的方向
           fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
           //4.根据标准光照模型中漫反射部分的公式C diffuse = ( C light .M diffuse) max(0,n,l) )完成漫反射部分的光照
           fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

            //计算高光反射部分
            //1.获取高光反射的反射方向,利用reflect(i,n)函数完成
            fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
            //2.获取世界空间下视线方向(注意运算)
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
            //3.根据标准光照模型中高光反射部分的公式C specular = ( C light . M specular) max(0,v.r)^m glossw完成高光反射部分的光照
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

             o.color = ambient + diffuse + specular;

             return o;
}
  1. 理解:

    由于颜色的范围在[0,1] 中,所以_Diffuse_Specular 属性可以用fixed精度的变量存储,但是_Gloss 的范围在0到几百,范围很大,所以需要float精度来存储。

    漫反射部分的计算和逐顶点漫反射光照的计算是一样的,这里就不作解释。

    对于高光反射部分,我们先利用reflect函数和worldLightDir 点到光源的方向(非正确的入射方向)worldNormal世界法线得到反射方向。

    上一点中我所写的worldLightDir不是正确的入射方向,是因为reflect函数要求的是由光源到点的方向而不是点到光源的方向。要解决这个问题,我们只需要在代码中对worldLightDir取反就行了。即-worldLightDir

    对于如何获取世界空间下的视线方向,我们只需要通过内置变量_WorldSpaceCameraPos减去世界空间下的顶点位置即可。

    上一点所提及的内置变量_WorldSpaceCameraPos,它的具体含义是:获取世界空间下的摄像机位置。
  2. 全部代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/SpecularVertexLevel"
{
    Properties
    {
       _Diffuse("Diffuse", Color) = (1,1,1,1)
       _Specular("Specular", Color) = (1,1,1,1)
       _Gloss("Gloss",Range(8,256)) = 20
    }
     SubShader
    {
      Pass{
            Tags{"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert//定义了顶点着色器的名字
            #pragma fragment frag//定义了片元着色器的名字

            #include "Lighting.cginc" //引用Unity的内置文件
               
            //定义和  Properties 语义块中 相同 的声明的属性
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            //定义顶点着色器和片元着色器的输入 结构体
            struct a2v {
                float4 vertex : POSITION; //类型 名称 :( 类别?)
                float4 normal : NORMAL;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };
            //顶点着色器的计算
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);//将模型的顶点从模型空间转换到裁剪空间 transform the vertex from object space to projection space 

                //计算漫反射部分
                //1.得到环境光部分
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //2.将物体的法线从模型空间转换到世界空间 transform the normal from object space to world space
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                //3.得到在世界空间下光线的方向
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //4.根据标准光照模型中漫反射部分的公式C diffuse = ( C light .M diffuse) max(0,n,l) )完成漫反射部分的光照
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                //计算高光反射部分
                //1.获取高光反射的反射方向,利用reflect(i,n)函数完成
                float3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //2.获取世界空间下视线方向(注意运算)
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
                //3.根据标准光照模型中高光反射部分的公式C specular = ( C light . M specular) max(0,v.r)^m glossw完成高光反射部分的光照
                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);
            }

            ENDCG

        }
    }
    FallBack "Specular"
}

3.逐像素高光反射光照:

  1. 关键代码:
//片元着色器的计算
fixed4 frag(v2f i) : SV_Target{
			//计算光照
			//1.获取环境光
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            //2.计算 漫反射
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
            //3.计算高光反射
            //3.1计算高光反射方向,利用reflect()公式计算反射方向
            fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
            //3.2 获取视角方向
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            //3.3利用公式计算高光反射
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

            return fixed4(ambient + diffuse + specular, 1);

}
  1. 理解:

    原理和逐顶点高光反射计算一样,这里不做解释。
  2. 全部代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/SpecularPixelLevel"
{
    Properties
    {
       _Diffuse("Diffuse", Color) = (1,1,1,1)
       _Specular("Specular", Color) = (1,1,1,1)
       _Gloss("Gloss",Range(8,256)) = 20
    }
     SubShader
    {
      Pass{
            Tags{ "LightMode" = "ForwardBase" }

            CGPROGRAM


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

            #include "Lighting.cginc" //引用Unity的内置文件
               
            //定义和  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;
                o.pos = UnityObjectToClipPos(v.vertex);//将模型的顶点从模型空间转换到裁剪空间  transform the vertex from object space to projection space 
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将模型的顶点的法线从模型空间转换到世界空间
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//将模型顶点位置从模型空间转换到世界空间
                return o;
            }

            //片元着色器的计算
            fixed4 frag(v2f i) : SV_Target{
                //计算光照
                //1.获取环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //2.计算 漫反射
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                //3.计算高光反射
                //3.1计算高光反射方向,利用reflect()公式计算反射方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //3.2 获取视角方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                //3.3利用公式计算高光反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1);

            }

            ENDCG

        }
    }
    FallBack "Specular"
}

4.Blinn-Phong光照模型:

  1. 关键代码:
//片元着色器的计算
fixed4 frag(v2f i) : SV_Target{
         //计算光照
         //1.获取环境光
         fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
         //2.计算 漫反射
         fixed3 worldNormal = normalize(i.worldNormal);
         fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
         fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
         //3.计算高光反射
         //3.1 获取视角方向
         fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
         //3.2获取新的矢量halfDir
         fixed3 halfDir = normalize(viewDir + worldLightDir);
         //3.3利用公式计算高光反射
         fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
         //fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);//思考的答案:因为前面已经归一化,所以这里可以直接用max来筛选大于0的数

         return fixed4(ambient + diffuse + specular, 1);

}
  1. 理解:

    Blinn模型计算高光反射的公式:
    在这里插入图片描述

    Blinn模型和Phong模型在实现高光反射的部分相比,Blinn模型没有使用反射方向,而是引入一个新的矢量h

    新的矢量h : 它是有视角方向 v 和 光照方向 I 相加后再归一化获得的。
    在这里插入图片描述

    为什么 不用反射方向r 而要引进一个新的变量h 呢?
    因为反射方向r的计算更加复杂,并且Blinn-Phong着色比Phong着色的性能更好。

  2. 全部代码:

Shader "Custom/Blinn_Phong"
{
    Properties
    {
       _Diffuse("Diffuse", Color) = (1,1,1,1)
       _Specular("Specular", Color) = (1,1,1,1)
       _Gloss("Gloss",Range(8,256)) = 20
    }
        SubShader
    {
      Pass{
            Tags{ "LightMode" = "ForwardBase" }

            CGPROGRAM




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

            #include "Lighting.cginc" //引用Unity的内置文件

            //定义和  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;
                o.pos = UnityObjectToClipPos(v.vertex);//将模型的顶点从模型空间转换到裁剪空间  transform the vertex from object space to projection space 
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将模型的顶点的法线从模型空间转换到世界空间
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//将模型顶点位置从模型空间转换到世界空间
                return o;
            }

            //片元着色器的计算
            fixed4 frag(v2f i) : SV_Target{
             //计算光照
             //1.获取环境光
             fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            //2.计算 漫反射
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
            //3.计算高光反射
            //3.1 获取视角方向
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            //3.2获取新的矢量halfDir
            fixed3 halfDir = normalize(viewDir + worldLightDir);
            //3.3利用公式计算高光反射
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
            //fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);//思考的答案:因为前面已经归一化,所以这里可以直接用max来筛选大于0的数

            return fixed4(ambient + diffuse + specular, 1);

             }

            ENDCG

       }
    }
        FallBack "Specular"
}

三、UnityCG.cginc中常用的函数

在这里插入图片描述


版权声明:本文为Nartu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。