本教程介绍了使用Phong反射模型的每个顶点照明(也称为Gouraud shading)。
它将“Diffuse Reflection”部分中的shader代码扩展成了两个附加术语:环境光照和镜面反射(高光)。这三个术语同时构成了Phong反射模型。
环境光
思考上图画作的左边, 尽管白衬衫的大部分处于阴影中,但没有任何一部分是完全黑色的。显然,总是有一些光从墙壁和其他物体反射而出,以照亮场景中的所有物体-至少在一定程度上。在Phong反射模型中,环境光照考虑了这种影响,环境光照取决于一般的环境光强度
I
a
m
b
i
e
n
t
I_{ambient}
Iambient和用于漫反射的材料颜色
k
d
i
f
f
u
s
e
k_{diffuse}
kdiffuse。 环境照明强度
I
a
m
b
i
e
n
t
I_{ambient}
Iambient的方程式:
*
与“漫反射”部分中的漫反射方程类似,该方程也可以解释为光的红色,绿色和蓝色分量的矢量方程。
在unity中,一个均匀的环境光可以通过主菜单选择Window > Rendering > Lighting Settings
设置Scene > Environment Lighting > Source
为Color
以指定环境光。在Unity的Cg着色器中,此颜色将以UNITY_LIGHTMODEL_AMBIENT
提供。(如果选择的“Gradient”而不是“Color”,那么UNITY_LIGHTMODEL_AMBIENT
和unity_AmbientSky
指定天空颜色,赤道颜色由unity_AmbientEquator
指定,地面颜色由unity_AmbientGround
指定)
镜面高光
如果您仔细观察上面的油画,你会看到一些镜面高光在:鼻子,头发,嘴唇,琵琶,小提琴,弓,水果等上面。Phong反射模型包含了镜面反射项,可以模拟发亮表面上的此类高光;它还包括参数
n
s
h
i
n
n
i
n
e
s
s
n_{shinniness}
nshinniness用以指定材质的光泽度。光泽度指定高光大小:光泽度越高,高光越小。
一个完美的发光表面只会在几何反射方向R上反射来自光源的光。对于不太完美发亮的表面,光会反射到R周围的方向:光泽度越小,散布就越广,高光越大,镜面反射效果越不明显。在数学上,对于归一化的表面法线向量N和归一化光源方向L,归一化的反射方向R的定义方程式为:
*
在Cg中,函数float3 reflect(float3 I, float3 N)
或float4 reflect(float4 I, float4 N)
计算上式相同的反射矢量,但是方向I
是从光源到曲面上的点的方向。因此,我们必须使用方向L
的反方向,才能使用此函数。
镜面反射项计算在观察方向V
看到的镜面反射。如上所述,如果V
接近R
,则镜面反射强度较大,其中接近程度
由光泽度
n
s
h
i
n
i
n
e
s
s
n_{shininess}
nshininess参数化。在Phong反射模型中,R
和V
余弦值 的
n
s
h
i
n
i
n
e
s
s
n_{shininess}
nshininess幂用于生成不同光泽度的高光。与漫反射的情况类似,我们应将余弦值小于0的结果固定在0。此外,镜面反射要求镜面反射的材质颜色为
k
s
p
e
c
u
l
a
r
k_{specular}
kspecular,通常为白色,以便所有高光都具有入射光
I
i
n
c
o
m
i
n
g
I_{incoming}
Iincoming的颜色。例如,上图的油画中,所有高光都是白色的。那么,Phong反射模型的镜面反射项为:
*
与漫反射情况类似,如果光源在表面的相反一侧,则应忽略镜面反射项, 即,如果点积N·L为负。
shader代码
环境光照的着色器代码非常简单,使用逐分量矢量的向量积:
float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
为了实现镜面反射,我们需要我们需要在世界空间中定义指向观察者的方向。我们可以将其计算为相机位置和顶点位置的差。相机在世界空间中的位置由Unity内置参数_WorldSpaceCameraPos
提供。然后可以像下面这样实现在世界空间中的镜面反射公式:
float3 viewDir = _WorldSpaceCameraPos - mul(unity_ObjectToWorld,input.vertex).xyz;
float3 specularReflection;
if(dot(normalDir,lightDir) < 0.0)
{
I_specular = float3(0,0,0);
}
else
{
I_specular = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0,
dot(reflect(-lightDirection, normalDirection),viewDir)),_Shininess);
}
该代码使用了与“漫反射”部分着色器代码相同的变量,以及用户指定的属性SpecColor
和_Shininess
。pow(a,b)
计算
a
b
a^b
ab。
如果将环境光照添加到第一个Pass,并且将镜面反射添加到“漫反射”部分的着色器的两个Pass中,则代码如下:
Shader "Cg per-vertex lighting" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
// pass for ambient light and first light source
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")
// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = unity_ObjectToWorld;
float3x3 modelMatrixInverse = unity_WorldToObject;
float3 normalDirection = normalize(
mul(input.normal, modelMatrixInverse));
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
output.col = float4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Blend One One // additive blending
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")
// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = unity_ObjectToWorld;
float3x3 modelMatrixInverse = unity_WorldToObject;
float3 normalDirection = normalize(
mul(input.normal, modelMatrixInverse));
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
output.col = float4(diffuseReflection
+ specularReflection, 1.0);
// no ambient contribution in this pass
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return input.col;
}
ENDCG
}
}
Fallback "Specular"
}
总结
恭喜,您刚刚学习了如何实现Phong反射模型。我们已经学会了:
- Phong反射模型中的环境照明是什么。(一般的环境光强度 I a m b i e n t I_{ambient} Iambient和用于漫反射的材料颜色 k d i f f u s e k_{diffuse} kdiffuse)
- Phong反射模型中的镜面反射项是什么。( I s p e c u l a r = I i n c o m i n g k s p e c u l a r m a x ( 0 , R ⋅ V ) n s h i n i n e s s I_{specular}=I_{incoming}k_{specular}max(0,R·V)^{n_{shininess}} Ispecular=Iincomingkspecularmax(0,R⋅V)nshininess)
- 这些术语如何在Unity中的Cg中实现。