Navigation▲
Tutoriel précédent : tampons, shaders et HLSL | Sommaire | Tutoriel suivant : lumière diffuse |
II. Introduction▲
Dans ce tutoriel, nous prendrons pour exemple cette image :
Que nous appliquerons au polygone du tutoriel précédent afin d'obtenir ceci :
Nous utiliserons le format DDS pour nos textures. C'est le format Direct Draw Surface que DirectX utilise. L'outil utilisé pour créer des fichiers .dds est livré avec le SDK de DirectX. L'outil se nomme DirectX Texture Tool, il est situé dans DirectX Utilities. Vous pouvez créer des textures de toutes tailles et tous formats, puis couper et coller votre image vers un autre format de texture et l'enregistrer comme un fichier .dds. Il est très simple à utiliser.
Avant de rentrer dans le code, nous devrions voir comment l'application de texture fonctionne. Pour lier des pixels de l'image dds au polygone, nous utilisons ce qu'on appelle le « système de coordonnées Texel ». Ce système convertit la valeur entière du pixel vers une valeur flottante entre 0.0f et 1.0f. Par exemple, pour une texture de 256 pixels de largeur, le premier pixel est à 0.0f, le 256e pixel à 1.0f, et le 128e pixel (au centre) à 0.5f.
Dans le système de coordonnées Texel, la valeur selon la largeur est nommée « U » et la valeur selon la hauteur est nommée « V ». La largeur va de 0.0 (à gauche) à 1.0 (à droite). La hauteur va de 0.0 (en haut) à 1.0 (en bas). Par exemple, le coin supérieur gauche sera noté U 0.0, V 0.0 et le coin inférieur droit sera noté U 1.0, V 1.0. Le schéma ci-dessous illustre ce système :
Maintenant que nous avons les bases de l'application de textures sur les polygones, nous pouvons regarder notre cadre de travail actualisé pour ce tutoriel :
Les modifications apportées depuis le tutoriel précédent concernent la nouvelle classe TextureClass au sein de la classe ModelClass, et TextureShaderClass qui remplace ColorShaderClass. Nous commencerons par regarder le code des nouveaux shaders de texture HLSL.
III. Texture.vs▲
Les vertex shaders sont similaires aux précédents, exceptées les parties intégrant l'application de texture.
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : texture.vs
////////////////////////////////////////////////////////////////////////////////
////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
matrix
worldMatrix;
matrix
viewMatrix;
matrix
projectionMatrix;
}
;
Dans notre type, nous utilisons des coordonnées de texture à la place de la couleur. Puisque les coordonnées de texture nécessitent deux flottants U et V, nous utilisons le type float2. Pour les vertex et pixel shaders, la sémantique utilisée pour les coordonnées de texture est TEXCOORD0. Vous pouvez mettre n'importe quel nombre à la place de ce zéro final afin d'indiquer avec quel ensemble de coordonnées vous travaillez, étant donné que vous êtes autorisé à travailler avec plusieurs coordonnées de texture.
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
float4
position : POSITION
;
float2
tex : TEXCOORD0
;
}
;
struct PixelInputType
{
float4
position : SV_POSITION;
float2
tex : TEXCOORD0
;
}
;
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType TextureVertexShader
(
VertexInputType input)
{
PixelInputType output;
// Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.
input.position.w =
1
.0f
;
// 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);
La seule différence dans le vertex shader de texture par rapport au vertex shader de couleur du tutoriel précédent est que, au lieu de faire une copie de la couleur du sommet en entrée, nous prenons les coordonnées de la texture et les transmettons au pixel shader.
// Stocke les coordonnées de la texture pour le pixel shader.
output.tex =
input.tex;
return
output;
}
IV. Texture.ps▲
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : texture.ps
////////////////////////////////////////////////////////////////////////////////
Le pixel shader de texture a deux variables globales. La première Texture2D shaderTexture est la ressource de texture qui sera utilisée pour le rendu de la texture sur le modèle. La deuxième SamplerState SampleType, l'état de l'échantillonneur, nous permet de modifier la façon dont les pixels sont écrits dans la face du polygone lorsqu'elle est réduite. Par exemple, si le polygone est vraiment très loin et ne représente que 8 pixels sur l'écran, nous utilisons l'état de l'échantillonneur pour déterminer quels pixels ou quelle combinaison de pixels de la texture originale seront effectivement dessinés. La texture originale peut faire 256x256 pixels, donc décider quels pixels seront dessinés est vraiment important afin de veiller à ce que la texture ressemble toujours à quelque chose, même sur de très petites faces. Nous définirons aussi l'état de l'échantillonneur dans la classe TextureShaderClass et l'attacherons au pointeur de ressources afin que ce pixel shader puisse l'utiliser pour déterminer quels pixels seront dessinés.
////////////////////////
// VARIABLES GLOBALES //
////////////////////////
Texture2D shaderTexture;
SamplerState SampleType;
Le type PixelInputType du pixel shader de texture est également modifié pour mettre les coordonnées de texture à la place des valeurs de couleur.
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
float4
position : SV_POSITION;
float2
tex : TEXCOORD0
;
}
;
Le pixel shader a été modifié de sorte qu'il utilise dorénavant le code de la fonction d'échantillonnage HLSL. Cette fonction utilise l'état de l'échantillonneur défini ci-dessus et les coordonnées de texture du pixel afin de déterminer et retourner la valeur du pixel correspondant aux coordonnées UV sur la face du polygone.
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4
TexturePixelShader
(
PixelInputType input) : SV_TARGET
{
float4
textureColor;
// É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);
return
textureColor;
}
V. Textureclass.h▲
La classe TextureClass encapsule le chargement, le déchargement, et l'accès à une ressource de texture unique. Un objet de cette classe doit être instancié chaque fois que nous avons besoin d'une texture.
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureclass.h
///
/////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_
///
/////////////
// INCLUSIONS //
///
/////////////
#include
<d3d11.h>
#include
<d3dx11tex.h>
///
/////////////////////////////////////////////////////////////////////////////
// Nom de la classe : TextureClass
///
/////////////////////////////////////////////////////////////////////////////
class
TextureClass
{
public
:
TextureClass();
TextureClass(const
TextureClass&
);
~
TextureClass();
Les deux premières méthodes chargeront une texture à partir du nom d'un fichier donné et la déchargeront quand elle ne sera plus nécessaire.
bool
Initialize(ID3D11Device*
, WCHAR*
);
void
Shutdown();
La méthode GetTexture renvoie un pointeur vers la texture de sorte qu'elle puisse être utilisée pour le rendu par les shaders.
ID3D11ShaderResourceView*
GetTexture();
private
:
// C'est la ressource de texture privée.
ID3D11ShaderResourceView*
m_texture;
}
;
#endif
VI. Textureclass.cpp▲
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureclass.cpp
///
/////////////////////////////////////////////////////////////////////////////
#include
"textureclass.h"
Le constructeur de la classe initialise à zéro le pointeur de la texture du shader.
TextureClass::
TextureClass()
{
m_texture =
0
;
}
TextureClass::
TextureClass(const
TextureClass&
other)
{
}
TextureClass::
~
TextureClass()
{
}
La méthode Initialize prend en paramètre le dispositif Direct3D et le nom du fichier de la texture. Elle charge le fichier dans la variable ressource du shader nommée m_texture. La texture peut maintenant être utilisée pour effectuer le rendu.
bool
TextureClass::
Initialize(ID3D11Device*
device, WCHAR*
filename)
{
HRESULT result;
// Charge la texture.
result =
D3DX11CreateShaderResourceViewFromFile(device, filename, NULL
, NULL
, &
m_texture, NULL
);
if
(FAILED(result))
{
return
false
;
}
return
true
;
}
La méthode Shutdown libère la ressource de la texture si elle a été chargée et met le pointeur à la valeur nulle.
void
TextureClass::
Shutdown()
{
// Libère la ressource de la texture.
if
(m_texture)
{
m_texture->
Release();
m_texture =
0
;
}
return
;
}
La méthode GetTexture est appelée par d'autres objets qui ont besoin d'accéder à la ressource de la texture du shader afin qu'ils puissent l'utiliser pour le rendu.
ID3D11ShaderResourceView*
TextureClass::
GetTexture()
{
return
m_texture;
}
VII. Modelclass.h▲
La classe ModelClass a changé de façon à permettre l'application de texture.
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
///
/////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
///
///////////
// INCLUDES //
///
///////////
#include
<d3d11.h>
#include
<d3dx10math.h>
L'en-tête de la classe TextureClass est maintenant inclus.
///
//////////////////////////
// INCLUSIONS DE MA CLASSE //
///
//////////////////////////
#include
"textureclass.h"
// NOUVEAU
///
/////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
///
/////////////////////////////////////////////////////////////////////////////
class
ModelClass
{
private
:
Le type VertexType remplace la composante couleur (verte, utilisée dans le tutoriel précédent) par les coordonnées de texture.
struct
VertexType // MODIFIÉ
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
}
;
public
:
ModelClass();
ModelClass(const
ModelClass&
);
~
ModelClass();
bool
Initialize(ID3D11Device*
, WCHAR*
);
void
Shutdown();
void
Render(ID3D11DeviceContext*
);
int
GetIndexCount();
La classe ModelClass a également une méthode GetTexture afin qu'elle puisse passer sa propre ressource de texture de shaders qui dessinera le modèle.
ID3D11ShaderResourceView*
GetTexture();
private
:
bool
InitializeBuffers(ID3D11Device*
);
void
ShutdownBuffers();
void
RenderBuffers(ID3D11DeviceContext*
);
La classe ModelClass possède deux méthodes privées LoadTexture et ReleaseTexture pour le chargement et la libération de la texture qui sera utilisée pour le rendu de ce modèle.
bool
LoadTexture(ID3D11Device*
, WCHAR*
); // NOUVEAU
void
ReleaseTexture(); // NOUVEAU
private
:
ID3D11Buffer *
m_vertexBuffer, *
m_indexBuffer;
int
m_vertexCount, m_indexCount;
La variable m_Texture est utilisée pour le chargement, la libération, et l'accès à la texture du modèle.
TextureClass*
m_Texture; // NOUVEAU
}
;
#endif
VIII. Modelclass.cpp▲
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
///
/////////////////////////////////////////////////////////////////////////////
#include
"modelclass.h"
ModelClass::
ModelClass()
{
m_vertexBuffer =
0
;
m_indexBuffer =
0
;
Le constructeur de la classe initialise maintenant à la valeur nulle le nouvel objet texture.
m_Texture =
0
; // NOUVEAU
}
ModelClass::
ModelClass(const
ModelClass&
other)
{
}
ModelClass::
~
ModelClass()
{
}
La méthode Initialize prend maintenant en entrée le nom du fichier .dds de la texture que le modèle utilisera
bool
ModelClass::
Initialize(ID3D11Device*
device, WCHAR*
textureFilename) // MODIFIÉ
{
bool
result;
// Initialise les tampons de sommets et d'indices qui contiennent la géométrie du triangle.
result =
InitializeBuffers(device);
if
(!
result)
{
return
false
;
}
… et appelle maintenant une nouvelle fonction privée qui va charger la texture.
// NOUVEAU - charge la texture pour ce modèle.
result =
LoadTexture(device, textureFilename);
if
(!
result)
{
return
false
;
}
return
true
;
}
void
ModelClass::
Shutdown()
{
La fonction Shutdown appelle maintenant la nouvelle fonction privée pour libérer l'objet texture chargé lors de l'initialisation.
// // NOUVEAU - 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;
}
La méthode GetTexture retourne la texture du modèle. Le shader de texture aura besoin d'y accéder pour effectuer le rendu du modèle.
ID3D11ShaderResourceView*
ModelClass::
GetTexture() // NOUVEAU
{
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 tableau des sommets a maintenant une composante de texture à la place de la composante de couleur. Le vecteur texture a toujours U en premier et V en second. Par exemple, la première coordonnée de texture est celle en bas à gauche du triangle correspondant à U 0.0, V 1.0. Référez-vous à pour savoir quelles coordonnées utiliser. Notez que vous pouvez modifier ces coordonnées pour lier n'importe quelle partie de la texture à n'importe quelle partie de la face du polygone. Dans ce tutoriel, je fais juste une correspondance directe pour des raisons de simplicité.
// 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); // MODIFIÉ
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); // MODIFIÉ
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); // MODIFIÉ
// 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;
// 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
;
}
LoadTexture est une nouvelle méthode privée qui va créer l'objet texture et l'initialiser avec le nom du fichier d'entrée fourni. Cette fonction est appelée lors de l'initialisation.
bool
ModelClass::
LoadTexture(ID3D11Device*
device, WCHAR*
filename) // NOUVEAU
{
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
;
}
La méthode ReleaseTexture libérera l'objet texture créé et chargé dans la méthode LoadTexture.
void
ModelClass::
ReleaseTexture() // NOUVEAU
{
// Libère l'objet texture.
if
(m_Texture)
{
m_Texture->
Shutdown();
delete
m_Texture;
m_Texture =
0
;
}
return
;
}
IX. Textureshaderclass.h▲
La classe TextureShaderClass est juste une version mise à jour de ColorShaderClass du tutoriel précédent. Cette classe sera utilisée pour dessiner les modèles 3D en utilisant les vertex et pixel shaders.
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureshaderclass.h
///
/////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_
///
/////////////
// INCLUSIONS //
///
/////////////
#include
<d3d11.h>
#include
<d3dx10math.h>
#include
<d3dx11async.h>
#include
<fstream>
using
namespace
std;
///
/////////////////////////////////////////////////////////////////////////////
// Nom de la classe : TextureShaderClass
///
/////////////////////////////////////////////////////////////////////////////
class
TextureShaderClass
{
private
:
struct
MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
}
;
public
:
TextureShaderClass();
TextureShaderClass(const
TextureShaderClass&
);
~
TextureShaderClass();
bool
Initialize(ID3D11Device*
, HWND);
void
Shutdown();
bool
Render(ID3D11DeviceContext*
, int
, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*
);
private
:
bool
InitializeShader(ID3D11Device*
, HWND, WCHAR*
, WCHAR*
);
void
ShutdownShader();
void
OutputShaderErrorMessage(ID3D10Blob*
, HWND, WCHAR*
);
bool
SetShaderParameters(ID3D11DeviceContext*
, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*
);
void
RenderShader(ID3D11DeviceContext*
, int
);
private
:
ID3D11VertexShader*
m_vertexShader;
ID3D11PixelShader*
m_pixelShader;
ID3D11InputLayout*
m_layout;
ID3D11Buffer*
m_matrixBuffer;
Une nouvelle variable privée est introduite : le pointeur vers l'état de l'échantillonneur. Ce pointeur sera utilisé pour l'interface avec le shader de texture.
ID3D11SamplerState*
m_sampleState;
}
;
#endif
X. Textureshaderclass.cpp▲
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureshaderclass.cpp
///
/////////////////////////////////////////////////////////////////////////////
#include
"textureshaderclass.h"
TextureShaderClass::
TextureShaderClass()
{
m_vertexShader =
0
;
m_pixelShader =
0
;
m_layout =
0
;
m_matrixBuffer =
0
;
Le nouveau pointeur sur l'état de l'échantillonneur est mis à la valeur nulle dans le constructeur de la classe.
m_sampleState =
0
;
}
TextureShaderClass::
TextureShaderClass(const
TextureShaderClass&
other)
{
}
TextureShaderClass::
~
TextureShaderClass()
{
}
bool
TextureShaderClass::
Initialize(ID3D11Device*
device, HWND hwnd)
{
bool
result;
Les nouveaux fichiers HLSL texture.vs et texture.ps sont chargés pour ce shader.
// Initialise les vertex et pixel shaders.
result =
InitializeShader(device, hwnd, L"../Engine/texture.vs"
, L"../Engine/texture.ps"
);
if
(!
result)
{
return
false
;
}
return
true
;
}
La méthode Shutdown appelle la fonction de libération des variables shaders.
void
TextureShaderClass::
Shutdown()
{
// Ferme les vertex et pixel shaders ainsi que les objets associés.
ShutdownShader();
return
;
}
La méthode Render prend désormais un nouveau paramètre nommé texture représentant le pointeur vers la ressource de texture. Celui-ci est envoyé dans la méthode SetShaderParameters afin qu'elle soit définie dans le shader et utilisée pour le rendu.
bool
TextureShaderClass::
Render(ID3D11DeviceContext*
deviceContext, int
indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView*
texture)
{
bool
result;
// Définit les paramètres du shader qui seront utilisés pour le rendu.
result =
SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture);
if
(!
result)
{
return
false
;
}
// Effectue le rendu des tampons avec le shader.
RenderShader(deviceContext, indexCount);
return
true
;
}
La méthode InitializeShader met en place le shader de texture.
bool
TextureShaderClass::
InitializeShader(ID3D11Device*
device, HWND hwnd, WCHAR*
vsFilename, WCHAR*
psFilename)
{
HRESULT result;
ID3D10Blob*
errorMessage;
ID3D10Blob*
vertexShaderBuffer;
ID3D10Blob*
pixelShaderBuffer;
D3D11_INPUT_ELEMENT_DESC polygonLayout[2
];
unsigned
int
numElements;
D3D11_BUFFER_DESC matrixBufferDesc;
Nous avons une nouvelle variable qui contient la description de l'échantillonneur de la texture et qui sera configurée dans cette méthode.
D3D11_SAMPLER_DESC samplerDesc;
// Initialise à zéro les pointeurs que la méthode va utiliser.
errorMessage =
0
;
vertexShaderBuffer =
0
;
pixelShaderBuffer =
0
;
Charge les nouveaux vertex et pixel shaders de texture.
// Compile le code du vertex shader.
result =
D3DX11CompileFromFile(vsFilename, NULL
, NULL
, "TextureVertexShader"
, "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
;
}
// Compile le code du pixel shader.
result =
D3DX11CompileFromFile(psFilename, NULL
, NULL
, "TexturePixelShader"
, "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
;
}
La disposition d'entrée a changé, nous avons maintenant un élément de texture à la place de la couleur. Le premier élément de la position reste inchangé, mais les champs SemanticName et Format du second élément ont été modifiés en TexCoord et DXGI_FORMAT_R32G32_FLOAT. Ces deux modifications sont alors en accord avec la disposition de notre nouveau VertexType à la fois dans la définition de la classe ModelClass et dans les alias des fichiers de shader.
// 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
;
// 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
;
// Met en place la description du tampon constant de matrice dynamique 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
;
}
La description de l'état de l'échantillonneur de la texture est configurée ici et peut ensuite être transmise au pixel shader. L'élément le plus important est le champ Filtre qui détermine la méthode utilisée pour garder et combiner les pixels servant à créer l'apparence finale de la texture sur la face du polygone. Dans cet exemple, j'utilise D3D11_FILTER_MIN_MAG_MIP_LINEAR qui est plus coûteux en termes de traitement, mais donne le meilleur résultat visuel. Il demande à l'échantillonneur d'effectuer une interpolation linéaire pour le rétrécissement, le grossissement et l'échantillonnage de MIP maps.
Les champs AddressU et AddressV sont mis à Wrap, ce qui garantit que les coordonnées restent entre 0.0f et 1.0f. Celles qui se trouvent en dehors sont ramenées dans cet intervalle. Tous les autres paramètres sont définis par défaut.
// 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
;
}
return
true
;
}
La méthode ShutdownShader libère toutes les variables utilisées dans la classe.
void
TextureShaderClass::
ShutdownShader()
{
Elle libère maintenant le nouvel état de l'échantillonneur créé lors de l'initialisation.
// Libère l'état de l'échantillonneur.
if
(m_sampleState)
{
m_sampleState->
Release();
m_sampleState =
0
;
}
// Libère le tampon de matrice constant.
if
(m_matrixBuffer)
{
m_matrixBuffer->
Release();
m_matrixBuffer =
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
;
}
La méthode OutputShaderErrorMessage écrit les erreurs dans un fichier texte si le shader HLSL n'a pas pu être chargé.
void
TextureShaderClass::
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 un pointeur vers une ressource de texture pour le transmettre au shader. Notez que la texture doit être fixée avant le rendu du tampon.
bool
TextureShaderClass::
SetShaderParameters(ID3D11DeviceContext*
deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView*
texture)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType*
dataPtr;
unsigned
int
bufferNumber;
// 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 puisse é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 la matrice 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);
La méthode SetShaderParameters a été modifiée pour intégrer la texture dans le pixel shader.
// Définit la texture dans le pixel shader.
deviceContext->
PSSetShaderResources(0
, 1
, &
texture);
return
true
;
}
La méthode RenderShader appelle le shader pour effectuer le rendu des polygones.
void
TextureShaderClass::
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
);
La méthode RenderShader intègre maintenant l'état de l'échantillonneur dans le pixel shader avant le rendu.
// 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
;
}
XI. 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 comprend maintenant le fichier d'en-tête de la classe TextureShaderClass tandis que celui de la classe ColorShaderClass a été supprimé.
#include
"textureshaderclass.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
:
bool
Render();
private
:
D3DClass*
m_D3D;
CameraClass*
m_Camera;
ModelClass*
m_Model;
Nous avons ajouté un nouvel objet TextureShaderClassprivé.
A new TextureShaderClass private object has been added.
TextureShaderClass*
m_TextureShader; // NOUVEAU
}
;
#endif
XII. Graphicsclass.cpp▲
///
/////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
///
/////////////////////////////////////////////////////////////////////////////
#include
"graphicsclass.h"
La variable m_TextureShader est définie à la valeur nulle dans le constructeur.
GraphicsClass::
GraphicsClass()
{
m_D3D =
0
;
m_Camera =
0
;
m_Model =
0
;
m_TextureShader =
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
;
}
// Crée l'objet Model.
m_Model =
new
ModelClass;
if
(!
m_Model)
{
return
false
;
}
La méthode ModelClass::initialize prend désormais le nom de la texture utilisée pour le rendu du modèle.
// Initialise l'objet Model.
result =
m_Model->
Initialize(m_D3D->
GetDevice(), L"../Engine/data/seafloor.dds"
); // MODIFIÉ
if
(!
result)
{
MessageBox(hwnd, L"Could not initialize the model object."
, L"Error"
, MB_OK);
return
false
;
}
Le nouvel objet TextureShaderClass est créé et initialisé.
// NOUVEAU - crée l'objet shader de texture.
m_TextureShader =
new
TextureShaderClass;
if
(!
m_TextureShader)
{
return
false
;
}
// NOUVEAU - initialise l'objet shader de texture.
result =
m_TextureShader->
Initialize(m_D3D->
GetDevice(), hwnd);
if
(!
result)
{
MessageBox(hwnd, L"Could not initialize the texture shader object."
, L"Error"
, MB_OK);
return
false
;
}
return
true
;
}
void
GraphicsClass::
Shutdown()
{
L'objet TextureShaderClass est également libéré dans la méthode Shutdown.
// NOUVEAU - libère l'objet shader de texture.
if
(m_TextureShader)
{
m_TextureShader->
Shutdown();
delete
m_TextureShader;
m_TextureShader =
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;
// Effectue le rendu de la scène.
result =
Render();
if
(!
result)
{
return
false
;
}
return
true
;
}
bool
GraphicsClass::
Render()
{
D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
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->
GetProjectionMatrix(projectionMatrix);
m_D3D->
GetWorldMatrix(worldMatrix);
// 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->
GetDevice());
Le shader de texture est maintenant appelé à la place du shader de couleur pour effectuer le rendu du modèle. Notez qu'il prend aussi le pointeur de la ressource de texture du modèle de sorte que le shader texture y ait accès.
// NOUVEAU - effectue le rendu du modèle avec le shader de texture.
result =
m_TextureShader->
Render(m_D3D->
GetDeviceContext(), m_Model->
GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
m_Model->
GetTexture());
if
(!
result)
{
return
false
;
}
// Affiche la scène rendue à l'écran.
m_D3D->
EndScene();
return
true
;
}
XIII. Résumé▲
Vous devriez maintenant savoir charger une texture, la lier à un polygone, et effectuer son rendu avec un shader.
XIV. Exercices à faire▲
- Recompilez le code et assurez-vous que la texture liée au triangle apparaisse à l'écran. Ceci fait, pressez la touche « Echap » pour quitter le programme ;
- Créez votre propre fichier .dds et placez-le dans le même dossier que le fichier seafloor.dds. Dans la méthode GraphicsClass::Initialize, changez le nom du modèle pour utiliser votre texture, puis recompilez et exécutez de nouveau votre programme ;
- Modifiez le code afin de créer deux triangles formant un carré. Liez la texture entière à ce carré de sorte qu'elle s'affiche correctement ;
- Faites varier la distance avec la caméra afin d'observer l'effet du filtre MIN_MAG_MIP_LINEAR ;
- Essayez d'autres filtres et bougez la caméra afin de voir les différents résultats.
XV. Code source▲
- Projet Visual Studio 2010 : dx11tut05.zip
- Source seule : dx11src05.zip
- Exécutable seul : dx11exe05.zip
XVI. Remerciements▲
Cet article est une traduction autorisée de l'article paru sur RasterTek.
Navigation▲
Tutoriel précédent : tampons, shaders et HLSL | Sommaire | Tutoriel suivant : lumière diffuse |