12. Reserved Fragment Shaders

A reserved fragment shader can process lighting and other effects for fragments output from the previous shader.

As described in 5. Shader Programs, the reserved fragment shader does not need to be loaded from a binary. To use it, attach it to the same program object as the vertex shader and geometry shader, using the special name GL_DMP_FRAGMENT_SHADER_DMP. The reserved fragment shader is a collection of fragment processing features. These features include the following.

  • Fragment lighting
  • Shadows
  • Fog
  • Gas
  • Miscellaneous (alpha test, w buffer)

There are reserved uniforms for each fragment process. These reserved uniforms have default values and must be set, as necessary, by the application.

12.1. Fragment Operation Mode

Reserved fragment processing replaces the standard OpenGL fragment pipeline (from the alpha test onward) with independent processing that can handle the special rendering passes required by shadows and gas. To switch this fragment operation mode, set the reserved uniform (dmp_FragOperation.mode) to the desired mode using the glUniform1i() function.

Table 12-1. Fragment Operation Modes

Fragment Operation Mode

Pipeline

GL_FRAGOP_MODE_GL_DMP (default)

Standard OpenGL fragment pipeline (standard mode).

GL_FRAGOP_MODE_SHADOW_DMP

Fragment pipeline for the shadow accumulation pass (shadow mode).

GL_FRAGOP_MODE_GAS_ACC_DMP

Fragment pipeline for rendering density information (gas mode).

12.2. Fragment Lighting

The 3DS system uses fragment lighting, which calculates the primary and secondary colors for each fragment, rather than for each vertex. It also has the following features: bump mapping, which references a texture to perturb normal vectors; shadows, which involve the creation of shadow textures and color calculations; and attenuation, which is calculated from the distance to a spotlight or another light.

The primary and secondary colors are determined by first combining multiple functions that output lookup table values based on the dot product of two vectors, and then using bitwise AND/OR operations to combine those output values. With 3DS, although you cannot fully customize the method that combines the lookup tables and vectors for the dot products, you can select different configurations from preset settings.

Nintendo expects the CTR system to use eye coordinates for several vectors, because lighting equations are considered to be in eye coordinates. Although eye coordinates are not necessarily required, all the vectors that are used must be in the same coordinate system.

To use fragment lighting, you must set dmp_FragmentLighting.enabled to GL_TRUE with the glUniform1i() function and enable at least one light. In the vertex shader, the normal vector, view vector, and tangent vector (when required for lighting) must be converted into a quaternion and output as a single vertex attribute.

Table 12-2. Enabling and Disabling Fragment Lighting

Reserved Uniform

Type

Setting Value

dmp_FragmentLighting.enabled

bool

GL_TRUE: Enable lighting.

GL_FALSE: Disable lighting (default).

12.2.1. Quaternion Conversion

Lighting calculations require all vectors to use the same coordinate system. In other words, bump mapping (described later) must convert the perturbation normals referenced in a texture from surface-local coordinates (with the vertex at the origin and the normal vector along the positive z-axis) into eye coordinates.

The following 3×3 rotation matrix converts surface-local coordinates into eye coordinates. It comprises a normal, tangent, and binormal vector, and can be converted into a quaternion (Qx, Qy, Qz, Qw).

(E represents the eye coordinates, T is the tangent, N is the normal, B is the binormal, and S represents the surface-local coordinates.)

Fragment lighting is implemented to generate a quaternion for each fragment from a quaternion for each vertex, rather than generate a vector for each fragment from the normal, tangent, and binormal (which can be calculated from the normal and tangent) vectors input for each vertex. The quaternion is converted into the original rotation matrix during fragment light processing. To use fragment lighting, you must convert each vector into a valid quaternion in the vertex shader.

Generating Normal-Only Quaternions

The CTR-SDK sample demos include a vertex shader (l_position_view_quaternion) that generates quaternions using only the vertex normal information. To generate a quaternion that transforms normals in surface-local coordinates to normals in perspective coordinates, the vertex shader uses the half angle vector between the unit normal vector (0, 0, 1) and a normal vector that has been transformed to perspective coordinates (Nx, Ny, Nz) as the axis of rotation, and derives a quaternion that performs 180° rotations.

Figure 12-1. The Rotation Axis and Angle of a Quaternion

θ Axis of rotation (α, β, γ) Unit normal vector (0, 0, 1) Perspective coordinate system normal (Nx, Ny, Nz)

For an axis of rotation (α, β, γ) and an angle of rotation θ, the derived quaternion Q would be computed as follows.

Note:

In the vertex shader, the real component of the quaternion is set to the w component.

For a θ value of 180°, this becomes:

Q = ( 0; α, β, γ )

Taking the half-angle vector as the axis of rotation, this becomes:

The orientation of the half-angle vector is undefined only when (Nx, Ny, Nz) is (0, 0, –1). In this case, assume that (α, β, γ) = (1, 0, 0).

12.2.2. Lighting Overview

3DS fragment lighting always calculates the primary and secondary colors.

The primary color is calculated first by accumulating each light's effect (with shadows, spotlight attenuation, and distance attenuation applied) on a fragment's ambient and diffuse light. The fragment's emissive light and the effect of the scene's ambient light is then added to this accumulated value. This becomes the fragment's base color.

The secondary color is calculated by accumulating each light's effect on a fragment's second specular light with shadows, spotlight attenuation, and distance attenuation applied. This color is mainly used for fragment highlights.

Like OpenGL, an object's color is determined from its ambient, diffuse, emissive, and specular light. Unlike OpenGL, however, lighting uses per-fragment calculations and second specular light, making it possible to calculate the specular light in a variety of ways. The second specular light in particular can be used to represent materials with colors that change depending on the angle.

Figure 12-2. Fragment Lighting

Ambient Light Material Settings Emissive Light Diffuse Light Effect on the Ambient Light Light Settings Effect on the Diffuse Light Specular Light Light Direction or Position Vector Effect on the Specular Light Ambient Light Scene Settings Normal Vector

12.2.3. Scene Settings

Fragment lighting can handle scene sizes from -216 through 215. Do not allow the distance between the viewpoint and any fragment or light in the scene to be greater than or equal to 216.

The scene affects fragments through its ambient light (the global ambient light). To specify the scene's global ambient light, set the reserved uniform dmp_FragmentLighting.ambient to an RGBA color using the glUniform4fv() function.

Table 12-3. Reserved Uniforms for Scene Settings

Reserved Uniform

Type

Setting Value

dmp_FragmentLighting.ambient

vec4

Specifies the scene's global ambient light (R, G, B, A).

Each component has a value between 0.0 and 1.0.

This is (0.2, 0.2, 0.2, 1.0) by default.

12.2.4. Material Settings

Material settings can be described simply as settings that use color information, such as the ambient and specular light, to represent a fragment's materials and texture. Specify material-related settings in the reserved uniforms dmp_FragmentMaterial.*.

12.2.7. Equations for the Primary Color and 12.2.8. Equations for the Secondary Color describe how the settings are used in lighting calculations.

Table 12-4. Reserved Uniforms for Material Settings

Reserved Uniform

Type

Setting Value

dmp_FragmentMaterial.ambient

vec4

Specifies the ambient light (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.2, 0.2, 0.2, 1.0) by default.

dmp_FragmentMaterial.diffuse

vec4

Specifies the ambient light (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.8, 0.8, 0.8, 1.0) by default.

dmp_FragmentMaterial.emission

vec4

Specifies the ambient light (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.0, 0.0, 0.0, 1.0) by default.

dmp_FragmentMaterial.specular0

vec4

Specifies specular light 0 (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.0, 0.0, 0.0, 1.0) by default.

dmp_FragmentMaterial.specular1

vec4

Specifies specular light 1 (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.0, 0.0, 0.0, 1.0) by default.

dmp_FragmentMaterial.samplerXX

(XX=DO,D1,RR,RG,RB,FR)

int

Specifies the lookup table numbers to use for lighting calculations.

Each factor is a number between 0 and 31.

12.2.5. Light Settings

There are two types of light settings. One configures the effect of light on a material and the other configures the light itself. Fragment lighting can handle eight lights. Specify light-related settings in the reserved uniforms (dmp_FragmentLightSource[i].*, where i is the light number between 0 and 7).

12.2.7. Equations for the Primary Color and 12.2.8. Equations for the Secondary Color describe how the settings are used in lighting calculations.

Table 12-5. Reserved Uniforms for Light Settings

Reserved Uniform

Type

Setting Value

*.enabled

bool

Enables or disables a light.

GL_TRUE: Enable light.
GL_FALSE: Disable light (default).

*.ambient

vec4

Specifies an ambient light (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.0, 0.0, 0.0, 0.0) by default.

*.diffuse

vec4

Specifies a diffuse light (R, G, B, A). Each component has a value between 0.0 and 1.0.

By default, only light number 0 is (1.0, 1.0, 1.0, 1.0).
All of the others are (0.0, 0.0, 0.0, 0.0).

*.specular0

vec4

Specifies specular light 0 (R, G, B, A). Each component has a value between 0.0 and 1.0.

By default, only light number 0 is (1.0, 1.0, 1.0, 1.0).
All of the others are (0.0, 0.0, 0.0, 0.0).

*.specular1

vec4

Specifies specular light 1 (R, G, B, A). Each component has a value between 0.0 and 1.0.

This is (0.0, 0.0, 0.0, 0.0) by default.

*.position

vec4

Specifies the position of a light source (x, y, z, w).

The vector does not need to be normalized. The w component is used to distinguish between directional (0.0) and positional light sources.

This is (0.0, 0.0, 1.0, 0.0) by default.

*.spotDirection

vec3

Specifies a spotlight direction (x, y, z).

The vector does not need to be normalized.

(0.0, 0.0, –1.0) by default.

*.shadowed

bool

Specifies whether a light is affected by shadows.

GL_TRUE: Affected by shadows.
GL_FALSE: Not affected by shadows (default).

*.geomFactor0

bool

Specifies whether to use geometry factor 0 in lighting calculations.

GL_TRUE: Use geometry factor 0.
GL_FALSE: Do not use geometry factor 0 (default).

*.geomFactor1

bool

Specifies whether to use geometry factor 1 in lighting calculations.

GL_TRUE: Use geometry factor 1.
GL_FALSE: Do not use geometry factor 1 (default).

*.twoSideDiffuse

bool

Specifies whether to use two-sided lighting.

GL_TRUE: Use two-sided lighting.
GL_FALSE: Do not use two-sided lighting (default).

*.spotEnabled

bool

Specifies whether to attenuate a spotlight based on its light distribution.

GL_TRUE: Apply spotlight attenuation.
GL_FALSE: Do not apply spotlight attenuation (default).

*.distanceAttenuationEnabled

bool

Specifies whether to attenuate a light over distance.

GL_TRUE: Apply distance attenuation.
GL_FALSE: Do not apply distance attenuation.

*.distanceAttenuationBias

