第十七章 Lights

来源:互联网 发布:淘宝开店最新流程ppt 编辑:程序博客网 时间:2024/06/07 14:34

第十七章 Lights

本章将会开发一组类函数用于支持directional lights、point lights以及spotlights。这项工作完成了C++渲染引擎框的全部基础内容,并标志着第三部分“Rendering with DirectX”的结束。

Motivation

在第6章“Lighting Models”和第7章“Additional Lighting Models”,我们花了大量的精力编写用于模拟光照的shaders。具体包括如下几种光照模型:
Ambient lighting(环境光)
Diffuse lighting with directional lights(由方向光源产生的漫反射光)
Specular highlights(镜面光)
Point lights(点光源)
Spotlights(聚光灯)
我们需要一种在C++渲染引擎框架中表示这些光照模型的方法。另外,需要创建一系列materials用于封装应用程序与这些光照effects的交互,这就是本章需要完成的工作。最后使用这类material类编写了一系列的示例程序,用于模拟实时光照渲染。

Light Data Types

在前面所编写的shaders中已经模拟了三种不同的光照“类型”:directional、point和spotlights。这三种光照模型中都包含有一个color值,因此可以编写一个Light基类,包含一个成员变量表示color。另外,在directional和point lights中不包含任何共同的数据。一个directional light照射一个特定的方向,但没有坐标位置(因此也不会衰减)。相反,一个point light具有一个坐标位置和一个辐射半径,但是没有特定的方向。因此,可以想象把这两种光源分别使用DirectionalLight和PointLight表示,这两个类都由Light基类派生。最后,一个spotlight中同时包含directional和point lights;除了一个坐标位置和一个辐射半径,还会以一个特定的方向照射。你可能会想象编写一个SpotLight继承自DirectionalLight和PointLight类。但是,我倾向于避免使用多重继承,因此SpotLight只由PointLinght类派生,并重新创建directional light中的相关成员。
图17.1中显示了这几种光源类型的类结构图。


图17.1 Class diagrams for the light data types.
在这个几个类的几乎没有任何复杂的实现,因此在这里没有列出代码。在本书的配套网站上提供了全部的代码。

A Diffuse Lighting Material

回顾一下在第6章编写的diffuse lighting shader。其中包含了一系列shader constants用于表示ambient color、light color以及light direction,还有一个color texture,worldviewprojection和world矩阵。把该shader对应的effect文件(DiffuseLight.fx)添加工程中,并创建一个DiffuseLightingMaterial类,以及DiffuseLightingMaterialVertex结构体,该类的声明代码如列表17.1所示。
列表17.1 Declarations of Diffuse Lighting Material and Vertex Data Types
#pragma once#include "Common.h"#include "Material.h"using namespace Library;namespace Rendering{    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;    };}

注意
我们并没有忘记添加ambient lighting!只是因为在diffuse lighting effect中包含了ambient lighting,因此在这个C++渲染框架中没有特地演示。在本书的配套网站上提供了一个单独的ambient lighting示例。

在diffuse lighting effect中每一个vertex都由一个position、UV以及normal成员组成;通过DiffuseLightingMaterialVertex结构体可以表示这些成员。在DiffuseLightingMaterial类中通过使用MATERAIL_VARIABLE_DECLARATION宏声明了这些shader constants对应的成员变量,并提供了访问这些变量的公有函数。因此,该类的成员列表比上一章编写的类要更长一些,但是声明方法是一样的。同样,在DiffuseLightingMaterial类的实现代码中也是如此。为了理解熟悉材质系统中宏声明的语法,列表17.2中列出了实现代码中完整的成员列表。但是,这也是本章最后一次列出这些成员列表。所有剩下的materials中都会使用这种编码方法。
列表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);    }}

在DiffuseLightingMaterial类的构造函数中,使用MATERIAL_VARIABLE_INITIALIZATION宏对每一个shader variables进行了初始化。然后分别使用MATERIAL_VARIABLE_DEFINITION和MATERIAL_VARIABLE_RETRIVE宏定义了这些变量对应的公有访问函数以及变量赋值。接下来,在DiffuseLightingMaterial::Initialize()函数中创建了input layout与DiffuseLightingMaterialVertex对应。最后,在DiffuseLightingMaterial::CreateVertexBuffer()函数中,从一个mesh对象中获取vertex positions,texture coordinates以及normals。这些是具体material的实现细节,不同的material具有不同的实现方式。

A Diffuse Lighting Demo

下面,我们通过一个示例程序中演示如何使用diffuse lighting material。在这个程序中,创建了新的DirectionalLight类,在该类中通过调用DiffuseMaterial的LightColor()和LightDirection()函数给对应的成员赋值。为了使该示例程序看起来更有趣一点,我们将会使用键盘的方向键更新光源方向(在运行时),并使用Page Up和Page Down键调整ambient light的光照强度。首先,创建一个DiffuseLightingDemo类,该类的声明代码如列表17.3所示。
列表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 LightModulationRate;static const XMFLOAT2 LightRotationRate;Effect* mEffect;DiffuseLightingMaterial* mMaterial;ID3D11ShaderResourceView* mTextureShaderResourceView;ID3D11Buffer* mVertexBuffer;ID3D11Buffer* mIndexBuffer;UINT mIndexCount;XMCOLOR mAmbientColor;DirectionalLight* mDirectionalLight;Keyboard* mKeyboard;XMFLOAT4X4 mWorldMatrix;ProxyModel* mProxyModel;RenderStateHelper* mRenderStateHelper;SpriteBatch* mSpriteBatch;SpriteFont* mSpriteFont;XMFLOAT2 mTextPosition;};

