大数据最新DirectX12(D3D12)基础教程(十七)—(1),2024大数据开发面试题知识点总结

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

UINT m_nNumIndices;
UINT m_nBaseVertex;
UINT m_nBaseIndex;
UINT m_nMaterialIndex;

};

typedef CAtlArray<ST_GRS_SUBMESH_DATA> CGRSSubMesh;

const UINT g_ncSlotCnt = 4; // 用4个插槽上传顶点数据
struct ST_GRS_MESH_DATA
{
const aiScene* m_paiModel;
CStringA m_strFileName;
XMMATRIX m_mxModel;

CGRSSubMesh			m_arSubMeshInfo;

CGRSARPositions		m_arPositions;
CGRSARNormals		m_arNormals;
CGRSARTexCoords		m_arTexCoords;
CGRSARVertexBones	m_arBoneIndices;
CGRSARIndices		m_arIndices;

CGRSMapString2UINT	m_mapTextrueName2Index;
CGRSMapUINT2UINT	m_mapTextureIndex2HeapIndex;

CGRSARBoneDatas		m_arBoneDatas;
CGRSMapString2UINT	m_mapName2Bone;			//名称->骨骼的索引
CGRSMapString2UINT	m_mapAnimName2Index;	//名称->动画的索引

UINT				m_nCurrentAnimIndex;	// 当前播放的动画序列索引(当前动作)

};


  这两个结构体定义的总体思路是将一个模型文件中所有的子网格顶点数据都分组连续存储在几个数组中(CGRSARPositions m\_arPositions等5个数组),并且用ST\_GRS\_SUBMESH\_DATA结构体数组来对应每个子网格数据在数组中的起始位置和长度信息(CGRSSubMesh m\_arSubMeshInfo;中存储)。


  另外用两个映射(map:CGRSMapString2UINT m\_mapTextrueName2Index;CGRSMapUINT2UINT m\_mapTextureIndex2HeapIndex;)来标识出每个子网格对应的纹理索引以及对应描述符索引。


  有了结构体,下一步就是组织代码把数据稍作转换,变换到我们的结构体中,这正好就是一开始我们说的LoadMesh函数做的事情:



BOOL LoadMesh(LPCSTR pszFileName, ST_GRS_MESH_DATA& stMeshData)
{
stMeshData.m_nCurrentAnimIndex = 0;
stMeshData.m_paiModel = g_aiImporter.ReadFile(pszFileName, ASSIMP_LOAD_FLAGS);

if (nullptr == stMeshData.m_paiModel)
{
	ATLTRACE("无法解析文件(%s):%s (%d)\n", pszFileName, g_aiImporter.GetErrorString(), ::GetLastError());
	return FALSE;
}

// 获取根节点的变换矩阵,其实就是 Module->World 的变换矩阵
stMeshData.m_mxModel = XMMatrixTranspose(MXEqual(stMeshData.m_mxModel, stMeshData.m_paiModel->mRootNode->mTransformation));

// 获取网格数量
UINT nMeshCnt = stMeshData.m_paiModel->mNumMeshes;
if ( 0 == nMeshCnt )
{
	ATLTRACE("文件(%s)中没有网格数据!\n", pszFileName);
	return FALSE;
}

const aiMesh\* paiSubMesh = nullptr;
const aiVector3D	Zero3D(0.0f, 0.0f, 0.0f);
UINT nNumBones = 0;
UINT nNumVertices = 0;
UINT nNumIndices = 0;

stMeshData.m_arSubMeshInfo.SetCount(nMeshCnt);

// 加载Vertex基本信息
for (UINT i = 0; i < nMeshCnt; i++)
{
	paiSubMesh = stMeshData.m_paiModel->mMeshes[i];

	stMeshData.m_arSubMeshInfo[i].m_nMaterialIndex = paiSubMesh->mMaterialIndex;
	stMeshData.m_arSubMeshInfo[i].m_nNumIndices = paiSubMesh->mNumFaces \* GRS_INDICES_PER_FACE;
	stMeshData.m_arSubMeshInfo[i].m_nBaseVertex = nNumVertices;
	stMeshData.m_arSubMeshInfo[i].m_nBaseIndex = nNumIndices;

	// 当前Mesh的顶点数量和索引数量累加后,就是下个Mesh顶点和索引在整体缓冲中的索引开始位置
	nNumVertices	+= stMeshData.m_paiModel->mMeshes[i]->mNumVertices;
	nNumIndices		+= stMeshData.m_arSubMeshInfo[i].m_nNumIndices;		

	// 加载顶点常规数据
	for (UINT j = 0; j < paiSubMesh->mNumVertices; j++)
	{
		stMeshData.m_arPositions.Add(XMFLOAT4(paiSubMesh->mVertices[j].x
			, paiSubMesh->mVertices[j].y
			, paiSubMesh->mVertices[j].z
			, 1.0f));

		stMeshData.m_arNormals.Add(XMFLOAT4(paiSubMesh->mNormals[j].x
			, paiSubMesh->mNormals[j].y
			, paiSubMesh->mNormals[j].z
			, 0.0f));

		// 注意这个地方只考虑一个纹理的情况,其实最多可以有八个,可以再做个循环进行加载
		const aiVector3D\* pTexCoord = paiSubMesh->HasTextureCoords(0)
			? &(paiSubMesh->mTextureCoords[0][j])
			: &Zero3D;

		stMeshData.m_arTexCoords.Add(XMFLOAT2(pTexCoord->x, pTexCoord->y));
	}

	// 加载索引数据
	for (UINT j = 0; j < paiSubMesh->mNumFaces; j++)
	{
		const aiFace& Face = paiSubMesh->mFaces[j];
		// 已经通过导入标志强制为三角形网格了,每个面就三个索引
		ATLASSERT(Face.mNumIndices == GRS_INDICES_PER_FACE);

		for (UINT k = 0; k < Face.mNumIndices; k++)
		{
			stMeshData.m_arIndices.Add(Face.mIndices[k]);
		}
	}
}

stMeshData.m_arBoneIndices.SetCount(nNumVertices);

UINT		VertexID = 0;
FLOAT		Weight = 0.0f;
UINT		nBoneIndex = 0;
CStringA	strBoneName;
aiMatrix4x4 mxBoneOffset;
aiBone\*		pBone = nullptr;

// 加载骨骼数据
for (UINT i = 0; i < nMeshCnt; i++)
{
	paiSubMesh = stMeshData.m_paiModel->mMeshes[i];

	for (UINT j = 0; j < paiSubMesh->mNumBones; j++)
	{
		nBoneIndex = 0;
		pBone = paiSubMesh->mBones[j];
		strBoneName = pBone->mName.data;

		if ( nullptr == stMeshData.m_mapName2Bone.Lookup(strBoneName) )
		{
			// 新骨头索引
			nBoneIndex = nNumBones ++;
			stMeshData.m_arBoneDatas.SetCount(nNumBones);

			stMeshData.m_arBoneDatas[nBoneIndex].m_mxBoneOffset 
				= XMMatrixTranspose(MXEqual(stMeshData.m_arBoneDatas[nBoneIndex].m_mxBoneOffset, pBone->mOffsetMatrix));

			stMeshData.m_mapName2Bone.SetAt(strBoneName, nBoneIndex);
		}
		else
		{
			nBoneIndex = stMeshData.m_mapName2Bone[strBoneName];
		}

		for (UINT k = 0; k < pBone->mNumWeights; k++)
		{
			VertexID = stMeshData.m_arSubMeshInfo[i].m_nBaseVertex + pBone->mWeights[k].mVertexId;
			Weight = pBone->mWeights[k].mWeight;
			stMeshData.m_arBoneIndices[VertexID].AddBoneData(nBoneIndex, Weight);
		}
	}
}

// 获取材质数量
UINT nMatCnt = stMeshData.m_paiModel->mNumMaterials;
UINT nTextureIndex = 0;
UINT nTmpIndex = 0;
CStringA strTextureFileName;
aiString aistrPath;
for (UINT i = 0; i < stMeshData.m_paiModel->mNumMaterials; i++)
{
	const aiMaterial\* pMaterial = stMeshData.m_paiModel->mMaterials[i];
	if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0)
	{
		if ( pMaterial->GetTexture(aiTextureType_DIFFUSE
			, 0, &aistrPath, nullptr, nullptr, nullptr, nullptr, nullptr)
			== AI_SUCCESS )
		{
			strTextureFileName = aistrPath.C\_Str();
			nTmpIndex = 0;
			if ( !stMeshData.m_mapTextrueName2Index.Lookup( strTextureFileName , nTmpIndex ) )
			{
				stMeshData.m_mapTextrueName2Index.SetAt( strTextureFileName, nTextureIndex );
				nTmpIndex = nTextureIndex;
				++ nTextureIndex;
			}	
			stMeshData.m_mapTextureIndex2HeapIndex.SetAt( i, nTmpIndex );
		}
	}
}

