Skip to content

3D shapes

Chuck Walbourn edited this page Jan 24, 2022 · 19 revisions
Getting Started

This lesson draws simple shapes in 3D.

Setup

First create a new project using the instructions from the earlier lessons: Using DeviceResources and Adding the DirectX Tool Kit which we will use for this lesson.

Background

In the previous lesson, we generated geometry with code using PrimitiveBatch to draw simple shapes. Here we make use of GeometricPrimitive which procedurally generates shapes like spheres, cubes, etc. These 3D shapes are more efficient to render because they make use of indexed primitives, and because they can make use of a static rather than dynamic vertex buffer and index buffer.

Drawing a sphere

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 the setup):

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

std::unique_ptr<DirectX::GeometricPrimitive> m_shape;

std::unique_ptr<DirectX::BasicEffect> m_effect;

In Game.cpp, add to the TODO of CreateDeviceDependentResources after where you have created m_graphicsMemory:

RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(),
    m_deviceResources->GetDepthBufferFormat());

EffectPipelineStateDescription pd(
    &GeometricPrimitive::VertexType::InputLayout,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullNone,
    rtState);

m_effect = std::make_unique<BasicEffect>(device, EffectFlags::Lighting, pd);
m_effect->EnableDefaultLighting();

m_shape = GeometricPrimitive::CreateSphere();

m_world = Matrix::Identity;

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

auto size = m_deviceResources->GetOutputSize();

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

m_effect->SetView(m_view);
m_effect->SetProjection(m_proj);

In Game.cpp, add to the TODO of OnDeviceLost where you added m_graphicsMemory.reset():

m_shape.reset();
m_effect.reset();

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

m_effect->SetWorld(m_world);

m_effect->Apply(commandList);

m_shape->Draw(commandList);

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

auto time = static_cast<float>(timer.GetTotalSeconds());

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

Build and run, and you'll see a white lit sphere.

Screenshot Sphere

Unlike DirectX Tool Kit for DirectX 11, you must create your own effect to render with GeometricPrimitive.

Drawing other built-in shapes

In Game.cpp modify the TODO of CreateDeviceDependentResources:

m_shape = GeometricPrimitive::CreateTorus();

Build and run to see a torus instead of a sphere.

Screenshot Torus

You can try out other shapes like a cube, cone, cylinder, dodecahedron, or the classic teapot.

m_shape = GeometricPrimitive::CreateCube();

m_shape = GeometricPrimitive::CreateCone();

m_shape = GeometricPrimitive::CreateCylinder();

m_shape = GeometricPrimitive::CreateDodecahedron();

m_shape = GeometricPrimitive::CreateTeapot();

Adding textures to 3D shapes

Start by saving earth.bmp into your new project's directory, and then from the top menu select Project / Add Existing Item.... Select "earth.bmp" and click "OK".

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

std::unique_ptr<DirectX::DescriptorHeap> m_resourceDescriptors;
Microsoft::WRL::ComPtr<ID3D12Resource> m_texture;
std::unique_ptr<DirectX::CommonStates> m_states;

enum Descriptors
{
    Earth,
    Count
};

In Game.cpp, add to the TODO of CreateDeviceDependentResources (be sure to add it before you create the m_effect instance, but after you created m_graphicsMemory).

m_resourceDescriptors = std::make_unique<DescriptorHeap>(device,
    Descriptors::Count);

m_states = std::make_unique<CommonStates>(device);

ResourceUploadBatch resourceUpload(device);

resourceUpload.Begin();

DX::ThrowIfFailed(
    CreateWICTextureFromFile(device, resourceUpload, L"earth.bmp",
        m_texture.ReleaseAndGetAddressOf(), false));

CreateShaderResourceView(device, m_texture.Get(),
    m_resourceDescriptors->GetCpuHandle(Descriptors::Earth));

auto uploadResourcesFinished = resourceUpload.End(
    m_deviceResources->GetCommandQueue());

uploadResourcesFinished.wait();

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

m_texture.Reset();
m_states.reset();
m_resourceDescriptors.reset();

In Game.cpp modify the TODO of CreateDeviceDependentResources:

m_effect = std::make_unique<BasicEffect>(device,
    EffectFlags::Lighting | EffectFlags::Texture, pd);
m_effect->EnableDefaultLighting();
m_effect->SetTexture(m_resourceDescriptors->GetGpuHandle(Descriptors::Earth),
    m_states->AnisotropicWrap());

m_shape = GeometricPrimitive::CreateSphere();

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

ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap(), m_states->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);

m_effect->SetWorld(m_world);

m_effect->Apply(commandList);

m_shape->Draw(commandList);

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

auto time = static_cast<float>(timer.GetTotalSeconds());

m_world = Matrix::CreateRotationY(time);

Build and you'll see planet earth spinning.

Screenshot Earth

Remember that setting the descriptor heap is left to the caller using SetDescriptorHeaps as part of your render function. This allows the application to use their own heaps instead of the DescriptorHeap class for where the textures are in memory. You can freely mix and match heaps in the application, but remember that you can have only a single texture descriptor heap and a single sampler descriptor heap active at any given time.

Using custom lighting and effects

In Game.cpp modify the TODO of CreateDeviceDependentResources:

m_effect = std::make_unique<BasicEffect>(device,
    EffectFlags::PerPixelLighting | EffectFlags::Texture, pd);
m_effect->SetLightEnabled(0, true);
m_effect->SetLightDiffuseColor(0, Colors::White);
m_effect->SetLightDirection(0, -Vector3::UnitZ);
m_effect->SetTexture(m_resourceDescriptors->GetGpuHandle(Descriptors::Earth),
                     m_states->AnisotropicWrap());

Build and run to see earth with more 'space-like' lighting.

Screenshot Earth

Optimizing vertex & index buffers

By default, the GeometricPrimitive class creates it's vertex and index buffers using GraphicsMemory which is "upload heap" memory. You can render directly from this, but it has the same performance as the 'dynamic' VBs/IBs used by PrimitiveBatch.

Since we have added a ResourceUploadBatch batch for the texture above, we can also uploading the VB/IB to dedicated video memory for faster render performance:

In Game.cpp modify the TODO of CreateDeviceDependentResources:

m_shape = GeometricPrimitive::CreateSphere();

...

ResourceUploadBatch resourceUpload(device);

resourceUpload.Begin();

...

m_shape->LoadStaticBuffers(device, resourceUpload);

auto uploadResourcesFinished = resourceUpload.End(
    m_deviceResources->GetCommandQueue());

uploadResourcesFinished.wait();

More to explore

  • The GeometricPrimitive class is designed for simple rendering, so it always uses the VertexPositionNormalTexture vertex format. For details on how to utilize the built-in shape generation for other vertex formats, see Custom vertex format.

  • Using a custom shader, you can use the basic box or sphere primitive as a "skybox". See Authoring an Effect.

  • Methods are provided to get access to the shape data as std::vectors of VertexType and uint16_t, and GeometricPrimitive also allows you to create drawable instances from such data. See Custom geometry

Next lesson: Rendering a model

Further reading

DirectX Tool Kit docs Effects, EffectPipelineStateDescription, GeometricPrimitive, RenderTargetState

For Use

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

Architecture

  • x86
  • x64
  • ARM64

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