GLSL 编程/Unity/半透明物体
本教程涵盖半透明物体。
它是关于光照的几个教程之一,这些教程超出了 Phong 反射模型。但是,它基于每个像素的光照,使用 Phong 反射模型,如 “平滑镜面高光”部分 中所述。如果您还没有阅读本教程,请先阅读。
Phong 反射模型没有考虑半透明性,即光线穿过材料的可能性。虽然 “半透明表面”部分 处理了半透明表面,但本教程处理的是三维物体而不是薄表面。半透明材料的例子包括蜡、玉石、大理石、皮肤等。
不幸的是,半透明物体中的光线传输(即次表面散射)在实时游戏引擎中相当具有挑战性。从光源的角度渲染深度图会有所帮助,但由于本教程仅限于 Unity 的免费版本,因此这种方法不可取。因此,我们将模拟次表面散射的一些效果。
第一个效果将被称为“蜡状”,描述了蜡的光滑、有光泽的外观,它缺乏漫反射可以提供的强烈对比。理想情况下,我们希望在计算漫反射(但不计算镜面反射)之前平滑表面法线,实际上,如果使用法线贴图,这是可能的。然而,这里我们采取了另一种方法。为了软化漫反射的强烈对比,这是由 max(0, N·L) 项引起的(参见 “漫反射”部分),我们减少了该项的影响,因为蜡状 从 0 增加到 1。更具体地说,我们将 max(0, N·L) 项乘以 。但是,这不仅会降低对比度,还会降低照明的整体亮度。为了避免这种情况,我们将蜡状 添加到模拟由于次表面散射而产生的额外光线,该光线越“蜡状”,强度就越强。
因此,代替漫反射的这个方程
我们得到
蜡状 在 0(即常规漫反射)和 1(即不依赖于 N·L)之间。
这种方法易于实现,易于 GPU 计算,易于控制,并且它确实类似于蜡和玉石的外观,特别是如果与具有高光泽度的镜面高光相结合。
我们将要模拟的第二个效果是背光穿过物体并在物体可见的前部出射。这种效果越强,背部和前部之间的距离越小,即特别是在轮廓处,背部和前部之间的距离实际上变为零。因此,我们可以使用 “轮廓增强”部分 中讨论的技术来在轮廓处生成更多照明。但是,如果我们将封闭网格背面的实际漫反射照明考虑在内,这种效果会更有说服力。为此,我们按以下步骤进行
- 我们只渲染背面,并计算漫反射,该漫反射用一个因子加权,该因子描述了该点(在背面)离轮廓的距离。我们将像素标记为 0 的不透明度。(通常,帧缓冲区中的像素具有 1 的不透明度。通过将像素的不透明度设置为 0 来标记像素的技术也被使用,并在 “镜子”部分 中更详细地解释。)
- 我们只渲染正面(以黑色),并将所有不透明度为 1 的像素的颜色设置为黑色(即我们在第一步中没有光栅化的所有像素)。如果另一个物体与网格相交,这将是必要的。
- 我们再次渲染正面,使用来自正面的照明,并将帧缓冲区中的颜色加上一个因子,该因子描述了该点(在正面)离轮廓的距离。
在第一步和第三步中,我们使用轮廓因子 1 - |N·L|,它在轮廓处为 1,如果观察者直接看向表面,则为 0。(可以为点积引入指数以允许更多艺术控制。)因此,所有计算实际上都相当简单。复杂的部分是混合。
该实现严重依赖于混合,这在 “透明度”部分 中讨论。除了对应于上面提到的步骤的三次传递之外,我们还需要两次额外的传递,用于在背面和正面的额外光源。有了这么多传递,明确了解渲染传递应该做什么是有意义的。为此,没有 GLSL 代码的着色器框架非常有用
Shader "GLSL translucent bodies" {
Properties {
_Color ("Diffuse Color", Color) = (1,1,1,1)
_Waxiness ("Waxiness", Range(0,1)) = 0
_SpecColor ("Specular Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
_TranslucentColor ("Translucent Color", Color) = (0,0,0,1)
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// ambient light and first light source on back faces
Cull Front // render back faces only
Blend One Zero // mark rasterized pixels in framebuffer
// with alpha = 0 (usually they should have alpha = 1)
GLSLPROGRAM
[...]
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on back faces
Cull Front // render back faces only
Blend One One // additive blending
GLSLPROGRAM
[...]
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// setting pixels that were not rasterized to black
Cull Back // render front faces only (default behavior)
Blend Zero OneMinusDstAlpha // set colors of pixels
// with alpha = 1 to black by multiplying with 1-alpha
GLSLPROGRAM
#ifdef VERTEX
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main() { gl_FragColor = vec4(0.0); }
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// ambient light and first light source on front faces
Cull Back // render front faces only
Blend One SrcAlpha // multiply color in framebuffer
// with silhouetteness in fragment's alpha and add colors
GLSLPROGRAM
[...]
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on front faces
Cull Back // render front faces only
Blend One One // additive blending
GLSLPROGRAM
[...]
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
这个骨架已经很长了; 然而,它很好地说明了整体着色器是如何组织的。
在以下完整的着色器代码中,请注意,在背面计算漫反射和环境光部分时,使用属性 _TranslucentColor
而不是 _Color
。另外还要注意,如何计算背面和正面上的“轮廓感”; 但是,它只直接乘以背面的片段颜色。在正面,它只通过片段颜色的 alpha 分量以及这个 alpha 与目标颜色(帧缓冲区中像素的颜色)的混合进行间接乘法。最后,“蜡质”仅用于正面的漫反射。
Shader "GLSL translucent bodies" {
Properties {
_Color ("Diffuse Color", Color) = (1,1,1,1)
_Waxiness ("Waxiness", Range(0,1)) = 0
_SpecColor ("Specular Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
_TranslucentColor ("Translucent Color", Color) = (0,0,0,1)
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// ambient light and first light source on back faces
Cull Front // render back faces only
Blend One Zero // mark rasterized pixels in framebuffer
// with alpha = 0 (usually they should have alpha = 1)
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// 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
#ifdef VERTEX
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));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
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 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_TranslucentColor);
vec3 diffuseReflection = attenuation
* vec3(_LightColor0) * vec3(_TranslucentColor)
* max(0.0, dot(normalDirection, lightDirection));
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor = vec4(silhouetteness
* (ambientLighting + diffuseReflection), 0.0);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on back faces
Cull Front // render back faces only
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// 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
#ifdef VERTEX
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));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
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 diffuseReflection = attenuation
* vec3(_LightColor0) * vec3(_TranslucentColor)
* max(0.0, dot(normalDirection, lightDirection));
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor =
vec4(silhouetteness * diffuseReflection, 0.0);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// setting pixels that were not rasterized to black
Cull Back // render front faces only (default behavior)
Blend Zero OneMinusDstAlpha // set colors of pixels
// with alpha = 1 to black by multiplying with 1-alpha
GLSLPROGRAM
#ifdef VERTEX
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = vec4(0.0);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// ambient light and first light source on front faces
Cull Back // render front faces only
Blend One SrcAlpha // multiply color in framebuffer
// with silhouetteness in fragment's alpha and add colors
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// 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
#ifdef VERTEX
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));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
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 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* (_Waxiness + (1.0 - _Waxiness)
* 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);
}
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor = vec4(ambientLighting + diffuseReflection
+ specularReflection, silhouetteness);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on front faces
Cull Back // render front faces only
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// 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
#ifdef VERTEX
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));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
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 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* (_Waxiness + (1.0 - _Waxiness)
* 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);
}
gl_FragColor =
vec4(diffuseReflection + specularReflection, 1.0);
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
恭喜!你完成了关于半透明物体的本教程,它主要讲述了
- 如何模拟蜡的外观。
- 如何模拟背光照射的半透明材料的轮廓。
- 如何实现这些技术。
如果你还想了解更多
- 关于半透明表面,你应该阅读 “半透明表面”部分.
- 关于 Phong 反射模型的漫反射项,你应该阅读 “漫反射”部分.
- 关于 Phong 反射模型的环境光或镜面反射项,你应该阅读 “镜面高光”部分.
- 关于使用 Phong 反射模型的逐像素光照,你应该阅读 “平滑镜面高光”部分.
- 关于用于次表面散射的基本实时技术,你可以阅读 Randima Fernando(编辑)2004 年由 Addison-Wesley 出版、名为 “GPU Gems” 的书中的 Simon Green 编写的第 16 章 “实时次表面散射近似”,该书可以 在线获取.