`

DX11渲染管道 :IA输入汇编阶段

 
阅读更多

概述:图形渲染本质即是CPU+GPU编程,这比传统的CPU编程更加复杂,就连其存储数据的位置,CPU和GPU对数据的访问权限都变的更加复杂。IA阶段是整个渲染过程中数据加载阶段,把这个模块学好对渲染整个渲染过程的理解都有很好的帮助。

模型文件里面的信息,会加载到内存中,然后映射到ID3D11Buffer里面,有了数据管道才能开始渲染。这里面分两个过程,一是创建buffer的过程,这一步会把数据存储在相应的位置。二是IASet,这个过程是把数据输送到渲染管道,提供使用。所以,实现的时候创建buffer会在程序初始化的时候,而IASet是在渲染的时候,你有可能创建了100个buffer而实际渲染的时候只渲染了10个。

 

整个渲染管道是由多个阶段组成的,而这所有的阶段都是由ID3D11DeviceContext进行控制的,它控制的第一个阶段就是IA阶段。IA是整个渲染过程数据准备阶段,包括格式的定义和数据的输入。这个阶段会向管道输入四种类型的对象:

 

IAGetIndexBuffer :获得一个指向索引缓冲区,也势必将输入汇编阶段。

IAGetInputLayout :获取一个指针,指向输入布局对象被绑定到输入汇编阶段。

IAGetPrimitiveTopology :获取信息的基本类型和数据顺序来描述的输入数据输入汇编阶段。

IAGetVertexBuffers :获取顶点缓冲区的输入汇编阶段的必然。

IASetIndexBuffer :索引缓冲区绑定到汇编器的输入阶段。

IASetInputLayout :输入布局对象绑定到汇编器的输入阶段。

IASetPrimitiveTopology :绑定信息的基本类型和数据顺序来描述的输入数据输入汇编阶段。

IASetVertexBuffers :顶点缓冲区数组绑定到汇编器的输入阶段。

这个过程其实是向管道传入四个对象:2个ID3D11Buffer对象,一个ID3D11InputLayout对象 ,一个

D3D11_PRIMITIVE_TOPOLOGY对象。

 

VertexBuffer 和 IndexBuffer:(分别描述顶点和顶点的索引)

1.定义顶点格式:用一个结构体描述顶点的信息,渲染的时候用到

struct VertexInput  // 结构体的名字任意取,成员的类型也不是必须得用D3DX的类型,关键得hlsl里面有这样的类型即可。
{
	Vector3 position; // 位置
	Vector3 normal;   // 法向量
	Vector3 tangent;  // 切线
	Vector3 bitangent; // 双向切线
	Vector2 texture; // 纹理坐标
	Color4 color; // 顶点颜色
};

 这个是我自己定义的格式,根据不同需要灵活定义。

2.CreateBuffer :

HRESULT CreateBuffer(
  [in]             const D3D11_BUFFER_DESC *pDesc,
  [in, optional]   const D3D11_SUBRESOURCE_DATA *pInitialData,
  [out, optional]  ID3D11Buffer **ppBuffer
); 

 最终会得到一个ID3D11Buffer对象,但是要想创建必须传入上述两个结构体对象。 

 

3.D3D11_BUFFER_DESC

typedef struct D3D11_BUFFER_DESC {  // 这个结构体对buffer的结果进行描述,站多大内容空间之类的,这样好开辟内存空间。
  UINT        ByteWidth;  // 缓冲区大小,单位为字节
  D3D11_USAGE Usage; // 一个枚举,描述的是GPU和CPU对此buffer的读写能力,其实这涉及到这个buffer将会存储在那个地方
  UINT        BindFlags; // 这个描述的是buffer的类型
  UINT        CPUAccessFlags; // 设置CPU访问权限,不访问设置为0
  UINT        MiscFlags; // 如果不使用设置为0,可以多选,暂时不知用法
  UINT        StructureByteStride; // ??? 不理解
} D3D11_BUFFER_DESC;

typedef enum D3D11_USAGE { // 楼上的第二个参数
  D3D11_USAGE_DEFAULT    = 0,  // 默认值,GPU具备读写能力
  D3D11_USAGE_IMMUTABLE  = 1, // GPU可读,CPU不可访问,如果选择这种方式必须得初始化buffer,你懂的
  D3D11_USAGE_DYNAMIC    = 2, // GPU只读,CPU只写
  D3D11_USAGE_STAGING    = 3 // 这种是CPU可以完全控制,GPU只能复制数据,与0完全相反。
} D3D11_USAGE;

typedef enum D3D11_BIND_FLAG { // 对应第3个参数,此标志是多选
  D3D11_BIND_VERTEX_BUFFER     = 0x1L, // 顶点缓存
  D3D11_BIND_INDEX_BUFFER      = 0x2L, // 索引缓存
  D3D11_BIND_CONSTANT_BUFFER   = 0x4L, // 常数缓存,不可与其他标志共用
  D3D11_BIND_SHADER_RESOURCE   = 0x8L, // DX11.1版本才可用
  D3D11_BIND_STREAM_OUTPUT     = 0x10L, // 选此标志,buffer可以用来输出,管道的输出阶段
  D3D11_BIND_RENDER_TARGET     = 0x20L, // ???
  D3D11_BIND_DEPTH_STENCIL     = 0x40L, // ???
  D3D11_BIND_UNORDERED_ACCESS  = 0x80L, // ???
  D3D11_BIND_DECODER           = 0x200L, // ???
  D3D11_BIND_VIDEO_ENCODER     = 0x400L // ???
} D3D11_BIND_FLAG; 

typedef enum D3D11_CPU_ACCESS_FLAG {  // 对应第4个参数,多选,与第二个参数要结合使用.
  D3D11_CPU_ACCESS_WRITE  = 0x10000L, 
  D3D11_CPU_ACCESS_READ   = 0x20000L
} D3D11_CPU_ACCESS_FLAG;

 buffer是渲染管道的数据基础,也是GPU和CPU的桥梁,而这个结构体它正是对buffer的各种信息的描述,以至于buffer能够以合理的大小放在合理的位置,并且具备合理的能力,从而提高性能,后续需要继续熟悉这些配置。

参考:D3D11_USAGED3D11_BIND_FLAGD3D11_RESOURCE_MISC_FLAG

4.D3D11_SUBRESOURCE_DATA :

typedef struct D3D11_SUBRESOURCE_DATA {
  const void *pSysMem; // 指向数据源的指针
  UINT       SysMemPitch; // 步长,当缓存的是纹理的时候有用
  UINT       SysMemSlicePitch; // 同上
} D3D11_SUBRESOURCE_DATA;

这个结构比上面的简单,pSysMem此时指向数据存储在内存的源,最终它在创建buffer的时候被拷贝到一个新的位置。

5.IASetVertexBuffers:

void IASetVertexBuffers(
  [in]  UINT StartSlot, // 开始的输入插槽,只需要set第一个buffer的时候指定,后面的会自动向后排,DX11最大有32个输入插槽,这个是并行输入数据的能力。
  [in]  UINT NumBuffers, // 这个是顶点缓冲区的数目,不能超过最大插槽数,这个方法传递的不是一个顶点buffer,而是一个顶点buffer的数组,这里指定的是数组的数目。
  [in]  ID3D11Buffer *const *ppVertexBuffers, // 顶点buffer的指针或者数组指针
  [in]  const UINT *pStrides, // 我理解这个是每个顶点信息的宽度
  [in]  const UINT *pOffsets // 偏移量
);

6.IASetIndexBuffer 

void IASetIndexBuffer(
  [in]  ID3D11Buffer *pIndexBuffer, // 所以缓存指针
  [in]  DXGI_FORMAT Format, // 索引节点的数据格式
  [in]  UINT Offset
);

例子:mScene是assimp导入的模型

void SceneNode::initInstance(ID3D11Device* device)
{
	for(int i=0;i<mScene->mNumMeshes;i++)
	{
		Mesh* mesh = mScene->mMeshes[i];
		mVertexCount += mesh->mNumVertices;
		mIndexCount += mesh->mNumFaces * 3;
	}

	VertexData* vertices = new VertexData[mVertexCount];
	unsigned long* indices = new unsigned long[mIndexCount]; 

	int v_index = 0;
	int i_index = 0;
	for(int i=0;i<mScene->mNumMeshes;i++)
	{
		Mesh* mesh = mScene->mMeshes[i];
		for(int j=0;j<mesh->mNumVertices;j++)
		{
			vertices[v_index].position = mesh->mVertices[j];
			vertices[v_index].normal = mesh->mNormals[j];
			vertices[v_index].tangent = mesh->mTangents[j];
			vertices[v_index].bitangent = mesh->mBitangents[j];
			vertices[v_index].texture = Vector2(mesh->mTextureCoords[0][j].x,mesh->mTextureCoords[0][j].y);//mesh->mTextureCoords[j];
			vertices[v_index].color = Color4(mesh->mColors[0][j].r,mesh->mColors[0][j].g,mesh->mColors[0][j].b,mesh->mColors[0][j].a);
			v_index++;
		}

		for(int j=0;j<mesh->mNumFaces;j++)
		{
			const Face& face = mesh->mFaces[i]; 
			indices[i_index] = face.mIndices[0];
			i_index++;
			indices[i_index] = face.mIndices[1];
			i_index++;
			indices[i_index] = face.mIndices[2];
			i_index++;
		}
	}

	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; 
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	// 设置顶点缓冲描述 
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; 
	vertexBufferDesc.ByteWidth = sizeof(VertexData) * mVertexCount; 
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; 
	vertexBufferDesc.CPUAccessFlags = 0; 
	vertexBufferDesc.MiscFlags = 0; 
	vertexBufferDesc.StructureByteStride = 0;

	// 指向保存顶点数据的临时缓冲. 
	vertexData.pSysMem = vertices; 
	vertexData.SysMemPitch = 0; 
	vertexData.SysMemSlicePitch = 0;

	// 创建顶点缓冲. 
	device->CreateBuffer(&vertexBufferDesc, &vertexData, &mVertex); 

	// 设置索引缓冲描述. 
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; 
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount; 
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; 
	indexBufferDesc.CPUAccessFlags = 0; 
	indexBufferDesc.MiscFlags = 0; 
	indexBufferDesc.StructureByteStride = 0;

	// 指向存临时索引缓冲. 
	indexData.pSysMem = indices; 
	indexData.SysMemPitch = 0; 
	indexData.SysMemSlicePitch = 0;

	// 创建索引缓冲. 
	device->CreateBuffer(&indexBufferDesc, &indexData, &mIndex); 

	// 释放临时缓冲. 
	delete [] vertices; 
	vertices = 0;

	delete [] indices; 
	indices = 0;
}

 

InputLayout

buffer把数据传递到渲染管道,但是它只是描述了整个buffer有多大,buffer里面的数据的结构依旧不清晰,这样的话管道是无法处理这些数据的。inputlayout的主要作用就是描述出顶点buffer的细节,还有就是告知第一个处理数据的渲染器是谁。

HRESULT CreateInputLayout(
  [in]   const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,  // 一个数组这个数组描述一个顶点,是一个数组描述一个顶点,不是数组里面的一个元素描述这个顶点。
  [in]   UINT NumElements, // 数组的长度
  [in]   const void *pShaderBytecodeWithInputSignature, // 一个已经编译的渲染器指针
  [in]   SIZE_T BytecodeLength, // 渲染器大小
  [out]  ID3D11InputLayout **ppInputLayout // 结果
);

 刚才我们定义顶点buffer的时候,最先回定义一个结构体来描述顶点,这个结构体是CPU的描述是C++语言,到了GPU的时候HLSL不清楚。所以这里会有一个D3D11_INPUT_ELEMENT_DESC数组来描述顶点的结构,让GPU可以识别。每个D3D11_INPUT_ELEMENT_DESC对应顶点中的一个元素。

D3D11_INPUT_ELEMENT_DESC :

typedef struct D3D11_INPUT_ELEMENT_DESC {
  LPCSTR  SemanticName; // 这个是定义HLSL中的语义,HLSL就是通过这个语义来识别它。
  UINT  SemanticIndex; // 这个是语义取重名的时候,用来区分的序号,例如矩阵要描述里面的每一个元素,总不能取不同的名字吧,所以用序号区分
  DXGI_FORMAT  Format; // 数据格式,它对应HLSL的数据类型
  UINT  InputSlot; // 设置为0即可
  UINT  AlignedByteOffset; // 对齐的偏移量,不知道该如何计算,还好只需要设置为:D3D11_APPEND_ALIGNED_ELEMENT就好了
  D3D11_INPUT_CLASSIFICATION InputSlotClass; // 
  UINT  InstanceDataStepRate; // 这个值必须为0 - -。
} D3D11_INPUT_ELEMENT_DESC;

typedef enum D3D11_INPUT_CLASSIFICATION { 对应倒数第2个
  D3D11_INPUT_PER_VERTEX_DATA    = 0,  // 输入的是每个顶点的数据
  D3D11_INPUT_PER_INSTANCE_DATA  = 1 // 输入的是每个实例的数据
} D3D11_INPUT_CLASSIFICATION;

 在这个结构体里面,SemanticName和Format比较重要,如同C++与hlsl的接口暗号。

注意:这里有一个InputSlot和InputSlotClass官网上只是说了InputSlot是输入插槽并且最多15个,但是并没有说插槽是跟什么对应的,而InputSlotClass跟InputSlot是什么关系也没有说。个人理解InputSlot对应buffer,但是又怎么跟InputSlotClass对应呢?目前不知道这个地方怎么样的,只能在D3D11_INPUT_PER_INSTANCE_DATA的时候为instance创建一个VB然后作为第二个输入进去,然后把InputSlot设置为1。

 

AlignedByteOffset的计算方式:每一个32位的浮点站4个字节因此一个R32G32B32_FLOAT站12个,需要注意的是这里的计算是按照vs里面你定义的input的类型来进行计算的,而不是inputlayout里面你定义语义的格式。所以,计算的时候是float4为16个,起始为0。

例子:

	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
    unsigned int numElements;
	// 设置数据布局,以便在shader中使用.
	// 定义要和ModelClass中的顶点结构一致.
	polygonLayout[0].SemanticName = "POSITION";//vs中的输入参数 
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "COLOR";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// 得到layout中的元素数量
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
	// 创建顶点输入布局.
	ID3D10Blob* color_vsb = nullptr;
	renderManager->getVertexShaderBuffer("color.vs",&color_vsb);
	result = m_pd3dDevice->CreateInputLayout(polygonLayout, numElements, color_vsb->GetBufferPointer(), 
		color_vsb->GetBufferSize(), &m_layout); 

 使用也很简单只需要调用: // 绑定顶点布局.m_immediateContext->IASetInputLayout(m_layout);即可。

 

原语的设定:

我的理解就是用来描述最终显示的是什么样子,我们操作的是顶点,但是最终显示的结果并不是满屏幕都是点,而显示成什么样子取决于原语的设定。

void IASetPrimitiveTopology(
  [in]  D3D11_PRIMITIVE_TOPOLOGY Topology
);

这里设置的参数是一个枚举类型D3D11_PRIMITIVE_TOPOLOGY,具体参考另外一篇文章。

 

 

其它:常量buffer的传递

 

渲染管道数据的获取很大一部分来源是从IA输入进去的,但是除此之外,有一些数据它只需要提供给某一个渲染器使用,例如世界转换矩阵,很明显只有顶点着色器才会用到。针对类似这种数据,DX11里面设定了一种常量buffer的类型,并且每个渲染器都有一个获取常量buffer的方法。

以VS渲染器为例:

void VSSetConstantBuffers(
  [in]  UINT StartSlot,  // 常量缓冲的位置
  [in]  UINT NumBuffers, // 常量缓冲的数量
  [in]  ID3D11Buffer *const *ppConstantBuffers // 常量缓冲指针,多个的画就是数组了
);

每一个渲染器能够获取的常量缓存是有限个的,这个个数为:StartSlot + NumBuffers < 一个常数。这个常数为:D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT。

从输入的类型为ID3D11Buffer可知,常量buffer跟顶点和索引的buffer是一样的,只不过类型不一样。

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics