DirectX 11 - Tutoriel 6 : Lumière diffuse

RasterTek

Traducteur

Jérôme Marsaguet

Dans ce tutoriel, je vais expliquer comment éclairer des objets 3D en utilisant un éclairage diffus et DirectX 11. Nous partirons du code du tutoriel précédent et le modifierons.

Commentez

- Titre : DirectX 11 - Tutoriel 6 : Lumière diffuse

- Auteur : RasterTek

- Traducteur : Jérôme Marsaguet

- Parution : 9 juillet 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Navigation

- II. Introduction

- III. Framework

- IV. Light.vs

- V. Light.ps

- VI. Lightshaderclass.h

- VII. Lightshaderclass.cpp

- VIII. Modelclass.h

- IX. Modelclass.cpp

- X. Lightclass.h

- XI. Lightclass.cpp

- XII. Graphicsclass.h

- XIII. Graphicsclass.cpp

- XIV. Résumé

- XV. Exercices à faire

- XVI. Source Code

- XVII. Remerciements

- Navigation

Navigation

Tutoriel précédent : application de textures

Sommaire

Tutoriel suivant : rendu de modèles 3D

II - Introduction

Le type d'éclairage diffus que nous allons réaliser est appelé éclairage directionnel : il est comparable au rayonnement du Soleil sur la Terre. Il s'agit d'une source de lumière basée sur la direction, qui se situe à une grande distance. Elle émet une certaine quantité mesurable de lumière sur chaque objet. Toutefois, contrairement à l'éclairage ambiant (un autre modèle d'éclairage que nous verrons bientôt), elle n'éclairera pas les surfaces qu'elle ne touche pas directement.

J'ai choisi un éclairage directionnel pour débuter, car il est très facile à déboguer visuellement. En outre, puisqu'il ne nécessite que la direction, la formule est plus simple comparée aux autres types d'éclairage diffus tels que les spots ou lumières ponctuelles.

La mise en œuvre de l'éclairage diffus dans DirectX 11 s'effectue avec les vertex et pixel shaders. Il ne nécessite que la direction de la lumière et le vecteur normal des polygones que nous souhaitons éclairer. La direction est un vecteur unique que vous définissez, et vous pouvez obtenir la normale de chaque polygone en utilisant les trois sommets qui le composent. Dans ce tutoriel, nous incorporerons également la couleur de la lumière diffuse dans l'équation de l'éclairage.

III - Framework

Pour ce tutoriel, nous allons créer une nouvelle classe appelée LightClass qui représentera les sources de lumière dans les scènes. LightClass ne servira ici qu'à stocker la direction et la couleur de la lumière. Nous allons également supprimer la classe TextureShaderClass pour la remplacer par LightShaderClass qui gérera le rendu de la lumière sur le modèle. L'ajout de ces nouvelles classes au cadre de travail donnera ceci :

Nous commencerons par regarder le code du shader HLSL de lumière. Vous remarquerez que c'est juste une version mise à jour du shader de texture du tutoriel précédent.

IV - Light.vs

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : light.vs
////////////////////////////////////////////////////////////////////////////////

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};

Les deux structures ont maintenant un float3 représentant le vecteur normal utilisé pour obtenir la quantité de lumière qui se calcule à partir de l'angle entre la direction de la normale et la direction de la lumière.

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
float4 position : POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
};

////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
PixelInputType output;

// Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.

// Calcule la position du sommet selon les matrices monde, de vue et de projection.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);

// Stocke les coordonnées de la texture pour le pixel shader.
output.tex = input.tex;

Le vecteur normal de ce sommet est calculé dans l'espace monde et normalisé avant d'être envoyé en entrée du pixel shader.

// Calcule le vecteur normal à partir de la matrice monde seulement.
output.normal = mul(input.normal, (float3x3)worldMatrix);

// Normalise le vecteur normal.
output.normal = normalize(output.normal);

return output;
}

V - Light.ps

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : light.ps
////////////////////////////////////////////////////////////////////////////////

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
Texture2D shaderTexture;
SamplerState SampleType;

