Skip to content

Commit

Permalink
Better ShadowCatcher
Browse files Browse the repository at this point in the history
- regard lightSamples
- reuse NEE code
- correct accumulation within spp (e.g. with colored shadows)
- demonstrate shadowCatcher via ospExamples
- documentation and tests

Co-authored-by: David E. DeMarle <[email protected]>
  • Loading branch information
johguenther and demarle committed Oct 10, 2024
1 parent 024d096 commit f982427
Show file tree
Hide file tree
Showing 26 changed files with 356 additions and 203 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Version History
- New parameter `specularMetallic` for the Principled material to
optionally disable the incluence of `specular` to metallicness,
improving compatibility with glTF `KHR_materials_specular`
- Improvements to and documentation of the pathtracer's Shadow Catcher
feature (enabled via parameter `shadowCatcherPlane`)

### Changes in v3.2.0:

Expand Down
73 changes: 63 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,7 @@ table below.
| float | metallic | 0 | mix between dielectric (diffuse and/or specular) and metallic (specular only with complex IOR) in \[0–1\] |
| float | diffuse | 1 | diffuse reflection weight in \[0–1\] |
| float | specular | 1 | specular reflection/transmission weight in \[0–1\] |
| bool | specularMetallic | true | whether specular influences metallic |
| float | ior | 1 | dielectric index of refraction |
| float | transmission | 0 | specular transmission weight in \[0–1\] |
| vec3f | transmissionColor | white | attenuated color due to transmission (Beer’s law, linear RGB) |
Expand Down Expand Up @@ -2870,8 +2871,8 @@ variance below the `varianceThreshold`. This feature requires a
Per default the background of the rendered image will be transparent
black, i.e., the alpha channel holds the opacity of the rendered
objects. This eases transparency-aware blending of the image with an
arbitrary background image by the application (via
$ospray.rgb + appBackground.rgb⋅(1-ospray.alpha)$). The parameter
arbitrary background image by the application (via the “over” operator,
i.e., $ospray.rgb + appBackground.rgb⋅(1-ospray.alpha)$). The parameter
`backgroundColor` or `map_backplate` can be used to already blend with a
constant background color or backplate texture, respectively, (and
alpha) during rendering.
Expand Down Expand Up @@ -2960,14 +2961,15 @@ realistic materials. This renderer is created by passing the type string
parameters](#renderer) understood by all renderers the path tracer
supports the following special parameters:
| Type | Name | Default | Description |
|:------|:--------------------------|--------:|:------------------------------------------------------------------------------------------------------------------------------------------|
| int | lightSamples | all | number of random light samples per path vertex, best results when a power of 2; per default all light sources are sampled |
| bool | limitIndirectLightSamples | true | after the first non-specular (i.e., diffuse and glossy) path vertex take (at most) a single light sample (instead of `lightSamples` many) |
| int | roulettePathLength | 5 | ray recursion depth at which to start Russian roulette termination |
| int | maxScatteringEvents | 20 | maximum number of non-specular (i.e., diffuse and glossy) bounces |
| float | maxContribution | ∞ | samples are clamped to this value before they are accumulated into the framebuffer |
| bool | backgroundRefraction | false | allow for alpha blending even if background is seen through refractive objects like glass |
| Type | Name | Default | Description |
|:------|:--------------------------|---------:|:------------------------------------------------------------------------------------------------------------------------------------------|
| int | lightSamples | all | number of random light samples per path vertex, best results when a power of 2; per default all light sources are sampled |
| bool | limitIndirectLightSamples | true | after the first non-specular (i.e., diffuse and glossy) path vertex take (at most) a single light sample (instead of `lightSamples` many) |
| int | roulettePathLength | 5 | ray recursion depth at which to start Russian roulette termination |
| int | maxScatteringEvents | 20 | maximum number of non-specular (i.e., diffuse and glossy) bounces |
| float | maxContribution | ∞ | samples are clamped to this value before they are accumulated into the framebuffer |
| bool | backgroundRefraction | false | allow for alpha blending even if background is seen through refractive objects like glass |
| vec4f | shadowCatcherPlane | disabled | components of an invisible [plane](#planes) that captures shadow attentuations, which are blended with the background |
Special parameters understood by the path tracer.
Expand All @@ -2980,6 +2982,57 @@ The scattering albedo can be specified using the [transfer
function](#transfer-function). Extinction is assumed to be spectrally
constant.
Rendering an object with an [HDRI light](#hdri-light) results in
realistic illumination, but the object looks detached and floats in
space. As remedy a so-called Shadow Catcher can be used by setting the
coefficients of a [plane](#planes) via `shadowCatcherPlane`, which will
ground the object by computing matching (contact) shadows. The accuracy
of the shadow estimation depends on the number of light samples per
frame (via `lightSamples` and/or `pixelSamples`), in particular when
using a high-contrast HDRI or multiple (colored) light sources. With
just a single light sample the intensity, gradient and color of the
shadows will potentially be quite off.
<figure>
<img src="https://ospray.github.io/images/shadowcatcher_envonly.jpg"
style="width:80.0%"
alt="Chair rendered in an interior HDRI environment." />
<figcaption aria-hidden="true">Chair rendered in an interior HDRI
environment.</figcaption>
</figure>
<figure>
<img src="https://ospray.github.io/images/shadowcatcher.jpg"
style="width:80.0%"
alt="Same scene with enabled Shadow Catcher plane." />
<figcaption aria-hidden="true">Same scene with enabled Shadow Catcher
plane.</figcaption>
</figure>
Applications can also perform their own compositing of the captured
shadow, because the shadow is also baked into the alpha channel (which
however means loosing the color tint of the shadow). To do so, the
`backgroundColor` must be transparent (alpha=0) and should be black
(otherwise the background color will bleed into the shadow); likewise,
light sources should be set to `visible=false` to avoid them being
visible in the background. When using the [denoiser](#denoiser) best
enable `denoiseAlpha` as well.
<figure>
<img src="https://ospray.github.io/images/shadowcatcher_alpha.png"
style="width:80.0%"
alt="The Shadow Catcher also bakes the (monochrome) shadow into the alpha channel, which can be used for further compositing." />
<figcaption aria-hidden="true">The Shadow Catcher also bakes the
(monochrome) shadow into the alpha channel, which can be used for
further compositing.</figcaption>
</figure>
Framebuffer
-----------
Expand Down
7 changes: 7 additions & 0 deletions apps/ospExamples/GLFWOSPRayWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,13 @@ void GLFWOSPRayWindow::buildUI()
addObjectToCommit(renderer->handle());
}

static vec4f shadowCatcherPlane{0.0f};
if (ImGui::DragFloat4(
"shadowCatcherPlane", &shadowCatcherPlane[0], 0.05f, -1.f, 1.0f)) {
renderer->setParam("shadowCatcherPlane", shadowCatcherPlane);
addObjectToCommit(renderer->handle());
}

if (ImGui::SliderFloat("camera motion blur", &cameraMotionBlur, 0.f, 1.f)) {
updateCamera();
}
Expand Down
1 change: 1 addition & 0 deletions apps/ospTestSuite/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ add_executable(ospTestSuite
test_framebuffer.cpp
test_interpolation.cpp
test_imageop.cpp
test_shadowcatcher.cpp
ospTestSuite.cpp
)

Expand Down
89 changes: 89 additions & 0 deletions apps/ospTestSuite/test_shadowcatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#include "test_fixture.h"

namespace OSPRayTestScenes {

// Fixture class for a unit test of the pathtracer's shadowCatcherPlane feature
class ShadowCatcher : public Base,
public ::testing::TestWithParam<bool /*multipleLights*/>
{
public:
ShadowCatcher();
void SetUp() override;

protected:
};

ShadowCatcher::ShadowCatcher()
{
rendererType = "pathtracer";
samplesPerPixel = 32;
}

void ShadowCatcher::SetUp()
{
Base::SetUp();
camera.setParam("position", vec3f(-0.2f, 2.5f, -3.f));
camera.setParam("direction", vec3f(0.f, -0.3f, 1.0f));

cpp::Geometry boxGeometry("box");
boxGeometry.setParam("box", cpp::CopiedData(box3f(vec3f(0.f), vec3f(0.5f, 2.f, 1.0f))));
boxGeometry.commit();
cpp::GeometricModel model(boxGeometry);

cpp::Material material("obj");
material.setParam("kd", vec3f(0.1f));
material.commit();
model.setParam("material", material);
AddModel(model);

// floor, to be hidden by ShadowCatcher
cpp::Geometry planeGeometry("plane");
planeGeometry.setParam("plane.coefficients", cpp::CopiedData(vec4f(0.0, 1.0, 0.0, 0.f)));
planeGeometry.commit();
cpp::GeometricModel planeModel(planeGeometry);

cpp::Material planeMaterial("obj");
planeModel.setParam("material", planeMaterial);
AddModel(planeModel);

if (GetParam()) {
cpp::Light distant("distant");
distant.setParam("direction", vec3f(5.f, -2.0f, 5.8f));
distant.setParam("color", vec3f(15.0f, 3.0f, 3.0f));
distant.setParam("angularDiameter", 3.0f);
AddLight(distant);

cpp::Light sphere("sphere");
sphere.setParam("position", vec3f(2.f, 2.4f, -1.0f));
sphere.setParam("color", vec3f(3.f, 150.f, 3.f));
sphere.setParam("radius", 0.02f);
AddLight(sphere);

cpp::Light ambient("ambient");
ambient.setParam("visible", false);
ambient.setParam("color", vec3f(0.0f, 0.0f, 3.0f));
AddLight(ambient);
} else {
cpp::Light light("spot");
light.setParam("position", vec3f(-1.f, 2.4f, 1.0f));
light.setParam("direction", vec3f(1.5f, -1.0f, 0.0f));
light.setParam("radius", 0.2f);
AddLight(light);
}

renderer.setParam("shadowCatcherPlane", vec4f(0.0, 1.0, 0.0, 0.1f));
renderer.setParam("lightSamples", 1);
renderer.setParam("backgroundColor", vec4f(0.3f, 0.3f, 0.3f, 0.0f));
}

TEST_P(ShadowCatcher, multipleLights)
{
PerformRenderTest();
};

INSTANTIATE_TEST_SUITE_P(TestShadowCatcher, ShadowCatcher, ::testing::Bool());

} // namespace OSPRayTestScenes
4 changes: 2 additions & 2 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ SHELL := /bin/bash
webpages := $(addprefix www/, $(addsuffix .html, index tutorials documentation gallery downloads related_projects displaywall ospray2_porting_guide legal))
process_version := $(addprefix tmp/, $(addsuffix .md, tutorials getting_ospray readme_head))
tmptexfiles := $(addprefix tmp/, $(addsuffix .tex, overview changelog compilation api tutorials))
images_jpg := $(addprefix images/, $(addsuffix .jpg, exampleViewer $(addprefix camera_, perspective architectural stereo orthographic panoramic) $(addprefix material_, OBJ Principled CarPaint Metal Alloy Glass ThinGlass MetallicPaint Luminous) ColoredWindow $(addprefix ospMPIDistribTutorial, Volume Spheres _firstFrame _accumulatedFrame)))
images_png := $(addprefix images/, $(addsuffix .png, diffuse_rooms normalmap_frustum tutorial_accumulatedframe tutorial_firstframe ospExamples renderSunSky))
images_jpg := $(addprefix images/, $(addsuffix .jpg, exampleViewer $(addprefix camera_, perspective architectural stereo orthographic panoramic) $(addprefix material_, OBJ Principled CarPaint Metal Alloy Glass ThinGlass MetallicPaint Luminous) ColoredWindow $(addprefix ospMPIDistribTutorial, Volume Spheres _firstFrame _accumulatedFrame) shadowcatcher shadowcatcher_envonly))
images_png := $(addprefix images/, $(addsuffix .png, diffuse_rooms normalmap_frustum tutorial_accumulatedframe tutorial_firstframe ospExamples renderSunSky shadowcatcher_alpha))
images_fig := spot_light c-gamma_coords quad_light hdri_light
images_svg := structured_spherical_coords vdb_structure

Expand Down
38 changes: 36 additions & 2 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2845,8 +2845,8 @@ refinement of image regions that have an estimated variance below the
Per default the background of the rendered image will be transparent
black, i.e., the alpha channel holds the opacity of the rendered
objects. This eases transparency-aware blending of the image with an
arbitrary background image by the application (via $ospray.rgb +
appBackground.rgb⋅(1-ospray.alpha)$). The parameter
arbitrary background image by the application (via the "over" operator,
i.e., $ospray.rgb + appBackground.rgb⋅(1-ospray.alpha)$). The parameter
`backgroundColor` or `map_backplate` can be used to already blend with a
constant background color or backplate texture, respectively, (and
alpha) during rendering.
Expand Down Expand Up @@ -2988,6 +2988,11 @@ supports the following special parameters:
bool backgroundRefraction false allow for alpha blending even if
background is seen through
refractive objects like glass

vec4f shadowCatcherPlane disabled components of an invisible [plane]
that captures shadow
attentuations, which are blended
with the background
------ -------------------------- -------- ----------------------------------
: Special parameters understood by the path tracer.

Expand All @@ -2998,6 +3003,35 @@ The path tracer supports [volumes](#volumes) with multiple scattering.
The scattering albedo can be specified using the [transfer function].
Extinction is assumed to be spectrally constant.

Rendering an object with an [HDRI light] results in realistic
illumination, but the object looks detached and floats in space. As
remedy a so-called Shadow Catcher can be used by setting the
coefficients of a [plane] via `shadowCatcherPlane`, which will ground
the object by computing matching (contact) shadows. The accuracy of the
shadow estimation depends on the number of light samples per frame
(via `lightSamples` and/or `pixelSamples`), in particular when using a
high-contrast HDRI or multiple (colored) light sources. With just a
single light sample the intensity, gradient and color of the shadows
will potentially be quite off.

![Chair rendered in an interior HDRI environment.][imgNoShadowCatcher]

![Same scene with enabled Shadow Catcher plane.][imgShadowCatcher]

Applications can also perform their own compositing of the captured
shadow, because the shadow is also baked into the alpha channel (which
however means loosing the color tint of the shadow). To do so, the
`backgroundColor` must be transparent (alpha=0) and should be black
(otherwise the background color will bleed into the shadow); likewise, light
sources should be set to `visible=false` to avoid them being visible in
the background. When using the [denoiser] best enable `denoiseAlpha` as
well.

![The Shadow Catcher also bakes the (monochrome) shadow into the alpha
channel, which can be used for further
compositing.][imgShadowCatcherAlpha]


Framebuffer
-----------

Expand Down
3 changes: 3 additions & 0 deletions doc/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@
[imgColoredWindow]: ColoredWindow.jpg { width=60% }
[imgStructuredSphericalCoords]: structured_spherical_coords.svg { width=60% }
[imgVdbStructure]: vdb_structure.svg { width=80% }
[imgShadowCatcher]: shadowcatcher.jpg { width=80% }
[imgNoShadowCatcher]: shadowcatcher_envonly.jpg { width=80% }
[imgShadowCatcherAlpha]: shadowcatcher_alpha.png { width=80% }

2 changes: 2 additions & 0 deletions doc/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
[group]: documentation.html#groups
[instance]: documentation.html#instances
[world]: documentation.html#world
[plane]: documentation.html#planes
[data]: documentation.html#data
[GeometricModel]: documentation.html#geometricmodels
[VolumetricModel]: documentation.html#volumetricmodels
Expand All @@ -22,6 +23,7 @@
[material]: documentation.html#materials
[OBJ material]: documentation.html#obj-material
[point light]: documentation.html#point-light-sphere-light
[HDRI light]: documentation.html#hdri-light

[example applications]: tutorials.html#tutorials
[tutorial]: tutorials.html#osptutorial
Expand Down
1 change: 0 additions & 1 deletion modules/cpu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ set(OSPRAY_KERNEL_SOURCES
render/pathtracer/GeometryLight.ispc
render/pathtracer/VirtualLight.ispc
render/pathtracer/TransparentShadow.ispc
render/pathtracer/ShadowCatcher.ispc
render/pathtracer/NextEventEstimation.ispc

render/bsdfs/BSDF.ispc
Expand Down
5 changes: 5 additions & 0 deletions modules/cpu/common/FeatureFlagsEnum.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ enum FeatureFlagsOther

FFO_CAMERA_MOTION_BLUR = 1 << 29,

FFO_RENDERER_SHADOWCATCHER = 1 << 30,

FFO_ALL = 0xffffffff
};

Expand Down Expand Up @@ -367,6 +369,9 @@ inline void printFfo(uint32_t flags)

if (flags & FFO_CAMERA_MOTION_BLUR)
std::cout << "FFO_CAMERA_MOTION_BLUR" << std::endl;

if (flags & FFO_RENDERER_SHADOWCATCHER)
std::cout << "FFO_RENDERER_SHADOWCATCHER" << std::endl;
}
#endif

Expand Down
21 changes: 18 additions & 3 deletions modules/cpu/render/ScreenSample.ih
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ struct ScreenSample
unsigned int primID;
unsigned int geomID;
unsigned int instID;

// for shadow catcher
vec4f unshaded;
vec4f shadow; // includes blended background in rgb, but not in alpha
unsigned int cnt;
};

inline ScreenSample make_ScreenSample_zero()
Expand All @@ -42,6 +47,9 @@ inline ScreenSample make_ScreenSample_zero()
screenSample.firstNormal = make_vec3f(0.0f);
screenSample.normal = make_vec3f(0.0f);
screenSample.albedo = make_vec3f(0.0f);
screenSample.unshaded = make_vec4f(0.0f);
screenSample.shadow = make_vec4f(0.0f);
screenSample.cnt = 0;
screenSample.primID = RTC_INVALID_GEOMETRY_ID;
screenSample.geomID = RTC_INVALID_GEOMETRY_ID;
screenSample.instID = RTC_INVALID_GEOMETRY_ID;
Expand All @@ -57,6 +65,9 @@ inline void ScreenSample_accumulate(
accum.firstNormal = accum.firstNormal + sample.firstNormal;
accum.normal = accum.normal + sample.normal;
accum.albedo = accum.albedo + sample.albedo;
accum.unshaded = accum.unshaded + sample.unshaded;
accum.shadow = accum.shadow + sample.shadow;
accum.cnt += sample.cnt;
if (absf(sample.z) < absf(accum.z)) {
accum.z = sample.z;
accum.primID = sample.primID;
Expand All @@ -65,15 +76,19 @@ inline void ScreenSample_accumulate(
}
}

inline void ScreenSample_normalize(ScreenSample &sample, uniform int32 spp)
inline void ScreenSample_normalize(ScreenSample &sample, uniform float rspp)
{
const uniform float rspp = rcpf(spp);
sample.rgb = sample.rgb * rspp;
sample.alpha = sample.alpha * rspp;
sample.alpha *= rspp;
sample.position = sample.position * rspp;
sample.firstNormal = sample.firstNormal * rspp;
sample.normal = sample.normal * rspp;
sample.albedo = sample.albedo * rspp;
}

inline void ScreenSample_normalize(ScreenSample &sample, uniform int32 spp)
{
ScreenSample_normalize(sample, rcpf(spp));
}

OSPRAY_END_ISPC_NAMESPACE
Loading

0 comments on commit f982427

Please sign in to comment.