float

Specifies the distance attenuation bias.

0.0 by default.

*.distanceAttenuationScale

float

Specifies the distance attenuation scale.

1.0 by default.

*.samplerXX

(XX=SP,DA)

int

Specifies the lookup table number to use for calculating spotlight attenuation and distance attenuation.

Each factor is a number between 0 and 31.

The table uses asterisks (*) to indicate dmp_FragmentLightSource[i]. Where i is a light number between 0 and 7.

12.2.6. Lighting Environment

The reserved uniforms dmp_LightEnv.* configure settings related to general lighting, including shadow texture selection, bump map settings, and lookup table input.

12.2.7. Equations for the Primary Color and 12.2.8. Equations for the Secondary Color describe how the settings are used in lighting calculations.

Table 12-6. Reserved Uniforms for the Lighting Environment

Reserved Uniform

Type

Setting Value

*.absLutInputXX

(XX=D0,D1,RR,RG,RB,FR,SP)

bool

Specifies whether to convert lookup table input for each factor into absolute values.

GL_TRUE: Convert to absolute values.
GL_FALSE: Do not convert to absolute values (default).

*.lutInputXX

(XX=D0,D1,RR,RG,RB,FR,SP)

int

Specifies the cosine of the angle between two vectors to use as lookup table input for each factor.

GL_LIGHT_ENV_NH_DMP: The normal and half vectors (default).
GL_LIGHT_ENV_VH_DMP: The view and half vectors.
GL_LIGHT_ENV_NV_DMP: The normal and view vectors.
GL_LIGHT_ENV_LN_DMP: The light and normal vectors.
GL_LIGHT_ENV_SP_DMP: The light and spotlight vectors.
GL_LIGHT_ENV_CP_DMP: The tangent vector and the projection of the half vector onto the tangent plane.

*.lutScaleXX

(XX=D0,D1,RR,RG,RB,FR,SP)

float

Specifies the scale to apply to each factor's lookup table output. After applying a scale value to the value output by the lookup table, clamping is performed within a range from -2.0 through 2.0.

0.25
0.5
1.0 (default)
2.0
4.0
8.0

*.shadowSelector

int

Specifies the texture unit to use for shadows.

GL_TEXTURE0 (default)
GL_TEXTURE1
GL_TEXTURE2
GL_TEXTURE3

*.bumpSelector

int

Specifies the texture unit to use for bump maps.

GL_TEXTURE0 (default)
GL_TEXTURE1
GL_TEXTURE2
GL_TEXTURE3

*.bumpMode

int

Specifies the perturbation mode for normal and tangent vectors.

GL_LIGHT_ENV_BUMP_NOT_USED_DMP: No bump mapping (default).
GL_LIGHT_ENV_BUMP_AS_BUMP_DMP: Perturb normal vectors.
GL_LIGHT_ENV_BUMP_AS_TANG_DMP: Perturb tangent vectors.

*.bumpRenorm

bool

Specifies whether to regenerate the third component of the normal vector.

GL_TRUE: Regenerate.
GL_FALSE: Do not regenerate (default).

*.config

int

Specifies the configuration for each factor.

GL_LIGHT_ENV_LAYER_CONFIG0_DMP (default)
GL_LIGHT_ENV_LAYER_CONFIG1_DMP
GL_LIGHT_ENV_LAYER_CONFIG2_DMP
GL_LIGHT_ENV_LAYER_CONFIG3_DMP
GL_LIGHT_ENV_LAYER_CONFIG4_DMP
GL_LIGHT_ENV_LAYER_CONFIG5_DMP
GL_LIGHT_ENV_LAYER_CONFIG6_DMP
GL_LIGHT_ENV_LAYER_CONFIG7_DMP

*.invertShadow

bool

Specifies whether to invert the shadow term (1.0 - shadow).

GL_TRUE: Invert.
GL_FALSE: Do not invert (default).

*.shadowPrimary

bool

Specifies whether to apply shadows to the primary color.

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

*.shadowSecondary

bool

Specifies whether to apply shadows to the secondary color.

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

*.shadowAlpha

bool

Specifies whether to apply shadows to the alpha component.

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

*.fresnelSelector

int

Specifies the output mode for the Fresnel factor.

GL_LIGHT_ENV_NO_FRESNEL_DMP (default)
GL_LIGHT_ENV_PRI_ALPHA_FRESNEL_DMP
GL_LIGHT_ENV_SEC_ALPHA_FRESNEL_DMP
GL_LIGHT_ENV_PRI_SEC_ALPHA_FRESNEL_DMP

*.clampHighlights

bool

Specifies whether to clamp the specular output value.

GL_TRUE: Clamp.
GL_FALSE: Do not clamp (default).

*.lutEnabledD0

bool

Specifies whether to apply output values from the lookup table for distribution 0 (D0).

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

*.lutEnabledD1

bool

Specifies whether to apply output values from the lookup table for distribution 1 (D1).

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

*.lutEnabledRefl

bool

Specifies whether to apply output values from the lookup tables for reflection (RR, RG, RB).

GL_TRUE: Apply.
GL_FALSE: Do not apply (default).

The table uses asterisks (*) to indicate dmp_LightEnv.

12.2.7. Equations for the Primary Color

The following formula summarizes how the primary color is calculated.

Colorprimary = ∑((Diffuse × DPLN × Shadow + Ambient) × Spot × DistAtt + Ambientglobal + Emission

The following paragraphs provide more information about how each term is calculated.

The product of the material and light color components is applied to the diffuse and ambient light.

Diffuse = Diffusematerial × Diffuselight
Ambient = Ambientmaterial × Ambientlight

The diffuse light is affected by shadows and the angle of incident light.

DPLN = max { 0, L∙N } or abs ( L∙N )

The effect of the angle of incident light is specified by DPLN in the equation. It is the dot product of a normalized light vector and normal vector. For one-sided lighting, it is the larger of 0 and the dot product, and for two-sided lighting it is the absolute value of the dot product. To enable one-sided or two-sided lighting, set dmp_FragmentLightSource[i].twoSideDiffuse to GL_FALSE or GL_TRUE respectively.

The effect of shadow attenuation is represented by Shadow in the equation. If either dmp_LightEnv.shadowPrimary or dmp_FragmentLightSource[i].shadowed is GL_FALSE, shadows have no effect and a value of 1.0 is applied. If either reserved uniform is GL_TRUE, the value used is sampled from the texture unit specified by dmp_LightEnv.shadowSelector. If dmp_LightEnv.invertShadow is GL_TRUE, the value used is actually the sampled value subtracted from 1.0. If any texture other than a shadow texture is bound to the specified texture unit, color components are applied unchanged to the sampled value.

The effect of spotlights is represented by Spot in the equation. You can set a lookup table for each light and configure whether it is used. Use dmp_FragmentLightSource[i].spotEnabled to configure whether spotlights are used and dmp_FragmentLightSource[i].samplerSP to set the lookup tables used by spotlights. Use dmp_FragmentLightSource[i].spotDirection to set the spotlight direction vector. A value of GL_LIGHT_ENV_SP_DMP is usually specified as the lookup table input for spotlights (dmp_LightEnv.lutInputSP).

The effect of distance attenuation is represented by DistAtt in the equation. Directional light sources are unaffected by distance attenuation.

The reserved uniform dmp_FragmentLightSource[i].distanceAttenuationEnabled configures whether distance attenuation is applied. This can be controlled for each light. However, the effect of distance attenuation is disabled (1.0 is applied) when dmp_LightEnv.config is GL_LIGHT_ENV_LAYER_CONFIG7_DMP.

The lookup table to use is set by dmp_FragmentLightSource[i].samplerDA. Lookup table input is affected by the values set for the scale (dmp_FragmentLightSource[i].distanecAttenuationScale) and bias (dmp_FragmentLightSource[i].distanceAttenuationBias).

This section has so far described the effect of each light on a fragment's primary color. This effect is calculated only for valid lights and then it is added to the material's emissive color and the global ambient color, which are unaffected by lights, to compute the fragment's final primary color.

The global ambient light is the product of the material's ambient light and the scene's ambient light.

Ambientglobal = Ambientmaterial × Ambientscene

When light source 0 is disabled (dmp_FragmentLightSource[0].enabled is GL_FALSE), however, a value of 0.0 is applied to the global ambient light and the material's emissive light.

12.2.8. Equations for the Secondary Color

The following formula summarizes how the secondary color is calculated.

Colorsecondary = ∑((Specular0 + Specular1) × f × Shadow × Spot × DistAtt)

The following paragraphs provide more information about how each term is calculated.

Except that dmp_LightEnv.shadowSecondary settings are used instead of dmp_LightEnv.shadowPrimary settings, Shadow, Spot, and DistAtt are each calculated in the same way that they are calculated for the primary color.

Specular0 and Specular1 are each calculated as follows.

Specular0 = Specular0material × Specular0light × Distribution0 × Geometry0
Specular1
= ReflectionRGB × Specular1light × Distribution1 × Geometry1

The specular term is usually calculated as the product of the specular property of a material, the specular color of a light, a distribution function, and a geometry factor. Some settings allow output from lookup tables that define different reflections for each color to be applied to the term that corresponds to a material's specular light 1. By adjusting the reflection and distribution lookup tables, you can represent fragments with a variety of different textures.

Distribution functions (factors) are represented by Distribution0 and Distribution1 in the equation. The distribution functions are configured through lookup tables for distribution 0 (D0) and distribution 1 (D1).

The reserved uniform dmp_LightEnv.lutEnabledD0 (lutEnabledD1) controls whether these functions are used. The reserved uniform dmp_FragmentLightSource[i].samplerD0 (samplerD1) specifies the lookup table number to use. The reserved uniforms dmp_LightEnv.lutInputD0 and dmp_LightEnv.lutInputD1 specify the lookup table input. dmp_LightEnv.absLutInputD0 and dmp_LightEnv.absLutInputD1 specify the absolute value of the input. Lookup table output accounts for the scale values specified by dmp_LightEnv.lutScaleD0 and dmp_LightEnv.lutScaleD1.

Reflections are represented by ReflectionRGB in the equation. They use lookup tables (RR, RG, RB) to set functions that calculate the reflection for each RGB component instead of a material's specular light 1. The reserved uniform dmp_LightEnv.lutEnabledRefl controls whether this feature is used. If GL_FALSE is specified, the material's specular color 1 is applied. The reserved uniform dmp_FragmentLightSource[i].samplerRR (samplerRG, samplerRB) specifies the lookup table number to use. The reserved uniforms dmp_LightEnv.lutInputXX specify the lookup table input and dmp_LightEnv.absLutInputXX specify the absolute value of the input (where XX is RR, RG, or RB).

Geometry factors are represented by Geometry0 and Geometry1 in the equation. They are used by the Cook-Torrance lighting model. The reserved uniforms dmp_FragmentLightSource[i].geomFactor0 and dmp_FragmentLightSource[i].geomFactor1 control whether they are used. A value of 1.0 is applied for a setting of GL_FALSE and an approximation of the geometry factors used in the Cook-Torrance lighting model is applied for a setting of GL_TRUE.

f is a function that uses the dot product of the normalized light vector and normal vector to determine whether lighting is enabled for a fragment. A value of 1.0 is always applied when dmp_LightEnv.clampHighlights is set to GL_FALSE. This setting is used to represent translucent objects that allow light to pass through them to areas where it would not otherwise reach. If GL_TRUE is specified, a value of 0.0 is applied when the dot product is 0.0 or less and a value of 1.0 is applied when the dot product is greater than 0.0. In this case, unlit areas have no specular light.

A fragment's final secondary color is calculated from the effect of each valid light's effect on it.

12.2.9. Alpha Component Lighting

The previous sections described how to calculate the primary and secondary colors with the alpha component fixed at 1.0. Fragment lighting allows you to apply Fresnel factors and shadows to the alpha component.

Fresnel factors were originally intended to be used as lookup tables (FR) for Fresnel reflections in translucent objects, but by replacing the alpha component with lookup table output, they can also be used for other purposes.

The reserved uniform dmp_FragmentLightSource[i].samplerFR specifies the number of the lookup table to use for the Fresnel factors. The reserved uniform dmp_LightEnv.lutInputFR specifies the lookup table input and dmp_LightEnv.absLutInputFR specifies the absolute values of the input. If multiple lights have been enabled, the light with the largest number has its light vector used in the dot product that is input to the lookup table. The reserved uniform dmp_LightEnv.fresnelSelector uses the following values to control the extent to which Fresnel factors are applied.

Table 12-7. Scope of the Applied Fresnel Factor

Setting Value

Applies To

GL_LIGHT_ENV_NO_FRESNEL_DMP (default)

Nothing. (Fix the alpha component at 1.0.)

GL_LIGHT_ENV_PRI_ALPHA_FRESNEL_DMP

Only the alpha component for the primary color.

GL_LIGHT_ENV_SEC_ALPHA_FRESNEL_DMP

Only the alpha component for the secondary color.

GL_LIGHT_ENV_PRI_SEC_ALPHA_FRESNEL_DMP

The alpha component for the primary and secondary colors.

The Fresnel factor is also applied to the alpha component for shadows. The shadow alpha component is multiplied with the applied alpha component when a value of GL_TRUE is specified for dmp_LightEnv.shadowAlpha, which controls the effect on the shadow alpha component. If dmp_LightEnv.invertShadow is GL_TRUE, the shadow alpha value is subtracted from 1.0 (as it is for colors) before being multiplied.

12.2.10. Creating and Specifying Lookup Tables

Lighting equations use the following eight types of lookup tables.

  • Reflections (three types: RR, RG, and RB)
  • Distribution factors (two types: D0 and D1)
  • Fresnel factors (FR)
  • Spotlights (SP)
  • Distance attenuation of light

The lookup tables for reflections (RR, RG, RB), distribution factors (D0, D1), and Fresnel factors are all material settings, and are common to all lights. The lookup tables for spotlights (SP) and the distance attenuation of light (DA) can be set differently for each light.

The layer configuration (dmp_LightEnv.config) can control which lookup tables are used for each term in the secondary color's lighting equation, except for the distance attenuation of light.

Table 12-8. Lookup Tables and Number of Cycles for Each Layer Configuration

Layer Configuration

Rr

Rg

Rb

D0

D1

Fr

Sp

Cycles

GL_LIGHT_ENV_LAYER_CONFIG0_DMP

RR

RR

RR

D0

-

-

SP

1

GL_LIGHT_ENV_LAYER_CONFIG1_DMP

RR

RR

RR

-

-

FR

SP

1

GL_LIGHT_ENV_LAYER_CONFIG2_DMP

RR

RR

RR

D0

D1

-

-

1

GL_LIGHT_ENV_LAYER_CONFIG3_DMP

-

-

-

D0

D1

FR

-

1

GL_LIGHT_ENV_LAYER_CONFIG4_DMP

RR

RG

RB

D0

D1

-

SP

2

GL_LIGHT_ENV_LAYER_CONFIG5_DMP

RR

RG

RB

D0

-

FR

SP

2

GL_LIGHT_ENV_LAYER_CONFIG6_DMP

RR

RR

RR

D0

D1

FR

SP

2

GL_LIGHT_ENV_LAYER_CONFIG7_DMP

RR

RG

RB

D0

D1

FR

SP

4

The table shows which lookup tables are used to get values for a reflection's RGB components, the distribution factors, the Fresnel factors, and the spotlight term. A value of 1.0 is applied to the lighting equation for any cell that contains a hyphen (-). In other words, the corresponding term disappears from the equation. The Cycles column shows the number of hardware cycles required for lighting calculations. To speed up lighting calculations, choose a layer configuration that minimizes this number.

Warning:

When only write access to the color buffer has been configured (when the glColorMask() function has set a value of GL_TRUE for all color buffer components and the glDisable() function has disabled GL_BLEND and GL_COLOR_LOGIC_OP), layer configurations from GL_LIGHT_ENV_LAYER_CONFIG4_DMP through GL_LIGHT_ENV_LAYER_CONFIG6_DMP require three rather than two cycles to process a single pixel.

A setting of GL_LIGHT_ENV_LAYER_CONFIG7_DMP disables the effect of distance attenuation when the primary and secondary color are calculated.

For example, when GL_LIGHT_ENV_LAYER_CONFIG0_DMP is set, all of the reflection RGB components are obtained from the RR lookup table, the distribution 0 values are obtained from the D0 lookup table, and the spotlight values are obtained from the SP lookup table. A fixed value of 1.0 is applied for distribution 1 and the Fresnel factor.

As described in 7.7. Loading Lookup Tables, lookup tables are prepared by the glTexImage1D() function. The lookup tables used for fragment lighting have a fixed width of 512 elements. The first 256 elements store the lookup table's sampling values and the last 256 elements store the differences between each of the sampling values.

The order that sampling values are stored in depends on whether lookup table input is between 0.0 and 1.0 or between –1.0 and 1.0. The reserved uniforms dmp_LightEnv.absLutInputXX (where XX is D0, D1, RR, RG, RB, FR, or SP) set the range of input values from 0.0 to 1.0 when GL_TRUE is specified or from –1.0 to 1.0 when GL_FALSE is specified.

Procedures for getting sampling values are described next, followed by the corresponding procedures for storing them.

If the range of input values is between 0.0 and 1.0, each input value is multiplied by 256 and then clamped to 255. The integer portion of this number is the index for getting values. The index is first used to get a sampling value from the lookup table, and then it is incremented by 256 to get a difference value. The difference value is multiplied by the fractional portion of the input value, and then added to the original sampling value to find the final sampling value.

Code 12-1. Procedure to Get Sampling Values for Input Between 0.0 and 1.0 (Pseudocode)
index=min(floor(input * 256), 255);
samplingValue=LUT[index] + LUT[index + 256] * (input * 256 - index); 

If the range of input values is between –1.0 and 1.0, each input value is multiplied by 128 and then its integer portion is converted to a two's complement index. The index is first used to get a sampling value from the lookup table, and then it is incremented by 256 to get a difference value. The difference value is multiplied by the fractional portion of the input value, and then added to the original sampling value to find the final sampling value.

Code 12-2. Procedure to Get Sampling Values for Input Between –1.0 and 1.0 (Pseudocode)
if (input < 0.0) {
    flooredInput=floor(input * 128);
    index = 255 + flooredInput;
    samplingValue=LUT[index] + LUT[index + 256] * (input * 128 - flooredInput);
} else {
    index=min(floor(input * 128), 127);
    samplingValue=LUT[index] + LUT[index + 256] * (input * 128 - index);
} 

Figure 12-3 shows the order that sampling values are stored in.

Figure 12-3. Order of Sampling Values Stored in the Lookup Table

0 1 254 255 256 257 510 511 Input Values 0.0000 0.0039 0.9922 0.9961 Lookup Table Diff 2 258 Value for an input of 1.0 Input Values Between 0.0 and 1.0 0 1 126 127 256 257 382 383 Input Values 0.0000 0.0078 0.9844 0.9922 Lookup Table Diff 2 258 Value for an input of 1.0 Input Values Between –1.0 and 1.0 129 385 0.0078 0.0156 –0.9922 128 384 –1.000 254 255 510 511 –0.0156 –0.078 Value for an input of 0.0

Lookup tables are usually created by storing the sampling value that results from dividing the index by 256 or 128. The difference between each sampling value and the next is stored 256 indices later. Note that the lookup table is discontinuous for input values between –1.0 and 1.0.

The following pseudocode samples illustrate this process using func as the function that calculates sampling values from input values.

Code 12-3. Creating a Lookup Table for Input Between 0.0 and 1.0 (Pseudocode)
for (i = 0; i < 256; i++) LUT[i] = func((float) i / 256.0f);
for (i = 0; i < 255; i++) LUT[i + 256] = LUT[i + 1] - LUT[i];
LUT[511] = func(1.0f) - LUT[255] * 16.0f / 15.0f; 

The difference value for an input of 1.0 (the last difference element) is multiplied by 16.0/15.0 because the GPU has a fractional precision of 4 bits. If the original difference value were stored, the highest input value would not produce the sampling value for 1.0.

Code 12-4. Creating a Lookup Table for Input Between –1.0 and 1.0 (Pseudocode)
for (i = 0; i < 128; i++) {
    LUT[i] = func((float) i / 128.0f);
    LUT[255 - i] = func((float) (i + 1) * -1.0f / 128.0f);
}
for (i= 0; i < 127; i++) {
    LUT[i + 256] = LUT[i + 1] - LUT[i];
    LUT[i + 384] = LUT[i + 129] - LUT[i + 128];
}
LUT[383] = func(1.0f) - LUT[127] * 16.0f / 15.0f;
LUT[511] = LUT[0] - LUT[255]; 

In this example, the value is multiplied by 16.0/15.0 because, if the original difference value were stored, the highest input value would not produce the sampling value for 1.0.

Load the created lookup table using the glTexImage1D() function. For target for the glBindTexture() function, specify GL_LUT_TEXTUREi_DMP to specify the lookup table to load. To reference the lookup table during lighting calculations, use the glUniform1i() function to specify the lookup table number as the reserved uniform. Note that the lookup table number specified here, GL_LUT_TEXTUREi_DMP, where i represents a number from 0 to 31, does not specify the name (ID) of the texture nor GL_LUT_TEXTUREi_DMP directly. The reserved uniform specified by the lookup table number will be either material settings when referring to reflections, distribution factors, or Fresnel factors (dmp_FragmentMaterial.samplerXX (where XX=DO,D1,RR,RG,RB,FR)) or light settings when referring to spotlight and light distance attenuation (dmp_FragmentLightSource[i].samplerXX (where XX=SP,DA)).

Code 12-5. Specifying the Reflection Lookup Table (for the R Component)
glBindTexture(GL_LUT_TEXTURE2_DMP, lutTextureID);
glTexImage1D(GL_LUT_TEXTURE2_DMP, 0, GL_LUMINANCEF_DMP, 512, 0, 
             GL_LUMINANCEF_DMP, GL_FLOAT, LUT);
glUniform1i(glGetUniformLocation(progID, "dmp_FragmentMaterial.samplerRR"), 2); 

12.2.11. Lookup Table Input

All terms (D0, D1, RR, RG, RB, FR, SP), except for the distance attenuation of light (DA), take as input the cosine of the angle between two vectors (the dot product of two normalized vectors).

The input vectors are the normal vector (N), light vector (L), view vector (V), half vector (H), tangent vector (T), binormal vector (B), spotlight direction vector, and the projection of the half vector onto the tangent plane.

Figure 12-4. Vectors Used as Lookup Table Input

N T B L H V Φ

To specify the values used as lookup table input for each factor, set the reserved uniforms for the lighting environment, dmp_LightEnv.lutInputXX (where XX is D0, D1, RR, RG, RB, FR, or SP), to one of the following values using the glUniform1i() function. The cosine of the angle between the two specified vectors is used as lookup table input for each factor.

Table 12-9. Settings for Lookup Table Input

Setting Value

Input Vector Pair

GL_LIGHT_ENV_NH_DMP

The normal and half vectors (default).

GL_LIGHT_ENV_VH_DMP

The view and half vectors.

GL_LIGHT_ENV_NV_DMP

The normal and view vectors.

GL_LIGHT_ENV_LN_DMP

The light and normal vectors.

GL_LIGHT_ENV_SP_DMP

The inverse light vector and the spotlight vector (cannot be used with RR, RG, RB, and FR).

GL_LIGHT_ENV_CP_DMP

The tangent vector and the projection of the half vector onto the tangent plane (cannot be used with RR, RG, RB, and FR).

12.2.12. Distance Attenuation of Light

The following equation calculates input values for the distance attenuation of light.

fposition is the fragment position and lposition is the light position. Both are expected to use eye coordinates. Position only has meaning for a point light source. It means nothing for a directional light source.

This equation shows that the distance between a fragment and a light is multiplied by a scale value, and then added to a bias value to calculate an input value for the distance attenuation of light.

Create lookup tables that take input between 0.0 and 1.0 (absolute values). Use the glUniform1f() function to set the scale value in the reserved uniform dmp_FragmentLightSource[i].distanceAttenuationScale and the bias value in dmp_FragmentLightSource[i].distanceAttenuationBias.

When GL_LIGHT_ENV_LAYER_CONFIG7_DMP is specified as the layer configuration’s setting value, you must disable distance attenuation. Set dmp_FragmentLightSource[i].distanceAttenuationEnabled to GL_FALSE.

12.2.13. Texture Combiner Settings

The primary and secondary colors calculated by fragment lighting can each be used as an input source to a texture combiner.

To use them, set the reserved uniforms for the input source (dmp_TexEnv[i].srcRgb and dmp_TexEnv[i].srcAlpha) to GL_FRAGMENT_PRIMARY_COLOR_DMP for the primary color, and to GL_FRAGMENT_SECONDARY_COLOR_DMP for the secondary color.

The output value is (0.0, 0.0, 0.0, 1.0) when lighting is disabled (when dmp_FragmentLighting.enabled is GL_FALSE).

12.3. Bump Mapping

Bump mapping is a feature of fragment lighting that perturbs (alters) a fragment's normal and tangent vectors according to a normal map that is input as a texture. Bump mapping can make an object appear to have shadows caused by surface irregularities. This allows you to render a simple model that looks complex but actually has a small polygon count.

12.3.1. Reserved Uniform

The following reserved uniforms are used for bump mapping.

Normal Maps

For the normal map texture for bump mapping (dmp_LightEnv.bumpSelector), specify the texture unit to which the texture is bound. A normal map texture is created with the x, y, and z components of the perturbation vectors encoded in the R, G, and B components, respectively. A vector value of &ndash;1.0 is encoded as the minimum luminance (0 in an 8-bit format), and 1.0 is encoded as the maximum luminance (255 in an 8-bit format).

Perturbation Mode

To enable bump mapping, set the perturbation mode (dmp_LightEnv.bumpMode) to any value other than GL_LIGHT_ENV_BUMP_NOT_USED_BUMP. The following table shows the perturbation modes.

Table 12-10. Perturbation Modes

Perturbation Mode

Perturbed Vectors

GL_LIGHT_ENV_BUMP_NOT_USED_BUMP

None.

GL_LIGHT_ENV_BUMP_AS_BUMP_DMP

Normal vectors (bump mapping).

GL_LIGHT_ENV_BUMP_AS_TANG_DMP

Tangent vectors (tangent mapping).

Normal Recalculation

If dmp_LightEnv.bumpRenorm is set to GL_TRUE to enable recalculation of normal vectors, the z-component of the normal vector is not obtained from the B component sampled from a texture. Instead, the z-component is recalculated from the x-component and the y-component.

Note: If the expression inside the square root is negative, the result is 0.

In most cases, recalculating values yields better results than sampling them from a texture. This recalculation feature must be enabled, if bump mapping (of normals) uses a texture that only has R and G components (GL_HILO8_DMP). However, if you have selected tangent mapping as the perturbation mode for use with a technique such as anisotropic reflections, we recommend that you avoid using this feature. This is because tangent mapping (for fragment lighting) expects input perturbation tangents that do not have a z-component. If the recalculation feature is enabled, nonzero values may be generated in the z-component.

If recalculation is disabled, the perturbation normal vectors sampled from the texture are not normalized before they are used. Make sure that you normalize values before storing them in a texture. Re-enable normal calculation when point sampling (GL_NEAREST) is not configured as the texture filter mode because filtering can cause the non-normalized values to be used as the perturbation normals.

Table 12-11. Reserved Uniforms Used for Bump Mapping

Reserved Uniform

Type

Setting Value

dmp_LightEnv.bumpSelector

int

Specifies the texture unit to use for the normal map.

GL_TEXTURE0 (default)
GL_TEXTURE1
GL_TEXTURE2
GL_TEXTURE3

dmp_LightEnv.bumpMode

int

Specifies the perturbation mode for normal and tangent vectors.

GL_LIGHT_ENV_BUMP_NOT_USED_DMP: No bump mapping (default).
GL_LIGHT_ENV_BUMP_AS_BUMP_DMP: Perturb normal vectors.
GL_LIGHT_ENV_BUMP_AS_TANG_DMP: Perturb tangent vectors.

dmp_LightEnv.bumpRenorm

bool

Specifies whether to regenerate the third component of the normal vector.

GL_TRUE: Regenerate.
GL_FALSE: Do not regenerate (default).

12.4. Shadows

3DS shadows are rendered in two passes. First, the shadow accumulation pass creates a shadow buffer (that contains the scene's depth values taking the light source as the origin), which is then referenced by the shadow lookup pass to cast shadows. The shadow intensity information collected, along with the depth values in the first pass, allows you to represent soft shadows.

12.4.1. Shadow Accumulation Pass

The shadow accumulation pass requires that the fragment operation mode (dmp_FragOperation.mode) be switched to shadow mode (GL_FRAGOP_MODE_SHADOW_DMP), and that the shadow information (depth values and shadow intensity) be stored in a shadow texture (with a format of GL_SHADOW_DMP and a type of GL_UNSIGNED_INT). Note that only texture unit 0 (GL_TEXTURE0) can write shadow information to a shadow texture. Also note that mipmaps cannot be applied to shadow textures.

When the fragment pipeline switches to shadow mode, shadow information is output to the attachment point for the color buffer rather than the depth or stencil buffer. As a result, a shadow texture must be attached to the color buffer's attachment point (GL_COLOR_ATTACHMENT0). Render targets attached to depth and stencil attachment points are ignored in shadow mode. The alpha and stencil tests are skipped.

You can use the following procedure to create a shadow texture and specify a render target.

Code 12-6. Creating a Shadow Texture and Specifying a Render Target
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &shadowTexID);
glBindTexture(GL_TEXTURE_2D, shadowTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SHADOW_DMP, shadowWidth, shadowHeight, 0, 
        GL_SHADOW_DMP, GL_UNSIGNED_INT, 0);
glGenFramebuffers(1, &shadowFboID);
glBindFramebuffer(GL_FRAMEBUFFER, shadowFboID);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 
        shadowTexID, 0); 

Shadow information is accumulated using the coordinate system of the light source. Shadow information comprises the depth from the light source (the depth values) and the shadow intensity. When rendering shadows, there are no shadows wherever the G component of the color information is 1.0 (the R, B, and A components have no effect), there are opaque hard shadows wherever the G component of the color information is 0.0, and there are non-opaque shadows (soft shadows) everywhere else.

When opaque hard shadows are rendered, only the shadow depth values are updated. The shadow intensity does not change. The depth of a fragment and its corresponding pixel in the shadow buffer are compared (using GL_LESS). If the fragment has a smaller value, the depth value in the shadow buffer is updated.

When non-opaque soft shadows are rendered, only the shadow intensity is updated. The shadow depth values do not change. The depth of a fragment and its corresponding pixel in the shadow buffer are compared (using GL_LESS). If the fragment has a smaller value, the shadow intensity is also compared (using GL_LESS) and then, if the fragment still has a smaller value, the shadow intensity in the shadow buffer is updated.

When you initialize the color buffer in the shadow accumulation pass you must set the clear color to (1.0, 1.0, 1.0, 1.0) by using the glClearColor() function and specify GL_COLOR_BUFFER_BIT to the glClear() function. Note that you must set all color components (R, G, B, and A)—not just the G component—equal to 1.0 in the clear color.

The next shadow lookup pass is processed in eye coordinates, so the depth values must be created by using linear interpolation in eye space. (In most cases this differs from OpenGL, which uses non-linear relationships.) As a result, you must set a value in the reserved uniform for the w-buffer's scale factor (dmp_FragOperation.wScale), using the glUniform1f() function. This has an initial value of 0.0, which results in the same non-linear relationship as OpenGL. Depth values have a lower valid precision around the far clipping plane, when the near clipping plane is close to the viewpoint. To use linear interpolation, with f set as the clip value for the far clipping plane, set the scale factor to 1.0/f for a perspective projection or to 1.0 for an orthographic projection.

An object casts a hard shadow if it is rendered with a G color component of 0.0. This is generally implemented by disabling textures, and then implementing a vertex shader that outputs those vertex colors with G components of 0.0. Every other rendered color is treated as a soft shadow.

Several rendering passes may be necessary to accumulate the required shadow information. Information for non-opaque shadows must be accumulated after information for opaque shadows. Results are not guaranteed if information is accumulated in the opposite order or in alternating order. When light sources do not move, you can generate shadow textures more efficiently by rendering motionless objects alone to a shadow texture ahead of time. Simplifying object shapes and decreasing polygon counts are also effective ways to improve performance.

