Chapter 7. Additional Lighting Models

This chapter expands on the previous lighting models by introducing point lights, spotlights, and multiple lights for your scenes. Along the way, you’ll refine your experience with HLSL and the effect framework.

Point Lights

A point light in a scene is like a light bulb—it has a position that is local to your surroundings, and it radiates light in all directions. This is in contrast to a directional light, which is infinitely far away and whose light appears to come from a single direction. Directional lights have no concept of “moving the light;” point lights have no concept of “rotating the light.”

You can use the same diffuse and specular lighting models with point lights, just as with directional lights. But with point lights, you supply the light’s position and then must compute the light vector. This is a simple matter of subtracting the position of the object from the position of the light (where both positions are in world space). Figure 7.1 illustrates a point light and the computed light vector.

Image

Figure 7.1 An illustration of a point light and the computed light vector. (Stone wall texture by Nick Zuccarello, Florida Interactive Entertainment Academy.)

Additionally, because point lights have a specific location, you can attenuate the light with respect to the distance from the surface. The farther the light is from the surface, the less brightness it imparts. Listing 7.1 presents an effect for a single point light.

Listing 7.1 PointLight.fx


#include "include\Common.fxh"

/************* Resources *************/

cbuffer CBufferPerFrame
{
    float4 AmbientColor : AMBIENT <
        string UIName =  "Ambient Light";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 0.0f};

    float4 LightColor : COLOR <
        string Object = "LightColor0";
        string UIName =  "Light Color";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 1.0f};

    float3 LightPosition : POSITION <
        string Object = "PointLight0";
        string UIName =  "Light Position";
        string Space = "World";
    > = {0.0f, 0.0f, 0.0f};

    float LightRadius <
        string UIName =  "Light Radius";
        string UIWidget = "slider";
        float UIMin = 0.0;
        float UIMax = 100.0;
        float UIStep = 1.0;
    > = {10.0f};

    float3 CameraPosition : CAMERAPOSITION < string UIWidget="None"; >;
}

cbuffer CBufferPerObject
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION < string
UIWidget="None"; >;
    float4x4 World : WORLD < string UIWidget="None"; >;

    float4 SpecularColor : SPECULAR <
        string UIName =  "Specular Color";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 1.0f};

    float SpecularPower : SPECULARPOWER <
        string UIName =  "Specular Power";
        string UIWidget = "slider";
        float UIMin = 1.0;
        float UIMax = 255.0;
        float UIStep = 1.0;
    > = {25.0f};
}

Texture2D ColorTexture <
    string ResourceName = "default_color.dds";
    string UIName =  "Color Texture";
    string ResourceType = "2D";
>;

SamplerState ColorSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

RasterizerState DisableCulling
{
    CullMode = NONE;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 ObjectPosition : POSITION;
    float2 TextureCoordinate : TEXCOORD;
    float3 Normal : NORMAL;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float3 Normal : NORMAL;
    float2 TextureCoordinate : TEXCOORD0;
    float4 LightDirection : TEXCOORD1;
    float3 ViewDirection : TEXCOORD2;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;

    OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
    OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.
TextureCoordinate);
    OUT.Normal = normalize(mul(float4(IN.Normal, 0), World).xyz);

    float3 worldPosition = mul(IN.ObjectPosition, World).xyz;
    float3 lightDirection = LightPosition - worldPosition;
    OUT.LightDirection.xyz = normalize(lightDirection);
    OUT.LightDirection.w = saturate(1.0f - (length(lightDirection) /
LightRadius)); // Attenuation

    OUT.ViewDirection = normalize(CameraPosition - worldPosition);

    return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 OUT = (float4)0;

    float3 normal = normalize(IN.Normal);
    float3 lightDirection = normalize(IN.LightDirection.xyz);
    float3 viewDirection = normalize(IN.ViewDirection);
    float n_dot_l = dot(normal, lightDirection);
    float3 halfVector = normalize(lightDirection + viewDirection);
    float n_dot_h = dot(normal, halfVector);

    float4 color = ColorTexture.Sample(ColorSampler,
IN.TextureCoordinate);
    float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

    float3 ambient = get_vector_color_contribution(AmbientColor, color.
rgb);
    float3 diffuse = get_vector_color_contribution(LightColor,
lightCoefficients.y * color.rgb) * IN.LightDirection.w;
    float3 specular = get_scalar_color_contribution(SpecularColor,
min(lightCoefficients.z, color.w)) * IN.LightDirection.w;

    OUT.rgb = ambient + diffuse + specular;
    OUT.a = 1.0f;

    return OUT;
}

/************* Techniques *************/

technique10 main10
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader()));

        SetRasterizerState(DisableCulling);
    }
}


Point Light Preamble

As before, this effect builds from previous work—specifically, the Blinn-Phong effect using the lit() intrinsic. The CBufferPerFrame block has been modified to remove the light direction and replace it with LightPosition. This constant represents the position of the light in world space. Furthermore, a LightRadius shader constant has been added to define the distance within which the light has influence. Outside this radius, the light has no impact.

The CBufferPerObject, ColorTexture, and ColorSampler objects have not changed. Nor has the VS_INPUT struct—it still accepts the vertex position (in object space), a set of texture coordinates, and the normal. But the VS_OUTPUT struct’s LightDirection vector is now a float4 instead of a float3, and it’s now required in this effect; compare this with the Blinn-Phong effect, which could have used the global LightDirection shader constant instead. The additional channel in the LightDirection shader input stores the attenuation factor of the light. Both the direction and the attenuation are computed in the vertex shader.


Note

Storing the attenuation factor in the .w component of the LightDirection vector is just an efficiency trick. Because that channel isn’t otherwise employed, you can use it instead of occupying another output register unnecessarily. The attenuation factor bears no relation to the xyz components of the LightDirection vector.


Point Light Vertex and Pixel Shader

The vertex shader stores the computed light direction in a temporary float3, which is then normalized and copied to the corresponding output struct. Next, the light attenuation factor is calculated with this equation:

Image

Because you stored the prenormalized vector in the lightDirection variable, you can use its length to find the distance between the surface and the light. Whenever the distance is greater than or equal to the light’s radius, the calculated attenuation will be less than or equal to zero. The saturate() call keeps the final value out of negative territory.

The pixel shader is identical to that of Blinn-Phong, except that now the diffuse and specular terms are multiplied by the attenuation factor.

Point Light Output

Figure 7.2 shows the results of the point light applied to a sphere with the Earth texture using a half-intensity ambient light, a full-intensity specular highlight, and a full-intensity point light. The lights and the specular color are pure white.

Image

Figure 7.2 PointLight.fx applied to a sphere with a texture of Earth, using a half-intensity ambient light, a full-intensity specular highlight, and a full-intensity point light. All light colors are pure white. To the left, the point light is positioned farther away from the sphere; to the right, it is closer. (Texture from Reto Stöckli, NASA Earth Observatory.)

Point Light Modifications

In the right-side image of Figure 7.2, a glitch in the appearance of the specular highlight is caused by computing the light direction within the vertex shader. It’s apparent only when the point light is close to the object because that’s when the light vector varies (more significantly) between pixels. Figure 7.3 shows a comparison of the output when the light direction is computed in the vertex shader and then in the pixel shader.

Image

Figure 7.3 Comparison of the specular highlight produced with a per-vertex calculation (left) of the light direction and a per-pixel calculation (right). (Texture from Reto Stöckli, NASA Earth Observatory.)

Listing 7.2 presents an abbreviation of the effect for computing the light direction in the pixel shader. This listing displays only the code changes between the two iterations.

Listing 7.2 Per-Pixel Calculation of the View Direction


struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float3 Normal : NORMAL;
    float2 TextureCoordinate : TEXCOORD0;
    float3 WorldPosition : TEXCOORD1;
    float Attenuation : TEXCOORD2;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;

    OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
    OUT.WorldPosition = mul(IN.ObjectPosition, World).xyz;
    OUT.Normal = normalize(mul(float4(IN.Normal, 0), World).xyz);
    OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.
TextureCoordinate);

    float3 lightDirection = LightPosition - OUT.WorldPosition;
    OUT.Attenuation = saturate(1.0f - (length(lightDirection) /
LightRadius));

    return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 OUT = (float4)0;

    float3 lightDirection = normalize(LightPosition
- IN.WorldPosition);
    float3 viewDirection = normalize(CameraPosition
- IN.WorldPosition);

    float3 normal = normalize(IN.Normal);
    float n_dot_l = dot(normal, lightDirection);
    float3 halfVector = normalize(lightDirection + viewDirection);
    float n_dot_h = dot(normal, halfVector);

    float4 color = ColorTexture.Sample(ColorSampler,
IN.TextureCoordinate);
    float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

    float3 ambient = get_vector_color_contribution(AmbientColor, color.
rgb);
    float3 diffuse = get_vector_color_contribution(LightColor,
lightCoefficients.y * color.rgb) * IN.Attenuation;
    float3 specular = get_scalar_color_contribution(SpecularColor,
min(lightCoefficients.z, color.w)) * IN.Attenuation;

    OUT.rgb = ambient + diffuse + specular;
    OUT.a = 1.0f;

    return OUT;
}


The computation for the light direction is in world space. Therefore, you must pass the world-space position of the surface through the VS_OUTPUT struct. And because the light vector is no longer computed in the vertex shader, you can remove it from the VS_OUTPUT struct. However, you don’t need to move the attenuation calculation to the pixel shader (which would be much more expensive). At the close distance your point light will be, to have a visible impact on the light direction, any attenuation will be negligible. So you add/keep the Attenuation float in the vertex shader output struct. This does require that you compute the light direction twice: once for the attenuation calculation and again for the more accurate light direction in the pixel shader. But this cost is minimal compared to the expense of calculating the attenuation factor in the pixel shader.

Spotlights

A spotlight is a combination of a directional light and a point light. It has a position in world space, but it shines light in a specific direction. Furthermore, a spotlight attenuates with distance, as a point light does, but its light also attenuates around its source direction. You can think of a spotlight as a virtual flashlight, complete with a focus beam that falls off as the light gets father from the center.

You can model a spotlight with a position, a direction, a radius of influence, a color and intensity, and floating-point values for inner and outer angles that describe how light radiates around the focus beam. Figure 7.4 illustrates these elements.

Image

Figure 7.4 An illustration of a spotlight.

Listing 7.3 presents the code for an effect with a single spotlight. As before, I recommend that you transcribe this code into NVIDIA FX Composer. We discuss the specifics in the following sections.

Listing 7.3 Spotlight.fx


#include "include\Common.fxh"

/************* Resources *************/

cbuffer CBufferPerFrame
{
    float4 AmbientColor : AMBIENT <
        string UIName =  "Ambient Light";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 0.0f};

    float4 LightColor : COLOR <
        string Object = "LightColor0";
        string UIName =  "Spotlight Color";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 1.0f};

    float3 LightPosition : POSITION <
        string Object = "SpotLightPosition0";
        string UIName =  "Spotlight Position";
        string Space = "World";
    > = {0.0f, 0.0f, 0.0f};

    float3 LightLookAt : DIRECTION <
        string Object = "SpotLightDirection0";
        string UIName =  "Spotlight Direction";
        string Space = "World";
    > = {0.0f, 0.0f, -1.0f};

    float LightRadius <
        string UIName =  "Spotlight Radius";
        string UIWidget = "slider";
        float UIMin = 0.0;
        float UIMax = 100.0;
        float UIStep = 1.0;
    > = {10.0f};

    float SpotLightInnerAngle <
        string UIName =  "Spotlight Inner Angle";
        string UIWidget = "slider";
        float UIMin = 0.5;
        float UIMax = 1.0;
        float UIStep = 0.01;
    > = {0.75f};

    float SpotLightOuterAngle <
        string UIName =  "Spotlight Outer Angle";
        string UIWidget = "slider";
        float UIMin = 0.0;
        float UIMax = 0.5;
        float UIStep = 0.01;
    > = {0.25f};

    float3 CameraPosition : CAMERAPOSITION < string UIWidget="None"; >;
}

cbuffer CBufferPerObject
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION < string
UIWidget="None"; >;
    float4x4 World : WORLD < string UIWidget="None"; >;

    float4 SpecularColor : SPECULAR <
        string UIName =  "Specular Color";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 1.0f};

    float SpecularPower : SPECULARPOWER <
        string UIName =  "Specular Power";
        string UIWidget = "slider";
        float UIMin = 1.0;
        float UIMax = 255.0;
        float UIStep = 1.0;
    > = {25.0f};
}

Texture2D ColorTexture <
    string ResourceName = "default_color.dds";
    string UIName =  "Color Texture";
    string ResourceType = "2D";
>;

SamplerState ColorSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

RasterizerState DisableCulling
{
    CullMode = NONE;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 ObjectPosition : POSITION;
    float2 TextureCoordinate : TEXCOORD;
    float3 Normal : NORMAL;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float3 Normal : NORMAL;
    float2 TextureCoordinate : TEXCOORD0;
    float3 WorldPosition : TEXCOORD1;
    float Attenuation : TEXCOORD2;
    float3 LightLookAt : TEXCOORD3;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;

    OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
    OUT.WorldPosition = mul(IN.ObjectPosition, World).xyz;
    OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.
TextureCoordinate);
    OUT.Normal = normalize(mul(float4(IN.Normal,0), World).xyz);

    float3 lightDirection = LightPosition - OUT.WorldPosition;
    OUT.Attenuation = saturate(1.0f - length(lightDirection) /
LightRadius);

    OUT.LightLookAt = -LightLookAt;

    return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 OUT = (float)0;

    float3 lightDirection = normalize(LightPosition
- IN.WorldPosition);
    float3 viewDirection = normalize(CameraPosition
- IN.WorldPosition);

    float3 normal = normalize(IN.Normal);
    float n_dot_l = dot(normal, lightDirection);
    float3 halfVector = normalize(lightDirection + viewDirection);
    float n_dot_h = dot(normal, halfVector);
    float3 lightLookAt = normalize(IN.LightLookAt);

    float4 color = ColorTexture.Sample(ColorSampler,
IN.TextureCoordinate);
    float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

    float3 ambient = get_vector_color_contribution(AmbientColor, color.
rgb);
    float3 diffuse = get_vector_color_contribution(LightColor,
lightCoefficients.y * color.rgb) * IN.Attenuation;
    float3 specular = get_scalar_color_contribution(SpecularColor,
min(lightCoefficients.z, color.w)) * IN.Attenuation;

    float spotFactor = 0.0f;
    float lightAngle = dot(lightLookAt, lightDirection);
    if (lightAngle > 0.0f)
    {
        spotFactor = smoothstep(SpotLightOuterAngle,
SpotLightInnerAngle, lightAngle);
    }

    OUT.rgb = ambient + (spotFactor * (diffuse + specular));
    OUT.a = 1.0f;

    return OUT;
}

/************* Techniques *************/

technique10 main10
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader()));

        SetRasterizerState(DisableCulling);
    }
}


Spotlight Preamble

The CBufferPerFrame block contains self-describing members for LightColor, LightPosition, and LightRadius. The member LightLookAt defines the direction of the focus beam. It’s named this way to avoid confusion with the computed lightDirection vector that specifies where the light is in relation to the surface.

CBufferPerFrame also contains members for SpotLightOuterAngle and SpotLightInnerAngle and (through annotations) limits these values to [0.0, 0.5] and [0.5, 1.0], respectively. As you’ll see in the pixel shader, these values represent minimum and maximum values for a spotlight coefficient that is dependent on the angle between the focus beam and the computed lightDirection.

The only change to your shader inputs is the new LightLookAt vector in the VS_OUTPUT struct. This is a pass-through variable that stores the inverted LightLookAt global shader constant. As with directional lights, this is necessary because NVIDIA FX Composer sends a light’s direction from the light and you need it from the surface. In your own CPU-side applications, you can send this data in properly and omit this shader input.

Spotlight Vertex and Pixel Shader

The spotlight vertex shader is identical to the second iteration of the point light effect, except for the inclusion of the inverted LightLookAt statement. Similarly, the pixel shader borrows heavily from the point light shader, except for the addition of the spotFactor calculation. Indeed, the diffuse and specular terms are computed as if the light were a point light. The spotlight’s look at vector and its angle to the lightDirection determines the final color of these terms.

You determine the lightAngle with a dot product of the lightLookAt and lightDirection vectors. If it is positive, the spotFactor falls within the values specified as the outer and inner “angles” of the spotlight. Using the HLSL smoothstep() intrinsic, the calculated lightAngle is interpolated between SpotLightOuterAngle and SpotLightInnerAngle. Limiting the values of these constants to [0.0, 0.5] and [0.5, 1.0] should now make sense. If you specify 0.0 as the outer angle and 1.0 as the inner angle, your spotFactor moves from 1.0 to 0.0 as the surface faces farther away from the focus beam.

In the composition of the final pixel color, the spotFactor modulates the diffuse and specular terms. The ambient term is unaffected by any additional light sources.

Spotlight Output

Figure 7.5 presents the results of the spotlight effect. In this figure, the effect has been applied to a plane with a checkerboard texture. The ambient and specular terms have been disabled (by dialing their intensities to zero), and the spotlight is pure white at full intensity. In the left image, the inner and outer spotlight angles are 0.0 and 1.0, respectively. In the right image, the inner and outer spotlight angles are both 0.5. Note the hard-edged boundary these settings create.

Image

Figure 7.5 Spotlight.fx applied to a plane with a checkerboard texture. To the left, the outer-to-inner range is [0.0, 1.0]; to the right, both values are set to 0.5.

Multiple Lights

So far, you have been working with single light sources (discounting ambient light). But there’s no reason you cannot combine directional, point, and spotlights in the same effect, nor multiple lights of the same type. Your limitations are a matter of performance and the instruction count available with a given shader model.

Listings 7.4 and 7.5 present an effect with support for multiple point lights. This code has quite a few new constructs, so we start by discussing the updated Common.fxh file, which contains new support structs and utility functions. Then we dive into the details of the effect.

Listing 7.4 Common.fxh with Support for Multiple Point Lights


#ifndef _COMMON_FXH
#define _COMMON_FXH

/************* Constants *************/

#define FLIP_TEXTURE_Y 1

/************* Data Structures *************/

struct POINT_LIGHT
{
    float3 Position;
    float LightRadius;
    float4 Color;
};

struct LIGHT_CONTRIBUTION_DATA
{
    float4 Color;
    float3 Normal;
    float3 ViewDirection;
    float4 LightColor;
    float4 LightDirection;
    float4 SpecularColor;
    float SpecularPower;
};

/************* Utility Functions *************/

float2 get_corrected_texture_coordinate(float2 textureCoordinate)
{
    #if FLIP_TEXTURE_Y
        return float2(textureCoordinate.x, 1.0 - textureCoordinate.y);
    #else
        return textureCoordinate;
    #endif
}

float3 get_vector_color_contribution(float4 light, float3 color)
{
    // Color (.rgb) * Intensity (.a)
    return light.rgb * light.a * color;
}

float3 get_scalar_color_contribution(float4 light, float color)
{
    // Color (.rgb) * Intensity (.a)
    return light.rgb * light.a * color;
}

float4 get_light_data(float3 lightPosition, float3 worldPosition, float
lightRadius)
{
    float4 lightData;
    float3 lightDirection = lightPosition - worldPosition;

    lightData.xyz = normalize(lightDirection);
    lightData.w = saturate(1.0f - length(lightDirection) /
lightRadius); // Attenuation

    return lightData;
}

float3 get_light_contribution(LIGHT_CONTRIBUTION_DATA IN)
{
    float3 lightDirection = IN.LightDirection.xyz;
    float n_dot_l = dot(IN.Normal, lightDirection);
    float3 halfVector = normalize(lightDirection + IN.ViewDirection);
    float n_dot_h = dot(IN.Normal, halfVector);

    float4 lightCoefficients = lit(n_dot_l, n_dot_h, IN.SpecularPower);
    float3 diffuse = get_vector_color_contribution(IN.LightColor,
lightCoefficients.y * IN.Color.rgb) * IN.LightDirection.w;
    float3 specular = get_scalar_color_contribution(IN.SpecularColor,
min(lightCoefficients.z, IN.Color.w)) * IN.LightDirection.w *
IN.LightColor.w;

    return (diffuse + specular);
}

#endif /* _COMMON_FXH */


Support Structures and Utility Functions

Each point light requires a position, a color, and a radius. So if you want to support four point lights, you need four of each of these. The POINT_LIGHT struct bundles these members together so that you can create an array of point lights in a single declaration, such as POINT_LIGHT PointLights[NUM_LIGHTS];. Notice that the POINT_LIGHT data members are not marked with semantics or annotations. You have little reason to mark the members of a struct with semantics when the intent is to use the struct within an array, and annotations cannot be used for struct members.

The second support struct is LIGHT_CONTRIBUTION_DATA. You populate this structure with data for a surface’s color and normal, along with the view direction, light color, light direction, specular color, and specular power—the elements you’ve used thus far to implement diffuse and specular terms for a single light. This data type is used for the parameter to get_light_contribution(), which computes the diffuse and specular terms through similar steps as before and returns their combined color value as a single float3. An additional utility function, get_light_data(), computes the light direction and attenuation for subsequent inclusion in the LIGHT_CONTRIBUTION_DATA struct. Listing 7.5 presents the code for the multiple point light effect, which uses these structures and utility functions.

