渲染:如何决定一个像素的颜色? 决定一个像素的可见性;决定这个像素上的光照计算。而光照模型就是用于决定一个像素上进行怎样的光照计算。
模拟光照,考虑的三种物理现象:
1.光线从光源中被发射出来。
2.光线和物体相交:一些光被吸收,一些光被散射到其他方向。
3.摄像机吸收一些光,形成图像。
光照:使用辐射度(irradiance)来量化光。散射只会改变光的方向,不会改变光的密度和颜色。吸收光会改变光的密度和颜色。散射到物体内部产生折射(refraction)或投射(tansmission);另一种散射到外部产生折射(reflection)。
计算时使用两种方式计算:高光反射(specular)部分表示物体表面是如何反射光线的;漫反射(diffuse)部分表示有多少光线会被折射、吸收、散射出表面。根据入射光线的数量和方向,可以计算出射出光线的数量和方向,使用出射度来描述。辐射度和出射度之间满足线性关系,之间的比值就是材质的漫反射和高光反射属性。
着色:是指根据材质属性(漫反射等)、光源信息(光源方向、辐照度),使用一个等式去计算沿某个方向的出射度的过程。这个等式叫做光照模型(Lighting Model)。不同光照模型有不同的目的。
BRDF(Bidirectional Reflectance Distribution Function):已知光源位置、方向、视角方向时。需要知道一个表面和光照如何进行交互,多少光被反射、漫反射?方向如何?。当给定模型表面上的一点时,BRDF包含了对该点外观的完整描述,使用一个数学公式来描述,并且提供一些参数来调整材质属性,通俗来说,当给定入射光线的方向和辐射度后,能计算出某个方向上的光照能量分布。
如果他看上去是对的,那么他就是对的。
标准光照模型:只关心直接光照,从光源出发出来射到物体表面后,经过表面一次反射直接进入摄像机的光线。
进入摄像机的光线分为四部分:
- 自发光(emissive),描述给定方向时,一个表面本身会向该方向发射多少辐射量。若没使用全局光照,自发光不会照亮周围物体,只是自身看起来很亮。
- 高光反射(specular):描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射(diffuse):当光线从光源照射到模型表面时。该表面会向每个方向散射多少辐射量。
- 环境光(ambient):描述其他所有间接光照。
环境光
间接光照是指光线经多个物体反射后进入摄像机。在标准光照模型中使用环境光的部分近似模拟间接光照。
自发光
光线直接从光源出发进入摄像机,不需要经过任何物体的反射。如未开启全局光照,不会当成一个光源照亮周围物体。
漫反射
用于对那些物体表面随机散射到各个方向的辐射度进行建模。漫反射中,视角的位置不重要,反射是完全随机的,而入射角度很重要。
高光反射
沿着完全镜面反射方向被反射的光线,可以让物体看起来有光泽。列如金属材质。需要知道表面法线、视角方向、光源方向、反射方向等。
光泽度(gloss)或反光度(shininess)。用于控制高光区域的“亮点”有多宽。gloss越大,亮点越小。
如果摄像机和光源距离模型足够远,Blinn模型比Phong模型更快。
在那里计算光照模型:
在片元着色器中计算,逐像素光照(per-pixel lighting); 在顶点着色器中计算,逐顶点光照(per-vertex lighting)。
逐像素光照:一每个像素为基础,得到它的法线,在进行光照模型计算。在面片之间对顶点法线进行插值的技术称为Phong着色。
逐顶点光照:在每个顶点上计算光照,在渲染图元内部进行线性插值,最后输出像素颜色,顶点数目远远小于像素数目,因此计算量会小很多。但是依赖于线性插值得到像素光照,当光照模型中有非线性的计算时,逐顶点计算就会有问题。而且由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点高的最高颜色值。
untiy中实现漫反射光照模型:逐顶点光照和逐像素光照
逐像素光照:
像素计算得到更加平滑的光照效果。但是使用了逐像素漫反射光照,在光照无法到达的地方,模型外观完全为黑色,没有明暗变化。
改善技术半兰伯特光照模型(Half Lambert)
逐顶点计算
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/3"
{
Properties{
_Diffuse("Diffuse",Color)=(1,1,1,1)
}
SubShader
{
Pass{
Tags {"LightMode"="ForwardBase"}//定义该pass在unity的光照流水线中的角色
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//使用内置的lighting cg文件
#include "Lighting.cginc"
//精度在0到1之间 使用fixed4来控制精度
fixed4 _Diffuse;
//定义顶点着色器的输入输出
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;//为了访问顶点法线使用NORMAL语义,这样顶点法线会填充normal变量
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR;//为了将顶点着色器计算到的顶点颜色传到片元着色器,使用了COLOR语义,也可使用TEXCOORD0语义
};
//顶点着色器的最基本的任务就是将顶点位置从模型空间转换到裁剪空间,因此使用内置的模型*世界*投影矩阵UNITY_MATRIX_MVP来完成坐标转换。
v2f vert(a2v v){
v2f o;
//先将模型顶点转换到裁剪空间
o.pos=UnityObjectToClipPos(v.vertex);
//再获取环境光 通过内置的UNITY_LIGHTMODEL_AMBIENT获取环境光
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//将模型法线转换到世界空间,并归一化 首先得到模型到世界的变换矩阵逆矩阵_World2Object,在通过mul置换他的位置,得到于转置矩阵相同的矩阵乘法。
fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//获取世界空间的光照方向 ,并归一化
fixed3 worldLight=normalize(_WorldSpaceLightPos0.xyz);
//使用_LightColor0访问该Pass处理的光源的颜色强度及光源方向。(必须使用合适LightMode标签才能得到正确值)
//光源方向通过 _WorldSpaceLightPos0来获取,若场景中有多个点光源等其他内型光照,直接使用_WorldSpaceLightPos0得不到正确结果。
//计算diffuse=光照*材质漫反射系数*max(0,点积(法线,光照方向)) 进行点积时只有在同意坐标系中才有意义。
//防止得到负数,使用saturate进行约束,作用就是将参数截取到[0,1]的范围
fixed3 diffuse=_LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
o.color=ambient+diffuse;
return o;
}
//最后将顶点着色器的结果输出
fixed4 frag(v2f v):SV_Target{
return fixed4(v.color,1.0);
}
ENDCG
}
}
FALLBACK "Diffuse"
}
逐像素计算
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/4"
{
Properties{
_Diffuse("Diffuse",Color)=(1.0,1.0,1.0,1.0)
}
SubShader{
Pass{
Tags {"LightModel"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2f{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
};
v2f vert(a2f f){
v2f o;
o.pos=UnityObjectToClipPos(f.vertex);
o.worldNormal=mul(f.normal,(float3x3)unity_WorldToObject);
return o;
}
//逐像素计算得到更加平滑的光照效果。但是使用了逐像素漫反射光照,在光照无法到达的地方,模型外观完全为黑色,没有明暗变化。
//看起来像一个平面,会失去模型细节表现。
fixed4 frag(v2f v):SV_Target{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal=normalize(v.worldNormal);
fixed3 worldLightNormal=normalize(_WorldSpaceLightPos0.xyz);
//计算 Diffuse
fixed3 diffuse=_LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightNormal));
fixed3 color=diffuse+ambient;
return fixed4(color,1.0);
}
ENDCG
}
}
FALLBACK "Diffuse"
}
半兰伯特计算
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/5"
{
Properties{
_Diffuse("Diffuse",Color)=(1.0,1.0,1.0,1.0)
}
SubShader{
Pass{
Tags {"LightModel"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2f{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
};
v2f vert(a2f a){
v2f o;
o.pos=UnityObjectToClipPos(a.vertex);
o.worldNormal=mul(a.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f v):SV_Target{
//获取环境光
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取世界光方向
float3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
//使用半兰伯特光照模型计算diffuse
fixed3 halfLambert=0.5*dot(v.worldNormal,worldLightDir)+0.5;
fixed3 diffuse =_LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color=diffuse+ambient;
return fixed4(color,1.0);
}
ENDCG
}
}
FALLBACK "Diffuse"
}
高光反射光照模型
逐顶点光照计算高光:出现明显不平滑的高光。因为高光反射部分的计算是非线性的。而顶点着色器中计算光照再插值的过程是线性的,破坏了原计算的非线性关系。
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unlit/6"
{
Properties{
_Diffuse("Diffuse",Color)=(1.0,1.0,1.0,1.0)
//高光控制
_Specular("Specular",Color)=(1.0,1.0,1.0,1.0)
//光泽度,越大亮点越小
_Gloss("Gloss",Range(8.0,256))=20
}
Subshader{
Pass{
Tags {"LightModel"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2f{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};
v2f vert(a2f a){
v2f v;
v.pos=UnityObjectToClipPos(a.vertex);
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//模型在世界空间中的法线
float3 worldNormal=normalize(mul(a.normal,(float3x3)unity_WorldToObject));
//世界光照方向
float3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
//计算 Diffuse
fixed3 diffuse=_LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
//获取世界空间反射方向
fixed3 reflectDir =normalize(reflect(-worldLightDir,worldNormal));
//计算视角方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(unity_ObjectToWorld,a.vertex).xyz);
//计算高光 pow(x,y) 计算x的y次方
fixed3 specular=_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);
v.color=ambient+diffuse+specular;
return v;
}
fixed4 frag(v2f v):SV_Target{
return fixed4(v.color,1.0);
}
ENDCG
}
}
FALLBACK "Specular"
}
逐像素计算高光:得到更加平滑的高光效果。(Phong光照模型)
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/7"
{
Properties{
_Diffuse("Diffuse",Color)=(1.0,1.0,1.0,1.0)
_Specular("Specular",Color)=(1.0,1.0,1.0,1.0)
_Gloss("Gloss",Range(2.0,256.0))=20
}
SubShader{
Pass{
Tags {"LightModel"="FowardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2f{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2f a){
v2f v;
v.pos=UnityObjectToClipPos(a.vertex);
v.worldNormal=normalize(mul(a.normal,(float3x3)unity_WorldToObject));
//转换模型的位置为世界空间的位置
v.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
return v;
}
fixed4 frag(v2f v):SV_Target{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT;
float3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
float3 reflectDir=normalize(reflect(-worldLightDir,v.worldNormal));
//相机向量-物体在世界空间的向量==视角方向
float3 viewDir=normalize(_WorldSpaceCameraPos.xyz-v.worldPos.xyz);
fixed3 diffuse=_LightColor0.rgb * _Diffuse.rgb * saturate(dot(v.worldNormal,worldLightDir));
fixed3 specular=_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);
fixed3 color=ambient+diffuse+specular;
return fixed4(color,1.0);
}
ENDCG
}
}
FALLBACK "Specular"
}
Blinn-Phong光照模型:高光部分看起来更加亮、大一些。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/8"
{
Properties
{
_Diffuse("Diffuse",Color)=(1.0,1.0,1.0,1.0)
_Specular("Specular",Color)=(1.0,1.0,1.0,1.0)
_Gloss("Gloss",Range(8.0,256.0))=20
}
SubShader
{
Pass
{
Tags{"LightModel"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2f
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (a2f a)
{
v2f o;
o.pos=UnityObjectToClipPos(a.vertex);
o.worldNormal=normalize(mul(a.normal,(float3x3)unity_WorldToObject));
o.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 viewDir=normalize(_WorldSpaceCameraPos.xyz-i.worldPos);
float3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
float3 reflectDir=normalize(reflect(-worldLightDir,i.worldNormal));
fixed3 diffuse=_LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal,worldLightDir));
float3 h=normalize(viewDir+worldLightDir);
fixed3 specular=_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,h)),_Gloss);
fixed3 color=ambient+specular+diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
FALLBACK "Specular"
}