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: #include "CommonStates.h" #include "DDSTextureLoader.h" #include "DirectXHelpers.h" #include "Effects.h" #include "GamePad.h" #include "GeometricPrimitive.h" #include "GraphicsMemory.h" #include "Keyboard.h" #include "Model.h" #include "Mouse.h" #include "PostProcess.h" #include "PrimitiveBatch.h" #include "ScreenGrab.h" #include "SimpleMath.h" #include "SpriteBatch.h" #include "SpriteFont.h" #include "VertexTypes.h" #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(): #define DBG_RENDER 0x00000100 void Debug(DWORD Mask, const char *Fmt, ...) { va_list ArgList; char text[200]; int ChrCt= 0; va_start(ArgList,Fmt); ChrCt+= _vsnprintf(text+ChrCt,sizeof(text)-ChrCt,Fmt,ArgList); ChrCt+= _snprintf(text+ChrCt,sizeof(text)-ChrCt,"\r\n"); OutputDebugStringA(text); va_end(ArgList); }

Clean up the template code and add the callouts.

Game.cpp template: /*************************************************************************/ /** DirectXTK framework **/ /** This code should be left mostly alone. **/ /*************************************************************************/ Game::Game(): m_window(nullptr), m_outputWidth(800), m_outputHeight(600), m_featureLevel(D3D_FEATURE_LEVEL_9_1) { } void Game::Initialize(HWND window, int width, int height) { m_window = window; m_outputWidth = std::max(width,1); m_outputHeight = std::max(height,1); CreateDevice(); CreateResources(); Initialize2(window); } void Game::Tick(void) { m_timer.Tick([&]() { Update(m_timer); }); Render(); } void Game::Update(DX::StepTimer const& timer) { float elapsedTime = float(timer.GetElapsedSeconds()); float totalTime= float(timer.GetTotalSeconds()); Update2(totalTime,elapsedTime); } void Game::Render(void) { if(m_timer.GetFrameCount() == 0) return; Clear(); Render2(); Present(); } void Game::Clear(void) { m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(),Colors::CornflowerBlue); m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(),D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,1.0f,0); m_d3dContext->OMSetRenderTargets(1,m_renderTargetView.GetAddressOf(),m_depthStencilView.Get()); CD3D11_VIEWPORT viewport(0.0f,0.0f,static_cast<float>(m_outputWidth),static_cast<float>(m_outputHeight)); m_d3dContext->RSSetViewports(1,&viewport); } void Game::Present(void) { HRESULT hr = m_swapChain->Present(1,0); if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { OnDeviceLost(); } else { DX::ThrowIfFailed(hr); } } void Game::OnActivated(void) { } void Game::OnDeactivated(void) { } void Game::OnSuspending(void) { } void Game::OnResuming(void) { m_timer.ResetElapsedTime(); } void Game::OnWindowSizeChanged(int width, int height) { m_outputWidth = std::max(width,1); m_outputHeight = std::max(height,1); CreateResources(); } void Game::GetDefaultSize(int& width, int& height) const { width = 800; height = 600; } void Game::CreateDevice(void) { UINT creationFlags = 0; #ifdef _DEBUG creationFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif static const D3D_FEATURE_LEVEL featureLevels[] ={ D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1, }; ComPtr<ID3D11Device> device; ComPtr<ID3D11DeviceContext> context; DX::ThrowIfFailed(D3D11CreateDevice( nullptr, // specify nullptr to use the default adapter D3D_DRIVER_TYPE_HARDWARE, nullptr, creationFlags, featureLevels, _countof(featureLevels), D3D11_SDK_VERSION, device.ReleaseAndGetAddressOf(), // returns the Direct3D device created &m_featureLevel, // returns feature level of device created context.ReleaseAndGetAddressOf() // returns the device immediate context )); #ifndef NDEBUG ComPtr<ID3D11Debug> d3dDebug; if(SUCCEEDED(device.As(&d3dDebug))) { ComPtr<ID3D11InfoQueue> d3dInfoQueue; if(SUCCEEDED(d3dDebug.As(&d3dInfoQueue))) { #ifdef _DEBUG d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION,true); d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR,true); #endif D3D11_MESSAGE_ID hide[] = { D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS, // TODO: Add more message IDs here as needed. }; D3D11_INFO_QUEUE_FILTER filter ={ }; filter.DenyList.NumIDs = _countof(hide); filter.DenyList.pIDList = hide; d3dInfoQueue->AddStorageFilterEntries(&filter); } } #endif DX::ThrowIfFailed(device.As(&m_d3dDevice)); DX::ThrowIfFailed(context.As(&m_d3dContext)); CreateDevice2(); } void Game::CreateResources(void) { // Clear the previous window size specific context. ID3D11RenderTargetView* nullViews[] ={ nullptr }; m_d3dContext->OMSetRenderTargets(_countof(nullViews),nullViews,nullptr); m_renderTargetView.Reset(); m_depthStencilView.Reset(); m_d3dContext->Flush(); UINT backBufferWidth = static_cast<UINT>(m_outputWidth); UINT backBufferHeight = static_cast<UINT>(m_outputHeight); DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM; DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; UINT backBufferCount = 2; // If the swap chain already exists, resize it, otherwise create one. if(m_swapChain) { HRESULT hr = m_swapChain->ResizeBuffers(backBufferCount,backBufferWidth,backBufferHeight,backBufferFormat,0); if(hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { // If the device was removed for any reason, a new device and swap chain will need to be created. OnDeviceLost(); // Everything is set up now. Do not continue execution of this method. OnDeviceLost will reenter this method // and correctly set up the new device. return; } else { DX::ThrowIfFailed(hr); } } else { // First, retrieve the underlying DXGI Device from the D3D Device. ComPtr<IDXGIDevice1> dxgiDevice; DX::ThrowIfFailed(m_d3dDevice.As(&dxgiDevice)); // Identify the physical adapter (GPU or card) this device is running on. ComPtr<IDXGIAdapter> dxgiAdapter; DX::ThrowIfFailed(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); // And obtain the factory object that created it. ComPtr<IDXGIFactory2> dxgiFactory; DX::ThrowIfFailed(dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()))); // Create a descriptor for the swap chain. DXGI_SWAP_CHAIN_DESC1 swapChainDesc ={ }; swapChainDesc.Width = backBufferWidth; swapChainDesc.Height = backBufferHeight; swapChainDesc.Format = backBufferFormat; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = backBufferCount; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc ={ }; fsSwapChainDesc.Windowed = TRUE; // Create a SwapChain from a Win32 window. DX::ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd( m_d3dDevice.Get(), m_window, &swapChainDesc, &fsSwapChainDesc, nullptr, m_swapChain.ReleaseAndGetAddressOf() )); // This template does not support exclusive fullscreen mode and prevents DXGI from responding to the ALT+ENTER shortcut. DX::ThrowIfFailed(dxgiFactory->MakeWindowAssociation(m_window,DXGI_MWA_NO_ALT_ENTER)); } // Obtain the backbuffer for this window which will be the final 3D rendertarget. ComPtr<ID3D11Texture2D> backBuffer; DX::ThrowIfFailed(m_swapChain->GetBuffer(0,IID_PPV_ARGS(backBuffer.GetAddressOf()))); // Create a view interface on the rendertarget to use on bind. DX::ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(backBuffer.Get(),nullptr,m_renderTargetView.ReleaseAndGetAddressOf())); // Allocate a 2-D surface as the depth/stencil buffer and // create a DepthStencil view on this surface to use on bind. CD3D11_TEXTURE2D_DESC depthStencilDesc(depthBufferFormat,backBufferWidth,backBufferHeight,1,1,D3D11_BIND_DEPTH_STENCIL); ComPtr<ID3D11Texture2D> depthStencil; DX::ThrowIfFailed(m_d3dDevice->CreateTexture2D(&depthStencilDesc,nullptr,depthStencil.GetAddressOf())); CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); DX::ThrowIfFailed(m_d3dDevice->CreateDepthStencilView(depthStencil.Get(),&depthStencilViewDesc,m_depthStencilView.ReleaseAndGetAddressOf())); CreateResources2(); } void Game::OnDeviceLost(void) { m_depthStencilView.Reset(); m_renderTargetView.Reset(); m_swapChain.Reset(); m_d3dContext.Reset(); m_d3dDevice.Reset(); OnDeviceLost2(); //Recreate everything CreateDevice(); CreateResources(); }

Add the callout stubs.

Game.cpp callouts: /*************************************************************************/ /** TODO additions **/ /*************************************************************************/ void Game::Initialize2(HWND window) { } void Game::CreateDevice2(void) { } void Game::CreateResources2(void) { } void Game::OnDeviceLost2(void) { } void Game::Update2(float totalTime,float elapsedTime) { } void Game::Render2(void) { } //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: std::unique_ptr<DirectX::CommonStates> m_states; std::unique_ptr<DirectX::BasicEffect> m_effect; std::unique_ptr<DirectX::PrimitiveBatch<DirectX::VertexPositionColor>> m_batch; Microsoft::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(): void Game::CreateDevice2(void) { m_states= std::make_unique<CommonStates>(m_d3dDevice.Get()); m_effect= std::make_unique<BasicEffect>(m_d3dDevice.Get()); m_effect->SetVertexColorEnabled(true); void const *shaderByteCode; size_t byteCodeLength; m_effect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength); DX::ThrowIfFailed(m_d3dDevice->CreateInputLayout( VertexPositionColor::InputElements, VertexPositionColor::InputElementCount, shaderByteCode,byteCodeLength, m_inputLayout.ReleaseAndGetAddressOf() )); m_batch= std::make_unique<PrimitiveBatch<VertexPositionColor>>(m_d3dContext.Get()); }

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(): void Game::OnDeviceLost2(void) { m_states.reset(); m_effect.reset(); m_batch.reset(); m_inputLayout.Reset(); }

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

Render2(): void Game::Render2(void) { m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); m_d3dContext->RSSetState(m_states->CullNone()); m_effect->Apply(m_d3dContext.Get()); m_d3dContext->IASetInputLayout(m_inputLayout.Get()); m_batch->Begin(); VertexPositionColor v1(Vector3(0.f,0.5f,0.5f),Colors::Orange); VertexPositionColor v2(Vector3(0.5f,-0.5f,0.5f),Colors::Blue); VertexPositionColor v3(Vector3(-0.5f,-0.5f,0.5f),Colors::Aquamarine); m_batch->DrawTriangle(v1,v2,v3); m_batch->End(); }

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: class Game { private: int vertexCt; std::vector<DirectX::SimpleMath::Vector3> m_vertex; 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(): void Game::CreateResources2(void) { vertexCt= 20; m_vertex.resize(vertexCt*vertexCt); for(int y0=0;y0<vertexCt;y0++) { for(int x0=0;x0<vertexCt;x0++) { //(x0,y0) is an integer index into the X/Y plane of the grid. float x1= (float)x0/(float)vertexCt; float y1= (float)y0/(float)vertexCt; float z1= 1.0f; //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. float x2= x1*0.5f; float y2= y1*0.5f; float z2= z1*0.5f; //(x2,y2,z2) is the actual 3D position of the vertex. getVertex(x0,y0)= Vector3(x2,y2,z2); } } }

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: void Game::Render2(void) { m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); m_d3dContext->RSSetState(m_states->CullNone()); m_effect->Apply(m_d3dContext.Get()); m_d3dContext->IASetInputLayout(m_inputLayout.Get()); m_batch->Begin(); for(int x0=0;x0+1<vertexCt;x0++) { for(int y0=0;y0+1<vertexCt;y0++) { VertexPositionColor v0(getVertex(x0,y0),Colors::); VertexPositionColor v1(getVertex(x0+1,y0),Colors::White); VertexPositionColor v2(getVertex(x0+1,y0+1),Colors::White); VertexPositionColor v3(getVertex(x0,y0+1),Colors::White); m_batch->DrawQuad(v0,v1,v2,v3); } } m_batch->End(); }

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: class Game { private: DirectX::SimpleMath::Matrix m_world; DirectX::SimpleMath::Matrix m_view; DirectX::SimpleMath::Matrix m_proj;

These are initialized in CreateResources2().

CreateResources2(): void Game::CreateResources2(void) { // Create the perspective. m_world= Matrix::Identity; m_view= Matrix::CreateLookAt(Vector3(1.0f,1.0f,1.0f),Vector3::Zero,Vector3::UnitZ); m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); m_effect->SetView(m_view); 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(): void Game::Update2(float totalTime,float elapsedTime) { m_world= Matrix::CreateRotationZ(cosf(totalTime)*XM_PI); } void Game::Render2(void) { m_effect->SetWorld(m_world); ... }

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

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

Direct3D simple grid

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

Game::Render2(): void Game::Render2(void) { m_effect->SetWorld(m_world); m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); m_d3dContext->RSSetState(m_states->CullNone()); m_effect->Apply(m_d3dContext.Get()); m_d3dContext->IASetInputLayout(m_inputLayout.Get()); m_batch->Begin(); VertexPositionColor v0,v1,v2,v3; for(int x0=0;x0+1<vertexCt;x0++) { for(int y0=0;y0+1<vertexCt;y0++) { v0= VertexPositionColor(getVertex(x0,y0),Colors::Goldenrod); v1= VertexPositionColor(getVertex(x0+1,y0),Colors::Bisque); v2= VertexPositionColor(getVertex(x0+1,y0+1),Colors::Chartreuse); v3= VertexPositionColor(getVertex(x0,y0+1),Colors::DarkGray); //m_batch->DrawQuad(v0,v1,v2,v3); m_batch->DrawLine(v0,v1); m_batch->DrawLine(v1,v2); } } m_batch->End(); }

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(): void Game::CreateResources2(void) { // Create the perspective. m_world= Matrix::Identity; m_view= Matrix::CreateLookAt(Vector3(0.5f,0.5f,1.0f),Vector3::Zero,Vector3::UnitZ); m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); m_yaw= XMConvertToRadians(+00.0f); m_pitch= XMConvertToRadians(+20.0f); m_effect->SetView(m_view); m_effect->SetProjection(m_proj);
Game::Update2(): void Game::Update2(float totalTime,float elapsedTime) { auto kb= m_keyboard->GetState(); Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(m_yaw,0.0f,m_pitch); Vector3 move= Vector3::Zero; if(kb.Left || kb.A) move.x= -1.0f; if(kb.Right || kb.D) move.x= +1.0f; if(kb.Up || kb.W) move.y= +1.0f; if(kb.Down || kb.S) move.y= -1.0f; move= Vector3::Transform(move,moveQ)*0.010f; m_viewPt+= move; }
Game::Render2(): void Game::Render2(void) { float r= 0.1f; XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); m_effect->SetView(view); m_effect->SetProjection(m_proj); //m_effect->SetWorld(m_world); ...

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(): void Game::CreateResources2(void) { // Create the perspective. m_world= Matrix::Identity; m_view= Matrix::CreateLookAt(Vector3(0.5f,0.5f,1.0f),Vector3::Zero,Vector3::UnitZ); m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); m_yaw= XMConvertToRadians(+00.0f); m_pitch= XMConvertToRadians(+20.0f); m_effect->SetView(m_view); m_effect->SetProjection(m_proj);
Game::Update2(): void Game::Update2(float totalTime,float elapsedTime) { auto mouse= m_mouse->GetState(); Vector3 delta= Vector3(float(mouse.x),float(mouse.y),0.0f)*0.01f; m_yaw-= delta.x; m_pitch-= delta.y; // limit pitch to straight up or straight down // with a little fudge-factor to avoid gimbal lock float limit = XM_PI/ 2.0f - 0.01f; m_pitch = std::max(-limit,m_pitch); m_pitch = std::min(+limit,m_pitch); // keep longitude in sane range by wrapping if(m_yaw > XM_PI) { m_yaw -= XM_PI * 2.0f; } else if(m_yaw < -XM_PI) { m_yaw += XM_PI * 2.0f; } auto kb= m_keyboard->GetState(); if(kb.Escape) PostQuitMessage(0); Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(0.0f,m_pitch,m_yaw); Vector3 move= Vector3::Zero; if(kb.Left || kb.A) move.x= -1.0f; if(kb.Right || kb.D) move.x= +1.0f; if(kb.Up || kb.W) move.y= +1.0f; if(kb.Down || kb.S) move.y= -1.0f; move= Vector3::Transform(move,moveQ)*0.010f; m_viewPt+= move; }
Game::Render2(): void Game::Render2(void) { float r= 0.1f; XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); m_effect->SetView(view); m_effect->SetProjection(m_proj); //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: class Game { private: //Custom std::unique_ptr<DirectX::CommonStates> m_states; std::unique_ptr<DirectX::BasicEffect> m_effect; std::unique_ptr<DirectX::PrimitiveBatch<DirectX::VertexPositionColor>> m_batch; Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout; //Grid int vertexCt; //Grid size (vertex/side) std::vector<DirectX::SimpleMath::Vector3> m_vertex; DirectX::SimpleMath::Vector3 &getVertex(int x0, int y0) { return(m_vertex.at(y0*vertexCt+x0)); }; DirectX::SimpleMath::Matrix m_world; bool m_doWireFrame; //DirectX::SimpleMath::Matrix m_view; DirectX::SimpleMath::Matrix m_proj; DirectX::SimpleMath::Vector3 m_viewPt; float m_yaw; float m_pitch; float m_roll; // User input std::unique_ptr<DirectX::Keyboard> m_keyboard; std::unique_ptr<DirectX::Mouse> m_mouse; bool m_kbTab;
SimpleGrid.cpp: void Game::Initialize2(HWND window) { m_keyboard= std::make_unique<Keyboard>(); m_mouse= std::make_unique<Mouse>(); m_mouse->SetWindow(window); m_mouse->SetMode(Mouse::MODE_RELATIVE); m_kbTab= false; } void Game::CreateDevice2(void) { m_states= std::make_unique<CommonStates>(m_d3dDevice.Get()); m_effect= std::make_unique<BasicEffect>(m_d3dDevice.Get()); m_effect->SetVertexColorEnabled(true); void const *shaderByteCode; size_t byteCodeLength; m_effect->GetVertexShaderBytecode(&shaderByteCode,&byteCodeLength); DX::ThrowIfFailed(m_d3dDevice->CreateInputLayout( VertexPositionColor::InputElements, VertexPositionColor::InputElementCount, shaderByteCode,byteCodeLength, m_inputLayout.ReleaseAndGetAddressOf() )); m_batch= std::make_unique<PrimitiveBatch<VertexPositionColor>>(m_d3dContext.Get()); } void Game::CreateResources2(void) { // Create the perspective. m_doWireFrame= false; m_world= Matrix::Identity; m_viewPt= Vector3(0.5f,0.1f,0.5f); m_yaw= XMConvertToRadians(-170.0f); m_pitch= XMConvertToRadians(-2.0f); //m_view= Matrix::CreateLookAt(Vector3(0.5f,1.0f,0.5f),Vector3::Zero,Vector3::UnitY); m_proj= Matrix::CreatePerspectiveFieldOfView(XM_PI/4.0f,float(m_outputWidth)/float(m_outputHeight),0.1f,10.0f); //m_effect->SetView(m_view); m_effect->SetProjection(m_proj); // Create the grid. vertexCt= 20; m_vertex.resize(vertexCt*vertexCt); // Put the grid in the XZ plane with elevation along the Y axis. for(int z0=0;z0<vertexCt;z0++) { for(int x0=0;x0<vertexCt;x0++) { //(x0,y0) is an integer index into the X/Y plane of the grid. float x1= (float)x0/(float)vertexCt; float z1= (float)z0/(float)vertexCt; float y1= -cosf(x1*4.0f*XM_PI)*sinf(z1*2.0f*XM_PI); //(x1,y1,z1) is a scaled value from 0.0 to 1.0 across the grid. float x2= x1*0.5f; float y2= y1*0.1f; float z2= z1*0.5f; //(x2,y2,z2) is the actual 3D position of the vertex. getVertex(x0,z0)= Vector3(x2,y2,z2); } } } void Game::OnDeviceLost2(void) { m_states.reset(); m_effect.reset(); m_batch.reset(); m_inputLayout.Reset(); } void Game::Update2(float totalTime,float elapsedTime) { auto mouse= m_mouse->GetState(); Vector3 delta= Vector3(float(mouse.x),float(mouse.y),0.0f); m_yaw-= delta.x*0.0010f; m_pitch-= delta.y*0.0001f; // limit pitch to straight up or straight down // with a little fudge-factor to avoid gimbal lock float limit = XM_PI/ 2.0f - 0.01f; m_pitch = std::max(-limit,m_pitch); m_pitch = std::min(+limit,m_pitch); // keep longitude in sane range by wrapping if(m_yaw > XM_PI) { m_yaw -= XM_PI * 2.0f; } else if(m_yaw < -XM_PI) { m_yaw += XM_PI * 2.0f; } auto kb= m_keyboard->GetState(); if(kb.Escape) PostQuitMessage(0); if(kb.Tab) { if(!m_kbTab) { m_doWireFrame= !m_doWireFrame; m_kbTab= true; } } else { m_kbTab= false; } Quaternion moveQ= Quaternion::CreateFromYawPitchRoll(m_yaw,m_pitch,0.0f); Vector3 move= Vector3::Zero; if(kb.Left || kb.A) move.x= +1.0f; if(kb.Right || kb.D) move.x= -1.0f; if(kb.Up || kb.W) move.z= +1.0f; if(kb.Down || kb.S) move.z= -1.0f; if(kb.PageUp) move.y= +1.0f; if(kb.PageDown) move.y= -1.0f; move= Vector3::Transform(move,moveQ)*0.010f; m_viewPt+= move; } void Game::Render2(void) { float r= 0.1f; //Debug(DBG_RENDER,"viewPt(%f,%f,%f) (%f,%f)",m_viewPt.x,m_viewPt.y,m_viewPt.z,XMConvertToDegrees(m_yaw),XMConvertToDegrees(m_pitch)); XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); m_effect->SetView(view); m_effect->SetProjection(m_proj); //m_effect->SetWorld(m_world); m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); m_d3dContext->OMSetDepthStencilState(m_states->DepthNone(),0); m_d3dContext->RSSetState(m_states->CullNone()); m_effect->Apply(m_d3dContext.Get()); m_d3dContext->IASetInputLayout(m_inputLayout.Get()); m_batch->Begin(); VertexPositionColor v0,v1,v2,v3; for(int x0=0;x0+1<vertexCt;x0++) { for(int z0=0;z0+1<vertexCt;z0++) { v0= VertexPositionColor(getVertex(x0,z0),Colors::Goldenrod); v1= VertexPositionColor(getVertex(x0+1,z0),Colors::Bisque); v2= VertexPositionColor(getVertex(x0+1,z0+1),Colors::Chartreuse); v3= VertexPositionColor(getVertex(x0,z0+1),Colors::DarkGray); if(m_doWireFrame) { m_batch->DrawLine(v0,v1); m_batch->DrawLine(v1,v2); } else { m_batch->DrawQuad(v0,v1,v2,v3); } } } m_batch->End(); } //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(): void Game::Render2(void) { float r= 0.1f; //Debug(DBG_RENDER,"viewPt(%f,%f,%f) (%f,%f)",m_viewPt.x,m_viewPt.y,m_viewPt.z,XMConvertToDegrees(m_yaw),XMConvertToDegrees(m_pitch)); XMVECTOR lookAt= m_viewPt + Vector3(r*sinf(m_yaw),sinf(m_pitch),r*cosf(m_yaw)); XMMATRIX view= XMMatrixLookAtRH(m_viewPt,lookAt,Vector3::Up); m_effect->SetView(view); m_effect->SetProjection(m_proj); //m_effect->SetWorld(m_world); m_d3dContext->OMSetBlendState(m_states->Opaque(),nullptr,0xFFFFFFFF); m_d3dContext->OMSetDepthStencilState(m_states->DepthDefault(),0); m_d3dContext->RSSetState(m_states->CullCounterClockwise()); m_effect->Apply(m_d3dContext.Get()); m_d3dContext->IASetInputLayout(m_inputLayout.Get()); m_batch->Begin();


WebV7 (C)2018 nlited | Rendered by tikope in 57.749ms | 3.16.47.89