Listing 7.5 MultiplePointLights.fx


#include "include\Common.fxh"

/************* Resources *************/

#define NUM_LIGHTS 4

cbuffer CBufferPerFrame
{
    POINT_LIGHT PointLights[NUM_LIGHTS];

    float4 AmbientColor : AMBIENT <
        string UIName =  "Ambient Light";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 0.0f};

    float3 CameraPosition : CAMERAPOSITION < string UIWidget="None"; >;
}

cbuffer CBufferPerObject
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION < string
UIWidget="None"; >;
    float4x4 World : WORLD < string UIWidget="None"; >;

    float4 SpecularColor : SPECULAR <
        string UIName =  "Specular Color";
        string UIWidget = "Color";
    > = {1.0f, 1.0f, 1.0f, 1.0f};

    float SpecularPower : SPECULARPOWER <
        string UIName =  "Specular Power";
        string UIWidget = "slider";
        float UIMin = 1.0;
        float UIMax = 255.0;
        float UIStep = 1.0;
    > = {25.0f};
}

Texture2D ColorTexture <
    string ResourceName = "default_color.dds";
    string UIName =  "Color Texture";
    string ResourceType = "2D";
>;

SamplerState ColorSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

RasterizerState DisableCulling
{
    CullMode = NONE;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 ObjectPosition : POSITION;
    float2 TextureCoordinate : TEXCOORD;
    float3 Normal : NORMAL;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float3 WorldPosition : POSITION;
    float3 Normal : NORMAL;
    float2 TextureCoordinate : TEXCOORD0;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;

    OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
    OUT.WorldPosition = mul(IN.ObjectPosition, World).xyz;
    OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.
TextureCoordinate);
    OUT.Normal = normalize(mul(float4(IN.Normal,0) World).xyz);

    return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN, uniform int lightCount) : SV_Target
{
    float4 OUT = (float4)0;

    float3 normal = normalize(IN.Normal);
    float3 viewDirection = normalize(CameraPosition
- IN.WorldPosition);
    float4 color = ColorTexture.Sample(ColorSampler,
IN.TextureCoordinate);
    float3 ambient = get_vector_color_contribution(AmbientColor, color.
rgb);

    LIGHT_CONTRIBUTION_DATA lightContributionData;
    lightContributionData.Color = color;
    lightContributionData.Normal = normal;
    lightContributionData.ViewDirection = viewDirection;
    lightContributionData.SpecularColor = SpecularColor;
    lightContributionData.SpecularPower = SpecularPower;

    float3 totalLightContribution = (float3)0;

    [unroll]
    for (int i = 0; i < lightCount; i++)
    {
        lightContributionData.LightDirection = get_light_
data(PointLights[i].Position, IN.WorldPosition, PointLights[i].
LightRadius);
        lightContributionData.LightColor = PointLights[i].Color;
        totalLightContribution += get_light_contribution
(lightContributionData);
    }

    OUT.rgb = ambient + totalLightContribution;
    OUT.a = 1.0f;

    return OUT;
}

/************* Techniques *************/

technique10 Lights1
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader(1)));

        SetRasterizerState(DisableCulling);
    }
}

technique10 Lights2
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader(2)));

        SetRasterizerState(DisableCulling);
    }
}

technique10 Lights3
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader(3)));

        SetRasterizerState(DisableCulling);
    }
}

technique10 Lights4
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, pixel_shader(4)));

        SetRasterizerState(DisableCulling);
    }
}


Multiple Point Lights Preamble

The multiple point lights effect begins with the NUM_LIGHTS definition, which controls the number of elements in the PointLights array. Extending or reducing the number of supported lights is as simple as modifying this macro and the corresponding techniques (discussed shortly). The ambient color and camera position remain the same for the CBufferPerFrame struct. Indeed, the remainder of the preamble is identical to that of the single point light effect, except that the Attenuation member has been removed from the VS_OUTPUT struct. The attenuation factor for each light is now computed within the pixel shader.

Multiple Point Lights Vertex and Pixel Shader

