06.顶点索引
使用索引可以定义如何绘制几何体。例如,可以通过创建两个三角形来绘制一个正方形,每个三角形有3个顶点。这就需要创建6个顶点。使用索引缓冲区,您可以像我们将要做的那样只使用4个顶点创建一个正方形。我们的索引列表将第一个三角形定义为顶点0、1、2,第二个顶点定义为0、2、3。
本节的源代码下载地址:
- https://www.braynzarsoft.net/file/7
- DX11_Lesson_06_Indices_zip.zip
顶点索引介绍
本课程直接建立在上一课程的基础上。我们将学习如何使用索引。索引列表通常是一个三角形列表。它们对于绘制几何图形非常有用,并且可以减少顶点计数。
索引原理
如果我们想画一个正方形,在没有索引缓冲区的情况下,我们需要画两个三角形。每个三角形将包含3个顶点,因此顶点缓冲区看起来像:{v0,v1,v2,v3,v4,v5},其中三角形1由v0,v1,v2定义,三角形2由v3,v4,v5定义。
现在,想想一个包含数千个三角形的巨大模型,以及你需要制作多少个重复的顶点。使用索引列表来定义三角形,我们的顶点缓冲区可能看起来像:{v0,v1,v2,v3},其中三角形1由v0,v1,v2定义,三角形2由v0、v2、v3定义。
这样可以大量合并相同的顶点,减少内存。
全局声明
由于我们在上一课中已经介绍了ID3D11Buffer,因此我将不在这里介绍它们。但正如您所看到的,我们已经创建了一个新的缓冲区来保存我们的平方索引列表。您还可以看到,我们将顶点缓冲区的名称更改为正方形,而不是三角形。
ID3D11Buffer* squareIndexBuffer;
ID3D11Buffer* squareVertBuffer;
顶点列表
正方形包括四个顶点,这里我们列出所有顶点坐标及颜色。
Vertex v[] =
{
Vertex( -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f ),
Vertex( -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f ),
Vertex( 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f ),
Vertex( 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f ),
};
顶眯索引
这是我们将创建索引缓冲区的地方。
首先创建一个DWORD数组来定义我们的两个三角形。然后我们初始化并填写一个D3D11_BUFFER_DESC对象,该对象将定义我们的索引缓冲区。您可以看到字节宽度是DWORD的大小乘以2乘以3。这是因为索引数组中的每个元素都是DWORD类型,所以我们需要获得DWORD类型的大小,将其乘以面(或三角形)的数量,然后将其乘以3,因为每个三角形都包含3个顶点或索引数组中元素。之后,我们将绑定标志设置为D3D11_bind_INDEX_BUFFER。这将告诉管道的IA阶段这是一个索引缓冲区。
DWORD indices[] = {
0, 1, 2,
0, 2, 3,
};
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
接下来,我们将索引数组放入D3D11_SUBRESOURCE_DATA中,通过调用D3D11DeviceContext::CreateBuffer()创建缓冲区,然后通过调用函数D3D11DDeviceContext::IASetIndexBuffer(第一个参数是创建的索引缓冲区,第二个参数是格式。我们指定了DXGI_FORMAT_R32_UNT,这是一个简单的32位浮点,但我们也可以为16位浮点指定DXGI_FFORMAT_R16_UINT。最后一个参数是偏移量,它是要在索引数组中使用的第一个索引。也许您的索引缓冲区定义了多个对象,但您只想绘制一个。在绘制对象之前,可以将索引缓冲区与该对象起点的偏移量绑定。
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &squareIndexBuffer);
d3d11DevCon->IASetIndexBuffer( squareIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
由于我们在顶点数组中添加了另一个顶点,我们需要将缓冲区更新为4个顶点的大小,而不是像上一个那样只有3个顶点。所以对于顶点的内容也可相应的修改。从原来的6个改成4个。
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
对于清理函数,增加释放顶点索引代码。
void CleanUp()
{
//Release the COM Objects we created
SwapChain->Release();
d3d11Device->Release();
d3d11DevCon->Release();
renderTargetView->Release();
squareVertBuffer->Release();
squareIndexBuffer->Release();//顶点索引
VS->Release();
PS->Release();
VS_Buffer->Release();
PS_Buffer->Release();
vertLayout->Release();
}
顶点索引绘制
最后我们画出正方形。我们可以通过调用D3D11DeviceContext::DrawIndexed()方法来绘制索引基元。
void DrawIndexed(
[in] UINT IndexCount, //绘制顶点索引数量
[in] UINT StartIndexLocation,//给制的顶点索引开始位置
[in] INT BaseVertexLocation//顶点偏移
);
第一个参数是要绘制的索引数。
第二个参数是从索引缓冲区开始绘制的偏移量。如果我们只想画第二个三角形,我们可以在这里指定一个“4”。
第三个参数可能有点令人困惑,但可能不会。这是从顶点缓冲区开始到开始绘制的偏移量。您可能有两个索引缓冲区,一个描述球体,另一个描述长方体。但也许两者都在一个顶点缓冲区中,其中球体是顶点缓冲区的第一组顶点,长方体是第二组顶点。因此,要绘制长方体,您需要将第三个参数设置为球体中的顶点数,这样长方体将成为第一个开始绘制的对象。
void DrawScene()
{
//Clear our backbuffer
float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);
//Draw the triangle
d3d11DevCon->DrawIndexed( 6, 0, 0 );//6个索引
//Present the backbuffer to the screen
SwapChain->Present(0, 0);
}
思考
1.制作两个索引缓冲区。一个装一个三角形,另一个装两个三角形。将所有顶点放在一个缓冲区中,然后在顶点缓冲区中只绘制后两个三角形。HINT—>>(DrawIndexed方法的第三个参数)
2.做与第一个练习中相同的事情,但不要有两个索引缓冲区,而是制作两个顶点缓冲区和一个索引缓冲区时。HINT—>>(DrawIndexed方法的第二个参数)
代码
main.cpp
//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <xnamath.h>
//Global Declarations - Interfaces//
IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;
///////////////**************new**************////////////////////
ID3D11Buffer* squareIndexBuffer;
ID3D11Buffer* squareVertBuffer;
///////////////**************new**************////////////////////
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;
//Global Declarations - Others//
LPCTSTR WndClassName = L"firstwindow";
HWND hwnd = NULL;
HRESULT hr;
const int Width = 300;
const int Height = 300;
//Function Prototypes//
bool InitializeDirect3d11App(HINSTANCE hInstance);
void CleanUp();
bool InitScene();
void UpdateScene();
void DrawScene();
bool InitializeWindow(HINSTANCE hInstance,
int ShowWnd,
int width, int height,
bool windowed);
int messageloop();
LRESULT CALLBACK WndProc(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
//Vertex Structure and Vertex Layout (Input Layout)//
struct Vertex //Overloaded Vertex Structure
{
Vertex(){}
Vertex(float x, float y, float z,
float cr, float cg, float cb, float ca)
: pos(x,y,z), color(cr, cg, cb, ca){}
XMFLOAT3 pos;
XMFLOAT4 color;
};
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "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 },
};
UINT numElements = ARRAYSIZE(layout);
int WINAPI WinMain(HINSTANCE hInstance, //Main windows function
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true))
{
MessageBox(0, L"Window Initialization - Failed",
L"Error", MB_OK);
return 0;
}
if(!InitializeDirect3d11App(hInstance)) //Initialize Direct3D
{
MessageBox(0, L"Direct3D Initialization - Failed",
L"Error", MB_OK);
return 0;
}
if(!InitScene()) //Initialize our scene
{
MessageBox(0, L"Scene Initialization - Failed",
L"Error", MB_OK);
return 0;
}
messageloop();
CleanUp();
return 0;
}
bool InitializeWindow(HINSTANCE hInstance,
int ShowWnd,
int width, int height,
bool windowed)
{
typedef struct _WNDCLASS {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2);
wc.lpszMenuName = NULL;
wc.lpszClassName = WndClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Error registering class",
L"Error", MB_OK | MB_ICONERROR);
return 1;
}
hwnd = CreateWindowEx(
NULL,
WndClassName,
L"Lesson 4 - Begin Drawing",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height,
NULL,
NULL,
hInstance,
NULL
);
if (!hwnd)
{
MessageBox(NULL, L"Error creating window",
L"Error", MB_OK | MB_ICONERROR);
return 1;
}
ShowWindow(hwnd, ShowWnd);
UpdateWindow(hwnd);
return true;
}
bool InitializeDirect3d11App(HINSTANCE hInstance)
{
//Describe our Buffer
DXGI_MODE_DESC bufferDesc;
ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));
bufferDesc.Width = Width;
bufferDesc.Height = Height;
bufferDesc.RefreshRate.Numerator = 60;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
//Describe our SwapChain
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
swapChainDesc.BufferDesc = bufferDesc;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.Windowed = TRUE;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
//Create our SwapChain
hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL,
D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);
//Create our BackBuffer
ID3D11Texture2D* BackBuffer;
hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer );
//Create our Render Target
hr = d3d11Device->CreateRenderTargetView( BackBuffer, NULL, &renderTargetView );
BackBuffer->Release();
//Set our Render Target
d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, NULL );
return true;
}
void CleanUp()
{
//Release the COM Objects we created
SwapChain->Release();
d3d11Device->Release();
d3d11DevCon->Release();
renderTargetView->Release();
///////////////**************new**************////////////////////
squareVertBuffer->Release();
squareIndexBuffer->Release();
///////////////**************new**************////////////////////
VS->Release();
PS->Release();
VS_Buffer->Release();
PS_Buffer->Release();
vertLayout->Release();
}
bool InitScene()
{
//Compile Shaders from shader file
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_0", 0, 0, 0, &PS_Buffer, 0, 0);
//Create the Shader Objects
hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);
//Set Vertex and Pixel Shaders
d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);
///////////////**************new**************////////////////////
//Create the vertex buffer
Vertex v[] =
{
Vertex( -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f ),
Vertex( -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f ),
Vertex( 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f ),
Vertex( 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f ),
};
DWORD indices[] = {
0, 1, 2,
0, 2, 3,
};
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &squareIndexBuffer);
d3d11DevCon->IASetIndexBuffer( squareIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = v;
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &squareVertBuffer);
///////////////**************new**************////////////////////
//Set the vertex buffer
UINT stride = sizeof( Vertex );
UINT offset = 0;
d3d11DevCon->IASetVertexBuffers( 0, 1, &squareVertBuffer, &stride, &offset );
//Create the Input Layout
hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(),
VS_Buffer->GetBufferSize(), &vertLayout );
//Set the Input Layout
d3d11DevCon->IASetInputLayout( vertLayout );
//Set Primitive Topology
d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
//Create the Viewport
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = Width;
viewport.Height = Height;
//Set the Viewport
d3d11DevCon->RSSetViewports(1, &viewport);
return true;
}
void UpdateScene()
{
}
void DrawScene()
{
//Clear our backbuffer
float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);
///////////////**************new**************////////////////////
//Draw the triangle
d3d11DevCon->DrawIndexed( 6, 0, 0 );
///////////////**************new**************////////////////////
//Present the backbuffer to the screen
SwapChain->Present(0, 0);
}
int messageloop(){
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
while(true)
{
BOOL PeekMessageL(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
// run game code
UpdateScene();
DrawScene();
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
switch( msg )
{
case WM_KEYDOWN:
if( wParam == VK_ESCAPE ){
DestroyWindow(hwnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,
msg,
wParam,
lParam);
}
Effects.fx
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float4 Color : COLOR;
};
VS_OUTPUT VS(float4 inPos : POSITION, float4 inColor : COLOR)
{
VS_OUTPUT output;
output.Pos = inPos;
output.Color = inColor;
return output;
}
float4 PS(VS_OUTPUT input) : SV_TARGET
{
return input.Color;
}