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.0
f;
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.0
f;
// 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.0
f, -
1.0
f, 0.0
f); // En bas à gauche.
vertices[0
].texture =
D3DXVECTOR2(0.0
f, 1.0
f);
vertices[0
].normal =
D3DXVECTOR3(0.0
f, 0.0
f, -
1.0
f); // NOUVEAU
vertices[1
].position =
D3DXVECTOR3(0.0
f, 1.0
f, 0.0
f); // En haut au milieu.
vertices[1
].texture =
D3DXVECTOR2(0.5
f, 0.0
f);
vertices[1
].normal =
D3DXVECTOR3(0.0
f, 0.0
f, -
1.0
f); // NOUVEAU
vertices[2
].position =
D3DXVECTOR3(1.0
f, -
1.0
f, 0.0
f); // En bas à droite.
vertices[2
].texture =
D3DXVECTOR2(1.0
f, 1.0
f);
vertices[2
].normal =
D3DXVECTOR3(0.0
f, 0.0
f, -
1.0
f); // 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.0
f;
const
float
SCREEN_NEAR =
0.1
f;
///
/////////////////////////////////////////////////////////////////////////////
// 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.0
f, 0.0
f, -
10.0
f);
// 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.0
f, 0.0
f, 1.0
f, 1.0
f);
m_Light->
SetDirection(0.0
f, 0.0
f, 1.0
f);
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.0
f; // NOUVEAU
// NOUVEAU - met à jour l'angle de rotation lors de chaque trame.
rotation +=
(float
)D3DX_PI *
0.01
f;
if
(rotation >
360.0
f)
{
rotation -=
360.0
f;
}
// 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.0
f, 0.0
f, 0.0
f, 1.0
f);
// 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▲
- 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 ;
- 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 ;
- Mettez une couleur de lumière verte à la ligne m_Light->SetDiffuseColor dans le code de la classe GraphicsClass ;
- 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 |