Nous avons deux nouvelles variables globales à l'intérieur du LightBuffer qui contiennent la couleur et la direction de la lumière diffuse. Elles sont définies à partir des valeurs du nouvel objet LightClass.

cbuffer LightBuffer
{
float4 diffuseColor;
float3 lightDirection;
float padding;
};

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
};

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
float4 textureColor;
float3 lightDir;
float lightIntensity;
float4 color;

// Évalue la couleur du pixel à partir de la texture en utilisant l'échantillonneur aux coordonnées désignées.
textureColor = shaderTexture.Sample(SampleType, input.tex);

C'est ici que l'équation d'éclairage mentionnée plus tôt est mise en œuvre. La valeur de l'intensité de la lumière est obtenue par le produit scalaire entre le vecteur normal du triangle et le vecteur de direction de lumière.

// Inverse la direction de la lumière pour les calculs.
lightDir = -lightDirection;

// Calcule la quantité de lumière sur ce pixel.
lightIntensity = saturate(dot(input.normal, lightDir));

Enfin, la valeur de la lumière diffuse est combinée avec la valeur du pixel de la texture pour produire la couleur finale.

// Détermine la quantité finale de couleur diffuse en la combinant avec l'intensité de la lumière.
color = saturate(diffuseColor * lightIntensity);

// Multiplie le pixel de la texture et la couleur diffuse finale pour obtenir la couleur finale du pixel.
color = color * textureColor;

return color;
}

VI - Lightshaderclass.h

La nouvelle classe LightShaderClass est une légère réécriture de la classe TextureShaderClass des tutoriels précédents visant à intégrer l'éclairage.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe: LightShaderClass
////////////////////////////////////////////////////////////////////////////////
class LightShaderClass
{
private:
struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};

La nouvelle structure LightBufferType sera utilisée pour stocker les informations sur l'éclairage, sa définition est la même que celle du pixel shader. Notez que j'ajoute un float complémentaire afin de m'assurer que la taille de la structure soit un multiple de 16. Sans cette précaution, la structure aurait tenu sur seulement 28 octets, et CreateBuffer aurait échoué si nous avions utilisé sizeof(LightBufferType) car il nécessite un multiple de 16 pour fonctionner correctement.

struct LightBufferType
{
D3DXVECTOR4 diffuseColor;
D3DXVECTOR3 lightDirection;
float padding;  // Ajout d'un float complémentaire afin que la taille de la structure soit un multiple de 16 pour les besoins de la fonction CreateBuffer.
};

public:
LightShaderClass();
LightShaderClass(const LightShaderClass&);
~LightShaderClass();

bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);

private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);
void RenderShader(ID3D11DeviceContext*, int);

private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11SamplerState* m_sampleState;
ID3D11Buffer* m_matrixBuffer;

Il y a un nouveau tampon constant privé porteur de l'information lumineuse (couleur et direction). Ce tampon de lumière sera utilisé par la classe pour définir les variables globales de lumière au sein du pixel shader HLSL.

ID3D11Buffer* m_lightBuffer;
};

#endif

VII - Lightshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightshaderclass.h"

LightShaderClass::LightShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_sampleState = 0;
m_matrixBuffer = 0;

Nous initialisons à la valeur nulle le tampon de lumière constant dans le constructeur de la classe.

m_lightBuffer = 0;
}

LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}

LightShaderClass::~LightShaderClass()
{
}

bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;

Les nouveaux fichiers shaders HLSL light.vs et light.ps sont utilisés comme entrées pour initialiser le shader de lumière.

// Initialise les vertex et pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps");
if(!result)
{
return false;
}

return true;
}

void LightShaderClass::Shutdown()
{
// Ferme les vertex et pixel shaders ainsi que les objets associés.
ShutdownShader();

return;
}

La méthode Render prend maintenant dans la direction et la couleur de la lumière diffuse comme entrées. Ces variables sont ensuite envoyées dans la méthode SetShaderParameters puis définies dans le shader lui-même.

bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor)
{
bool result;

// Définit les paramètres du shader qui seront utilisés pour le rendu.
result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor);
if(!result)
{
return false;
}

// Effectue le rendu des tampons avec le shader.
RenderShader(deviceContext, indexCount);

return true;
}

bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
HRESULT result;
ID3D10Blob* errorMessage;
ID3D10Blob* vertexShaderBuffer;
ID3D10Blob* pixelShaderBuffer;

La variable polygonLayout a été modifiée pour comporter trois éléments au lieu de deux, afin qu'il puisse accueillir un vecteur normal dans la disposition.

D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
unsigned int numElements;
D3D11_SAMPLER_DESC samplerDesc;
D3D11_BUFFER_DESC matrixBufferDesc;

Nous ajoutons aussi une nouvelle description de variable pour le tampon de lumière constant.

D3D11_BUFFER_DESC lightBufferDesc;

// Initialise à zéro les pointeurs que la méthode va utiliser.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;

Nous chargeons le nouveau vertex shader de lumière.

// Compile le code du vertex shader.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
}
// Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
else
{
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
}

return false;
}

Nous chargeons le nouveau pixel shader de lumière.

// Compile le code du pixel shader.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
}
// Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
else
{
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
}

return false;
}

// Crée le vertex shader à partir du tampon.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
if(FAILED(result))
{
return false;
}

// Crée le pixel shader à partir du tampon.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
if(FAILED(result))
{
return false;
}

// Crée la description de la disposition d'entrée du vertex.
// Cette disposition doit être la même que celle de la structure VextexType du shader.
polygonLayout[0].SemanticName = "POSITION";
polygonLayout[0].SemanticIndex = 0;
polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[0].InputSlot = 0;
polygonLayout[0].AlignedByteOffset = 0;
polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[0].InstanceDataStepRate = 0;

polygonLayout[1].SemanticName = "TEXCOORD";
polygonLayout[1].SemanticIndex = 0;
polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
polygonLayout[1].InputSlot = 0;
polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[1].InstanceDataStepRate = 0;

L'un des principaux changements apportés à l'initialisation du shader se situe ici dans le polygonLayout. Nous ajoutons un troisième élément pour le vecteur normal qui sera utilisé pour l'éclairage. Son nom sémantique est NORMAL et son format est l'ordinaire DXGI_FORMAT_R32G32B32_FLOAT qui gère trois flottants pour les valeurs x, y et z du vecteur normal. La disposition correspondra dès lors à l'entrée du vertex shader HLSL attendue.

polygonLayout[2].SemanticName = "NORMAL";
polygonLayout[2].SemanticIndex = 0;
polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[2].InputSlot = 0;
polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[2].InstanceDataStepRate = 0;

// Obtient le nombre d'éléments de la disposition des données.
numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

// Crée la disposition d'entrée du vertex.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(),
&m_layout);
if(FAILED(result))
{
return false;
}

// Libère les vertex et pixel shaders, car nous n'en avons plus besoin.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;

pixelShaderBuffer->Release();
pixelShaderBuffer = 0;

// Crée une description de l'état de l'échantillonneur de la texture.
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

// Crée l'état de l'échantillonneur de la texture.
result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
if(FAILED(result))
{
return false;
}

// Met en place la description du tampon de matrice dynamique constant situé dans le vertex shader.
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;

// Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
if(FAILED(result))
{
return false;
}

Nous configurons ici la description du tampon de lumière constant qui gérera la couleur et la direction de la lumière diffuse. Faites attention à la taille des tampons constants, si ce n'est pas un multiple de 16, vous avez besoin de remplir l'espace manquant vers la fin ou la fonction CreateBuffer échouera. Dans notre cas, le tampon constant a une taille de 28 octets auxquels on ajoute 4 octets pour en obtenir 32.

// Met en place la description du tampon de lumière constant du pixel shader.
// Remarquez que le champ ByteWidth doit toujours être un multiple de 16
// lorsque l'on utilise D3D11_BIND_CONSTANT_BUFFER ou l'utilisation de CreateBuffer échouera.
lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
lightBufferDesc.ByteWidth = sizeof(LightBufferType);
lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
lightBufferDesc.MiscFlags = 0;
lightBufferDesc.StructureByteStride = 0;

// Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
if(FAILED(result))
{
return false;
}

return true;
}

void LightShaderClass::ShutdownShader()
{

Le nouveau tampon de lumière constant est libéré dans la méthode ShutdownShader.

// Libère le tampon de lumière constant.
if(m_lightBuffer)
{
m_lightBuffer->Release();
m_lightBuffer = 0;
}

// Libère le tampon de matrice constant.
if(m_matrixBuffer)
{
m_matrixBuffer->Release();
m_matrixBuffer = 0;
}

// Libère l'état de l'échantillonneur.
if(m_sampleState)
{
m_sampleState->Release();
m_sampleState = 0;
}

// Libère la disposition.
if(m_layout)
{
m_layout->Release();
m_layout = 0;
}

// Libère le pixel shader.
if(m_pixelShader)
{
m_pixelShader->Release();
m_pixelShader = 0;
}

// Libère le vertex shader.
if(m_vertexShader)
{
m_vertexShader->Release();
m_vertexShader = 0;
}

return;
}

void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
char* compileErrors;
unsigned long bufferSize, i;
ofstream fout;

// Récupère le pointeur vers le message d'erreur du tampon.
compileErrors = (char*)(errorMessage->GetBufferPointer());

// Obtient la longueur du message.
bufferSize = errorMessage->GetBufferSize();

// Ouvre un fichier pour y écrire le message d'erreur.
fout.open("shader-error.txt");

// Écrit le message d'erreur.
for(i=0; i<bufferSize; i++)
{
fout << compileErrors[i];
}

// Ferme le fichier.
fout.close();

// Libère le message d'erreur.
errorMessage->Release();
errorMessage = 0;

// Affiche un message à l'écran pour avertir l'utilisateur qu'il doit vérifier
// le contenu du fichier pour y trouver les erreurs de compilation.
MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

return;
}

La méthode SetShaderParameters prend maintenant en entrée les variables lightDirection et diffuseColor.

bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection,
D3DXVECTOR4 diffuseColor)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
unsigned int bufferNumber;
MatrixBufferType* dataPtr;
LightBufferType* dataPtr2;

// Transpose les matrices afin de les préparer pour le shader.
D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

// Pose un verrou sur le tampon constant afin que l'on puise écrire dedans.
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}

// Récupère le pointeur des données dans le tampon constant.
dataPtr = (MatrixBufferType*)mappedResource.pData;

// Copie les matrices dans le tampon constant.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;

// Déverrouille le tampon constant.
deviceContext->Unmap(m_matrixBuffer, 0);

// Définit la position du tampon constant dans le vertex shader.
bufferNumber = 0;

// Fixe le tampon constant contenant les données à jour dans le vertex shader.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

// Définit la texture dans le pixel shader.
deviceContext->PSSetShaderResources(0, 1, &texture);

Le tampon de lumière constant est configuré de la même manière que le tampon de matrice constant. Nous verrouillons d'abord le tampon et obtenons un pointeur vers lui permettant de définir la couleur et la direction de la lumière diffuse. Ceci fait, nous déverrouillons le tampon, et le transmettons au pixel shader. Notez que nous utilisons la fonction PSSetConstantBuffers au lieu de VSSetConstantBuffers puisqu'il s'agit d'un tampon de pixel shader.

// Pose un verrou sur le tampon de lumière constant afin que l'on puisse écrire dedans.
result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}

// Récupère le pointeur des données dans le tampon constant.
dataPtr2 = (LightBufferType*)mappedResource.pData;

// Copie les variables de lumière dans le tampon constant.
dataPtr2->diffuseColor = diffuseColor;
dataPtr2->lightDirection = lightDirection;
dataPtr2->padding = 0.0f;

// Déverrouille le tampon constant.
deviceContext->Unmap(m_lightBuffer, 0);

// Définit la position du tampon de lumière constant dans le pixel shader.
bufferNumber = 0;

// Fixe finalement le tampon de lumière constant contenant les données à jour dans le pixel shader.
deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

return true;
}

void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
// Définit la disposition des données en entrée du vertex.
deviceContext->IASetInputLayout(m_layout);

// Définit les vertex et pixel shaders qui seront utilisés pour le rendu du triangle.
deviceContext->VSSetShader(m_vertexShader, NULL, 0);
deviceContext->PSSetShader(m_pixelShader, NULL, 0);

// Définit l'état de l'échantillonneur dans le pixel shader.
deviceContext->PSSetSamplers(0, 1, &m_sampleState);

// Effectue le rendu du triangle.
deviceContext->DrawIndexed(indexCount, 0, 0);

return;
}

VIII - Modelclass.h

La classe ModelClass a été légèrement modifiée pour gérer les composantes de lumière.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>

/////////////////////////////
// INCLUSIONS DE LA CLASSE //
/////////////////////////////
#include "textureclass.h"

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:

La structure VertexType possède maintenant un vecteur normal pour l'éclairage.

struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
D3DXVECTOR3 normal; // NOUVEAU
};

public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();

bool Initialize(ID3D11Device*, WCHAR*);
void Shutdown();
void Render(ID3D11DeviceContext*);

int GetIndexCount();
ID3D11ShaderResourceView* GetTexture();

private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);

bool LoadTexture(ID3D11Device*, WCHAR*);
void ReleaseTexture();

private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
TextureClass* m_Texture;
};

#endif

IX - Modelclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
m_Texture = 0;
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename)
{
bool result;

// Initialise les tampons de sommets et d'indices.
result = InitializeBuffers(device);
if(!result)
{
return false;
}

// Charge la texture pour ce modèle.
result = LoadTexture(device, textureFilename);
if(!result)
{
return false;
}

return true;
}

void ModelClass::Shutdown()
{
// Libère la texture du modèle.
ReleaseTexture();

// Libère les tampons de sommets et d'indices.
ShutdownBuffers();

return;
}

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Place les tampons de sommets et d'indices sur le pipeline graphique pour les préparer à être dessinés.
RenderBuffers(deviceContext);

return;
}

int ModelClass::GetIndexCount()
{
return m_indexCount;
}

ID3D11ShaderResourceView* ModelClass::GetTexture()
{
return m_Texture->GetTexture();
}

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;

// Fixe la taille du tableau de sommets.
m_vertexCount = 3;

// Fixe la taille du tableau d'indices.
m_indexCount = 3;

// Crée le tableau de sommets.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}

// Crée le tableau d'indices.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}

Le seul changement au sein de la méthode InitializeBuffers se situe dans la définition du sommet. Chaque sommet a maintenant une normale servant aux calculs de l'éclairage. La normale est une ligne perpendiculaire à la face du polygone, de sorte à pouvoir déterminer l'orientation exacte de la face. Par souci de simplicité, j'ai réglé la normale de chaque sommet le long de l'axe Z avec une composante de Z à -1.0f qui l'oriente vers le spectateur.

// Charge les données dans le tableau de sommets.
vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // En bas à gauche.
vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
vertices[0].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // En haut au milieu.
vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);
vertices[1].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // En bas à droite.
vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
vertices[2].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

// Charge les données dans le tableau d'indices.
indices[0] = 0;  // En bas à gauche.
indices[1] = 1;  // En haut au milieu.
indices[2] = 2;  // En bas à droite.

// Met en place la description du tampon de sommets.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

// Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

// Crée le tampon des sommets.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}

// Met en place la description du tampon statique des indices.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

// Crée le tampon des indices.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}

// Libère les tableaux, maintenant que les tampons ont été créés et remplis.
delete [] vertices;
vertices = 0;

delete [] indices;
indices = 0;

return true;
}

void ModelClass::ShutdownBuffers()
{
// Libère le tampon d'indices.
if(m_indexBuffer)
{
m_indexBuffer->Release();
m_indexBuffer = 0;
}

// Libère le tampon de sommets.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}

return;
}

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;

