本系列为UnityShader入门精要读书笔记总结,
原作者博客链接:http://blog.csdn.net/candycat1992/article/
书籍链接:http://product.dangdang.com/23972910.html
通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象。
首先,光线从光源中被发射出来。
然后,光线和场景中的一些物体相交:一些光线被物体吸收了,而另一些光线被散射到其他方向。
最后,摄像机吸收了一些光,产生了一张图像。
我们如何测量一个光源发射出了多少光呢?也就是说,我们如何量化光呢?在光学里,我们使用辐照度(irradiance)来量化光。对于平行光来说,它的辐照度可通过计算在垂直于 L 的单位面积上单位时间内穿过的能量来得到。在计算光照模型时,我们需要知道一个物体表面的辐照度,而物体表面往往是和l不垂直的,我们可以使用光源方向l和表面法线n之间的夹角的余弦值来得到。
因为辐照度是和照射到物体表面时光线之间的距离d/cosθ 成反比的,因此辐照度就和cosθ 成正比。
cosθ 可以使用光源方向l和表面法线n的点积来得到。这就是使用点积来计算辐照度的由来。
光照由光源发射出来后,就会与一些物体相交。通常的结果又两个:散射和吸收。
着色指的是,根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型。不同的光照模型有不同的目的。例如,一些用于描述粗糙的物体表面,一些用于描述金属表面等。
计算机图形学的第一定律:如果它看起来是对的,那么它就是对的。
标准光照模型只关心直接光照,也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。
它的基本方法是,把进入到摄像机内的光线分为4个部分,每个部分使用一种方法来计算它的贡献度。这4个部分是:
1)自发光部分。这个部分用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照技术,这些自发光的表面并不会真的照亮周围的物体,而是它本身看来更亮了而已。
2)高光反射部分。这个部分用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
3)漫反射部分。这个部分用于描述,当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
4)环境光部分。它用于描述其他所有的间接光照。
虽然标准光照模型的重点在于描述直接光照,但在真实的世界中,物体也可以被间接光照所照亮。间接光照指的是,光线通常会在多个物体之间进行反射,最后进入摄像机,也就是说,在光线进入摄像机之间,经过了不止一次的物体反射。
在标准光照模型中,我们使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它通常是一个全局变量,即场景中的所有物体都使用这个环境光。
光线也可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的共享度。它的计算也很简单,就是直接使用了该材质的自发光颜色。
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射反向上的分布都是一样的。但是,入射光线的角度很重要。
漫反射光照符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。
因此,漫反射部分的计算如下:
其中,n是表面法线,I是指向光源的单位矢量,m(diffuse)是材质的漫反射颜色,c(light)是光源颜色。需要注意的是,我们需要防止返现和光源方向点乘的结果为负值,为此,我们使用取最大值的函数来将其截取到0,这可以防止物体被从后面来的光源照亮。
这里的高光反射是一种经验模型,也就是说,它并不完全符合真实世界中的高光反射现象。它可以用于计算那些沿着完全镜面反射反向被反射的光线,这可以让物体看起来是由光泽的,例如金属材质。
计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。我们假设这些矢量都是单位矢量,下图给出了这些方向矢量。
在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第4个矢量——反射方向可以通过其他信息计算得到:
计算原理可以查看这个链接,
https://www.cnblogs.com/graphics/archive/2013/02/21/2920627.html
注意这个博客的I是入射方向。
这样,我们就可以利用Phong模型来计算高光反射的部分:
其中m(gloss)是材质的光泽度,也被反称为反光度。它用于控制高光区域的“亮点”有多宽,m(gloss)越大,亮点就越小。m(spscular)是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。c(light)则是光源的颜色和强度。
和上述的Phong模型相比,Blinn提出了一个简单的修改方法来得到类似的效果。它的基本思想是,避免计算反射方向。为此,Blinn模型引入了一个新的矢量,如下:
然后,使用n和h之间的夹角进行计算,而非v和r之间的夹角,如下图所示:
总结一下,Blinn模型的公式如下:
实际上,在一些情况下,Blinn模型更符合实验结果。
在标准光照模型中,环境光和自发光的计算是最简单的。
在Unity 5中,场景中的环境光可以在Window -> Lighting -> Ambient Source / Ambient Color / Ambient Intensity 中控制,如下图所示。在Shader 中个,我们只需要通过Unity 内置变量 UNITY_LIGHTMODEL_AMBIENT 就可以得到环境光的颜色和强度信息。而大多数物体是没有自发光特性的,因此在本书绝大部分的Shader 中都没有计算自发光部分。如果要计算自发光也非常简单,我们只需要再片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可。
漫反射光照模式公式-兰伯特模型
在漫反射公式中可以看出,要计算漫反射需要知道4个参数:入射光线的颜色和强度,材质的漫反射系数,表面法线以及光源方向。
实践:逐顶点光照漫反射
Shader "Unlit/Chapter6-MyDiffuseVertexLevel"
{
Properties {
//漫反射的颜色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
//设置渲染路径
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//使用Unity内置的一些变量而包含的内置文件
#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.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 = _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"
}
实践:逐像素光照漫反射
Shader "Unlit/Chapter6-MyDiffusePixelLevel"
{
Properties {
//漫反射的颜色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
//设置渲染路径
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//使用Unity内置的一些变量而包含的内置文件
#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.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"
}
实践:半兰伯特模型
跟上边相同只有计算漫反射部分有区别。点积的范围是(-1,1) 小于等于0的部分就是黑色,为了提高亮度,我们将它映射到0-1范围,所以先进行乘以0.5,然后加上一个0.5。
//计算漫反射
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
这部分代码之前涉及到的东西就不进行注释了。
基本光照模型中高光发射部分的计算公式:Phong模型
函数reflect(i,n)可以计算反射方向:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。
实践:逐顶点光照的高光反射效果
Shader "Unlit/Chapter6-MySpecularVertexLevel"
{
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"
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;
//顶点位置转换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
//获得环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//将法线信息转换到世界空间
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//获取世界空间灯光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//获得反射方向 第一个参数是灯光入射方向 所以需要取反
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//获取视角方向 摄像机位置-顶点位置 在世界空间处理
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//计算高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
实践:逐像素光照的高光反射效果
Shader "Unlit/Chapter6-MySpecularPixelLevel"
{
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"
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;
//两个TEXCOORD类型变量分别传递 世界空间的法线和位置信息
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
//法线信息转换到世界空间
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//顶点转换到世界空间
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
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 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//计算视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//计算高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
//直接将计算的颜色结果给最后的结果
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
Blinn模型没有使用反射方向,而是引入一个新的矢量 h ,它是通过对视角方向 v 和光照方向 i 相加后再归一化得到的
高光计算公式
实践:逐像素光照的高光反射效果-Blinn-Phong光照模型
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 计算出公式中的h向量 视角方向和灯光方向相加 然后归一化
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 计算新的高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
Blinn-Phong 光照模型的高光反射部分看起来更大、更亮一些。在实际渲染中,绝大多数情况我们都会选择Blinn-Phong光照模型