The vertex shader is extremely simple and doesn’t compute any data specific to an individual light. This responsibility falls to the pixel shader. The pixel shader begins by sampling the color texture and computing the ambient term. Then it populates a LIGHT_CONTRIBUTION_DATA variable with values that are immutable between lights—specifically, the sampled surface color, the surface normal, the view direction, and the specular color and power.

Next, the pixel shader performs a loop through the number of lights specified by the uniform shader parameter lightCount. This parameter doesn’t come from the rasterizer stage and doesn’t vary per pixel. Instead, the lightCount parameter is specified when the shader is compiled, which is done within a technique. So this value is constant at compile time but is accessed as if it were a regular variable. With uniform parameters, the HLSL compiler actually builds multiple versions of the pixel shader, one for each variation of the lightCount parameter supplied by a technique. The HLSL compiler then can unroll the loop and eliminate any performance penalties incurred by dynamic flow control.

Within the loop, the variable members of the lightContribution struct are assigned and the light’s diffuse and specular terms are calculated with a call to get_light_contribution(). The influence of each light is added to totalLightContribution, which is finally combined with the ambient term to produce the final color of the pixel.

Multiple Point Lights Techniques

This is the first effect with which you’ve used multiple techniques, and here they vary the lightCount parameter of the pixel shader. Specifically, this effect has four techniques, named Lights1, Lights2, Lights3, and Lights4; the name corresponds to the number of lights enabled within the effect. The lightCount parameter is specified inside the Compiler-Shader() call, which ignores the nonuniform shader inputs and matches the supplied value to the first uniform parameter.

The CPU-side application selects one of these techniques to execute. For instance, if three active point lights are in the vicinity of an object, the CPU-side application would select the Lights3 technique. From within NVIDIA FX Composer, expand the multiple point lights material (from the Assets panel) to select a particular technique (see Figure 7.6).

Image

Figure 7.6 Options for selecting multiple techniques within the Assets panel of NVIDIA FX Composer.

You can also use this method of multiple techniques without the notion of uniform parameters. For example, you might have Low-, Medium-, and High-Quality vertex and/or pixel shaders that correspond to the capabilities of the video hardware. Or you could have multiple uniform parameters and myriad technique permutations. As with all programming, you can use multiple ways to achieve a result. I encourage you to experiment.

Multiple Point Lights Output

Figure 7.7 shows the results of the multiple point lights effect with four lights enabled.

Image

Figure 7.7 MultiplePointLights.fx, with four lights enabled, applied to a sphere with a texture of Earth. (Texture from Reto Stöckli, NASA Earth Observatory.)

Each light is positioned 10 units away from the origin, along the x-, y-, and z-axes. Note the reddish tint to Australia and bluish-purple hue on Africa. The lights illuminating those areas are pure red and blue, and the lights toward the bottom and at the center are pure white. Figure 7.8 shows the specific positions, colors, and radii. Also notice that you must manually specify the positions and colors of the lights because binding annotations aren’t permitted on struct members.

Image

Figure 7.8 The Properties panel of NVIDIA FX Composer, showing the data supplied to the multiple point lights effect.

Summary

In this chapter, you learned about point lights, spotlights, and multiple lights for your scenes. You implemented effects for each of these light systems and refined your HLSL skills. You extended your set of common structures and utility functions to support your growing library of effects, and you discovered uniform shader parameters and their use with multiple HLSL techniques. You’ve come to the end of your introduction to HLSL; the coming chapters introduce you to more intermediate and advanced topics.

Exercises

1. Experiment with the point light effect. Create a point light in NVIDIA FX Composer’s Render panel, and then bind it to your material. Change the light’s position, rotation, color, and intensity, and observe the results.

2. Implement the modification to the point light effect, which performs the light direction calculation in the pixel shader. Compare the output to the original point light effect.

3. Explore the spotlight effect. Create a plane in NVIDIA FX Composer’s Render panel, and assign it your spotlight material. Then create and bind a spotlight. Change the light’s position, rotation, color, and intensity, and observe the results.

4. Experiment with the multiple point lights effect. Select different techniques to enable one, two, three, and four lights. Vary the data for each of the lights, and observe the results.

5. Extend the multiple point lights effect to support up to eight lights.

6. Write an effect that supports directional, point, and spotlights—one of each type. Ensure that the specular highlight is modulated by the intensity of each light.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.167.161