// Règle le pas et l'offset du tampon.
stride = sizeof(VertexType);
offset = 0;

// Active le tampon des sommets dans l'assembleur d'entrées pour effectuer son rendu.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

// Active le tampon des indices dans l'assembleur d'entrées pour effectuer son rendu.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

// Définit le type de primitive qui doit être rendue depuis ce tampon de sommets, ici un triangle.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

return;
}

bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
bool result;

// Crée l'objet texture.
m_Texture = new TextureClass;
if(!m_Texture)
{
return false;
}

// Initialise l'objet texture.
result = m_Texture->Initialize(device, filename);
if(!result)
{
return false;
}

return true;
}

void ModelClass::ReleaseTexture()
{
// Libère l'objet texture.
if(m_Texture)
{
m_Texture->Shutdown();
delete m_Texture;
m_Texture = 0;
}

return;
}

X - Lightclass.h

Maintenant, nous allons regarder la nouvelle classe LightClass qui reste très simple. Son seul rôle est de maintenir la direction et la couleur des lumières.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3dx10math.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : LightClass
////////////////////////////////////////////////////////////////////////////////
class LightClass
{
public:
LightClass();
LightClass(const LightClass&);
~LightClass();

void SetDiffuseColor(float, float, float, float);
void SetDirection(float, float, float);

D3DXVECTOR4 GetDiffuseColor();
D3DXVECTOR3 GetDirection();

private:
D3DXVECTOR4 m_diffuseColor;
D3DXVECTOR3 m_direction;
};

#endif

XI - Lightclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightclass.h"

LightClass::LightClass()
{
}

LightClass::LightClass(const LightClass& other)
{
}

LightClass::~LightClass()
{
}

void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
return;
}

void LightClass::SetDirection(float x, float y, float z)
{
m_direction = D3DXVECTOR3(x, y, z);
return;
}

D3DXVECTOR4 LightClass::GetDiffuseColor()
{
return m_diffuseColor;
}

D3DXVECTOR3 LightClass::GetDirection()
{
return m_direction;
}

XII - Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

////////////////////////////
// INCLUSIONS DE LA CLASSE//
////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"

La classe GraphicsClass inclut deux nouveaux fichiers d'en-tête des classes LightShaderClass et LightClass.

#include "lightshaderclass.h" // NOUVEAU
#include "lightclass.h" // NOUVEAU

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();

bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();

private:

La méthode Render prend une entrée de type float.

bool Render(float); // NOUVEAU

private:
D3DClass* m_D3D;
CameraClass* m_Camera;
ModelClass* m_Model;

Il y a deux nouveaux attributs privés pour le shader de lumière et l'objet lumière.

LightShaderClass* m_LightShader; // NOUVEAU
LightClass* m_Light; // NOUVEAU
};

#endif

XIII - Graphicsclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
m_D3D = 0;
m_Camera = 0;
m_Model = 0;

Le shader de lumière et l'objet lumière sont initialisés à la valeur nulle dans le constructeur de la classe.

m_LightShader = 0; // NOUVEAU
m_Light = 0; // NOUVEAU
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;

// Crée l'objet Direct3D.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}

// Initialise l'objet Direct3D.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
return false;
}

// Crée l'objet Camera.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}

// Règle la position initiale de la caméra.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

// Crée l'objet Model.
m_Model = new ModelClass;
if(!m_Model)
{
return false;
}

// Initialise l'objet Model.
result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
if(!result)
{
MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
return false;
}

Le nouvel objet shader de lumière est créé et initialisé ici.

// NOUVEAU - crée l'objet shader de lumière.
m_LightShader = new LightShaderClass;
if(!m_LightShader)
{
return false;
}

// NOUVEAU - initialise l'objet shader de lumière
result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
return false;
}

Le nouvel objet lumière est créé ici.

// NOUVEAU - crée l'objet lumière.
m_Light = new LightClass;
if(!m_Light)
{
return false;
}

Nous définissons une couleur de lumière violette, et l'orientons selon l'axe Z positif.

