Chapter 17. Lights

In this chapter, you develop a set of types to support directional, point, and spotlights. This work rounds out the base of your C++ rendering framework and marks the end of Part III, “Rendering with DirectX.”

Motivation

In Chapter 6, “Lighting Models,” and Chapter 7, “Additional Lighting Models,” you expended a lot of effort authoring shaders to simulate lighting. Specifically, you authored effects for the following topics:

Image Ambient lighting

Image Diffuse lighting with directional lights

Image Specular highlights

Image Point lights

Image Spotlights

You need a way to represent those lights within your C++ rendering framework. Furthermore, you need to create a set of materials to accommodate the interactions with these effects. That’s the work you do in this chapter. To exercise the code, you author a set of demos that include the capability to manipulate lights at runtime.

Light Data Types

You’ve modeled three distinct “types” of light in your shaders: directional, point, and spotlights. They all have a color, so you can imagine a base Light class that contains a color member. Otherwise, directional and point lights don’t share any data. A directional light aims in a particular direction but has no position (and, thus, no attenuation). Conversely, a point light has a position and a radius of influence, but no specific direction. Thus, you can envision separate PointLight and DirectionalLight classes that both derive from the base Light class. Finally, a spotlight has elements of both directional and point lights; it has a position in space and a radius of influence, but it also casts light in a particular direction. You might envision a SpotLight class that inherits from both respective types. However, I tend to avoid multiple inheritance, so the SpotLight type derives only from the PointLight class and re-creates the directional light members.

Figure 17.1 shows the class diagrams for the light data types.

Image

Figure 17.1 Class diagrams for the light data types.

Practically no complex implementation exists for any of these data types, so their source files are not listed here. You can find all these types on the companion website.

A Diffuse Lighting Material

Refer back to the diffuse lighting shader you wrote in Chapter 6. It includes shader constants for an ambient color, a light color, and a light direction, along with a color texture and world-view-projection and world matrices. Add that effect file (DiffuseLighting.fx) to one of your projects, and create a DiffuseLightingMaterial class and DiffuseLightingMaterialVertex structure with the declarations in Listing 17.1.

Listing 17.1 Declarations of Diffuse Lighting Material and Vertex Data Types


typedef struct _DiffuseLightingMaterialVertex
{
    XMFLOAT4 Position;
    XMFLOAT2 TextureCoordinates;
    XMFLOAT3 Normal;

    _DiffuseLightingMaterialVertex() { }

    _DiffuseLightingMaterialVertex(XMFLOAT4 position, XMFLOAT2 textureCoordinates, XMFLOAT3 normal)
        : Position(position), TextureCoordinates(textureCoordinates),
Normal(normal) { }
} DiffuseLightingMaterialVertex;

class DiffuseLightingMaterial : public Material
{
    RTTI_DECLARATIONS(DiffuseLightingMaterial, Material)

    MATERIAL_VARIABLE_DECLARATION(WorldViewProjection)
    MATERIAL_VARIABLE_DECLARATION(World)
    MATERIAL_VARIABLE_DECLARATION(AmbientColor)
    MATERIAL_VARIABLE_DECLARATION(LightColor)
    MATERIAL_VARIABLE_DECLARATION(LightDirection)
    MATERIAL_VARIABLE_DECLARATION(ColorTexture)

public:
    DiffuseLightingMaterial();

    virtual void Initialize(Effect* effect) override;
    virtual void CreateVertexBuffer(ID3D11Device* device, const Mesh&
mesh, ID3D11Buffer** vertexBuffer) const override;
    void CreateVertexBuffer(ID3D11Device* device,
DiffuseLightingMaterialVertex* vertices, UINT vertexCount,
ID3D11Buffer** vertexBuffer) const;
    virtual UINT VertexSize() const override;
};



Note

We haven’t forgotten ambient lighting! But because it’s included within the diffuse lighting effect, we have omitted a specific demonstration of how to incorporate this shader into the C++ framework. You can find an independent ambient lighting demo on the companion website.


Each vertex for the diffuse lighting effect consists of a position, a UV, and a normal; these elements are expressed by the DiffuseLightingMaterialVertex structure. The DiffuseLightingMaterial class declares members and accessors for each of the shader constants through the MATERIAL_VARIABLE_DECLARATION macro. Thus, the class listing is a little larger than the materials you wrote in the last chapter but follows the same pattern. The same is true for the class implementation. Listing 17.2 presents the full listing for this implementation, to reinforce the syntax for the material system; however, this is the last material listing included in this chapter. All the remaining materials follow these same patterns.

