写在前面
现在问你:什么是漫反射?什么是高光反射?Phong和BlinnPhong模型的区别在哪儿?漫反射有哪几个参数?高光反射有几个参数?公式分别是啥?计算出来的结果指的是什么?...如果这种超基础的理论知识都回答不上来的话,建议再去恶补一遍理论!!一切都搞清楚了再上手代码也不迟~!
参考书籍
仍旧是冯乐乐那本《Unity Shader 入门精要》
1 实现漫反射光照模型
1.1逐顶点光照:Shader代码
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level"
{
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 color : COLOR;
};
v2f vert(a2v v) {
v2f o; //定义返回值o
o.pos = UnityObjectToClipPos(v.vertex); //顶点着色器的基本任务
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //获得环境光
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //法向量由物体->世界
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, worldLight));
o.color = diffuse + ambient;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果会跟后面的逐像素光照模型一起放出来做一个对比。
1.2 逐像素光照:Shader代码
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level"
{
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 : TEXCOORD0; //自定义的一些参数就用texcoord0
};
v2f vert(a2v v) {
v2f o; //定义返回值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 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(worldNormal, worldLight));
fixed3 color = diffuse + ambient;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
1.3 半兰伯特模型:Shader代码
简单谈一谈半兰伯特(Half Lambert)光照模型为什么会被提出来?
兰伯特(Lambert)光照模型使用了max把法向量和指向光源方向的向量点积限制在了[0, 1]之间,这样做就会导致原本为负的区域直接给全部映射到了0,这就意味着背面光的效果直接没有了!
而半兰伯特做了一个这样的操作,就像之前做法线[-1, 1]->像素颜色值[0, 1]映射的做法一样,它把原始点积结果映射到了[0, 1]上,那么背面光不就也有个缓慢过渡的效果了!但我们需要明白,半兰伯特是没有任何物理依据的,只是为了加强视觉效果而提出的一个方法而已。
Shader "Unity Shaders Book/Chapter 6/Diffuse 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 a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//顶点着色器输出的
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0; //自定义的一些参数就用texcoord0
};
v2f vert(a2v v) {
v2f o; //定义返回值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);
//computer diffuse
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * halfLambert;
fixed3 color = diffuse + ambient;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
1.4 三者效果动态对比
1.4.1 逐顶点和逐像素的对比
这里我直接做了一个动图,左边是逐顶点,右边是逐像素,效果可能不是特别明显但凑合,大概可以看得出来左边变化的会有锯齿一样的不匀称的感觉,而右边相对来说就很平滑:
1.4.2 逐像素和半兰伯特的对比
二者对比如下:可以看到左边的逐像素不如右边的过渡效果好,右边有个背光过渡的感觉。
2 实现高光反射光照模型
2.1 逐顶点光照:Shader代码
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unity Shaders Book/Chapter 6/Specular Vertex-Level"
{
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"
//properties
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;
//pos2clip space
o.pos = UnityObjectToClipPos(v.vertex);
//ambient
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//normal:object -> world & normalize
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//lightDir
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//reflectDir
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//viewDir =camera.pos - vertex.pos
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//the diffuse:
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//the specular:
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
//all:
o.color = diffuse + specular + ambient;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
2.2 逐像素光照:Shader代码
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level"
{
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"
//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;
//pos2clip space
o.pos = UnityObjectToClipPos(v.vertex);
//worldnormal
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//worldPos
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
//ambient
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//lightDir
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//worldNormal
fixed3 worldNormal = normalize(i.worldNormal);
//viewDir
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//reflectDir
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLightDir));
fixed3 specular = _LightColor0 * _Specular * pow(saturate(dot(viewDir, reflectDir)), _Gloss);
//plus 3:
fixed3 color = specular + ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
2.3 二者动态效果对比
我就简单录了个随着光照方向改变,逐顶点(左)和逐像素(右)的高光区域的变化,可以很明显看到左边是有锯齿的感觉的,右边明显顺滑很多!
3 实现Blinn-Phong光照模型
Blinn-Phong就是在2.3中实现的逐像素(高光+漫反射+环境光)的基础上,把高光项的max()里的viewDir和LightDir替换成了更易计算的halfDir和normal向量,相比于Phong计算的更加快了。
3.1 Shader代码
Shader "Unity Shaders Book/Chapter 6/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"
//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;
//pos2clip space
o.pos = UnityObjectToClipPos(v.vertex);
//worldnormal
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//IF using UnityObjectToWorldNormal:
//o.worldNormal = UnityObjectToWorldNormal(v.normal);
//worldPos
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
//ambient
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//lightDir
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//IF using UnityWorldSpaceLightDir:
//fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
//worldNormal
fixed3 worldNormal = normalize(i.worldNormal);
//viewDir
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//IF using UnityWorldSpaceViewDir:
//fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// //reflectDir
// fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// h:
fixed3 hDir = normalize(viewDir + worldLightDir);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLightDir));
fixed3 specular = _LightColor0 * _Specular * pow(saturate(dot(worldNormal, hDir)), _Gloss);
//plus 3:
fixed3 color = specular + ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
3.2 Blinn-Phong与Phong效果对比
可以看到Blinn-Phong(右)的高光范围比Phong(左)的更大、更亮一些,实际渲染中也多选择Blinn-Phong模型来计算。