dev.nlited.com

>>

SimpleGrid with DirectXTK

<<<< prev
next >>>>

2017-11-27 05:27:41 chip Page 2073 📢 PUBLIC

Nov 22 2017

Source code:
DirectX/DX11/Tutorials/SimpleGrid
ssh://git.nlited.org/git/src/HQ/Dev/SB/Chip/DirectX11

I am still struggling to establish a beachhead on DirectX11. The closest I have been is the "Simple3D" grid example from the DirectXTK tutorial, so I am revisiting that project. This time I want to use it as a platform to write a lot of experimental code, so I need to reformat it to make it friendlier to new code. The first step is to make a clearer separation between the template code and my modifications. Rather than adding code directly into the template functions I am adding callouts to empty functions that will contain only my code. For example: CreateDevice() calls out to CreateDevice2(); CreateResources() calls out to CreateResources2(); etc.

Remember that the DirectXTK package needs to be "installed" into each newly created project.

pch.h additions:



pch.h: 1#include "CommonStates.h" 2#include "DDSTextureLoader.h" 3#include "DirectXHelpers.h" 4#include "Effects.h" 5#include "GamePad.h" 6#include "GeometricPrimitive.h" 7#include "GraphicsMemory.h" 8#include "Keyboard.h" 9#include "Model.h" 10#include "Mouse.h" 11#include "PostProcess.h" 12#include "PrimitiveBatch.h" 13#include "ScreenGrab.h" 14#include "SimpleMath.h" 15#include "SpriteBatch.h" 16#include "SpriteFont.h" 17#include "VertexTypes.h" 18#include "WICTextureLoader.h"

And at the top of Game.cpp:
using namespace DirectX; using namespace DirectX::SimpleMath; using Microsoft::WRL::ComPtr;

Add a simple Debug() function:

Debug(): 1#define DBG_RENDER 0x00000100 2 3void Debug(DWORD Mask, const char *Fmt, ...) { 4 va_list ArgList; 5 char text[200]; 6 int ChrCt= 0; 7 va_start(ArgList,Fmt); 8 ChrCt+= _vsnprintf(text+ChrCt,sizeof(text)-ChrCt,Fmt,ArgList); 9 ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"\r\n"); 10 OutputDebugStringA(text); 11 va_end(ArgList); 12}

Clean up the template code and add the callouts.

