目录
基本光照模型中漫反射计算公式:
从公式可以看出,要计算漫反射需要知道四个参数:入射光线的颜色和强度clight,材质的漫反射系数mdiffuse,表面的法线n和光源方向I。
为了防止点积的结果为负数,需要使用max操作,Cg提供了这样的函数,使用另一个函数也可以达到同样的目的,即saturate函数。
函数:saturate(x)
参数:x为用于操作的标量或者矢量,可以是float,float2,float3等类型。
描述:把x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。
逐顶点光照
新建材质和Shader,并且重命名
打开Shader进行编辑
//为Shader命名
Shader "MyShader/DiffuseVertex"
{
Properties
{
//定义一个Color属性,用于控制材质的漫反射颜色
_Diffuse ("Diffuse",Color)=(1,1,1,1)
}
SubShader {
//定义Pass语义块,顶点/片元着色器的代码需要写在Pass语义块中
Pass
{
/*LightMode标签是Pass标签中的一种,用于定义该Pass在Unity的光照流水线中的角色
只有正确定义了LightMode,我们才能得到一些Unity的内置光照模型*/
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
//使用#pragma指令告诉Unity我们定义的顶点着色器和片元着色器叫什么名字
#pragma vertex vert
#pragma fragment frag
//为了使用Unity内置的一些变量,还需要包含进Unity的内置文件Lighting.cginc
#include "Lighting.cginc"
//为了在Shader中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
fixed4 _Diffuse;
/*
定义顶点着色器的输入和输出结构体(输出结构体同时也是片元着色器的输入结构体)
为了访问顶点的法线,需要在av2中定义一个normal变量,并通过使用NORMAL语义告诉Unity
要把模型顶点的法线信息存储到normal变量中
为了把在顶点着色器中计算得到的光照颜色传递给片元着色器,需要在v2f中定义一个color变量,
并且不是必须使用COLOR语义,一些资料中会使用TEXCOORD0语义。
*/
struct a2v
{
float4 vertex: POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};
/*
在顶点着色器中进行逐顶点的漫反射光照
在第一行定义了返回值o,顶点着色器最近本的任务是把顶点位置从模型空间转换到裁剪空间中
因此需要使用Unity内置的模型*世界*投影矩阵UNITY_MATRIX_MVP完成这样的坐标转换,高版本写法为UnityObjectToClipPos(*)
然后通过Unity内置的UNITY_LIGHTMODEL_AMBIENT得到环境光部分
*/
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
/*
我们已经知道啦材质的漫反射颜色_Diffuse以及顶点法线v,normal
我们还需要知道光源的颜色和强度信息以及光源的方向
Unity提供内置变量_LightColor0来访问该Pass处理的光源的颜色和强度信息(注意:想要得到正确的值需要定义合适的LightMode标签)
光源方向可以由_WorldSpaceLightPos0来得到
(需要注意的是,这里对光源方向的计算不具有通用性,这里我们假设场景中只有一个光源且该光源的类型是平行光。
但如果场景中有多个光源并且类型可能是点光源等其它类型,直接使用_WorldSpaceLightPos0就不能得到正确的结果)
在计算法线和光源方向之间的点积时,我们需要选择它们所在的坐标系,只有两者处于同坐标空间下它们的点积才有意义,在这里我们选择了世界坐标空间。
由于av2得到的顶点法线位于模型空间中,我们需要把法线转换到世界空间中。
我们知道可以使用顶点变换矩阵的逆转置矩阵对法线进行相同的变换,因此我们首先得到模型空间到世界空间的变换矩阵的逆矩阵unity_WorldToObject,
然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵乘法。由于法线是一个三维矢量,因此只需要截取unity_WorldToObject的前三行前三列即可。
在得到世界空间中的法线和光源方向后需要进行归一化操作,在得到点积结果后需要防止结构为负数。因此使用了saturate函数。
saturate函数是Cg提供的一种函数,作用是把参数截取到[0,1]范围内。
最后在与光源的颜色和强度以及材质的漫反射颜色相乘即可得到最终的漫反射部分。
*/
fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
fixed3 worldDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldDir));
//对环境光和漫反射光部分相加,得到最终的光照部分。
o.color=ambient+diffuse;
return o;
}
//所有的计算都在顶点着色器中,因此片元着色器只需要直接把顶点颜色输出即可
fixed4 frag(v2f i):SV_TARGET
{
return fixed4(i.color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果:
逐像素光照
只需要对Shader进行一些更改就可以实现逐像素的漫反射效果。
Shader "MyShader/DiffusePiexl"
{
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 worldNormal:TEXCOORD0;
};
//顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器
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 worldDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldDir));
fixed3 color=ambient+diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果: