GLSL 编程/Unity/镜面高光
本教程涵盖使用Phong 反射模型的逐顶点光照(也称为Gouraud 着色)。
它扩展了“漫反射”部分中的着色器代码,增加了两个额外的项:环境光和镜面反射。这三个项共同构成了 Phong 反射模型。如果您还没有阅读“漫反射”部分,现在是一个很好的机会去阅读它。
请仔细观察左侧的卡拉瓦乔的画作。虽然白衬衫的大部分区域都处于阴影中,但没有任何部分是完全黑色的。显然,总有一些光线从墙壁和其他物体反射出来,照亮场景中的所有东西——至少在一定程度上。在 Phong 反射模型中,这种效果通过环境光来考虑,环境光取决于一般环境光强度 和漫反射材料颜色。在环境光强度 的方程中
类似于“漫反射”部分中漫反射方程,这个方程也可以解释为光的红、绿、蓝分量的向量方程。
在 Unity 中,环境光可以通过从主菜单中选择编辑 > 渲染设置(在 Unity5 中选择窗口 > 光照)来指定。在 Unity 中的 GLSL 着色器中,此颜色始终可用作gl_LightModel.ambient
,它是“世界空间中的着色”部分中提到的 OpenGL 兼容性配置文件的预定义制服之一。
如果你仔细观察卡拉瓦乔的画作,你会看到几个镜面高光:在鼻子上、在头发上、在嘴唇上、在鲁特琴上、在小提琴上、在弓上、在水果上等等。Phong 反射模型包含一个镜面反射项,可以模拟光滑表面上的这种高光;它甚至包含一个参数 来指定材料的光泽度。光泽度指定高光的大小:越光亮,高光越小。
一个完美光滑的表面只会反射来自光源的光,并且反射方向为R。对于光泽度低于完美的表面,光线会反射到R周围的方向:光泽度越低,散射范围越广。在数学上,归一化反射方向R 定义为
对于归一化表面法线向量N 和归一化光源方向L。在 GLSL 中,函数vec3 reflect(vec3 I, vec3 N)
(或vec4 reflect(vec4 I, vec4 N)
)计算相同的反射向量,但对于从光源到表面上点的方向I
。因此,我们必须取反我们的方向L 来使用此函数。
镜面反射项计算观察者V方向的镜面反射。如上所述,如果V 接近R,则强度应该很大,其中“接近度”由光泽度 参数化。在 Phong 反射模型中,R 和V 之间角度的余弦被用来生成不同光泽度的高光,该角度被提升到 次方。类似于漫反射的情况,我们应该将负余弦钳位到 0。此外,镜面项需要一个镜面反射材料颜色,通常为白色,这样所有高光都具有入射光的颜色。例如,卡拉瓦乔画作中所有的高光都是白色的。Phong 反射模型的镜面项为
与 漫反射类似,如果光源位于表面的“错误”一侧,则应忽略镜面反射项;即,如果点积N·L为负。
着色器代码
[edit | edit source]环境光照的着色器代码很简单,使用逐分量向量-向量乘积
vec3 ambientLighting = vec3(gl_LightModel.ambient) * vec3(_Color);
为了实现镜面反射,我们需要在世界空间中获取观察者的方向,我们可以通过世界空间中相机位置和顶点位置之间的差值来计算。世界空间中的相机位置由 Unity 在 uniform _WorldSpaceCameraPos
中提供;顶点位置可以转换为世界空间,如 “漫反射”部分所述。然后可以像这样在世界空间中实现镜面反射项的方程
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
vec3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = vec3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
此代码片段使用与 “漫反射”部分中着色器代码相同的变量,另外还包括用户指定的属性 _SpecColor
和 _Shininess
。(名称特意选择为 fallback 着色器可以访问它们;请参阅 “漫反射”部分中的讨论。)pow(a, b)
计算 .
如果环境光照添加到第一遍(我们只需要一次),并且镜面反射添加到 “漫反射”部分中完整着色器的两遍,它看起来像这样
Shader "GLSL 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
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 color;
// the Phong lighting computed in the vertex shader
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
vec3 normalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0
- modelMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* max(0.0, dot(normalDirection, lightDirection));
vec3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = vec3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
color = vec4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = color;
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 color;
// the diffuse lighting computed in the vertex shader
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
vec3 normalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0
- modelMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
// vec3 ambientLighting =
// vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* max(0.0, dot(normalDirection, lightDirection));
vec3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = vec3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
color = vec4(diffuseReflection + specularReflection, 1.0);
// no ambient lighting in this pass
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = color;
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
总结
[edit | edit source]恭喜,您刚刚学习了如何实现 Phong 反射模型。具体来说,我们已经看到了
- Phong 反射模型中的环境光照是什么。
- Phong 反射模型中的镜面反射项是什么。
- 如何在 Unity 的 GLSL 中实现这些项。
进一步阅读
[edit | edit source]如果您还想了解更多
- 关于着色器代码,您应该阅读 “漫反射”部分.