【CDX随笔总结】P1_Vertex 的整理和分析

效果图

提交单:https://github.com/CartmanORCamille/CDX/commit/afc7a52fc96466ddb1ab5233e4986bb739037e33

关键点

  • 渲染管线基础。
  • Shader
  • C 与 C++ 交叉编译和全局变量。
  • 位表(键盘事件,摄像机视角与观察点)。

渲染几何体基础

画一个正方体

【猜测】demo 里(gif图)在旋转的时候看不到底部&顶部三角形,但是在静止时可以看到,应该是 Z 轴没设置好导致摄像机范围内看不到两个面。

demo 里是用索引数据绘制正方体,三角形序列用 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST。
demo 里用三角形列表而不是三角形带,因为三角形带导致几何体必须按照带状方式组织,实现难度较大。三角形列表具有更好的灵活性(三角形不必彼此相连),三角形连接顺序都是顺时针。

主要渲染过程

  1. 创建顶点数据。
  2. 创建顶点索引数据。
  3. 创建 shader 编译对象,读取 shader 对象里的 Technique 和常量齐次矩阵(世界矩阵 * 观察矩阵 * 投影矩阵)对象。
  4. 创建渲染视图(IA 阶段装配信息)。
  5. 创建渲染模式,并默认选择线框模式渲染。
  6. IA 阶段绑定各项数据。
  7. 获取 Technique 数据对象。
  8. 根据 Technique pass 数量获取 pass 并绑定的上下文(Context)。
  9. 绘制索引顶点。

编译 Shader

编译 Shader

编译 Shader 对象并存入指针 pCompiledShader
C++ code.

// build shader.
hrResult = D3DX11CompileFromFile(
	L".\\FX\\P1.fx", NULL, NULL, NULL, "fx_5_0", dwShaderFlags, 0, NULL, &pCompiledShader, &pCompiledErrMsgs, NULL
);

shader code.

technique11 ColorTech
{
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
		//SetRasterizerState(RS);
	}
}

Effect 和 technique

effect

effect 是个框架。它包含以下。
一个 shader 通常保存在 effect 文件中(fx),这是一个纯文本(类似 C 语言代码保存在 .c 文件中)。
在一个 effect 文件中,必须包含一个 technique,一个 technique 必须包含一个 pass。

technique

一个 technique 由一个或多个 pass 组成,用于创建一个渲染技术。每个 pass 实现一种不同的几何体渲染方式。可以按照某种方式将多个 pass 的渲染结果混合在一起就可以得到想要的渲染结果。
比如在地形渲染中使用多通道纹理映射技术(multi-pass texturing technique),多通道技术通常会占用大量的系统资源,因为每个 pass 要对几何体进行一次渲染。
在 shader 里声明定义个 technique 关键字是 technique11

pass

一个 pass 由一个顶点着色器,一个可选的集合着色器,一个像素着色器和一些渲染状态组成,这部分定义了 pass 的几何体渲染方式。
像素着色器也是可选的,比如指向绘制深度缓冲,不想绘制后台缓冲,在这种情况下就不需要像素着色器计算像素颜色。

Demo

在 demo 里用了 1 个 technqiue,包含 1 个 pass。在这个 pass 里指定了顶点着色器和像素着色器,注释了渲染状态(渲染状态初始化时指定了,并由键盘控制)。

创建 Effect 并存入指针 pFX

hrResult = D3DX11CreateEffectFromMemory(
	pCompiledShader->GetBufferPointer(),
	pCompiledShader->GetBufferSize(),
	0,
	ptCdx->pD3d,
	&(ptP1Demo->pFX)
);
TH_CHECKERR_FAILED(hrResult);

获取指定的 technique 和齐次坐标矩阵。

ptP1Demo->pTech = ptP1Demo->pFX->GetTechniqueByName("ColorTech");
ptP1Demo->pMfxWorldViewProj = ptP1Demo->pFX->GetVariableByName("gWorldViewProj")->AsMatrix();

渲染视图

创建输入布局对象描述数组

