注:下面涉及的代码都基于这篇文章的内容
https://blog.csdn.net/Kurozaki_Kun/article/details/86709050
要在DX上绘制一个基本图形,大体流程有以下几步
主要针对于步骤2,绘制什么样的几何体应该需要做一下封装,我希望能够用一个结构体描述顶点,通过不同的函数为结构体填充顶点信息,然后根据结构体创建顶点缓冲,索引缓冲。
如下,定义一个结构体Shape3D
struct Shape3DVertex;
struct Shape3D;
#include "AppPublic.h"
struct Shape3DVertex // 顶点的结构
{
DirectX::XMFLOAT3 Pos; // 仅描述顶点的坐标
};
struct Shape3D // 图形的结构体
{
Shape3DVertex* VertexArrPtr;
UINT VertexNum;
WORD* IndexArrPtr;
UINT IndexNum;
~Shape3D()
{
delete[] VertexArrPtr;
delete[] IndexArrPtr;
}
};
这里分别演示如何创建长方体,圆柱,球体和正棱锥四种图形。声明下面函数
// 创建长方体
bool Shape3DCreateCuboid(float length, float width, float height, Shape3D* pShape);
//
// 创建圆柱
//
// diff 用于描述用正 n 边形拟合圆柱,其中 n = diff
//
bool Shape3DCreateCylinder(float radius, float height, UINT diff, Shape3D* pShape);
//
// 创建一个球
//
// radius: 半径
// longitude: 经线的数量
// latitude: 纬线的数量(不包含两极)
//
// 本算法可优化,做出 1/8 球面的顶点,然后利用对称性
//
bool Shape3DCreateBall(float radius, UINT longitude, UINT latitude, Shape3D* pShape);
//
// 创建一个正棱锥图形
//
bool Shape3DCreateRegularPyramid(float radius, float height, UINT sideNum, Shape3D* pShape);
接着是函数实现
bool Shape3DCreateCuboid(float length, float width, float height, Shape3D * pShape)
{
if (nullptr == pShape)
return false;
float x = length / 2;
float y = width / 2;
float z = height / 2;
DirectX::XMFLOAT3 poses[] =
{
DirectX::XMFLOAT3(-x, +y, +z),
DirectX::XMFLOAT3(+x, +y, +z),
DirectX::XMFLOAT3(+x, -y, +z),
DirectX::XMFLOAT3(-x, -y, +z),
DirectX::XMFLOAT3(-x, +y, -z),
DirectX::XMFLOAT3(+x, +y, -z),
DirectX::XMFLOAT3(+x, -y, -z),
DirectX::XMFLOAT3(-x, -y, -z),
};
WORD indexes[] =
{
3, 1, 0,
2, 1, 3,
0, 5, 4,
1, 5, 0,
3, 4, 7,
0, 4, 3,
1, 6, 5,
2, 6, 1,
2, 7, 6,
3, 7, 2,
6, 4, 5,
7, 4, 6,
};
pShape->IndexNum = ARRAYSIZE(indexes);
pShape->VertexNum = ARRAYSIZE(poses);
pShape->IndexArrPtr = new WORD[pShape->IndexNum];
pShape->VertexArrPtr = new Shape3DVertex[pShape->VertexNum];
// fill index array
memcpy(pShape->IndexArrPtr, indexes, sizeof(WORD)*pShape->IndexNum);
// fill vertex array
for (UINT i = 0; i < pShape->VertexNum; i++)
pShape->VertexArrPtr[i] = { poses[i] };
return true;
}
bool Shape3DCreateCylinder(float r, float h, UINT n, Shape3D * pShape)
{
if (nullptr == pShape)
return false;
if (n < 6) n = 6;
if (n > 64) n = 64;
pShape->VertexNum = (n + 1) << 1; // n*2+2 个顶点
pShape->VertexArrPtr = new Shape3DVertex[pShape->VertexNum];
pShape->IndexNum = (n << 2) * 3; // n*4个三角形
pShape->IndexArrPtr = new WORD[pShape->IndexNum];
const float _2pi_div_n = DirectX::XM_PI * 2 / n;
const float _h_div_2 = h / 2;
float sint = 0.0f, cost = 0.0f;
//
// 填充顶点数组
//
for (UINT i = 0; i < n; i++) // 两个底面圆周的顶点
{
sint = sin(_2pi_div_n * i) * r;
cost = cos(_2pi_div_n * i) * r;
pShape->VertexArrPtr[(i << 1) + 0] = { {cost, sint, +_h_div_2} };
pShape->VertexArrPtr[(i << 1) + 1] = { {cost, sint, -_h_div_2} };
}
pShape->VertexArrPtr[(n << 1) + 0] = { {0.0f, 0.0f, +_h_div_2 } }; // 两个底面的中心
pShape->VertexArrPtr[(n << 1) + 1] = { {0.0f, 0.0f, -_h_div_2 } };
//
// 填充索引数组
//
UINT _index_seq[] =
{
1, 2, 0,
3, 2, 1
};
for (UINT i = 0; i < n; i++)
{
for (int j = 0; j < 6; j++)
pShape->IndexArrPtr[i * 6 + j] = (_index_seq[j] + i * 2) % (n << 1);
pShape->IndexArrPtr[(n + i) * 6 + 0] = n * 2 + 0;
pShape->IndexArrPtr[(n + i) * 6 + 1] = (i * 2 + 0) % (n << 1);
pShape->IndexArrPtr[(n + i) * 6 + 2] = (i * 2 + 2) % (n << 1);
pShape->IndexArrPtr[(n + i) * 6 + 3] = n * 2 + 1;
pShape->IndexArrPtr[(n + i) * 6 + 4] = (i * 2 + 3) % (n << 1);
pShape->IndexArrPtr[(n + i) * 6 + 5] = (i * 2 + 1) % (n << 1);
}
return true;
}
bool Shape3DCreateBall(float radius, UINT longitude, UINT latitude, Shape3D * pShape)
{
if (nullptr == pShape)
return false;
if (longitude < 16) longitude = 16;
if (latitude < 8) latitude = 8;
UINT n = longitude * latitude;
// 顶点数量 = 经纬线交点 + 2个极点
pShape->VertexNum = n + 2;
pShape->IndexNum = n * 6;
pShape->VertexArrPtr = new Shape3DVertex[pShape->VertexNum];
pShape->IndexArrPtr = new WORD[pShape->IndexNum];
auto vptr = pShape->VertexArrPtr;
// 纬线变化的角度(纬度)增量
// 因为 n 条纬线(不含极点)会将球分割成 n+1 个带状部分
// 纬度变化从一个极点到另一个极点,可以认为跨越的角度是 180°(π)
const float delta_lati = DirectX::XM_PI / (latitude + 1);
// 类似于圆柱,每条纬线要被经线分割
// 纬线是一个圆周,跨越角度是360°(2π)
const float delta_long = DirectX::XM_PI * 2 / (longitude);
for (UINT i = 0; i < latitude; i++) // 求出每条纬线上的交点
{
float radius_i = sin((i + 1)*delta_lati)*radius; // 经线的半径
float zval_i = cos((i + 1)*delta_lati)*radius; // 纬线的半径
for (UINT j = 0; j < longitude; j++)
{
float cost = cos(j * delta_long);
float sint = sin(j * delta_long);
vptr[i*longitude + j] = { {cost*radius_i, sint*radius_i, zval_i} };
}
}
vptr[n + 0] = { { 0.0f, 0.0f, +radius } };
vptr[n + 1] = { { 0.0f, 0.0f, -radius } };
auto iptr = pShape->IndexArrPtr;
for (UINT i = 0; i < latitude - 1; i++)
{
for (UINT j = 0; j < longitude; j++)
{
int x0 = j;
int x1 = (j + 1) % longitude;
int y0 = i;
int y1 = i + 1;
iptr[0] = y0 * longitude + x0;
iptr[1] = y1 * longitude + x0;
iptr[2] = y0 * longitude + x1;
iptr[3] = y0 * longitude + x1;
iptr[4] = y1 * longitude + x0;
iptr[5] = y1 * longitude + x1;
iptr += 6;
}
}
for (UINT i = 0; i < longitude; i++)
{
iptr[0] = n + 0;
iptr[1] = i;
iptr[2] = (i + 1) % longitude;
iptr[3] = n + 1;
iptr[4] = (i + 1) % longitude + (longitude) * (latitude - 1);
iptr[5] = i + (longitude) * (latitude - 1);
iptr += 6;
}
return true;
}
bool Shape3DCreateRegularPyramid(float radius, float height, UINT sideNum, Shape3D * pShape)
{
if (nullptr == pShape)
return false;
if (sideNum < 3) sideNum = 3; // 最小必须是正三棱锥
pShape->VertexNum = sideNum + 2;
pShape->IndexNum = sideNum * 2 * 3; // 侧面和底面
pShape->VertexArrPtr = new Shape3DVertex[pShape->VertexNum];
pShape->IndexArrPtr = new WORD[pShape->IndexNum];
const float _2pi_div_side_num = 2 * DirectX::XM_PI / sideNum;
for (UINT i = 0; i < sideNum; i++)
{
float cost = cos(i * _2pi_div_side_num) * radius;
float sint = sin(i * _2pi_div_side_num) * radius;
pShape->VertexArrPtr[i] = { {cost, sint, 0.0f} };
}
pShape->VertexArrPtr[sideNum + 0] = { {0.0f, 0.0f, height} };
pShape->VertexArrPtr[sideNum + 1] = { {0.0f, 0.0f, 0.0f} };
auto iptr = pShape->IndexArrPtr;
for (UINT i = 0; i < sideNum; i++)
{
UINT p0 = i;
UINT p1 = (i + 1) % sideNum;
iptr[0] = sideNum + 0;
iptr[1] = p0;
iptr[2] = p1;
iptr[3] = sideNum + 1;
iptr[4] = p1;
iptr[5] = p0;
iptr += 6;
}
return true;
}
定义 D3DGeometryTestApp 的类,继承D3DApp
struct CBuffer;
class D3DGeometryTestApp;
#include "D3DApp.h"
using namespace DirectX;
struct CBuffer
{
XMMATRIX world;
XMMATRIX view;
};
class D3DGeometryTestApp : public D3DApp
{
private:
ID3D11InputLayout* m_pInputLayout;
ID3D11VertexShader* m_pVertexShader;
ID3D11GeometryShader* m_pGeometryShader;
ID3D11PixelShader* m_pPixelShader;
ID3D11Buffer* m_pVertexBuffer;
ID3D11Buffer* m_pIndexBuffer;
ID3D11Buffer* m_pConstBuffer;
UINT m_IBCount;
public:
D3DGeometryTestApp(HINSTANCE hInstance, HWND hWnd);
virtual ~D3DGeometryTestApp();
virtual void Update() override;
virtual void Render() override;
};
实现
D3DGeometryTestApp::D3DGeometryTestApp(HINSTANCE hInstance, HWND hWnd)
:D3DApp(hInstance, hWnd)
{
HRESULT hr;
ID3DBlob* pVSBlob = nullptr;
ID3DBlob* pGSBlob = nullptr;
ID3DBlob* pPSBlob = nullptr;
LPCWSTR filename = L"geomtry.fx";
hr = ShaderUtil::CompileFromFile(filename, "VS", "vs_4_0", &pVSBlob);
if (FAILED(hr))
EXCEPT("Error at compile VS");
//
// VS
//
hr = m_pD3dDevice->CreateVertexShader(
pVSBlob->GetBufferPointer(),
pVSBlob->GetBufferSize(),
nullptr,
&m_pVertexShader
);
if (FAILED(hr))
EXCEPT("Error at create VS");
//
// PS
//
hr = ShaderUtil::CompileFromFile(filename, "PS", "ps_4_0", &pPSBlob);
if (FAILED(hr))
EXCEPT("Error at compile PS");
hr = m_pD3dDevice->CreatePixelShader(
pPSBlob->GetBufferPointer(),
pPSBlob->GetBufferSize(),
nullptr,
&m_pPixelShader
);
if (FAILED(hr))
EXCEPT("Error at create PS");
//
// InputLayout
//
D3D11_INPUT_ELEMENT_DESC inputDescs[] =
{
{ "POS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numDesc = ARRAYSIZE(inputDescs);
hr = m_pD3dDevice->CreateInputLayout(
inputDescs,
numDesc,
pVSBlob->GetBufferPointer(),
pVSBlob->GetBufferSize(),
&m_pInputLayout
);
if (FAILED(hr))
EXCEPT("Error at create input layout");
Shape3D shape;
// Shape3DCreateCuboid(0.8f, 0.6f, 0.6f, &shape);
// Shape3DCreateCylinder(0.5f, 0.4f, 40, &shape);
Shape3DCreateBall(0.5f, 40, 20, &shape);
// Shape3DCreateRegularPyramid(0.4f, 0.4f, 12, &shape);
//
// vertex buffer
//
D3D11_BUFFER_DESC buffDesc = {};
D3D11_SUBRESOURCE_DATA initData = {};
buffDesc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_VERTEX_BUFFER;
buffDesc.Usage = D3D11_USAGE::D3D11_USAGE_DEFAULT;
buffDesc.CPUAccessFlags = 0;
buffDesc.ByteWidth = sizeof(Shape3DVertex) * shape.VertexNum;
initData.pSysMem = shape.VertexArrPtr;
hr = m_pD3dDevice->CreateBuffer(&buffDesc, &initData, &m_pVertexBuffer);
if (FAILED(hr))
EXCEPT("Error at create vertex buffer");
//
// index buffer
//
buffDesc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_INDEX_BUFFER;
buffDesc.Usage = D3D11_USAGE::D3D11_USAGE_DEFAULT;
buffDesc.CPUAccessFlags = 0;
buffDesc.ByteWidth = sizeof(WORD) * shape.IndexNum;
initData.pSysMem = shape.IndexArrPtr;
hr = m_pD3dDevice->CreateBuffer(&buffDesc, &initData, &m_pIndexBuffer);
if (FAILED(hr))
EXCEPT("Error at create index buffer");
m_IBCount = shape.IndexNum;
//
// const buffer
//
buffDesc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_CONSTANT_BUFFER;
buffDesc.Usage = D3D11_USAGE::D3D11_USAGE_DEFAULT;
buffDesc.CPUAccessFlags = 0;
buffDesc.ByteWidth = sizeof(CBuffer);
hr = m_pD3dDevice->CreateBuffer(&buffDesc, nullptr, &m_pConstBuffer);
if (FAILED(hr))
EXCEPT("Error at create const buffer");
if (pVSBlob) pVSBlob->Release();
if (pGSBlob) pGSBlob->Release();
if (pPSBlob) pPSBlob->Release();
ID3D11RasterizerState* pRSState; // 设置渲染模式为线框
D3D11_RASTERIZER_DESC rd = {};
m_pD3dDevice->CreateRasterizerState(&rd, &pRSState);
rd.CullMode = D3D11_CULL_MODE::D3D11_CULL_BACK;
rd.FillMode = D3D11_FILL_MODE::D3D11_FILL_WIREFRAME;
m_pD3dDevice->CreateRasterizerState(&rd, &pRSState);
m_pImmediateContext->RSSetState(pRSState);
UINT stride = sizeof(Shape3DVertex);
UINT offset = 0;
m_pImmediateContext->IASetInputLayout(m_pInputLayout);
m_pImmediateContext->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);
m_pImmediateContext->IASetIndexBuffer(m_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_pConstBuffer);
m_pImmediateContext->VSSetShader(m_pVertexShader, nullptr, 0);
m_pImmediateContext->PSSetShader(m_pPixelShader, nullptr, 0);
}
D3DGeometryTestApp::~D3DGeometryTestApp()
{
if (m_pInputLayout) m_pInputLayout->Release();
if (m_pVertexShader) m_pVertexShader->Release();
if (m_pGeometryShader) m_pGeometryShader->Release();
if (m_pPixelShader) m_pPixelShader->Release();
if (m_pVertexBuffer) m_pVertexBuffer->Release();
if (m_pIndexBuffer) m_pIndexBuffer->Release();
if (m_pConstBuffer) m_pConstBuffer->Release();
}
void D3DGeometryTestApp::Update()
{
static float time = 0.0f;
static CBuffer cb;
time += 0.0008f;
cb.world =
XMMatrixRotationZ(time);
cb.view =
XMMatrixLookAtLH({ 2.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f })*
XMMatrixPerspectiveFovLH(XM_PIDIV4, 1.33f, 0.01f, 20.f);
m_pImmediateContext->UpdateSubresource(m_pConstBuffer, 0, nullptr, &cb, 0, 0);
}
void D3DGeometryTestApp::Render()
{
if (!m_pImmediateContext) return;
m_pImmediateContext->ClearRenderTargetView(m_pRenderTargetView, D3DApp::_bg_color);
m_pImmediateContext->ClearDepthStencilView(m_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
m_pImmediateContext->DrawIndexed(m_IBCount, 0, 0);
m_pSwapChain->Present(0, 0);
}
最后还有着色器文件geomtry.fx
cbuffer ConstBuff : register(b0)
{
row_major matrix world;
row_major matrix view;
}
void VS(
float3 pos : POS,
out float4 outPos : SV_Position
)
{
outPos = float4(pos, 1);
outPos = mul(outPos, world);
outPos = mul(outPos, view);
}
void PS(
float4 pos : SV_Position,
out float4 color : SV_Target
)
{
color = float4(0.6f, 0.6f, 0.6f, 1.0f); // 简单的单色线框
}
一个简单的长方体(长宽高设置为相同的值就是正方体了)
一个四棱锥
当n较大时,n棱锥可以用来表示圆锥
圆柱
球体