目录
前言
–
对于数据类型的选择:
float:高精度类型,32位,通常用于世界坐标下的位置,纹理UV,或涉及到复杂函数的计算
half:中精度类型,16位,数值范围[-6000,+6000]
,通常用于本地坐标下的位置、方向向量、HDR颜色等。
fixed:低精度类型,11位,数值范围[-2,+2]
,通常用于常规的颜色与贴图,以及一些低精度的运算等。
一、漫反射计算公式
1.基本光照模型中漫反射部分计算公式:
-
基本公式:
参数含义:
1 、m diffuse 材质的漫反射系数
2 、 c light 入射光的颜色和强度
3 、 n 表面法线
4 、 I 光源方向 -
有用的函数:
函数:saturate(x)
描述:把用于操作的标量或矢量x
截取在[0,1]
范围内,如果x
是一个矢量,那么会对它的每一个分量都进行这样的操作。
提示:如果计算rgb过程中出现小于0的颜色,那么该顶点的颜色会变成0,使该公式变成非线形,导致插值错误,那么就会导致过渡不平滑或者出现锯齿。这个时候可以考虑使用max(0,x)同类函数来解决这个问题。
2.逐顶点漫反射光照:
- 关键代码(漫反射计算部分):
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;
}
- 理解:
顾名思义,逐顶点的计算应当要在顶点着色器中进行计算,每个顶点都计算出该点的颜色,直接作为顶点着色器的输出。比如一个三角形面片,计算了每个顶点的颜色值后再经过片元着色器的投影,最后会根据显示在屏幕上的像素的周围的顶点来插值计算像素的最终颜色。
那么对于具体的漫反射的计算,我们可以根据第一点中的漫反射基本公式代入相应的量来计算。
–
对于m diffuse 来说,它的具体含义就是材质的漫反射颜色,我们可以在Properties语义块中自定义它,它的值类型是Color。
–
对于 c light 来说,它在这里代表了光源的颜色,我们可以通过Unity内置的变量——LightColor0来获取该Pass处理的光源颜色和强度。
–
在公式中,比较麻烦的就是 n 表面法线 和 I 光源方向 的计算。但是只要掌握了基本的规律和方法,我们就可以处理绝大部分的n 表面法线 和 I 光源方向 的计算。
–
在片元着色器中,要获得世界法线,我们就必须要先将顶点的法线信息从模型空间转换到世界空间。(至于为什么要获取世界法线,那是因为我们选定的计算空间就是世界空间)
–
对于 I 光源方向 ,由于我们探究的是漫反射的计算,所以对于灯光的设置,我们就当作只有一个平行光,此时,我们就可以使用_WorldSpaceLightPos0(如果场景中有多个光源那么用该内置变量就会出现错误) - 全部代码:
// 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.逐像素漫反射光照:
- 关键代码(漫反射计算部分):
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);
}
-
理解:
由于使用的公式原理和逐顶点漫反射光照计算一样,两者不同之处在于最后的效果(更加平滑)和计算的地方不同,其它的这里就不做解释。
–
补充:在顶点着色器阶段只是进行了简单的顶点变化操作以及顶点的法线转换到世界空间的操作。 -
全部代码:
// 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