I integrated Optix denoiser inside our path tracer using Optix 8.0.0 and Cuda 12.6 . While I get decent results with some scenes, I get black square artefacts on some other scenes after several path tracing samples.
Please see this video for a demo description: https://youtu.be/AGtOWDGt17M
At 100 samples per pixel(glitch is already there) gbuffer/aovs looks like this:
https://www.dropbox.com/scl/fi/j94a6mnib7ddcgugb2wmq/gBuffer.zip?rlkey=x4b1985pp276hmc6c9t5la6f0&st=6y9e3jp3&dl=0
Meaning the denoiser input is good.
Workflow:
- Rendering start: we create a new denoiser
- Everytime we render a new frame(by cumulative moving average) we feed it to the optix denoiser and its output becomes the current render.
What am I missing? Our implementation is straightforward. We use the unmodified denoiser wrapper from the toolkit(OptiXDenoiser.h).
HEADER
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;
};
SIMPLIFIED CPP: To test the issue we made a test branch that uses RGBA textures instead of RGB, since this is what the denoiser wrapper needs. Unfortunately it has the square artifacts glitch as well.
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::RGBA32F);
const Graphics::GBuffer* gBuffer = denoisingArgs.gBuffer;
ASSERT(gBuffer);
const Uint32 width = imageOut.getWidth();
const Uint32 height = imageOut.getHeight();
//-----------------------------------------------------------------------------------------------------------------------------------------------------
// Build the Optix input data
//-----------------------------------------------------------------------------------------------------------------------------------------------------
OptiXDenoiser::Data optixDenoisingData;
optixDenoisingData.width = width;
optixDenoisingData.height = height;
optixDenoisingData.color = reinterpret_cast<const float*>(gBuffer->m_radiance->getRawData());
optixDenoisingData.normal = reinterpret_cast<const float*>(gBuffer->m_normals->getRawData());
optixDenoisingData.albedo = reinterpret_cast<const float*>(gBuffer->m_albedo->getRawData());
optixDenoisingData.flow = reinterpret_cast<const float*>(gBuffer->m_motionVectors->getRawData());
optixDenoisingData.outputs.push_back(reinterpret_cast<float*>(imageOut.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();
}
PRODUCTION CPP: The CPP of our main branch. We use RGB textures. Without any surprise the black squares issue is still there.
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);
});
}
We can provide executables as well as the test scenes if it can help us solve the issue :)
The problem is there on both an RTX 3080 and an RTX 3060
Thank you very much for helping!