You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:09:53