return TRUE;

}


  这个函数没什么复杂的逻辑,就不过多赘述了。


  最后,这样的设计就是暗示大家可以将加载模型数据的整个过程设计成一个Builder模式。当然按照本系列教程的一贯风格,就不过多讨论封装设计的问题了,只是放个图,启发一下思路:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021071812310712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQwMzgxNDM=,size_16,color_FFFFFF,t_70)


### 12.2、Layout的定义


  其次,在D3D12中就不能再简单的通过定义与shader中对应的顶点结构的方式来组织和上传数据到GPU了,具体的只需要按照D3D12\_INPUT\_ELEMENT\_DESC结构体的要求,结合数据自身的结构特点定义具体的数据上传布局。D3D12\_INPUT\_ELEMENT\_DESC结构体的定义如下:



typedef struct D3D12_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;


  其定义中InputSlot即是指定当前记录使用哪个插槽(Slot)来传输数据(理解为通道更恰当一些)。


  在本例中具体定义Layout如下:



// 定义传入管线的数据结构,这里使用了多Slot方式,注意Slot的用法
D3D12_INPUT_ELEMENT_DESC stIALayoutSphere[] =
{
{ “POSITION”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “NORMAL”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “TEXCOORD”, 0, DXGI_FORMAT_R32G32_FLOAT, 2, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “BLENDINDICES”, 0, DXGI_FORMAT_R32G32B32A32_UINT, 3, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “BLENDWEIGHT”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 3, 16, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};


  上面的代码片段中,可以看到整齐排列的第四列,按顺序指定了对应的Slot号,此时第五列的含义就是相对于当前Slot中的偏移量了,因此前4个偏移量就都是从头开始的,就是0了,也就是没有偏移的意思。而第五条记录中,因为与第四条记录使用了相同的插槽,所以偏移就是第四条记录表示的数据长度。


  同时,第一列中除了常见的位置、法线、纹理坐标这三个语义外,还增加了BLENDINDICES、BLENDWEIGHT两个新的语义,其实就是代表顶点数据中的骨骼绑定需要的“骨骼索引”和“受影响权重”。对应的Shader中的Vertex Shader输入参数结构(也就是顶点数据结构)定义如下(注意语义和数据类型及向量大小):



struct VSInput
{
float4 position : POSITION0; //顶点位置
float4 normal : NORMAL0; //法线
float2 texuv : TEXCOORD0; //纹理坐标
uint4 bonesID : BLENDINDICES0; //骨骼索引
float4 fWeights : BLENDWEIGHT0; //骨骼权重
};


### 12.3、缓冲区准备


  准备完了合适的数据,并且定义完对应的Layout,接着就是创建数据的缓冲区。因为这次我们使用的是多Slot上传数据,所以在对应的顶点数据缓冲区的创建上也是有对应的特殊处理的,核心的思想就是不能再像以前那样按照一个结构体数组的形式创建一个Vertex Buffer和一个Index Buffer就完事了,多Slot情况下,就需要为每组数据分开创建缓冲区。在本例中是像下面这样创建了4个顶点数据缓冲区,以及对应的顶点缓冲描述符:



size_t szAlign = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
// 计算所有的缓冲大小
size_t szPositions = g_stMeshData.m_arPositions.GetCount() * sizeof( g_stMeshData.m_arPositions[0] );
size_t szNormals = g_stMeshData.m_arNormals.GetCount() * sizeof( g_stMeshData.m_arNormals[0] );
size_t szTexCoords = g_stMeshData.m_arTexCoords.GetCount() * sizeof( g_stMeshData.m_arTexCoords[0] );
size_t szBoneIndices = g_stMeshData.m_arBoneIndices.GetCount() * sizeof( g_stMeshData.m_arBoneIndices[0] );
size_t szIndices = g_stMeshData.m_arIndices.GetCount() * sizeof( g_stMeshData.m_arIndices[0] );

// 需要的缓冲大小+64k-1 使得刚好是64k边界大小时,可以多分配64k出来,防止CreatePlacedResource报错
size_t szVBBuffer = GRS_UPPER( szPositions, szAlign )
+ GRS_UPPER( szNormals, szAlign )
+ GRS_UPPER( szTexCoords, szAlign )
+ GRS_UPPER( szBoneIndices, szAlign )
+ szAlign - 1;

D3D12_HEAP_DESC stUploadHeapDesc = { };
// 上传堆类型就是普通的缓冲,可以摆放任意数据
stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
// 实际数据大小的5*64K边界对齐大小,因为有5个Buffer
stUploadHeapDesc.SizeInBytes = GRS_UPPER( szVBBuffer, szAlign );
// 注意上传堆肯定是Buffer类型,可以不指定对齐方式,其默认是64k边界对齐
stUploadHeapDesc.Alignment = 0;
stUploadHeapDesc.Properties = stUploadHeapProps;

// 创建顶点数据的上传堆
GRS_THROW_IF_FAILED( pID3D12Device4->CreateHeap( &stUploadHeapDesc, IID_PPV_ARGS( &pIUploadHeapModel ) ) );
GRS_SET_D3D12_DEBUGNAME_COMPTR( pIUploadHeapModel );

size_t szOffset = 0;
BYTE* pData = nullptr;
// Positions Upload Buffer
stBufferResSesc.Width = GRS_UPPER( szPositions, szAlign );
GRS_THROW_IF_FAILED( pID3D12Device4->CreatePlacedResource(
pIUploadHeapModel.Get()
, szOffset
, &stBufferResSesc
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS( &pIVBPositionsUp ) ) );

GRS_SET_D3D12_DEBUGNAME_COMPTR( pIVBPositionsUp );

GRS_THROW_IF_FAILED( pIVBPositionsUp->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) ) );
// 第一次 Copy!CPU Memory -> Share Memory 实质上就是在内存中倒腾,只是物理地址不同
// 估计会有那么一天,这个Copy动作可能被简单替换为CPU物理地址映射的变更,传说Vulkan中已经大致是这样了
memcpy( pData, g_stMeshData.m_arPositions.GetData(), szPositions );
pIVBPositionsUp->Unmap( 0, nullptr );
pData = nullptr;

// Normals Upload Buffer
szOffset += stBufferResSesc.Width;
stBufferResSesc.Width = GRS_UPPER( szNormals, szAlign );

GRS_THROW_IF_FAILED( pID3D12Device4->CreatePlacedResource(
pIUploadHeapModel.Get()
, szOffset
, &stBufferResSesc
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS( &pIVBNormalsUp ) ) );

GRS_SET_D3D12_DEBUGNAME_COMPTR( pIVBNormalsUp );

GRS_THROW_IF_FAILED( pIVBNormalsUp->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) ) );
// 第一次 Copy!CPU Memory -> Share Memory 实质上就是在内存中倒腾,只是物理地址不同
// 估计会有那么一天,这个Copy动作可能被简单替换为CPU物理地址映射的变更,传说Vulkan中已经大致是这样了
memcpy( pData, g_stMeshData.m_arNormals.GetData(), szNormals );
pIVBNormalsUp->Unmap( 0, nullptr );
pData = nullptr;

// TexCoords Upload Buffer
szOffset += stBufferResSesc.Width;
stBufferResSesc.Width = GRS_UPPER( szTexCoords, szAlign );

GRS_THROW_IF_FAILED( pID3D12Device4->CreatePlacedResource(
pIUploadHeapModel.Get()
, szOffset
, &stBufferResSesc
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS( &pIVBTexCoordsUp ) ) );