Texture unit 0 and the texture combiners are configured as follows, when shadows are rendered using a vertex shader implementation that outputs vertex colors.

Code 12-7. Sample Settings for Texture Units and Texture Combiners When Rendering Shadows
glUniform1i(glGetUniformLocation(progID, "dmp_Texture[0].samplerType"), 
        GL_FALSE);
glUniform1i(glGetUniformLocation(progID, "dmp_TexEnv[0].combineRgb"), 
        GL_REPLACE);
glUniform1i(glGetUniformLocation(progID, "dmp_TexEnv[0].combineAlpha"), 
        GL_REPLACE);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].operandRgb"), 
        GL_SRC_COLOR, GL_SRC_COLOR, GL_SRC_COLOR);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].operandAlpha"), 
        GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].srcRgb"), 
        GL_PRIMARY_COLOR, GL_PRIMARY_COLOR, GL_PRIMARY_COLOR);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].srcAlpha"), 
        GL_PRIMARY_COLOR, GL_PRIMARY_COLOR, GL_PRIMARY_COLOR); 

12.4.2. Shadow Lookup Pass

The shadow lookup pass requires that the fragment operation mode be switched to normal mode (GL_FRAGOP_MODE_GL_DMP), and that texture unit 0 (GL_TEXTURE0) be configured to reference the shadow texture that has accumulated the shadow information. To reference a shadow texture, you must set the reserved uniform dmp_TexEnv[0].samplerType to GL_TEXTURE_SHADOW_2D_DMP (which specifies shadow textures), and bind the texture that has accumulated shadow information to GL_TEXTURE_2D. When fragment lighting is enabled, its primary and secondary colors are used as texture combiner input. Otherwise, vertex colors and output from texture unit 0 are used.

The reserved uniform dmp_Texture[0].perspectiveShadow indicates whether a perspective projection or orthographic projection was applied when the shadow accumulation pass was run. Specify GL_TRUE for perspective projection or GL_FALSE for orthographic projection.

Code 12-8. Sample Settings When Fragment Lighting Is Disabled
glUniform1i(glGetUniformLocation(progID, "dmp_Texture[0].samplerType"), 
        GL_TEXTURE_SHADOW_2D_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_Texture[0].perspectiveShadow"), 
        GL_TRUE);
glUniform1i(glGetUniformLocation(progID, "dmp_TexEnv[0].combineRgb"), 
        GL_MODULATE);
glUniform1i(glGetUniformLocation(progID, "dmp_TexEnv[0].combineAlpha"), 
        GL_MODULATE);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].operandRgb"), 
        GL_SRC_COLOR, GL_SRC_COLOR, GL_SRC_COLOR);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].operandAlpha"), 
        GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].srcRgb"), 
        GL_TEXTURE0, GL_PRIMARY_COLOR, GL_PRIMARY_COLOR);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[0].srcAlpha"), 
        GL_TEXTURE0, GL_PRIMARY_COLOR, GL_PRIMARY_COLOR);
glUniform1i(glGetUniformLocation(progID, "dmp_FragOperation.mode"), 
        GL_FRAGOP_MODE_GL_DMP);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shadowTexID); 

Both 3DS and OpenGL reference shadow information from textures. The texture unit compares texture coordinates and shadow information obtained from texels. For this to work properly, the correct texture coordinates must be specified. Note that a shadow texture is referenced using texture coordinates (s/q, t/q, r/q) in OpenGL, and (s/r, t/r, r - bias) on 3DS. The texture transformation matrix has already been applied to texture coordinates (s, t, r, q), and the reserved uniform dmp_Texture[0].shadowZBias specifies the bias value. If shadow information is accumulated by an orthographic projection, texture coordinates s and t are referenced directly. A perspective projection, however, requires adjustments to the texture transformation matrix and bias value.

The following texture transformation matrix and bias value could be used to compare the texture coordinates and shadow information accumulated by a perspective projection.

Texture Transformation Matrix and Bias for Perspective Projection

texture_matrix bias f n r t

Texture Transformation Matrix and Bias for Parallel Projection

texture_matrix bias f n r t

n is the clip value at the near clipping plane, f is the clip value at the far clipping plane, and r and t are the right and top side values of the frustum.

When calculating the value that is compared to the shadow buffer depth (that is, rbias ), if the texture coordinate r is not within the range from 0.0 to 1.0, r is clamped to between 0.0 and 1.0 before bias is subtracted. To compare values correctly, specify a bias of 0 for any objects placed beyond the far clipping plane in the coordinate system of the light source used during the shadow accumulation pass.

You can create a texture transformation matrix with the following OpenGL code.

Code 12-9. Creating a Texture Transformation Matrix With OpenGL Code
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
// (glFrustum(-r, r, -t, t, n, f))
glFrustumf(r/n, -3r/n, t/n, -3t/n, 1.0f, 0.0f);
glScalef(-1.0f/(f-n), -1.0f/(f-n), -1.0f/(f-n));
// (glOrtho(-r, r, -t, t, n, f))
glOrthof(-3r, r, -3t, t, 2n-f, f); 

By setting the border color and texture wrapping mode, you can control the sampling results for texture coordinates that are less than 0.0 or greater than 1.0. The texture wrapping mode guarantees that when GL_CLAMP_TO_BORDER is configured for the s and t texture coordinates, out-of-range sampling values are set to the border color (which must have a value of 0.0 or 1.0 for all components). Sampling results are undefined in the current implementation if the wrapping mode is not GL_CLAMP_TO_BORDER or if the border color is neither (0.0, 0.0, 0.0, 0.0) nor (1.0, 1.0, 1.0, 1.0).

Code 12-10. Sample Settings for the Wrapping Mode and Border Color
glBindTexture(GL_TEXTURE_2D, shadowTexID);
GLfloat bcolor[] = {1.0f, 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bcolor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); 

If the depth values in light source coordinates are greater than the depth values in shadow texels, the shadow intensity will be 0.0 after the comparison. Otherwise, the shadow intensity in the shadow texels is used. Texture units output the shadow intensity as a value for each RGBA component.

12.4.3. Omnidirectional Shadow Mapping

You can combine cube mapping with shadow textures to implement omnidirectional shadow mapping.

This implementation requires that you bind a cube map texture (one of six types: GL_TEXTURE_CUBE_MAP_POSITIVE_{X,Y,Z} or GL_TEXTURE_CUBE_MAP_NEGATIVE_{X,Y,Z}) to the texture unit during the shadow accumulation pass, render six times, and then specify GL_TEXTURE_SHADOW_CUBE_DMP as the texture to reference during the shadow lookup pass (dmp_Texture[0].samplerType). Also, you must specify GL_CLAMP_TO_EDGE as the texture coordinate wrapping mode to use in both the S and T directions.

12.4.4. Soft Shadows Using the Silhouette Shader

Shadow textures contain depth values and shadow intensity. The sampling value output by a texture unit that references a shadow texture is either the color black (0.0, 0.0, 0.0), within a shadow region, or the shadow intensity saved in the shadow texture (between 0.0 and 1.0), outside of a shadow region. You can represent soft shadows by setting this shadow intensity to the appropriate values.

The 3DS silhouette shader can render silhouette primitives (edges) as soft shadow regions, whose shadow intensity changes gradually until they disappear. Opaque hard shadows are rendered before the silhouette shader is used for rendering. Set the G component of the color at the silhouette edge to 1.0, and set the G component of the vertex color output by the vertex shader to 0.0. All other settings are the same as those used to render opaque hard shadows. This renders the rectangle for a silhouette edge so that its G component gradually changes from 0.0 on the object side to 1.0 on the outer side.

During the shadow accumulation pass for these soft shadow regions, you can apply additive modulation to the shadow intensity, according to the relative distance to an object. This is called an attenuation factor, and it can adjust the width of the soft shadow region through the reserved uniforms for the soft shadow bias (dmp_FragOperation.penumbraBias) and scale (dmp_FragOperation.penumbraScale). By adjusting these values, you can represent more natural soft shadows that narrow close to the object.

The attenuation factor is calculated by the following equation. The equation uses Zfrag to represent a fragment's depth value and Zrec to represent the depth value stored in a shadow texture. The shadow intensity is not attenuated properly if objects have not already been rendered and depth values have not already been saved in a shadow texture.

12.4.5. Handling Shadow Artifacts

12.4.5.1. Self-Shadow Aliasing

Various problems can occur during multiple-pass shadow rendering. For example, by accidentally casting a shadow on itself (called self-shadow aliasing) a fragment can cause a moiré pattern to be rendered. This occurs when depth values from the shadow accumulation pass are slightly smaller than depth values from the shadow lookup pass (measured from the light source). Unnatural shadows such as these are called shadow artifacts.

One way to suppress shadow artifacts is to apply a negative offset (bias) to depth values during the shadow lookup pass. You can set the bias value in the reserved uniform dmp_Texture[0].shadowZBias.

Code 12-11. Sample Settings to Suppress Self-Shadow Aliasing
glUniform1f(glGetUniformLocation(progID, "dmp_Texture[0].shadowZBias"), 
            1.2f*n/(f-n)); 

12.4.5.2. Silhouette Shadow Artifacts

As described previously, the shadow intensity in a shadow texture is output as a sampling value for any location that is determined to be outside of a shadow region during the shadow lookup pass. Although non-shadow regions usually have a shadow intensity of 1.0 and no brightness attenuation, the soft shadow regions rendered by the silhouette shader can have an output shadow intensity that is not 1.0, and thereby have brightness attenuation. This causes shadow artifacts to occur for some objects.

The method used to suppress self-shadow aliasing does not work for silhouette shadow artifacts, but these artifacts can still be suppressed through adjustments to shadow settings and texture combiner settings.

Because the main cause of these artifacts is the brightness attenuation of a light source by soft shadows on a parallel plane, the shadow texture output (shadow attenuation) can be calculated by using the following equation.

ShadowAttenuation = 1.0 – f (1.0 – ShadowIntensity)

Where f is a function that returns a value close to 0.0 when the fragment normals are perpendicular to the light source and 1.0 when the fragment normals are parallel to the light source.

This equation yields a shadow attenuation of 1.0 (shadows have no effect) when a fragment's normals are nearly perpendicular to the light source and yields the shadow intensity itself when the normals are nearly parallel to the light source.

This shadow attenuation term can be implemented by using the Fresnel factor (lookup table FR) as the f function, reserved uniforms for the lighting environment (dmp_LightEnv.shadowAlpha and dmp_LightEnv.invertShadow) as the inverse of the shadow intensity, and texture combiner settings as the inverse of the final value.

Lighting is configured as follows. Note that the Fresnel factor affects only the alpha component. The alpha component is multiplied by the texture combiners. The f function is configured to return the square of the input value.