Game.cpp template: 1/*************************************************************************/ 2/** DirectXTK framework **/ 3/** This code should be left mostly alone. **/ 4/*************************************************************************/ 5Game::Game(): 6 m_window(nullptr), 7 m_outputWidth(800), 8 m_outputHeight(600), 9 m_featureLevel(D3D_FEATURE_LEVEL_9_1) 10{ 11} 12 13void Game::Initialize(HWND window, int width, int height) { 14 m_window = window; 15 m_outputWidth = std::max(width,1); 16 m_outputHeight = std::max(height,1); 17 CreateDevice(); 18 CreateResources(); 19 Initialize2(window); 20} 21 22void Game::Tick(void) { 23 m_timer.Tick([&]() { 24 Update(m_timer); 25 }); 26 Render(); 27} 28 29void Game::Update(DX::StepTimer const& timer) { 30 float elapsedTime = float(timer.GetElapsedSeconds()); 31 float totalTime= float(timer.GetTotalSeconds()); 32 Update2(totalTime,elapsedTime); 33} 34 35void Game::Render(void) { 36 if(m_timer.GetFrameCount() == 0) 37 return; 38 Clear(); 39 Render2(); 40 Present(); 41} 42 43void Game::Clear(void) { 44 m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(),Colors::CornflowerBlue); 45 m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(),D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,1.0f,0); 46 m_d3dContext->OMSetRenderTargets(1,m_renderTargetView.GetAddressOf(),m_depthStencilView.Get()); 47 CD3D11_VIEWPORT viewport(0.0f,0.0f,static_cast<float>(m_outputWidth),static_cast<float>(m_outputHeight)); 48 m_d3dContext->RSSetViewports(1,&viewport); 49} 50 51void Game::Present(void) { 52 HRESULT hr = m_swapChain->Present(1,0); 53 if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { 54 OnDeviceLost(); 55 } else { 56 DX::ThrowIfFailed(hr); 57 } 58} 59 60void Game::OnActivated(void) { } 61void Game::OnDeactivated(void) { } 62void Game::OnSuspending(void) { } 63 64void Game::OnResuming(void) { 65 m_timer.ResetElapsedTime(); 66} 67 68void Game::OnWindowSizeChanged(int width, int height) { 69 m_outputWidth = std::max(width,1); 70 m_outputHeight = std::max(height,1); 71 CreateResources(); 72} 73 74void Game::GetDefaultSize(int& width, int& height) const { 75 width = 800; 76 height = 600; 77} 78 79void Game::CreateDevice(void) { 80 UINT creationFlags = 0; 81#ifdef _DEBUG 82 creationFlags |= D3D11_CREATE_DEVICE_DEBUG; 83#endif 84 static const D3D_FEATURE_LEVEL featureLevels[] ={ 85 D3D_FEATURE_LEVEL_11_1, 86 D3D_FEATURE_LEVEL_11_0, 87 D3D_FEATURE_LEVEL_10_1, 88 D3D_FEATURE_LEVEL_10_0, 89 D3D_FEATURE_LEVEL_9_3, 90 D3D_FEATURE_LEVEL_9_2, 91 D3D_FEATURE_LEVEL_9_1, 92 }; 93 ComPtr<ID3D11Device> device; 94 ComPtr<ID3D11DeviceContext> context; 95 DX::ThrowIfFailed(D3D11CreateDevice( 96 nullptr, // specify nullptr to use the default adapter 97 D3D_DRIVER_TYPE_HARDWARE, 98 nullptr, 99 creationFlags, 100 featureLevels, 101 _countof(featureLevels), 102 D3D11_SDK_VERSION, 103 device.ReleaseAndGetAddressOf(), // returns the Direct3D device created 104 &m_featureLevel, // returns feature level of device created 105 context.ReleaseAndGetAddressOf() // returns the device immediate context 106 )); 107#ifndef NDEBUG 108 ComPtr<ID3D11Debug> d3dDebug; 109 if(SUCCEEDED(device.As(&d3dDebug))) { 110 ComPtr<ID3D11InfoQueue> d3dInfoQueue; 111 if(SUCCEEDED(d3dDebug.As(&d3dInfoQueue))) { 112#ifdef _DEBUG 113 d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION,true); 114 d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR,true); 115#endif 116 D3D11_MESSAGE_ID hide[] = 117 { 118 D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS, 119 // TODO: Add more message IDs here as needed. 120 }; 121 D3D11_INFO_QUEUE_FILTER filter ={ }; 122 filter.DenyList.NumIDs = _countof(hide); 123 filter.DenyList.pIDList = hide; 124 d3dInfoQueue->AddStorageFilterEntries(&filter); 125 } 126 } 127#endif 128 DX::ThrowIfFailed(device.As(&m_d3dDevice)); 129 DX::ThrowIfFailed(context.As(&m_d3dContext)); 130 CreateDevice2(); 131} 132 133void Game::CreateResources(void) { 134 // Clear the previous window size specific context. 135 ID3D11RenderTargetView* nullViews[] ={ nullptr }; 136 m_d3dContext->OMSetRenderTargets(_countof(nullViews),nullViews,nullptr); 137 m_renderTargetView.Reset(); 138 m_depthStencilView.Reset(); 139 m_d3dContext->Flush(); 140 UINT backBufferWidth = static_cast<UINT>(m_outputWidth); 141 UINT backBufferHeight = static_cast<UINT>(m_outputHeight); 142 DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM; 143 DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; 144 UINT backBufferCount = 2; 145 // If the swap chain already exists, resize it, otherwise create one. 146 if(m_swapChain) { 147 HRESULT hr = m_swapChain->ResizeBuffers(backBufferCount,backBufferWidth,backBufferHeight,backBufferFormat,0); 148 if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { 149 // If the device was removed for any reason, a new device and swap chain will need to be created. 150 OnDeviceLost(); 151 // Everything is set up now. Do not continue execution of this method. OnDeviceLost will reenter this method 152 // and correctly set up the new device. 153 return; 154 } else { 155 DX::ThrowIfFailed(hr); 156 } 157 } else { 158 // First, retrieve the underlying DXGI Device from the D3D Device. 159 ComPtr<IDXGIDevice1> dxgiDevice; 160 DX::ThrowIfFailed(m_d3dDevice.As(&dxgiDevice)); 161 // Identify the physical adapter (GPU or card) this device is running on. 162 ComPtr<IDXGIAdapter> dxgiAdapter; 163 DX::ThrowIfFailed(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); 164 // And obtain the factory object that created it. 165 ComPtr<IDXGIFactory2> dxgiFactory; 166 DX::ThrowIfFailed(dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()))); 167 // Create a descriptor for the swap chain. 168 DXGI_SWAP_CHAIN_DESC1 swapChainDesc ={ }; 169 swapChainDesc.Width = backBufferWidth; 170 swapChainDesc.Height = backBufferHeight; 171 swapChainDesc.Format = backBufferFormat; 172 swapChainDesc.SampleDesc.Count = 1; 173 swapChainDesc.SampleDesc.Quality = 0; 174 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 175 swapChainDesc.BufferCount = backBufferCount; 176 DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc ={ }; 177 fsSwapChainDesc.Windowed = TRUE; 178 // Create a SwapChain from a Win32 window. 179 DX::ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd( 180 m_d3dDevice.Get(), 181 m_window, 182 &swapChainDesc, 183 &fsSwapChainDesc, 184 nullptr, 185 m_swapChain.ReleaseAndGetAddressOf() 186 )); 187 // This template does not support exclusive fullscreen mode and prevents DXGI from responding to the ALT+ENTER shortcut. 188 DX::ThrowIfFailed(dxgiFactory->MakeWindowAssociation(m_window,DXGI_MWA_NO_ALT_ENTER)); 189 } 190 // Obtain the backbuffer for this window which will be the final 3D rendertarget. 191 ComPtr<ID3D11Texture2D> backBuffer; 192 DX::ThrowIfFailed(m_swapChain->GetBuffer(0,IID_PPV_ARGS(backBuffer.GetAddressOf()))); 193 // Create a view interface on the rendertarget to use on bind. 194 DX::ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(backBuffer.Get(),nullptr,m_renderTargetView.ReleaseAndGetAddressOf())); 195 // Allocate a 2-D surface as the depth/stencil buffer and 196 // create a DepthStencil view on this surface to use on bind. 197 CD3D11_TEXTURE2D_DESC depthStencilDesc(depthBufferFormat,backBufferWidth,backBufferHeight,1,1,D3D11_BIND_DEPTH_STENCIL); 198 ComPtr<ID3D11Texture2D> depthStencil; 199 DX::ThrowIfFailed(m_d3dDevice->CreateTexture2D(&depthStencilDesc,nullptr,depthStencil.GetAddressOf())); 200 CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); 201 DX::ThrowIfFailed(m_d3dDevice->CreateDepthStencilView(depthStencil.Get(),&depthStencilViewDesc,m_depthStencilView.ReleaseAndGetAddressOf())); 202 CreateResources2(); 203} 204 205void Game::OnDeviceLost(void) { 206 m_depthStencilView.Reset(); 207 m_renderTargetView.Reset(); 208 m_swapChain.Reset(); 209 m_d3dContext.Reset(); 210 m_d3dDevice.Reset(); 211 OnDeviceLost2(); 212 //Recreate everything 213 CreateDevice(); 214 CreateResources(); 215}

Add the callout stubs.

Game.cpp callouts: 1/*************************************************************************/ 2/** TODO additions **/ 3/*************************************************************************/ 4void Game::Initialize2(HWND window) { 5} 6 7void Game::CreateDevice2(void) { 8} 9 10void Game::CreateResources2(void) { 11} 12 13void Game::OnDeviceLost2(void) { 14} 15 16void Game::Update2(float totalTime,float elapsedTime) { 17} 18 19void Game::Render2(void) { 20} 21 22 //EOF: SIMPLEGRID.CPP

SimpleTriangle

Fill in the code for the simple triangle. This time I am paying more attention to the code being added, not just rushing toward spinning cubes...

Game.h: 1std::unique_ptr<DirectX::CommonStates> m_states; 2std::unique_ptr<DirectX::BasicEffect> m_effect; 3std::unique_ptr<DirectX::PrimitiveBatch<DirectX::VertexPositionColor>> m_batch; 4Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout;

DirectX::CommonStates: Provided by DirectXTK.

"The CommonStates class is a factory which simplifies setting the most common combinations of Direct3D rendering states."

BasicEffect: Provided by DirectXTK.

"This is a native Direct3D 11 implementation of the built-in BasicEffect from XNA Game Studio 4 which supports texture mapping, vertex coloring, directional vertex lighting, directional per-pixel lighting, and fog."

PrimitiveBatch: Provided by DirectXTK.

This is a helper for easily and efficiently drawing dynamically generated geometry using Direct3D 11 such as lines or trianges. It fills the same role as the legacy Direct3D 9 APIs DrawPrimitiveUP and DrawIndexedPrimitiveUP. Dynamic submission is a highly effective pattern for drawing procedural geometry, and convenient for debug rendering, but is not nearly as efficient as static buffers which is more suited to traditional meshes where the VBs and IBs do not change every frame. Excessive dynamic submission is a common source of performance problems in apps. Therefore, you should prefer to use Model, GeometricPrimitive, or your own VB/IB over PrimitiveBatch unless you really need the flexibility to regenerate the topology every frame.

PrimitiveBatch manages the vertex and index buffers for you, using DISCARD and NO_OVERWRITE hints to avoid stalling the GPU pipeline. It automatically merges adjacent draw requests, so if you call DrawLine 100 times in a row, only a single GPU draw call will be generated.

PrimitiveBatch is responsible for setting the vertex buffer, index buffer, and primitive topology, then issuing the final draw call. Unlike the higher level SpriteBatch helper, it does not provide shaders, set the input layout, or set any state objects. PrimitiveBatch is often used in conjunction with BasicEffect and the structures from VertexTypes, but it can work with any other shader or vertex formats of your own.

ID3D11InputLayout: DirectX11

An input-layout interface holds a definition of how to feed vertex data that is laid out in memory into the input-assembler stage of the graphics pipeline.

CreateDevice2(): 1void Game::CreateDevice2(void) { 2 m_states= std::make_unique<CommonStates>(m_d3dDevice.Get()); 3 m_effect= std::make_unique<BasicEffect>(m_d3dDevice.Get()); 4 m_effect->SetVertexColorEnabled(true); 5 void const *shaderByteCode; 6 size_t byteCodeLength; 7 m_effect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength); 8 DX::ThrowIfFailed(m_d3dDevice->CreateInputLayout( 9 VertexPositionColor::InputElements, 10 VertexPositionColor::InputElementCount, 11 shaderByteCode,byteCodeLength, 12 m_inputLayout.ReleaseAndGetAddressOf() 13 )); 14 m_batch= std::make_unique<PrimitiveBatch<VertexPositionColor>>(m_d3dContext.Get()); 15}