GRS_SET_D3D12_DEBUGNAME_COMPTR( pIVBTexCoordsUp );

GRS_THROW_IF_FAILED( pIVBTexCoordsUp->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) ) );
// 第一次 Copy!CPU Memory -> Share Memory 实质上就是在内存中倒腾,只是物理地址不同
// 估计会有那么一天,这个Copy动作可能被简单替换为CPU物理地址映射的变更,传说Vulkan中已经大致是这样了
memcpy( pData, g_stMeshData.m_arTexCoords.GetData(), szTexCoords );
pIVBTexCoordsUp->Unmap( 0, nullptr );
pData = nullptr;

// Bone Indices Upload Buffer
szOffset += stBufferResSesc.Width;
stBufferResSesc.Width = GRS_UPPER( szBoneIndices, szAlign );

GRS_THROW_IF_FAILED( pID3D12Device4->CreatePlacedResource(
pIUploadHeapModel.Get()
, szOffset
, &stBufferResSesc
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS( &pIVBBoneIndicesUp ) ) );

GRS_SET_D3D12_DEBUGNAME_COMPTR( pIVBBoneIndicesUp );

GRS_THROW_IF_FAILED( pIVBBoneIndicesUp->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) ) );
// 第一次 Copy!CPU Memory -> Share Memory 实质上就是在内存中倒腾,只是物理地址不同
// 估计会有那么一天,这个Copy动作可能被简单替换为CPU物理地址映射的变更,传说Vulkan中已经大致是这样了
memcpy( pData, g_stMeshData.m_arBoneIndices.GetData(), szBoneIndices );
pIVBBoneIndicesUp->Unmap( 0, nullptr );
pData = nullptr;

// 创建默认堆(显存中的堆)
D3D12_HEAP_DESC stDefaultHeapDesc = {};
// 大小跟上传堆一样
stDefaultHeapDesc.SizeInBytes = stUploadHeapDesc.SizeInBytes;
stDefaultHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
// 指定堆的对齐方式,这里使用了默认的64K边界对齐,因为这里实际放的是顶点数据
stDefaultHeapDesc.Alignment = szAlign;
stDefaultHeapDesc.Properties = stDefautHeapProps;

// Vertex Data Default Heap
GRS_THROW_IF_FAILED( pID3D12Device4->CreateHeap( &stDefaultHeapDesc, IID_PPV_ARGS( &pIDefaultHeapModel ) ) );
GRS_SET_D3D12_DEBUGNAME_COMPTR( pIDefaultHeapModel );

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

PV_ARGS( &pIDefaultHeapModel ) ) );
GRS_SET_D3D12_DEBUGNAME_COMPTR( pIDefaultHeapModel );

[外链图片转存中…(img-makwF3HX-1715048461040)]
[外链图片转存中…(img-HXYlR7pK-1715048461040)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值