Cycles: Add Portal BSDF #114386
No reviewers
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
8 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#114386
Loading…
Reference in New Issue
No description provided.
Delete Branch "david494/blender:cycles-portal-bsdf"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Initial implementation of a portal closure.
This will allow various non physical effects to be rendered directly in cycles without the need for compositing. In this implementation the portals also allow passes and AOVs through, which has more possibilities such as reflected and refracted passes and AOVs.
This is just a first stab at an implementation, it is at least missing:
The actual code is mostly a duplication of the transparent closure, with changes at some key points.
I am unsure about a couple of things:
sd->P
andsd->wi
to set the new ray origin and direction, maybe this should be stored somewhere else?state->isect.object
inshade_surface.h
to give the next ray the possibility to hit the same polygon againSH_NODE_BSDF_PORTAL
sd->closure_transparent_extinction
: This is currently shared with transparent closures, and I think that is the best solution, but mixing transparency and portals might lead to weird results.edit: added example of camera filming its own screen
edit2: added obvious test image :)
edit3: added some of the scenes that I used for testing
Modifying these in the BSDF sampling code is problematic. We should handle it as a separate case like we do for subsurface scatting, since it's not a great fit to handle it as a regular BSDF.
Better add this add the end of the list, after
SH_NODE_MIX
. Otherwise it reuses the value from an old node.Outgoing seems more natural. I would name it "Position" and "Direction".
Use:
That seems fine.
Thanks so much for taking the time to look at this! <3
I tried integrating all the comments. I still have some questions:
I left a part of the copied transparent closure code in
bsdf_portal.h
that I am not sure about. If we don't incrementsd->num_closure_left
if the path would terminate, I guess we just terminate one bounce earlier? I am not sure about the intention of this in the case of the transparent closure.I still overwrite
sd->P
,sd->wi
andsd->object
, but now this is handled as a special case inshade_surface.h
instead of the bsdf code, so it might be fine?Since it is no longer handled as a bsdf, I removed the
CLOSURE_BSDF_PORTAL_ID
case frombsdf_sample()
, but I am not sure if that function may be called in some other path, where handling a portal closure might be useful?Also just wanted to say, I really enjoy reading the code, I am impressed and surprised how readable it is for a production renderer!
WIP: Cycles: Add Portal BSDFto Cycles: Add Portal BSDFRegarding this:
I found this comment that seems relevant:
I think that explains the intention, and I believe we then want to have the same code for the portal closure, as it is also transparent for shadow rays, because it reuses the transparency and extinction logic. I updated the patch to do that.
And regarding this:
After testing some more, this is obviously not fine, so I updated the code to use a custom closure to hold the needed parameters.
This is looking good, but I need some time to look into the finer details for transparency, AOVs and path termination. Hopefully I will have time for it next week.
Just some of the things about AOVs that I thought about while writing this:
All in all, as a user I think I would not be surprised that not all passes make sense through a portal, but of course it would be nice if we could do better.
@LukasStockner, is this something you can review?
Not a full review, just some quick comments from reading the code.
@ -0,0 +27,4 @@
return;
}
if (sd->flag & SD_TRANSPARENT || sd->flag & SD_PORTAL) {
More efficient:
(sd->flag & (SD_TRANSPARENT|SD_PORTAL))
Yep, thanks. Also reads better :)
@ -0,0 +28,4 @@
}
if (sd->flag & SD_TRANSPARENT || sd->flag & SD_PORTAL) {
sd->closure_transparent_extinction += weight;
The initialization of this is getting complicated.
We better set this to zero in
surface_shader_eval
next tosd->num_closure
, and then always use+=
.That simplifies things nicely, I agree.
@ -69,3 +69,3 @@
}
if (!(sd->flag & SD_TRANSPARENT) || kernel_data.film.pass_alpha_threshold == 0.0f ||
if (!(sd->flag & SD_TRANSPARENT || sd->flag & SD_PORTAL) ||
(sd->flag & (SD_TRANSPARENT|SD_PORTAL))
@ -108,3 +108,3 @@
/* ray through transparent keeps same flags from previous ray and is
* not counted as a regular bounce, transparent has separate max */
if (label & LABEL_TRANSPARENT) {
if (label & LABEL_TRANSPARENT || label & LABEL_PORTAL) {
(label & (LABEL_TRANSPARENT|LABEL_PORTAL))
@ -52,2 +52,4 @@
# endif
/* Disable transparent shadows for portals */
if (shadow_sd->flag & SD_PORTAL) {
This makes it so the presence of a single portal BSDF affects the whole material, regardless of its weight, which might for example be near zero. That doesn't seem right?
Yes, that one is a bit strange. I did not find a better place to disable transparent shadows for rays intersecting with portals, since I am reusing transparent_extinction, which is used in surface_shader_transparency(). I think we would either need to track portal transparency separately, or just treat portals as opaque (similar to refractions). The first option would add quite a bit of complexity I think, and the second one would get rid of one of my primary use cases (that passes and AOVs are visible through portals).
In my tests disabling transparent shadows for the whole material works out quite well, because with a weight=0 the shader is optimized out and we still get transparent shadows. If the transition is still problematic, one can still use "lightpath->is shadow ray" tricks to get transparent shadows (but this wasn't really necessary in my test scenes).
I think I found an unrelated bug with transparent shadows during my tests: If there are any materials that contain transparent closures and have the "Transparent Shadows" checkbox set, all other materials in the scene that are transparent, but don't have "Transparent Shadows", are now completely opaque for shadows. I will report this as a separate bug.
edit: reported here #117652
@ -944,3 +944,3 @@
return one_spectrum();
}
else if (sd->flag & SD_TRANSPARENT) {
else if (sd->flag & SD_TRANSPARENT || sd->flag & SD_PORTAL) {
(sd->flag & (SD_TRANSPARENT|SD_PORTAL))
Accidentally approved, did not mean to do it yet
@LukasStockner @weizhen is this something either of you could look at in the coming weeks, so we can get this into 4.2?
Code-wise, this seems mostly fine to me, just a few minor comments.
Also, OSL support is currently missing. I don't see any issues with adding it as a custom closure there - if you need any help with it, just let me know and I can take care of it.
There's also always the larger-picture view of whether the code complexity is warranted for a fairly niche feature, but I think in this case it's still reasonable.
Finally, one concern that I have is that we already have "light portals", so there's a danger of mixing things up (especially labels such as
SD_PORTAL
). I can't really think of a good alternative name for this BSDF though, so maybe we rename the existing Light Portals to e.g. Light Guides?@ -284,6 +285,7 @@ ccl_device_inline void bsdf_roughness_eta(const KernelGlobals kg,
*eta = 1.0f;
(not specific to this line, but to the file):
Currently,
CLOSURE_BSDF_PORTAL_ID
isn't handled in thebsdf_sample
switch statement. This works since it is handled byintegrate_surface_portal
instead, but we might want to make that explicit with akernel_assert(false)
case for it.I agree. Added, with a small comment.
@ -482,3 +487,4 @@
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
case CLOSURE_BSDF_PORTAL_ID:
eval = bsdf_transparent_eval(sc, sd->wi, wo, pdf);
I'd prefer a separate
bsdf_portal_eval
just for consistency, even if it's the same stub asbsdf_transparent_eval
.Yes, that makes it slightly more obvious if either has to change in the future.
@ -0,0 +15,4 @@
static_assert(sizeof(ShaderClosure) >= sizeof(PortalClosure), "PortalClosure is too large!");
ccl_device void bsdf_portal_setup(ccl_private ShaderData *sd,
I'm assuming that this is based on
bsdf_transparent_setup
.That one is a bit of a special case, since all transparent closures are merged together.
In general, the usual pattern is that closure allocation and the sample weight check is handled in
closure.h
, and the setup function only handles parameter clamping etc.That being said, the
closures.h
switch statement is massive, and I don't really see a problem with doing it this way...This is indeed based on the transparent BSDF code. While adding OSL support I also kept it close to the transparent code, which made the OSL changes very simple:
osl_closure_portal_bsdf_setup
just callsbsdf_portal_setup
, so having this separated seems useful here.@ -115,6 +115,10 @@ ccl_device_inline void path_state_next(KernelGlobals kg,
flag |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE;
}
if (shader_flag & SD_PORTAL) {
Shouldn't this check for
label & LABEL_PORTAL
instead? If an object is 50% diffuse and 50% portal, the diffuse bounces should still perform MIS.This is inside a check that already checks for either
LABEL_PORTAL
orLABEL_TRANSPARENT
(so should not disable MIS on diffuse?), and based on my tests I believe we want to disable MIS for transparent as well if there is a portal closure in the mix. Otherwise the shadow behind a mix between portal and transparent does not converge to the correct result.Ah, right, I missed the previous check.
@ -160,0 +166,4 @@
float sum_sample_weight = 0.0f;
for (int i = 0; i < sd->num_closure; i++) {
ccl_private const ShaderClosure *other_sc = &sd->closure[i];
other_sc
is misleading here, since unlike the MIS code the loop isn't skippingsc
.Yes, that is a little bit of unfortunate naming. I did not want to shadow the function parameter
sc
, but maybe that is fine as its immediately cast topc
and we can refer topc
everywhere after that.@ -160,0 +192,4 @@
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
#endif
INTEGRATOR_STATE_WRITE(state, path, throughput) *= pc->weight / sc->sample_weight *
Might be a bit clearer to explicitly write
even though it means an extra division.
I agree, looks more readable.
Sorry, it took me a while to get back to this. Thanks for the review! All the code comments should be addressed.
OSL should be working now as well. That was suprisingly straightforward! (If I indeed did it correctly)
I agree that it is niche, but seems to be fairly well isolated code-wise. At least there have been very few updates needed to keep the code up to date with main.
Light portals are already named light portals in other renderers, so I think we should keep that name. I don't have a strong opinion on the name of this feature. This really just does a new raytrace anywhere, so maybe "Raytrace BSDF", or "Trace BSDF"? (Still ignoring that it is not really a BSDF). Having this as the user facing name might be okay, but in the code this seems very generic and might be even more confusing. I am open to other suggestions... maybe "Ray Portal"?
Code looks good to me now.
In terms of name, I like "Ray Portal", that seems distinct enough from "Light Portal" and describes what is happening quite nicely.
Thanks again, I now replaced all references to 'portal' with 'ray portal'.
(and updated the test files in the top post)
Mostly LGTM now, just two nitpicks and one detail that I'm not certain about.
@ -160,0 +176,4 @@
return LABEL_NONE;
}
if (len_squared(sd->P - pc->P) > 1e-9f) {
Nitpick:
1e-9f
is really small, we usually use1e-5f
as an epsilon value.1e-9 is somewhat arbitrary, but my reasoning was that this is a squared distance. 1e-5 would still consider an offset of 0.001 the same point. I think a reasonable value here is dependent on scene scale.
Hm, yeah, for squared distance it's fine I guess.
@ -160,0 +189,4 @@
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
Not sure if this is the best way to handle it. This approach assumes that there's a 1:1 relationship between the shading position and the origin of the new ray, but that can't really be assumed.
The only realistic alternative I can think of is to set the differential to zero. Evaluating the real differential of the node input would be massive overkill.
@brecht, any opinions? I'm not 100% sure how important
ray->dP
currently is. Usingsd->dP
as an approximation is probably fine?I think just copying like this is ok. It's correct if the portal is a flat window and there is no rotation, which I guess is close enough as an approximation.
@ -0,0 +5,4 @@
#include "stdcycles.h"
shader node_ray_portal_bsdf(color Color = 0.8,
normal Normal = N,
Nitpick: I don't think we need this input.
@blender-bot build +gpu
LGTM now. The only open question from my side is the differentials topic, but I'd be fine with keeping it as-is.
@blender-bot build
We will need some simple blend files for automated tests, see the existing ones for a template:
https://projects.blender.org/blender/blender-test-data/src/branch/main/render
Mainly would like to see tested:
For the future, since for most users this will be something they'll have to look at a guide every time they want to set it up, so I think a simple portal setup would be a good fit for a built-in asset.
Yeah, I agree. It's really not easy to use. Maybe some easy-to-use nodes in the built-in essential would be nice.
Btw, while testing it I recognized the following:
It's awesome that we finally a way to create a mirror where we can mask the object in the mirror with Crytomatte!. But once you use a portal, there is now way to get the original object in the compositor in Crytomatteor. I think it would be handy to have an option, somehow also to get the original object.
Portal BSDF creats a Depth pass. That's cool. But it ignores Camera DOF. If somehow possible, a checkbox in PortalBSDF, to consider Camera DOF would be awesome. Currently, the only way is to use the Depth pass and create DOF in the compositor.
Shader AOVs can still be used to select the "Portal Object" rather than the objects visible through the portal
The Portal BSDF is interacting with DOF as expected in my testing. If you believe you have encountered a issue, please create a bug report.
I think there might be a slightly weird interaction with Geometry Nodes points (which allow perfect spheres in Cycles) - this might not actually be relevant here, and rather something about the default rendering coordinate systems for points?
but just in principle:
For some reason, with points, the Direction ends up being incorrect
Never mind, I think it's just generally the default. I thought the default for Direction is negative incoming, but apparently that's not the case
I was thinking this could be useful for screens, but then the DOF becomes an issue. Afaik the distance through the portal is added to the DOF, meaning it's gonna be problematic to focus on both the screen and what's through it. Is it possible to add a toggle to not add the distance through the portal?
@Calibrator , feature requests should be made on right click select.
As for your concern about DOF going through the portal, there are a few ways to work around it.
Ah got it. I thought this would be the relevant place to mention it, but I see now. My bad.
As for the solutions I'll need time to figure this stuff out. This is the most complicated BSDF shader so far to be honest. In the meanwhile I hope the # 2 solution works. I believe that would be the method the average user would need to use the portal for a screen.
Depth of field works by modifying the initial ray position and direction, there is no other information that is tracked that could be modified. For a computer screen, you'd discard the ray direction and create a completely new one, since what's shown on the screen does not depend on the viewing angle.