Listing 17.2 The DiffuseLightingMaterial.cpp File


#include "DiffuseLightingMaterial.h"
#include "GameException.h"
#include "Mesh.h"

namespace Rendering
{
    RTTI_DEFINITIONS(DiffuseLightingMaterial)

    DiffuseLightingMaterial::DiffuseLightingMaterial()
        : Material("main11"),
          MATERIAL_VARIABLE_INITIALIZATION(WorldViewProjection),
          MATERIAL_VARIABLE_INITIALIZATION(World),
          MATERIAL_VARIABLE_INITIALIZATION(AmbientColor),
          MATERIAL_VARIABLE_INITIALIZATION(LightColor),
          MATERIAL_VARIABLE_INITIALIZATION(LightDirection),
          MATERIAL_VARIABLE_INITIALIZATION(ColorTexture)
    {
    }

    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial,
WorldViewProjection)
    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial, World)
    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial, AmbientColor)
    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial, LightColor)
    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial,
LightDirection)
    MATERIAL_VARIABLE_DEFINITION(DiffuseLightingMaterial, ColorTexture)

    void DiffuseLightingMaterial::Initialize(Effect* effect)
    {
        Material::Initialize(effect);

        MATERIAL_VARIABLE_RETRIEVE(WorldViewProjection)
        MATERIAL_VARIABLE_RETRIEVE(World)
        MATERIAL_VARIABLE_RETRIEVE(AmbientColor)
        MATERIAL_VARIABLE_RETRIEVE(LightColor)
        MATERIAL_VARIABLE_RETRIEVE(LightDirection)
        MATERIAL_VARIABLE_RETRIEVE(ColorTexture)

        D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_
ALIGNED_ELEMENT
, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_
APPEND_ALIGNED_ELEMENT
, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };

        CreateInputLayout("main11", "p0", inputElementDescriptions,
ARRAYSIZE(inputElementDescriptions));
    }

    void DiffuseLightingMaterial::CreateVertexBuffer(ID3D11Device*
device, const Mesh& mesh, ID3D11Buffer** vertexBuffer) const
    {
        const std::vector<XMFLOAT3>& sourceVertices = mesh.Vertices();
        std::vector<XMFLOAT3>* textureCoordinates = mesh.
TextureCoordinates().at(0);
        assert(textureCoordinates->size() == sourceVertices.size());
        const std::vector<XMFLOAT3>& normals = mesh.Normals();
        assert(textureCoordinates->size() == sourceVertices.size());

        std::vector<DiffuseLightingMaterialVertex> vertices;
        vertices.reserve(sourceVertices.size());
        for (UINT i = 0; i < sourceVertices.size(); i++)
        {
            XMFLOAT3 position = sourceVertices.at(i);
            XMFLOAT3 uv = textureCoordinates->at(i);
            XMFLOAT3 normal = normals.at(i);

vertices.push_back(DiffuseLightingMaterialVertex(XMFLOAT4(position.x, position.y, position.z, 1.0f), XMFLOAT2(uv.x, uv.y), normal));
        }

        CreateVertexBuffer(device, &vertices[0], vertices.size(),
vertexBuffer);
    }

    void DiffuseLightingMaterial::CreateVertexBuffer(ID3D11Device*
device, DiffuseLightingMaterialVertex* vertices, UINT vertexCount,
ID3D11Buffer** vertexBuffer) const
    {
        D3D11_BUFFER_DESC vertexBufferDesc;
        ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
        vertexBufferDesc.ByteWidth = VertexSize() * vertexCount;
        vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

        D3D11_SUBRESOURCE_DATA vertexSubResourceData;
        ZeroMemory(&vertexSubResourceData, sizeof
(vertexSubResourceData));
        vertexSubResourceData.pSysMem = vertices;
        if (FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexSubResourceData, vertexBuffer)))
        {
            throw GameException("ID3D11Device::CreateBuffer()
failed.");
        }
    }

    UINT DiffuseLightingMaterial::VertexSize() const
    {
        return sizeof(DiffuseLightingMaterialVertex);
    }
}


The DiffuseLightingMaterial constructor initializes each of the shader variables using the MATERIAL_VARIABLE_INITIALIZATION macro. Then their accessors are defined and the associated members are cached through the MATERIAL_VARIABLE_DEFINITION and MATERIAL_VARIABLE_RETRIEVE macros, respectively. Next, the DiffuseLightingMaterial::Initialize() method creates the input layout to match the vertex structure. Finally, note how vertex positions, texture coordinates, and normals are retrieved from a mesh within the DiffuseLightingMaterial::CreateVertexBuffer() method. These are the implementation details that vary from one material to another.