在DiffuseLightingDemo类中包含了一些常用的数据成员:用于存储effect和material,用于表示一个texture的shader resource view,用于在场景中变换object的world矩阵,以及vertex和index buffers。新增的成员包括用于存储ambient color和directional light的变量。其中表示ambient color的成员变量类型为XMCOLOR类型;理论上应该使用一个通用的Light类对象表示,但是使用一个XMCOLOR类型的变量已经足够了。另外还有一个新增的ProxyModel数据类型,用于渲染一个表示光源的模型。在实际的游戏程序中,不应该渲染这种proxy模型,但是在开发过程中是很有用的。在没有proxy模型的情况下,很难实时观察一个光源的坐标位置和方向。为了简洁,在这里没有列出ProxyModel类的代码,而是在图17.2中展示了类的结构图。在本书的配套网站上提供了ProxyModel类的完整代码。


图17.2 Class diagram for the ProxyModel class.
Diffuse lighting示例中的渲染操作与前面几个示例中建立的设计方式完全一样。首先,设置primitive topology和input layout,并把vertex和index buffers绑定到图形管线的input-assembler阶段。然后更新material并执行绘制操作。列表17.4中列出了DiffuseLightingDemo类的Draw()函数代码。
列表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);mRenderStateHelper->SaveAll();mSpriteBatch->Begin();std::wostringstream helpLabel;helpLabel << L"Ambient Intensity (+PgUp/-PgDn): " << mAmbientColor.a << "\n";helpLabel << L"Directional Light Intensity (+Home/-End): " << mDirectionalLight->Color().a << "\n";helpLabel << L"Rotate Directional Light (Arrow Keys)\n";mSpriteFont->DrawString(mSpriteBatch, helpLabel.str().c_str(), mTextPosition);mSpriteBatch->End();mRenderStateHelper->RestoreAll();}

图17.3中显示了diffuse lighting示例程序的输出结果。其中proxy model(线框表示的箭头)描述了光源的照射方向。另外还有一个参考网格,该网络组件(对应Grid类)用于在示例程序中提供一个参照系。在本书的配套网站上提供了该Grid类的完整代码。


图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

要在程序运行时动态更新光照是简单明了的。比如,要更新ambient light,只需要修改DiffuseLightingDemo::mAmbientColor变量中的alpha通道值。可以根据一个键盘按键的响应事件修改该通道值,或者根据一个特定功能的时间而变化(比如创建一个脉冲或闪烁光)。列表17.5列表出一种通过按键Page Up和Page Down增加或减少ambient light强度值的方法。这种方法需要在DiffuseLightingDemo::Update()函数中执行。
列表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 += LightModulationRate * (float)gameTime.ElapsedGameTime();mAmbientColor.a = (UCHAR)XMMin<float>(ambientIntensity, UCHAR_MAX);}if (mKeyboard->IsKeyDown(DIK_PGDN) && ambientIntensity > 0){ambientIntensity -= LightModulationRate * (float)gameTime.ElapsedGameTime();mAmbientColor.a = (UCHAR)XMMax<float>(ambientIntensity, 0);}}}

在DiffuseLightingDemo::Update()函数中,定义了一个静态变量ambientIntensity用于实时记录当前ambient light的强度值,并随着相关的键盘按键被按下而更新。另外,变量AmbientModulationRate用于表示ambient的强度值改变的速率。比如,当该变量值 为255时,ambient强度值可以在1秒内从最小值(0)变化到最大值(255)。
另外,还可以在程序运行时动态改变directinal light。列表17.6提供了一个使用键盘方向键旋转一个directional light的函数。
列表17.6 Rotating a Directional Light

void DiffuseLightingDemo::UpdateDirectionalLight(const GameTime& gameTime){static float directionalIntensity = mDirectionalLight->Color().a;float elapsedTime = (float)gameTime.ElapsedGameTime();// Update directional light intensityif (mKeyboard->IsKeyDown(DIK_HOME) && directionalIntensity < UCHAR_MAX){directionalIntensity += LightModulationRate * elapsedTime;XMCOLOR directionalLightColor = mDirectionalLight->Color();directionalLightColor.a = (UCHAR)XMMin<float>(directionalIntensity, UCHAR_MAX);mDirectionalLight->SetColor(directionalLightColor);}if (mKeyboard->IsKeyDown(DIK_END) && directionalIntensity > 0){directionalIntensity -= LightModulationRate * elapsedTime;XMCOLOR directionalLightColor = mDirectionalLight->Color();directionalLightColor.a = (UCHAR)XMMax<float>(directionalIntensity, 0.0f);mDirectionalLight->SetColor(directionalLightColor);}// Rotate directional lightXMFLOAT2 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);}}

在该函数中,LightRotationRate向量表示light旋转的速率(向量中两个分量值可以针对水平和垂直方向使用不同的旋转速度)。使用该向量计算得到的旋转数量值rotationAmount,创建一个绕y轴旋转的矩阵以及一个绕directinal light的right向量旋转的矩阵。最后使用这些旋转矩阵(很可能同时由水平和垂直两个方向的旋转矩阵组)对directional light以及proxy模型执行变换。

0 0
原创粉丝点击