Cycles: Experiment with shadow catcher
Needs ReviewPublic

Authored by Sergey Sharybin (sergey) on Feb 11 2016, 11:42 AM.
Tags
None
Tokens
"Mountain of Wealth" token, awarded by johnroper100."Love" token, awarded by aliasguru."Love" token, awarded by hype."Love" token, awarded by aditiapratama."Love" token, awarded by looch."Love" token, awarded by venomgfx."Love" token, awarded by poor."Mountain of Wealth" token, awarded by electronicpulse."Love" token, awarded by juang3d."Love" token, awarded by GiantCowFIlms."Love" token, awarded by gandalf3."Love" token, awarded by saphires."Love" token, awarded by noemis."Love" token, awarded by quollism."Love" token, awarded by sebastian_k."Love" token, awarded by duarteframos."Love" token, awarded by januz."Love" token, awarded by Lapineige."Love" token, awarded by railla."Love" token, awarded by zuggamasta."Love" token, awarded by plasmasolutions."Love" token, awarded by plyczkowski.

Details

Summary

DISCLAIMER: This is more a code dump of a local branch, not somewhat really finished or so. Underlying math is the subject for rework since it's not quite physically based at all.

Publishing to start collaboration with other Cycles developers who are looking into solving this puzzle.

What do we consider a shadow catcher?

That's a good question actually, and there's no single formulation of what it exactly is and mathematically it's a bit malformed in the constraints we're working on. Ideally shadow catcher is a difference between image rendered without artificial objects and with them. Such approach gives best ever shadows, but takes 2x more time to render. So for good usability we need to get some assumptions, make system a bit more biased but give artists an useful tool.

Shadow catcher is mainly used by VFX artists to inject artificial objects into real footage. At least that definition we'll stick to
in Blender. Hence here's what shadow catcher should be capable of doing:

  • Receive shadows from other objects: be totally transparent when there's no shadows cast on it, be more opaque in shaded areas.
  • Ignore self-shadowing and shading. Shadows caused by occlusion with itself already exists in the footage. Same applies to the

shading -- all shading caused by material itself are also in the footage already.

  • Interact with other objects in the scene. This sounds a bit tricky but makes sense actually. Consider situation when one needs to put sharp glossy object into the footage: you'll want objects from a real scene to be reflected in the artificial object. And often you'll want the object on which shadow is to be cast to be reflected in such situations. Surely you can escape with copying object and playing with ray visibility, but that's complicated scene setup instead of making it simpler.
  • Be affected with indirect light. Cycles is the GI render engine after all!

