Unity Shader入门精要读书笔记(6)
-
光不是从石头里蹦出来的,而是由光源发射出来的。在实时渲染中,我们通常把光源当成一个没有体积的点,用 l 来表示它的方向。
-
光线由光源发射出来后,就会与一些物体相交。通常,相交的结果有两个:散射(scattering.和吸收(absorption)。
散射只改变光线的方向,但不改变光线的密度和颜色。而吸收只改变光线的密度和颜色,但不改变光线的方向。光线在物体表面经过散射后,有两种方向:一种将会散射到物体内部,这种现象被称为折射(refraction)或透射(transmission);另一种将会散射到外部,这种现象被称为反射(reflection)。对于不透明物体,折射进入物体内部的光线还会继续与内部的颗粒进行相交,其中一些光线最后会重新发射出物体表面,而另一些则被物体吸收。那些从物体表面重新发射出的光线将具有和入射光线不同的方向分布和颜色。
-
为了区分这两种不同的散射方向,我们在光照模型中使用了不同的部分来计算它们:高光反射(specular)部分表示物体表面是如何反射光线的,而漫反射(diffuse)部分则表示有多少光线会被折射、吸收和散射出表面。根据入射光线的数量和方向,我们可以计算出射光线的数量和方向,我们通常使用出射度(exitance)来描述它。辐照度和出射度之间是满足线性关系的,而它们之间的比值就是材质的漫反射和高光反射属性。
-
着色(shading)指的是,根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型。
-
环境光
(间接光照是指光线经过多个物体的反射,最终进入摄像机)。
在标准光照模型中,我们通常使用环境光来近似模拟所有间接光照,通常是一个全局变量,即场景中所有物体都使用该光照。 -
自发光
光线直接从光源进入摄像机,不经过任何反射,在标准光照模型中,直接使用该材质的自发光颜色,在实时渲染的时候,自发光的表面通常不会照亮周围的表面,也就是说该物体不会被当成光源。 -
漫反射
-
高光反射(经验模型)
Phong模型
-
Blinn模型
-
逐像素光照:在逐像素光照中,我们会以每个像素为基础,得到它的法线(可以是对顶点法线插值得到的,也可以是从法线纹理中采样得到的),然后进行光照模型的计算。这种在面片之间对顶点法线进行插值的技术被称为Phong 着色(Phong shading),也被称为Phong 插值或法线插值着色技术。这不同于我们之前讲到的Phong 光照模型。
-
逐顶点光照:与之相对的是逐顶点光照,也被称为高洛德着色(Gouraud shading)。在逐顶点光照中,我们在每个顶点上计算光照,然后会在渲染图元内部进行线性插值,最后输出成像素颜色。
-
逐顶点和逐像素的区别:由于顶点数目往往远小于像素数目,因此逐顶点光照的计算量往往要小于逐像素光照。但是,由于逐顶点光照依赖于线性插值来得到像素光照,因此,当光照模型中有非线性的计算(例如计算高光反射时)时,逐顶点光照就会出问题。在后面的章节中,我们将会看到这种情况。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象。
-
标准光照模型的局限:
(1)首先,有很多重要的物理现象无法用Blinn-Phong模型表现出来,例如菲涅耳反射(Fresnel reflection)。
(2)其次,Blinn-Phong模型是各项同性(isotropic)的,也就是说,当我们固定视角和光源方向旋转这个表面时,反射不会发生任何改变。但有些表面是具有各向异性(anisotropic)反射性质的,例如拉丝金属、毛发等。 -
防止点积为负的函数:
-
逐顶点的漫反射光照
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unlit/Chapter6_DiffuseVertexLevel"
{
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
{
fixed3 color : COLOR;
float4 pos : SV_POSITION;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//Get ambient term获得环境光
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"
}
- 逐像素关照
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unlit/Chapter6_DiffusePixelLevel"
{
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
{
float3 worldNormal:TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//Get ambient term获取环境光
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"
}
逐像素光照可以得到更加平滑的光照效果。但是,即便使用了逐像素漫反射光照,有一个问题仍然存在。在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。实际上我们可以通过添加环境光来得到非全黑的效果,但即便这样仍然无法解决背光面明暗一样的缺点。为此,有一种改善技术被提出来,这就是半兰伯特(Half Lambert)光照模型。
- 半兰伯特模型
- 高光反射模型
- 计算反射方向的函数:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unlit/Chapter6_SpecularVertexLevel"
{
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;//颜色属性范围在0-1
fixed4 _Specular;//颜色属性范围在0-1
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
fixed3 color : COLOR;
float4 pos : SV_POSITION;
};
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));//光源方向取反的原因是reflect接收的是从光源照到该顶点的向量
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"
}
- 使用逐顶点的方法得到的高光效果有比较大的问题,高光部分明显不平滑。这主要是因为,高光反射部分的计算是非线性的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。因此,我们就需要使用逐像素的方法来计算高光反射。
- 逐像素光照
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unlit/Chapter6_SpecularPixelLevel"
{
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;//颜色属性范围在0-1
fixed4 _Specular;//颜色属性范围在0-1
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float4 pos : SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
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
{
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 Phong光照模型
Shader "Unlit/Chapter6_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"
fixed4 _Diffuse;//颜色属性范围在0-1
fixed4 _Specular;//颜色属性范围在0-1
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float4 pos : SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
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
{
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 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
Fallback"Specular"
}