Code 12-12. Sample (Lighting) Settings to Suppress Silhouette Shadow Artifacts
glUniform1i(glGetUniformLocation(progID, "dmp_FragmentLighting.enabled"), 
        GL_TRUE);
// ..other code..
glUniform1i(glGetUniformLocation(progID, "dmp_LightEnv.lutInputFR"), 
        GL_LIGHT_ENV_LN_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_LightEnv.config"), 
        GL_LIGHT_ENV_LAYER_CONFIG1_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_LightEnv.fresnelSelector"), 
        GL_LIGHT_ENV_PRI_SEC_ALPHA_FRESNEL_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_LightEnv.shadowAlpha"), 
        GL_TRUE);
glUniform1i(glGetUniformLocation(progID, "dmp_LightEnv.invertShadow"), 
        GL_TRUE);

GLfloat lut[512];
int j;
memset(lut, 0, sizeof(lut));
for (j = 1; j < 128; j++)
{
    lut[j] = powf((float)j/127.0f, 2.0f);
    lut[j+255] = lut[j] - lut[j-1];
}
glTexImage1D(GL_LUT_TEXTURE0_DMP, 0, GL_LUMINANCEF_DMP, 512, 0,
        GL_LUMINANCEF_DMP, GL_FLOAT, lut);
glUniform1i(glGetUniformLocation(progID, "dmp_FragmentMaterial.samplerFR"), 0); 

In this code, f (1.0ShadowIntensity) is output as the fragment's primary and secondary alpha component.

The final shadow attenuation factor is calculated next from the texture combiner settings and is multiplied by the fragment's primary color. Note that the fragment's primary alpha value is inverted here (GL_ONE_MINUS_SRC_ALPHA).