std::make_unique<> is a wrapper around T *pT= new T(), creating a wrapper object that will automatically call the destructor when it falls out of scope. In this case, it is the equivalent of
m_states= new CommonStates(m_d3dDevice.Get());

This code is creating new CommonStates and BasicEffect objects tied to the Direct3D device. SetVertexColorEnabled(true) enables independent color information for each vertex and requires setting the input layout.

GetVertexShaderByteCode() retrieves the compiled GPU shader code that implements the selected effect.

ID3D11Device1::CreateInputLayout() creates an interface that describes the data layout for the input-assembler stage of the GPU pipeline. In this case, the data layout is in the form of vertices with position and color.

Finally, the batch processing interface is created, based on VertexPositionColor primitives.

OnDeviceLost2(): 1void Game::OnDeviceLost2(void) { 2 m_states.reset(); 3 m_effect.reset(); 4 m_batch.reset(); 5 m_inputLayout.Reset(); 6}

The basic rule here is to call Reset() (or reset()) on anything that was created during DeviceCreate().

Render2(): 1void Game::Render2(void) { 2 m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); 3 m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); 4 m_d3dContext->RSSetState(m_states->CullNone()); 5 m_effect->Apply(m_d3dContext.Get()); 6 m_d3dContext->IASetInputLayout(m_inputLayout.Get()); 7 m_batch->Begin(); 8 VertexPositionColor v1(Vector3(0.f,0.5f,0.5f),Colors::Orange); 9 VertexPositionColor v2(Vector3(0.5f,-0.5f,0.5f),Colors::Blue); 10 VertexPositionColor v3(Vector3(-0.5f,-0.5f,0.5f),Colors::Aquamarine); 11 m_batch->DrawTriangle(v1,v2,v3); 12 m_batch->End(); 13}

