Lesson 10:绘制一个三角形
课程一览
我们会通过创建一系列顶点构建一个三角形,并让硬件在屏幕上绘制出来。
这节课很长但可以分为以下部分:
1.首先告诉GPU怎样渲染我们的几何体。
2.第二,我们创建三角形的三个顶点
3.第三,我们把这些顶点的值存储到显卡。
4.第四,我们告诉GPU怎么读取这些顶点。
5.第五,最终渲染三角形。
使用着色器
渲染的过程受渲染管线的控制。它是一系列得到渲染图像的步骤,不幸的是渲染管线不会自动知道做什么。它必须被着色器编程。
着色器是误导性术语,以为着色器并不提供shade。一个着色器实际上是一个迷你程序,用来控制管线步骤。
有几种不同类型的着色器,每个在渲染过程中要运行多次。例如,一个顶点着色器程序在每个顶点被渲染的时候运行一次,而像素着色器程序在每个像素被绘制的时候都要调用。
加载着色器需要几个步骤,下面是我们要用的:
1.从着色器文件加载并编译两个着色器。
2.把每个着色器封装进着色器对象。
3.设置所有着色器为活动着色器。
1.从着色器文件加载并编译两个着色器
为了加载编译着色器我们需要调用D3DX11CompileFromFile()。这个函数有大量的参数,大部分可以设置为0和忽略。
让我们看一下函数原型:
HRESULT D3DX11CompileFromFile(
LPCTSTR pSrcFile, // file containing the code
D3D10_SHADER_MACRO *pDefines, // advanced
LPD3D10INCLUDE pInclude, // advanced
LPCSTR pFunctionName, // name of the shader's starting function
LPCSTR pProfile, // profile of the shader
UINT Flags1, // advanced
UINT Flags2, // advanced
ID3DX11ThreadPump *pPump, // advanced
ID3D10Blob **ppShader, // blob containing the compiled shader
ID3D10Blob **ppErrorMsgs, // advanced
HRESULT *pHResult); // advanced
LPCSTSTR pSrcFile,
第一个参数是包含未编译着色器代码的文件名。我们用L"shaders.shader"。
尽管你可以使用任何喜欢扩展名但是通常使用.shader作为着色器存储文件的扩展名。
LPCSTR pFunctionName,
这个参数是着色器的名字。代码中每个着色器会以特定的函数启动,它被认为是着色器的名字。本课示例着色器被称为VShader和PShader。
LPCSTR pProfile,
着色器配置文件是一个告诉编译器我们要编译什么类型的着色器和编译的版本的代码。这个代码的格式如下:
vs_4_0
"v"代表顶点,“s”代表着色器,“_4_0”代表HLSL版本为4.0.
你可以把“V”替换为“p”标识像素着色器。
ID3D10Blob **ppShader,
这个参数是一个指向blob(二进制片)对象的指针。这个blob对象将会以编译的着色器代码填充。
一个blob对象是一个小巧的COM对象,它存储缓冲区数据。我们可以使用GetBufferPointer()和GetBufferSize()来访问内容。
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
这个代码究竟做了什么?对于顶点着色器它加载“shaders.shader”的内容,找到函数“VShader”,作为4.0版本顶点着色器编译它并且把编译结果存储到blob VS。
这段代码将会增长,所以让我们把它放到新的叫做InitPipeline()的函数里:
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
}
2.把所有着色器封装到着色器对象
每个着色器被存储到他们自己的COM对象中。我们创建顶着色器和像素着色器,针对它们的COM对象被称作ID3D11_Shader:
// global
ID3D11VertexShader *pVS; // the vertex shader
ID3D11PixelShader *pPS; // the pixel shader
一旦我们有了指针,就可以用dev->Create_Shader()创建对象,像这样:
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
}
这里有4个参数,其中3个很明显。第一个是被编译数据的地址。第二个是文件数据的大小。第四个是着色器对象的地址。
第三个稍后介绍。
3.设置所有的着色器为活动着色器。
步骤很简单。像这样:
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
}
对这些函数,第一个参数是要设置的着色器对象的地址,而另外两个稍后介绍。
记住pVS和pPS都是COM对象,所以它们必须被释放。
void CleanD3D(void)
{
swapchain->SetFullscreenState(FALSE, NULL); // switch to windowed mode
// close and release all existing COM objects
pVS->Release();
pPS->Release();
swapchain->Release();
backbuffer->Release();
dev->Release();
devcon->Release();
}
总的来说这个函数为GPU渲染做准备。现在已经给出了把顶点转换为渲染图片所需的指令。那么现在就需要一些顶点了。
顶点缓冲
如果你仔细学习过Lesson1,你会想起顶点的定义:三维空间中精确点的属性和位置。定位只是3个数值代表顶点的坐标。顶点的属性也是有数值定义的。
Direct3D使用所谓的输入布局。输入布局是包含顶点属性和位置的数据的布局。它是可以根据需要修改和设置的数据的格式。让我们看看它是如何工作的。
顶点结构包含与要创建的3D图像相关的数据。为了显示图像,我们会把所有信息拷贝到GPU然后让Direct3D渲染数据到后台缓冲。
当然你可能无法立刻看出什么,但是我们只需要两个信息块。我们需要把它像这样更快地发送给GPU:
这就是我们使用输入布局产生的效果。我们选择想要的信息,,并在每个帧之间使我们发送更多的顶点。
创建顶点
让我们简单点,先制作单个顶点。
顶点通常使用一个结构创建。在这个结构中你可以放置任何你想要的相关数据(格式随你喜欢)。例如,如果我们打算让每个顶点包含位置和颜色我们可以构建下面的结构:
struct VERTEX
{
FLOAT X, Y, Z; // position
D3DXCOLOR Color; // color
};
正如你所看到的,我们有3个浮点数代表位置,最后一个成员代表颜色。
现在让我们用这个结构构建一个实际的顶点。像这样:
VERTEX OurVertex = {0.0f, 0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)};
当然我们也可以做一个像这样的顶点数组:
VERTEX OurVertices[] =
{
{0.0f, 0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)},
{0.45f, -0.5, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f)},
{-0.45f, -0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f)}
};
这样结果是个三角形。
创建顶点缓冲
当用C++创建一个结构体的时候数据存储在系统内存,但是我们需要它存储到显存。
为了能够让我们访问显存,Direct3D给我们提供了一个特定的COM对象,它能让我们维护系统内存和显存。
缓冲在系统和显卡内存中是如何存在的?好的,最初缓冲里的数据存储在系统内存。当渲染时调用它,Direct3D会自动复制它到显存。如果显存内存不足,Direct3D会删除还没有使用的缓冲,或者说是“低优先级”缓冲以供空间给新资源。
这个COM对象就叫做ID3D11Buffer。我们使用CreateBuffer()函数创建它。
ID3D11Buffer *pVBuffer; // global
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU
bd.ByteWidth = sizeof(VERTEX) * 3; // size is the VERTEX struct * 3
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer
dev->CreateBuffer(&bd, NULL, &pVBuffer); // create the buffer
bd.Usage
为了尽可能高效地配置缓冲。Direct3D需要知道我们打算如何访问它。
可能用到的各种标记展示在下表里。
填充顶点缓冲区
现在我们有了一些三角形顶点,并且有了放置它们的顶点缓冲。现在我们需要做的就是把它们拷贝到缓冲。
由于Direct3D在后台工作,它不会让你直接访问CPU,为了访问它,缓冲必须被映射。这意味着Direct3D允许做任何事情直到缓冲完成,然后锁定GPU缓冲直到未被映射。
所以我们这样填充顶点缓冲:
1.映射顶点缓冲(从而获得缓冲区的位置)
2.复制数据到缓冲区(使用memcpy())。
3.取消映射缓冲。
D3D11_MAPPED_SUBRESOURCE ms;
devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); // map the buffer
memcpy(ms.pData, OurVertices, sizeof(OurVertices)); // copy the data
devcon->Unmap(pVBuffer, NULL); // unmap the buffer
devcon->Map()
第三个参数是一套映射后允许我们控制CPU访问缓冲的标记。我们使用D3D11_MAP_WRITE_DISCARD,其它的标记见下表。
这部分有很多代码。为了更简单让我们包装它们,我已经把所有相关代码放在单独的函数InitGraphics()里了。
struct VERTEX{FLOAT X, Y, Z; D3DXCOLOR Color;}; // a struct to define a vertex
ID3D11Buffer *pVBuffer; // the vertex buffer
void InitGraphics()
{
// create a triangle using the VERTEX struct
VERTEX OurVertices[] =
{
{0.0f, 0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)},
{0.45f, -0.5, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f)},
{-0.45f, -0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f)}
};
// create the vertex buffer
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU
bd.ByteWidth = sizeof(VERTEX) * 3; // size is the VERTEX struct * 3
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer
dev->CreateBuffer(&bd, NULL, &pVBuffer); // create the buffer
// copy the vertices into the buffer
D3D11_MAPPED_SUBRESOURCE ms;
devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); // map the buffer
memcpy(ms.pData, OurVertices, sizeof(OurVertices)); // copy the data
devcon->Unmap(pVBuffer, NULL); // unmap the buffer
}
这是3D编程的重要部分,我们将频繁使用和修改它。所以我建议你仔细把这部分看几遍。
验证输入布局
目前课程我们已经做的
A)加载并设置控制管线的着色器
B)创建形状的顶点并把它们读入到GPU使用
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">ID3D11InputLayout对象存储了我们的VERTEX结构体布局。为了创建这个对象我们调用<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">CreateInputLayout()函数。</span></span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;"><span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">这包括俩部分。首先我们需要定义每个顶点元素。第二我们需要创建输入布局对象line</span></span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;"><span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">创建输入元素</span></span>
一个顶点布局由一个或多个输入元素组成。一个输入元素是一个顶点的属性,例如位置或颜色。
每个元素被叫做<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgb(238, 238, 238); -webkit-text-stroke-width: 0px;">D3D11_INPUT_ELEMENT_DESC的结构体定义。这个结构体描述了单个顶点属性。</span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgb(238, 238, 238); -webkit-text-stroke-width: 0px;">为了用多属性创建一个顶点,我们必须把这些结构放到数组中。</span>
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">Create the Input Layout Object</span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;"></span>我保证这是在绘制前的最后一件事情了。
这里我们调用<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">CreateInputLayout()创建一个代表顶点格式的对象。</span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;">让我们看一下函数原型:</span>
<span style="font: 12px/24px Tahoma, Arial; text-align: justify; color: rgb(67, 67, 67); text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; background-color: rgba(255, 255, 255, 0.8); -webkit-text-stroke-width: 0px;"></span><pre class="cpp" name="code">HRESULT CreateInputLayout(
D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
UINT NumElements,
void *pShaderBytecodeWithInputSignature,
SIZE_T BytecodeLength,
ID3D11InputLayout **pInputLayout);
实际代码写的时候像这样:
<pre class="cpp" name="code">ID3D11InputLayout *pLayout; // global
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
当然,为了让函数运行,我们需要访问VSFile和VSSize,所以我们将放置这些代码到InitPipeline()函数。
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
// create the input layout object
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
}
最后设置输入布局,调用IASetInputLayout()函数。它的唯一参数就是输入布局对象。
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
// create the input layout object
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
devcon->IASetInputLayout(pLayout);
}
绘制三角形
有三个简单的用于渲染的函数。
第一个设置我们打算使用的顶点缓冲。第二个设置我们要用的图元类型(例如三角形列表,线条等)。第三个就是绘制图形。
IASetVertexBuffers()
这些函数的第一个是IASetVertexBuffers()。它将会告诉GPU渲染时读取哪些顶点。
void IASetVertexBuffers(UINT StartSlot,
UINT NumBuffers,
ID3D11Buffer **ppVertexBuffers,
UINT *pStrides,
UINT *pOffsets);
第五个参数是一个UINT数组告知开始渲染时进入到顶点缓冲的字节数。
UINT stride = sizeof(VERTEX);
UINT offset = 0;
devcon->IASetVertexBuffers(0, 1, &pBuffer, &stride, &offset);
IASetPrimitiveTopology()
第二个函数告诉Direct3D使用哪种类型的图元。
</pre><pre>
这个函数的唯一参数就是这些标记
devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
Draw()
有了材料和烹饪方法,接下来就是做出实际内容了。
这个函数绘制顶点缓冲中的图元到后台缓冲。
原型:
void Draw(UINT VertexCount, // the number of vertices to be drawn
UINT StartVertexLocation); // the first vertex to be drawn
像这样用:
devcon->Draw(3, 0); // draw 3 vertices, starting from vertex 0
// this is the function used to render a single frame
void RenderFrame(void)
{
// clear the back buffer to a deep blue
devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
// select which vertex buffer to display
UINT stride = sizeof(VERTEX);
UINT offset = 0;
devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
// select which primtive type we are using
devcon->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// draw the vertex buffer to the back buffer
devcon->Draw(3, 0);
// switch the back buffer and the front buffer
swapchain->Present(0, 0);
}
Main.cpp
// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")
// define the screen resolution
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
// global declarations
IDXGISwapChain *swapchain; // the pointer to the swap chain interface
ID3D11Device *dev; // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon; // the pointer to our Direct3D device context
ID3D11RenderTargetView *backbuffer; // the pointer to our back buffer
ID3D11InputLayout *pLayout; // the pointer to the input layout
ID3D11VertexShader *pVS; // the pointer to the vertex shader
ID3D11PixelShader *pPS; // the pointer to the pixel shader
ID3D11Buffer *pVBuffer; // the pointer to the vertex buffer
// a struct to define a single vertex
struct VERTEX{FLOAT X, Y, Z; D3DXCOLOR Color;};
// function prototypes
void InitD3D(HWND hWnd); // sets up and initializes Direct3D
void RenderFrame(void); // renders a single frame
void CleanD3D(void); // closes Direct3D and releases memory
void InitGraphics(void); // creates the shape to render
void InitPipeline(void); // loads and prepares the shaders
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
RECT wr = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Our First Direct3D Program",
WS_OVERLAPPEDWINDOW,
300,
300,
wr.right - wr.left,
wr.bottom - wr.top,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
InitD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if(msg.message == WM_QUIT)
break;
}
RenderFrame();
}
// clean up DirectX and COM
CleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
// clear out the struct for use
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
scd.BufferCount = 1; // one back buffer
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferDesc.Width = SCREEN_WIDTH; // set the back buffer width
scd.BufferDesc.Height = SCREEN_HEIGHT; // set the back buffer height
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
scd.OutputWindow = hWnd; // the window to be used
scd.SampleDesc.Count = 4; // how many multisamples
scd.Windowed = TRUE; // windowed/full-screen mode
scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen switching
// create a device, device context and swap chain using the information in the scd struct
D3D11CreateDeviceAndSwapChain(NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
// get the address of the back buffer
ID3D11Texture2D *pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
// use the back buffer address to create the render target
dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
pBackBuffer->Release();
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, NULL);
// Set the viewport
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = SCREEN_WIDTH;
viewport.Height = SCREEN_HEIGHT;
devcon->RSSetViewports(1, &viewport);
InitPipeline();
InitGraphics();
}
// this is the function used to render a single frame
void RenderFrame(void)
{
// clear the back buffer to a deep blue
devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
// select which vertex buffer to display
UINT stride = sizeof(VERTEX);
UINT offset = 0;
devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
// select which primtive type we are using
devcon->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// draw the vertex buffer to the back buffer
devcon->Draw(3, 0);
// switch the back buffer and the front buffer
swapchain->Present(0, 0);
}
// this is the function that cleans up Direct3D and COM
void CleanD3D(void)
{
swapchain->SetFullscreenState(FALSE, NULL); // switch to windowed mode
// close and release all existing COM objects
pLayout->Release();
pVS->Release();
pPS->Release();
pVBuffer->Release();
swapchain->Release();
backbuffer->Release();
dev->Release();
devcon->Release();
}
// this is the function that creates the shape to render
void InitGraphics()
{
// create a triangle using the VERTEX struct
VERTEX OurVertices[] =
{
{0.0f, 0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)},
{0.45f, -0.5, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f)},
{-0.45f, -0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f)}
};
// create the vertex buffer
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU
bd.ByteWidth = sizeof(VERTEX) * 3; // size is the VERTEX struct * 3
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer
dev->CreateBuffer(&bd, NULL, &pVBuffer); // create the buffer
// copy the vertices into the buffer
D3D11_MAPPED_SUBRESOURCE ms;
devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); // map the buffer
memcpy(ms.pData, OurVertices, sizeof(OurVertices)); // copy the data
devcon->Unmap(pVBuffer, NULL); // unmap the buffer
}
// this function loads and prepares the shaders
void InitPipeline()
{
// load and compile the two shaders
ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
// create the input layout object
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
devcon->IASetInputLayout(pLayout);
}
shaders.shader
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = position;
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}