这篇教程将会介绍如何使用DirectX11.我们将会学习如何初始化和关闭Direct3D,还有如何在窗口中渲染。
我们要在框架中加入另一个处理所有Direct3D系统函数的类。我们把类命名为D3DClass。下面是新的系统结构图
你会发现我们把D3DClass加入到了Graphicsclass类中。之前的教程有提到所有与图形图像有关的类都会包括在GraphicsClass中,所以要把D3DClass类加入到Graphicsclass类中。现在来看看Graphicsclass类中要做哪些改变:
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
第一处改变是我们把windows.h这个头文件,移到了d3dclass.h中。
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
第二处改变,我们添加了一个私有类型的指针变量,指向D3DClass,变量的名称是m_D3D。你也许会奇怪为什么我要在类变量前加一个m_。
这是因为当我写代码的时候我能很快的想到哪些是成员变量哪些不是。
D3DClass* m_D3D;};
#endif
在前一个教程中,这个类是空的。现在我们有了一个D3DClass成员变量,所以我们要开始在GraphicsClass中添加初始化和关闭D3DClass对象的代码。我们也要添加BeginSence和EndScene,这两个函数会被Render函数调用,这样我们就能用Direct3D在窗口上进行渲染。
首先我们要在类构造函数里做改动。这里我们把指针初始化为空,为了安全起见所有的类指针都要这样做。
GraphicsClass::GraphicsClass()
{ m_D3D = 0;}
第二处的改变在GraphicsClass的Initialize函数里。我们要创建D3DClass对象,然后调用D3DClass的Initialize函数。我们会把窗口的宽和高,窗口的句柄,还有定义在Graphicsclass.h文件中的四个全局变量传入这个函数。D3DClass类会用这些参数设置Direct3D系统。我们会对d3dclass.cpp进行更加深入的研究。
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;
// Create the Direct3D object.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
return false;
}
return true;
}
下一个修改的地方是在Graphicsclass类的Shutdown函数里。释放掉所有使用过的图形对象,所以我们在这个函数里调用D3DClass类的shutdown函数。注意,这里我检测了指针是否有被初始化。如果没有被初始化我们能确认它没有被设置也就不会去试着释放对象。所以在类的构造函数里要把这些指针初始化为空。如果判断指针有被初始化程序将会尝试释放掉D3DClass然后清除指针。
void GraphicsClass::Shutdown()
{
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}
return;
}
每一帧画面Frame函数都会更新,现在它会调用Render函数。
bool GraphicsClass::Frame()
{
bool result;
// Render the graphics scene.
result = Render();
if(!result)
{
return false;
}
return true;
}
最后一个要修改的地方在Render函数里。我们调用D3D对象把屏幕用灰色清空。之后我们调用EndScene这样窗口就能显示为灰色。
bool GraphicsClass::Render()
{
// Clear the buffers to begin the scene.
m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);
// Present the rendered scene to the screen.
m_D3D->EndScene();
return true;
}
现在来看看行的D3DClass头文件。
D3dclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_
在头文件中,首先要把用到的库模块链接进来。这些库文件包含了Direct3D所有的在DirectX中设置和绘制3D图形的功能模块,也能作为与电脑硬件的接口来获取关于显示器刷新率,是否使用显卡等等。你会注意到一些Directx10 的库还在使用,这是因为这些库的功能并不需要改变,所以在DirectX11里并没有更新。
/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
接下来我们要做的是包含这些库的头文件,这些头文件中包含了关于DirectX的类型定义等等。
//////////////
// INCLUDES //
//////////////
#include
#include
#include
#include
#include
本次教程中D3DClass类的结构会保持最简。它包含标准的构造函数,拷贝构造函数和析构函数。重要的是它包括了Initialize和Shutdown函数。这是本次教程我们主要关注的两个函数。另外还有一些帮助函数,在本教程中这不是重点,还有一些私有的成员变量,在分析d3dclass.cpp文件的时候我们会看到。现在仅仅实现Initialize和Shutdown函数里我们关心的部分。
////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
D3DClass();
D3DClass(const D3DClass&);
~D3DClass();
bool Initialize(int, int, bool, HWND, bool, float, float);
void Shutdown();
void BeginScene(float, float, float, float);
void EndScene();
ID3D11Device* GetDevice();
ID3D11DeviceContext* GetDeviceContext();
void GetProjectionMatrix(D3DXMATRIX&);
void GetWorldMatrix(D3DXMATRIX&);
void GetOrthoMatrix(D3DXMATRIX&);
void GetVideoCardInfo(char*, int&);
private:
bool m_vsync_enabled;
int m_videoCardMemory;
char m_videoCardDescription[128];
IDXGISwapChain* m_swapChain;
ID3D11Device* m_device;
ID3D11DeviceContext* m_deviceContext;
ID3D11RenderTargetView* m_renderTargetView;
ID3D11Texture2D* m_depthStencilBuffer;
ID3D11DepthStencilState* m_depthStencilState;
ID3D11DepthStencilView* m_depthStencilView;
ID3D11RasterizerState* m_rasterState;
D3DXMATRIX m_projectionMatrix;
D3DXMATRIX m_worldMatrix;
D3DXMATRIX m_orthoMatrix;
};
#endif
现在这些已经类似Direct3D了,你也会注意到我并没有写视图矩阵。因为我要把它写在camera类里,在将来的教程中我们会看到。
D3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"
就像我们刚开始做的那样,在类构造函数里把所有的成员指针初始化为空。头文件里的所有的成员指针都要包括进来。
D3DClass::D3DClass()
{
m_swapChain = 0;
m_device = 0;
m_deviceContext = 0;
m_renderTargetView = 0;
m_depthStencilBuffer = 0;
m_depthStencilState = 0;
m_depthStencilView = 0;
m_rasterState = 0;
}
D3DClass::D3DClass(const D3DClass& other)
{
}
D3DClass::~D3DClass()
{
}
Initialize函数执行所有DirectX11下关于Direct3D的设置。我在这里添加了所有必要的代码,此外某些补充的代码在以后的教程中能帮助我们。我能去掉这些补充项目,让代码更简单,但我认为也许在一篇教程里就完整的实现它会更好.
screenWidth和screenHeight变量将我们在SystemClass里创建的窗口的宽和高传递给函数。Direct3D会用这些在同样的窗口里初始化。hwnd变量是窗口的句柄。Direct3D需要这个句柄访问之前创建的窗口。fullscreen变量确定程序是以全屏还是窗口模式运行。Direct3D需要这个来正确创建窗口。screenDepth和screenNear变量是渲染在窗口上3D环境的深度设置。vsync变量表示我们想要Direct3D根据用户的显示器刷新率还是最大刷新率来渲染图形。
bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen,
float screenDepth, float screenNear)
{
HRESULT result;
IDXGIFactory* factory;
IDXGIAdapter* adapter;
IDXGIOutput* adapterOutput;
unsigned int numModes, i, numerator, denominator, stringLength;
DXGI_MODE_DESC* displayModeList;
DXGI_ADAPTER_DESC adapterDesc;
int error;
DXGI_SWAP_CHAIN_DESC swapChainDesc;
D3D_FEATURE_LEVEL featureLevel;
ID3D11Texture2D* backBufferPtr;
D3D11_TEXTURE2D_DESC depthBufferDesc;
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
D3D11_RASTERIZER_DESC rasterDesc;
D3D11_VIEWPORT viewport;
float fieldOfView, screenAspect;
// Store the vsync setting.
m_vsync_enabled = vsync;
在我们初始化Direct3D之前,我们必须获得显卡和显示器的帧速率。每个电脑之间可能有细微的差别,所以我们要获取这个信息。在设置过程中,我们获取到求帧速率需要的分子和分母,然后把它们传递到DirectX中,它会自动计算出正确的刷新率。如果不这样做,仅仅把刷新率设置为默认值,但计算机不一定会有这个值,之后DirectX会自动切换到blit(移动到平面)操作代替缓存翻转,这样会降低执行效率同时在调试的时候返回一些奇怪的错误。
// Create a DirectX graphics interface factory.
result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
if(FAILED(result))
{
return false;
}
// Use the factory to create an adapter for the primary graphics interface (video card).
result = factory->EnumAdapters(0, &adapter);
if(FAILED(result))
{
return false;
}
// Enumerate the primary adapter output (monitor).
result = adapter->EnumOutputs(0, &adapterOutput);
if(FAILED(result))
{
return false;
}
// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
if(FAILED(result))
{
return false;
}
// Create a list to hold all the possible display modes for this monitor/video card combination.
displayModeList = new DXGI_MODE_DESC[numModes];
if(!displayModeList)
{
return false;
}
// Now fill the display mode list structures.
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
if(FAILED(result))
{
return false;
}
// Now go through all the display modes and find the one that matches the screen width and height.
// When a match is found store the numerator and denominator of the refresh rate for that monitor.
for(i=0; i
现在我们有了求刷新率所需要的分子和分母。最后使用适配器前我们需要显卡的名字和显卡的内存。
// Get the adapter (video card) description.
result = adapter->GetDesc(&adapterDesc);
if(FAILED(result))
{
return false;
}
// Store the dedicated video card memory in megabytes.
m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);
// Convert the name of the video card to a character array and store it.
error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
if(error != 0)
{
return false;
}
现在已经存储了刷新率的分子和分母还有显卡信息,这样我们能释放存储它的数据结构和获得信息的接口。
// Release the display mode list.
delete [] displayModeList;
displayModeList = 0;
// Release the adapter output.
adapterOutput->Release();
adapterOutput = 0;
// Release the adapter.
adapter->Release();
adapter = 0;
// Release the factory.
factory->Release();
factory = 0;
.
现在可以用得到的刷新率来初始化DirectX。首先我们要填充交换链的描述。交换链包括一个前置缓冲和一个后置缓冲,保存了要显示的图形。通常情况你仅仅使用后置缓冲,完成所有绘制任务,然后把它交换到前置缓冲然后显示在用户的平面上。所以它就被叫做交换链。
// Initialize the swap chain description.
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
// Set to a single back buffer.
swapChainDesc.BufferCount = 1;
// Set the width and height of the back buffer.
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;
// Set regular 32-bit surface for the back buffer.
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
下一个需要输入的交换链描述是刷新率。刷新率是每秒钟后置缓存转为前置缓存的次数。如果vsync被设置为true,这样会锁定刷新速率(例如60hz)。意思是程序每秒钟仅绘制60次(或者更高如果系统刷新率比60大)。然而如果我们设置vsync为false,系统会始终以最大数率绘制,这样可以形成某些视觉效果。
// Set the refresh rate of the back buffer.
if(m_vsync_enabled)
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}
// Set the usage of the back buffer.
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
// Set the handle for the window to render to.
swapChainDesc.OutputWindow = hwnd;
// Turn multisampling off.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
// Set to full screen or windowed mode.
if(fullscreen)
{
swapChainDesc.Windowed = false;
}
else
{
swapChainDesc.Windowed = true;
}
// Set the scan line ordering and scaling to unspecified.
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// Discard the back buffer contents after presenting.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// Don't set the advanced flags.
swapChainDesc.Flags = 0;
设置完交换链描述后我们还要设置另一个变量,叫版本号。这个变量告诉DirectX我们打算用哪个版本渲染。这里我们设置的值是11.0,代表DirectX11。如果想要支持多个版本或运行在低端的硬件设备上,你可以设置为10或者9,来使用较低版本的DirectX。
// Set the feature level to DirectX 11.
featureLevel = D3D_FEATURE_LEVEL_11_0;
现在交换链描述和版本号已经填好了,我们可以创建交换链,Direct3D设备和Direct3D设备上下文。Direct3D设备和Direct3D设备上下文非常重要,它们是Direct3D所有功能函数的入口。我们用到的几乎所有的功能都来自这个指针接口。
到这里你对Direct3D设备会比较熟悉,这与前一个版本相似,但对新的Direct3D设备上下文比较陌生。基本上来说他们把Direct3D设备的功能分为两部分,分别封装到两个不同的设备所以这两个设备你都要用。
// Create the swap chain, Direct3D device, and Direct3D device context.
result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
if(FAILED(result))
{
return false;
}
某些情况下如果主显卡和DirectX11不兼容调用这个函数创建设备的时候有可能会失败。一些机器可能有一块支持DirectX10的主显卡和一块支持DirectX11的辅助显卡。还有些混合图形卡是以低端的Intel卡为主显卡,高端的Nvidia显卡为附显卡。为了避免这种情况你不能使用默认的设备,你应该枚举出你机器里的所有显卡,然后选择使用哪个显卡并在创建设备的时候指定这块显卡。
现在我们有了一个交换链,我们需要得到一个指向后置缓冲的指针然后把它与交换链连接。我们会使用CreateRenderTargetView函数来连接后置缓冲和交换链。
// Get the pointer to the back buffer.
result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
if(FAILED(result))
{
return false;
}
// Create the render target view with the back buffer pointer.
result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
if(FAILED(result))
{
return false;
}
// Release pointer to the back buffer as we no longer need it.
backBufferPtr->Release();
backBufferPtr = 0;
// Initialize the description of the depth buffer.
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
// Set up the description of the depth buffer.
depthBufferDesc.Width = screenWidth;
depthBufferDesc.Height = screenHeight;
depthBufferDesc.MipLevels = 1;
depthBufferDesc.ArraySize = 1;
depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthBufferDesc.SampleDesc.Count = 1;
depthBufferDesc.SampleDesc.Quality = 0;
depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthBufferDesc.CPUAccessFlags = 0;
depthBufferDesc.MiscFlags = 0;
填充好描述后,我们来创建深度/模板缓存。你会注意到我们使用CreateTexture2D函数来创建缓冲,所以缓冲是二维的贴图。原因是当你的多边形被归类并光栅化后,输出到2D缓冲中的结果是颜色像素。然后这个2D的缓冲会被绘制到屏幕上。
// Create the texture for the depth buffer using the filled out description.
result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
if(FAILED(result))
{
return false;
}
现在我们需要设置深度模板描述。这允许我们控制Direct3D对每个像素的深度控制
// Initialize the description of the stencil state.
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
// Set up the description of the stencil state.
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;
// Stencil operations if pixel is front-facing.
depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// Stencil operations if pixel is back-facing.
depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
填写好描述我们能创建一个深度模板缓冲状态。
// Create the depth stencil state.
result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
if(FAILED(result))
{
return false;
}
创建好深度模板状态后,为了让它起作用,我们要设置它。注意我们使用设备上下文去设置它。
// Set the depth stencil state.
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);
接下来我们需要创建深度模板缓冲的视图描述。我们这样做可以让Direct3D知道使用深度缓冲作为深度模板结构。写好描述后我们调用CreateDepthStencilView去创建它。
// Initailze the depth stencil view.
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
// Set up the depth stencil view description.
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;
// Create the depth stencil view.
result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
if(FAILED(result))
{
return false;
}
创建完成后我们能调用OMSetRenderTargets。这样我们能绑定渲染目标和深度模板缓冲然后输入到渲染线管。这样线管渲染的图形会被绘制到之前创建的后置缓冲中。当图形写入到绘制缓冲后我们能把它交换到前面,之后图形就显示在用户的屏幕上了。
// Bind the render target view and depth stencil buffer to the output render pipeline.
m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);
现在渲染目标设置完成,我们能继续设置其他的一些功能,这样在将来的教程中我们能得到对场景更多的控制权。首先我们创建光栅状态。这样我们可以控制像素的渲染方式。我们可以让我们的场景以线框的方式渲染或者让DirectX同时绘制多边形的前后两个面。默认情况下DirectX已经有一个光栅状态和下面的这个工作方式相似,但如果你不自己设置,你对光栅状态是没有控制权的
// Setup the raster description which will determine how and what polygons will be drawn.
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D11_CULL_BACK;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D11_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;
// Create the rasterizer state from the description we just filled out.
result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
if(FAILED(result))
{
return false;
}
// Now set the rasterizer state.
m_deviceContext->RSSetState(m_rasterState);
视口也需要被设置,这样Direct3D能绘制裁剪区坐标系到渲染目标空间。这里的设置的是整个窗口。
// Setup the viewport for rendering.
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
// Create the viewport.
m_deviceContext->RSSetViewports(1, &viewport);
现在我们能创建投影矩阵。投影矩阵是用来转换3D场景到我们之前设置的2D视口区域。我们需要保留这个矩阵的一份拷贝,这样我们能把矩阵传入我们的shader(着色器)里然后渲染我们的场景。
// Setup the projection matrix.
fieldOfView = (float)D3DX_PI / 4.0f;
screenAspect = (float)screenWidth / (float)screenHeight;
// Create the projection matrix for 3D rendering.
D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);
我们也需要创建一个世界矩阵。这个矩阵被用来将我们模型上的顶点转换为3D世界中的顶点。这个矩阵能让我们的模型在3D世界中旋转,变换和形变。目前我们只是把矩阵初始化为单位矩阵,再在这个对象中保存一份拷贝。这个拷贝要传入shader(着色器)去渲染。
// Initialize the world matrix to the identity matrix.
D3DXMatrixIdentity(&m_worldMatrix);
这个是你通常都要建立的视图矩阵。视图矩阵是用作计算场景中我们眼睛看的位置。你可以把它想象成摄像机,而你则通过这个摄像机去观察场景。我要把它放在camera类里,逻辑上它在那里更适合现在先跳过它。
最后我们会在Initialize函数里设置一个正交投影矩阵。这个矩阵是可以允许我们跳过3D渲染在屏幕上渲染2D元素比如用户界面。在之后的教程里,当我们渲染2D图形和文字在屏幕上的时候你会看到
// Create an orthographic projection matrix for 2D rendering.
D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);
return true;
}
void D3DClass::Shutdown()
{
// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
if(m_swapChain)
{
m_swapChain->SetFullscreenState(false, NULL);
}
if(m_rasterState)
{
m_rasterState->Release();
m_rasterState = 0;
}
if(m_depthStencilView)
{
m_depthStencilView->Release();
m_depthStencilView = 0;
}
if(m_depthStencilState)
{
m_depthStencilState->Release();
m_depthStencilState = 0;
}
if(m_depthStencilBuffer)
{
m_depthStencilBuffer->Release();
m_depthStencilBuffer = 0;
}
if(m_renderTargetView)
{
m_renderTargetView->Release();
m_renderTargetView = 0;
}
if(m_deviceContext)
{
m_deviceContext->Release();
m_deviceContext = 0;
}
if(m_device)
{
m_device->Release();
m_device = 0;
}
if(m_swapChain)
{
m_swapChain->Release();
m_swapChain = 0;
}
return;
}
在D3DClass中我有一些帮助函数。前两个是BeginScene和EndScene。在我们渲染一个新的3D场景的每一帧的开始BeginScene会被调用。函数的功能是初始化缓冲为空。另一个是EndScenc。当所有的绘制已经完成,在每一帧最后它告诉交换链来显示我们的3D场景。
void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
float color[4];
// Setup the color to clear the buffer to.
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;
// Clear the back buffer.
m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);
// Clear the depth buffer.
m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
return;
}
void D3DClass::EndScene()
{
// Present the back buffer to the screen since rendering is complete.
if(m_vsync_enabled)
{
// Lock to screen refresh rate.
m_swapChain->Present(1, 0);
}
else
{
// Present as fast as possible.
m_swapChain->Present(0, 0);
}
return;
}
下面的函数仅仅是得到指向Direct3D设备和Direct3D设备上下文的指针。这些帮助函数会经常被framework调用。
ID3D11Device* D3DClass::GetDevice()
{
return m_device;
}
ID3D11DeviceContext*D3DClass::GetDeviceContext()
{
return m_deviceContext;
}
void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
projectionMatrix = m_projectionMatrix;
return;
}
void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
worldMatrix = m_worldMatrix;
return;
}
void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
orthoMatrix = m_orthoMatrix;
return;
}
最后一个帮助函数返回显卡的参考名称和显卡的专用显存。了解显卡名称和显存大小能帮助在不同的设置中调试。
void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
strcpy_s(cardName, 128, m_videoCardDescription);
memory = m_videoCardMemory;
return;
}
总结
目前我们能初始化和关闭Direct3D,并把窗口渲染为某种颜色。编译运行这些代码会产生和上一个教程一样的窗口但现在Direct3D已经被初始化同时窗口会被渲染为灰色。编译运行代码也能展示你编译的设置是否正确同时能否从SDK中读到头文件和库文件。
练习
如果没有看前一个教程,重新编译和运行代码保证Directx能运行。按下ESC键来退出
改变在graphicsclass.h中设置全屏的全局变量,让程序全屏运行。
改变GraphicsClass::Render中的颜色设置,把屏幕渲染为黄色。
把显卡的名称和内存输出到文本文件中。
原文链接(可以去原文地址下载完整的工程和源代码)