GLSL Programming/Unity/Layers of Textures
This tutorial introduces multitexturing, i.e. the use of multiple texture images in a shader.
It extends the shader code of Section “Textured Spheres” to multiple textures and shows a way of combining them. If you haven't read that tutorial, this would be a very good opportunity to read it.
Layers of Surfaces
editMany real surfaces (e.g. the human skin illustrated in the image to the left) consist of several layers of different colors, transparencies, reflectivities, etc. If the topmost layer is opaque and doesn't transmit any light, this doesn't really matter for rendering the surface. However, in many cases the topmost layer is (semi)transparent and therefore an accurate rendering of the surface has to take multiple layers into account.
In fact, the specular reflection that is included in the Phong reflection model (see Section “Specular Highlights”) often corresponds to a transparent layer that reflects light: sweat on human skin, wax on fruits, transparent plastics with embedded pigment particles, etc. On the other hand, the diffuse reflection corresponds to the layer(s) below the topmost transparent layer.
Lighting such layered surfaces doesn't require a geometric model of the layers: they can be represented by a single, infinitely thin polygon mesh. However, the lighting computation has to compute different reflections for different layers and has to take the transmission of light between layers into account (both when light enters the layer and when it exits the layer). Examples of this approach are included in the “Dawn” demo by Nvidia (see Chapter 3 of the book “GPU Gems”, which is available online) and the “Human Head” demo by Nvidia (see Chapter 14 of the book “GPU Gems 3”, which is also available online).
A full description of these processes is beyond the scope of this tutorial. Suffice to say that layers are often associated with texture images to specify their characteristics. Here we just show how to use two textures and one particular way of combining them. The example is in fact not related to layers and therefore illustrates that multitexturing has more applications than layers of surfaces.
Lit and Unlit Earth
editDue to human activities, the unlit side of the Earth is not completely dark. Instead, artificial lights mark the position and extension of cities as shown in the image to the left. Therefore, diffuse lighting of the Earth should not just dim the texture image for the sunlit surface but actually blend it to the unlit texture image. Note that the sunlit Earth is far brighter than human-made lights on the unlit side; however, we reduce this contrast in order to show off the nighttime texture.
The shader code extends the code from Section “Textured Spheres” to two texture images and uses the computation described in Section “Diffuse Reflection” for a single, directional light source:
According to this equation, the level of diffuse lighting levelOfLighting
is max(0, N·L). We then blend the colors of the daytime texture and the nighttime texture based on levelOfLighting
. This could be achieved by multiplying the daytime color with levelOfLighting
and multiplying the nighttime color with 1.0 - levelOfLighting
before adding them to determine the fragment's color. Alternatively, the built-in GLSL function mix
can be used (mix(a, b, w) = b*w + a*(1.0-w)
), which is likely to be more efficient. Thus, the fragment shader could be:
#ifdef FRAGMENT
void main()
{
vec4 nighttimeColor = _Color
* texture2D(_MainTex, vec2(textureCoordinates));
vec4 daytimeColor = _LightColor0
* texture2D(_DecalTex, vec2(textureCoordinates));
gl_FragColor =
mix(nighttimeColor, daytimeColor, levelOfLighting);
// = daytimeColor * levelOfLighting
// + nighttimeColor * (1.0 - levelOfLighting)
}
#endif
Note that this blending is very similar to the alpha blending that was discussed in Section “Transparency” except that we perform the blending inside a fragment shader and use levelOfLighting
instead of the alpha component (i.e. the opacity) of the texture that should be blended “over” the other texture. In fact, if _DecalTex
specified an alpha component (see Section “Transparent Textures”), we could use this alpha component to blend _DecalTex
over _MainTex
. This is actually what Unity's standard Decal
shader does and it corresponds to a layer which is partially transparent on top of an opaque layer that is visible where the topmost layer is transparent.
Complete Shader Code
editThe names of the properties of the shader were chosen to agree with the property names of the fallback shader — in this case the Decal
shader (note that the fallback Decal
shade and the standard Decal
shader appear to use the two textures in opposite ways). Also, an additional property _Color
is introduced and multiplied (component-wise) to the texture color of the nighttime texture in order to control its overall brightness. Furthermore, the color of the light source _LightColor0
is multiplied (also component-wise) to the color of the daytime texture in order to take colored light sources into account.
Shader "GLSL multitexturing of Earth" {
Properties {
_DecalTex ("Daytime Earth", 2D) = "white" {}
_MainTex ("Nighttime Earth", 2D) = "white" {}
_Color ("Nighttime Color Filter", Color) = (1,1,1,1)
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
// pass for the first, directional light
GLSLPROGRAM
uniform sampler2D _MainTex;
uniform sampler2D _DecalTex;
uniform vec4 _Color;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
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 float levelOfLighting;
// level of diffuse lighting computed in vertex shader
varying vec4 textureCoordinates;
#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 lightDirection;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
lightDirection = vec3(0.0, 0.0, 0.0);
// ignore other light sources
}
levelOfLighting =
max(0.0, dot(normalDirection, lightDirection));
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec4 nighttimeColor = _Color
* texture2D(_MainTex, vec2(textureCoordinates));
vec4 daytimeColor = _LightColor0
* texture2D(_DecalTex, vec2(textureCoordinates));
gl_FragColor =
mix(nighttimeColor, daytimeColor, levelOfLighting);
// = daytimeColor * levelOfLighting
// + nighttimeColor * (1.0 - levelOfLighting)
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Decal"
}
When you run this shader, make sure that you have an activated directional light source in your scene.
Summary
editCongratulations! You have reached the end of the last tutorial on basic texturing. We have looked at:
- How layers of surfaces can influence the appearance of materials (e.g. human skin, waxed fruits, plastics, etc.)
- How artificial lights on the unlit side can be taken into account when texturing a sphere representing the Earth.
- How to implement this technique in a shader.
- How this is related to blending an alpha texture over a second opaque texture.
Further Reading
editIf you still want to know more
- about basic texturing, you should read Section “Textured Spheres”.
- about diffuse reflection, you should read Section “Diffuse Reflection”.
- about alpha textures, you should read Section “Transparent Textures”.
- about advanced skin rendering, you could read Chapter 3 “Skin in the ‘Dawn’ Demo” by Curtis Beeson and Kevin Bjorke of the book “GPU Gems” by Randima Fernando (editor) published 2004 by Addison-Wesley, which is available online, and Chapter 14 “Advanced Techniques for Realistic Real-Time Skin Rendering” by Eugene d’Eon and David Luebke of the book “GPU Gems 3” by Hubert Nguyen (editor) published 2007 by Addison-Wesley, which is also available online.