ssdt函数索引号_DX12摄像机篇:动态索引

最近由于换工作,上班较忙,以前的学习节奏被打乱了,导致DX12的学习进度落下挺多的,现正在逐渐适应并调整学习时间,争取尽快步入正轨。

言归正传,本篇我们要分享动态索引。本次案例将对纹理数组进行动态索引,所以我们将使用StructuredBuffer将材质数据上传到GPU,由每个渲染物体动态索引到对应材质,再在材质的基础上动态索引到对应的贴图。这样做的目的是将每次渲染时的描述符数量降到最低,降低程序开销。

1. 动态索引前的数据准备

首先我们取消使用MatConstants,而使用MaterialData(之后将作为StructureBuffer使用),将材质数据列入其中,并加入diffuseMapIndex字段,以此来索引材质所对应的贴图。

//作为StructureBuffer上传到GPU
struct MaterialData
{
	XMFLOAT4 diffuseAlbedo = { 1.0f, 1.0f, 1.0f, 1.0f };//材质反照率
	XMFLOAT3 fresnelR0 = { 0.01f, 0.01f, 0.01f };//RF(0)值,即材质的反射属性
	float roughness = 0.25f;//材质的粗糙度
	XMFLOAT4X4 matTransform = MathHelper::Identity4x4();//纹理动画位移矩阵

	UINT diffuseMapIndex = 0;//纹理数组索引
	//占位,向量数据打包时必须占满4位
	UINT matPad0;
	UINT matPad1;
	UINT matPad2;
};

接着在ObjectConstants中加入materialIndex字段,使得渲染每个物体时,索引到对应的材质。

//单个物体的物体常量数据(不变的)
struct ObjectConstants
{
	//初始化物体空间变换到裁剪空间矩阵,Identity4x4是单位矩阵,需要包含MathHelper头文件
	XMFLOAT4X4 world = MathHelper::Identity4x4();
	XMFLOAT4X4 texTransform = MathHelper::Identity4x4();//UV坐标变化矩阵

	//不同物体去索引对应材质
	UINT materialIndex = 0;//材质的索引
	UINT objPad0;	//占位
	UINT objPad1;	//占位
	UINT objPad2;	//占位

};

然后我们在帧资源中定义matSB的智能指针,并初始化构造函数,由于纹理和材质数据是根据物体不同而动态上传的,也就是我们需要运行时动态修改数据,所以使用UploadBuffer来存放数据。注意此时的matSB是属于StructuredBuffer,不属于ConstantBuffer,所以isConstant的Bool值为false

struct FrameResources
{
public:
	............

	//每帧都需要单独的资源缓冲区(此案例仅为3个常量缓冲区)
	std::unique_ptr<UploadBufferResource<ObjectConstants>> objCB = nullptr;
	std::unique_ptr<UploadBufferResource<PassConstants>> passCB = nullptr;
	//std::unique_ptr<UploadBufferResource<MatConstants>> matCB = nullptr;

	//指向结构化缓冲区MaterialData的智能指针
	std::unique_ptr<UploadBufferResource<MaterialData>> matSB = nullptr;

	............
};

FrameResources::FrameResources(ID3D12Device* device, UINT passCount, UINT objCount, UINT matCount)
{
	............

	matSB = std::make_unique<UploadBufferResource<MaterialData>>(device, matCount, false);
}

接着,将objCB、matSB、passCB以及texture资源分别绑定到跟签名上。matSB需绑定到SRV或者UAV上,我们选择SRV,由于texture也是绑定到SRV上的,所以我们让他们分别绑定到SRV寄存器的不同空见上,具体写法:InitAsShaderResourceView(/*寄存器槽号*/0, /*RegisterSpace*/ 1);

CD3DX12_DESCRIPTOR_RANGE srvTable;
srvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV,	//描述符类型
	4,	//表中的描述符数量(纹理数量)
	0);	//描述符所绑定的寄存器槽号

//根参数可以是描述符表、根描述符、根常量
CD3DX12_ROOT_PARAMETER slotRootParameter[4];
slotRootParameter[0].InitAsDescriptorTable(1,//Range数量
	&srvTable,	//Range指针
	D3D12_SHADER_VISIBILITY_PIXEL);	//该资源只能在像素着色器可读
slotRootParameter[1].InitAsConstantBufferView(0);//objCB绑定槽号为0的寄存器

//matSB绑定槽号为0的寄存器(和纹理公用一个SRV寄存器,但是不同Space)
//StructureBuffer必须使用SRV或者UAV来绑定
slotRootParameter[2].InitAsShaderResourceView(/*寄存器槽号*/0, /*RegisterSpace*/ 1);
slotRootParameter[3].InitAsConstantBufferView(1);//passCB绑定槽号为1的寄存器

