PathTracer now applies OpenImageDenoise.

This commit is contained in:
Philip Rideout
2019-05-24 17:33:07 -07:00
parent 0f7aa70f2d
commit cc89b6ad1e
8 changed files with 89 additions and 2 deletions

View File

@@ -68,6 +68,7 @@ find_package(embree 3.0 QUIET PATHS /usr/lib64/cmake)
if (embree_FOUND AND NOT ANDROID)
message("Found embree in ${embree_DIR}")
add_definitions(-DFILAMENT_HAS_EMBREE)
set(DENOISE_LIBRARY OpenImageDenoise)
else()
message("Embree not found, pipeline features are disabled.")
endif()
@@ -420,6 +421,7 @@ if (NOT ANDROID AND NOT WEBGL AND NOT IOS)
add_subdirectory(${EXTERNAL}/libz/tnt)
add_subdirectory(${EXTERNAL}/skylight/tnt)
add_subdirectory(${EXTERNAL}/tinyexr/tnt)
add_subdirectory(${EXTERNAL}/OpenImageDenoise)
add_subdirectory(${TOOLS}/cmgen)
add_subdirectory(${TOOLS}/filamesh)

View File

@@ -744,6 +744,7 @@ void Pipeline::bakeAmbientOcclusion(const cgltf_data* sourceAsset, image::Linear
.meshes(meshes, numMeshes)
.outputPlane(filament::rays::AMBIENT_OCCLUSION, target)
.uvCamera()
.denoise()
.tileCallback(onTile, userData)
.doneCallback(onDone, userData);
@@ -762,6 +763,7 @@ void Pipeline::renderAmbientOcclusion(const cgltf_data* sourceAsset, image::Line
.meshes(meshes, numMeshes)
.outputPlane(filament::rays::AMBIENT_OCCLUSION, target)
.filmCamera(camera)
.denoise()
.tileCallback(onTile, userData)
.doneCallback(onDone, userData);

View File

@@ -77,6 +77,9 @@ LinearImage edtFromCoordField(const LinearImage& coordField, bool sqrt);
// Dereferences the given coordinate field. Useful for creating Voronoi diagrams or dilated images.
LinearImage voronoiFromCoordField(const LinearImage& coordField, const LinearImage& src);
// Copies content of a source image into a target image. Requires width/height/channels to match.
void blitImage(LinearImage& target, const LinearImage& source);
} // namespace image

View File

@@ -413,4 +413,13 @@ LinearImage voronoiFromCoordField(const LinearImage& coordField, const LinearIma
return result;
}
void blitImage(LinearImage& target, const LinearImage& source) {
ASSERT_PRECONDITION(source.getWidth() == target.getWidth(), "Images must have same width.");
ASSERT_PRECONDITION(source.getHeight() == target.getHeight(), "Images must have same height.");
ASSERT_PRECONDITION(source.getChannels() == target.getChannels(),
"Images must have same number of channels.");
memcpy(target.getPixelRef(), source.getPixelRef(),
sizeof(float) * source.getWidth() * source.getHeight() * source.getChannels());
}
} // namespace image

View File

