Skip to content

Rendering a model

Chuck Walbourn edited this page Aug 8, 2016 · 39 revisions

This lesson loads and draws models in 3D.

Setup

First create a new project using the instructions from the first two lessons: The basic game loop and Adding the DirectX Tool Kit which we will use for this lesson.

Creating a model

Source assets for models are often stored in Autodesk FBX, Wavefront OBJ, or similar formats. A build process is used to convert them to a more run-time friendly format that is easier to load and render.

For this tutorial, we will make of use of the DirectXMesh meshconvert command-line tool. Start by saving cup._obj, cup.mtl, and cup.jpg into your new project's directory, and then from the top menu select Project / Add Existing Item.... Select "cup.jpg" and click "OK".

  1. Download the Meshconvert.exe from the DirectXMesh site save the EXE into your project's folder.
  2. Open a command-prompt and then change to your project's folder.

Run the following command-line

meshconvert cup._obj -sdkmesh -nodds -y -flipu

Then from the top menu in Visual Studio select Project / Add Existing Item.... Select cup.sdkmesh and click "OK".

Technical notes

  • The switch -sdkmesh selects the output type. The meshconvert command-line tool also supports -cmo and -vbo. See Geometry formats for more information.
  • The switch -nods causes any texture file name references in the material information of the source file to stay in their original file format (such as .png or .jpg). Otherwise, it assumes you will be converting all the needed texture files to a .dds instead.
  • The -flipu flips the direction of the texture coordinates. Since SimpleMath and these tutorials assume we are using right-handed viewing coordinates and the model was created using left-handed viewing coordinates we have to flip them to get the text in the texture to appear correctly.
  • The switch -y indicates that it is ok to overwrite the output file in case you run it multiple times.

DirectX Tool Kit for DirectX 12 does not support .cmo models.

Drawing a model

In the Game.h file, add the following variables to the bottom of the Game class's private declarations (right after where you added m_graphicsMemory as part of setup):

DirectX::SimpleMath::Matrix m_world;
DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;

std::unique_ptr<DirectX::CommonStates> m_states;
std::unique_ptr<DirectX::EffectFactory> m_fxFactory;
std::unique_ptr<DirectX::EffectTextureFactory> m_modelResources; 
std::unique_ptr<DirectX::Model> m_model;
std::vector<std::shared_ptr<DirectX::IEffect>> m_modelNormal;

In Game.cpp, add to the TODO of CreateDevice right after you create m_graphicsMemory:

m_states = std::make_unique<CommonStates>(m_d3dDevice.Get());

m_model = Model::CreateFromSDKMESH(L"cup.sdkmesh");

ResourceUploadBatch resourceUpload(m_d3dDevice.Get());

resourceUpload.Begin();

m_modelResources = m_model->LoadTextures(m_d3dDevice.Get(), resourceUpload);

m_fxFactory = std::make_unique<EffectFactory>(m_modelResources->Heap(), m_states->Heap());

auto uploadResourcesFinished = resourceUpload.End(m_commandQueue.Get());

WaitForGpu();

uploadResourcesFinished.wait();

RenderTargetState rtState(DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_D32_FLOAT);

EffectPipelineStateDescription pd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullClockwise,
    rtState);

EffectPipelineStateDescription pdAlpha(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthDefault,
    CommonStates::CullClockwise,
    rtState);

m_modelNormal = m_model->CreateEffects(*m_fxFactory, pd, pdAlpha);

m_world = Matrix::Identity;

In Game.cpp, add to the TODO of CreateResources:

m_view = Matrix::CreateLookAt(Vector3(2.f, 2.f, 2.f),
    Vector3::Zero, Vector3::UnitY);
m_proj = Matrix::CreatePerspectiveFieldOfView(XM_PI / 4.f,
    float(backBufferWidth) / float(backBufferHeight), 0.1f, 10.f);

In Game.cpp, add to the TODO of OnDeviceLost:

m_states.reset();
m_fxFactory.reset();
m_modelResources.reset();
m_model.reset();
m_modelNormal.clear();

In Game.cpp, add to the TODO of Render:

ID3D12DescriptorHeap* heaps[] = { m_modelResources->Heap(), m_states->Heap() };
m_commandList->SetDescriptorHeaps(_countof(heaps), heaps);