需要注意的是,寄存器Space的用法只在ShaderModel5.1及以上才能使用,所以我们将ShaderModel改成5.1

vsBytecode = ToolFunc::CompileShader(L"Shaderscolor.hlsl", nullptr, "VS", "vs_5_1");
psBytecode = ToolFunc::CompileShader(L"Shaderscolor.hlsl", nullptr, "PS", "ps_5_1");

由于我们是在shader中动态索引贴图和材质,所以我们只需将texture和matSB一次性绑定到流水线上,所以在Draw函数直接设置一次即可,不用像之前那样,在DrawRenderItems函数中每次绘制都要设置一遍。而objCB还是在DrawRenderItems中设置。

void ShapesApp::Draw()
{
	............

	//设置描述符表,将纹理资源与流水线绑定(因为只绑定一次,所以不需要做地址偏移)
	cmdList->SetGraphicsRootDescriptorTable(0, srvHeap->GetGPUDescriptorHandleForHeapStart());

	//设置matSB的描述符(因为只绑定一次,所以不需要做地址偏移)
	auto matSB = currFrameResources->matSB->Resource();
	cmdList->SetGraphicsRootShaderResourceView(2,//根参数索引
		matSB->GetGPUVirtualAddress());//子资源地址

	//设置根描述符
	auto passCB = currFrameResources->passCB->Resource();
	cmdList->SetGraphicsRootConstantBufferView(3,	//根参数索引
		passCB->GetGPUVirtualAddress());

	//绘制渲染项
	DrawRenderItems();

	............
}

然后更新objConstants中的materialIndex属性,通过当前渲染项所对应的材质ID赋值。这样就能索引到对应材质了,并通过材质中的纹理ID索引到对应纹理。

//将材质常量缓冲区中的matCBIndex赋值给新增在objConstants中的materialIndex字段
objConstants.materialIndex = e->mat->matCBIndex;

同时更新matSB中数据,此处函数名仍旧是UpdateMatCBs,由于我懒,所以没有改,其实已经是SB了。

void ShapesApp::UpdateMatCBs()
{
	auto currMatSB = currFrameResources->matSB.get();
	for (auto& e : materials)
	{
		Material* mat = e.second.get();//获得键值对的值,即Material指针(智能指针转普通指针)
		if (mat->numFramesDirty > 0)
		{
			//将定义的材质属性传给常量结构体中的元素
			matData.diffuseAlbedo = mat->diffuseAlbedo;
			matData.fresnelR0 = mat->fresnelR0;
			matData.roughness = mat->roughness;
			XMMATRIX matTransform = XMLoadFloat4x4(&mat->matTransform);
			XMStoreFloat4x4(&matData.matTransform, XMMatrixTranspose(matTransform));
			matData.diffuseMapIndex = mat->diffuseSrvHeapIndex;//纹理在SRV堆中索引

			//将材质常量数据复制到常量缓冲区对应索引地址处
			currMatSB->CopyData(mat->matCBIndex, matData);
			//更新下一个帧资源
			mat->numFramesDirty--;
		}
	}
}

2. Shader中进行动态索引

来到shader中,首先定义MaterialData,用来接收matSB中数据

struct MaterialData
{
    float4 gDiffuseAlbedo; //材质反照率
    float3 gFresnelR0; //RF(0)值,即材质的反射属性
    float gRoughness; //材质的粗糙度
    float4x4 gMatTransform; //UV动画变换矩阵
    uint gDiffuseMapIndex;//纹理数组索引
    uint gMatPad0;
    uint gMatPad1;
    uint gMatPad2;
};

接着定义纹理数组。还记得之前我们是使用Texture2DArray数据类型来定义的纹理数组,但那要求将纹理预先打包成Texture2DArray数据类型,而此次是直接使用Texture2D类型的数组来定义纹理数组,如:Texture2D tex[4] ,而这种形式的纹理数组必须是ShaderModel5.1及以上才能使用。Texture2D与Texture2DArray类型数组不同的是,此数组中所存纹理的尺寸和格式可各不相同,这使它比一般纹理数组更为灵活。

//一种只有SM5.1才支持的纹理数组
//与Texture2DArray类型数组不同的是,此数组中所存纹理的尺寸和格式可各不相同
//这使它比一般纹理数组更为灵活
Texture2D gDiffuseMap[4] : register(t0);

然后定义结构化缓冲区,注意我们使用了space1空间来存放数据。

//材质数据的结构化缓冲区,使用t0的space1空间
StructuredBuffer<MaterialData> gMaterialData : register(t0, space1);