How to use the shadow catcher?

  1. Create an object on which you want to receive shadow.
  2. Create some basic material setup which is close to a real object.
  3. Enable "Shadow Catcher" in Object buttons -> Cycles Settings.
  4. Be happy! (hopefully, once we've debugged all the code)

What this patch actually contains?

It contains all the bits which tries to implement definition of shadow catcher above. It is trying to implement it all in a way so we don't need to make big changes in the ray integration loop, hence it has some tricky magic to deduct what was the received shadow from the light passes and will fail in certain situations, mainly when there is no direct lighting of the object at all. It is totally tweakable to become more artists friendly, i just didn't have enough time to try all the ideas and used whatever latest semi-working formula was.

Major changes are in fact made around shadow_blocked() to exclude shading from self. This part is based on an older patch which tried to expose it to an user. That exposing settings are somewhat malformed and shouldn't really be used. In fact, we should remove those settings from the interface.

Some pictures?

Sure, here's one from a hackish patch:

(This is a glossy monkey on a checker board floor, floor is makred as a catcher, Here's .blend file shadow_catcher.blend)

Diff Detail

Repository
rB Blender
Branch
cycles_shadowcatcher_v5
Build Status
Buildable 426
Build 426: arc lint + arc unit
There are a very large number of changes, so older changes are hidden. Show Older Changes

I like it so far! Simple to set up, very nice to see in the viewport, and a bit faster and easier to use in compositing. Great work! Looking forward to seeing how it continues to develop.

Is the idea to someday have it as an actual shader? That might give a bit more control, especially in the viewport.

Anyway, nice work so far! Keep it up!

Hey everyone, I wanted to pass on some notes from a VFX studio we're working with and hoping to utilize Shadowcatcher. We did some tests and found some discrepancies. Maybe we compiled it wrong?

Shadowcatcher-notes.pdf

I like it so far! Simple to set up, very nice to see in the viewport, and a bit faster and easier to use in compositing. Great work! Looking forward to seeing how it continues to develop.

Is the idea to someday have it as an actual shader? That might give a bit more control, especially in the viewport.

Anyway, nice work so far! Keep it up!

Can you try to repeat the tests I did some post before to check how is it behaving with geometry lights and skylight?

I'm very happy this is evolving! :D

Cheers!

For some reason it's not compiling against latest master for me on linux:

blender/intern/cycles/render/film.cpp: In member function ‘void ccl::Film::device_update(ccl::Device*, ccl::DeviceScene*, ccl::Scene*)’:
blender/intern/cycles/render/film.cpp:321:49: error: ‘class ccl::ObjectManager’ has no member named ‘has_shadow_catcher’
                          scene->object_manager->has_shadow_catcher;

I did a couple tests to see if I had any problems in the alpha channel.

In this scene, it's just an environment light (HDRI). Seems to be working as expected.

In this next one, I added a point light and a mesh light (plane). Still seems to be working ok.

And as I was doing a demo scene for a blog post, I noticed that a mesh light on my test object was wrecking the shadow catcher. It's a bit strange that a geometry light was wrecking the scene in my blog test scene, but the mesh light in the other test scene did not. Perhaps it has to do with the positioning and shape of the mesh light. In mt blog test scene, it's a cylinder, and is inset into other geometry a bit. Perhaps that's causing the ground plane to be picking up a big cast shadow from the geometry that is blocking the cylinder light?

I did a couple tests to see if I had any problems in the alpha channel.

In this scene, it's just an environment light (HDRI). Seems to be working as expected.

In this next one, I added a point light and a mesh light (plane). Still seems to be working ok.

And as I was doing a demo scene for a blog post, I noticed that a mesh light on my test object was wrecking the shadow catcher. It's a bit strange that a geometry light was wrecking the scene in my blog test scene, but the mesh light in the other test scene did not. Perhaps it has to do with the positioning and shape of the mesh light. In mt blog test scene, it's a cylinder, and is inset into other geometry a bit. Perhaps that's causing the ground plane to be picking up a big cast shadow from the geometry that is blocking the cylinder light?

Yeah, it's possible, can you post a screenshot of the viewport to check it?

Cheers.

Nooo! Do not share screenshots, share .blend file instead! That is the only way for developers to troubleshoot.

This applies to @Sean Kennedy (hype) and @David Andrade (davidandrade).

@David Andrade (davidandrade), when you mention GPU, is it CUDA or OpenCL? OpenCL is currently missing implementation, so i'm not sure what funky results will be happening there.

Nooo! Do not share screenshots, share .blend file instead! That is the only way for developers to troubleshoot.

This applies to @Sean Kennedy (hype) and @David Andrade (davidandrade).

@David Andrade (davidandrade), when you mention GPU, is it CUDA or OpenCL? OpenCL is currently missing implementation, so i'm not sure what funky results will be happening there.

You are right, sorry, I just asked for a screenshot to confirm the posibility he was saying because I have no time right now to test a blend file (I'm in a tight deadline project right now), but you are totally right, is best if it's a blend file so it can be investigated :)

Cheers.

Woops, just noticed i didn't put a smiley face is the reply. So don't read that an angry reply ;) It just really saves a lot of time if you simply upload .blend file here. Then we have all the information immediately to start troubleshooting.

Don't worry :) I took it right I never take anything as angry or too serious unless it's pretty obvious it is writte with anger or seriousness hehe

Cheers!

Here's the blend file of the scene with the green sphere

shadow_catcher_submit_01.blend

And as I was doing a demo scene for a blog post, I noticed that a mesh light on my test object was wrecking the shadow catcher. It's a bit strange that a geometry light was wrecking the scene in my blog test scene, but the mesh light in the other test scene did not. Perhaps it has to do with the positioning and shape of the mesh light. In mt blog test scene, it's a cylinder, and is inset into other geometry a bit. Perhaps that's causing the ground plane to be picking up a big cast shadow from the geometry that is blocking the cylinder light?

I tested moving the emission cylinder today, and that's what was causing the darkened ground plane. So this was NOT a bug or a problem, just an issue with the placement and design. Shadow catcher is working as expected in this scene.

Blend attached :) via Casi @ Barnstorm VFX{F337964}

Update against latest master

@Sean Kennedy (hype), indeed this is just a shadow from sphere. Not sure it is correct or not, it is just how it is.

@David Andrade (davidandrade), self-shadowing of catcher should be disabled by default. But seems i did some mistake when doing that for indirect light. having a look now.

Update self-shadoing part of the patch.

Solves point (2) pointed by @David Andrade (davidandrade).

YAFU (YAFU) added a subscriber: YAFU (YAFU).EditedAug 18 2016, 1:12 AM

