1.标准光照模型
标准光照模型的基本概念:只关心直接光照(direct light),也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。
它的基本方法使把光线分成4个部分,每个部分使用一种方法计算贡献度。以下是四个部分:
自发光 c emissive 丨高光反射 c specular 丨漫反射 c diffuse 丨环境光 c ambient丨
下面列出4个部分的计算公式:
环境光:在标准光照模型中,环境光是一个全局变量,计算式如下:
c ambient = g ambient
自发光:标准光照模型中,使用自发光来计算,就是直接使用了该材质的自发光颜色:
c emissive = m emissive
漫反射:漫反射符合兰伯特定律:反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比,计算公式如下:
c diffuse = (c light · m diffuse)max(0,n·l)
n是表面法线,l是指向光源的矢量单位,m diffuse是材质的漫反射颜色,c light是光源颜色。
使用max函数是为了防止法线和光源方向点乘的结果为负值。
高光反射:比较复杂,额,我是有些晕的
使用Phong模型,公式为:c specular = (c light · m specular )max(0,v·r)m gloss 因为上下标的原因,现在这样看是错的。
m gloss 控制高光区域的“亮点”有多宽,m gloss越大,亮点就越小。m specular是材质的高光反射颜色,用于控制高光反射的强度和颜色。
c light则是光源的颜色和强度,这里也需要防止v·r的结果为负数。
使用Blinn模型,公式为:c specular = (c light · m specular )max(0,n·h)m gloss
如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,反之亦然。
(这里没有很明白,等看看实操写代码有什么理解,再回来看看,实在不行就背下来!)
2.逐像素还是逐顶点
就是这些计算实在顶点着色器中计算还是在片元着色器中计算。
3.总结:标准光照模型是一个经验模型,并不完全符合真实世界中的光照现象。但是由于它的易用性,计算速度和得到的效果都比较好,所以仍被广泛应用。但是仍然有很多局限性,很多物理效果无法实现。
4.在Unity Shader中实现漫反射光照模型
4.1.逐顶点,以下是代码:(亲测)
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties{
//为了得到并控制漫反射颜色,声明一个Color类型的属性,初始为白色
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//为了使用一些内置的变量,需包含内置文件Lighting.cginc
#include "Lighting.cginc"
//定义漫反射属性变量
fixed4 _Diffuse;
//定义顶点着色器的输入与输出结构体(输出结构体同时也是片元着色器的输入结构体)
struct a2v{
float4 vertex:POSITION;
//为了访问顶点的法线,定义一个normal变量
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//计算漫反射光
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
//计算表面法线
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//计算光源方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//漫反射计算公式
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
}
}
//设置回调Shader为内置的“Diffuse”
FallBack "Diffuse"
}
4.2.逐像素,以下是代码:
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties{
//为了得到并控制漫反射颜色,声明一个Color类型的属性,初始为白色
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//为了使用一些内置的变量,需包含内置文件Lighting.cginc
#include "Lighting.cginc"
//定义漫反射属性变量
fixed4 _Diffuse;
//定义顶点着色器的输入与输出结构体(输出结构体同时也是片元着色器的输入结构体)
struct a2v{
float4 vertex:POSITION;
//为了访问顶点的法线,定义一个normal变量
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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射计算公式
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
//两者相加
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
4.3 半兰伯特模型
前面两种漫反射计算模型,在光照无法到达的区域,会显示为全黑,缺少细节,所以有了一种改善技术,就是半兰伯特光照模型,
主要思路,就是把n·l的结果范围从【-1,1】映射到【0,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 "Unity Shaders Book/Chapter 6/HalfLambert" {
Properties{
//为了得到并控制漫反射颜色,声明一个Color类型的属性,初始为白色
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//为了使用一些内置的变量,需包含内置文件Lighting.cginc
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v{
float4 vertex:POSITION;
//为了访问顶点的法线,定义一个normal变量
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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//主要修改这里的计算公式
fixed3 halfLambert = dot(worldNormal,worldLightDir)*0.5+0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
三种代码的效果如下:可以看到逐顶点的会有明显的锯齿,半兰伯特即使暗部也会有细节。
因为篇幅太长,高光反射留在下篇。
以上。