A Diffuse Lighting Demo

Now let’s exercise the diffuse lighting material with a demo. This application employs the newly created DirectionalLight class to feed data into the LightColor() and LightDirection() members of the DiffuseMaterial type. To make the demo a bit more interesting, you’ll update the direction of the light (at runtime) using the arrow keys and change the intensity of the ambient light with the Page Up and Page Down keys. Start by creating a DiffuseLightingDemo class with the declaration in Listing 17.3.

Listing 17.3 Declaration of the DiffuseLightingDemo Class


class DiffuseLightingDemo : public DrawableGameComponent
{
    RTTI_DECLARATIONS(DiffuseLightingDemo, DrawableGameComponent)

public:
    DiffuseLightingDemo(Game& game, Camera& camera);
    ~DiffuseLightingDemo();

    virtual void Initialize() override;
    virtual void Update(const GameTime& gameTime) override;
    virtual void Draw(const GameTime& gameTime) override;

private:
    DiffuseLightingDemo();
    DiffuseLightingDemo(const DiffuseLightingDemo& rhs);
    DiffuseLightingDemo& operator=(const DiffuseLightingDemo& rhs);

    void UpdateAmbientLight(const GameTime& gameTime);
    void UpdateDirectionalLight(const GameTime& gameTime);

    static const float AmbientModulationRate;
    static const XMFLOAT2 RotationRate;

    Effect* mEffect;
    DiffuseLightingMaterial* mMaterial;
    ID3D11ShaderResourceView* mTextureShaderResourceView;
    ID3D11Buffer* mVertexBuffer;
    ID3D11Buffer* mIndexBuffer;
    UINT mIndexCount;

    XMCOLOR mAmbientColor;
    DirectionalLight* mDirectionalLight;
    Keyboard* mKeyboard;
    XMFLOAT4X4 mWorldMatrix;

    ProxyModel* mProxyModel;
};


You have the usual data members within the DiffuseLightingDemo class: storage for an effect and a material, a shader resource view for a texture, a world matrix for transforming the object within the scene, and vertex and index buffers. New are class members for storing the ambient color and directional light. Note that the ambient color is represented through the XMCOLOR type; this could have been stored as a generic Light object, but an XMCOLOR suffices. Also new is the ProxyModel data type, which renders a model representing a light. In an actual game, you wouldn’t render a proxy model, but this is useful during development. Without a proxy model, the position and orientation of a light would be difficult to keep track of. A listing of the ProxyModel class is omitted for brevity; Figure 17.2 shows its class diagram. You can find the source code for this class on the companion website.

Image

Figure 17.2 Class diagram for the ProxyModel class.

Rendering for the diffuse lighting demo follows the patterns established over the last few demos. You set the primitive topology and input layout, and you bind the vertex and index buffers to the input-assembler pipeline stage. Then you update the material and execute a draw call. Listing 17.4 presents the Draw() method of the DiffuseLightingDemo class.

Listing 17.4 Implementation of the DiffuseLightingDemo::Draw() Method


void DiffuseLightingDemo::Draw(const GameTime& gameTime)
{
    ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
    direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_
TOPOLOGY_TRIANGLELIST);

    Pass* pass = mMaterial->CurrentTechnique()->Passes().at(0);
    ID3D11InputLayout* inputLayout = mMaterial->InputLayouts().
at(pass);
    direct3DDeviceContext->IASetInputLayout(inputLayout);

    UINT stride = mMaterial->VertexSize();
    UINT offset = 0;
    direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);
    direct3DDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_
R32_UINT, 0);

    XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
    XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
    XMVECTOR ambientColor = XMLoadColor(&mAmbientColor);

    mMaterial->WorldViewProjection() << wvp;
    mMaterial->World() << worldMatrix;
    mMaterial->AmbientColor() << ambientColor;
    mMaterial->LightColor() << mDirectionalLight->ColorVector();
    mMaterial->LightDirection() <<
mDirectionalLight->DirectionVector();
    mMaterial->ColorTexture() << mTextureShaderResourceView;

    pass->Apply(0, direct3DDeviceContext);

    direct3DDeviceContext->DrawIndexed(mIndexCount, 0, 0);

    mProxyModel->Draw(gameTime);
}


Figure 17.3 shows the output of the diffuse lighting demo. Note the proxy model (the wireframe arrow) depicting the direction of the light. Also note the reference grid within the image. This useful component (the Grid class) can help provide a frame of reference for your demos. You can find the source code for the Grid class on the book’s companion website.

Image

