OptiX去噪器集成路径追踪器出现黑方块artifacts问题求助
问题描述
使用OptiX 8.0.0 + CUDA 12.6将OptiX Denoiser集成到路径追踪器中,部分场景去噪效果正常,但部分场景在多次路径追踪采样后出现黑方块artifacts。已确认每像素100采样时的GBuffer/AOV输入数据正常,直接使用OptiX SDK未修改的OptiXDenoiser.h封装实现。
实现代码
头文件
#include "OptiXDenoiser.h"//SDK wrapper struct NvidiaOptixDenoiser { NvidiaOptixDenoiser(); ~NvidiaOptixDenoiser(); void denoise(Graphics::Texture::Image& imageOut, const DenoisingArgs& denoisingArgs); private: OptiXDenoiser* m_denoiser = nullptr; nbBool m_firstFrame = true; };
实现文件
NvidiaOptixDenoiser::NvidiaOptixDenoiser() { m_denoiser = new OptiXDenoiser(); } NvidiaOptixDenoiser::~NvidiaOptixDenoiser() { m_denoiser->finish(); delete m_denoiser; } void NvidiaOptixDenoiser::denoise(Graphics::Texture::Image& imageOut, const DenoisingArgs& denoisingArgs) { ASSERT(imageOut.getFormat() == ImageFormat::RGB32F); RGB32FImage* imageRGBFOut = dynamic_cast<RGB32FImage*>(&imageOut); ASSERT(imageRGBFOut); const Graphics::GBuffer* gBuffer = denoisingArgs.gBuffer; ASSERT(gBuffer); const std::unique_ptr<Graphics::Texture::RGB32FImage>& imageIn = gBuffer->m_radiance; const Uint32 width = imageOut.getWidth(); const Uint32 height = imageOut.getHeight(); //----------------------------------------------------------------------------------------------------------------------------------------------------- // Build the input images. Optix Denoiser only support FLOAT4. Maybe just a limitation of the wrapper? //----------------------------------------------------------------------------------------------------------------------------------------------------- RGBA32FImage optixSrcColor(width, height); RGBA32FImage optixNormals(width, height); RGBA32FImage optixAlbedo(width, height); RGBA32FImage optixFlow(width, height); const Uint32 optixNbPixels = width * height; tbb::parallel_for(size_t(0), size_t(optixNbPixels), [&](size_t tbbIdx) { const Uint32 pixelIdx = (nbUint32)tbbIdx; const Uint32 pixelPosX = (nbUint32)(pixelIdx % width); const Uint32 pixelPosY = (nbUint32)(pixelIdx / width); const Math::Uvec2 pixelPos = Math::Uvec2(pixelPosX, pixelPosY); { // In color. { const RGBFColor color = imageIn->getPixelFromPosition(pixelPos); optixSrcColor.setPixelFromPosition(RGBAFColor(color.x, color.y, color.z, 0.0f), pixelPos); } // Normals. { const RGBFColor normal = gBuffer->m_normals->getPixelFromPosition(pixelPos); optixNormals.setPixelFromPosition(RGBAFColor(normal.x, normal.y, normal.z, 0.0f), pixelPos); } // Albedo. { const RGBFColor albedo = gBuffer->m_albedo->getPixelFromPosition(pixelPos); optixAlbedo.setPixelFromPosition(RGBAFColor(albedo.x, albedo.y, albedo.z, 0.0f), pixelPos); } // In flow. They are motion vectors { const RGBFColor flow = denoisingArgs.gBuffer->m_motionVectors->getPixelFromPosition(pixelPos); optixFlow.setPixelFromPosition(RGBAFColor(flow.x, flow.y, flow.z, 0.0f), pixelPos); } } }); //----------------------------------------------------------------------------------------------------------------------------------------------------- // Build the Optix input data //----------------------------------------------------------------------------------------------------------------------------------------------------- RGBA32FImage optixDestColor(width, height); OptiXDenoiser::Data optixDenoisingData; optixDenoisingData.width = width; optixDenoisingData.height = height; optixDenoisingData.color = reinterpret_cast<const float*>(optixSrcColor.getRawData()); optixDenoisingData.normal = reinterpret_cast<const float*>(optixNormals.getRawData()); optixDenoisingData.albedo = reinterpret_cast<const float*>(optixAlbedo.getRawData()); optixDenoisingData.flow = reinterpret_cast<const float*>(optixFlow.getRawData()); optixDenoisingData.outputs.push_back(reinterpret_cast<float*>(optixDestColor.getMutableRawData())); //----------------------------------------------------------------------------------------------------------------------------------------------------- // Perform denoising //----------------------------------------------------------------------------------------------------------------------------------------------------- if (m_firstFrame) { m_denoiser->init(optixDenoisingData, 0, 0, false, true, false, false, 0, false); m_firstFrame = false; } else { m_denoiser->update(optixDenoisingData); } m_denoiser->exec(); m_denoiser->getResults(); //----------------------------------------------------------------------------------------------------------------------------------------------------- // Read back results //----------------------------------------------------------------------------------------------------------------------------------------------------- tbb::parallel_for(size_t(0), size_t(optixNbPixels), [&](size_t tbbIdx) { const Uint32 pixelIdx = (nbUint32)tbbIdx; const Uint32 pixelPosX = (nbUint32)(pixelIdx % width); const Uint32 pixelPosY = (nbUint32)(pixelIdx / width); const Math::Uvec2 pixelPos = Math::Uvec2(pixelPosX, pixelPosY); const RGBAFColor color = optixDestColor.getPixelFromPosition(pixelPos); imageRGBFOut->setPixelFromPosition(RGBFColor(color.x, color.y, color.z), pixelPos); }); }
可能的原因与修复方案
1. 运动向量(Flow)数据格式错误
OptiX Denoiser要求运动向量是屏幕空间的像素偏移量,仅需存储在RG通道(R为X方向偏移,G为Y方向偏移),B/A通道应置0。你的代码直接将RGB格式的motionVectors复制到RGBA的R/G/B通道,会导致去噪器解析运动向量时出错,进而产生黑块。
修复:
// 修正运动向量赋值逻辑 const RGBFColor flow = denoisingArgs.gBuffer->m_motionVectors->getPixelFromPosition(pixelPos); // 仅保留X/Y分量到R/G通道,B/A置0 optixFlow.setPixelFromPosition(RGBAFColor(flow.x, flow.y, 0.0f, 0.0f), pixelPos);
2. 时间累积模式的帧索引未正确传递
路径追踪的多次采样属于时间累积场景,你在init和update时未传递正确的递增帧索引,会导致去噪器无法正确累积帧间信息,引发异常输出。
修复:
假设denoisingArgs包含当前帧索引(从0开始递增),修改初始化和更新逻辑:
if (m_firstFrame) { // 传递当前帧索引到init方法 m_denoiser->init(optixDenoisingData, denoisingArgs.frameIndex, 0, false, true, false, false, 0, false); m_firstFrame = false; } else { // 更新时同样传递当前帧索引 m_denoiser->update(optixDenoisingData, denoisingArgs.frameIndex); }
3. 输出纹理未显式初始化
虽然多数图像构造函数会默认初始化内存,但仍可能残留垃圾数据导致黑块。显式清零输出纹理可避免此类问题。
修复:
创建输出纹理后添加清零操作:
RGBA32FImage optixDestColor(width, height); // 显式清零输出纹理内存 memset(optixDestColor.getMutableRawData(), 0, width * height * sizeof(RGBAFColor));
4. 辐射值范围异常(NaN/Inf)
多次采样累积后的辐射值可能出现NaN/Inf,或超出OptiX Denoiser的预期范围,导致去噪失败。
修复:
传递颜色数据前添加钳制和异常值检查:
const RGBFColor color = imageIn->getPixelFromPosition(pixelPos); // 钳制颜色值到合理范围,替换NaN/Inf为0 RGBFColor clampedColor = clamp(color, 0.0f, 1e6f); if (isnan(clampedColor.x) || isinf(clampedColor.x)) clampedColor.x = 0.0f; if (isnan(clampedColor.y) || isinf(clampedColor.y)) clampedColor.y = 0.0f; if (isnan(clampedColor.z) || isinf(clampedColor.z)) clampedColor.z = 0.0f; optixSrcColor.setPixelFromPosition(RGBAFColor(clampedColor.x, clampedColor.y, clampedColor.z, 0.0f), pixelPos);
5. 临时图像资源未复用
每次调用denoise时创建临时RGBA图像,会导致CUDA设备内存频繁分配/释放,引发资源冲突。将临时图像改为成员变量复用可减少内存操作开销。
修复:
在NvidiaOptixDenoiser结构体中添加成员变量:
struct NvidiaOptixDenoiser { // ... 原有成员 private: OptiXDenoiser* m_denoiser = nullptr; nbBool m_firstFrame = true; // 添加复用的临时图像 std::unique_ptr<RGBA32FImage> m_optixSrcColor; std::unique_ptr<RGBA32FImage> m_optixNormals; std::unique_ptr<RGBA32FImage> m_optixAlbedo; std::unique_ptr<RGBA32FImage> m_optixFlow; std::unique_ptr<RGBA32FImage> m_optixDestColor; };
在denoise方法中检查并复用图像:
void NvidiaOptixDenoiser::denoise(Graphics::Texture::Image& imageOut, const DenoisingArgs& denoisingArgs) { // ... 原有代码 const Uint32 width = imageOut.getWidth(); const Uint32 height = imageOut.getHeight(); // 初始化/复用临时图像 if (!m_optixSrcColor || m_optixSrcColor->getWidth() != width || m_optixSrcColor->getHeight() != height) { m_optixSrcColor = std::make_unique<RGBA32FImage>(width, height); m_optixNormals = std::make_unique<RGBA32FImage>(width, height); m_optixAlbedo = std::make_unique<RGBA32FImage>(width, height); m_optixFlow = std::make_unique<RGBA32FImage>(width, height); m_optixDestColor = std::make_unique<RGBA32FImage>(width, height); } // 后续操作直接使用成员变量替代临时对象 // ... 原有复制数据的代码替换为m_optixSrcColor等成员变量 }
内容的提问来源于stack exchange,提问作者TheChamp

