目录
一.漫反射
1.兰伯特光照模型
漫反射光照符合兰伯特定律,即反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。所以漫反射的计算公式为
这里可以根据这个图可以更好理解,当法线与光源方向夹角越小,二者点乘是越大的,光照强度也是越大的
对于世界空间光源的方向,可以用WorldSpaceLightDir或者_WorldSpaceLightPos0,具体使用方法见代码
世界空间光源的强度以及颜色,可以用_LightColor0获取
这里注意法线方向和光源方向一定要处于同一坐标空间下,否则会有错误
法线从模型空间变换到世家空间要用模型空间变换到世界空间的逆转置矩阵,即normal=mul(float4(normal,0),unity_WorldToObject).xyz;
或者也可以使用unity内置的变换法线的方法UnityObjectToWorldNormal,具体使用方法见代码
代码
Shader "Custom/Diffuse 1"
{
Properties
{
_SpecularColor("高光反射颜色",COLOR)=(1,1,1,1)
_Diffuse("漫反射颜色",COLOR)=(1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include"Lighting.cginc"
fixed4 _Diffuse;
fixed4 _SpecularColor;
struct v2f
{
float4 pos:POSITION;
float4 color:COLOR;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//将顶点转换到裁剪空间
o.color=UNITY_LIGHTMODEL_AMBIENT;//环境光
//float3 normal=normalize(v.normal);
// float3 L=normalize(_WorldSpaceLightPos0).xyz;
float3 L=normalize(WorldSpaceLightDir(v.vertex)).xyz;
//normal=mul(float4(normal,0),unity_WorldToObject).xyz;
//normal=normalize(normal);
float3 normal = UnityObjectToWorldNormal(v.normal);
float ndotl=saturate(dot(normal,L));//兰伯特模型
//float ndotl=0.5*(dot(normal,L))+0.5;//半兰伯特模型
o.color+=ndotl*_LightColor0*_Diffuse;
return o;
}
fixed4 frag (v2f i) : COLOR
{
return i.color;
}
ENDCG
}
}
}
效果图
2.半兰伯特光照模型
Cdiffuse=(Clight · mdiffuse)(0.5 (n· I)+ 0.5)
对于背光面,在兰伯特模型中,点积结果为负数,然后均会被映射到 0 处,就会使得背光面看起来像一个平面一样;而在半兰伯特模型中,不同的点积结果会映射到不同的值,就会产生明暗变化。
不过需要注意的是,半兰伯特模型没有物理根据,仅仅是一个视觉加强技术。
总体代码只是在顶点着色器计算漫反射的时候进行了部分修改
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//将顶点转换到裁剪空间
o.color=UNITY_LIGHTMODEL_AMBIENT;//环境光
//float3 normal=normalize(v.normal);
// float3 L=normalize(_WorldSpaceLightPos0).xyz;
float3 L=normalize(WorldSpaceLightDir(v.vertex)).xyz;
//normal=mul(float4(normal,0),unity_WorldToObject).xyz;
//normal=normalize(normal);
float3 normal = UnityObjectToWorldNormal(v.normal);
//float ndotl=saturate(dot(normal,L));//兰伯特模型
float ndotl=0.5*(dot(normal,L))+0.5;//半兰伯特模型
o.color+=ndotl*_LightColor0*_Diffuse;
return o;
}
接下来给出半兰伯特模型(左)和兰伯特模型(右)的对比,可以看出差别还是挺大的
好像只看正面区别不是很明显,那么看背面区别就很明显了
二.高光反射
1.Phong模型
这里所说的高光反射是一种经验模型,并不完全符合现实中的高光发射现象。可以让物体看起来比较有光泽,金属材质是其一。要计算高光发射需要4个参数:①表面法线 ②视角方向 ③光源方向 ④反射方向 。
其中 是材质的 光泽度(gloss)或者称为 反光度(shininess)。而Max函数是为了防止点乘的结果为负数,所以将之截取至0。
可以根据这个图更好理解,当反射向量R与摄像机的方向V完全重合时,可以认为反射光完全射入了摄像机,此时这个顶点就是非常高亮的,当反射方向偏离V时,高亮就会很快的衰减,所以根据V和R的夹角,可以更快的求出高亮的程度
摄像机的方向可以用WorldSpaceViewDir(v.vertex)求出
反射向量R可以用unity给我们提供的内置函数reflect(-L,normal),也可以自己计算出反射向量R
这里可以根据这个图理解一下反射向量时怎么求的
具体代码如下
Shader "Custom/Specular"
{
Properties
{
_SpecularColor("高光反射颜色",COLOR)=(1,1,1,1)
_Diffuse("漫反射颜色",COLOR)=(1,1,1,1)
_shinness("光泽度",Range(1,64))=8
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include"Lighting.cginc"
fixed4 _Diffuse;
fixed4 _SpecularColor;
float _shinness;
struct v2f
{
float4 pos:POSITION;
float4 color:COLOR;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//将顶点转换到裁剪空间
o.color=UNITY_LIGHTMODEL_AMBIENT;//环境光
//float3 normal=normalize(v.normal);
// float3 L=normalize(_WorldSpaceLightPos0).xyz;
//normal=mul(float4(normal,0),unity_WorldToObject).xyz;
//normal=normalize(normal);
//漫反射部分
float3 L=normalize(WorldSpaceLightDir(v.vertex)).xyz;
float3 normal = UnityObjectToWorldNormal(v.normal);
//float ndotl=saturate(dot(normal,L));//兰伯特模型
float ndotl=0.5*(dot(normal,L))+0.5;//半兰伯特模型
o.color+=ndotl*_LightColor0*_Diffuse;
//高光反射部分
float3 R=reflect(-L,normal);
float3 V=WorldSpaceViewDir(v.vertex);
R=normalize(R);
V=normalize(V);
o.color.rgb+=_LightColor0*_SpecularColor*pow(saturate(dot(R,V)),_shinness);
return o;
}
fixed4 frag (v2f i) : COLOR
{
return i.color;
}
ENDCG
}
}
}
为了便于观察,我将高光反射的颜色设置为绿色,效果图如下
2.Blinn-Phong 光照模型
和上面的Phong模型相比,Blinn模型避免了反射方向 的计算,而是引入了一个新的矢量
计算公式为 :
Blinn模型 和 Phong模型都是经验模型,在实际情况中,有时候Blinn模型更加合适。
可以根据这个图更好的理解,如果V+L得到的H向量与N向量重合,那么正好也说明了反射光线与V向量重合,说名此时顶点非常高亮。所以根据N和H的夹角,可以更快的求出高亮的程度
下面是实现的代码
Shader "Custom/Specular"
{
Properties
{
_SpecularColor("高光反射颜色",COLOR)=(1,1,1,1)
_Diffuse("漫反射颜色",COLOR)=(1,1,1,1)
_shinness("光泽度",Range(1,64))=8
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include"Lighting.cginc"
fixed4 _Diffuse;
fixed4 _SpecularColor;
float _shinness;
struct v2f
{
float4 pos:POSITION;
float4 color:COLOR;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//将顶点转换到裁剪空间
o.color=UNITY_LIGHTMODEL_AMBIENT;//环境光
//float3 normal=normalize(v.normal);
// float3 L=normalize(_WorldSpaceLightPos0).xyz;
//normal=mul(float4(normal,0),unity_WorldToObject).xyz;
//normal=normalize(normal);
//漫反射部分
float3 L=normalize(WorldSpaceLightDir(v.vertex)).xyz;
float3 normal = UnityObjectToWorldNormal(v.normal);
//float ndotl=saturate(dot(normal,L));//兰伯特模型
float ndotl=0.5*(dot(normal,L))+0.5;//半兰伯特模型
o.color+=ndotl*_LightColor0*_Diffuse;
//高光反射部分
//Phong模型
/* float3 R=reflect(-L,normal);
float3 V=WorldSpaceViewDir(v.vertex);
R=normalize(R);
V=normalize(V);
o.color.rgb+=_LightColor0*_SpecularColor*pow(saturate(dot(R,V)),_shinness);*/
//Blinn-Phong 光照模型
float3 V=WorldSpaceViewDir(v.vertex);
V=normalize(V);
float3 H=normalize(L+V);
o.color.rgb+=_LightColor0*_SpecularColor*pow(saturate(dot(H,normal)),_shinness);
return o;
}
fixed4 frag (v2f i) : COLOR
{
return i.color;
}
ENDCG
}
}
}
下面是效果图
可以看出同等光泽度下,Blinn-Phong 光照模型的高光反射区域更大更亮一些。
三.逐像素光照与逐顶点光照
先介绍一下什么是逐像素光照与逐顶点光照
在顶点着色器中计算。 此方法称为 逐顶点光照 或 高洛德着色(Gouraud shading), 在每个顶点上计算光照,然后在渲染图元内部进行线性插值,输出成像素颜色。而顶点数目通常会远小于像素数目,所以逐顶点光照的计算量往往更小。不过,由于逐顶点计算依赖于线性插值,如果光照模型中存在非线性插值时(如计算高光反射)就会出现问题。而且,渲染图元内部的颜色总是暗于顶点处的最高颜色值。
在片元着色器中计算。此方法称为 逐像素光照 或 Phong着色(Phong shading),以每个像素为基础,得到它的法线(可以对顶点法线插值得到,也可以从法线纹理中采样得到)
我这次实现光照模型都使用的是逐顶点光照,在逐像素光照中只需要对shader进行一些改动就可以了,只要将计算光照的部分放到片元着色器里,通常来说,逐像素光照可以得到更平滑的效果。
四.总结
本文列出的Shader均是不完整的Shader,不可以直接运用于项目之中,会缺乏光照衰减等现象,只是为了学习光照模型的原理。且本文所列出的光照模型都是经验模型,并不能真正代表现实中的光照情况,但其仍在实时渲染领域被引用多年。