// NOUVEAU - initialise l'objet lumière.
m_Light->SetDiffuseColor(1.0f, 0.0f, 1.0f, 1.0f);
m_Light->SetDirection(0.0f, 0.0f, 1.0f);

return true;
}

void GraphicsClass::Shutdown()
{

La méthode Shutdown libère les deux nouveaux objets.

// NOUVEAU - libère l'objet lumière.
if(m_Light)
{
delete m_Light;
m_Light = 0;
}

// NOUVEAU - libère l'objet shader de lumière.
if(m_LightShader)
{
m_LightShader->Shutdown();
delete m_LightShader;
m_LightShader = 0;
}

// Libère l'objet Model.
if(m_Model)
{
m_Model->Shutdown();
delete m_Model;
m_Model = 0;
}

// Libère l'objet Caméra.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}

// Libère l'objet Direct3D.
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}

return;
}

bool GraphicsClass::Frame()
{
bool result;

Nous ajoutons une nouvelle variable statique afin de conserver l'angle de rotation et le mettre à jour à chaque trame, elle sera passée à la méthode Render.

static float rotation = 0.0f; // NOUVEAU

// NOUVEAU - met à jour l'angle de rotation lors de chaque trame.
rotation += (float)D3DX_PI * 0.01f;
if(rotation > 360.0f)
{
rotation -= 360.0f;
}

// Effectue le rendu de la scène.
result = Render(rotation); // NOUVEAU
if(!result)
{
return false;
}

return true;
}

bool GraphicsClass::Render(float rotation)
{
D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
bool result;

// Efface les tampons pour débuter la scène.
m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

// Génère la matrice de vue à partir de la position de la caméra.
m_Camera->Render();

// Récupère les matrices monde, de vue et de projection depuis les objets Caméra et D3D.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);

Ici, nous appliquons une rotation à la matrice monde de sorte que lorsque nous effectuerons le rendu du triangle à l'aide de cette matrice, il sera tourné par la valeur de l'angle.

// NOUVEAU - applique une rotation à la matrice monde afin de faire tourner le triangle.
D3DXMatrixRotationY(&worldMatrix, rotation);

// Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
m_Model->Render(m_D3D->GetDeviceContext());

Le shader de lumière est appelé ici pour effectuer le rendu du triangle. Le nouvel objet de lumière est utilisé pour envoyer la couleur et la direction de la lumière diffuse dans la méthode Render de sorte que le shader ait accès à ces valeurs.

// NOUVEAU - effectue le rendu du modèle avec le shader de lumière.
result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
if(!result)
{
return false;
}

// Affiche la scène rendue à l'écran.
m_D3D->EndScene();

return true;
}

XIV - Résumé

Avec peu de modifications du code, nous avons pu mettre en œuvre un éclairage directionnel de base. Assurez-vous de comprendre le fonctionnement des vecteurs normaux et leur importance dans le calcul de l'éclairage sur les faces des polygones. Notez que l'arrière du triangle en rotation n'est pas éclairé puisque le back face culling est activé dans notre classe D3DClass.

XV - Exercices à faire

1. Recompilez le projet et assurez-vous d'obtenir un triangle texturé en rotation qui est éclairé par une lumière violette. Appuyez sur Echap pour quitter ;

2. Commentez la ligne color = couleur * textureColor; dans le pixel shader de sorte que la texture shaderTexture ne soit plus utilisée, vous devriez voir l'effet de la lumière sans la texture ;

3. Mettez une couleur de lumière verte à la ligne m_Light->SetDiffuseColor dans le code de la classe GraphicsClass ;

4. Changez la direction de la lumière selon l'axe des X positif et négatif. Vous pouvez aussi modifier la vitesse de rotation.

XVI - Source Code

- Projet Visual Studio 2010 : dx11tut06.zip

- Source seule : dx11src06.zip

- Exécutable seul : dx11exe06.zip

XVII - Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Merci à LittleWhite pour sa relecture lors de la traduction et f-leb pour sa relecture orthographique.

Navigation

Tutoriel précédent : application de textures

Sommaire

Tutoriel suivant : rendu de modèles 3D