Model::UpdateEffectMatrices(m_modelNormal, m_world, m_view, m_proj);

m_model->Draw(m_commandList.Get(), m_modelNormal.cbegin());

In Game.cpp, add to the TODO of Update:

float time = float(timer.GetTotalSeconds());

m_world = Matrix::CreateRotationZ(cosf(time) * 2.f);

Build and run and you will see our cup model rendered with default lighting:

Screenshot of cup model

Troubleshooting: If you get a runtime exception, then you may have the "cup.jpg" or "cup.sdkmesh" in the wrong folder, have modified the "Working Directory" in the "Debugging" configuration settings, or otherwise changed the expected paths at runtime of the application. You should set a break-point on Model::CreateFromSDKMESH and step into the code to find the exact problem.

Updating effects settings in a model

The Model class creates effects for the loaded materials which are set to default lighting parameters. Because the effects system is flexible, we must first enable C++ Run-Time Type Information (RTTI) in order to safely discover the various interfaces supported at runtime. From the drop-down menu, select Project / Properties. Set to "All Configurations" / "All Platforms". On the left-hand tree view select C/C++ / Language. Then set "Enable Run-Time Type Information" to "Yes". Click "OK".

Screenshot C++ RTTI settings

In the Game.h file, add the following variable to the bottom of the Game class's private declarations:

std::vector<std::shared_ptr<DirectX::IEffect>> m_modelFog;

In Game.cpp, add to the TODO of CreateDevice after you create the m_modelNormal effect array:

m_fxFactory->EnableFogging(true);
m_fxFactory->EnablePerPixelLighting(true);
m_modelFog = m_model->CreateEffects(*m_fxFactory, pd, pdAlpha);

for (auto& effect : m_modelFog)
{
    auto lights = dynamic_cast<IEffectLights*>(effect.get());
    if (lights)
    {
        lights->SetLightEnabled(0, true);
        lights->SetLightDiffuseColor(0, Colors::Gold);
        lights->SetLightEnabled(1, false);
        lights->SetLightEnabled(2, false);
    }

    auto fog = dynamic_cast<IEffectFog*>(effect.get());
    if (fog)
    {
        fog->SetFogColor(Colors::CornflowerBlue);
        fog->SetFogStart(3.f);
        fog->SetFogEnd(4.f);
    }
}

In Game.cpp, add to the TODO of OnDeviceLost:

m_modelFog.clear();

In Game.cpp, modify the TODO of Render:

ID3D12DescriptorHeap* heaps[] = { m_modelResources->Heap(), m_states->Heap() };
m_commandList->SetDescriptorHeaps(_countof(heaps), heaps);

Model::UpdateEffectMatrices(m_modelFog, m_world, m_view, m_proj);

m_model->Draw(m_commandList.Get(), m_modelFog.cbegin());

Build and run to get our cup with a colored light, per-pixel rather than vertex lighting, and fogging enabled.

Screenshot of fogged cup model

Technical notes

Here we've made use to two C++ concepts:

  • The dynamic_cast operator allows us to safely determine if an effect in the model supports lighting and/or fog using the C++ run-time type checking. If the IEffect instance does not support the desired interface, the cast returns a nullptr and our code will skip those settings.
  • We used a ranged-based for loop to run through the fog effects array.

In DirectX Tool Kit for DirectX 11 the effect would select the proper shaders to use when you called Apply. With the Direct3D 12 Pipeline State Object, the shader configuration must be chosen when the effect is created. Therefore, we used EnableFogging and EnablePerPixelLighting on the effect factory to ensure the effects for m_modelFog were created with EffectFlags::PerPixelLighting | EffectFlags::Fog.

Next lesson: Using advanced shaders

Further reading

DirectX Tool Kit docs EffectFactory, EffectTextureFactory, Effects, Model

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Xbox One
  • Xbox Series X|S

For Development

  • Visual Studio 2022
  • Visual Studio 2019 (16.11)
  • clang/LLVM v12 - v18
  • MinGW 12.2, 13.2
  • CMake 3.20

Related Projects

DirectX Tool Kit for DirectX 11

DirectXMesh

DirectXTex

DirectXMath

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

See also

DirectX Landing Page

Clone this wiki locally