GLSL 编程/Unity/拉丝金属
本教程涵盖各向异性镜面高光。
这是关于光照的多个教程之一,它超越了 Phong 反射模型。但是,它基于使用 Phong 反射模型的光照,如“镜面高光”部分(针对每个顶点光照)和“平滑镜面高光”部分(针对每个像素光照)中所述。如果你还没有阅读过这些教程,你应该先阅读它们。
虽然 Phong 反射模型对于纸张、塑料和一些具有各向同性反射(即圆形高光)的其他材料来说相当不错,但本教程专门介绍具有各向异性反射(即非圆形高光)的材料,例如左侧照片中的拉丝铝。
Gregory Ward 在他的作品“测量和建模各向异性反射”,计算机图形(SIGGRAPH ’92 论文集),第 265-272 页,1992 年 7 月中发表了一个合适的各向异性反射模型。(该论文的副本可以在 网上 获得。)该模型用 BRDF(双向反射分布函数)来描述反射,BRDF 是一个四维函数,它描述了来自任何方向的光线如何反射到任何其他方向。他的 BRDF 模型由两项组成:漫反射项,它是 ,以及一个更复杂的镜面反射项。
让我们先来看看漫反射项 : 只是一个常数(约为 3.14159),而 指定漫反射率。原则上,每个波长都需要一个反射率;然而,通常为三个颜色分量(红色、绿色和蓝色)中的每一个指定一个反射率。如果我们包含常数 , 只是代表漫反射材质颜色 ,我们首先在“漫反射”部分中看到它,但它也出现在 Phong 反射模型中(参见“镜面高光”部分)。你可能想知道为什么因子 max(0, L·N) 没有出现在 BRDF 中。答案是 BRDF 的定义方式,它不包含该因子(因为它不是材料的真正属性),但在进行任何光照计算时,应该将其与 BRDF 相乘。
因此,为了实现给定不透明材料的 BRDF,我们必须将 BRDF 的所有项与 max(0, L·N) 相乘,并且 − 除非我们要实现物理上正确的照明 − 我们可以将任何常数因子替换为用户指定的颜色,这通常比物理量更容易控制。
对于他 BRDF 模型的镜面项,Ward 在他的论文的方程 5b 中给出了一个近似值。我稍微调整了一下,使其使用归一化表面法向量 N、归一化指向观察者的方向 V、归一化指向光源的方向 L 以及归一化半向量 H,它是 (V + L) / |V + L|。使用这些向量,Ward 对镜面项的近似值变为
这里, 是镜面反射率,描述镜面高光的颜色和强度; 和 是描述高光形状和大小的材料常数。由于所有这些变量都是材料常数,我们可以将它们合并成一个常数 。因此,我们得到一个稍微简短的版本
请记住,在着色器中实现它时,我们仍然必须将此 BRDF 项乘以 **L**·**N**,如果 **L**·**N** 小于 0,则将其设置为 0。此外,如果 **V**·**N** 小于 0,它也应该为 0,即如果我们从“错误”一侧观察表面。
还有两个向量尚未描述:**T** 和 **B**。**T** 是表面上的刷子方向,**B** 与 **T** 正交,但也位于表面上。Unity 为我们提供了一个表面上的切线向量作为顶点属性(参见 部分“着色器的调试”),我们将使用它作为向量 **T**。计算 **N** 和 **T** 的叉积生成向量 **B**,它与 **N** 和 **T** 正交,正如它应该的那样。
我们的实现基于 “平滑镜面高光”部分 中的每个像素光照着色器。我们需要另一个变化变量 tangentDirection
用于切线向量 T(即画笔方向),我们计算另外两个方向:halfwayVector
用于半程向量 H 和 binormalDirection
用于副法线向量 B。属性是 _Color
用于 ,_SpecColor
用于 ,_AlphaX
用于 ,以及 _AlphaY
用于 。
然后,片段着色器与 “平滑镜面高光”部分 中的版本非常相似,除了它规范化 tangentDirection
,计算 halfwayVector
和 binormalDirection
,并实现镜面部分的不同方程。此外,这个着色器仅计算一次点积 L·N 并将其存储在 dotLN
中,这样就可以重复使用它,而无需重新计算。它看起来像这样
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 tangentDirection = normalize(varyingTangentDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
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 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 halfwayVector =
normalize(lightDirection + viewDirection);
vec3 binormalDirection =
cross(normalDirection, tangentDirection);
float dotLN = dot(lightDirection, normalDirection);
// compute this dot product only once
vec3 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(_Color);
vec3 diffuseReflection = attenuation * vec3(_LightColor0)
* vec3(_Color) * max(0.0, dotLN);
vec3 specularReflection;
if (dotLN < 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
{
float dotHN = dot(halfwayVector, normalDirection);
float dotVN = dot(viewDirection, normalDirection);
float dotHTAlphaX =
dot(halfwayVector, tangentDirection) / _AlphaX;
float dotHBAlphaY = dot(halfwayVector,
binormalDirection) / _AlphaY;
specularReflection = attenuation * vec3(_SpecColor)
* sqrt(max(0.0, dotLN / dotVN))
* exp(-2.0 * (dotHTAlphaX * dotHTAlphaX
+ dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
}
gl_FragColor = vec4(ambientLighting
+ diffuseReflection + specularReflection, 1.0);
}
#endif
注意术语 sqrt(max(0, dotLN / dotVN))
,它是从 乘以 得来的。这确保了所有内容都大于 0。
完整的着色器代码仅定义了适当的属性和切线属性。此外,它需要一个具有加性混合但没有环境光照的第二个通道,用于额外的光源。
Shader "GLSL anisotropic per-pixel lighting" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_AlphaX ("Roughness in Brush Direction", Float) = 1.0
_AlphaY ("Roughness orthogonal to Brush Direction", Float) = 1.0
}
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 _AlphaX;
uniform float _AlphaY;
// 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 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
varying vec3 varyingTangentDirection;
// brush direction in world space
#ifdef VERTEX
attribute vec4 Tangent; // tangent vector provided
// by Unity (used as brush direction)
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
varyingTangentDirection = normalize(vec3(
modelMatrix * vec4(vec3(Tangent), 0.0)));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 tangentDirection = normalize(varyingTangentDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
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 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 halfwayVector =
normalize(lightDirection + viewDirection);
vec3 binormalDirection =
cross(normalDirection, tangentDirection);
float dotLN = dot(lightDirection, normalDirection);
// compute this dot product only once
vec3 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection = attenuation * vec3(_LightColor0)
* vec3(_Color) * max(0.0, dotLN);
vec3 specularReflection;
if (dotLN < 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
{
float dotHN = dot(halfwayVector, normalDirection);
float dotVN = dot(viewDirection, normalDirection);
float dotHTAlphaX =
dot(halfwayVector, tangentDirection) / _AlphaX;
float dotHBAlphaY =
dot(halfwayVector, binormalDirection) / _AlphaY;
specularReflection = attenuation * vec3(_SpecColor)
* sqrt(max(0.0, dotLN / dotVN))
* exp(-2.0 * (dotHTAlphaX * dotHTAlphaX
+ dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
}
gl_FragColor = vec4(ambientLighting
+ diffuseReflection + specularReflection, 1.0);
}
#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 _AlphaX;
uniform float _AlphaY;
// 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 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
varying vec3 varyingTangentDirection;
// brush direction in world space
#ifdef VERTEX
attribute vec4 Tangent; // tangent vector provided
// by Unity (used as brush direction)
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
varyingTangentDirection = normalize(vec3(
modelMatrix * vec4(vec3(Tangent), 0.0)));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 tangentDirection = normalize(varyingTangentDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
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 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 halfwayVector =
normalize(lightDirection + viewDirection);
vec3 binormalDirection =
cross(normalDirection, tangentDirection);
float dotLN = dot(lightDirection, normalDirection);
// compute this dot product only once
vec3 diffuseReflection = attenuation * vec3(_LightColor0)
* vec3(_Color) * max(0.0, dotLN);
vec3 specularReflection;
if (dotLN < 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
{
float dotHN = dot(halfwayVector, normalDirection);
float dotVN = dot(viewDirection, normalDirection);
float dotHTAlphaX =
dot(halfwayVector, tangentDirection) / _AlphaX;
float dotHBAlphaY =
dot(halfwayVector, binormalDirection) / _AlphaY;
specularReflection = attenuation * vec3(_SpecColor)
* sqrt(max(0.0, dotLN / dotVN))
* exp(-2.0 * (dotHTAlphaX * dotHTAlphaX
+ dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
}
gl_FragColor =
vec4(diffuseReflection + specularReflection, 1.0);
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
恭喜你完成了这个相当高级的教程!我们已经看到了
- 什么是 BRDF(双向反射分布函数)。
- Ward 用于各向异性反射的 BRDF 模型是什么。
- 如何实现 Ward 的 BRDF 模型。
如果你还想了解更多
- 关于 Phong 反射模型的光照,你可以阅读 “镜面高光”部分。
- 关于每个像素的光照(即 Phong 着色),你可以阅读 “平滑镜面高光”部分。
- 关于 Ward 的 BRDF 模型,你可以阅读他的文章“测量和建模各向异性反射”,计算机图形学(SIGGRAPH ’92 论文集),第 265-272 页,1992 年 7 月。(该论文的副本可 在线获取。)或者你可以阅读 Randi Rost 等人于 2009 年由 Addison-Wesley 出版的三版《OpenGL 着色语言》第 14.3 节,或 Wolfgang Engel、Jack Hoxley、Ralf Kornmann、Niko Suni 和 Jason Zink 编著的《编程顶点、几何和像素着色器》(第二版,2008 年)照明章节的第 8 节(可在 在线获取)。