Hi.
I recently discovered this about the Shadow Catcher and I'm not good at compositing, then sorry if my .blend file have errors.
My concern is whether the intensity of the shadows obtained on the plane with shadow catcher should be similar to that obtained on a plane with difuse material and without shadow catcher. Here in this scene render shadow catcher when you just open and render image. If you enable the first render layer and disables the other three, you get the scene with shadow over the plane without shadow catcher (I guess):

shadow_catcher_testing_2.blend

Shadows over the plane:
http://www.image-share.com/upload/3302/244.jpg

Shadows over Shadow Catcher plane:
http://www.image-share.com/upload/3302/245.jpg

In the scene the two sun lamps differ in intensity. Some differences are also notices if they differ in size.

Eeh, had some unpublished comments since yesterday. @YAFU (YAFU), they'll answer your concern :)

  • While new approach solved issues with semi-shaded catcher when light looks out of the mesh, it indeed introduced other issue which is more uniform intensities of shadows. This i'm looking into now.
  • I've tried to support reflections, but so far didn't find really reliable way of doing that. The issue was that light itself might be reflected (similar to when you've got fully glossy material). Need to find a way of avoiding that.
  • In regard of making it a shader: the idea of the approach is to allow the shadow catcher interact with CG objects, so it can be properly reflected in artificial objects. If shadow catcher is a shader on it's own it becomes either tricky setup with two objects needed with camera/shadow ray visibility to achieve similar effect, or would need to be added several exceptions in the surface sampling code which will ignore one or other closures. For fine-tuning think it's more powerful to have a shadow catcher pass which gives lots of flexibility. I also guess there should not be much tweaks needed once we solve the intensity/reflections.

Anyway, guess i'd better go back to code/research :)

New patch with another approach.

Uses approach of doing sort of deferred rendering by calculating a difference
between overall path radiance and the directly visible one (without shadow
blocked check). This seems to be one of the few approaches which takes proper
shadow into account (so weaker lights has less strong shadow).

NOTE: Requires branched path trace and all light sample option enabled to have proper shadows.
YAFU (YAFU) added a comment.EditedAug 18 2016, 10:54 PM

Thank you sergey.
This new patch seems to be working much better. I've tried it with my file with different sizes and strength in lamps and shadows match pretty well in the plane with shadow catcher and in the plane without shadow catcher.
I have also tried the file 'shadow_catcher_submit_01.blend' by hype, and the blue emmiter ring no longer have much influence on the total shading, which is good. In this scene I just noticed that there are some reflections in the plane with Displace Modifier under the hard black shadow, and I'm not sure about whether this should be so. Select the Sun lamp, set Size=0, and you start to move the Strength smoothly from 0 to 10 while you see the shadow changes in rendered view preview. It looks a little weird for me, but maybe that's as it should be.

Edit:
Above someone had said that the previous patch did not work with branched path tracing. That's why I had used only path tracing with the old patch in previous message/file. So considering that with this new patch I used BPT, I think that direct comparison of the two versions of the patch I did in this new post is not valid.

Hi Sergey,

did test it quickly and got a few questions / observations:

Question

  • Is it possible to mimic the old BI "shadow only" behavior with this patch as well (so that the background / scene behind the catcher is visible)? I target especially at motion graphics here where you would have a curved background "shadow only" plane on a hand drawn background in camera. Atm I've got the impression that it's only possible to create a "holdout shader" version with shadow that you have to composite over your footage (no big deal - but one step more)

Observations

  • Fiddled around with all settings really long until I was aware that it does not work with GPU (for now)
  • If you've got a gray background in the scene and no light you have no working shadow catcher even if you enabled it
  • As soon as you set one color component of a point light to 0 (R 0.0, G 1.0, B 1.0 for example), the shadow disappears (only one light in the scene - no world)

Thanks so much for your efforts, very much appreciated!

@YAFU (YAFU), yeah, apparently previous approach worked fine-ish with BPT as well. Since it was somewhat simpler i'll try rolling back to it but making it to work the same for PR and BPT. That should also fix the issue you're reported about displacement.

@Thomas Beck (plasmasolutions),

  • In the previous version of the patch that was __SHADOW_CATCHER_BACKGROUND__ toggle in the sources which does exactly that. Question is: do we always want background to be there when film is not transparent? Probably we do, since for VFX you'll likely want to have transparent film anyway. But that's an open discussion.
  • GPU might have indeed be borken with the very latest version of the patch. Works fine in the WIP update i've got locally now (as mentioned above, reverting parts of the latest update trying to see if simpler approach will work just fine when it's properly done for both BPT and PT).
  • That's somewhat tricky, but will try to compensate for that.
  • Should also be fixed in an upcoming version of the patch.

