3. Preparing OBJ Files for Direct3D
In the IntializeDemo() function, the shader and input layout is first created as usual. After that the OBJ file is loaded in an ObjModel
object. A loop then follows that loops through each mesh of the model
and creates a vertex buffer and texture out of it. This vertex buffer
and texture is added to the DX10Mesh list and is what we render later in the rendering function. The InitializeDemo() function is shown in Listing 11, where the order of the function’s execution is as follows.
1. | Load the shader.
| 2. | Create the input layout.
| 3. | Load the OBJ file.
| 4. | Loop through each mesh.
| 5. | Load the mesh’s texture.
| 6. | Create a temp array of DX10Vertex
and fill it with the vertex information (you could use the mesh itself,
but additional information is in it that Direct3D 10 does not need).
| 7. | Create the mesh’s vertex buffer.
| 8. | Delete the temporary memory.
| 9. | Set the view and projection matrices.
|
Listing 11. The InitializeDemo() Function from the OBJ Models Demo
bool InitializeDemo()
{
// Load the shader.
DWORD shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
shaderFlags |= D3D10_SHADER_DEBUG;
#endif
ID3D10Blob *errors = NULL;
HRESULT hr = D3DX10CreateEffectFromFile("TextureMap.fx", NULL,
NULL, "fx_4_0", shaderFlags, 0, g_d3dDevice, NULL, NULL,
&g_shader, &errors, NULL);
if(errors != NULL)
{
MessageBox(NULL, (LPCSTR)errors->GetBufferPointer(),
"Error in Shader!", MB_OK);
errors->Release()
}
if(FAILED(hr))
return false;
g_textureMapTech = g_shader->GetTechniqueByName(
"TextureMapping");
g_worldEffectVar = g_shader->GetVariableByName(
"World")->AsMatrix();
g_viewEffectVar = g_shader->GetVariableByName(
"View")->AsMatrix();
g_projEffectVar = g_shader->GetVariableByName(
"Projection")->AsMatrix();
g_decalEffectVar = g_shader->GetVariableByName(
"decal")->AsShaderResource();
// Create the layout.
D3D10_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
unsigned int numElements = sizeof(layout) / sizeof(layout[0]);
D3D10_PASS_DESC passDesc;
g_textureMapTech->GetPassByIndex(0)->GetDesc(&passDesc);
hr = g_d3dDevice->CreateInputLayout(layout, numElements,
passDesc.pIAInputSignature, passDesc.IAInputSignatureSize,
&g_layout);
if(FAILED(hr))
return false;
// Load the model from the file.
ObjModel model;
if(model.LoadOBJ("box.ob] ") == false)
return false;
g_meshes.reserve(model.GetMeshCount());
D3D10_BUFFER_DESC buffDesc;
D3D10_SUBRESOURCE_DATA resData;
// Loop through and create vertex buffers for each mesh.
for(int m = 0; m < model.GetMeshCount(); m++)
{
DX10Mesh mesh;
g_meshes.push_back(mesh);
// Load the texture.
string textureFile = model.GetMeshTextureFile(m);
hr = D3DX10CreateShaderResourceViewFromFile(g_d3dDevice,
textureFile.c_str(), NULL, NULL,
&g_meshes[m].m_decal, NULL);
if(FAILED(hr))
return false;
g_meshes[m].m_totalVerts = model.GetMeshTotalVerts(m);
DX10Vertex *vertices =
new DX10Vertex[g_meshes[m].m_totalVerts];
float *modelVerts = model.GetMeshVertices(m);
float *modelNorms = model.GetMeshNormals(m);
float *modelTexC = model.GetMeshTexCoords(m);
for(int i = 0; i < g_meshes[m].m_totalVerts;
{
vertices[i].pos.x = *(modelVerts + 0);
vertices[i].pos.y = *(modelVerts + 1);
vertices[i].pos.z = *(modelVerts + 2);
modelVerts += 3;
vertices[i].normal.x = *(modelNorms + 0);
vertices[i].normal.y = *(modelNorms + 1);
vertices[i].normal.z = *(modelNorms + 2);
modelNorms += 3;
vertices[i].tex0.x = *(modelTexC + 0);
vertices[i].tex0.y = *(modelTexC + 1);
modelTexC +=2;
}
// Create the vertex buffer.
buffDesc.Usage = D3D10_USAGE_DEFAULT;
buffDesc.ByteWidth = sizeof(DX10Vertex) *
g_meshes[m].m_totalVerts;
buffDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
buffDesc.CPUAccessFlags = 0;
buffDesc.MiscFlags = 0;
resData.pSysMem = vertices;
hr = g_d3dDevice->CreateBuffer(&buffDesc, &resData,
&g_meshes[m].m_vertices);
if(FAILED(hr))
return false;
delete[] vertices;
}
// Set the shader matrix variables that won't change once here.
D3DXMatrixIdentity(&g_worldMat);
D3DXMatrixIdentity(&g_viewMat);
g_viewEffectVar->SetMatrix((float*)&g_viewMat);
g_pro]EffectVar->SetMatrix((float*)&g_projMat);
return true;
}
|
Rendering OBJ Models
The
last piece of this demo deals with the rendering and shutdown
functions. In this demo the model is rotating. This is done by creating
the world matrix for the model that rotates a little bit along the X and
Y axis each frame. This is done in the Update() function, which is called after the rendering to slightly update the world matrix. The Update() function is shown in Listing 12.
Listing 12. The Update() Function from the OBJ Models Demo
void Update()
{
g_xRot += 0.0001f;
g_yRot += 0.0002f;
if(g_xRot < 0) g_xRot = 359;
else if(g_xRot >= 360) g_xRot = 0;
if(g_yRot < 0) g_yRot = 359;
else if(g_yRot >= 360) g_yRot = 0;
D3DXMATRIX trans, rotX, rotY;
D3DXMatrixRotationX(&rotX, g_xRot);
D3DXMatrixRotationY(&rotY, g_yRot);
D3DXMatrixTranslation(&trans, 0, 0, 6);
g_worldMat = (rotX * rotY) * trans;
}
|
The
rendering function is pretty much the same as in previous demos, with
the exception that there is now a loop that loops through and sets the
vertex buffer and texture for each mesh in the mesh list. Each mesh is
rendered out one at a time in this manner. The Shutdown()
function is also the same as in previous demos, with the exception of a
loop being used to release the vertex buffers and textures that were
loaded in the InitializeDemo() function. The RenderScene() and Shutdown() functions from the OBJ Models demo are shown in Listing 13.
Listing 13. The RenderScene() and Shutdown() Functions
void RenderScene()
{
float col[4] = { 0, 0, 0, 1 };
g_d3dDevice->ClearRenderTargetView(g_renderTargetView, col);
g_d3dDevice->ClearDepthStencilView(g_depthStencilView,
D3D10_CLEAR_DEPTH, 1.0f, 0);
g_d3dDevice->IASetInputLayout(g_layout);
g_d3dDevice->IASetPrimitiveTopology(
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
D3D10_TECHNIQUE_DESC techDesc;
g_textureMapTech->GetDesc(&techDesc);
unsigned int stride = sizeof(DX10Vertex);
unsigned int offset = 0;
for(int m = 0; m < (int)g_meshes.size(); m++)
{
g_worldEffectVar->SetMatrix((float*)&g_worldMat);
g_decalEffectVar->SetResource(g_meshes[m].m_decal);
g_d3dDevice->IASetVertexBuffers(0, 1,
&g_meshes[m].m_vertices, &stride, &offset);
for(unsigned int i = 0; i < techDesc.Passes; i++)
{
g_textureMapTech->GetPassByIndex(i)->Apply(0);
g_d3dDevice->Draw(g_meshes[m].m_totalVerts, 0);
}
}
g_swapChain->Present(0, 0);
Update();
}
void Shutdown()
{
if(g_d3dDevice) g_d3dDevice->ClearState();
if(g_swapChain) g_swapChain->Release()
if(g_renderTargetView) g_renderTargetView->Release()
if(g_depthStencilTex) g_depthStencilTex->Release()
if(g_depthStencilView) g_depthStencilView->Release()
if(g_shader) g_shader->Release()
if(g_layout) g_layout->Release()
for(int m = 0; m < (int)g_meshes.size(); m++)
{
if(g_meshes[m].m_vertices)
g_meshes[m].m_vertices->Release()
if(g_meshes[m].m_decal)
{
ID3D10Resource *pRes;
g_meshes[m].m_decal->GetResource(&pRes);
pRes->Release()
g_meshes[m].m_decal->Release()
}
}
if(g_d3dDevice) g_d3dDevice->Release()
}
|
To conclude this demo we must take a look at the shader’s file. Because we are rendering a model with volume,
we must set the depth buffer state so that the geometry renders
correctly. Without it we must render the triangles from back to front
order to ensure that triangles behind other triangles are not rendered
on top, but with the depth buffer the hardware ensures that primitives
are drawn correctly.
Depth testing is a technique used to avoid having
to render primitives in a specific order. In Direct3D and other APIs, a
depth buffer is an actual buffer in the graphics card memory, similar
to the color buffer that is the rendered image that is written to every
time an object is rendered. The depth buffer stores depth values on the
pixel level, which are the projected distances between the surface and
the camera. Thus, when new primitives are rendered, Direct3D can look at
the depth buffer (depth testing) and determine if the new primitive is
in front of or behind one that was already rendered. If it is in front
of the old primitive, then the new primitive is rendered; if it is not,
the rendered scene is not affected.
The OBJ Models demo’s HLSL effect file is shown in Listing 14. The DepthStencilState is set in the technique by calling the HLSL function SetDepthStencilState(). A screenshot of the OBJ Models demo is shown in Figure 1.
Listing 14. The OBJ Models Demo’s HLSL Effect Shader
/*
Texture Mapping HLSL Shader
Ultimate Game Programming with DirectX 2nd Edition
Created by Allen Sherrod
*/
Texture2D decal;
SamplerState DecalSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
ddressV = Wrap;
};
DepthStencilState DepthStencilInfo
{
DepthEnable = true;
DepthWriteMask = ALL;
DepthFunc = Less;
// Set up stencil states
StencilEnable = true;
StencilReadMask = 0xFF;
StencilWriteMask = 0×00;
FrontFaceStencilFunc = Not_Equal;
FrontFaceStencilPass = Keep;
FrontFaceStencilFail = Zero;
BackFaceStencilFunc = Not_Equal;
BackFaceStencilPass = Keep;
BackFaceStencilFail = Zero;
};
cbuffer cbChangesEveryFrame
{
matrix World;
matrix View;
};
cbuffer cbChangeOnResize
{
matrix Projection;
};
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Norm : NORMAL;
float2 Tex : TEXCOORD;
};
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORDO;
};
PS_INPUT TextureMapVS(VS_INPUT input)
{
PS_INPUT output = (PS_INPUT)0;
float4 Pos = mul(input.Pos, World);
Pos = mul(Pos, View);
output.Pos = mul(Pos, Projection);
output.Tex = input.Tex;
return output;
}
float4 TextureMapPS(PS_INPUT input) : SV_Target
{
return decal.Sample(DecalSampler, input.Tex);
}
techniquel0 TextureMapping
{
pass P0
{
SetDepthStencilState(DepthStencilInfo, 0);
SetVertexShader(CompileShader(vs_4_0, TextureMapVS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_4_0, TextureMapPS()));
}
}
|
|