@@ -23,7 +23,7 @@ set(SRCS
include_directories(${PUBLIC_HDR_DIR} ${EMBREE_INCLUDE_DIRS})
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
target_link_libraries(${TARGET} PUBLIC math utils image ${EMBREE_LIBRARY})
target_link_libraries(${TARGET} PUBLIC math utils image ${EMBREE_LIBRARY} ${DENOISE_LIBRARY})
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
# ==================================================================================================

View File

@@ -77,6 +77,7 @@ public:
size_t samplesPerPixel = 256;
SimpleCamera filmCamera;
bool uvCamera = false;
bool denoise = false;
TileCallback tileCallback = nullptr;
void* tileUserData = nullptr;
DoneCallback doneCallback = nullptr;
@@ -110,6 +111,11 @@ public:
*/
Builder& uvCamera();
/**
* Executes OpenImageDenoise after rendering the complete image.
*/
Builder& denoise();
/**
* Signals that a region within each render target has become available, typically used for
* progress notification. This can be called from any thread.

View File

@@ -27,6 +27,8 @@
#ifdef FILAMENT_HAS_EMBREE
#include <embree3/rtcore.h>
#include <embree3/rtcore_ray.h>
#include <OpenImageDenoise/oidn.h>
#endif
using namespace filament::math;
@@ -99,6 +101,11 @@ PathTracer::Builder& PathTracer::Builder::uvCamera() {
return *this;
}
PathTracer::Builder& PathTracer::Builder::denoise() {
mConfig.denoise = true;
return *this;
}
PathTracer::Builder& PathTracer::Builder::tileCallback(TileCallback onTile, void* userData) {
mConfig.tileCallback = onTile;
mConfig.tileUserData = userData;
@@ -292,7 +299,57 @@ static void dilateCharts(EmbreeContext* context) {
};
auto coords = image::computeCoordField(meshNormals, presence, nullptr);
auto dilated = image::voronoiFromCoordField(coords, ao);
memcpy(ao.getPixelRef(), dilated.getPixelRef(), sizeof(float) * ao.getWidth() * ao.getHeight());
blitImage(ao, dilated);
}
static void denoise(EmbreeContext* context) {
LinearImage ao = context->config.renderTargets[(int) AMBIENT_OCCLUSION];
const LinearImage& meshNormals = context->config.renderTargets[(int) MESH_NORMALS];
// The denoiser requires color inputs, so convert 1-chan to 3-chan.
const size_t width = ao.getWidth();
const size_t height = ao.getHeight();
LinearImage denoiseSource = combineChannels({ ao, ao, ao });
// Construct a fake albedo image, which for our purposes can be white everywhere that a surface
// is present. This is optional but the denoise library doesn't produce good results without it.
LinearImage fakeAlbedo(width, height, 3);
for (int32_t row = 0; row < height; ++row) {
for (uint32_t col = 0; col < width; ++col) {
const float* normal = meshNormals.getPixelRef(col, row);
float* albedo = fakeAlbedo.getPixelRef(col, row);
albedo[0] = albedo[1] = albedo[2] = (normal[0] == EMPTY_SENTINEL ? 0.0f : 1.0f);
}
}
// Invoke the denoiser.
LinearImage denoiseTarget(width, height, 3);
OIDNDevice device = oidnNewDevice(OIDN_DEVICE_TYPE_DEFAULT);
oidnCommitDevice(device);
OIDNFilter filter = oidnNewFilter(device, "RT");
oidnSetSharedFilterImage(filter, "color", denoiseSource.getPixelRef(),
OIDN_FORMAT_FLOAT3, width, height, 0, 0, 0);
oidnSetSharedFilterImage(filter, "normal", (void*) meshNormals.getPixelRef(),
OIDN_FORMAT_FLOAT3, width, height, 0, 0, 0);
oidnSetSharedFilterImage(filter, "albedo", fakeAlbedo.getPixelRef(),
OIDN_FORMAT_FLOAT3, width, height, 0, 0, 0);
oidnSetSharedFilterImage(filter, "output", denoiseTarget.getPixelRef(),
OIDN_FORMAT_FLOAT3, width, height, 0, 0, 0);
oidnCommitFilter(filter);
oidnExecuteFilter(filter);
// Check for errors.
const char* errorMessage;
if (oidnGetDeviceError(device, &errorMessage) != OIDN_ERROR_NONE) {
printf("OpenImageDenoise Error: %s\n", errorMessage);
oidnReleaseFilter(filter);
oidnReleaseDevice(device);
return;
}
oidnReleaseFilter(filter);
oidnReleaseDevice(device);
blitImage(ao, extractChannel(denoiseTarget, 0));
}
static void renderTileFromGbuffer(EmbreeContext* context, PixelRectangle rect) {
@@ -482,6 +539,9 @@ bool PathTracer::render() {
context->config.tileUserData);
}, [](EmbreeContext* context) {
dilateCharts(context);
if (context->config.denoise) {
denoise(context);
}
context->config.doneCallback(context->config.doneUserData);
rtcReleaseScene(context->scene);
rtcReleaseDevice(context->device);
@@ -499,6 +559,9 @@ bool PathTracer::render() {
context->config.tileCallback(rect.topLeft, rect.bottomRight,
context->config.tileUserData);
}, [](EmbreeContext* context) {
if (context->config.denoise) {
denoise(context);
}
context->config.doneCallback(context->config.doneUserData);
rtcReleaseScene(context->scene);
rtcReleaseDevice(context->device);

View File

@@ -440,6 +440,7 @@ static void renderAsset(App& app) {
auto onRenderDone = [](void* userData) {
App* app = (App*) userData;
app->requestStatePop = true;
app->requestOverlayUpdate = true;
delete app->pipeline;
};
app.pipeline->renderAmbientOcclusion(asset, app.ambientOcclusion, camera, onRenderTile,
@@ -498,6 +499,7 @@ static void bakeAsset(App& app) {
auto onRenderDone = [](void* userData) {
App* app = (App*) userData;
delete app->pipeline;
app->requestOverlayUpdate = true;
app->state = BAKED;
};
app.pipeline->bakeAmbientOcclusion(asset, app.ambientOcclusion, onRenderTile, onRenderDone,