ID3D11DeviceContext1 m_d3dContext controls how the device renders the GPU commands. OMSetBlendState() tells the GPU how to blend together color and alpha channels, in this case from one vertex to the next. OMSetDepthStencilState() binds a stencil state to the current context. The stencil state is used for pixel culling, deciding which pixels are visible and which are occluded by other pixels. (The stencil state is essentially a Z-buffer.) In this case, it is not being used. RSSetState() binds a ID3D11RasterizerState to the context. This determines how polygon primitives are filled with solid pixels. In this case, the VertexShader from CreateDevice2() is being used to blend the colors from the vertices. m_effect->Apply() is a DirectXTK function that simplifies using the predefined effect on the DeviceContext. IASetInputLayout() sets the data input layout (VertexPositionColor) for the device context.

Once the device context is fully defined, I can begin submitting graphic primitives to the GPU.

In this case, I am drawing a simple triangle with 3 vertices -- the 3D equivalent of "Hello, World!"

End() concludes the graphics batch and releases the GPU to do its thing, writing to the current back buffer.

Direct3D tutorial

Even though this is a static image, it is being updated 30 times per second.

A slightly more interesting and complex variation is to draw a simple 3D grid. This will start as a flat plane of squares that will evolve into a hilly terrain. I don't want to draw the grid using lines since this will not evolve well. I need to become accustomed to using polygon primitives (which all devolve into triangles in 3D space) so the grid will be composed of quadrangles (quads) and rendered using the VertexPositionColor input format.

The first task is to create a data definition for the grid. I am creating a simple vector that will hold all the vertices in the grid, and creating a getVertex(x0,y0) interface that will return a reference to the vertex above the point(x0,y0) in the x/y plane. This lets me isolate how the vertices are stored from the code that uses them.

NOTE: Direct3D uses a coordinate system where X is to the right, Y is up, and Z is out of the screen toward the eye. In this example, I am placing the grid in the X/Y plane with the vertices elevated along the Z axis. The default view looks directly up along the Z axis from the origin.

Game.h: 1class Game { 2private: 3 int vertexCt; 4 std::vector<DirectX::SimpleMath::Vector3> m_vertex; 5 DirectX::SimpleMath::Vector3 &getVertex(int x0, int z0) { return(m_vertex.at(z0*vertexCt+x0)); };

I need to set all the vertices during CreateResources().

CreateResources(): 1void Game::CreateResources2(void) { 2 vertexCt= 20; 3 m_vertex.resize(vertexCt*vertexCt); 4 for(int y0=0;y0<vertexCt;y0++) { 5 for(int x0=0;x0<vertexCt;x0++) { 6 //(x0,y0) is an integer index into the X/Y plane of the grid. 7 float x1= (float)x0/(float)vertexCt; 8 float y1= (float)y0/(float)vertexCt; 9 float z1= 1.0f; 10 //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. 11 float x2= x1*0.5f; 12 float y2= y1*0.5f; 13 float z2= z1*0.5f; 14 //(x2,y2,z2) is the actual 3D position of the vertex. 15 getVertex(x0,y0)= Vector3(x2,y2,z2); 16 } 17 } 18}

This creates 400 vertices (20*20) with elevation of 0, resulting in a flat grid.

I am reusing the shaders and context from the simple triangle, the only thing that needs to change is Render2().

Render2: 1void Game::Render2(void) { 2 m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); 3 m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); 4 m_d3dContext->RSSetState(m_states->CullNone()); 5 m_effect->Apply(m_d3dContext.Get()); 6 m_d3dContext->IASetInputLayout(m_inputLayout.Get()); 7 m_batch->Begin(); 8 for(int x0=0;x0+1<vertexCt;x0++) { 9 for(int y0=0;y0+1<vertexCt;y0++) { 10 VertexPositionColor v0(getVertex(x0,y0),Colors::); 11 VertexPositionColor v1(getVertex(x0+1,y0),Colors::White); 12 VertexPositionColor v2(getVertex(x0+1,y0+1),Colors::White); 13 VertexPositionColor v3(getVertex(x0,y0+1),Colors::White); 14 m_batch->DrawQuad(v0,v1,v2,v3); 15 } 16 } 17 m_batch->End(); 18}

Direct3D simple grid

Not very impressive, but better than nothing. The "grid" is a solid block because I am still using the opaque vertex shader, which is filling in the quads with a solid blend of all white vertices. Changing the colors of the vertices makes the grid more apparent.
VertexPositionColor v0(getVertex(x0,y0),Colors::Goldenrod); VertexPositionColor v1(getVertex(x0+1,y0),Colors::Bisque); VertexPositionColor v2(getVertex(x0+1,y0+1),Colors::Chartreuse); VertexPositionColor v3(getVertex(x0,y0+1),Colors::DarkGray);

Direct3D simple grid

Looking straight up at the bottom of the grid makes it impossible to see any elevation. I need to move the viewpoint by creating a position for the viewpoint. This requires three transformation matrices: World, View, and Projection.

Game.h: 1class Game { 2private: 3 DirectX::SimpleMath::Matrix m_world; 4 DirectX::SimpleMath::Matrix m_view; 5 DirectX::SimpleMath::Matrix m_proj;

These are initialized in CreateResources2().

CreateResources2(): 1void Game::CreateResources2(void) { 2 // Create the perspective. 3 m_world= Matrix::Identity; 4 m_view= Matrix::CreateLookAt(Vector3(1.0f,1.0f,1.0f),Vector3::Zero,Vector3::UnitZ); 5 m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); 6 m_effect->SetView(m_view); 7 m_effect->SetProjection(m_proj);

Direct3D simple grid

m_view is set to look from (1.0,1.0,1.0) to the origin (Zero) with "up" along the Y-axis. The grid ranges from (0,0) to (0.5,0.5) so the near corner of the grid is halfway between the eye and the origin.

I can make this a bit more dynamic by rotating the world around the Z axis with the pivot at (0,0). The Z-axis extends perpendicular to the X/Y axis of the grid and the origin is at the "far" corner.

Game::Update2(): 1void Game::Update2(float totalTime,float elapsedTime) { 2 m_world= Matrix::CreateRotationZ(cosf(totalTime)*XM_PI); 3} 4 5void Game::Render2(void) { 6 m_effect->SetWorld(m_world); 7... 8}

A flat grid is too boring. Terrain can be simulated by adding some sines and cosines to the Z values.

Game::CreateResources2(): 1 for(int y0=0;y0<vertexCt;y0++) { 2 for(int x0=0;x0<vertexCt;x0++) { 3 //(x0,y0) is an integer index into the X/Y plane of the grid. 4 float x1= (float)x0/(float)vertexCt; 5 float y1= (float)y0/(float)vertexCt; 6 float z1= -cosf(x1*4.0f*XM_PI)*sinf(y1*2.0f*XM_PI); 7 //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. 8 float x2= x1*0.5f; 9 float y2= y1*0.5f; 10 float z2= z1*0.1f; 11 //(x2,y2,z2) is the actual 3D position of the vertex. 12 getVertex(x0,y0)= Vector3(x2,y2,z2); 13 } 14 }

Direct3D simple grid

Rendering the grid as a wire-frame can be helpful.

Game::Render2(): 1void Game::Render2(void) { 2 m_effect->SetWorld(m_world); 3 m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); 4 m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); 5 m_d3dContext->RSSetState(m_states->CullNone()); 6 m_effect->Apply(m_d3dContext.Get()); 7 m_d3dContext->IASetInputLayout(m_inputLayout.Get()); 8 m_batch->Begin(); 9 VertexPositionColor v0,v1,v2,v3; 10 for(int x0=0;x0+1<vertexCt;x0++) { 11 for(int y0=0;y0+1<vertexCt;y0++) { 12 v0= VertexPositionColor(getVertex(x0,y0),Colors::Goldenrod); 13 v1= VertexPositionColor(getVertex(x0+1,y0),Colors::Bisque); 14 v2= VertexPositionColor(getVertex(x0+1,y0+1),Colors::Chartreuse); 15 v3= VertexPositionColor(getVertex(x0,y0+1),Colors::DarkGray); 16 //m_batch->DrawQuad(v0,v1,v2,v3); 17 m_batch->DrawLine(v0,v1); 18 m_batch->DrawLine(v1,v2); 19 } 20 } 21 m_batch->End(); 22}

Direct3D simple grid

It is time to get interactive by controlling the view directly using the keyboard and mouse.

First make the changes to the template code to add keyboard and mouse support.

I will use the control symantecs from Darwinia, which work really well. The ASWD keys are used to update the X/Y coordinates, the mouse XY updates the view pitch and yaw, and the scroll wheel updates the Z elevation. I implement the view XY first.
Game::CreateResources2(): 1void Game::CreateResources2(void) { 2 // Create the perspective. 3 m_world= Matrix::Identity; 4 m_view= Matrix::CreateLookAt(Vector3(0.5f,0.5f,1.0f),Vector3::Zero,Vector3::UnitZ); 5 m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); 6 m_yaw= XMConvertToRadians(+00.0f); 7 m_pitch= XMConvertToRadians(+20.0f); 8 m_effect->SetView(m_view); 9 m_effect->SetProjection(m_proj);
Game::Update2(): 1void Game::Update2(float totalTime,float elapsedTime) { 2 auto kb= m_keyboard->GetState(); 3 Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(m_yaw,0.0f,m_pitch); 4 Vector3 move= Vector3::Zero; 5 if(kb.Left || kb.A) 6 move.x= -1.0f; 7 if(kb.Right || kb.D) 8 move.x= +1.0f; 9 if(kb.Up || kb.W) 10 move.y= +1.0f; 11 if(kb.Down || kb.S) 12 move.y= -1.0f; 13 move= Vector3::Transform(move,moveQ)*0.010f; 14 m_viewPt+= move; 15}
Game::Render2(): 1void Game::Render2(void) { 2 float r= 0.1f; 3 XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); 4 XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); 5 m_effect->SetView(view); 6 m_effect->SetProjection(m_proj); 7 //m_effect->SetWorld(m_world); 8...

The movement action is backward and the yaw seems to be in the wrong axis. It will be easier to figure this out if I add the yaw and pitch control.

Game::CreateResources2(): 1void Game::CreateResources2(void) { 2 // Create the perspective. 3 m_world= Matrix::Identity; 4 m_view= Matrix::CreateLookAt(Vector3(0.5f,0.5f,1.0f),Vector3::Zero,Vector3::UnitZ); 5 m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); 6 m_yaw= XMConvertToRadians(+00.0f); 7 m_pitch= XMConvertToRadians(+20.0f); 8 m_effect->SetView(m_view); 9 m_effect->SetProjection(m_proj);
Game::Update2(): 1void Game::Update2(float totalTime,float elapsedTime) { 2 auto mouse= m_mouse->GetState(); 3 Vector3 delta= Vector3(float(mouse.x),float(mouse.y),0.0f)*0.01f; 4 m_yaw-= delta.x; 5 m_pitch-= delta.y; 6 // limit pitch to straight up or straight down 7 // with a little fudge-factor to avoid gimbal lock 8 float limit = XM_PI/ 2.0f - 0.01f; 9 m_pitch = std::max(-limit,m_pitch); 10 m_pitch = std::min(+limit,m_pitch); 11 // keep longitude in sane range by wrapping 12 if(m_yaw > XM_PI) { 13 m_yaw -= XM_PI * 2.0f; 14 } else if(m_yaw < -XM_PI) { 15 m_yaw += XM_PI * 2.0f; 16 } 17 18 auto kb= m_keyboard->GetState(); 19 if(kb.Escape) 20 PostQuitMessage(0); 21 Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(0.0f,m_pitch,m_yaw); 22 Vector3 move= Vector3::Zero; 23 if(kb.Left || kb.A) 24 move.x= -1.0f; 25 if(kb.Right || kb.D) 26 move.x= +1.0f; 27 if(kb.Up || kb.W) 28 move.y= +1.0f; 29 if(kb.Down || kb.S) 30 move.y= -1.0f; 31 move= Vector3::Transform(move,moveQ)*0.010f; 32 m_viewPt+= move; 33}
Game::Render2(): 1void Game::Render2(void) { 2 float r= 0.1f; 3 XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); 4 XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); 5 m_effect->SetView(view); 6 m_effect->SetProjection(m_proj); 7 //m_effect->SetWorld(m_world);

The code works, but the coordinates are all screwed up; there is a fundamental discord between my coordinates and the example code. My code assumes the "floor" is in the XY plane while the example puts the floor in the XZ plane. It is easier to change my code than to figure out how to change the example's math.

Quite a bit changed since the last code dump, so here is the whole thing.

Game.h: 1class Game { 2private: 3 //Custom 4 std::unique_ptr<DirectX::CommonStates> m_states; 5 std::unique_ptr<DirectX::BasicEffect> m_effect; 6 std::unique_ptr<DirectX::PrimitiveBatch<DirectX::VertexPositionColor>> m_batch; 7 Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout; 8 9 //Grid 10 int vertexCt; //Grid size (vertex/side) 11 std::vector<DirectX::SimpleMath::Vector3> m_vertex; 12 DirectX::SimpleMath::Vector3 &getVertex(int x0, int y0) { return(m_vertex.at(y0*vertexCt+x0)); }; 13 DirectX::SimpleMath::Matrix m_world; 14 bool m_doWireFrame; 15 //DirectX::SimpleMath::Matrix m_view; 16 DirectX::SimpleMath::Matrix m_proj; 17 DirectX::SimpleMath::Vector3 m_viewPt; 18 float m_yaw; 19 float m_pitch; 20 float m_roll; 21 22 // User input 23 std::unique_ptr<DirectX::Keyboard> m_keyboard; 24 std::unique_ptr<DirectX::Mouse> m_mouse; 25 bool m_kbTab;
SimpleGrid.cpp: 1void Game::Initialize2(HWND window) { 2 m_keyboard= std::make_unique<Keyboard>(); 3 m_mouse= std::make_unique<Mouse>(); 4 m_mouse->SetWindow(window); 5 m_mouse->SetMode(Mouse::MODE_RELATIVE); 6 m_kbTab= false; 7} 8 9void Game::CreateDevice2(void) { 10 m_states= std::make_unique<CommonStates>(m_d3dDevice.Get()); 11 m_effect= std::make_unique<BasicEffect>(m_d3dDevice.Get()); 12 m_effect->SetVertexColorEnabled(true); 13 void const *shaderByteCode; 14 size_t byteCodeLength; 15 m_effect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength); 16 DX::ThrowIfFailed(m_d3dDevice->CreateInputLayout( 17 VertexPositionColor::InputElements, 18 VertexPositionColor::InputElementCount, 19 shaderByteCode,byteCodeLength, 20 m_inputLayout.ReleaseAndGetAddressOf() 21 )); 22 m_batch= std::make_unique<PrimitiveBatch<VertexPositionColor>>(m_d3dContext.Get()); 23} 24 25void Game::CreateResources2(void) { 26 // Create the perspective. 27 m_doWireFrame= false; 28 m_world= Matrix::Identity; 29 m_viewPt= Vector3(0.5f,0.1f,0.5f); 30 m_yaw= XMConvertToRadians(-170.0f); 31 m_pitch= XMConvertToRadians(-2.0f); 32 //m_view= Matrix::CreateLookAt(Vector3(0.5f,1.0f,0.5f),Vector3::Zero,Vector3::UnitY); 33 m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); 34 //m_effect->SetView(m_view); 35 m_effect->SetProjection(m_proj); 36 // Create the grid. 37 vertexCt= 20; 38 m_vertex.resize(vertexCt*vertexCt); 39 // Put the grid in the XZ plane with elevation along the Y axis. 40 for(int z0=0;z0<vertexCt;z0++) { 41 for(int x0=0;x0<vertexCt;x0++) { 42 //(x0,y0) is an integer index into the X/Y plane of the grid. 43 float x1= (float)x0/(float)vertexCt; 44 float z1= (float)z0/(float)vertexCt; 45 float y1= -cosf(x1*4.0f*XM_PI)*sinf(z1*2.0f*XM_PI); 46 //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. 47 float x2= x1*0.5f; 48 float y2= y1*0.1f; 49 float z2= z1*0.5f; 50 //(x2,y2,z2) is the actual 3D position of the vertex. 51 getVertex(x0,z0)= Vector3(x2,y2,z2); 52 } 53 } 54} 55 56void Game::OnDeviceLost2(void) { 57 m_states.reset(); 58 m_effect.reset(); 59 m_batch.reset(); 60 m_inputLayout.Reset(); 61} 62 63void Game::Update2(float totalTime,float elapsedTime) { 64 auto mouse= m_mouse->GetState(); 65 Vector3 delta= Vector3(float(mouse.x),float(mouse.y),0.0f); 66 m_yaw-= delta.x*0.0010f; 67 m_pitch-= delta.y*0.0001f; 68 // limit pitch to straight up or straight down 69 // with a little fudge-factor to avoid gimbal lock 70 float limit = XM_PI/ 2.0f - 0.01f; 71 m_pitch = std::max(-limit,m_pitch); 72 m_pitch = std::min(+limit,m_pitch); 73 // keep longitude in sane range by wrapping 74 if(m_yaw > XM_PI) { 75 m_yaw -= XM_PI * 2.0f; 76 } else if(m_yaw < -XM_PI) { 77 m_yaw += XM_PI * 2.0f; 78 } 79 80 auto kb= m_keyboard->GetState(); 81 if(kb.Escape) 82 PostQuitMessage(0); 83 if(kb.Tab) { 84 if(!m_kbTab) { 85 m_doWireFrame= !m_doWireFrame; 86 m_kbTab= true; 87 } 88 } else { 89 m_kbTab= false; 90 } 91 Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(m_yaw,m_pitch,0.0f); 92 Vector3 move= Vector3::Zero; 93 if(kb.Left || kb.A) 94 move.x= +1.0f; 95 if(kb.Right || kb.D) 96 move.x= -1.0f; 97 if(kb.Up || kb.W) 98 move.z= +1.0f; 99 if(kb.Down || kb.S) 100 move.z= -1.0f; 101 if(kb.PageUp) 102 move.y= +1.0f; 103 if(kb.PageDown) 104 move.y= -1.0f; 105 move= Vector3::Transform(move,moveQ)*0.010f; 106 m_viewPt+= move; 107} 108 109void Game::Render2(void) { 110 float r= 0.1f; 111 //Debug(DBG_RENDER,"viewPt(%f,%f,%f) (%f,%f)",m_viewPt.x,m_viewPt.y,m_viewPt.z,XMConvertToDegrees(m_yaw),XMConvertToDegrees(m_pitch)); 112 XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); 113 XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); 114 m_effect->SetView(view); 115 m_effect->SetProjection(m_proj); 116 //m_effect->SetWorld(m_world); 117 m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); 118 m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); 119 m_d3dContext->RSSetState(m_states->CullNone()); 120 m_effect->Apply(m_d3dContext.Get()); 121 m_d3dContext->IASetInputLayout(m_inputLayout.Get()); 122 m_batch->Begin(); 123 VertexPositionColor v0,v1,v2,v3; 124 for(int x0=0;x0+1<vertexCt;x0++) { 125 for(int z0=0;z0+1<vertexCt;z0++) { 126 v0= VertexPositionColor(getVertex(x0,z0),Colors::Goldenrod); 127 v1= VertexPositionColor(getVertex(x0+1,z0),Colors::Bisque); 128 v2= VertexPositionColor(getVertex(x0+1,z0+1),Colors::Chartreuse); 129 v3= VertexPositionColor(getVertex(x0,z0+1),Colors::DarkGray); 130 if(m_doWireFrame) { 131 m_batch->DrawLine(v0,v1); 132 m_batch->DrawLine(v1,v2); 133 } else { 134 m_batch->DrawQuad(v0,v1,v2,v3); 135 } 136 } 137 } 138 m_batch->End(); 139} 140 141 //EOF: SIMPLEGRID.CPP

This version lets me walk around the grid. Keys A and D "strafe" left and right, W and S move forward and back, PageUp and PageDown elevate up and down. The mouse changes the yaw (left/right) and pitch (up/down) angles. The TAB key switches between wireframe and fill modes.

Culling

I have the basics working: creating the model, creating the Direct3D device, rendering the view, user input, and moving the camera. There are glitches in the polygon fill mode where the wrong polygons are being occluded. I need to figure out the stencil and culling modes.

The fix was to set OMSetDepthStencilState(m_states->DepthDefault(),0) and RSSetSet(m_states->CullCounterClockwise()). Note that the culling assumes the polygons always form a concave solid, that it is impossible to see the "backside" of a polygon. This is not strictly true for my simple terrain model, so it is possible to see the "underside" of the hills from the edges.

Game::Render2(): 1void Game::Render2(void) { 2 float r= 0.1f; 3 //Debug(DBG_RENDER,"viewPt(%f,%f,%f) (%f,%f)",m_viewPt.x,m_viewPt.y,m_viewPt.z,XMConvertToDegrees(m_yaw),XMConvertToDegrees(m_pitch)); 4 XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); 5 XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); 6 m_effect->SetView(view); 7 m_effect->SetProjection(m_proj); 8 //m_effect->SetWorld(m_world); 9 m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); 10 m_d3dContext->OMSetDepthStencilState(m_states->DepthDefault(),0); 11 m_d3dContext->RSSetState(m_states->CullCounterClockwise()); 12 m_effect->Apply(m_d3dContext.Get()); 13 m_d3dContext->IASetInputLayout(m_inputLayout.Get()); 14 m_batch->Begin();


WebV7 (C)2018 nlited | Rendered by tikope in 69.689ms | 3.138.151.175