Code 12-13. Sample (Texture Combiner) Settings to Suppress Silhouette Shadow Artifacts
glUniform1i(glGetUniformLocation(progID, ("dmp_TexEnv[0].combineRgb"), 
        GL_MODULATE);
glUniform1i(glGetUniformLocation(progID, ("dmp_TexEnv[0].combineAlpha"), 
        GL_REPLACE);
glUniform3i(glGetUniformLocation(progID, ("dmp_TexEnv[0].operandRgb"), 
        GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR);
glUniform3i(glGetUniformLocation(progID, ("dmp_TexEnv[0].operandAlpha"), 
        GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA);
glUniform3i(glGetUniformLocation(progID, ("dmp_TexEnv[0].srcRgb"), 
        GL_FRAGMENT_PRIMARY_COLOR_DMP, GL_FRAGMENT_PRIMARY_COLOR_DMP, 
        GL_PRIMARY_COLOR);
glUniform3i(glGetUniformLocation(progID, ("dmp_TexEnv[0].srcAlpha"), 
        GL_PRIMARY_COLOR, GL_PRIMARY_COLOR, GL_PRIMARY_COLOR); 

12.4.6. Reserved Uniform

The following table lists the reserved uniforms used for shadows.

Table 12-12. Reserved Uniforms Used for Shadows

Reserved Uniform

Type

Setting Value

dmp_Texture[0].samplerType

int

Specifies the type of texture to reference. The following two types can be used for shadows.

GL_TEXTURE_SHADOW_2D_DMP
GL_TEXTURE_SHADOW_CUBE_DMP

dmp_Texture[0].perspectiveShadow

bool

Specifies whether a perspective projection has been applied as the projection transformation.

GL_TRUE: Has been applied (default).
GL_FALSE: Has not been applied.

dmp_Texture[0].shadowZBias

float

Specifies the bias value for the negative offset to apply to depth values during the lookup pass.

0.0 (default)

dmp_FragOperation.mode

int

Specifies the fragment operation mode. Specifies shadow mode during the accumulation pass and standard mode during the lookup pass.

GL_FRAGOP_MODE_GL_DMP (standard mode)
GL_FRAGOP_MODE_SHADOW_DMP (shadow mode)

dmp_FragOperation.wScale

float

Specifies the scale factor for the w-buffer.

0.0 (default)

dmp_FragOperation.penumbraScale

float

Specifies the scale value for soft shadows.

1.0 (default)

dmp_FragOperation.penumbraBias

float

Specifies the bias value for soft shadows.

0.0 (default)

12.4.7. Checking Shadow Texture Content

To check the image that was rendered to a shadow texture, attach the shadow texture to the current color buffer, and then read the texel data by calling the glReadPixels() function and specifying GL_RGBA for the format parameter and GL_UNSIGNED_BYTE for the type parameter. When accessed through a u32 pointer, the least-significant 8 bits of texel data store the shadow intensity, and the most-significant 24 bits store the depth value. Shift this to the right by 8 bits to get only the depth value.

Because shadow textures store an 8-bit shadow intensity and a 24-bit depth value, and both of these represent values in the range from 0.0 to 1.0, the shadow intensity and depth value have different precisions.

The shadow intensity takes a value between 0x00 and 0xFF, where 0xFF indicates the absence of a soft shadow region and all other values indicate the presence of a soft shadow region. In other words, a value of 0x00 indicates a shadow intensity of 0.0 and 0xFF indicates a shadow intensity of 1.0.

The depth value is scaled so that 0x000000 is the near value and 0xFFFFFF is the far value. In other words, a value of 0x000000 indicates a depth value of 0.0 and 0xFFFFFF indicates a depth value of 1.0. Note that this scaling is uniform if the w buffer is enabled during the shadow accumulation pass. For more information, see 10.3.3. W-Buffer.

A shadow intensity of 0xFF and a depth value of 0xFFFFFF are written to the shadow texture wherever there are no shadows. A shadow intensity of 0xFF and a depth value other than 0xFFFFFF are written to the shadow texture wherever there are only hard shadows. A shadow intensity other than 0xFF and a depth value other than 0xFFFFFF are used in regions that have both hard and soft shadows.

Table 12-13. Types of Shadows and the Values Written to Shadow Textures

Shadow Type

Shadow Intensity

Depth Values

No shadows

0xFF

0xFFFFFF

Only hard shadows

0xFF

Value other than 0xFFFFFF

Soft shadows

Value other than 0xFF

Value other than 0xFFFFFF

12.5. Fog

Although 3DS fog has nearly the same features as fog defined in OpenGL ES 1.1, the effect of 3DS fog is determined by projection-corrected depth values, whereas the effect of OpenGL fog is determined by the distance from the viewpoint. Another difference is that fog coefficients are specified by lookup tables. There are also no settings for fog properties, such as the beginning, end, and density.

As in OpenGL, the following equation determines a fragment's color after fog has been applied.

Color = f × Cfragment + (1 - f) × Cfog

f is the fog coefficient (between 0.0 and 1.0).

12.5.1. Reserved Uniform

The following reserved uniforms are used for fog.

Fog Mode

To enable fog, specify a fog mode (dmp_Fog.mode) of GL_FOG using the glUniform1i() function. To disable it, specify GL_FALSE.

Fog Color

Use the glUniform3f() function to set the fog color (dmp_Fog.color). Only the RGB components are set. (The alpha component is not.)

Fog Coefficients

Fog coefficients configure a lookup table that takes depth values in window coordinates as input. Call the glUniform1i() function to specify the reserved uniform (dmp_Fog.sampler) to the lookup table number to use. Note that the specified lookup table number GL_LUT_TEXTUREi_DMP, where i represents a number from 0 to 31, does not specify the name (ID) of the texture nor GL_LUT_TEXTUREi_DMP directly.

Whether to Invert Input Depth Value

You can choose whether to invert input values (changing z to 1 - z) for the fog coefficient lookup table. To invert values, set the reserved uniform dmp_Fog.zFlip to GL_TRUE, using the glUniform1i() function.

Table 12-14. Reserved Uniforms Used for Fog

Reserved Uniform

Type

Setting Value

dmp_Fog.mode

int

Specifies the mode for processing the fog pipeline (the fog mode).

GL_FOG
GL_GAS_DMPGL_FALSE (default)

dmp_Fog.color

vec3

Specifies the fog color. There is no alpha component.

Each component has a value between 0.0 and 1.0.

(0.0, 0.0, 0.0) by default.

dmp_Fog.zFlip

bool

Specifies whether to invert the depth values used as input to the lookup table for fog coefficients.

GL_TRUE or GL_FALSE (default).

dmp_Fog.sampler

int

Specifies the lookup table to use for fog coefficients.

0 through 31

12.5.2. Creating and Specifying Lookup Tables

A lookup table for fog coefficients takes input values between 0.0 and 1.0, and has a fixed width of 256. Like other lookup tables, it stores the output values in the first 128 elements and the differences between output values in the last 128 elements.

You must be careful about how you convert input values, when you create lookup tables to implement OpenGL fog coefficients on a 3DS system. The input depth values are specified in window coordinates, with the near clipping plane at the minimum value (0.0) and the far clipping plane at the maximum value (1.0). For a perspective projection, however, these depth values have a non-linear relationship with depth values in eye coordinates. As a result, lookup table output must be calculated using depth values that were converted from window coordinates into eye coordinates.

Considering that input in window coordinates (between 0.0 and 1.0) is mapped to clip coordinates (between 0.0 and –1.0), the following equation is used to convert values into eye coordinates. Note that the sign of the input is reversed.

(Xe Ye Ze We) = (0.0 0.0 -Zw 1.0) × Mprojection-1

Fog coefficients are a function of the distance to a fragment from the origin in eye coordinates. Input to this function can be approximated as the distance -Ze / We between the xy plane and the fragment in eye coordinates.

This is shown by the following sample code. FogCoef is the fog coefficient function.

Code 12-14. Creating a Fog Lookup Table
float Fog_LUT[256], Fog_c[128 + 1];
int i;
Matrix44 invPM;
Vector4 v_eye, v_clip(0.0f, 0.0f, 0.0f, 1.0f);

MTX44Inverse(&invPM, &projMatrix);
Vector4 v0(invPM.m[0]);
Vector4 v1(invPM.m[1]);
Vector4 v2(invPM.m[2]);
Vector4 v3(invPM.m[3]);
for (i = 0; i <= 128; i++) {
    v_clip.z = -(static_cast<f32>(i)) / 128;
    v_eye.x = VEC4Dot(&v0, &v_clip);    v_eye.y = VEC4Dot(&v1, &v_clip);
    v_eye.z = VEC4Dot(&v2, &v_clip);    v_eye.w = VEC4Dot(&v3, &v_clip);
    Fog_c[i] = -(v_eye.z / v_eye.w);
}
for (i = 0; i < 128; i++) {
    Fog_LUT[i] = FogCoef(Fog_c[i]);
    Fog_LUT[128 + i]= FogCoef(Fog_c[i + 1]) - FogCoef(Fog_c[i]);
} 
Note:

OpenGL fog is affected by the eye coordinates' z value, but 3DS fog is affected by depth values that have been corrected with a perspective projection. As a result, fog changes when the near and far clipping planes change, and the same lookup table can produce different effects depending on whether the w-buffer or a normal depth buffer is used. For more information, see 10.3.3. W-Buffer.

12.6. Gas Rendering

Gas rendering uses the fog feature, configured in gas mode, to render gaseous bodies from density information, which is itself generated based on depth values of polygon objects. Gaseous bodies require three rendering passes: the polygon object rendering pass, which generates depth values for the polygon objects; the density rendering pass, which accumulates density information in a gas texture; and the shading pass, which renders gaseous bodies by referencing a gas texture.

12.6.1. Polygon Object Rendering Pass

This pass renders polygon objects as usual, generating depth information that is used to determine where the polygon objects and gaseous bodies intersect. The content of the depth buffer is the only rendering result that is used by the next pass.

12.6.2. Density Rendering Pass

This pass uses a means such as point sprites to render the smallest units comprising a gaseous body and gas particles (a texture with a density pattern), and it accumulates a gas's depth information in the color buffer. The content of the color buffer is copied to the gas texture and is used for the next path.

Preparing Color Buffers and Gas Textures

Prepare a color buffer with an internal format of GL_GAS_DMP and a texture (gas texture), both of which are used to copy the rendering results. Create the color buffer with the same size as the depth buffer, but the gas texture must have a width and height (in texels) that are both powers of 2, and GL_UNSIGNED_SHORT must be specified for type. Because gas textures always use point sampling, minification and magnification filters have no effect. Use GL_NEAREST as the filter setting. Note that mipmaps cannot be applied to gas textures.

Code 12-15. Preparing a Color Buffer and Gas Texture for Gas Rendering
// Generating Object
glGenFramebuffers(1, &gasAccFboID);
glGenTextures(1, &gasTexID);
// Renderbuffer & Framebuffer
glGenRenderbuffers(1, &gasAccColorID);
glBindRenderbuffer(GL_RENDERBUFFER, gasAccColorID);
glRenderbufferStorage(GL_RENDERBUFFER, GL_GAS_DMP, GAS_ACC_WIDTH,
        GAS_ACC_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, gasAccFboID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
        gasAccColorID);
// Gaseous Texture
glBindTexture(GL_TEXTURE_2D, gasTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_GAS_DMP, GAS_TEX_WIDTH, GAS_TEX_HEIGHT, 0,
        GL_GAS_DMP, GL_UNSIGNED_SHORT, 0);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); 
Rendering Gas Particles

To render density information to the color buffer, you must switch the fragment operation mode (dmp_FragOperation.mode) to gas mode (GL_FRAGOP_MODE_GAS_ACC_DMP). Two types of density information are accumulated in the color buffer: information that is simply accumulated (D1), and information that accounts for intersections with polygon offsets (D2).

Disable the depth test, the depth mask, blending, and fog to prevent the content of the depth buffer from being updated. However, do not change the comparison function that was used for the depth test, when polygon objects were rendered.

Clear only one buffer, the color buffer to which density information is rendered. The depth buffer must not be cleared.

The R component of the gas particles to be rendered (Df) is accumulated in the color buffer as density information. D1 and D2 are updated to D1' and D2' by the following equations. The equation for D2' depends on the comparison function for the depth test.

Zb is the depth value stored in the depth buffer, and Zf is the depth value of the fragment.

D1 accumulates fragment (gas particle) density information unchanged. D2 accumulates fragment density information that has been multiplied by the attenuation coefficient EZ in the depth direction and also by the difference between the depth values in the depth buffer and fragment. The EZ coefficient is a floating-point number set by the glUniform1f() function in the reserved uniform dmp_Gas.deltaZ.

Code 12-16. Rendering Gas Particles
// Change to gas accumulation mode.
glBindFramebuffer(GL_FRAMEBUFFER, gasAccFboID);
glUniform1i(glGetUniformLocation(progID, "dmp_FragOperation.mode"),
        GL_FRAGOP_MODE_GAS_ACC_DMP);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glUniform1i(glGetUniformLocation(progID, "dmp_Fog.mode"), GL_FALSE);
// Colorbuffer Clear
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Set dmp_Gas.deltaZ.
glDepthFunc(GL_LESS);
glUniform1f(glGetUniformLocation(progID, "dmp_Gas.deltaZ"), 50.0f); 
Copying Information to a Gas Texture

After all gas particles have been rendered, the density information accumulated in the color buffer is copied to a gas texture.

The gas texture is not usually allocated with the same size as the color buffer (the width and height must be powers of 2), so the glCopyTexSubImage2D() function partially copies color buffer data. The gas texture is required for the next pass.

Code 12-17.Coping Information to a Gas Texture
// Bind and copy to gaseous texture.
glBindTexture(GL_TEXTURE_2D, gasTexID);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GAS_ACC_WIDTH,
                    GAS_ACC_HEIGHT); 

12.6.3. Shading Pass

This pass shades gaseous bodies by referencing the density information accumulated in a gas texture, and then blends the shading results with the color buffer contents from the polygon object rendering pass.

Gaseous bodies are shaded using the fog feature configured in gas mode. Special settings are required for fog input from the texture combiners.

Fog Input (Texture Combiner Settings)

Fog takes two inputs: the RGBA components from the second-to-last texture combiner (though only the R component is actually used), and the density information from the input source, specified as input source 2 (the third component of srcRgb and srcAlpha) to the last texture combiner (the specified texture unit must sample the gas texture). Because output from the last texture combiner is ignored, the number of usable texture combiner levels is reduced by one.

Ultimately, fog output is blended with the contents of the color buffer. Because fog outputs alpha values that account for intersections with polygon objects, blending both outputs based on the fog's alpha values allows gaseous bodies to be rendered in the correct order front-to-back.

Figure 12-5.Fog Input and Blending With the Color Buffer

GaseousTexture TextureCombiner[last-1] TextureCombiner[last] Fog Blending ColorBuffer RGBA D1,D2 RGBA

Fog Operations in Gas Mode

In gas mode, fog shades gaseous bodies based on input information. To enable fog in the gas mode, specify a fog mode (dmp_Fog.mode) for GL_GAS_DMP.

The RGB components of the shading results are determined by shading lookup tables. To determine the alpha component, the gas attenuation value (dmp_Gas.attenuation) is multiplied by density information that accounts for intersections (D2), and the result is looked up in the lookup table specified by the fog coefficient (dmp_Fog.sampler).

Figure 12-6. Fog in Gas Mode

 Shading Sampler D2 × Gas Attenuation Fog Sampler Green Alpha (input) × Inverted Max Accumulation D1 D2 Shading Intensity Red lightXY, lightZ samplerTR,samplerTG,samplerTB autoAcc, accMax colorLut Input shading DensitySrc attenuation sampler Red Blue

Shading Lookup Tables

Shading lookup tables accept either density information or shading intensity as input, and provide the RGB components of the shading results as output.

Shading lookup tables are specified separately for each component using tables generated with width set to 16. Both the input and output values are between 0.0 and 1.0. As with other lookup tables, the output values are stored in the first eight elements, the differences between output values are stored in the last eight elements, and the output values are interpolated using the delta values.

The shading lookup table to use uses the glUniform1i() function to specify the lookup table number as the following reserved uniform. Note that the specified lookup table number GL_LUT_TEXTUREi_DMP, where i represents a number from 0 to 31, does not specify the name (ID) of the texture nor GL_LUT_TEXTUREi_DMP directly.

Table 12-15. Reserved Uniforms Used to Specify Shading Lookup Tables

Reserved Uniform

Type

Setting Value

dmp_Gas.samplerTR
dmp_Gas.samplerTG
dmp_Gas.samplerTB

int

Specifies the lookup table number to use as the shading lookup tables (for the R, G, and B components).

0 through 31

The following are examples of shading lookup tables and their implementations.

Figure 12-7. A Shading Lookup Table

Code 12-18. Creating Shading Lookup Tables
// Define
GLfloat shading_color[3 * 9] = {
    0.00f, 0.00f, 0.00f,
    0.20f, 0.15f, 0.05f,
    0.60f, 0.25f, 0.15f,
    0.90f, 0.35f, 0.20f,
    0.92f, 0.60f, 0.15f,
    0.95f, 0.85f, 0.05f,
    1.00f, 0.95f, 0.00f,
    1.00f, 1.00f, 1.00f,
    1.00f, 1.00f, 1.00f
};
GLfloat samplerTR[16], samplerTG[16], samplerTB[16];
// Table
for(int i = 0; i < 8; i++) {
    // shading color value
    samplerTR[i] = shading_color[3*i + 0];
    samplerTG[i] = shading_color[3*i + 1];
    samplerTB[i] = shading_color[3*i + 2];
    // difference of shading color value
    samplerTR[8 + i] = shading_color[3*(i + 1) + 0] - shading_color[3*i + 0];
    samplerTG[8 + i] = shading_color[3*(i + 1) + 1] - shading_color[3*i + 1];
    samplerTB[8 + i] = shading_color[3*(i + 1) + 2] - shading_color[3*i + 2];
}
// Texture
glBindTexture(GL_LUT_TEXTURE1_DMP, samplerTR_ID);
glTexImage1D(GL_LUT_TEXTURE1_DMP, 0, GL_LUMINANCEF_DMP, 16, 0,
        GL_LUMINANCEF_DMP, GL_FLOAT, samplerTR);
glBindTexture(GL_LUT_TEXTURE2_DMP, samplerTG_ID);
glTexImage1D(GL_LUT_TEXTURE2_DMP, 0, GL_LUMINANCEF_DMP, 16, 0,
        GL_LUMINANCEF_DMP, GL_FLOAT, samplerTG);
glBindTexture(GL_LUT_TEXTURE3_DMP, samplerTB_ID);
glTexImage1D(GL_LUT_TEXTURE3_DMP, 0, GL_LUMINANCEF_DMP, 16, 0,
        GL_LUMINANCEF_DMP, GL_FLOAT, samplerTB);
// Set Uniform
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTR"), 1);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTG"), 2);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTB"), 3); 
Shading Based on Density Information

For shading that is based on density information alone (without accounting for the effect of shadows cast by light), set the reserved uniform dmp_Gas.colorLutInput to GL_GAS_DENSITY_DMP, using the glUniform1i() function to select density information, as the shading lookup table input.

As described for the density rendering pass, two types of density information are stored in a gas texture. One type of density information does not account for intersections with polygon objects (D1), and the other type does (D2). You can set a value in the reserved uniform dmp_Gas.shadingDensitySrc to select the density information to use.

Specify GL_GAS_PLAIN_DENSITY_DMP for D1 and GL_GAS_DEPTH_DENSITY_DMP for D2, using the glUniform1i() function. Even if you choose D1 as input to the shading lookup tables, fog in gas mode outputs alpha components calculated from density information that accounts for intersections (D2). By using alpha values while blending, polygon objects and gaseous bodies can be rendered with the correct front-to-back ordering.

Lookup table input uses values between 0.0 and 1.0. You can multiply the density information by the reciprocal of the maximum density value to keep values in this range. The reciprocal of the maximum value can be calculated automatically. By setting the reserved uniform dmp_Gas.autoAcc to GL_TRUE, you can use the glUniform1i() function to calculate the reciprocal from the maximum D1 value in the density rendering pass.

When GL_FALSE is specified, the value set in the reserved uniform dmp_Gas.accMax by the glUniform1f() function is used as the reciprocal of the maximum value.

Shading Based on the Shading Intensity

To use shading that is calculated from the shading intensity (accounting for the effect of shadows cast by light), set the reserved uniform dmp_Gas.colorLutInput to GL_GAS_LIGHT_FACTOR_DMP, using the glUniform1i() function. This selects the shading intensity as input to the shading lookup table.

The shading intensity is the total of two calculated values: the planar shading intensity (IG) and the view shading intensity (IS). IG and IS are defined as follows.

ig = r × (1.0 - lightAtt × d1)
IG = (1.0 - ig) × lightMin + ig × lightMax
is = LZ × (1.0 - scattAtt × d1)
IS = (1.0 - is) × scattMin + is × scattMax

d1 is the result of multiplying the density information that does not account for intersections with polygon objects (D1), by the reciprocal of the maximum density. This mechanism is similar to the one used for shading based on density information. r is the R component output from the texture combiner that is used as fog input. lightAtt, lightMin, and lightMax are coefficients of the planar shading intensity. LZ is the light direction along the z-axis in eye coordinates. scattAtt, scattMin, and scattMax are coefficients of the view shading intensity. These all take values between 0.0 and 1.0.

The planar shading intensity is proportional to r and (1.0lightAtt × d1), as its equation shows. It approaches lightMax when r increases and lightMin when d1 increases. Likewise, the view shading intensity is proportional to LZ and (1.0scattAtt × d1). It approaches scattMax when LZ increases and scattMin when d1 increases.

Note that the shading intensity input to a shading lookup table is larger for gaseous bodies that are less dense. This emulates the real behavior of light, which penetrates through the thin (low-density) areas of a gaseous body, and is absorbed in the thick (high-density) areas. Accordingly, lightMin and scattMin represent the shading intensity when the effect of light is small, and lightMax and scattMax represent the shading intensity when the effect of light is large. lightAtt and scattAtt set the ratio of light attenuation caused by density. Note that, depending on the values set in the shading lookup tables, lightMin is not necessarily less than lightMax (and scattMin is not necessarily less than scattMax). Also note that alpha values are determined by fog coefficients whose input is proportional to density.

Coefficients for the planar shading intensity (lightMin, lightMax, and lightAtt) are set, as a group, in the reserved uniform dmp_Gas.lightXY. Coefficients for the view shading intensity (scattMin, scattMax, and scattAtt), and the light direction along the z-axis in eye coordinates (LZ), are set as a group in the reserved uniform dmp_Gas.lightZ. The minimum value, maximum value, and attenuation are set in that order. They are followed by LZ for the view shading intensity.

The following code shows how to set the shading intensity coefficients.

Code 12-19. Setting Shading Intensity Coefficients
GLfloat lightXY[3], lightZ[4];
GLfloat lightMin, lightMax, lightAtt;
GLfloat scattMin, scattMax, scattAtt, LZ;

//...

// lightXY
lightXY[0] = lightMin;
lightXY[1] = lightMax;
lightXY[2] = lightAtt;
// lightZ
lightZ[0] = scattMin;
lightZ[1] = scattMax;
lightZ[2] = scattAtt;
lightZ[3] = LZ;
// Set Uniform
glUniform3fv(glGetUniformLocation(progID, "dmp_Gas.lightXY"), 1, lightXY);
glUniform4fv(glGetUniformLocation(progID, "dmp_Gas.lightZ"), 1, lightZ); 
Alpha Shading

The results of shading the alpha component are determined by output from the lookup table for fog coefficients (dmp_Fog.sampler), given the product of the gas attenuation (dmp_Gas.attenuation) and the density information that accounts for intersections (D2) as input.

Fog coefficients are normally specified using a function that approaches 0.0 when the gas density is low, and 1.0 when the gas density is high.

Code 12-20. Setting Fog Coefficients
// Fog factor
for(int i = 0; i < 128; i++) {
    fogTable[i]= 1.0f - exp(-8.0f * i / 128.0f);
}
for(int i = 0; i < 128; i++) {
    fogTable[128 + i] = fogTable[i + 1] - fogTable[i];
}
fogTable[255] = 0;
// Set LUT
glGenTextures(1, &fogLUT_ID);
glBindTexture(GL_LUT_TEXTURE0_DMP, fogLUT_ID);
glTexImage1D(GL_LUT_TEXTURE0_DMP, 0, GL_LUMINANCEF_DMP, 256, 0,
        GL_LUMINANCEF_DMP, GL_FLOAT, fogTable); 

The following sample code sets uniforms required by the shading pass and renders a quad (polygon), to which a gas texture has been applied.

Code 12-21. Setting Uniforms for the Shading Pass
// Bind Framebuffer(Colorbuffer)
glBindFramebuffer(GL_FRAMEBUFFER, renderFboID);
// Bind Gas Texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gasTexID);
glUniform1i(glGetUniformLocation(progID, "dmp_Texture[0].samplerType"),
        GL_TEXTURE_2D);
// Set TextureCombiner #5
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[5].srcRgb"),
        GL_PREVIOUS, GL_PREVIOUS, GL_TEXTURE0);
glUniform3i(glGetUniformLocation(progID, "dmp_TexEnv[5].srcAlpha"),
        GL_PREVIOUS, GL_PREVIOUS, GL_TEXTURE0);
// Set uniform for gas shading mode.
glUniform1i(glGetUniformLocation(progID, "dmp_Fog.sampler"), 0);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.autoAcc"), GL_FALSE);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTR"), 1);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTG"), 2);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.samplerTB"), 3);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.shadingDensitySrc"),
        GL_GAS_DEPTH_DENSITY_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_Gas.colorLutInput"),
        GL_GAS_DENSITY_DMP);