然后定义传入的objCB数据,增加gMaterialDataIndex字段。

cbuffer cbPerObject : register(b0)
{
	float4x4 gWorld; 
    float4x4 gTexTransform; //UV顶点变换矩阵
    uint gMaterialDataIndex;
    uint gObjPad0;
    uint gObjPad1;
    uint gObjPad2;
};

passCB以及采样器类型数据我们就不贴了,和之前代码是一样的。

然后是顶点着色器代码。StructuredBuffer本身就是一个数组,所以它能通过材质索引来找到当前顶点所对应的材质属性,比如gMatTransform

VertexOut VS(VertexIn vin)
{
    VertexOut vout;

    //使用结构化缓冲区数组(结构化缓冲区是由若干类型数据所组成的数组)
    MaterialData matData = gMaterialData[gMaterialDataIndex];

    ............

    //配合时间函数计算UV坐标的动态偏移(UV动画)
    vout.UV = mul(texCoord, matData.gMatTransform).xy;
    
    return vout;
}

最后是片段着色器,同理我们将材质中需要用到的diffuseAlbedo, fresnelR0, roughness动态传递给光照函数,以便计算最终光照。我们还使用了纹理数组来动态调用纹理,并采样返回albedo

float4 PS(VertexOut pin) : SV_Target
{ 
    //获取材质数据(需要点出来,和CB使用不太一样)
    MaterialData matData = gMaterialData[gMaterialDataIndex];
    float4 diffuseAlbedo = matData.gDiffuseAlbedo;
    float3 fresnelR0 = matData.gFresnelR0;
    float roughness = matData.gRoughness;
    uint diffuseTexIndex = matData.gDiffuseMapIndex;

    //在数组中动态地查找纹理
    diffuseAlbedo *= gDiffuseMap[diffuseTexIndex].Sample(gSamAnisotropicWarp, pin.UV);
    
    float3 worldNormal = normalize(pin.WorldNormal);
    float3 worldView = normalize(gEyePosW - pin.WorldPos);
    
    Material mat = { diffuseAlbedo, fresnelR0, roughness };
    float3 shadowFactor = 1.0f;//暂时使用1.0,不对计算产生影响
    //直接光照
    float4 directLight = ComputerLighting(gLights, mat, pin.WorldPos, worldNormal, worldView, shadowFactor);
    //环境光照
    float4 ambient = gAmbientLight * diffuseAlbedo;
    //漫反射光照
    float4 diffuse = directLight * diffuseAlbedo;
    //总光照
    float4 finalCol = ambient + diffuse;
    finalCol.a = diffuseAlbedo.a;

    return finalCol;
}

编译运行,画面正常显示,但是这时候的材质和贴图都是在shader中动态调用的。

3638f4092041a46115fe3e0f1aac8910.png

本篇就分享就这里,下一篇我们将进行章后习题的分享,巩固本章学习,下篇见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
城市应急指挥系统是智慧城市建设的重要组成部分,旨在提高城市对突发事件的预防和处置能力。系统背景源于自然灾害和事故灾难频发,如汶川地震和日本大地震等,这些事件造成了巨大的人员伤亡和财产损失。随着城市化进程的加快,应急信息化建设面临信息资源分散、管理标准不统一等问题,需要通过统筹管理和技术创新来解决。 系统的设计思路是通过先进的技术手段,如物联网、射频识别、卫星定位等,构建一个具有强大信息感知和通信能力的网络和平台。这将促进不同部门和层次之间的信息共享、交流和整合,提高城市资源的利用效率,满足城市对各种信息的获取和使用需求。在“十二五”期间,应急信息化工作将依托这些技术,实现动态监控、风险管理、预警以及统一指挥调度。 应急指挥系统的建设目标是实现快速有效的应对各种突发事件,保障人民生命财产安全,减少社会危害和经济损失。系统将包括预测预警、模拟演练、辅助决策、态势分析等功能,以及应急值守、预案管理、GIS应用等基本应用。此外,还包括支撑平台的建设,如接警中心、视频会议、统一通信等基础设施。 系统的实施将涉及到应急网络建设、应急指挥、视频监控、卫星通信等多个方面。通过高度集成的系统,建立统一的信息接收和处理平台,实现多渠道接入和融合指挥调度。此外,还包括应急指挥中心基础平台建设、固定和移动应急指挥通信系统建设,以及应急队伍建设,确保能够迅速响应并有效处置各类突发事件。 项目的意义在于,它不仅是提升灾害监测预报水平和预警能力的重要科技支撑,也是实现预防和减轻重大灾害和事故损失的关键。通过实施城市应急指挥系统,可以加强社会管理和公共服务,构建和谐社会,为打造平安城市提供坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值