@YAFU (YAFU), sorry for the spam but forgot to mention.. There will be certain extent of brighter-looking areas in the shadow due to indirect light bounces from the bumps.

Yet another version, hopefully addresses all the feedback

  • Solved issues with just a background light
  • Solves issue with colored lamps
  • Solves issues with GPU
  • Both PT and BPT are expected to work

One might add #define __SHADOW_CATCHER_BACKGROUND__ after __SHADOW_TRICKS__ in
kernel_types.h to have behavior requested by @Thomas Beck (plasmasolutions).

Updated the patch.

At this point i think we should create some sort of set of .blend files which demonstrates some real use cases and corner cases. Would help a lot ensuring that every next patch version is indeed an improvement.

Anyone to collect such a thing maybe perhaps? :)

Sounds good! I will get together a test file that demonstrates some of the real use cases that we've been going through :)

Hi Sergey,

after a lot of testing with your current patch I must say that this is now exactly how I would expect it to be: When I use your background define it's working as intended (Film->Transparent makes the background transparent, otherwise it's visible), GPU, colored lamps and no "real" background are working as well. Compositing it over real footage works perfectly... so nothing to complain anymore.

Will perform more tests in the following days...and gather some test files.

Hi Sergey!

We went ahead and made a similar file to one of our production scenes to show off exactly how Shadowcatcher was being used.

MHC_201_008_020_FGMGCU_AMBIENT_THEORY_v014.blend

Overall I've got no immediate notes! I think everything is looking good :)

Fixes for branched path traders

Was missing some scaling factors here and there. Also did some simplification
of what needs to be stored to have properly calculated shadow.

Once thing i still didn't manage to solve is difference between PT and BPT
when catching shadow in a scene lit by only world AO.

Fixes for AO

BPT gets closer to PT with change and it's seems logical thing to do anyway,
but somehow there is still some difference in AO between BPT and PT.

Also disabled shadow tricks for split kernel OpenCL -- it's a bit tricky to
implement them in this kernel.

applying the patch currently fails, the kernel_types.h.rej file says:

diff a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h    (rejected hunks)
@@ -728,10 +744,11 @@
     SD_OBJECT_HAS_VOLUME        = (1 << 26),  /* object has a volume shader */
     SD_OBJECT_INTERSECTS_VOLUME = (1 << 27),  /* object intersects AABB of an object with volume shader */
     SD_OBJECT_HAS_VERTEX_MOTION = (1 << 28),  /* has position for motion vertices */
+    SD_OBJECT_SHADOW_CATCHER    = (1 << 29),  /* object is used to catch shadows */
 
     SD_OBJECT_FLAGS = (SD_HOLDOUT_MASK|SD_OBJECT_MOTION|SD_TRANSFORM_APPLIED|
                        SD_NEGATIVE_SCALE_APPLIED|SD_OBJECT_HAS_VOLUME|
-                       SD_OBJECT_INTERSECTS_VOLUME)
+                       SD_OBJECT_INTERSECTS_VOLUME|SD_OBJECT_SHADOW_CATCHER)
 };
 
 #ifdef __SPLIT_KERNEL__

Hey guys, I just realized in production tests this is self-shadowing. Are we doing something wrong? Is there anyway to remove that?

Rebase against latest master

More real work is upcoming.

Fix typo in light sampling

Was causing missing shadows in files with no world in certain cases.

Hi Sergey,

What are the chances to have the most anticipated patch of the century D1788 merged to master soon? The functionality seems to be complete, we're running the patched build from 3rd of October here without any issues. However, due to the speed of developers, this patch often becomes un-applyable after a short time period, at least for non-C++ guys. Would be nice to know if you're having plans to merge it with master?

best regards,
Rainer

I think the one really remaining issue is the self shadowing from Enviroment maps. The object that is a shadow catcher should never cast shadows

Mainly rebasing against latest master, don't expect any difference in behavior

@Lukas Stockner (lukasstockner97), i've tried implementing your idea of using non-MIS-weighted
numbers for the shadow, but there were following issues:

  • It made BPT somewhat darker shadows than with PT
  • I'm not sure we can store MIS weight in BsdfEval anymore: this weight is different for different terms in the BSDF eval sum, so don't think we can divide final sum bu overall MIS weight to get number we want?

Either i misunderstood which exact MIS you suggest to exclude from there or
did some mistakes in implementation. Think this is most relevant experiment
patch for this idea:

1diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h
2index a87e6bf..04dc53f 100644
3--- a/intern/cycles/kernel/kernel_accumulate.h
4+++ b/intern/cycles/kernel/kernel_accumulate.h
5@@ -52,30 +52,36 @@ ccl_device_inline void bsdf_eval_init(BsdfEval *eval, ClosureType type, float3 v
6​ {
7​ eval->diffuse = value;
8​ }
9+ eval->mis_weight = 0.0f;
10​ }
11
12-ccl_device_inline void bsdf_eval_accum(BsdfEval *eval, ClosureType type, float3 value)
13+ccl_device_inline void bsdf_eval_accum(BsdfEval *eval,
14+ ClosureType type,
15+ float3 value,
16+ float mis_weight)
17​ {
18+ const float3 mis_value = value * mis_weight;
19​ #ifdef __PASSES__
20​ if(eval->use_light_pass) {
21​ if(CLOSURE_IS_BSDF_DIFFUSE(type))
22- eval->diffuse += value;
23+ eval->diffuse += mis_value;
24​ else if(CLOSURE_IS_BSDF_GLOSSY(type))
25- eval->glossy += value;
26+ eval->glossy += mis_value;
27​ else if(CLOSURE_IS_BSDF_TRANSMISSION(type))
28- eval->transmission += value;
29+ eval->transmission += mis_value;
30​ else if(CLOSURE_IS_BSDF_BSSRDF(type))
31- eval->subsurface += value;
32+ eval->subsurface += mis_value;
33​ else if(CLOSURE_IS_PHASE(type))
34- eval->scatter += value;
35+ eval->scatter += mis_value;
36
37​ /* skipping transparent, this function is used by for eval(), will be zero then */
38​ }
39​ else
40​ #endif
41​ {
42- eval->diffuse += value;
43+ eval->diffuse += mis_value;
44​ }
45+ eval->mis_weight += mis_weight;
46​ }
47
48​ ccl_device_inline bool bsdf_eval_is_zero(BsdfEval *eval)
49@@ -333,7 +339,7 @@ ccl_device_inline void path_radiance_accum_light(PathRadiance *L, float3 through
50​ }
51
52​ #ifdef __SHADOW_TRICKS__
53- float3 light = throughput * bsdf_eval_sum(bsdf_eval);
54+ float3 light = throughput * bsdf_eval_sum(bsdf_eval) / bsdf_eval->mis_weight;
55​ L->path_total += light;
56​ L->path_total_shaded += shadow * light;
57​ #endif
58@@ -345,7 +351,7 @@ ccl_device_inline void path_radiance_accum_total_light(
59​ const BsdfEval *bsdf_eval)
60​ {
61​ #ifdef __SHADOW_TRICKS__
62- L->path_total += throughput * bsdf_eval_sum(bsdf_eval);
63+ L->path_total += throughput * bsdf_eval_sum(bsdf_eval) / bsdf_eval->mis_weight;
64​ #else
65​ (void) L;
66​ (void) throughput;
67diff --git a/intern/cycles/kernel/kernel_shader.h b/intern/cycles/kernel/kernel_shader.h
68index c1b3153..2d8de0e 100644
69--- a/intern/cycles/kernel/kernel_shader.h
70+++ b/intern/cycles/kernel/kernel_shader.h
71@@ -509,7 +509,7 @@ ccl_device_inline void _shader_bsdf_multi_eval(KernelGlobals *kg, ShaderData *sd
72​ float3 eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf);
73
74​ if(bsdf_pdf != 0.0f) {
75- bsdf_eval_accum(result_eval, sc->type, eval*sc->weight);
76+ bsdf_eval_accum(result_eval, sc->type, eval*sc->weight, 1.0f);
77​ sum_pdf += bsdf_pdf*sc->sample_weight;
78​ }
79
80@@ -537,7 +537,8 @@ ccl_device_inline void _shader_bsdf_multi_eval_branched(KernelGlobals *kg,
81​ float mis_weight = use_mis? power_heuristic(light_pdf, bsdf_pdf): 1.0f;
82​ bsdf_eval_accum(result_eval,
83​ sc->type,
84- eval * sc->weight * mis_weight);
85+ eval * sc->weight,
86+ mis_weight);
87​ }
88​ }
89​ }
90@@ -925,7 +926,7 @@ ccl_device_inline void _shader_volume_phase_multi_eval(const ShaderData *sd, con
91​ float3 eval = volume_phase_eval(sd, sc, omega_in, &phase_pdf);
92
93​ if(phase_pdf != 0.0f) {
94- bsdf_eval_accum(result_eval, sc->type, eval);
95+ bsdf_eval_accum(result_eval, sc->type, eval, 1.0f);
96​ sum_pdf += phase_pdf*sc->sample_weight;
97​ }
98
99diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h
100index ea3d514..9c1dcca 100644
101--- a/intern/cycles/kernel/kernel_types.h
102+++ b/intern/cycles/kernel/kernel_types.h
103@@ -455,6 +455,7 @@ typedef struct BsdfEval {
104​ float3 subsurface;
105​ float3 scatter;
106​ #endif
107+ float mis_weight;
108​ } BsdfEval;
109
110​ /* Shader Flag */

Mind checking if i understood your correct?

@Brecht Van Lommel (brecht), can we start some discussion about what is the best approach to get
this into master? What i have in mind is:

  • Figure out whether i got Lukas's idea right.
  • Make this an Experimental option, so we keep some margin about making possible changes in the render results if we'll re-consider some parts of the change to work better with denoiser.

Committing this first as experimental seems ok.

I'm not sure what the MIS idea is, I can't find any discussion about that? The problem that I remember from when I tried to implement something similar (D524) is that handling the BSDF sampling component of MIS is difficult. For light sampling we trace a shadow ray, and it is relatively easy to compute a shadowed and unshadowed contribution. For BSDF sampling we compute it by piggy-backing on the indirect light path, but because of the way that works we never compute unshadowed contributions.

A workaround would be to disable lamp MIS for shadow catchers and only use light sampling (at the cost of more noise and inability to handle perfectly sharp glossy). Is that the intended idea here?

To support lamp MIS, an extra call to indirect_lamp_emission() with an infinite ray length could work, next to the call to indirect_background()? I don't fully understand yet how that part of the patch works, handling transparency and shadow catcher self hits correctly is not so simple.

intern/cycles/kernel/kernel_types.h
447

Suggest to rename to combined_unshadowed and combined_shadowed, shaded is a bit ambiguous terminology here.

That is pretty much what I had in mind, yes. The problem indeed seems to be just summing up the weights when using BPT.
I didn't extensively test this, but as far as I can see this should work:

1diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h
2index a87e6bf..6444ba2 100644
3--- a/intern/cycles/kernel/kernel_accumulate.h
4+++ b/intern/cycles/kernel/kernel_accumulate.h
5@@ -52,10 +52,17 @@ ccl_device_inline void bsdf_eval_init(BsdfEval *eval, ClosureType type, float3 v
6​ {
7​ eval->diffuse = value;
8​ }
9+#ifdef __SHADOW_TRICKS__
10+ eval->sum_no_mis = make_float3(0.0f, 0.0f, 0.0f);
11+#endif
12​ }
13
14-ccl_device_inline void bsdf_eval_accum(BsdfEval *eval, ClosureType type, float3 value)
15+ccl_device_inline void bsdf_eval_accum(BsdfEval *eval, ClosureType type, float3 value, float mis_weight)
16​ {
17+#ifdef __SHADOW_TRICKS__
18+ eval->sum_no_mis += value;
19+#endif
20+ value *= mis_weight;
21​ #ifdef __PASSES__
22​ if(eval->use_light_pass) {
23​ if(CLOSURE_IS_BSDF_DIFFUSE(type))
24@@ -96,7 +103,7 @@ ccl_device_inline bool bsdf_eval_is_zero(BsdfEval *eval)
25​ }
26​ }
27
28-ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float value)
29+ccl_device_inline void bsdf_eval_mis(BsdfEval *eval, float value)
30​ {
31​ #ifdef __PASSES__
32​ if(eval->use_light_pass) {
33@@ -115,8 +122,19 @@ ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float value)
34​ }
35​ }
36
37+ccl_device_inline void bsdf_eval_mul(BsdfEval *eval, float value)
38+{
39+#ifdef __SHADOW_TRICKS__
40+ eval->sum_no_mis *= value;
41+#endif
42+ bsdf_eval_mis(eval, value);
43+}
44+
45​ ccl_device_inline void bsdf_eval_mul3(BsdfEval *eval, float3 value)
46​ {
47+#ifdef __SHADOW_TRICKS__
48+ eval->sum_no_mis *= value;
49+#endif
50​ #ifdef __PASSES__
51​ if(eval->use_light_pass) {
52​ eval->diffuse *= value;
53@@ -333,7 +351,7 @@ ccl_device_inline void path_radiance_accum_light(PathRadiance *L, float3 through
54​ }
55
56​ #ifdef __SHADOW_TRICKS__
57- float3 light = throughput * bsdf_eval_sum(bsdf_eval);
58+ float3 light = throughput * bsdf_eval->sum_no_mis;
59​ L->path_total += light;
60​ L->path_total_shaded += shadow * light;
61​ #endif
62@@ -345,7 +363,7 @@ ccl_device_inline void path_radiance_accum_total_light(
63​ const BsdfEval *bsdf_eval)
64​ {
65​ #ifdef __SHADOW_TRICKS__
66- L->path_total += throughput * bsdf_eval_sum(bsdf_eval);
67+ L->path_total += throughput * bsdf_eval->sum_no_mis;
68​ #else
69​ (void) L;
70​ (void) throughput;
71diff --git a/intern/cycles/kernel/kernel_shader.h b/intern/cycles/kernel/kernel_shader.h
72index c1b3153..e6fc3d6 100644
73--- a/intern/cycles/kernel/kernel_shader.h
74+++ b/intern/cycles/kernel/kernel_shader.h
75@@ -509,7 +509,7 @@ ccl_device_inline void _shader_bsdf_multi_eval(KernelGlobals *kg, ShaderData *sd
76​ float3 eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf);
77
78​ if(bsdf_pdf != 0.0f) {
79- bsdf_eval_accum(result_eval, sc->type, eval*sc->weight);
80+ bsdf_eval_accum(result_eval, sc->type, eval*sc->weight, 1.0f);
81​ sum_pdf += bsdf_pdf*sc->sample_weight;
82​ }
83
84@@ -537,7 +537,8 @@ ccl_device_inline void _shader_bsdf_multi_eval_branched(KernelGlobals *kg,
85​ float mis_weight = use_mis? power_heuristic(light_pdf, bsdf_pdf): 1.0f;
86​ bsdf_eval_accum(result_eval,
87​ sc->type,
88- eval * sc->weight * mis_weight);
89+ eval * sc->weight,
90+ mis_weight);
91​ }
92​ }
93​ }
94@@ -569,7 +570,7 @@ void shader_bsdf_eval(KernelGlobals *kg,
95​ _shader_bsdf_multi_eval(kg, sd, omega_in, &pdf, -1, eval, 0.0f, 0.0f);
96​ if(use_mis) {
97​ float weight = power_heuristic(light_pdf, pdf);
98- bsdf_eval_mul(eval, weight);
99+ bsdf_eval_mis(eval, weight);
100​ }
101​ }
102​ }
103@@ -925,7 +926,7 @@ ccl_device_inline void _shader_volume_phase_multi_eval(const ShaderData *sd, con
104​ float3 eval = volume_phase_eval(sd, sc, omega_in, &phase_pdf);
105
106​ if(phase_pdf != 0.0f) {
107- bsdf_eval_accum(result_eval, sc->type, eval);
108+ bsdf_eval_accum(result_eval, sc->type, eval, 1.0f);
109​ sum_pdf += phase_pdf*sc->sample_weight;
110​ }
111
112diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h
113index ea3d514..b172392 100644
114--- a/intern/cycles/kernel/kernel_types.h
115+++ b/intern/cycles/kernel/kernel_types.h
116@@ -455,6 +455,9 @@ typedef struct BsdfEval {
117​ float3 subsurface;
118​ float3 scatter;
119​ #endif
120+#ifdef __SHADOW_TRICKS__
121+ float3 sum_no_mis;
122+#endif
123​ } BsdfEval;
124
125​ /* Shader Flag */

Of course the naming could be improved...

@Brecht Van Lommel (brecht): The issue that I mentioned to @Sergey Sharybin (sergey) on IRC was that since the shadowcatcher only considers directly sampled light, including the MIS weight in the values used for it is wrong. My (uncommitted) fix in the denoiser branch was to just return the value from shader_eval_bsdf through direct_emission since I didn't really test BPT yet and didn't use BsdfEval, but the paste above is a cleaner fix.

As for compatibility with denoising, that's looking great - the two PathRadiance values are always generated, so they can just be used when writing the feature passes.

FYI: Currently the patch fails to apply in three modules (tried applying in blender-v2.78b-release branch on commit c727bc7):

  • ui.py
  • bvh_shadow_all.h
  • qbvh_shadow_all.h

The last commit when applying worked was 2ae39ff

Just as a side note: We're using the ShadowCatcher patch in production for a few months now without any issues.

Update against latest master

Update once again, used wrong flag in previous update

Just as a side note: We're using the ShadowCatcher patch in production for a few months now without any issues.

@Rainer Trummer (aliasguru) Did you use the SHADOW_CATCHER_BACKGROUND define to respect the film transparency setting? I would vote for always have that enabled (aka remove the ifndef parts) as this proved to be intuitively right and led to the correct output in all of our productions . How did you handle it?

@Sergey Sharybin (sergey) thanks for the rebase!

@Rainer Trummer (aliasguru) Did you use the __SHADOW_CATCHER_BACKGROUND__ define to respect the film transparency setting? I would vote

We are using this patch in Cycles for Rhino with __SHADOW_CATCHER_BACKGROUND__ enabled.

One of our testers asked if it is possible to have shadows on catcher objects be reflected in other objects as well, but I think that'll be very hard to do in a good way.

@Thomas Beck (plasmasolutions) I Just checked out the master Branch and patched it with the current version of this patch here (and a few others in addition, like the SurfaceFollow Modifier one). But I haven't changed any ifndef defines due to the total lack of C++ experience. I was only dealing with Python and C# so far.

Where would I need to change those definitions, and to what? I see an #ifdef Block in both kernel_path.h and kernel_path_branched.h, are those the files?

Thanks also to @Sergey Sharybin (sergey) for once again updating this patch!

Where would I need to change those definitions, and to what? I see an #ifdef Block in both kernel_path.h and kernel_path_branched.h, are those the files?

In intern/cycles/kernel/kernel_types.h (search for "kernel features" and add it there).

Cheers Thomas

Several changes:

  • Updated against latest master
  • Enabled background color by default now (seems to match to what people are requesting).
  • Worked around issue in scenes where there's just a shadow catcher and background (such case would cast shadow on the shadow catcher caused by indirect background light). Not fully happy here with the solution, but not sure what we can do better here.
  • Applied patch from Lukas so hopefully we are more aligned with denoise branch.

Worked around compilation error of OpenCL kernel

I tested the latest patch and found it working nicely.
It behaves a bit different than before where i could just add a bit tranparency to the shader to preserve
bottom reflection.
This can now be obtained other way ( see image ) so i would say all works as expected.
Also the hdri MIS problem when mixing various lamp types seems to be gone.
Made a lot of A/B comparison to find odd effects from SC to no avail.

Image 1: pure hdri


Image 2: hdri + sunlamp

Jens

Hi @Sergey Sharybin (sergey),

tested it as well in many different ways: Different light types, one light, multiple lights, colored lights, no lights, no world, plain world lighting, >1 shadow catcher, ... everything seems to work as intended!
There's only one case where I was unsure if the result is correct: textures on the catcher seem to give weird shadowing with point or spot lamps (no bump or displacement was used):

I'll append the blend here just in case it's indeed incorrect: ScTexture.blend

One last question: Is it somehow possible to make the catcher behave as if it was a real object (in the sense of coloring the shadow with the object color) or is the only option to tint it in post?

Cheers and thanks, Thomas

One last question: Is it somehow possible to make the catcher behave as if it was a real object (in the sense of coloring the shadow with the object color) or is the only option to tint it in post?

Not sure I understand this, but assuming you are blending the shadow on top of some colored object, that color would come through in the non-shadowed or partially shadowed areas. Anything else seems like it would be double counting the object color? Shadows don't have the color of the object they are on.

@Brecht Van Lommel (brecht) Yep, that was far too imprecise from me. What I meant was if the shadow could be optionally coloured by the bounce light from a coloured object when this object is near the catcher. But that can be perfectly done with the Diffuse Indirect pass, so ignore that ... was too late apparently.

I'm merging this into the denoising branch now, and it looks pretty good so far. Three minor things noted inline.

intern/cycles/kernel/kernel_accumulate.h
375

If the whole state is passed anyways, why keep the bounce as a separate argument?

intern/cycles/kernel/kernel_path.h
621

I'd move shadow_color into PathRadiance - it's not really necessary, but it would be nice to have it next to the other two shadowcatcher variables.

Also, in the denoising branch the L_sum computation happens outside of kernel_path_integrate, so it would save an additional parameter there (since the PathRadiance is already passed in as a pointer from kernel_path_trace).

intern/cycles/kernel/split/kernel_background_buffer_update.h
161

Why not pass state here as well?

intern/cycles/kernel/kernel_accumulate.h
375

Initial provision was to avoid dereference of the argument when building without shadow tricks. But guess that;s not important since all platforms should be including that sooner or later (OpenCL split might be a problem).

Let's just switch to single state here.

intern/cycles/kernel/kernel_path.h
621

Don't have strong opinion here. If that makes easier to merge with denoiser, let's just move it.

intern/cycles/kernel/split/kernel_background_buffer_update.h
161

Was giving some usual issues with address spaces. But now i look into the code and seems we can just change it to path_radiance_accum_background(ccl_addrspace State*) without issues.

Not sure why that wasn't done in original change. Will doublecheck and update.