【DirectX11学习02】绘制单个基本几何体的线框

注:下面涉及的代码都基于这篇文章的内容 

https://blog.csdn.net/Kurozaki_Kun/article/details/86709050

绘制过程

要在DX上绘制一个基本图形,大体流程有以下几步

  1. 给出输入布局(主要是描述顶点的格式)
  2. 给出描述几何体的顶点,索引,拓扑图元
  3. 加载并创建各种着色器
  4. 创建索引缓冲,顶点缓冲,以及必要的常量缓冲
  5. 将以上内容绑定到渲染管线,然后绘制

简单封装

主要针对于步骤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);    // 简单的单色线框
}

测试效果

一个简单的长方体(长宽高设置为相同的值就是正方体了)

【DirectX11学习02】绘制单个基本几何体的线框_第1张图片

一个四棱锥

【DirectX11学习02】绘制单个基本几何体的线框_第2张图片

当n较大时,n棱锥可以用来表示圆锥

【DirectX11学习02】绘制单个基本几何体的线框_第3张图片

圆柱

【DirectX11学习02】绘制单个基本几何体的线框_第4张图片

球体

【DirectX11学习02】绘制单个基本几何体的线框_第5张图片

 

你可能感兴趣的:(c++,DirectX)