D3D11_INPUT_ELEMENT_DESC arrtVertexDesc[csMaxVertexDesc] =
{
	{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
	{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, sizeof(XMFLOAT3), D3D11_INPUT_PER_VERTEX_DATA, 0}
};

创建 pass 描述

D3DX11_PASS_DESC tPassDesc = { 0 };
hrRet = ptP1Demo->pTech->GetPassByIndex(0)->GetDesc(&tPassDesc);
TH_CHECKERR_FAILED(hrRet);

创建布局对象

hrRet = ptCdx->pD3d->CreateInputLayout(
	arrtVertexDesc, csMaxVertexDesc, tPassDesc.pIAInputSignature, tPassDesc.IAInputSignatureSize, &(ptP1Demo->pInputLayout)
);
TH_CHECKERR_FAILED(hrRet);

渲染模式(渲染状态)

分别创建线框模式和填充模式渲染状态,默认使用线框模式。

int P1_CreateRasterizer(CDXDISPATCH_PTR ptCdx, P1DEMO_PTR ptP1Demo)
{
	int nResult = C_FALSE;
	HRESULT hrRet = E_FAIL;
	D3D11_RASTERIZER_DESC tWrDesc;
	D3D11_RASTERIZER_DESC tSoDesc;

	ZeroMemory(&tWrDesc, sizeof(D3D11_RASTERIZER_DESC));
	tWrDesc.FillMode = D3D11_FILL_WIREFRAME;
	tWrDesc.CullMode = D3D11_CULL_BACK;
	tWrDesc.FrontCounterClockwise = false;
	tWrDesc.DepthClipEnable = true;

	ZeroMemory(&tSoDesc, sizeof(D3D11_RASTERIZER_DESC));
	tSoDesc.FillMode = D3D11_FILL_SOLID;
	tSoDesc.CullMode = D3D11_CULL_BACK;
	tSoDesc.FrontCounterClockwise = false;
	tSoDesc.DepthClipEnable = true;

	hrRet = ptCdx->pD3d->CreateRasterizerState(&tWrDesc, &(ptP1Demo->pWirerameRS));
	TH_CHECKERR_FAILED(hrRet);
	hrRet = ptCdx->pD3d->CreateRasterizerState(&tSoDesc, &(ptP1Demo->pSoildRS));
	TH_CHECKERR_FAILED(hrRet);
	ptCdx->pD3dContext->RSSetState(ptP1Demo->pWirerameRS);

	nResult = C_OK;
Exit0:
	return nResult;
}

渲染阶段

清理深度缓存&模板缓存

const float carrfClearColor[4] = COLOR_LIGHTSTEELBLUE;
const unsigned int cunStride = sizeof(P1VERTEXT);
const unsigned int cunOffset = 0;

D3DX11_TECHNIQUE_DESC tTechDesc = { 0 };

ptCdx->pD3dContext->ClearRenderTargetView(
	ptCdx->pRenderTargetView,
	carrfClearColor
);

// clean z-buffer(depth buffer) and stencil buffer.
ptCdx->pD3dContext->ClearDepthStencilView(
	ptCdx->pDepthStencilView,
	D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,
	1.0f,
	0
);

绑定 IA 资源

ptCdx->pD3dContext->IASetInputLayout(g_p1DemoPtr->pInputLayout);
ptCdx->pD3dContext->IASetVertexBuffers(0, 1, &(g_p1DemoPtr->pVertexBuffer), &cunStride, &cunOffset);
ptCdx->pD3dContext->IASetIndexBuffer(g_p1DemoPtr->pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
ptCdx->pD3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

获取 pass 并渲染

hrRet = g_p1DemoPtr->pTech->GetDesc(&tTechDesc);
TH_CHECKERR_FAILED(hrRet);

for (UINT i = 0; i < tTechDesc.Passes; ++i)
{
	hrRet = g_p1DemoPtr->pTech->GetPassByIndex(i)->Apply(0, ptCdx->pD3dContext);
	TH_CHECKERR_FAILED(hrRet);

	ptCdx->pD3dContext->DrawIndexed(MAX_INDEXCOUNT, 0, 0);
}

提交管线,交换前后台缓存

hrRet = ptCdx->pDxgiSwapChain->Present(0, 0);
TH_CHECKERR_FAILED(hrRet);

Shader

demo 里只是基础的 Shader 在 Effect 文件中

  • 有 1 个常量缓存(cbuffer),用于存放 MVP 矩阵。
cbuffer cbPerObject
{
	float4x4 gWorldViewProj;
};
  • 有 1 个顶点输入结构体,结构体内是一个局部坐标,一个顶点颜色信息。
struct VertexIn
{
	float3 PosL: POSITION;
	float4 Color: COLOR;
};
  • 有 1 个顶点输出结构体,结构体内是一个齐次裁剪坐标(用于顶点的位置确定),一个顶点的颜色信息。
struct VertexOut
{
	float4 PosH : SV_POSITION;
	float4 Color: COLOR;
};
  • 有 1 个顶点着色器(函数),将顶点的局部坐标 * MVP 矩阵,得到顶点的在屏幕上的最终坐标,颜色不做处理。
VertexOut VS(VertexIn vin)
{
	VertexOut vout;
	vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
	//vout.PosH = vin.PosL;
	vout.Color = vin.Color;
	return vout;
}
  • 有 1 个像素着色器(函数),内部对颜色暂不做处理。
float4 PS(VertexOut pin) : SV_Target
{
	return pin.Color;
}
  • 有 1 个渲染状态(RasterizerState),设置渲染状态为填充颜色,启用背部消隐,顺时针环绕三角形是朝前的,但是在 shader 里未启用这个渲染状态,因为在 cpp 文件中用了。
RasterizerState RS
{
	FillMode = Solid;
	CullMode = Back;
	FrontCounterClockwise = false;
};
  • 有 1 个 technique(technique11),内部有 1 个 pass。
technique11 ColorTech
{
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
		//SetRasterizerState(RS);
	}
}

C 与 C++ 交叉编译

主项目是 C 语言,P1 Demo 是 C++ 文件。

extern "C"

extern "C" 目的是为了 C++ 能够调用 C 语言代码,加上这句后,表示这部分代码按照 C 语言的方式进行编译。
C++ 编译的时候会修改函数的名称(C++ 函数支持重载),但这样 C 语言就读不到了。

在 P1 Demo 里会使用到 D3D Math 这个数学库,这个库只能在 C++ 上用,所以这里就用到了交叉编译。

A.h
#ifdef  __cplusplus
extern "C" {
#endif
	// 这里导入所需要的代码。
    inlcude <DirectXMath.h>
    // 只在头文件中声明。
    int a;
#ifdef __cplusplus
}
#endif

A.cpp
// 在 main 函数外定义 a 变量。
int a = 1;
void main() {...}

extern "C" 是包含双重含义。首先是 extern,然后是 "C"。
extern 告诉编译器此变量会暴露,申明此变量或函数可以在本模块或其他模块中使用。
"C" 以 C 语言模式编译和链接,函数名编译后不会被修改。

全局变量

extern int a
这仅仅是个声明,而非定义,并没有为 a 分配内存空间。a 在所有模块中作为全局变量只能被定义一次(在 main 函数外),否则会报错。
B 模块想调用 A 模块中定义的变量/函数时,只需要导入头文件,虽然在编译阶段找不到此变量/函数,但是并不会报错,会在 A 模块编译生成中的目标代码找到。

与 extern 对应的是 static,static 表示变量或者函数只能在本模块使用,所以不可能被 extern "C" 修饰。

在 demo 里,C++ 调用 C 与 C 调用 C++ 都是用 extern "C",因为已经 extern 了,而且都已经导入了D3DMain.h头文件,可以链接到。

位图

Bitmap

位图用一串二进制表示布尔信息,可以节省大量的内存资源,不过位图只能简单表示 false 或 true。

用一个 byte 变量(unsigned char)就可以表示一个 8 位的信息图(表示 8 个 bool 变量)。
当需要存放大量的位图时,就创建一个 byte 类型的数组。

逻辑与&

两位同时为1,结果才为1,否则为0

逻辑非

参与运算的两个对象只要有一个为1,其值为1。

设置位图的内容

有了位图数组之后,首先需要确认需要修改的位图在数组下标是在哪一位,得到下标位置后就需要求得需求修改的位图的哪一位。

当键盘按下后,对应的位表将会被修改成1;当键盘松开后,对应的位表会被修改成0。

键盘按下

进行或运算,如果原来是1(即已经按下)则继续为1,如果为0则变更为1。

键盘松开

进行与运算,并且要取反,因为2个都为1的情况下不能恢复成0,所以取反。

void SetKeyActiveMap(unsigned long ulActiveStauts, unsigned int unNegation)
{
	const int cnStatusBits = GetNumBinaryBits(ulActiveStauts);
	const int cnArrPos = cnStatusBits / 8;
	const int cnBitPos = (cnStatusBits % 8) - 1;

	if (KEYACTIVE == unNegation)
	{
		g_bKeyActiveMap[cnArrPos] |= (1 << cnBitPos);
		THLOG("key active. now map: %d\n", g_bKeyActiveMap[cnArrPos]);
	}
	else if (KEYSHUT == unNegation)
	{
		g_bKeyActiveMap[cnArrPos] &= (~(1 << cnBitPos));
		THLOG("key shut. now map: %d\n", g_bKeyActiveMap[cnArrPos]);
	}
	
}

查询位图

void GetKeyActiveMapEx(const int nFlags, int* pnRes)
{
	const int cnTargetBit = GetNumBinaryBits(nFlags);
	const int cnArrPos = cnTargetBit / 8;
	if (nFlags >> (8 * cnArrPos) == g_bKeyActiveMap[cnArrPos])
	{
		*pnRes = C_TRUE;
	}
}

参考

https://www.cnblogs.com/Ray1024/p/6101284.html

posted @ 2023-07-05 17:07  阿初  阅读(52)  评论(0编辑  收藏  举报