glUniform1f(glGetUniformLocation(progID, "dmp_Gas.accMax"), 1.0f/6.0f);
glUniform4fv(glGetUniformLocation(progID, "dmp_Gas.lightZ"), 1, gasLightZ);
glUniform3fv(glGetUniformLocation(progID, "dmp_Gas.lightXY"), 1, gasLightXY);
// Change to gas shading mode.
glUniform1i(glGetUniformLocation(progID, "dmp_FragOperation.mode"),
        GL_FRAGOP_MODE_GL_DMP);
glUniform1i(glGetUniformLocation(progID, "dmp_Fog.mode"), GL_GAS_DMP);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE); 
Code 12-22. Rendering a Quad (Polygon) With a Gas Texture Applied
// Gaseous Shading
// Texture Coord
float u0 = 0.0f;
float v0 = 0.0f;
float u1 = (GAS_ACC_WIDTH * 1.0f) / (GAS_TEX_WIDTH * 1.0f);
float v1 = (GAS_ACC_HEIGHT * 1.0f) / (GAS_TEX_HEIGHT * 1.0f);
GLfloat texCoord[8]= {u0, v0, u0, v1, u1, v1, u1, v0};
// Vertex
GLushort quadIndex[6] = {0, 1, 2, 0, 2, 3};
GLfloat vertex[16] = {
    -1.0f, -1.0f, 0.0f, 1.0f,
    -1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, -1.0f, 0.0f, 1.0f
};
// Set Array
GLfloat quadIndexID, quadVertexID, quadTexCoordID;
glGenBuffers(1, &quadIndexID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIndexID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(GLushort), &quadIndex,
        GL_STATIC_DRAW);
glGenBuffers(1, &quadVertexID);
glBindBuffer(GL_ARRAY_BUFFER, quadVertexID);
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), &vertex, GL_STATIC_DRAW);
glGenBuffers(1, &quadTexCoordID);
glBindBuffer(GL_ARRAY_BUFFER, quadTexCoordID);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), &texCoord, GL_STATIC_DRAW);
// Draw Quad
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, quadVertexID);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, quadTexCoordID);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIndexID);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); 

12.6.4. Reserved Uniform

The following table lists the reserved uniforms used for gas.

Table 12-16. Reserved Uniforms Used for Gas

Reserved Uniform

Type

Setting Value

dmp_FragOperation.mode

int

Specifies the fragment operation mode. Specify gas mode in the density rendering pass. Specify standard mode in any other pass.

GL_FRAGOP_MODE_GL_DMP (standard mode)
GL_FRAGOP_MODE_SHADOW_DMP (shadow mode)

dmp_Fog.mode

int

Specifies the mode for processing the fog pipeline. Specify gas mode in the shading pass. Disable fog in the density rendering pass.

Specify GL_GAS_DMP for gas mode in the shading pass, and GL_FALSE to disable fog in the density rendering pass.

dmp_Fog.sampler

int

Specifies the lookup table to use for fog coefficients. Fog coefficients are used to calculate alpha values for gaseous bodies.

0 through 31

dmp_Gas.deltaZ

float

Specifies the attenuation coefficient EZ in the depth direction.

10.0 (default)

dmp_Gas.autoAcc

bool

Specifies whether to automatically calculate the reciprocal of the maximum density value.

GL_TRUE: Automatically calculate (default).
GL_FALSE: Do not automatically calculate.

dmp_Gas.accMax

float

Specifies the reciprocal of the maximum density value.

0.0 or more

1.0 (default)

dmp_Gas.samplerTR
dmp_Gas.samplerTG
dmp_Gas.samplerTB

int

Specifies the shading lookup table for each RGB component.

0 through 31

dmp_Gas.shadingDensitySrc

int

Specifies the density information to use for shading.

Specify GL_GAS_PLAIN_DENSITY_DMP (default) or GL_GAS_DEPTH_DENSITY_DMP.

dmp_Gas.colorLutInput

int

Specifies whether the density or shading intensity is given as input to the shading lookup tables.

Specify GL_GAS_LIGHT_FACTOR_DMP (default) or GL_GAS_DENSITY_DMP.

dmp_Gas.lightXY

vec3

Specifies factors used to control planar shading: the minimum intensity, maximum intensity, and density attenuation.

(lightMin, lightMax, lightAtt)

Each control value is between 0.0 and 1.0.

The default value is (0.0, 0.0, 0.0).

dmp_Gas.lightZ

vec4

Specifies factors used to control view shading: the minimum intensity, maximum intensity, density attenuation, and effect in the view direction.

(scattMin, scattMax, scattAtt, LZ)

Each control value is between 0.0 and 1.0.

The default value is (0.0, 0.0, 0.0, 0.0).

dmp_Gas.attenuation

float

Specifies the density attenuation coefficient to use when calculating alpha values for shading.

0.0 or more

1.0 (default)


CONFIDENTIAL