DirectX12加载DDS立方体贴图失败:GPU资源全黑求助
问题概述
使用DirectX12 DXR开发光线追踪应用,实现IBL(基于图像的光照)时遇到DDS立方体贴图加载异常:GPU资源的维度、数组大小、格式、Mip层级等参数均正确,但像素数据未成功上传,渲染结果全黑。已验证CPU端加载的纹理数据正常,DirectX12未返回任何错误,NSight Graphics检测显示GPU资源无像素数据。
实现步骤与关键代码
1. Miss着色器根签名创建
ComPtr<ID3D12RootSignature> D3D12HelloTriangle::CreateMissSignature() { nv_helpers_dx12::RootSignatureGenerator rsc; rsc.AddHeapRangesParameter({ {0, 1, 0, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 3} }); auto s = GetStaticSamplers(); return rsc.Generate(m_device.Get(), true, static_cast<UINT>(s.size()), s.data()); }
注:基于NVIDIA光线追踪示例项目,使用nv_helpers_dx12库实现
2. 立方体贴图加载
void D3D12HelloTriangle::CreateCubemapBuffer() { auto tex = std::make_unique<Texture>(); tex->name = "cubemap"; tex->filename = L"assets/earth-cubemap.dds"; // DirectXTex提供的示例纹理 ThrowIfFailed(CreateDDSTextureFromFile12( m_device.Get(), m_commandList.Get(), tex->filename.c_str(), tex->resource, tex->uploadHeap )); m_cubemap = std::move(tex); }
说明:参考DirectXTex的DX12纹理加载实现,Texture结构体封装资源名称、路径及ID3D12Resource指针
3. 着色器资源堆创建(立方体贴图位于第4个槽位)
void D3D12HelloTriangle::CreateShaderResourceHeap() { m_srvUavHeap = nv_helpers_dx12::CreateDescriptorHeap(m_device.Get(), 4, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, true); D3D12_CPU_DESCRIPTOR_HANDLE srvHandle = m_srvUavHeap->GetCPUDescriptorHandleForHeapStart(); // 创建输出纹理UAV(槽位0) D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; m_device->CreateUnorderedAccessView(m_outputResource.Get(), nullptr, &uavDesc, srvHandle); srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); // 创建顶层加速结构SRV(槽位1) D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = DXGI_FORMAT_UNKNOWN; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.RaytracingAccelerationStructure.Location = m_topLevelASBuffers.pResult->GetGPUVirtualAddress(); m_device->CreateShaderResourceView(nullptr, &srvDesc, srvHandle); srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); // 创建相机CBV(槽位2) D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; cbvDesc.BufferLocation = m_cameraBuffer->GetGPUVirtualAddress(); cbvDesc.SizeInBytes = m_cameraBufferSize; m_device->CreateConstantBufferView(&cbvDesc, srvHandle); srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); // 创建立方体贴图SRV(槽位3) D3D12_SHADER_RESOURCE_VIEW_DESC srvDescCubemap = {}; srvDescCubemap.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDescCubemap.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; srvDescCubemap.TextureCube.MostDetailedMip = 0; srvDescCubemap.TextureCube.MipLevels = m_cubemap->resource->GetDesc().MipLevels; srvDescCubemap.TextureCube.ResourceMinLODClamp = 0.0f; srvDescCubemap.Format = m_cubemap->resource->GetDesc().Format; m_device->CreateShaderResourceView(m_cubemap->resource.Get(), &srvDescCubemap, srvHandle); }
4. 着色器绑定表(SBT)创建
void D3D12HelloTriangle::CreateShaderBindingTable() { m_sbtHelper.Reset(); D3D12_GPU_DESCRIPTOR_HANDLE srvUavHeapHandle = m_srvUavHeap->GetGPUDescriptorHandleForHeapStart(); auto heapPointer = reinterpret_cast<UINT64*>(srvUavHeapHandle.ptr); m_sbtHelper.AddRayGenerationProgram(L"RayGen", { heapPointer }); m_sbtHelper.AddMissProgram(L"Miss", { heapPointer }); m_sbtHelper.AddHitGroup(L"HitGroup", { (void*)(m_vertexBuffer->GetGPUVirtualAddress()), heapPointer }); uint32_t sbtSize = m_sbtHelper.ComputeSBTSize(); m_sbtStorage = nv_helpers_dx12::CreateBuffer( m_device.Get(), sbtSize, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ, nv_helpers_dx12::kUploadHeapProps ); if (!m_sbtStorage) { throw std::logic_error("Could not allocate the shader binding table"); } m_sbtHelper.Generate(m_sbtStorage.Get(), m_rtStateObjectProps.Get()); }
5. Miss着色器代码
#include "Common.hlsl" TextureCube gCubemap : register(t0); SamplerState gsamBilinearWrap : register(s0); [shader("miss")] void Miss(inout HitInfo payload : SV_RayPayload) { uint2 launchIndex = DispatchRaysIndex().xy; float2 dims = float2(DispatchRaysDimensions().xy); float3 unitDir = normalize(WorldRayDirection()); //float ramp = 0.5f * (unitDir.y + 1.0f); //float3 color = lerp(float3(.91f, .94f, .99f), float3(.24f, .49f, .79f), ramp); // blue sky-like fade float4 color = gCubemap.SampleLevel(gsamBilinearWrap, WorldRayDirection(), 0); payload.colorAndDistance = float4(color.rgb,1e7); }
排查与解决方向
1. 命令列表提交与资源状态过渡
CreateDDSTextureFromFile12内部会使用传入的m_commandList执行纹理拷贝,但必须确保:
- 命令列表执行拷贝后已提交到GPU队列,并等待执行完成,否则GPU不会处理拷贝指令
- 立方体贴图资源在拷贝后已过渡到
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE(光线追踪着色器属于非像素着色器范畴)
可补充以下代码确保拷贝完成:
m_commandList->Close(); ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() }; m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // 等待GPU完成拷贝 m_fence->Signal(m_commandQueue.Get(), ++m_fenceValue); m_fence->SetEventOnCompletion(m_fenceValue, m_fenceEvent); WaitForSingleObject(m_fenceEvent, INFINITE);
2. 根签名与SRV槽位匹配
Miss着色器中gCubemap绑定的是t0(槽位0),但代码中立方体贴图SRV创建在槽位3,这会导致着色器访问的是槽位0的输出UAV而非目标立方体贴图。修改着色器绑定为t3即可:
TextureCube gCubemap : register(t3);
3. 上传堆资源生命周期
CreateDDSTextureFromFile12创建的uploadHeap是临时上传资源,需确保GPU完成拷贝前不被释放。当前代码中tex->uploadHeap被std::move到m_cubemap,只要m_cubemap在拷贝完成前未被销毁即可,可检查代码中是否存在提前释放m_cubemap的逻辑。
4. SRV格式兼容性
确认DDS纹理格式与SRV格式匹配,若使用压缩格式(如BC6H/BC7),需确保设备支持该格式。可尝试使用无压缩格式(如DXGI_FORMAT_R8G8B8A8_UNORM)的立方体贴图测试,排除格式兼容性问题。
5. SBT参数传递正确性
检查nv_helpers_dx12::ShaderBindingTableHelper的AddMissProgram方法是否正确传递根参数。当前传入的heapPointer是堆起始地址,需确认根签名要求的参数传递方式正确,可通过NSight Graphics查看Miss着色器的根参数绑定是否指向正确的SRV。
内容的提问来源于stack exchange,提问作者Brennen