Figure 17.3 Output of the diffuse lighting demo. (Original texture from Reto Stöckli, NASA Earth Observatory. Additional texturing by Nick Zuccarello, Florida Interactive Entertainment Academy.)

Interacting with Lights

Allowing your lights to be updated at runtime is straight forward. For example, to update the ambient light, you merely need to modify the alpha channel of the DiffuseLightingDemo::mAmbientColor member. You can do this in response to a keyboard press or as a function of time (to create, for example, a pulsing or flickering light). Listing 17.5 presents a method for incrementing and decrementing the ambient light intensity when the Page Up and Page Down keys are pressed. This method should be invoked from within DiffuseLightingDemo::Update().

Listing 17.5 Incrementing and Decrementing Ambient Light Intensity


void DiffuseLightingDemo::UpdateAmbientLight(const GameTime& gameTime)
{
    static float ambientIntensity = mAmbientColor.a;

    if (mKeyboard != nullptr)
    {
        if (mKeyboard->IsKeyDown(DIK_PGUP) && ambientIntensity <
UCHAR_MAX)
        {
            ambientIntensity += AmbientModulationRate *
(float)gameTime.ElapsedGameTime();
            mAmbientColor.a = XMMin<UCHAR>(ambientIntensity,
UCHAR_MAX);
        }

        if (mKeyboard->IsKeyDown(DIK_PGDN) && ambientIntensity > 0)
        {
            ambientIntensity -= LightModulationRate * (float)gameTime.
ElapsedGameTime();
            mAmbientColor.a = XMMax<UCHAR>(ambientIntensity, 0);
        }
    }
}


The ambientIntensity static variable tracks the current ambient intensity and is updated, over time, whenever the associated keys are pressed. The AmbientModulationRate value dictates how quickly the ambient intensity should increase or decrease. For example, with a LightModulationRate of 255, the ambient intensity goes from its minimum value (0) to its maximum value (255) in 1 second.

You can also interact with the directional light. Listing 17.6 presents a method for rotating a directional light with the arrow keys.

Listing 17.6 Rotating a Directional Light


void DiffuseLightingDemo::UpdateDirectionalLight(const GameTime&
gameTime)
{
    float elapsedTime = (float)gameTime.ElapsedGameTime();

    XMFLOAT2 rotationAmount = Vector2Helper::Zero;
    if (mKeyboard->IsKeyDown(DIK_LEFTARROW))
    {
        rotationAmount.x += LightRotationRate.x * elapsedTime;
    }
    if (mKeyboard->IsKeyDown(DIK_RIGHTARROW))
    {
        rotationAmount.x -= LightRotationRate.x * elapsedTime;
    }
    if (mKeyboard->IsKeyDown(DIK_UPARROW))
    {
        rotationAmount.y += LightRotationRate.y * elapsedTime;
    }
    if (mKeyboard->IsKeyDown(DIK_DOWNARROW))
    {
        rotationAmount.y -= LightRotationRate.y * elapsedTime;
    }

    XMMATRIX lightRotationMatrix = XMMatrixIdentity();
    if (rotationAmount.x != 0)
    {
        lightRotationMatrix = XMMatrixRotationY(rotationAmount.x);
    }

    if (rotationAmount.y != 0)
    {
        XMMATRIX lightRotationAxisMatrix = XMMatrixRotationAxis
(mDirectionalLight->RightVector(), rotationAmount.y);
        lightRotationMatrix *= lightRotationAxisMatrix;
    }

    if (rotationAmount.x != 0.0f || rotationAmount.y != 0.0f)
    {
        mDirectionalLight->ApplyRotation(lightRotationMatrix);
        mProxyModel->ApplyRotation(lightRotationMatrix);
    }
}


In this code, the LightRotationRate vector dictates how quickly the light will rotate (using two values allows different rates for horizontal and vertical rotation). The calculated rotation amounts are used to build matrices for rotation around the y-axis and the directional light’s right vector. The resulting rotation matrix (potentially consisting of both horizontal and vertical rotation) is used to transform the directional light and the proxy model.

A Point Light Demo

A point light demo is similar to the diffuse lighting demo. Create a PointLightMaterial class that matches the PointLight.fx effect from Chapter 7. This effect incorporates the Blinn-Phong specular highlighting model, and the material should contain the following variable declarations:

MATERIAL_VARIABLE_DECLARATION(WorldViewProjection)
MATERIAL_VARIABLE_DECLARATION(World)
MATERIAL_VARIABLE_DECLARATION(SpecularColor)
MATERIAL_VARIABLE_DECLARATION(SpecularPower)
MATERIAL_VARIABLE_DECLARATION(AmbientColor)
MATERIAL_VARIABLE_DECLARATION(LightColor)
MATERIAL_VARIABLE_DECLARATION(LightPosition)
MATERIAL_VARIABLE_DECLARATION(LightRadius)
MATERIAL_VARIABLE_DECLARATION(CameraPosition)
MATERIAL_VARIABLE_DECLARATION(ColorTexture)

The point light vertex shader accepts the same input as the diffuse lighting shader (position, texture coordinates, and a normal) so you can either duplicate the input layout and vertex buffer creation code or roll a system to share code between the materials.

The PointLightDemo class can borrow heavily from the DiffuseLightingDemo type, replacing the DirectionalLight data member with a PointLight and adding members for the specular color and power. Rendering for the point light demo is identical to the diffuse lighting demo, except for the shader variable updates. You update a point light material with code such as the following:

XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
XMVECTOR ambientColor = XMLoadColor(&mAmbientColor);
XMVECTOR specularColor = XMLoadColor(&mSpecularColor);

mMaterial->WorldViewProjection() << wvp;
mMaterial->World() << worldMatrix;
mMaterial->SpecularColor() << specularColor;
mMaterial->SpecularPower() << mSpecularPower;
mMaterial->AmbientColor() << ambientColor;
mMaterial->LightColor() << mPointLight->ColorVector();
mMaterial->LightPosition() << mPointLight->PositionVector();
mMaterial->LightRadius() << mPointLight->Radius();
mMaterial->ColorTexture() << mTextureShaderResourceView;
mMaterial->CameraPosition() << mCamera->PositionVector();

Note the updates for the specular color and power, and light position and radius. Figure 17.4 presents the output from the point light demo, with a proxy model representing the point light.

Image

Figure 17.4 Output of the point light demo. (Texture from Reto Stöckli, NASA Earth Observatory.)

Just as with the diffuse lighting demo, you can allow your point light to be manipulated at runtime. Listing 17.7 presents a method for moving the point light along the x-, y-, and z-axes in response to pressing number pad keys: 4/6 (x-axis), 3/9 (y-axis), and 8/2 (z-axis).

Listing 17.7 Moving the Point Light


void PointLightDemo::UpdatePointLight(const GameTime& gameTime)
{
    XMFLOAT3 movementAmount = Vector3Helper::Zero;
    if (mKeyboard != nullptr)
    {
        if (mKeyboard->IsKeyDown(DIK_NUMPAD4))
        {
            movementAmount.x = -1.0f;
        }

        if (mKeyboard->IsKeyDown(DIK_NUMPAD6))
        {
            movementAmount.x = 1.0f;
        }

        if (mKeyboard->IsKeyDown(DIK_NUMPAD9))
        {
            movementAmount.y = 1.0f;
        }

        if (mKeyboard->IsKeyDown(DIK_NUMPAD3))
        {
            movementAmount.y = -1.0f;
        }

        if (mKeyboard->IsKeyDown(DIK_NUMPAD8))
        {
            movementAmount.z = -1.0f;
        }

        if (mKeyboard->IsKeyDown(DIK_NUMPAD2))
        {
            movementAmount.z = 1.0f;
        }
    }

    XMVECTOR movement = XMLoadFloat3(&movementAmount) * MovementRate *
(float)gameTime.ElapsedGameTime();
    mPointLight->SetPosition(mPointLight->PositionVector() + movement);
    mProxyModel->SetPosition(mPointLight->Position());
}


A Spotlight Demo

A spotlight demo follows the same patterns. We’ve left it as an exercise for you to create the material that matches the SpotLight.fx effect (from Chapter 7) and the corresponding demo component. Figure 17.5 shows the output of the demo available on the companion website.

Image

Figure 17.5 Output of the spotlight demo.

Interaction with the spotlight is a combination of the methods used for the directional and point light demos. The arrow keys control the direction of the spotlight, and the number pad updates its position.

Summary

In this chapter, you developed a set of types to support directional, point, and spotlights. You created materials for interacting with the shaders you authored in Chapters 6 and 7. Then you developed some demo applications to exercise these new data types. This work completes Part III, “Rendering with DirectX.” In the next section, you begin exploring more intermediate-level rendering topics.

Exercises

1. Develop the spotlight demo discussed in the chapter.

2. Create materials for the environment mapping, transparency mapping, normal mapping, and displacement mapping shaders you authored in Chapter 8, “Gleaming the Cube,” and Chapter 9, “Normal Mapping and Displacement Mapping.” Develop corresponding demo application to exercise these materials.

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

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