Compare commits

...

99 Commits

Author SHA1 Message Date
9dd50b39a4 Merge remote-tracking branch 'origin/blender-v2.90-release' into soc-2020-production-ready-light-tree-2 2020-08-20 11:40:02 -05:00
35624cdfe5 Cycles: added comments to light tree code 2020-08-20 09:43:18 -05:00
4429c39264 Cycles: Cleaning up light tree code.
* removed unused update light picking code
* switched all light tree function parameters to be in the same P, V, t order
* renamed N_pick to V_pick since it is not always a normal
2020-08-19 13:58:30 -05:00
2452031268 Cycles: never hard code -1 for t_max when calling light_sample 2020-08-18 09:39:11 -05:00
9c6ef48315 Cycles: Removed some unused light tree code 2020-08-17 13:38:50 -05:00
114a5a8ce8 Cycles: Fixed small light tree volume bug 2020-08-14 15:22:04 -05:00
3b49ca465f Finished bringing light tree code up to date 2020-08-13 15:17:04 -05:00
7c38d008de Merge branch 'blender-v2.90-release' into soc-2020-production-ready-light-tree-2 2020-08-13 09:58:33 -05:00
cc86e03fd5 brought light tree code up to date 2020-08-12 15:00:25 -05:00
ac0bab2d43 Merge branch 'blender-v2.90-release' into soc-2020-production-ready-light-tree-2 2020-08-12 11:59:35 -05:00
df8bbe98c4 Merge commit 'e12c08e8d170b7ca40f204a5b0423c23a9fbc2c1' into gsoc-2018-many-light-sampling 2020-06-02 09:46:53 -06:00
76870830e2 Merge commit 'e12c08e8d170b7ca40f204a5b0423c23a9fbc2c1^1' into gsoc-2018-many-light-sampling 2020-06-02 09:30:44 -06:00
Erik Englesson
8d399eef67 Merge branch 'master' into gsoc-2018-many-light-sampling 2018-08-12 12:30:42 +02:00
Erik Englesson
4608e5ac26 Cycles: light_tree_pdf() now accounts for splitting
For the MIS calculations we need to be able to calculate the
probability to sample a light using the light tree. This
did not account for splitting so if splitting was used the
probability would be wrong. This has now been fixed.

Also, if we are in PATH mode then the splitting threshold is
set to zero.
2018-08-12 11:59:33 +02:00
Erik Englesson
2d839a08e4 Cycles: Removed unused code
The simplified GGX code is no longer needed
with the new splitting heuristic and has
now been removed.
2018-08-12 11:57:16 +02:00
Erik Englesson
0216daeb80 Cycles: Removed energy term in SAOH denominator
This should not affect the result since it is just a
scale factor to all terms in the minimization. This
term was not in eq. 2 of the paper.
2018-08-10 17:34:23 +02:00
Erik Englesson
8e36d94be2 Cycles: Added more comments and renamed variables
More code comments have been added to all code related to
the light tree. I also renamed all uses of "light BVH" to
use light tree instead to keep everything consistent.
Functions and variable names that used the camel case
naming convention has been changed to follow Blender's
code style. Also, unneeded includes were removed.
2018-08-10 17:31:33 +02:00
Erik Englesson
94af4326e3 Cycles: Light tree: volume and MIS fixes
Now using a more reliable way of knowing if a shading point
is inside or on the boundary of a volume.

Fixed a bug in light_background_sample() that used an index
into the lights array as an index into the distribution array.
2018-08-03 17:24:32 +02:00
Erik Englesson
f6305047f4 Cycles: Light tree split heuristic fix
Doubles are no longer needed in the split heuristic
calculations.
2018-08-03 17:24:32 +02:00
Erik Englesson
e8e0669785 Cycles: Picking position and normal fix for MIS
The picking position and normal has been changed to
use the position and normal from the last
non-transparent bounce.
2018-08-03 17:22:23 +02:00
Erik Englesson
5d344f4360 Cycles: Light tree: Area light fix
The bounding boxes for area lights was not calculated
properly but has now been fixed.
2018-08-03 17:22:23 +02:00
Erik Englesson
108594d8c8 Cycles: Light tree: energy and light picking fixes
When calculating the energy for a light source I previously returned
zero energy if is_constant_emission() returned false. This has now
been changed so it uses an emission of (1,1,1) instead.

The normal that is used for light picking for the BSDF approximation
in the importance calculations now takes into consideration if the
point is on glass, a reflective or transmissive surface. The position
and normal used for light picking is now stored in the ShaderData struct.
2018-08-03 17:22:23 +02:00
Erik Englesson
84fec21522 Cycles: Light tree instancing and energy fixes
Instancing for mesh lights now works with the light tree.
The conversion from emission to luminance is now using
linear_rgb_to_gray() instead.
2018-08-03 17:21:40 +02:00
b1837d5f69 Fix CUDA build for many light sampling, still fails when running. 2018-07-30 12:03:17 +02:00
b3c375e37e Fix missing viewport update when toggling light BVH option. 2018-07-30 12:03:17 +02:00
Erik Englesson
1fe1e742a6 Cycles: WIP: Volumes are not using the light tree.
If a shading point is inside or on the boundary of a volume
then it will use the old sampling method until we have
properly implemented the volume parts of the paper.

Also, the PDF picking probability for volumes have now been
updated properly since the PDF restructure.

Tried to provide the correct shading point and normal to
the MIS calculations. This part needs more work.
2018-07-27 18:19:09 +02:00
Erik Englesson
6045fc40f4 Cycles: Light tree optimization
The importance metric calculations now uses the
fast_cos() and fast_acos() functions. This gave
a 1.4x speedup on one of the test scenes.
2018-07-27 18:18:59 +02:00
Erik Englesson
00b90ede1b Cycles: Light tree related bug fixes
- Fixed bug in triangle_light_pdf_area() from PDF refactor
- Early exit if picking prob is zero in tree traversal
- The background index is now an index into the lights
  array instead of the distribution array.
2018-07-27 18:16:21 +02:00
Erik Englesson
beef4874d3 Cycles: Several emitters per leaf in light tree
This commits makes it possible for leaf nodes in the
light tree to have more than one emitter. This reduces
the maximum depth of the light tree which makes the
traversal of the tree faster but the sampling code
is slower if a leaf with several emitters is found.

Technical details:
-- If cost of splitting in build is larger than the
   energy of the node then we create a leaf instead
   of splitting.
-- Maximum emitters per leaf is now set to 64
-- During traversal if a leaf with several emitters
   is found then we sample one of these based on
   the importance of each emitter. This is calculated
   on the fly and therefore a CDF generation without
   dynamic allocation was implemented.
-  Moved the distribution id to node id lookup from
   light_distribution_pdf() to light_bvh_pdf().
2018-07-20 14:29:20 +02:00
Erik Englesson
ff4833a6b3 Cycles: Bug fixes
- Stopping recursive tree traversal if
  negative PDFs are encountered.

- accum_light_tree_contribution() now
  takes a scale factor as input which
  is passed through to accum_light_contribution

- light_bvh_sample now changes randu. This
  is similar to the other *_sample functions.
  This fixed a bug where recursive traversal
  with no splitting gave a different result
  compared to just using light_sample.
2018-07-13 13:47:18 +02:00
Erik Englesson
0db119ad11 Cycles: Updated SAOH with regularization factor
This factor penalizes thin bounding boxes in
the tree construction.
2018-07-13 13:44:25 +02:00
Erik Englesson
8a0ff32abd Cycles: Updated cone aggregation and measure
The paper provided a way to merge two bounding
cones and an updated version of the cone
measure.
2018-07-13 13:41:00 +02:00
Erik Englesson
260b2e9e20 Cycles: Updated the importance metric
The new paper provides an updated importance
metric which is now implemented. Also, a bug
in the index used for background lights in
the MIS calculations have been fixed.

Technical details:
- The new importance metric depends on the
  normal at the shading point. The MIS
  calculations have been changed
  accordingly.

- The uncertainty angle was described in
  the paper and is now implemented.

- In the implementation details section
  of the paper they mention that they
  limit the distance if splitting is
  disabled. This is implemented too.
2018-07-13 13:33:01 +02:00
Erik Englesson
2d6e1ddb94 Cycles: Updated rescaling of random numbers
The new paper describes how they rescale the
random numbers in the tree traversal. We do
the same now.
2018-07-13 13:30:09 +02:00
Erik Englesson
c6bd7a974b Cycles: Updated the split heuristic
The split heuristic is now based on the
new paper instead of the abstract/slides
from 2017.
2018-07-13 13:21:32 +02:00
Erik Englesson
36cfc9e9fd Cycles: First iteration on split traversal
This makes it possible to sample and evaluate several
lights in a single tree traversal. Should sample highly
specular lights better too. Can only be used in branched
path tracing.

This commit contains the following:
* GUI for setting the splitting threshold
* Recursive split traversal
  - Split method based on solid angle and BSDF peak
  - At leafs the path radiance is accumulated to L
  - Have created a simplified GGX eval that is not
    currently being used.
* Refactor of common code

This is in development.
2018-07-06 09:20:41 +02:00
Erik Englesson
5c10bd4f19 Fix: Merge conflict with background resolution 2018-06-29 16:25:28 +02:00
Erik Englesson
136991be1a Merge branch 'master' into gsoc-2018-many-light-sampling 2018-06-29 11:02:18 +02:00
Erik Englesson
2cdb08f4ff Cycles: Background lights works with light tree
Added support for background lights when using the
light tree, fixed minor bugs related to disabled lights
and trying to build the light tree without any lights.

Now samples either the light tree, distant lights or
background lights based on their energy.
2018-06-29 10:34:15 +02:00
Erik Englesson
8b24cf8c83 Cycles: Support for distant lights
Distant lights are not put in the light
BVH and are treated as a special case.
Either we sample a light from the BVH
or choose one of the distant lights.
2018-06-22 08:07:00 +02:00
Erik Englesson
118731d7d4 Cycles: New design for PDF computations
Now there are functions to calculate the picking
probability for a given lamp/triangle. Fixed
a bug that lamps was before triangles in the
distribution array.
2018-06-22 08:05:52 +02:00
Erik Englesson
2f85295be6 Cleanup: Replaced space tabs with real tabs 2018-06-21 11:33:01 +02:00
Erik Englesson
6f2d885f7e Cleanup: Refactored code
Merged the device_update_tree_distribution()
and device_update_distribution() functions.
2018-06-15 12:27:32 +02:00
Erik Englesson
df7f4d1e26 Cycles: Area lights support and better energy estimation
The light BVH now supports area lights. Also, the total emitted
energy of each light is now calculated by integrating luminance
over the sphere of directions and area.
2018-06-15 12:21:38 +02:00
Erik Englesson
0028f7cf0f Merge branch 'master' into gsoc-2018-many-light-sampling 2018-06-08 15:21:09 +02:00
Erik Englesson
a3826bf41c Cycles: Added a checkbox in Blender's GUI to turn
on/off the new light sampling method.

The checkbox can be found at the bottom of the sampling
section in the Render tab. The method is not used by
default.
2018-06-08 13:51:49 +02:00
Erik Englesson
4f55918a5e Cycles: Implemented the SAOH splitting method
Previously the light bvh construction code used a simple method
to split a node into two child nodes based only on the bounding
box of the node. Now, it splits a node into two by looking at
the energy and direction of the lights inside the nodes and the
area of their bounding boxes.

Technical changes:
- Implemented orientation BVH build
- Added energy to each node based on emission
- Updated PDF calculations for lights when using the light BVH
2018-06-08 13:51:43 +02:00
Erik Englesson
bcb89f0b96 Fix: Minimal compilation error fix 2018-06-04 11:10:08 +02:00
Erik Englesson
2e17c3f3a1 Added simple construction and sampling code for light BVH
The light BVH is created on the host out of all emissive
triangles and all lights in the scene. The nodes of the
light BVH is then copied to the device where it is used
when sampling lights. When a light is asked for it
traverses the light BVH randomly to find a single light.

This is still very much in development.
2018-06-01 16:12:17 +02:00
Erik Englesson
e558b1afef My own build path modifications 2018-06-01 16:04:28 +02:00
Hristo Gueorguiev
458104ee0b Fix OpenCL compilation error - BPT without SSS. 2018-06-01 15:41:19 +02:00
3342114d31 make.bat: fix support for building with just the build tools installed. 2018-06-01 15:41:19 +02:00
ed1b92cf8e Fix missing movieclip ID type in allowed ones for Outliner... 2018-06-01 15:41:19 +02:00
59e40721dd Edit Mode: add success return value
Also remove fix for T6614, since BKE_object_obdata_is_libdata
no longer checks proxy.
2018-06-01 15:41:19 +02:00
8e462b0f48 DNA: add OB_DATA_SUPPORT_EDITMODE macro 2018-06-01 15:41:19 +02:00
68b0cf6da0 Cleanup: Get rid of G.main in BKE_material.
Note that in some cases, this only moves the G.main case to somne other
places - in particular, RNA getters/setters are becoming annoying here...
2018-06-01 15:41:19 +02:00
55e96e1955 make.bat : fix release build not getting the right settings. 2018-06-01 15:41:19 +02:00
c5803441f3 make.bat : improve error message while detecting the msvc runtime when using clang 2018-06-01 15:41:19 +02:00
3e3a44dcfa make.bat : add support for building with ninja and clang together. 2018-06-01 15:41:19 +02:00
ac4eec8cb9 Windows: Add support for building with clang.
This commit contains the minimum to make clang build/work with blender, asan and ninja build support is forthcoming

Things to note:

1) Builds and runs, and is able to pass all tests (except for the freestyle_stroke_material.blend test which was broken at that time for all platforms by the looks of it)

2) It's slightly faster than msvc when using cycles. (time in seconds, on an i7-3370)

victor_cpu
	msvc:3099.51
	clang:2796.43

pavillon_barcelona_cpu
	msvc:1872.05
	clang:1827.72

koro_cpu
	msvc:1097.58
	clang:1006.51

fishy_cat_cpu
	msvc:815.37
	clang:722.2

classroom_cpu
	msvc:1705.39
	clang:1575.43

bmw27_cpu
	msvc:552.38
	clang:561.53

barbershop_interior_cpu
	msvc:2134.93
	clang:1922.33

3) clang on windows uses a drop in replacement for the Microsoft cl.exe (takes some of the Microsoft parameters, but not all, and takes some of the clang parameters but not all) and uses ms headers + libraries + linker, so you still need visual studio installed and will use our existing vc14 svn libs.

4) X64 only currently, X86 builds but crashes on startup.

5) Tested with llvm/clang 6.0.0

6) Requires visual studio integration, available at https://github.com/LazyDodo/llvm-vs2017-integration

7) The Microsoft compiler spawns a few copies of cl in parallel to get faster build times, clang doesn't, so the build time is 3-4x slower than with msvc.

8) No openmp support yet. Have not looked at this much, the binary distribution of clang doesn't seem to include it on windows.

9) No ASAN support yet, some of the sanitizers can be made to work, but it was decided to leave support out of this commit.

Reviewers: campbellbarton

Differential Revision: https://developer.blender.org/D3304
2018-06-01 15:41:19 +02:00
dac0b7c0eb Fix Cycles + OSL build error, pass main to node editing functions. 2018-06-01 15:41:19 +02:00
8b5f8c0131 UI: center align number buttons w/o text
This makes supporting split properties and text possible, see T54951
2018-06-01 15:41:18 +02:00
81406e39f8 Cleanup: Nuke most of remaining evil G.main from RNA.
The few ones in getters/setters we cannot remove as easily, for now we
can live with those I think...
2018-06-01 15:41:18 +02:00
88fa8610ef Cycles: Fix problems in the IES loader when rendering with no file selected 2018-06-01 15:41:18 +02:00
6c68384c76 UI: replace BLI_strncpy w/ memcpy
Size is already checked.
2018-06-01 15:41:18 +02:00
5522d28d54 Recently added IES conflicts w/ EEVEE 2018-06-01 15:41:18 +02:00
cbeb9f2c35 3D View: minor change to NDOF view orbit
This change is needed for 2.8, where the NULL check isn't a reliable way
of testing if dynamic offset is needed.
2018-06-01 15:41:18 +02:00
975194b237 Fix restrict error in BLI_str_format_byte_unit
Don't use sprintf to append a string to it's self.

Also correct BLI_str_rstrip_float_zero's return value.
2018-06-01 15:41:18 +02:00
8519814b89 make.bat : Fix builtime.txt being written in the wrong folder. 2018-06-01 15:41:18 +02:00
0347abee2d Cycles: Add Support for IES files as textures for light strength
This patch adds support for IES files, a file format that is commonly used to store the directional intensity distribution of light sources.
The new IES node is supposed to be plugged into the Strength input of the Emission node of the lamp.

Since people generating IES files do not really seem to care about the standard, the parser is flexible enough to accept all test files I have tried.
Some common weirdnesses are distributing values over multiple lines that should go into one line, using commas instead of spaces as delimiters and adding various useless stuff at the end of the file.

The user interface of the node is similar to the script node, the user can either select an internal Text or load a file.
Internally, IES files are handled similar to Image textures: They are stored in slots by the LightManager and each unique IES is assigned to one slot.

The local coordinate system of the lamp is used, so that the direction of the light can be changed. For UI reasons, it's usually best to add an area light,
rotate it and then change its type, since especially the point light does not immediately show its local coordinate system in the viewport.

Reviewers: #cycles, dingto, sergey, brecht

Reviewed By: #cycles, dingto, brecht

Subscribers: OgDEV, crazyrobinhood, secundar, cardboard, pisuke, intrah, swerner, micah_denn, harvester, gottfried, disnel, campbellbarton, duarteframos, Lapineige, brecht, juicyfruit, dingto, marek, rickyblender, bliblubli, lockal, sergey

Differential Revision: https://developer.blender.org/D1543
2018-06-01 15:41:18 +02:00
2b21ce73e9 Threads: add spinlock hit for hyperthreading processors on Windows.
Suggested by Percy Ross Tiglao.
2018-06-01 15:41:18 +02:00
c0e1765c3a make.bat: Add support for building with ninja.
ninja is an alternative to msbuild designed for fast rebuilds. However there is no IDE support, builds only from the command line.

Comparison between msbuild and ninja for a full build, build time in seconds.

Full Clean Build
msbuild     867.5
Ninja       801.2
Difference  -66.3 (-7.6%)

Minor Change
msbuild      43.0
Ninja        14.9
Difference  -28.1 (-64.4%)

No Changes
msbuild      23.0
Ninja         6.1
Difference  -16.9 (-73.5%)
2018-06-01 15:41:18 +02:00
68fce6c4db make.bat/cleanup: removed stray echo in make.bat 2018-06-01 15:41:17 +02:00
a554255c13 make.bat: refactor make.bat
make.bat was starting to become hard to maintain, this refactors it into separate batch files for each stage of the process.

-Improved detection of msvc2013/2015
-Improved failure handling.
-Added check for working msbuild and C++ compiler
-Added verbose switch to ease trouble shooting.
-Added Check if svn/cmake/git are in the path before using them
-Display the build configuration before asking to download the libraries
-Offer an option to recover an interrupted checkout of the libraries.
-Automatically check out sub-modules in-case they are missing.
2018-06-01 15:41:17 +02:00
ea1322c64d Add number and memory size formatting throughout the UI
This commit adds number formatting (thousands separator) to the baking panel. It also adds a new function to format memory sizes (KB/GB/etc) and applies it to the baking panel and scene stats. The new function is unit tested.

Reviewers: Severin
Tags: #user_interface
Differential Revision: https://developer.blender.org/D1248
2018-06-01 15:41:17 +02:00
f383c3ce78 [windows/make.bat] use a more reliable way of locating visual studio 2017.
The registry hack we were using wasn't very reliable, the recommended way to locating visual studio is using vswhere (15.2 and up), using it also allows to switch between the regular and pre-release versions.
2018-06-01 15:41:17 +02:00
1d5ecd2a6b WM: check modal handlers for keymap lookups
Keep in sync with 2.8x
2018-06-01 15:41:17 +02:00
f60520306c UI: fix assert
Replace hard-coded button size check with UI_UNIT_X.

Caused icon-only buttons to have strings assigned based on UI-scale.
2018-06-01 15:41:17 +02:00
efb19da3e8 3D View: add pixelsize function w/o UI scale 2018-06-01 15:41:17 +02:00
49496a460a Cycles: Cleanup: Remove duplicated atan2f definition for OpenCL
Turns out that atan2f was already defined for OpenCL.
2018-06-01 15:41:17 +02:00
2f12b83dbd Cycles Denoising: Don't use atomics in the accumulation kernel on CPUs
The GPU kernel needs to use atomics for accumulation since all offsets are processed in
parallel, but on CPUs that's not the case, so we can disable them there for a considerable speedup.
2018-06-01 15:41:17 +02:00
fef9647db4 Cycles/Compositor: Add arctan2 operation to the Math node
The Math node currently has the normal atan() function, but for
actual angles this is fairly useless without additional nodes to handle the signs.

Since the node has two inputs anyways, it only makes sense to add an arctan2 option.

Reviewers: sergey, brecht

Differential Revision: https://developer.blender.org/D3430
2018-06-01 15:41:17 +02:00
f914311949 3D View: remove poll 3D view for copy/paste
These operators only need selected objects.
2018-06-01 15:41:17 +02:00
d9b2b82532 Fix T55034: Setting duplication group for multiple selected items only
affects one item

UI editing multiple selected items missed the case of PROP_POINTER
properties

Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D3373
2018-06-01 15:41:17 +02:00
584cb4a9b0 Fix Extend property of Lasso select tool in Mask editor not working
Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D3361
2018-06-01 15:41:16 +02:00
9bccf1425d Fix T54336: Extend property of Lasso select tool in Node editor does not
work

Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D3360
2018-06-01 15:41:16 +02:00
4e7eef02bc Fix T55137: Compilation failing on non-x86-64 architectures
Some conversion helper functions were (most likely by accident) contained
inside an ifdef for SSE2 support, so on e.g. ARM they would be undefined
and therefore cause compilation to fail.
2018-06-01 15:41:16 +02:00
3633221c9b Cleanup: strip trailing space from interface files 2018-06-01 15:41:16 +02:00
db14acaf22 Fix incorrect size in aligned lockfree realloc
Thanks to @alikendarfen for finding.
2018-06-01 15:41:16 +02:00
95d786f598 blenderplayer: add stubs 2018-06-01 15:41:16 +02:00
cd502b8287 Fix T55093: Bisect + fill crash 2018-06-01 15:41:16 +02:00
72d9ca0db6 Memory allocator: use lockfree calls internally
Was already used in some areas.
2018-06-01 15:41:16 +02:00
deb5c1dae9 CMake: only include licences for enabled libs 2018-06-01 15:41:16 +02:00
ed657c9407 Cleanup: use const for transform internal API 2018-06-01 15:41:16 +02:00
72ba23c7fc RNA: support for PARM_OUTPUT & PARM_RNAPTR 2018-06-01 15:41:16 +02:00
96b252d90e Fix too much memory usage for Cycles attribute map.
Thanks to Thomas Krebs for identifying the problem and solution.
2018-06-01 15:41:15 +02:00
922b77f142 Fix/workaround RNA build error in C++ API.
It seems output parameter needs to be the last one.
2018-06-01 15:41:15 +02:00
f024ae11e3 Cleanup: correct variable names 2018-06-01 15:41:15 +02:00
d01207e4fb WM: utility to find a keymap item from an operator
Also RNA access to WM_keyconfig_update,
needed when generating dynamic keymaps used in menus immediately after.
2018-06-01 15:41:15 +02:00
37 changed files with 3265 additions and 368 deletions

View File

@@ -72,7 +72,7 @@ COMMANDLINE=$@
DISTRO=""
RPM=""
SRC="$HOME/src/blender-deps"
SRC="$HOME/Programming/blender-git/src/blender-deps"
INST="/opt/lib"
TMP="/tmp"
CWD=$PWD

View File

@@ -77,4 +77,4 @@ echo msbuild ^
/verbosity:minimal ^
/p:platform=%MSBUILD_PLATFORM% ^
/flp:Summary;Verbosity=minimal;LogFile=%BUILD_DIR%\Build.log >> %BUILD_DIR%\rebuild.cmd
echo echo %%TIME%% ^>^> buildtime.txt >> %BUILD_DIR%\rebuild.cmd
echo echo %%TIME%% ^>^> buildtime.txt >> %BUILD_DIR%\rebuild.cmd

View File

@@ -93,4 +93,4 @@ echo call "%VCVARS%" %BUILD_ARCH% >> %BUILD_DIR%\rebuild.cmd
echo ^) >> %BUILD_DIR%\rebuild.cmd
echo echo %%TIME%% ^> buildtime.txt >> %BUILD_DIR%\rebuild.cmd
echo ninja install >> %BUILD_DIR%\rebuild.cmd
echo echo %%TIME%% ^>^> buildtime.txt >> %BUILD_DIR%\rebuild.cmd
echo echo %%TIME%% ^>^> buildtime.txt >> %BUILD_DIR%\rebuild.cmd

View File

@@ -68,6 +68,8 @@ class AddPresetSampling(AddPresetBase, Operator):
"cycles.subsurface_samples",
"cycles.volume_samples",
"cycles.use_square_samples",
"cycles.use_light_tree",
"cycles.splitting_threshold",
"cycles.progressive",
"cycles.seed",
"cycles.sample_clamp_direct",

View File

@@ -291,6 +291,19 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default=False,
)
use_light_tree: BoolProperty(
name="Light Tree",
description="Samples many lights more efficiently",
default=False,
)
splitting_threshold: FloatProperty(
name="Splitting",
description="Amount of lights to sample at a time, from one light at 0.0, to adaptively more lights as needed, to all lights at 1.0",
min=0.0, max=1.0,
default=0.0,
)
samples: IntProperty(
name="Samples",
description="Number of samples to render for each pixel",

View File

@@ -326,6 +326,35 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
layout.row().prop(cscene, "use_layer_samples")
break
class CYCLES_RENDER_PT_sampling_light_tree(CyclesButtonsPanel, Panel):
bl_label = "Light Tree"
bl_parent_id = "CYCLES_RENDER_PT_sampling"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (context.scene.cycles.feature_set == 'EXPERIMENTAL')
def draw_header(self, context):
layout = self.layout
scene = context.scene
cscene = scene.cycles
layout.prop(cscene, "use_light_tree", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
cscene = scene.cycles
layout.active = cscene.use_light_tree
row = layout.row(align=True)
row.label(text="") # create empty column
row.prop(cscene, "splitting_threshold", text="Splitting")
class CYCLES_RENDER_PT_sampling_total(CyclesButtonsPanel, Panel):
bl_label = "Total Samples"
@@ -2272,6 +2301,7 @@ classes = (
CYCLES_RENDER_PT_sampling_adaptive,
CYCLES_RENDER_PT_sampling_denoising,
CYCLES_RENDER_PT_sampling_advanced,
CYCLES_RENDER_PT_sampling_light_tree,
CYCLES_RENDER_PT_light_paths,
CYCLES_RENDER_PT_light_paths_max_bounces,
CYCLES_RENDER_PT_light_paths_clamping,

View File

@@ -304,6 +304,8 @@ void BlenderSync::sync_integrator()
integrator->method = (Integrator::Method)get_enum(
cscene, "progressive", Integrator::NUM_METHODS, Integrator::PATH);
integrator->use_light_tree = get_boolean(cscene, "use_light_tree");
integrator->splitting_threshold = get_float(cscene, "splitting_threshold");
integrator->sample_all_lights_direct = get_boolean(cscene, "sample_all_lights_direct");
integrator->sample_all_lights_indirect = get_boolean(cscene, "sample_all_lights_indirect");
integrator->light_sampling_threshold = get_float(cscene, "light_sampling_threshold");
@@ -359,8 +361,13 @@ void BlenderSync::sync_integrator()
integrator->ao_bounces = 0;
}
if (integrator->modified(previntegrator))
if (integrator->modified(previntegrator)) {
integrator->tag_update(scene);
}
if (integrator->use_light_tree != previntegrator.use_light_tree ||
integrator->splitting_threshold != previntegrator.splitting_threshold) {
scene->light_manager->tag_update(scene);
}
}
/* Film */

View File

@@ -81,6 +81,9 @@ ccl_device_noinline void compute_light_pass(
kg, sd, emission_sd, L, &state, &ray, &throughput, &ss_indirect)) {
while (ss_indirect.num_rays) {
kernel_path_subsurface_setup_indirect(kg, &ss_indirect, &state, &ray, L, &throughput);
indirect_sd.P_pick = sd->P_pick;
indirect_sd.V_pick = sd->V_pick;
indirect_sd.t_pick = sd->t_pick;
kernel_path_indirect(kg, &indirect_sd, emission_sd, &ray, throughput, &state, L);
}
is_sss_sample = true;
@@ -97,6 +100,9 @@ ccl_device_noinline void compute_light_pass(
state.ray_t = 0.0f;
# endif
/* compute indirect light */
indirect_sd.P_pick = sd->P_pick;
indirect_sd.V_pick = sd->V_pick;
indirect_sd.t_pick = sd->t_pick;
kernel_path_indirect(kg, &indirect_sd, emission_sd, &ray, throughput, &state, L);
/* sum and reset indirect light pass variables for the next samples */

View File

@@ -222,6 +222,7 @@ ccl_device_noinline_cpu float3 indirect_primitive_emission(
/* multiple importance sampling, get triangle light pdf,
* and compute weight with respect to BSDF pdf */
float pdf = triangle_light_pdf(kg, sd, t);
pdf *= light_distribution_pdf(kg, sd->P_pick, sd->V_pick, sd->t_pick, sd->prim, sd->object);
float mis_weight = power_heuristic(bsdf_pdf, pdf);
return L * mis_weight;
@@ -270,11 +271,16 @@ ccl_device_noinline_cpu void indirect_lamp_emission(KernelGlobals *kg,
kernel_volume_shadow(kg, emission_sd, state, &volume_ray, &volume_tp);
lamp_L *= volume_tp;
}
#endif
if (!(state->flag & PATH_RAY_MIS_SKIP)) {
/* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */
/* multiply with light picking probablity to pdf */
ls.pdf *= light_distribution_pdf(
kg, emission_sd->P_pick, emission_sd->V_pick, emission_sd->t_pick, ~ls.lamp, -1);
float mis_weight = power_heuristic(state->ray_pdf, ls.pdf);
lamp_L *= mis_weight;
}
@@ -326,10 +332,14 @@ ccl_device_noinline_cpu float3 indirect_background(KernelGlobals *kg,
/* Background MIS weights. */
# ifdef __BACKGROUND_MIS__
/* Check if background light exists or if we should skip pdf. */
/* consider shading point at previous non-transparent bounce */
float3 P_pick = ray->P - state->ray_t * ray->D;
if (!(state->flag & PATH_RAY_MIS_SKIP) && kernel_data.background.use_mis) {
/* multiple importance sampling, get background light pdf for ray
* direction, and compute weight with respect to BSDF pdf */
float pdf = background_light_pdf(kg, ray->P, ray->D);
float pdf = background_light_pdf(kg, P_pick, ray->D);
float mis_weight = power_heuristic(state->ray_pdf, pdf);
return L * mis_weight;

View File

@@ -35,8 +35,31 @@ typedef struct LightSample {
LightType type; /* type of light */
} LightSample;
/* Updates the position and normal used to pick a light for direct lighting.
*
* The importance calculation for the light tree is different for scattering events on a surface
* and in a volume. For volume events we need to know the point where the ray started and the point
* it scattered. To do this we keep track of the ray direction and length. For surface events we
* need the normal at the surface. In this case we set the ray length to -1 to mark that surface
* importance sampling should be used.
*/
ccl_device void kernel_update_light_picking(ShaderData *sd, Ray *ray)
{
if (ray) {
sd->P_pick = ray->P;
sd->V_pick = ray->D;
sd->t_pick = ray->t;
}
else {
sd->P_pick = sd->P;
sd->V_pick = sd->N;
sd->t_pick = -1.0f;
}
}
/* Regular Light */
/* returns the PDF of sampling a point on this lamp */
ccl_device_inline bool lamp_light_sample(
KernelGlobals *kg, int lamp, float randu, float randv, float3 P, LightSample *ls)
{
@@ -153,8 +176,6 @@ ccl_device_inline bool lamp_light_sample(
}
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return (ls->pdf > 0.0f);
}
@@ -291,8 +312,6 @@ ccl_device bool lamp_light_eval(
return false;
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return true;
}
@@ -328,18 +347,15 @@ ccl_device_inline bool triangle_world_space_vertices(
return has_motion;
}
ccl_device_inline float triangle_light_pdf_area(KernelGlobals *kg,
const float3 Ng,
const float3 I,
float t)
ccl_device_inline float triangle_light_pdf_area(
KernelGlobals *kg, const float3 Ng, const float3 I, float t, float triangle_area)
{
float pdf = kernel_data.integrator.pdf_triangles;
float cos_pi = fabsf(dot(Ng, I));
if (cos_pi == 0.0f)
return 0.0f;
return t * t * pdf / cos_pi;
return t * t / (cos_pi * triangle_area);
}
ccl_device_forceinline float triangle_light_pdf(KernelGlobals *kg, ShaderData *sd, float t)
@@ -349,7 +365,7 @@ ccl_device_forceinline float triangle_light_pdf(KernelGlobals *kg, ShaderData *s
* to the length of the edges of the triangle. */
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, sd->object, sd->prim, sd->time, V);
triangle_world_space_vertices(kg, sd->object, sd->prim, sd->time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
@@ -380,34 +396,12 @@ ccl_device_forceinline float triangle_light_pdf(KernelGlobals *kg, ShaderData *s
return 0.0f;
}
else {
float area = 1.0f;
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
else {
area = 0.5f * len(N);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
return pdf / solid_angle;
return 1.0f / solid_angle;
}
}
else {
float pdf = triangle_light_pdf_area(kg, sd->Ng, sd->I, t);
if (has_motion) {
const float area = 0.5f * len(N);
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
pdf = pdf * area_pre / area;
}
return pdf;
const float area = 0.5f * len(N);
return triangle_light_pdf_area(kg, sd->Ng, sd->I, t, area);
}
}
@@ -425,7 +419,7 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals *kg,
* to the length of the edges of the triangle. */
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
triangle_world_space_vertices(kg, object, prim, time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
@@ -434,7 +428,6 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals *kg,
const float3 N0 = cross(e0, e1);
float Nl = 0.0f;
ls->Ng = safe_normalize_len(N0, &Nl);
float area = 0.5f * Nl;
/* flip normal if necessary */
const int object_flag = kernel_tex_fetch(__object_flag, object);
@@ -537,13 +530,7 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals *kg,
return;
}
else {
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
ls->pdf = pdf / solid_angle;
ls->pdf = 1.0f / solid_angle;
}
}
else {
@@ -564,20 +551,53 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals *kg,
ls->P = u * V[0] + v * V[1] + t * V[2];
/* compute incoming direction, distance and pdf */
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = triangle_light_pdf_area(kg, ls->Ng, -ls->D, ls->t);
if (has_motion && area != 0.0f) {
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
ls->pdf = ls->pdf * area_pre / area;
}
float area = 0.5f * Nl;
ls->pdf = triangle_light_pdf_area(kg, ls->Ng, -ls->D, ls->t, area);
ls->u = u;
ls->v = v;
}
}
/* chooses either to sample the light tree, distant or background lights by
* sampling a CDF based on energy */
ccl_device int light_group_distribution_sample(KernelGlobals *kg, float *randu)
{
/* This is basically std::upper_bound as used by pbrt, to find a point light or
* triangle to emit from, proportional to area. a good improvement would be to
* also sample proportional to power, though it's not so well defined with
* arbitrary shaders. */
const int num_groups = LIGHTGROUP_NUM;
int first = 0;
int len = num_groups + 1;
float r = *randu;
// todo: refactor this into its own function. It is used in several places
while (len > 0) {
int half_len = len >> 1;
int middle = first + half_len;
if (r < kernel_tex_fetch(__light_group_sample_cdf, middle)) {
len = half_len;
}
else {
first = middle + 1;
len = len - half_len - 1;
}
}
/* Clamping should not be needed but float rounding errors seem to
* make this fail on rare occasions. */
int index = clamp(first - 1, 0, num_groups - 1);
/* Rescale to reuse random number. this helps the 2D samples within
* each area light be stratified as well. */
float distr_min = kernel_tex_fetch(__light_group_sample_cdf, index);
float distr_max = kernel_tex_fetch(__light_group_sample_cdf, index + 1);
*randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
/* Light Distribution */
ccl_device int light_distribution_sample(KernelGlobals *kg, float *randu)
@@ -623,22 +643,691 @@ ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals *kg, int i
return (bounce > kernel_tex_fetch(__lights, index).max_bounces);
}
ccl_device_noinline bool light_sample(KernelGlobals *kg,
int lamp,
float randu,
float randv,
float time,
float3 P,
int bounce,
LightSample *ls)
/*
* Finds the solid angle of the smallest cone with vertex P that contains the bounding box. If P is
* inside the bounding box light can be in any direction so use the entire sphere.
*/
ccl_device float calc_bbox_solid_angle(float3 P,
float3 centroid_to_P_dir,
float3 bboxMin,
float3 bboxMax)
{
if (P.x < bboxMax.x && P.y < bboxMax.y && P.z < bboxMax.z && P.x > bboxMin.x &&
P.y > bboxMin.y && P.z > bboxMin.z) {
/* P is inside bounding box */
return M_PI_F;
}
else {
/* Find the smallest cone that contains the bounding box by checking which bbox vertex is
* farthest out. If we use a bounding sphere we get a too big cone. For example consider a long
* skinny bbox oriented with P next to one of the small sides. */
float theta_u = 0;
float3 corners[8];
corners[0] = bboxMin;
corners[1] = make_float3(bboxMin.x, bboxMin.y, bboxMax.z);
corners[2] = make_float3(bboxMin.x, bboxMax.y, bboxMin.z);
corners[3] = make_float3(bboxMin.x, bboxMax.y, bboxMax.z);
corners[4] = make_float3(bboxMax.x, bboxMin.y, bboxMin.z);
corners[5] = make_float3(bboxMax.x, bboxMin.y, bboxMax.z);
corners[6] = make_float3(bboxMax.x, bboxMax.y, bboxMin.z);
corners[7] = bboxMax;
for (int i = 0; i < 8; ++i) {
float3 P_to_corner = normalize(P - corners[i]);
const float cos_theta_u = dot(-centroid_to_P_dir, P_to_corner);
theta_u = fmaxf(fast_acosf(cos_theta_u), theta_u);
}
return theta_u;
}
}
/* calculates the importance metric for the given node and shading point P
* If t_max is negative assume that V is the normal vector and use surface importance calculation.
* Otherwise assume that V is the ray direction and use volume importance.
*/
ccl_device float calc_importance(KernelGlobals *kg,
float3 P,
float3 V,
float t_max,
float3 bboxMax,
float3 bboxMin,
float theta_o,
float theta_e,
float3 axis,
float energy,
float3 centroid)
{
if (t_max < 0.0f) {
/* eq. 3 */
const float3 centroid_to_P = P - centroid;
const float3 centroid_to_P_dir = normalize(centroid_to_P);
const float r2 = len_squared(bboxMax - centroid);
float d2 = len_squared(centroid_to_P);
/* based on comment in the implementation details of the paper */
const bool splitting = kernel_data.integrator.splitting_threshold != 0.0f;
if (!splitting) {
d2 = fmaxf(d2, r2 * 0.25f);
}
/* "theta_u captures the solid angle of the entire box" */
float theta_u = calc_bbox_solid_angle(P, centroid_to_P_dir, bboxMin, bboxMax);
/* cos(theta') */
float cos_theta = dot(axis, centroid_to_P_dir);
const float theta = fast_acosf(cos_theta);
const float theta_prime = fmaxf(theta - theta_o - theta_u, 0.0f);
if (theta_prime >= theta_e) {
return 0.0f;
}
const float cos_theta_prime = fast_cosf(theta_prime);
/* f_a|cos(theta'_i)| -- diffuse approximation */
const float cos_theta_i = dot(V, -centroid_to_P_dir);
const float theta_i = fast_acosf(cos_theta_i);
const float theta_i_prime = fmaxf(theta_i - theta_u, 0.0f);
const float cos_theta_i_prime = fast_cosf(theta_i_prime);
const float abs_cos_theta_i_prime = fabsf(cos_theta_i_prime);
/* doing something similar to bsdf_diffuse_eval_reflect() */
/* TODO: Use theta_i or theta_i_prime here? */
const float f_a = fmaxf(cos_theta_i_prime, 0.0f) * M_1_PI_F;
return f_a * abs_cos_theta_i_prime * energy * cos_theta_prime / d2;
}
else {
const float3 p_to_c = centroid - P;
/* find closest point to centroid */
const float t = fminf(t_max, fmaxf(0.0f, dot(V, p_to_c)));
const float3 geometry_point = P + V * t;
const float d_min = len(centroid - geometry_point);
const float3 V0 = normalize(P - centroid);
const float3 V1 = normalize(P + V * fminf(t_max, 1e12f) - centroid);
const float3 O0 = V0;
float3 O1, O2;
make_orthonormals_tangent(O0, V1, &O1, &O2);
const float b_max = fmaxf(dot(V0, axis), dot(V1, axis));
const float O0_dot_a = dot(O0, axis);
const float O1_dot_a = dot(O1, axis);
const float cos_phi_o = O0_dot_a / sqrtf(O1_dot_a * O1_dot_a + O0_dot_a * O0_dot_a); /* Eq 5 */
const float sin_phi_o = sqrtf(1 - cos_phi_o * cos_phi_o);
const float3 v = O0 * cos_phi_o + O1 * sin_phi_o;
/* Eq 6 */
float cos_theta_min;
if (O1_dot_a < 0 || dot(V0, V1) > cos_phi_o) {
cos_theta_min = b_max;
}
else {
cos_theta_min = dot(axis, v);
}
float theta_min = fast_acosf(cos_theta_min);
float theta_u = calc_bbox_solid_angle(
geometry_point, normalize(geometry_point - centroid), bboxMin, bboxMax);
float theta_prime = fmaxf(theta_min - theta_o - theta_u, 0);
if (theta_prime >= theta_e) {
return 0;
}
return energy * fast_cosf(theta_prime) / d_min; /* Eq 7 */
}
}
/* the energy, spatial and orientation bounds for the light are loaded and decoded
* and then this information is used to calculate the importance for this light.
* This function is used to calculate the importance for a light in a node
* containing several lights. */
ccl_device float calc_light_importance(
KernelGlobals *kg, float3 P, float3 V, float t_max, int node_offset, int light_offset)
{
/* find offset into light_tree_leaf_emitters array */
int first_emitter = kernel_tex_fetch(__leaf_to_first_emitter, node_offset / 4);
kernel_assert(first_emitter != -1);
int offset = first_emitter + light_offset * 3;
/* get relevant information to be able to calculate the importance */
const float4 data0 = kernel_tex_fetch(__light_tree_leaf_emitters, offset + 0);
const float4 data1 = kernel_tex_fetch(__light_tree_leaf_emitters, offset + 1);
const float4 data2 = kernel_tex_fetch(__light_tree_leaf_emitters, offset + 2);
/* decode data for this light */
const float3 bbox_min = make_float3(data0.x, data0.y, data0.z);
const float3 bbox_max = make_float3(data0.w, data1.x, data1.y);
const float theta_o = data1.z;
const float theta_e = data1.w;
const float3 axis = make_float3(data2.x, data2.y, data2.z);
const float energy = data2.w;
const float3 centroid = 0.5f * (bbox_max + bbox_min);
return calc_importance(
kg, P, V, t_max, bbox_max, bbox_min, theta_o, theta_e, axis, energy, centroid);
}
/* the combined energy, spatial and orientation bounds for all the lights for the
* given node are loaded and decoded and then this information is used to
* calculate the importance for this node. */
ccl_device float calc_node_importance(
KernelGlobals *kg, float3 P, float3 V, float t_max, int node_offset)
{
/* load the data for this node */
const float4 node0 = kernel_tex_fetch(__light_tree_nodes, node_offset + 0);
const float4 node1 = kernel_tex_fetch(__light_tree_nodes, node_offset + 1);
const float4 node2 = kernel_tex_fetch(__light_tree_nodes, node_offset + 2);
const float4 node3 = kernel_tex_fetch(__light_tree_nodes, node_offset + 3);
/* decode the data so it can be used to calculate the importance */
const float energy = node0.x;
const float3 bbox_min = make_float3(node1.x, node1.y, node1.z);
const float3 bbox_max = make_float3(node1.w, node2.x, node2.y);
const float theta_o = node2.z;
const float theta_e = node2.w;
const float3 axis = make_float3(node3.x, node3.y, node3.z);
const float3 centroid = 0.5f * (bbox_max + bbox_min);
return calc_importance(
kg, P, V, t_max, bbox_max, bbox_min, theta_o, theta_e, axis, energy, centroid);
}
/* given a node offset, this function loads and decodes the minimum amount of
* data needed for a the given node to be able to only either identify if it is
* a leaf node or how to find its two children
*
* child_o ffset is an offset into the nodes array to this nodes right child. the
* left child has index node_offset+4.
* distribution_id corresponds to an offset into the distribution array for the
* first light contained in this node. num_emitters is how many lights there are
* in this node. */
ccl_device void update_node(
KernelGlobals *kg, int node_offset, int *child_offset, int *distribution_id, int *num_emitters)
{
float4 node = kernel_tex_fetch(__light_tree_nodes, node_offset);
(*child_offset) = __float_as_int(node.y);
(*distribution_id) = __float_as_int(node.z);
(*num_emitters) = __float_as_int(node.w);
}
/* picks one of the distant lights and computes the probability of picking it */
ccl_device void light_distant_sample(
KernelGlobals *kg, float3 P, float *randu, int *index, float *pdf)
{
light_distribution_sample(kg, randu); // rescale random number
/* choose one of the distant lights randomly */
int num_distant = kernel_data.integrator.num_distant_lights;
int light = min((int)(*randu * (float)num_distant), num_distant - 1);
/* This assumes the distant lights are next to each other in the
* distribution array starting at distant_lights_offset. */
int distant_lights_offset = kernel_data.integrator.distant_lights_offset;
*index = light + distant_lights_offset;
*pdf = kernel_data.integrator.inv_num_distant_lights;
}
/* picks the background light and sets the probability of picking it */
ccl_device void light_background_sample(
KernelGlobals *kg, float3 P, float *randu, int *index, float *pdf)
{
*index = kernel_tex_fetch(__lamp_to_distribution, kernel_data.integrator.background_light_index);
*pdf = 1.0f;
}
/* picks a light from the light tree and returns its index and the probability of
* picking this light. */
ccl_device void light_tree_sample(KernelGlobals *kg,
float3 P,
float3 V,
float t_max,
float *randu,
int *index,
float *pdf_factor)
{
int sampled_index = -1;
*pdf_factor = 1.0f;
int offset = 0;
int right_child_offset, distribution_id, num_emitters;
do {
/* read in first part of node of light tree */
update_node(kg, offset, &right_child_offset, &distribution_id, &num_emitters);
/* Found a leaf - Choose which light to use */
if (right_child_offset == -1) { // Found a leaf
if (num_emitters == 1) {
sampled_index = distribution_id;
}
else {
/* At a leaf node containing several lights. Pick one of these
* by creating and sampling a CDF based on the importance metric.
*
* The number of lights in this leaf node is not known at compile
* time and dynamic allocation is not allowed on the GPU, so
* some more computations have to be done instead.
* (TODO: Could we allocate a fixed array of the same size as
* the maximum allowed number of lights per node in the
* construction algorithm? i.e. max_lights_in_node.)
*
* First, the total importance of all the lights are calculated.
* Then, a linear loop over the lights are done where the
* current CDF value is calculated. This loop can stop as soon
* as the random value used to sample the CDF is less than the
* current CDF value. The sampled light has index i-1 if i is
* the iteration counter of the loop over the lights. This is
* similar to for example light the old light_distribution_sample()
* except not having an array to store the CDF in. */
float sum = 0.0f;
for (int i = 0; i < num_emitters; ++i) {
sum += calc_light_importance(kg, P, V, t_max, offset, i);
}
if (sum == 0.0f) {
*pdf_factor = 0.0f;
return;
}
float sum_inv = 1.0f / sum;
float cdf_L = 0.0f;
float cdf_R = 0.0f;
float prob = 0.0f;
int light = 0;
for (int i = 1; i < num_emitters + 1; ++i) {
prob = calc_light_importance(kg, P, V, t_max, offset, i - 1) * sum_inv;
cdf_R = cdf_L + prob;
if (*randu < cdf_R) {
light = i - 1;
break;
}
cdf_L = cdf_R;
}
sampled_index = distribution_id + light;
*pdf_factor *= prob;
/* rescale random number */
*randu = (*randu - cdf_L) / (cdf_R - cdf_L);
}
break;
}
else { // Interior node, pick left or right randomly
/* calculate probability of going down left node */
int child_offsetL = offset + 4;
int child_offsetR = 4 * right_child_offset;
float I_L = calc_node_importance(kg, P, V, t_max, child_offsetL);
float I_R = calc_node_importance(kg, P, V, t_max, child_offsetR);
if ((I_L == 0.0f) && (I_R == 0.0f)) {
*pdf_factor = 0.0f;
break;
}
float P_L = I_L / (I_L + I_R);
/* choose which node to go down */
if (*randu <= P_L) { // Going down left node
/* rescale random number */
*randu = *randu / P_L;
offset = child_offsetL;
*pdf_factor *= P_L;
}
else { // Going down right node
/* rescale random number */
*randu = (*randu * (I_L + I_R) - I_L) / I_R;
offset = child_offsetR;
*pdf_factor *= 1.0f - P_L;
}
}
} while (true);
*index = sampled_index;
}
/* converts from an emissive triangle index to the corresponding
* light distribution index. */
ccl_device int triangle_to_distribution(KernelGlobals *kg, int triangle_id, int object_id)
{
/* binary search to find triangle_id which then gives distribution_id */
/* equivalent to implementation of std::lower_bound */
/* todo: of complexity log(N) now. could be made constant with a hash table? */
/* __triangle_to_distribution is an array of uints of the format below:
* [triangle_id0, object_id0, distribution_id0, triangle_id1,... ]
* where e.g. [triangle_id0,object_id0] corresponds to distribution id
* distribution_id0
*/
int first = 0;
int last = kernel_data.integrator.num_triangle_lights;
int count = last - first;
int middle, step;
while (count > 0) {
step = count / 2;
middle = first + step;
int triangle = kernel_tex_fetch(__triangle_to_distribution, middle * 3);
if (triangle < triangle_id) {
first = middle + 1;
count -= step + 1;
}
else
count = step;
}
/* If instancing then we can have several triangles with the same triangle_id
* so loop over object_id too. */
/* todo: do a binary search here too if many instances */
while (true) {
int object = kernel_tex_fetch(__triangle_to_distribution, first * 3 + 1);
if (object == object_id)
break;
++first;
}
kernel_assert(kernel_tex_fetch(__triangle_to_distribution, first * 3) == triangle_id);
return kernel_tex_fetch(__triangle_to_distribution, first * 3 + 2);
}
/* Decides whether to go down both childen or only one in the tree traversal.
* The split heuristic is based on the variance of the lighting within the node.
* There are two types of variances that are considered: variance in energy and
* in the distance term 1/d^2. The variance in energy is pre-computed on the
* host but the distance term is calculated here. These variances are then
* combined and normalized to get the final splitting heuristic. High variance
* leads to a lower splitting heuristic which leads to more splits during the
* traversal. */
ccl_device bool split(KernelGlobals *kg, float3 P, int node_offset)
{
/* early exists if never/always splitting */
const float threshold = kernel_data.integrator.splitting_threshold;
if (threshold == 0.0f) {
return false;
}
else if (threshold == 1.0f) {
return true;
}
/* extract bounding box of cluster */
const float4 node1 = kernel_tex_fetch(__light_tree_nodes, node_offset + 1);
const float4 node2 = kernel_tex_fetch(__light_tree_nodes, node_offset + 2);
const float3 bboxMin = make_float3(node1.x, node1.y, node1.z);
const float3 bboxMax = make_float3(node1.w, node2.x, node2.y);
/* if P is inside bounding sphere then split */
const float3 centroid = 0.5f * (bboxMax + bboxMin);
const float radius_squared = len_squared(bboxMax - centroid);
const float dist_squared = len_squared(centroid - P);
if (dist_squared <= radius_squared) {
return true;
}
/* eq. 8 & 9 */
/* the integral in eq. 8 requires us to know the interval the distance can
* be in: [a,b]. This is found by considering a bounding sphere around the
* bounding box of the node and "a" then becomes the smallest distance to
* this sphere and "b" becomes the largest. */
const float radius = sqrt(radius_squared);
const float dist = sqrt(dist_squared);
const float a = dist - radius;
const float b = dist + radius;
const float g_mean = 1.0f / (a * b);
const float g_mean_squared = g_mean * g_mean;
const float a3 = a * a * a;
const float b3 = b * b * b;
const float g_variance = (b3 - a3) / (3.0f * (b - a) * a3 * b3) - g_mean_squared;
/* eq. 10 */
const float4 node0 = kernel_tex_fetch(__light_tree_nodes, node_offset);
const float4 node3 = kernel_tex_fetch(__light_tree_nodes, node_offset + 3);
const float energy = node0.x;
const float e_variance = node3.w;
const float num_emitters = (float)__float_as_int(node0.w);
const float num_emitters_squared = num_emitters * num_emitters;
const float e_mean = energy / num_emitters;
const float e_mean_squared = e_mean * e_mean;
const float variance = (e_variance * (g_variance + g_mean_squared) +
e_mean_squared * g_variance) *
num_emitters_squared;
/* normalize the variance heuristic to be within [0,1]. Note that high
* variance corresponds to a low normalized variance. To give an idea of
* how this normalization function looks like:
* variance: 0 1 10 100 1000 10000 100000
* normalized variance: 1 0.8 0.7 0.5 0.4 0.3 0.2 */
const float variance_normalized = sqrt(sqrt(1.0f / (1.0f + sqrt(variance))));
return variance_normalized < threshold;
}
/* given a light in the form of a distribution id, this function computes the
* the probability of picking it using the light tree. this mimics the
* probability calculations in accum_light_tree_contribution()
*
* the nodes array contains all the nodes of the tree and each interior node
* has its left child directly after it in the nodes array and the right child
* is also after it at the second_child_offset.
*
* Given the structure of the nodes array we can find the path from the root
* to the leaf node the given light belongs to as follows:
*
* 1. Find the offset of the leaf node the given light belongs to.
* 2. Traverse the tree in a top-down manner where the decision to go down the
* left or right child is determined as follows:
* If the node we are looking for has a lower offset than the right child
* then it belongs to a node between the current node and the right child.
* That is, we should go down the left node. Otherwise, the right node.
* This is done recursively until the leaf node is found.
* 3. Before going down the left or right child:
* a) If we are splitting then the probability is not affected.
* b) If we are not splitting then the probability is multiplied by the
* probability of choosing this particular child node.
*/
ccl_device float light_tree_pdf(KernelGlobals *kg,
float3 P,
float3 V,
float t_max,
int distribution_id,
int offset,
float pdf,
bool can_split)
{
/* find mapping from distribution_id to node_id */
int node_id = kernel_tex_fetch(__light_distribution_to_node, distribution_id);
/* read in first part of node of light tree */
int right_child_offset, first_distribution_id, num_emitters;
update_node(kg, offset, &right_child_offset, &first_distribution_id, &num_emitters);
while (right_child_offset != -1) {
int child_offsetL = offset + 4;
int child_offsetR = 4 * right_child_offset;
/* choose whether to go down both(split) or only one of the children */
if (can_split && split(kg, P, offset)) {
/* go down to the child node that is an ancestor of this node_id
* without changing the probability since we split here */
if (node_id < child_offsetR) {
offset = child_offsetL;
}
else {
offset = child_offsetR;
}
return light_tree_pdf(kg, P, V, t_max, distribution_id, offset, pdf, true);
}
else {
/* go down one of the child nodes */
/* evaluate the importance of each of the child nodes */
float I_L = calc_node_importance(kg, P, V, t_max, child_offsetL);
float I_R = calc_node_importance(kg, P, V, t_max, child_offsetR);
if ((I_L == 0.0f) && (I_R == 0.0f)) {
return 0.0f;
}
/* calculate the probability of going down the left node */
float P_L = I_L / (I_L + I_R);
/* choose which node to go down */
if (node_id < child_offsetR) {
offset = child_offsetL;
pdf *= P_L;
}
else {
offset = child_offsetR;
pdf *= 1.0f - P_L;
}
update_node(kg, offset, &right_child_offset, &first_distribution_id, &num_emitters);
}
}
/* if there are several emitters in this leaf then pick one of them */
if (num_emitters > 1) {
/* the case of being a light inside a leaf node with several lights.
* during sampling, a CDF is created based on importance, so here
* the probability of sampling this light using the CDF has to be
* computed. This is done by dividing the importance of this light
* by the total sum of the importance of all lights in the leaf. */
float sum = 0.0f;
for (int i = 0; i < num_emitters; ++i) {
sum += calc_light_importance(kg, P, V, t_max, offset, i);
}
if (sum == 0.0f) {
return 0.0f;
}
pdf *= calc_light_importance(
kg, P, V, t_max, offset, distribution_id - first_distribution_id) /
sum;
}
return pdf;
}
/* computes the the probability of picking the given light out of all lights.
* this mimics the probability calculations in light_distribution_sample() */
ccl_device float light_distribution_pdf(
KernelGlobals *kg, float3 P, float3 V, float t_max, int prim_id, int object_id)
{
/* convert from triangle/lamp to light distribution */
int distribution_id;
if (prim_id >= 0) { // Triangle_id = prim_id
distribution_id = triangle_to_distribution(kg, prim_id, object_id);
}
else { // Lamp
int lamp_id = -prim_id - 1;
distribution_id = kernel_tex_fetch(__lamp_to_distribution, lamp_id);
}
kernel_assert((distribution_id >= 0) &&
(distribution_id < kernel_data.integrator.num_distribution));
/* compute picking pdf for this light */
if (kernel_data.integrator.use_light_tree) {
/* find out which group of lights to sample */
int group;
if (prim_id >= 0) {
group = LIGHTGROUP_TREE;
}
else {
int lamp = -prim_id - 1;
int light_type = kernel_tex_fetch(__lights, lamp).type;
if (light_type == LIGHT_DISTANT) {
group = LIGHTGROUP_DISTANT;
}
else if (light_type == LIGHT_BACKGROUND) {
group = LIGHTGROUP_BACKGROUND;
}
else {
group = LIGHTGROUP_TREE;
}
}
/* get probabilty to sample this group of lights */
float group_prob = kernel_tex_fetch(__light_group_sample_prob, group);
float pdf = group_prob;
if (group == LIGHTGROUP_TREE) {
pdf *= light_tree_pdf(kg, P, V, t_max, distribution_id, 0, 1.0f, true);
}
else if (group == LIGHTGROUP_DISTANT) {
pdf *= kernel_data.integrator.inv_num_distant_lights;
}
else if (group == LIGHTGROUP_BACKGROUND) {
/* there is only one background light so nothing to do here */
}
else {
kernel_assert(false);
}
return pdf;
}
else {
const ccl_global KernelLightDistribution *kdistribution = &kernel_tex_fetch(
__light_distribution, distribution_id);
return kdistribution->area * kernel_data.integrator.pdf_inv_totarea;
}
}
/* picks a light and returns its index and the probability of picking it */
ccl_device void light_distribution_sample(
KernelGlobals *kg, float3 P, float3 V, float t_max, float *randu, int *index, float *pdf)
{
if (kernel_data.integrator.use_light_tree) {
/* sample light type distribution */
int group = light_group_distribution_sample(kg, randu);
float group_prob = kernel_tex_fetch(__light_group_sample_prob, group);
if (group == LIGHTGROUP_TREE) {
light_tree_sample(kg, P, V, t_max, randu, index, pdf);
}
else if (group == LIGHTGROUP_DISTANT) {
light_distant_sample(kg, P, randu, index, pdf);
}
else if (group == LIGHTGROUP_BACKGROUND) {
light_background_sample(kg, P, randu, index, pdf);
}
else {
kernel_assert(false);
}
*pdf *= group_prob;
}
else { // Sample light distribution CDF
*index = light_distribution_sample(kg, randu);
const ccl_global KernelLightDistribution *kdistribution = &kernel_tex_fetch(
__light_distribution, *index);
*pdf = kdistribution->area * kernel_data.integrator.pdf_inv_totarea;
}
}
/* picks a point on a given light and computes the probability of picking this point*/
ccl_device void light_point_sample(KernelGlobals *kg,
int lamp,
float randu,
float randv,
float time,
float3 P,
int bounce,
int distribution_id,
LightSample *ls)
{
if (lamp < 0) {
/* sample index */
int index = light_distribution_sample(kg, &randu);
/* fetch light data */
/* fetch light data and compute rest of light pdf */
const ccl_global KernelLightDistribution *kdistribution = &kernel_tex_fetch(
__light_distribution, index);
__light_distribution, distribution_id);
int prim = kdistribution->prim;
if (prim >= 0) {
@@ -647,17 +1336,54 @@ ccl_device_noinline bool light_sample(KernelGlobals *kg,
triangle_light_sample(kg, prim, object, randu, randv, time, ls, P);
ls->shader |= shader_flag;
return (ls->pdf > 0.0f);
return;
}
lamp = -prim - 1;
}
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
ls->pdf = 0.0f;
return;
}
if (!lamp_light_sample(kg, lamp, randu, randv, P, ls)) {
ls->pdf = 0.0f;
return;
}
}
/* picks a light and then picks a point on the light and computes the
* probability of doing so. V and t_max are only used when using light tree for sampling. If t_max
* is negative V should be the normal vector at P. Otherwise V should be the direction of the light
* ray starting at P.
*/
ccl_device_noinline bool light_sample(KernelGlobals *kg,
int lamp,
float randu,
float randv,
float time,
float3 P,
float3 V,
float t_max,
int bounce,
LightSample *ls)
{
/* pick a light and compute the probability of picking this light */
float pdf_factor = 0.0f;
int index = -1;
light_distribution_sample(kg, P, V, t_max, &randu, &index, &pdf_factor);
if (pdf_factor == 0.0f) {
return false;
}
return lamp_light_sample(kg, lamp, randu, randv, P, ls);
/* pick a point on the light and the probability of picking this point */
light_point_sample(kg, lamp, randu, randv, time, P, bounce, index, ls);
/* combine pdfs */
ls->pdf *= pdf_factor;
return (ls->pdf > 0.0f);
}
ccl_device_inline int light_select_num_samples(KernelGlobals *kg, int index)

View File

@@ -99,6 +99,7 @@ ccl_device_forceinline void kernel_path_lamp_emission(KernelGlobals *kg,
Ray light_ray ccl_optional_struct_init;
light_ray.P = ray->P - state->ray_t * ray->D;
state->ray_t += isect->t;
light_ray.D = ray->D;
light_ray.t = state->ray_t;
@@ -185,6 +186,8 @@ ccl_device_forceinline VolumeIntegrateResult kernel_path_volume(KernelGlobals *k
shader_setup_from_volume(kg, sd, &volume_ray);
kernel_volume_decoupled_record(kg, state, &volume_ray, sd, &volume_segment, step_size);
kernel_update_light_picking(sd, &volume_ray);
volume_segment.sampling_method = sampling_method;
/* emission */
@@ -231,6 +234,8 @@ ccl_device_forceinline VolumeIntegrateResult kernel_path_volume(KernelGlobals *k
VolumeIntegrateResult result = kernel_volume_integrate(
kg, state, sd, &volume_ray, L, throughput, step_size);
kernel_update_light_picking(sd, NULL);
# ifdef __VOLUME_SCATTER__
if (result == VOLUME_PATH_SCATTERED) {
/* direct lighting */
@@ -390,6 +395,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg,
/* path iteration */
for (;;) {
/* Find intersection with objects in scene. */
Intersection isect;
bool hit = kernel_path_scene_intersect(kg, state, ray, &isect, L);
@@ -408,6 +414,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg,
else if (result == VOLUME_PATH_MISSED) {
break;
}
# endif /* __VOLUME__*/
/* Shade background. */
@@ -453,6 +460,8 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg,
throughput /= probability;
}
kernel_update_light_picking(sd, NULL);
# ifdef __DENOISING_FEATURES__
kernel_update_denoising_features(kg, sd, state, L);
# endif
@@ -518,7 +527,6 @@ ccl_device_forceinline void kernel_path_integrate(KernelGlobals *kg,
/* Shader data memory used for both volumes and surfaces, saves stack space. */
ShaderData sd;
# ifdef __SUBSURFACE__
SubsurfaceIndirectRays ss_indirect;
kernel_path_subsurface_init_indirect(&ss_indirect);
@@ -528,6 +536,7 @@ ccl_device_forceinline void kernel_path_integrate(KernelGlobals *kg,
/* path iteration */
for (;;) {
/* Find intersection with objects in scene. */
Intersection isect;
bool hit = kernel_path_scene_intersect(kg, state, ray, &isect, L);
@@ -590,6 +599,8 @@ ccl_device_forceinline void kernel_path_integrate(KernelGlobals *kg,
throughput /= probability;
}
kernel_update_light_picking(&sd, NULL);
# ifdef __DENOISING_FEATURES__
kernel_update_denoising_features(kg, &sd, state, L);
# endif

View File

@@ -102,6 +102,8 @@ ccl_device_forceinline void kernel_branched_path_volume(KernelGlobals *kg,
shader_setup_from_volume(kg, sd, &volume_ray);
kernel_volume_decoupled_record(kg, state, &volume_ray, sd, &volume_segment, step_size);
kernel_update_light_picking(sd, &volume_ray);
/* direct light sampling */
if (volume_segment.closure_flag & SD_SCATTER) {
volume_segment.sampling_method = volume_stack_sampling_method(kg, state->volume_stack);
@@ -134,6 +136,9 @@ ccl_device_forceinline void kernel_branched_path_volume(KernelGlobals *kg,
if (result == VOLUME_PATH_SCATTERED &&
kernel_path_volume_bounce(kg, sd, &tp, &ps, &L->state, &pray)) {
indirect_sd->P_pick = sd->P_pick;
indirect_sd->V_pick = sd->V_pick;
indirect_sd->t_pick = sd->t_pick;
kernel_path_indirect(kg, indirect_sd, emission_sd, &pray, tp * num_samples_inv, &ps, L);
/* for render passes, sum and reset indirect light pass variables
@@ -173,6 +178,8 @@ ccl_device_forceinline void kernel_branched_path_volume(KernelGlobals *kg,
VolumeIntegrateResult result = kernel_volume_integrate(
kg, &ps, sd, &volume_ray, L, &tp, step_size);
kernel_update_light_picking(sd, &volume_ray);
# ifdef __VOLUME_SCATTER__
if (result == VOLUME_PATH_SCATTERED) {
/* todo: support equiangular, MIS and all light sampling.
@@ -180,6 +187,9 @@ ccl_device_forceinline void kernel_branched_path_volume(KernelGlobals *kg,
kernel_path_volume_connect_light(kg, sd, emission_sd, tp, state, L);
if (kernel_path_volume_bounce(kg, sd, &tp, &ps, &L->state, &pray)) {
indirect_sd->P_pick = sd->P_pick;
indirect_sd->V_pick = sd->V_pick;
indirect_sd->t_pick = sd->t_pick;
kernel_path_indirect(kg, indirect_sd, emission_sd, &pray, tp, &ps, L);
/* for render passes, sum and reset indirect light pass variables
@@ -265,7 +275,9 @@ ccl_device_noinline_cpu void kernel_branched_path_surface_indirect_light(KernelG
}
ps.rng_hash = state->rng_hash;
indirect_sd->P_pick = sd->P_pick;
indirect_sd->V_pick = sd->V_pick;
indirect_sd->t_pick = sd->t_pick;
kernel_path_indirect(kg, indirect_sd, emission_sd, &bsdf_ray, tp * num_samples_inv, &ps, L);
/* for render passes, sum and reset indirect light pass variables
@@ -393,6 +405,7 @@ ccl_device void kernel_branched_path_integrate(KernelGlobals *kg,
* Indirect bounces are handled in kernel_branched_path_surface_indirect_light().
*/
for (;;) {
/* Find intersection with objects in scene. */
Intersection isect;
bool hit = kernel_path_scene_intersect(kg, &state, &ray, &isect, L);
@@ -445,6 +458,8 @@ ccl_device void kernel_branched_path_integrate(KernelGlobals *kg,
}
}
kernel_update_light_picking(&sd, NULL);
# ifdef __DENOISING_FEATURES__
kernel_update_denoising_features(kg, &sd, &state, L);
# endif

View File

@@ -68,6 +68,7 @@ ccl_device_inline
* integration loop stops when this function returns true.
*/
subsurface_scatter_multi_setup(kg, &ss_isect, hit, sd, state, bssrdf_type, bssrdf_roughness);
kernel_update_light_picking(sd, NULL);
kernel_path_surface_connect_light(kg, sd, emission_sd, *throughput, state, L);

View File

@@ -16,6 +16,220 @@
CCL_NAMESPACE_BEGIN
/* connect the given light sample with the shading point and calculate its
* contribution and accumulate it to L */
ccl_device void accum_light_contribution(KernelGlobals *kg,
ShaderData *sd,
ShaderData *emission_sd,
LightSample *ls,
ccl_addr_space PathState *state,
Ray *light_ray,
BsdfEval *L_light,
PathRadiance *L,
bool *is_lamp,
float terminate,
float3 throughput,
float scale)
{
if (direct_emission(kg, sd, emission_sd, ls, state, light_ray, L_light, is_lamp, terminate)) {
/* trace shadow ray */
float3 shadow;
if (!shadow_blocked(kg, sd, emission_sd, state, light_ray, &shadow)) {
/* accumulate */
path_radiance_accum_light(
kg, L, state, throughput * scale, L_light, shadow, scale, *is_lamp);
}
else {
path_radiance_accum_total_light(L, state, throughput * scale, L_light);
}
}
}
/* The accum_light_tree_contribution() function does the following:
* 1. Recursive tree traversal using splitting. This picks one or more lights.
* 2. For each picked light, a position on the light is also chosen.
* 3. The total contribution of all these light samples are evaluated and
* accumulated to L. */
ccl_device void accum_light_tree_contribution(KernelGlobals *kg,
float randu,
float randv,
int offset,
float pdf_factor,
bool can_split,
float3 throughput,
float scale_factor,
PathRadiance *L,
ccl_addr_space PathState *state,
ShaderData *sd,
ShaderData *emission_sd)
{
float3 P = sd->P_pick;
float3 V = sd->V_pick;
float t = sd->t_pick;
float time = sd->time;
int bounce = state->bounce;
float randu_stack[64];
float randv_stack[64];
int offset_stack[64];
float pdf_stack[64];
randu_stack[0] = randu;
randv_stack[0] = randv;
offset_stack[0] = offset;
pdf_stack[0] = pdf_factor;
int stack_idx = 0;
while (stack_idx > -1) {
randu = randu_stack[stack_idx];
randv = randv_stack[stack_idx];
offset = offset_stack[stack_idx];
pdf_factor = pdf_stack[stack_idx];
/* read in first part of node of light tree */
int right_child_offset, distribution_id, num_emitters;
update_node(kg, offset, &right_child_offset, &distribution_id, &num_emitters);
/* found a leaf */
if (right_child_offset == -1) {
/* if there are several emitters in this leaf then pick one of them */
if (num_emitters > 1) {
/* create and sample CDF without dynamic allocation.
* see comment in light_tree_sample() for this piece of code */
float sum = 0.0f;
for (int i = 0; i < num_emitters; ++i) {
sum += calc_light_importance(kg, P, V, t, offset, i);
}
if (sum == 0.0f) {
--stack_idx;
continue;
}
float sum_inv = 1.0f / sum;
float cdf_L = 0.0f;
float cdf_R = 0.0f;
float prob = 0.0f;
int light = num_emitters - 1;
for (int i = 1; i < num_emitters + 1; ++i) {
prob = calc_light_importance(kg, P, V, t, offset, i - 1) * sum_inv;
cdf_R = cdf_L + prob;
if (randu < cdf_R) {
light = i - 1;
break;
}
cdf_L = cdf_R;
}
distribution_id += light;
pdf_factor *= prob;
/* rescale random number */
randu = (randu - cdf_L) / (cdf_R - cdf_L);
}
/* pick a point on the chosen light(distribution_id) and calculate the
* probability of picking this point */
LightSample ls;
light_point_sample(kg, -1, randu, randv, time, P, bounce, distribution_id, &ls);
/* combine pdfs */
ls.pdf *= pdf_factor;
if (ls.pdf <= 0.0f) {
--stack_idx;
continue;
}
/* compute and accumulate the total contribution of this light */
Ray light_ray;
light_ray.t = 0.0f;
#ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
#endif
BsdfEval L_light;
bool is_lamp;
float terminate = path_state_rng_light_termination(kg, state);
accum_light_contribution(kg,
sd,
emission_sd,
&ls,
state,
&light_ray,
&L_light,
L,
&is_lamp,
terminate,
throughput,
scale_factor);
--stack_idx;
can_split = true;
continue;
}
else { // Interior node, choose which child(ren) to go down
int child_offsetL = offset + 4;
int child_offsetR = 4 * right_child_offset;
/* choose whether to go down both(split) or only one of the children */
if (can_split && split(kg, P, offset)) {
/* go down both child nodes */
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = child_offsetL;
pdf_stack[stack_idx] = pdf_factor;
++stack_idx;
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = child_offsetR;
pdf_stack[stack_idx] = pdf_factor;
}
else {
/* go down one of the child nodes */
/* evaluate the importance of each of the child nodes */
float I_L = calc_node_importance(kg, P, V, t, child_offsetL);
float I_R = calc_node_importance(kg, P, V, t, child_offsetR);
if ((I_L == 0.0f) && (I_R == 0.0f)) {
return;
}
/* calculate the probability of going down the left node */
float P_L = I_L / (I_L + I_R);
/* choose which node to go down */
if (randu <= P_L) { // Going down left node
/* rescale random number */
randu = randu / P_L;
offset = child_offsetL;
pdf_factor *= P_L;
}
else { // Going down right node
/* rescale random number */
randu = (randu * (I_L + I_R) - I_L) / I_R;
offset = child_offsetR;
pdf_factor *= 1.0f - P_L;
}
can_split = false;
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = offset;
pdf_stack[stack_idx] = pdf_factor;
}
}
}
}
#if defined(__BRANCHED_PATH__) || defined(__SUBSURFACE__) || defined(__SHADOW_TRICKS__) || \
defined(__BAKING__)
/* branched path tracing: connect path directly to position on one or more lights and add it to L
@@ -34,104 +248,174 @@ ccl_device_noinline_cpu void kernel_branched_path_surface_connect_light(
/* sample illumination from lights to find path contribution */
BsdfEval L_light ccl_optional_struct_init;
int num_lights = 0;
if (kernel_data.integrator.use_direct_light) {
if (sample_all_lights) {
num_lights = kernel_data.integrator.num_all_lights;
if (kernel_data.integrator.pdf_triangles != 0.0f) {
num_lights += 1;
}
bool use_light_tree = kernel_data.integrator.use_light_tree;
if (use_light_tree) {
Ray light_ray;
bool is_lamp;
light_ray.t = 0.0f;
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
int index;
float randu, randv;
path_state_rng_2D(kg, state, PRNG_LIGHT_U, &randu, &randv);
/* sample light group distribution */
int group = light_group_distribution_sample(kg, &randu);
float group_prob = kernel_tex_fetch(__light_group_sample_prob, group);
float pdf = 1.0f;
if (group == LIGHTGROUP_TREE) {
/* accumulate contribution to L from potentially several lights */
accum_light_tree_contribution(kg,
randu,
randv,
0,
group_prob,
true,
throughput,
num_samples_adjust,
L, // todo: is num_samples_adjust correct here?
state,
sd,
emission_sd);
/* have accumulated all the contributions so return */
return;
}
else if (group == LIGHTGROUP_DISTANT) {
/* pick a single distant light */
light_distant_sample(kg, sd->P, &randu, &index, &pdf);
}
else if (group == LIGHTGROUP_BACKGROUND) {
/* pick a single background light */
light_background_sample(kg, sd->P, &randu, &index, &pdf);
}
else {
num_lights = 1;
kernel_assert(false);
}
/* sample a point on the given distant/background light */
LightSample ls;
light_point_sample(kg, -1, randu, randv, sd->time, sd->P, state->bounce, index, &ls);
/* combine pdfs */
ls.pdf *= group_prob;
if (ls.pdf <= 0.0f)
return;
/* accumulate the contribution of this distant/background light to L */
float terminate = path_state_rng_light_termination(kg, state);
accum_light_contribution(kg,
sd,
emission_sd,
&ls,
state,
&light_ray,
&L_light,
L,
&is_lamp,
terminate,
throughput,
num_samples_adjust);
}
for (int i = 0; i < num_lights; i++) {
/* sample one light at random */
int num_samples = 1;
int num_all_lights = 1;
uint lamp_rng_hash = state->rng_hash;
bool double_pdf = false;
bool is_mesh_light = false;
bool is_lamp = false;
if (sample_all_lights) {
/* lamp sampling */
is_lamp = i < kernel_data.integrator.num_all_lights;
if (is_lamp) {
if (UNLIKELY(light_select_reached_max_bounces(kg, i, state->bounce))) {
continue;
else {
int num_lights = 0;
if (kernel_data.integrator.use_direct_light) {
if (sample_all_lights) {
num_lights = kernel_data.integrator.num_all_lights;
if (kernel_data.integrator.pdf_triangles != 0.0f) {
num_lights += 1;
}
num_samples = ceil_to_int(num_samples_adjust * light_select_num_samples(kg, i));
num_all_lights = kernel_data.integrator.num_all_lights;
lamp_rng_hash = cmj_hash(state->rng_hash, i);
double_pdf = kernel_data.integrator.pdf_triangles != 0.0f;
}
/* mesh light sampling */
else {
num_samples = ceil_to_int(num_samples_adjust * kernel_data.integrator.mesh_light_samples);
double_pdf = kernel_data.integrator.num_all_lights != 0;
is_mesh_light = true;
num_lights = 1;
}
}
float num_samples_inv = num_samples_adjust / (num_samples * num_all_lights);
for (int i = 0; i < num_lights; i++) {
/* sample one light at random */
int num_samples = 1;
uint lamp_rng_hash = state->rng_hash;
bool double_pdf = false;
bool is_mesh_light = false;
bool is_lamp = false;
for (int j = 0; j < num_samples; j++) {
Ray light_ray ccl_optional_struct_init;
light_ray.t = 0.0f; /* reset ray */
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
bool has_emission = false;
if (kernel_data.integrator.use_direct_light && (sd->flag & SD_BSDF_HAS_EVAL)) {
float light_u, light_v;
path_branched_rng_2D(
kg, lamp_rng_hash, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v);
float terminate = path_branched_rng_light_termination(
kg, lamp_rng_hash, state, j, num_samples);
/* only sample triangle lights */
if (is_mesh_light && double_pdf) {
light_u = 0.5f * light_u;
if (sample_all_lights) {
/* lamp sampling */
is_lamp = i < kernel_data.integrator.num_all_lights;
if (is_lamp) {
if (UNLIKELY(light_select_reached_max_bounces(kg, i, state->bounce))) {
continue;
}
num_samples = ceil_to_int(num_samples_adjust * light_select_num_samples(kg, i));
lamp_rng_hash = cmj_hash(state->rng_hash, i);
double_pdf = kernel_data.integrator.pdf_triangles != 0.0f;
}
/* mesh light sampling */
else {
num_samples = ceil_to_int(num_samples_adjust *
kernel_data.integrator.mesh_light_samples);
double_pdf = kernel_data.integrator.num_all_lights != 0;
is_mesh_light = true;
}
}
LightSample ls ccl_optional_struct_init;
const int lamp = is_lamp ? i : -1;
if (light_sample(kg, lamp, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) {
/* The sampling probability returned by lamp_light_sample assumes that all lights were
* sampled. However, this code only samples lamps, so if the scene also had mesh lights,
* the real probability is twice as high. */
if (double_pdf) {
ls.pdf *= 2.0f;
float num_samples_inv = num_samples_adjust / num_samples;
for (int j = 0; j < num_samples; j++) {
Ray light_ray ccl_optional_struct_init;
light_ray.t = 0.0f; /* reset ray */
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
if (kernel_data.integrator.use_direct_light && (sd->flag & SD_BSDF_HAS_EVAL)) {
float light_u, light_v;
path_branched_rng_2D(
kg, lamp_rng_hash, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v);
float terminate = path_branched_rng_light_termination(
kg, lamp_rng_hash, state, j, num_samples);
/* only sample triangle lights */
if (is_mesh_light && double_pdf) {
light_u = 0.5f * light_u;
}
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
}
}
/* trace shadow ray */
float3 shadow;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
if (has_emission) {
if (!blocked) {
/* accumulate */
path_radiance_accum_light(kg,
L,
state,
throughput * num_samples_inv,
&L_light,
shadow,
num_samples_inv,
is_lamp);
}
else {
path_radiance_accum_total_light(L, state, throughput * num_samples_inv, &L_light);
LightSample ls ccl_optional_struct_init;
const int lamp = is_lamp ? i : -1;
if (light_sample(kg,
lamp,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls)) {
/* The sampling probability returned by lamp_light_sample assumes that all lights were
* sampled. However, this code only samples lamps, so if the scene also had mesh
* lights, the real probability is twice as high. */
if (double_pdf) {
ls.pdf *= 2.0f;
}
accum_light_contribution(kg,
sd,
emission_sd,
&ls,
state,
&light_ray,
&L_light,
L,
&is_lamp,
terminate,
throughput,
num_samples_inv);
}
}
}
}
@@ -225,11 +509,11 @@ ccl_device_inline void kernel_path_surface_connect_light(KernelGlobals *kg,
int all = (state->flag & PATH_RAY_SHADOW_CATCHER);
kernel_branched_path_surface_connect_light(kg, sd, emission_sd, state, throughput, 1.0f, L, all);
# else
/* sample illumination from lights to find path contribution */
Ray light_ray ccl_optional_struct_init;
BsdfEval L_light ccl_optional_struct_init;
bool is_lamp = false;
bool has_emission = false;
light_ray.t = 0.0f;
# ifdef __OBJECT_MOTION__
@@ -241,27 +525,32 @@ ccl_device_inline void kernel_path_surface_connect_light(KernelGlobals *kg,
path_state_rng_2D(kg, state, PRNG_LIGHT_U, &light_u, &light_v);
LightSample ls ccl_optional_struct_init;
if (light_sample(kg, -1, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) {
if (light_sample(kg,
-1,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls)) {
float terminate = path_state_rng_light_termination(kg, state);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
accum_light_contribution(kg,
sd,
emission_sd,
&ls,
state,
&light_ray,
&L_light,
L,
&is_lamp,
terminate,
throughput,
1.0f);
}
}
/* trace shadow ray */
float3 shadow;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
if (has_emission) {
if (!blocked) {
/* accumulate */
path_radiance_accum_light(kg, L, state, throughput, &L_light, shadow, 1.0f, is_lamp);
}
else {
path_radiance_accum_total_light(L, state, throughput, &L_light);
}
}
# endif
#endif
}
@@ -310,6 +599,7 @@ ccl_device bool kernel_path_surface_bounce(KernelGlobals *kg,
/* setup ray */
ray->P = ray_offset(sd->P, (label & LABEL_TRANSMIT) ? -sd->Ng : sd->Ng);
kernel_update_light_picking(sd, NULL);
ray->D = normalize(bsdf_omega_in);
if (state->bounce == 0)
@@ -342,6 +632,8 @@ ccl_device bool kernel_path_surface_bounce(KernelGlobals *kg,
/* setup ray position, direction stays unchanged */
ray->P = ray_offset(sd->P, -sd->Ng);
kernel_update_light_picking(sd, NULL);
# ifdef __RAY_DIFFERENTIALS__
ray->dP = sd->dP;
# endif

View File

@@ -43,7 +43,16 @@ ccl_device_inline void kernel_path_volume_connect_light(KernelGlobals *kg,
path_state_rng_2D(kg, state, PRNG_LIGHT_U, &light_u, &light_v);
LightSample ls ccl_optional_struct_init;
if (light_sample(kg, -1, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) {
if (light_sample(kg,
-1,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls)) {
float terminate = path_state_rng_light_termination(kg, state);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
@@ -128,6 +137,210 @@ ccl_device_noinline_cpu bool kernel_path_volume_bounce(KernelGlobals *kg,
}
# if !defined(__SPLIT_KERNEL__) && (defined(__BRANCHED_PATH__) || defined(__VOLUME_DECOUPLED__))
ccl_device void accum_light_tree_contribution_volume(KernelGlobals *kg,
float randu,
float randv,
int offset,
float pdf_factor,
bool can_split,
float3 throughput,
float scale_factor,
PathRadiance *L,
ccl_addr_space PathState *state,
ShaderData *sd,
ShaderData *emission_sd,
Ray *ray,
const VolumeSegment *segment)
{
float3 P = sd->P_pick;
float3 V = sd->V_pick;
float t = sd->t_pick;
float time = sd->time;
int bounce = state->bounce;
float randu_stack[64];
float randv_stack[64];
int offset_stack[64];
float pdf_stack[64];
randu_stack[0] = randu;
randv_stack[0] = randv;
offset_stack[0] = offset;
pdf_stack[0] = pdf_factor;
int stack_idx = 0;
while (stack_idx > -1) {
randu = randu_stack[stack_idx];
randv = randv_stack[stack_idx];
offset = offset_stack[stack_idx];
pdf_factor = pdf_stack[stack_idx];
/* read in first part of node of light tree */
int right_child_offset, distribution_id, num_emitters;
update_node(kg, offset, &right_child_offset, &distribution_id, &num_emitters);
/* found a leaf */
if (right_child_offset == -1) {
/* if there are several emitters in this leaf then pick one of them */
if (num_emitters > 1) {
/* create and sample CDF without dynamic allocation.
* see comment in light_tree_sample() for this piece of code */
float sum = 0.0f;
for (int i = 0; i < num_emitters; ++i) {
sum += calc_light_importance(kg, P, V, t, offset, i);
}
if (sum == 0.0f) {
--stack_idx;
continue;
}
float sum_inv = 1.0f / sum;
float cdf_L = 0.0f;
float cdf_R = 0.0f;
float prob = 0.0f;
int light = num_emitters - 1;
for (int i = 1; i < num_emitters + 1; ++i) {
prob = calc_light_importance(kg, P, V, t, offset, i - 1) * sum_inv;
cdf_R = cdf_L + prob;
if (randu < cdf_R) {
light = i - 1;
break;
}
cdf_L = cdf_R;
}
distribution_id += light;
pdf_factor *= prob;
/* rescale random number */
randu = (randu - cdf_L) / (cdf_R - cdf_L);
}
/* pick a point on the chosen light(distribution_id) and calculate the
* probability of picking this point */
LightSample ls;
light_point_sample(kg, -1, randu, randv, time, P, bounce, distribution_id, &ls);
/* combine pdfs */
ls.pdf *= pdf_factor;
/* compute and accumulate the total contribution of this light */
Ray light_ray;
light_ray.t = 0.0f;
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
float3 tp = throughput;
bool has_emission = false;
bool is_lamp = false;
BsdfEval L_light ccl_optional_struct_init;
/* sample position on volume segment */
float rphase = path_branched_rng_1D(
kg, state->rng_hash, state, distribution_id, 1.0f, PRNG_PHASE_CHANNEL);
float rscatter = path_branched_rng_1D(
kg, state->rng_hash, state, distribution_id, 1.0f, PRNG_SCATTER_DISTANCE);
VolumeIntegrateResult result = kernel_volume_decoupled_scatter(kg,
state,
ray,
sd,
&tp,
rphase,
rscatter,
segment,
(ls.t != FLT_MAX) ? &ls.P :
NULL,
false);
if (result == VOLUME_PATH_SCATTERED) {
light_point_sample(kg, -1, randu, randv, time, sd->P_pick, bounce, distribution_id, &ls);
if (ls.pdf <= 0.0f) {
--stack_idx;
continue;
}
/* sample random light */
float terminate = path_branched_rng_light_termination(
kg, state->rng_hash, state, distribution_id, 1.0f);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
}
/* trace shadow ray */
float3 shadow;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
if (has_emission && !blocked) {
/* accumulate */
path_radiance_accum_light(kg, L, state, tp, &L_light, shadow, 1.0f, is_lamp);
}
--stack_idx;
can_split = true;
continue;
}
else { // Interior node, choose which child(ren) to go down
int child_offsetL = offset + 4;
int child_offsetR = 4 * right_child_offset;
/* choose whether to go down both(split) or only one of the children */
if (can_split && split(kg, P, offset)) {
/* go down both child nodes */
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = child_offsetL;
pdf_stack[stack_idx] = pdf_factor;
++stack_idx;
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = child_offsetR;
pdf_stack[stack_idx] = pdf_factor;
}
else {
/* go down one of the child nodes */
/* evaluate the importance of each of the child nodes */
float I_L = calc_node_importance(kg, P, V, t, child_offsetL);
float I_R = calc_node_importance(kg, P, V, t, child_offsetR);
if ((I_L == 0.0f) && (I_R == 0.0f)) {
return;
}
/* calculate the probability of going down the left node */
float P_L = I_L / (I_L + I_R);
/* choose which node to go down */
if (randu <= P_L) { // Going down left node
/* rescale random number */
randu = randu / P_L;
offset = child_offsetL;
pdf_factor *= P_L;
}
else { // Going down right node
/* rescale random number */
randu = (randu * (I_L + I_R) - I_L) / I_R;
offset = child_offsetR;
pdf_factor *= 1.0f - P_L;
}
can_split = false;
randu_stack[stack_idx] = randu;
randv_stack[stack_idx] = randv;
offset_stack[stack_idx] = offset;
pdf_stack[stack_idx] = pdf_factor;
}
}
}
}
ccl_device void kernel_branched_path_volume_connect_light(KernelGlobals *kg,
ShaderData *sd,
ShaderData *emission_sd,
@@ -141,113 +354,228 @@ ccl_device void kernel_branched_path_volume_connect_light(KernelGlobals *kg,
# ifdef __EMISSION__
BsdfEval L_light ccl_optional_struct_init;
int num_lights = 1;
if (sample_all_lights) {
num_lights = kernel_data.integrator.num_all_lights;
if (kernel_data.integrator.pdf_triangles != 0.0f) {
num_lights += 1;
bool use_light_tree = kernel_data.integrator.use_light_tree;
if (use_light_tree) {
Ray light_ray;
bool is_lamp;
light_ray.t = 0.0f;
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
int index;
float randu, randv;
path_state_rng_2D(kg, state, PRNG_LIGHT_U, &randu, &randv);
/* sample light group distribution */
int group = light_group_distribution_sample(kg, &randu);
float group_prob = kernel_tex_fetch(__light_group_sample_prob, group);
float pdf = 1.0f;
if (group == LIGHTGROUP_TREE) {
/* accumulate contribution to L from potentially several lights */
accum_light_tree_contribution_volume(kg,
randu,
randv,
0,
group_prob,
true,
throughput,
1.0f,
L, // todo: is num_samples_adjust correct here?
state,
sd,
emission_sd,
ray,
segment);
/* have accumulated all the contributions so return */
return;
}
else if (group == LIGHTGROUP_DISTANT) {
/* pick a single distant light */
light_distant_sample(kg, sd->P, &randu, &index, &pdf);
}
else if (group == LIGHTGROUP_BACKGROUND) {
/* pick a single background light */
light_background_sample(kg, sd->P, &randu, &index, &pdf);
}
else {
kernel_assert(false);
}
/* sample a point on the given distant/background light */
LightSample ls;
light_point_sample(kg, -1, randu, randv, sd->time, sd->P, state->bounce, index, &ls);
/* combine pdfs */
ls.pdf *= group_prob;
if (ls.pdf <= 0.0f)
return;
float3 tp = throughput;
bool has_emission = false;
/* sample position on volume segment */
float rphase = path_branched_rng_1D(
kg, state->rng_hash, state, index, 1.0f, PRNG_PHASE_CHANNEL);
float rscatter = path_branched_rng_1D(
kg, state->rng_hash, state, index, 1.0f, PRNG_SCATTER_DISTANCE);
VolumeIntegrateResult result = kernel_volume_decoupled_scatter(kg,
state,
ray,
sd,
&tp,
rphase,
rscatter,
segment,
(ls.t != FLT_MAX) ? &ls.P :
NULL,
false);
if (result == VOLUME_PATH_SCATTERED) {
light_point_sample(kg, -1, randu, randv, sd->time, sd->P_pick, state->bounce, index, &ls);
/* sample random light */
float terminate = path_branched_rng_light_termination(
kg, state->rng_hash, state, index, 1.0f);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
}
/* trace shadow ray */
float3 shadow;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
if (has_emission && !blocked) {
/* accumulate */
path_radiance_accum_light(kg, L, state, tp, &L_light, shadow, 1.0f, is_lamp);
}
}
for (int i = 0; i < num_lights; ++i) {
/* sample one light at random */
int num_samples = 1;
int num_all_lights = 1;
uint lamp_rng_hash = state->rng_hash;
bool double_pdf = false;
bool is_mesh_light = false;
bool is_lamp = false;
else {
int num_lights = 1;
if (sample_all_lights) {
/* lamp sampling */
is_lamp = i < kernel_data.integrator.num_all_lights;
if (is_lamp) {
if (UNLIKELY(light_select_reached_max_bounces(kg, i, state->bounce))) {
continue;
}
num_samples = light_select_num_samples(kg, i);
num_all_lights = kernel_data.integrator.num_all_lights;
lamp_rng_hash = cmj_hash(state->rng_hash, i);
double_pdf = kernel_data.integrator.pdf_triangles != 0.0f;
}
/* mesh light sampling */
else {
num_samples = kernel_data.integrator.mesh_light_samples;
double_pdf = kernel_data.integrator.num_all_lights != 0;
is_mesh_light = true;
num_lights = kernel_data.integrator.num_all_lights;
if (kernel_data.integrator.pdf_triangles != 0.0f) {
num_lights += 1;
}
}
for (int i = 0; i < num_lights; ++i) {
/* sample one light at random */
int num_samples = 1;
uint lamp_rng_hash = state->rng_hash;
bool double_pdf = false;
bool is_mesh_light = false;
bool is_lamp = false;
float num_samples_inv = 1.0f / (num_samples * num_all_lights);
for (int j = 0; j < num_samples; j++) {
Ray light_ray ccl_optional_struct_init;
light_ray.t = 0.0f; /* reset ray */
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
bool has_emission = false;
float3 tp = throughput;
if (kernel_data.integrator.use_direct_light) {
/* sample random position on random light/triangle */
float light_u, light_v;
path_branched_rng_2D(
kg, lamp_rng_hash, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v);
/* only sample triangle lights */
if (is_mesh_light && double_pdf) {
light_u = 0.5f * light_u;
}
LightSample ls ccl_optional_struct_init;
const int lamp = is_lamp ? i : -1;
light_sample(kg, lamp, light_u, light_v, sd->time, ray->P, state->bounce, &ls);
/* sample position on volume segment */
float rphase = path_branched_rng_1D(
kg, state->rng_hash, state, j, num_samples, PRNG_PHASE_CHANNEL);
float rscatter = path_branched_rng_1D(
kg, state->rng_hash, state, j, num_samples, PRNG_SCATTER_DISTANCE);
VolumeIntegrateResult result = kernel_volume_decoupled_scatter(kg,
state,
ray,
sd,
&tp,
rphase,
rscatter,
segment,
(ls.t != FLT_MAX) ? &ls.P :
NULL,
false);
if (result == VOLUME_PATH_SCATTERED) {
/* todo: split up light_sample so we don't have to call it again with new position */
if (light_sample(kg, lamp, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) {
if (double_pdf) {
ls.pdf *= 2.0f;
}
/* sample random light */
float terminate = path_branched_rng_light_termination(
kg, state->rng_hash, state, j, num_samples);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
if (sample_all_lights) {
/* lamp sampling */
is_lamp = i < kernel_data.integrator.num_all_lights;
if (is_lamp) {
if (UNLIKELY(light_select_reached_max_bounces(kg, i, state->bounce))) {
continue;
}
lamp_rng_hash = cmj_hash(state->rng_hash, i);
double_pdf = kernel_data.integrator.pdf_triangles != 0.0f;
}
/* mesh light sampling */
else {
num_samples = kernel_data.integrator.mesh_light_samples;
double_pdf = kernel_data.integrator.num_all_lights != 0;
is_mesh_light = true;
}
}
/* trace shadow ray */
float3 shadow;
float num_samples_inv = 1.0f / num_samples;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
for (int j = 0; j < num_samples; j++) {
Ray light_ray ccl_optional_struct_init;
light_ray.t = 0.0f; /* reset ray */
# ifdef __OBJECT_MOTION__
light_ray.time = sd->time;
# endif
bool has_emission = false;
if (has_emission && !blocked) {
/* accumulate */
path_radiance_accum_light(
kg, L, state, tp * num_samples_inv, &L_light, shadow, num_samples_inv, is_lamp);
float3 tp = throughput;
if (kernel_data.integrator.use_direct_light) {
/* sample random position on random light/triangle */
float light_u, light_v;
path_branched_rng_2D(
kg, lamp_rng_hash, state, j, num_samples, PRNG_LIGHT_U, &light_u, &light_v);
/* only sample triangle lights */
if (is_mesh_light && double_pdf) {
light_u = 0.5f * light_u;
}
LightSample ls ccl_optional_struct_init;
const int lamp = is_lamp ? i : -1;
light_sample(kg,
lamp,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls);
/* sample position on volume segment */
float rphase = path_branched_rng_1D(
kg, state->rng_hash, state, j, num_samples, PRNG_PHASE_CHANNEL);
float rscatter = path_branched_rng_1D(
kg, state->rng_hash, state, j, num_samples, PRNG_SCATTER_DISTANCE);
VolumeIntegrateResult result = kernel_volume_decoupled_scatter(
kg,
state,
ray,
sd,
&tp,
rphase,
rscatter,
segment,
(ls.t != FLT_MAX) ? &ls.P : NULL,
false);
if (result == VOLUME_PATH_SCATTERED) {
/* todo: split up light_sample so we don't have to call it again with new position */
if (light_sample(kg,
lamp,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls)) {
if (double_pdf) {
ls.pdf *= 2.0f;
}
/* sample random light */
float terminate = path_branched_rng_light_termination(
kg, state->rng_hash, state, j, num_samples);
has_emission = direct_emission(
kg, sd, emission_sd, &ls, state, &light_ray, &L_light, &is_lamp, terminate);
}
}
}
/* trace shadow ray */
float3 shadow;
const bool blocked = shadow_blocked(kg, sd, emission_sd, state, &light_ray, &shadow);
if (has_emission && !blocked) {
/* accumulate */
path_radiance_accum_light(
kg, L, state, tp * num_samples_inv, &L_light, shadow, num_samples_inv, is_lamp);
}
}
}
}

View File

@@ -66,6 +66,14 @@ KERNEL_TEX(KernelLightDistribution, __light_distribution)
KERNEL_TEX(KernelLight, __lights)
KERNEL_TEX(float2, __light_background_marginal_cdf)
KERNEL_TEX(float2, __light_background_conditional_cdf)
KERNEL_TEX(float4, __light_tree_nodes)
KERNEL_TEX(uint, __light_distribution_to_node)
KERNEL_TEX(uint, __lamp_to_distribution)
KERNEL_TEX(uint, __triangle_to_distribution)
KERNEL_TEX(float, __light_group_sample_cdf)
KERNEL_TEX(float, __light_group_sample_prob)
KERNEL_TEX(float4, __light_tree_leaf_emitters)
KERNEL_TEX(int, __leaf_to_first_emitter)
/* particles */
KERNEL_TEX(KernelParticle, __particles)

View File

@@ -626,6 +626,16 @@ enum PanoramaType {
PANORAMA_NUM_TYPES,
};
/* Light Sampling Group */
enum LightGroup {
LIGHTGROUP_TREE,
LIGHTGROUP_DISTANT,
LIGHTGROUP_BACKGROUND,
LIGHTGROUP_NUM,
};
/* Differential */
typedef struct differential3 {
@@ -939,6 +949,12 @@ typedef ccl_addr_space struct ccl_align(16) ShaderData
float3 N;
/* true geometric normal */
float3 Ng;
/* position used in light picking */
float3 P_pick;
/* normal or ray direction used in light picking */
float3 V_pick;
/* ray dist used for light picking */
float t_pick;
/* view/incoming direction */
float3 I;
/* shader id */
@@ -1320,13 +1336,22 @@ static_assert_align(KernelBackground, 16);
typedef struct KernelIntegrator {
/* emission */
int use_light_tree;
float splitting_threshold;
int use_direct_light;
int use_ambient_occlusion;
int num_distribution;
int num_all_lights;
int num_light_nodes;
int num_triangle_lights;
int num_distant_lights;
float inv_num_distant_lights;
float pdf_triangles;
float pdf_lights;
float pdf_inv_totarea;
float light_inv_rr_threshold;
int distant_lights_offset;
int background_light_index;
/* bounces */
int min_bounce;
@@ -1389,7 +1414,7 @@ typedef struct KernelIntegrator {
int max_closures;
int pad1, pad2;
int pad1;
} KernelIntegrator;
static_assert_align(KernelIntegrator, 16);
@@ -1533,8 +1558,10 @@ typedef struct KernelLight {
static_assert_align(KernelLight, 16);
typedef struct KernelLightDistribution {
float area;
float totarea;
int prim;
float pad1, pad2, pad3;
union {
struct {
int shader_flag;

View File

@@ -1125,6 +1125,11 @@ ccl_device VolumeIntegrateResult kernel_volume_decoupled_scatter(KernelGlobals *
/* move to new position */
sd->P = ray->P + sample_t * ray->D;
kernel_update_light_picking(sd, NULL);
sd->V_pick = ray->D;
sd->t_pick = ray->t;
return VOLUME_PATH_SCATTERED;
}
# endif /* __SPLIT_KERNEL */

View File

@@ -86,7 +86,16 @@ ccl_device void kernel_direct_lighting(KernelGlobals *kg,
float terminate = path_state_rng_light_termination(kg, state);
LightSample ls;
if (light_sample(kg, -1, light_u, light_v, sd->time, sd->P, state->bounce, &ls)) {
if (light_sample(kg,
-1,
light_u,
light_v,
sd->time,
sd->P_pick,
sd->V_pick,
sd->t_pick,
state->bounce,
&ls)) {
Ray light_ray;
light_ray.time = sd->time;

View File

@@ -63,6 +63,8 @@ ccl_device_noinline bool kernel_split_branched_path_volume_indirect_light_iter(K
VolumeIntegrateResult result = kernel_volume_integrate(
kg, ps, sd, &volume_ray, L, tp, step_size);
kernel_update_light_picking(sd, NULL);
# ifdef __VOLUME_SCATTER__
if (result == VOLUME_PATH_SCATTERED) {
/* direct lighting */

View File

@@ -59,7 +59,6 @@ ccl_device void kernel_lamp_emission(KernelGlobals *kg)
Ray ray = kernel_split_state.ray[ray_index];
ccl_global Intersection *isect = &kernel_split_state.isect[ray_index];
ShaderData *sd = kernel_split_sd(sd, ray_index);
kernel_path_lamp_emission(kg, state, &ray, throughput, isect, sd, L);
}
}

View File

@@ -30,6 +30,7 @@ set(SRC
integrator.cpp
jitter.cpp
light.cpp
light_tree.cpp
merge.cpp
mesh.cpp
mesh_displace.cpp

View File

@@ -77,6 +77,11 @@ NODE_DEFINE(Integrator)
SOCKET_BOOLEAN(sample_all_lights_direct, "Sample All Lights Direct", true);
SOCKET_BOOLEAN(sample_all_lights_indirect, "Sample All Lights Indirect", true);
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.05f);
SOCKET_BOOLEAN(use_light_tree, "Use light tree to optimize many light sampling", false);
SOCKET_FLOAT(splitting_threshold,
"Amount of lights to sample at a time, from one light at 0.0, to adaptively more "
"lights as needed, to all lights at 1.0",
0.0f);
static NodeEnum method_enum;
method_enum.insert("path", PATH);

View File

@@ -74,6 +74,8 @@ class Integrator : public Node {
bool sample_all_lights_direct;
bool sample_all_lights_indirect;
float light_sampling_threshold;
bool use_light_tree;
float splitting_threshold;
int adaptive_min_samples;
float adaptive_threshold;

View File

@@ -20,6 +20,7 @@
#include "render/film.h"
#include "render/graph.h"
#include "render/integrator.h"
#include "render/light_tree.h"
#include "render/mesh.h"
#include "render/nodes.h"
#include "render/object.h"
@@ -274,7 +275,89 @@ bool LightManager::object_usable_as_light(Object *object)
return false;
}
void LightManager::device_update_distribution(Device *,
float LightManager::distant_lights_energy(const Scene *scene, const vector<Primitive> &prims)
{
float luminance = 0.0f;
float3 emission;
foreach (Primitive prim, prims) {
if (prim.prim_id >= 0)
continue;
const Light *lamp = scene->lights[prim.lamp_id];
if (lamp->type != LIGHT_DISTANT)
continue;
/* get emission from shader */
bool is_constant_emission = lamp->shader->is_constant_emission(&emission);
if (!is_constant_emission)
continue; // TODO: Properly handle this case
luminance += scene->shader_manager->linear_rgb_to_gray(emission);
}
/* TODO: could project each bbox onto a disk outside the scene and sum up
* all the projected areas instead if this results in too high sampling */
/* get radius of bounding sphere of scene */
BoundBox scene_bbox = BoundBox::empty;
foreach (Object *object, scene->objects) {
// TODO: What about transforms?
scene_bbox.grow(object->bounds);
}
float radius_squared = len_squared(scene_bbox.max - scene_bbox.center());
return M_PI_F * radius_squared * luminance;
}
float LightManager::background_light_energy(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress,
const vector<Primitive> &prims)
{
/* compute energy for all background lights */
float average_luminance = 0.0f;
size_t num_pixels = 0;
/* find background lights */
foreach (Primitive prim, prims) {
if (prim.prim_id >= 0)
continue;
const Light *lamp = scene->lights[prim.lamp_id];
if (lamp->type != LIGHT_BACKGROUND)
continue;
vector<float3> pixels;
int2 res = get_background_map_resolution(lamp, scene);
shade_background_pixels(device, dscene, res.x, res.y, pixels, progress);
num_pixels += pixels.size();
for (int i = 0; i < pixels.size(); ++i) {
average_luminance += scene->shader_manager->linear_rgb_to_gray(pixels[i]);
}
break;
}
if (num_pixels == 0)
return 0.0f;
average_luminance /= (float)num_pixels;
/* get radius of bounding sphere of scene */
BoundBox scene_bbox = BoundBox::empty;
foreach (Object *object, scene->objects) {
// TODO: What about transforms?
scene_bbox.grow(object->bounds);
}
float radius_squared = len_squared(scene_bbox.max - scene_bbox.center());
return M_PI_F * radius_squared * average_luminance;
}
void LightManager::device_update_distribution(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress)
@@ -285,28 +368,35 @@ void LightManager::device_update_distribution(Device *,
size_t num_lights = 0;
size_t num_portals = 0;
size_t num_background_lights = 0;
size_t num_distant_lights = 0;
size_t num_triangles = 0;
bool background_mis = false;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
num_lights++;
}
if (light->is_portal) {
num_portals++;
}
}
/* The emissive_prims vector contains all emissive primitives in the scene,
* i.e., all mesh light triangles and all lamps. The order of the primitives
* in the vector is important since it has the same order as the
* light_distribution array.
*
* If using the light tree then the order is important since the light tree
* reordered the lights so lights in the same node are next to each other
* in memory.
*
* If NOT using the light tree then the order is important since during
* sampling we assume all triangles are first in the array. */
vector<Primitive> emissive_prims;
emissive_prims.reserve(scene->lights.size());
int object_id = 0;
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
if (!object_usable_as_light(object)) {
object_id++;
continue;
}
/* Count triangles. */
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->geometry);
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
@@ -316,35 +406,272 @@ void LightManager::device_update_distribution(Device *,
scene->default_surface;
if (shader->use_mis && shader->has_surface_emission) {
emissive_prims.push_back(Primitive(i + mesh->prim_offset, object_id));
num_triangles++;
}
}
object_id++;
}
/* light index is the index of this lamp in the device lights array*/
int light_index = 0;
/* light_id is the index of this lamp in the scene lights array */
int light_id = 0;
/* the light index of the background light */
int background_index = -1;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
emissive_prims.push_back(Primitive(~light_index, light_id));
num_lights++;
if (light->type == LIGHT_BACKGROUND) {
background_index = light_index;
}
light_index++;
}
if (light->is_portal) {
num_portals++;
}
light_id++;
}
size_t num_distribution = num_triangles + num_lights;
VLOG(1) << "Total " << num_distribution << " of light distribution primitives.";
if (scene->integrator->use_light_tree) {
/* create light tree */
double time_start = time_dt();
LightTree light_tree(emissive_prims, scene, 64);
VLOG(1) << "Light tree build time: " << time_dt() - time_start;
/* the light tree reorders the primitives so update emissive_prims */
const vector<Primitive> &ordered_prims = light_tree.get_primitives();
emissive_prims = ordered_prims;
if (progress.get_cancel())
return;
/* create the nodes to be used on the device */
const vector<CompactNode> &nodes = light_tree.get_nodes();
float4 *dnodes = dscene->light_tree_nodes.alloc(nodes.size() * LIGHT_TREE_NODE_SIZE);
/* convert each compact node into 4xfloat4
* 4 for energy, right_child_offset, prim_id, num_emitters
* 4 for bbox.min + bbox.max[0]
* 4 for bbox.max[1-2], theta_o, theta_e
* 4 for axis + energy variance */
size_t offset = 0;
size_t num_leaf_lights = 0;
foreach (CompactNode node, nodes) {
dnodes[offset].x = node.energy;
dnodes[offset].y = __int_as_float(node.right_child_offset);
dnodes[offset].z = __int_as_float(node.first_prim_offset);
dnodes[offset].w = __int_as_float(node.num_lights);
dnodes[offset + 1].x = node.bounds_s.min[0];
dnodes[offset + 1].y = node.bounds_s.min[1];
dnodes[offset + 1].z = node.bounds_s.min[2];
dnodes[offset + 1].w = node.bounds_s.max[0];
dnodes[offset + 2].x = node.bounds_s.max[1];
dnodes[offset + 2].y = node.bounds_s.max[2];
dnodes[offset + 2].z = node.bounds_o.theta_o;
dnodes[offset + 2].w = node.bounds_o.theta_e;
dnodes[offset + 3].x = node.bounds_o.axis[0];
dnodes[offset + 3].y = node.bounds_o.axis[1];
dnodes[offset + 3].z = node.bounds_o.axis[2];
dnodes[offset + 3].w = node.energy_variance;
offset += 4;
if ((node.right_child_offset == -1) && (node.num_lights > 1)) {
num_leaf_lights += node.num_lights;
}
}
/* store information needed for importance computations for each emitter
* in leaf nodes containing several emitters.
*
* each leaf node with several emitters stores relevant information about
* its emitters in the light_tree_leaf_emitters array. each such node
* also stores an offset into the light_tree_leaf_emitters array to where
* its first light is. this offset is stored in leaf_to_first_emitter.
*/
float4 *leaf_emitters = dscene->light_tree_leaf_emitters.alloc(num_leaf_lights * 3);
int *leaf_to_first_emitter = dscene->leaf_to_first_emitter.alloc(nodes.size());
offset = 0;
for (int i = 0; i < nodes.size(); ++i) {
const CompactNode &node = nodes[i];
/* only store this information for leaf nodes with several emitters */
if (!((node.right_child_offset == -1) && (node.num_lights > 1))) {
leaf_to_first_emitter[i] = -1;
continue;
}
leaf_to_first_emitter[i] = offset;
int start = node.first_prim_offset; // distribution id
int end = start + node.num_lights;
for (int j = start; j < end; ++j) {
/* todo: is there a better way than recalcing this? */
/* have getters for the light tree that just accesses build_data? */
BoundBox bbox = light_tree.compute_bbox(emissive_prims[j]);
Orientation bcone = light_tree.compute_bcone(emissive_prims[j]);
float energy = light_tree.compute_energy(emissive_prims[j]);
leaf_emitters[offset].x = bbox.min[0];
leaf_emitters[offset].y = bbox.min[1];
leaf_emitters[offset].z = bbox.min[2];
leaf_emitters[offset].w = bbox.max[0];
leaf_emitters[offset + 1].x = bbox.max[1];
leaf_emitters[offset + 1].y = bbox.max[2];
leaf_emitters[offset + 1].z = bcone.theta_o;
leaf_emitters[offset + 1].w = bcone.theta_e;
leaf_emitters[offset + 2].x = bcone.axis[0];
leaf_emitters[offset + 2].y = bcone.axis[1];
leaf_emitters[offset + 2].z = bcone.axis[2];
leaf_emitters[offset + 2].w = energy;
offset += 3;
}
}
if (progress.get_cancel())
return;
/* create CDF for distant lights, background lights and light tree */
float tree_energy = (nodes.size() > 0) ? nodes[0].energy : 0.0f;
float distant_energy = distant_lights_energy(scene, emissive_prims);
float background_energy = background_light_energy(
device, dscene, scene, progress, emissive_prims);
/* stores the function that the CDF will be generated from */
float3 func = make_float3(tree_energy, distant_energy, background_energy);
/* probs stores the probability of sampling each of the light groups.
* probs[0] corresponds to the probability to sample the tree, etc. */
float3 probs;
float4 cdf = make_float4(0.0f);
const int num_func_values = LIGHTGROUP_NUM;
const int num_cdf_values = num_func_values + 1;
for (int i = 1; i < num_cdf_values; ++i) {
cdf[i] = cdf[i - 1] + func[i - 1];
}
float func_integral = cdf[num_func_values];
if (func_integral == 0.0f) { // Sample uniformly if no energy
for (int i = 1; i < num_cdf_values; ++i) {
cdf[i] = (float)i / num_func_values;
}
probs = make_float3(1.0f / (float)num_func_values);
}
else {
for (int i = 0; i < num_cdf_values; ++i) {
cdf[i] /= func_integral;
}
probs = func / func_integral;
}
/* create and fill device arrays for the light group probabilities and CDF */
float *type_cdf = dscene->light_group_sample_cdf.alloc(num_cdf_values);
for (int i = 0; i < num_cdf_values; ++i) {
type_cdf[i] = cdf[i];
}
float *type_prob = dscene->light_group_sample_prob.alloc(num_func_values);
for (int i = 0; i < num_func_values; ++i) {
type_prob[i] = probs[i];
}
if (progress.get_cancel())
return;
/* find mapping between distribution_id to node_id, used for MIS */
uint *distribution_to_node = dscene->light_distribution_to_node.alloc(num_distribution);
for (int i = 0; i < nodes.size(); ++i) {
const CompactNode &node = nodes[i];
if (node.right_child_offset != -1)
continue; // Skip interior nodes
int start = node.first_prim_offset; // distribution id
int end = start + node.num_lights;
for (int j = start; j < end; ++j) {
distribution_to_node[j] = 4 * i;
}
}
}
if (progress.get_cancel())
return;
/* find mapping between lamp_id to distribution_id, used for MIS */
uint *lamp_to_distribution = dscene->lamp_to_distribution.alloc(num_lights);
for (int i = 0; i < emissive_prims.size(); ++i) {
const Primitive &prim = emissive_prims[i];
if (prim.prim_id >= 0)
continue; // Skip triangles
int lamp_id = -prim.prim_id - 1; // This should not use prim.lamp_id
lamp_to_distribution[lamp_id] = i;
}
/* find mapping between [triangle_id, object_id] to distribution_id, used for MIS */
/* tri_to_distr has the following format:
* [triangle_id0, object_id0, distrib_id0, triangle_id1,..]
* where [triangle_idX, object_idX] is mapped to distrib_idX. */
vector<std::tuple<uint, uint, uint>> tri_to_distr;
tri_to_distr.reserve(num_triangles);
for (int i = 0; i < emissive_prims.size(); ++i) {
const Primitive &prim = emissive_prims[i];
if (prim.prim_id < 0)
continue; // Skip lamps
tri_to_distr.push_back(std::make_tuple(prim.prim_id, prim.object_id, i));
}
std::sort(tri_to_distr.begin(), tri_to_distr.end());
assert(num_triangles == tri_to_distr.size());
uint *triangle_to_distribution = dscene->triangle_to_distribution.alloc(num_triangles * 3);
for (int i = 0; i < tri_to_distr.size(); ++i) {
triangle_to_distribution[3 * i] = std::get<0>(tri_to_distr[i]);
triangle_to_distribution[3 * i + 1] = std::get<1>(tri_to_distr[i]);
triangle_to_distribution[3 * i + 2] = std::get<2>(tri_to_distr[i]);
}
if (progress.get_cancel())
return;
/* create light distribution in same order as the emissive_prims */
/* emission area */
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
float totarea = 0.0f;
/* triangles */
size_t offset = 0;
int j = 0;
foreach (Object *object, scene->objects) {
assert(emissive_prims.size() == num_distribution);
/* create distributions for mesh lights */
foreach (Primitive prim, emissive_prims) {
if (progress.get_cancel())
return;
if (!object_usable_as_light(object)) {
j++;
if (prim.prim_id < 0) { // Early exit for lights
offset++;
continue;
}
const Object *object = scene->objects[prim.object_id];
/* Sum area. */
Mesh *mesh = static_cast<Mesh *>(object->geometry);
bool transform_applied = mesh->transform_applied;
Transform tfm = object->tfm;
int object_id = j;
int shader_flag = 0;
if (!(object->visibility & PATH_RAY_DIFFUSE)) {
@@ -364,39 +691,16 @@ void LightManager::device_update_distribution(Device *,
use_light_visibility = true;
}
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->shader[i];
Shader *shader = (shader_index < mesh->used_shaders.size()) ?
mesh->used_shaders[shader_index] :
scene->default_surface;
int triangle_id = prim.prim_id - mesh->prim_offset;
const float area = mesh->compute_triangle_area(triangle_id, object->tfm);
distribution[offset].area = area;
distribution[offset].totarea = totarea;
distribution[offset].prim = prim.prim_id;
distribution[offset].mesh_light.shader_flag = shader_flag;
distribution[offset].mesh_light.object_id = prim.object_id;
offset++;
if (shader->use_mis && shader->has_surface_emission) {
distribution[offset].totarea = totarea;
distribution[offset].prim = i + mesh->prim_offset;
distribution[offset].mesh_light.shader_flag = shader_flag;
distribution[offset].mesh_light.object_id = object_id;
offset++;
Mesh::Triangle t = mesh->get_triangle(i);
if (!t.valid(&mesh->verts[0])) {
continue;
}
float3 p1 = mesh->verts[t.v[0]];
float3 p2 = mesh->verts[t.v[1]];
float3 p3 = mesh->verts[t.v[2]];
if (!transform_applied) {
p1 = transform_point(&tfm, p1);
p2 = transform_point(&tfm, p2);
p3 = transform_point(&tfm, p3);
}
totarea += triangle_area(p1, p2, p3);
}
}
j++;
totarea += area;
}
float trianglearea = totarea;
@@ -404,19 +708,29 @@ void LightManager::device_update_distribution(Device *,
/* point lights */
float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f;
bool use_lamp_mis = false;
offset = 0;
int light_index = 0;
foreach (Light *light, scene->lights) {
if (!light->is_enabled)
/* create distributions for lights */
foreach (Primitive prim, emissive_prims) {
if (progress.get_cancel())
return;
if (prim.prim_id >= 0) { // Early exit for mesh lights
offset++;
continue;
}
const Light *light = scene->lights[prim.lamp_id];
distribution[offset].area = lightarea;
distribution[offset].totarea = totarea;
distribution[offset].prim = ~light_index;
distribution[offset].prim = prim.prim_id;
distribution[offset].lamp.pad = 1.0f;
distribution[offset].lamp.size = light->size;
totarea += lightarea;
if (light->type == LIGHT_DISTANT) {
num_distant_lights++;
use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
}
else if (light->type == LIGHT_POINT || light->type == LIGHT_SPOT) {
@@ -430,11 +744,11 @@ void LightManager::device_update_distribution(Device *,
background_mis |= light->use_mis;
}
light_index++;
offset++;
}
/* normalize cumulative distribution functions */
distribution[num_distribution].area = 0.0f;
distribution[num_distribution].totarea = totarea;
distribution[num_distribution].prim = 0.0f;
distribution[num_distribution].lamp.pad = 0.0f;
@@ -456,12 +770,34 @@ void LightManager::device_update_distribution(Device *,
kintegrator->use_direct_light = (totarea > 0.0f);
if (kintegrator->use_direct_light) {
/* update light tree */
kintegrator->use_light_tree = scene->integrator->use_light_tree;
kintegrator->splitting_threshold = scene->integrator->splitting_threshold;
dscene->light_tree_nodes.copy_to_device();
dscene->light_distribution_to_node.copy_to_device();
dscene->lamp_to_distribution.copy_to_device();
dscene->triangle_to_distribution.copy_to_device();
dscene->light_group_sample_cdf.copy_to_device();
dscene->light_group_sample_prob.copy_to_device();
dscene->leaf_to_first_emitter.copy_to_device();
dscene->light_tree_leaf_emitters.copy_to_device();
kintegrator->num_light_nodes = dscene->light_tree_nodes.size() / LIGHT_TREE_NODE_SIZE;
// TODO: Currently this is only the correct offset when using light tree
kintegrator->distant_lights_offset = num_distribution - num_distant_lights;
kintegrator->background_light_index = background_index;
/* number of emissives */
kintegrator->num_distribution = num_distribution;
kintegrator->num_triangle_lights = num_triangles;
kintegrator->num_distant_lights = num_distant_lights;
kintegrator->inv_num_distant_lights = 1.0f / (float)num_distant_lights;
/* precompute pdfs */
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
kintegrator->pdf_inv_totarea = 1.0f / totarea;
/* sample one, with 0.5 probability of light or triangle */
kintegrator->num_all_lights = num_lights;
@@ -509,10 +845,24 @@ void LightManager::device_update_distribution(Device *,
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
}
else {
dscene->light_group_sample_cdf.free();
dscene->light_group_sample_prob.free();
dscene->leaf_to_first_emitter.free();
dscene->light_tree_leaf_emitters.free();
dscene->light_distribution.free();
dscene->light_tree_nodes.free();
dscene->light_distribution_to_node.free();
dscene->lamp_to_distribution.free();
dscene->triangle_to_distribution.free();
kintegrator->pdf_inv_totarea = 0.0f;
kintegrator->num_light_nodes = 0;
kintegrator->num_triangle_lights = 0;
kintegrator->use_light_tree = false;
kintegrator->num_distribution = 0;
kintegrator->num_all_lights = 0;
kintegrator->num_distant_lights = 0;
kintegrator->inv_num_distant_lights = 0.0f;
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
kintegrator->use_lamp_mis = false;
@@ -521,8 +871,9 @@ void LightManager::device_update_distribution(Device *,
kbackground->portal_offset = 0;
kbackground->portal_weight = 0.0f;
kbackground->sun_weight = 0.0f;
kintegrator->distant_lights_offset = 0;
kintegrator->background_light_index = 0;
kbackground->map_weight = 0.0f;
kfilm->pass_shadow_scale = 1.0f;
}
}
@@ -662,14 +1013,6 @@ void LightManager::device_update_background(Device *device,
/* If the resolution isn't set manually, try to find an environment texture. */
if (res.x == 0) {
res = environment_res;
if (res.x > 0 && res.y > 0) {
VLOG(2) << "Automatically set World MIS resolution to " << res.x << " by " << res.y << "\n";
}
}
/* If it's still unknown, just use the default. */
if (res.x == 0 || res.y == 0) {
res = make_int2(1024, 512);
VLOG(2) << "Setting World MIS resolution to default\n";
}
kbackground->map_res_x = res.x;
kbackground->map_res_y = res.y;
@@ -1000,11 +1343,19 @@ void LightManager::device_update(Device *device,
void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background)
{
dscene->light_distribution.free();
dscene->light_tree_nodes.free();
dscene->light_distribution_to_node.free();
dscene->lamp_to_distribution.free();
dscene->triangle_to_distribution.free();
dscene->lights.free();
if (free_background) {
dscene->light_background_marginal_cdf.free();
dscene->light_background_conditional_cdf.free();
}
dscene->light_group_sample_prob.free();
dscene->light_group_sample_cdf.free();
dscene->leaf_to_first_emitter.free();
dscene->light_tree_leaf_emitters.free();
dscene->ies_lights.free();
}
@@ -1131,4 +1482,32 @@ void LightManager::device_update_ies(DeviceScene *dscene)
}
}
int2 LightManager::get_background_map_resolution(const Light *background_light, const Scene *scene)
{
int2 res = make_int2(background_light->map_resolution, background_light->map_resolution / 2);
/* If the resolution isn't set manually, try to find an environment texture. */
if (res.x == 0) {
Shader *shader = (scene->background->shader) ? scene->background->shader :
scene->default_background;
foreach (ShaderNode *node, shader->graph->nodes) {
if (node->type == EnvironmentTextureNode::node_type) {
EnvironmentTextureNode *env = (EnvironmentTextureNode *)node;
ImageMetaData metadata = env->handle.metadata();
res.x = max(res.x, metadata.width);
res.y = max(res.y, metadata.height);
}
}
if (res.x > 0 && res.y > 0) {
VLOG(2) << "Automatically set World MIS resolution to " << res.x << " by " << res.y << "\n";
}
}
/* If it's still unknown, just use the default. */
if (res.x == 0 || res.y == 0) {
res = make_int2(1024, 512);
VLOG(2) << "Setting World MIS resolution to default\n";
}
return res;
}
CCL_NAMESPACE_END

View File

@@ -31,6 +31,7 @@ CCL_NAMESPACE_BEGIN
class Device;
class DeviceScene;
class Object;
class Primitive;
class Progress;
class Scene;
class Shader;
@@ -128,6 +129,18 @@ class LightManager {
/* Check whether light manager can use the object as a light-emissive. */
bool object_usable_as_light(Object *object);
/* compute energy of background lights */
float background_light_energy(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress,
const vector<Primitive> &prims);
/* compute energy of distant lights */
float distant_lights_energy(const Scene *scene, const vector<Primitive> &prims);
int2 get_background_map_resolution(const Light *background_light, const Scene *scene);
struct IESSlot {
IESFile ies;
uint hash;

View File

@@ -0,0 +1,601 @@
/*
* Copyright 2011-2018 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render/light_tree.h"
#include "render/light.h"
#include "render/mesh.h"
#include "render/object.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
CCL_NAMESPACE_BEGIN
LightTree::LightTree(const vector<Primitive> &prims_,
Scene *scene_,
const uint max_lights_in_node_)
: max_lights_in_node(max_lights_in_node_), scene(scene_)
{
if (prims_.empty())
return;
/* background and distant lights are not added to the light tree and are
* considered seperately. so here all primitives except background and
* distant lights are moved into a local primitives array */
primitives.reserve(prims_.size());
vector<Primitive> distant_lights;
vector<Primitive> background_lights;
foreach (Primitive prim, prims_) {
/* put background and distant lights into their own arrays */
if (prim.prim_id < 0) {
const Light *lamp = scene->lights[prim.lamp_id];
if (lamp->type == LIGHT_DISTANT) {
distant_lights.push_back(prim);
continue;
}
else if (lamp->type == LIGHT_BACKGROUND) {
background_lights.push_back(prim);
continue;
}
}
primitives.push_back(prim);
}
/* initialize build_data array that stores the energy and spatial and
* orientation bounds for each light. */
vector<BVHPrimitiveInfo> build_data;
build_data.reserve(primitives.size());
for (int i = 0; i < primitives.size(); ++i) {
BoundBox bbox = compute_bbox(primitives[i]);
Orientation bcone = compute_bcone(primitives[i]);
float energy = compute_energy(primitives[i]);
build_data.push_back(BVHPrimitiveInfo(i, bbox, bcone, energy));
}
/* recursively build BVH tree */
uint total_nodes = 0;
vector<Primitive> ordered_prims;
ordered_prims.reserve(primitives.size());
BVHBuildNode *root = recursive_build(
0, primitives.size(), build_data, total_nodes, ordered_prims);
/* order the primitives array so lights belonging to the same node are
* next to each other */
primitives.swap(ordered_prims);
ordered_prims.clear();
build_data.clear();
/* add background lights to the primitives array */
for (int i = 0; i < background_lights.size(); ++i) {
primitives.push_back(background_lights[i]);
}
/* add distant lights to the end of primitives array */
for (int i = 0; i < distant_lights.size(); ++i) {
primitives.push_back(distant_lights[i]);
}
VLOG(1) << "Total BVH nodes: " << total_nodes;
if (!root)
return;
/* convert to linear representation of the tree */
nodes.resize(total_nodes);
int offset = 0;
flattenBVHTree(*root, offset);
assert(offset == total_nodes);
}
int LightTree::flattenBVHTree(const BVHBuildNode &node, int &offset)
{
CompactNode &compact_node = nodes[offset];
compact_node.bounds_s = node.bbox;
compact_node.bounds_o = node.bcone;
int my_offset = offset++;
if (node.is_leaf) {
/* create leaf node */
assert(!node.children[0] && !node.children[1]);
compact_node.energy = node.energy;
compact_node.energy_variance = node.energy_variance;
compact_node.first_prim_offset = node.first_prim_offset;
compact_node.num_lights = node.num_lights;
}
else {
/* create interior compact node */
compact_node.num_lights = node.num_lights;
compact_node.energy = node.energy;
compact_node.energy_variance = node.energy_variance;
assert(node.children[0] && node.children[1]);
flattenBVHTree(*node.children[0], offset);
compact_node.right_child_offset = flattenBVHTree(*node.children[1], offset);
compact_node.energy = node.energy;
}
return my_offset;
}
BoundBox LightTree::compute_bbox(const Primitive &prim)
{
BoundBox bbox = BoundBox::empty;
if (prim.prim_id >= 0) {
/* extract bounding box from emissive triangle */
const Object *object = scene->objects[prim.object_id];
const Mesh *mesh = (Mesh *)object->geometry;
const int triangle_id = prim.prim_id - mesh->prim_offset;
const Mesh::Triangle triangle = mesh->get_triangle(triangle_id);
float3 p0 = mesh->verts[triangle.v[0]];
float3 p1 = mesh->verts[triangle.v[1]];
float3 p2 = mesh->verts[triangle.v[2]];
/* instanced mesh lights have not applied their transform at this point.
* in this case, these points have to be transformed to get the proper
* spatial bound. */
if (!mesh->transform_applied) {
const Transform &tfm = object->tfm;
p0 = transform_point(&tfm, p0);
p1 = transform_point(&tfm, p1);
p2 = transform_point(&tfm, p2);
}
bbox.grow(p0);
bbox.grow(p1);
bbox.grow(p2);
}
else {
/* extract bounding box from lamp based on light type */
Light *lamp = scene->lights[prim.lamp_id];
if (lamp->type == LIGHT_POINT || lamp->type == LIGHT_SPOT) {
float radius = lamp->size;
bbox.grow(lamp->co + make_float3(radius));
bbox.grow(lamp->co - make_float3(radius));
}
else if (lamp->type == LIGHT_AREA) {
const float3 center = lamp->co;
const float3 half_axisu = 0.5f * lamp->axisu * (lamp->sizeu * lamp->size);
const float3 half_axisv = 0.5f * lamp->axisv * (lamp->sizev * lamp->size);
const float3 p0 = center - half_axisu - half_axisv;
const float3 p1 = center - half_axisu + half_axisv;
const float3 p2 = center + half_axisu - half_axisv;
const float3 p3 = center + half_axisu + half_axisv;
bbox.grow(p0);
bbox.grow(p1);
bbox.grow(p2);
bbox.grow(p3);
}
else {
/* LIGHT_DISTANT and LIGHT_BACKGROUND are handled separately */
assert(false);
}
}
return bbox;
}
Orientation LightTree::compute_bcone(const Primitive &prim)
{
Orientation bcone;
if (prim.prim_id >= 0) {
/* extract bounding cone from emissive triangle */
const Object *object = scene->objects[prim.object_id];
const Mesh *mesh = (Mesh *)object->geometry;
const int triangle_id = prim.prim_id - mesh->prim_offset;
const Mesh::Triangle triangle = mesh->get_triangle(triangle_id);
float3 p0 = mesh->verts[triangle.v[0]];
float3 p1 = mesh->verts[triangle.v[1]];
float3 p2 = mesh->verts[triangle.v[2]];
if (!mesh->transform_applied) {
const Transform &tfm = object->tfm;
p0 = transform_point(&tfm, p0);
p1 = transform_point(&tfm, p1);
p2 = transform_point(&tfm, p2);
}
float3 normal = make_float3(1.0f, 0.0f, 0.0f);
const float3 norm = cross(p1 - p0, p2 - p0);
const float normlen = len(norm);
if (normlen != 0.0f) {
normal = norm / normlen;
}
bcone.axis = normal;
bcone.theta_o = 0.0f;
bcone.theta_e = M_PI_2_F;
}
else {
Light *lamp = scene->lights[prim.lamp_id];
bcone.axis = lamp->dir / len(lamp->dir);
if (lamp->type == LIGHT_POINT) {
bcone.theta_o = M_PI_F;
bcone.theta_e = M_PI_2_F;
}
else if (lamp->type == LIGHT_SPOT) {
bcone.theta_o = 0;
bcone.theta_e = lamp->spot_angle * 0.5f;
}
else if (lamp->type == LIGHT_AREA) {
bcone.theta_o = 0;
bcone.theta_e = M_PI_2_F;
}
}
return bcone;
}
float LightTree::compute_energy(const Primitive &prim)
{
float3 emission = make_float3(0.0f);
Shader *shader = NULL;
if (prim.prim_id >= 0) {
/* extract shader from emissive triangle */
const Object *object = scene->objects[prim.object_id];
const Mesh *mesh = (Mesh *)object->geometry;
const int triangle_id = prim.prim_id - mesh->prim_offset;
int shader_index = mesh->shader[triangle_id];
shader = mesh->used_shaders.at(shader_index);
/* get emission from shader */
bool is_constant_emission = shader->is_constant_emission(&emission);
if (!is_constant_emission) {
emission = make_float3(1.0f);
}
const Transform &tfm = scene->objects[prim.object_id]->tfm;
float area = mesh->compute_triangle_area(triangle_id, tfm);
emission *= area * 4;
}
else {
const Light *light = scene->lights[prim.lamp_id];
emission = light->strength;
/* calculate the max emission in a single direction. */
if (light->type == LIGHT_POINT) {
emission /= M_PI_F;
}
else if (light->type == LIGHT_SPOT) {
emission /= M_PI_F;
}
else if (light->type == LIGHT_AREA) {
}
else {
/* LIGHT_DISTANT and LIGHT_BACKGROUND are handled separately */
assert(false);
}
}
return scene->shader_manager->linear_rgb_to_gray(emission);
}
Orientation LightTree::combine_bounding_cones(const vector<Orientation> &bcones)
{
if (bcones.size() == 0) {
return Orientation();
}
else if (bcones.size() == 1) {
return bcones[0];
}
Orientation cone = bcones[0];
for (int i = 1; i < bcones.size(); ++i) {
cone = cone_union(cone, bcones[i]);
}
return cone;
}
/* Algorithm 1 */
Orientation LightTree::cone_union(const Orientation &cone1, const Orientation &cone2)
{
const Orientation *a = &cone1;
const Orientation *b = &cone2;
if (b->theta_o > a->theta_o) {
a = &cone2;
b = &cone1;
}
float theta_d = safe_acosf(dot(a->axis, b->axis));
float theta_e = fmaxf(a->theta_e, b->theta_e);
if (fminf(theta_d + b->theta_o, M_PI_F) <= a->theta_o) {
return Orientation(a->axis, a->theta_o, theta_e);
}
float theta_o = (a->theta_o + theta_d + b->theta_o) * 0.5f;
if (M_PI_F <= theta_o) {
return Orientation(a->axis, M_PI_F, theta_e);
}
float theta_r = theta_o - a->theta_o;
float3 axis = rotate_around_axis(a->axis, cross(a->axis, b->axis), theta_r);
axis = normalize(axis);
return Orientation(axis, theta_o, theta_e);
}
float LightTree::calculate_cone_measure(const Orientation &bcone)
{
/* eq. 1 */
float theta_w = fminf(bcone.theta_o + bcone.theta_e, M_PI_F);
return M_2PI_F *
(1.0f - cosf(bcone.theta_o) + 0.5f * (theta_w - bcone.theta_o) * sinf(bcone.theta_o) +
0.25f * cosf(bcone.theta_o) - 0.25f * cosf(bcone.theta_o - 2.0f * theta_w));
}
void LightTree::split_saoh(const BoundBox &centroid_bbox,
const vector<BVHPrimitiveInfo> &build_data,
const int start,
const int end,
const int num_buckets,
const float node_M_Omega,
const BoundBox &node_bbox,
float &min_cost,
int &min_dim,
int &min_bucket)
{
struct BucketInfo {
BucketInfo() : count(0), energy(0.0f)
{
bounds = BoundBox::empty;
}
int count;
float energy; // total energy
BoundBox bounds; // bounds of all primitives
Orientation bcone;
};
min_cost = std::numeric_limits<float>::max();
min_bucket = -1;
const float extent_max = max3(centroid_bbox.size());
for (int dim = 0; dim < 3; ++dim) {
vector<BucketInfo> buckets(num_buckets);
vector<vector<Orientation>> bucket_bcones(num_buckets);
/* calculate total energy in each bucket and a bbox of it */
const float extent = centroid_bbox.max[dim] - centroid_bbox.min[dim];
if (extent == 0.0f) { // All dims cannot be zero
continue;
}
const float extent_inv = 1.0f / extent;
for (unsigned int i = start; i < end; ++i) {
int bucket_id = (int)((float)num_buckets *
(build_data[i].centroid[dim] - centroid_bbox.min[dim]) * extent_inv);
if (bucket_id == num_buckets)
bucket_id = num_buckets - 1;
buckets[bucket_id].count++;
buckets[bucket_id].energy += build_data[i].energy;
buckets[bucket_id].bounds.grow(build_data[i].bbox);
bucket_bcones[bucket_id].push_back(build_data[i].bcone);
}
for (unsigned int i = 0; i < num_buckets; ++i) {
if (buckets[i].count != 0) {
buckets[i].bcone = combine_bounding_cones(bucket_bcones[i]);
}
}
/* compute costs for splitting at bucket boundaries */
vector<float> cost(num_buckets - 1);
BoundBox bbox_L, bbox_R;
float energy_L, energy_R;
vector<Orientation> bcones_L, bcones_R;
for (int i = 0; i < num_buckets - 1; ++i) {
bbox_L = BoundBox::empty;
bbox_R = BoundBox::empty;
energy_L = 0;
energy_R = 0;
bcones_L.clear();
bcones_R.clear();
/* L corresponds to all buckets up to and including i */
for (int j = 0; j <= i; ++j) {
if (buckets[j].count != 0) {
energy_L += buckets[j].energy;
bbox_L.grow(buckets[j].bounds);
bcones_L.push_back(buckets[j].bcone);
}
}
/* R corresponds to bucket i+1 and all after */
for (int j = i + 1; j < num_buckets; ++j) {
if (buckets[j].count != 0) {
energy_R += buckets[j].energy;
bbox_R.grow(buckets[j].bounds);
bcones_R.push_back(buckets[j].bcone);
}
}
/* eq. 2 */
const Orientation bcone_L = combine_bounding_cones(bcones_L);
const Orientation bcone_R = combine_bounding_cones(bcones_R);
const float M_Omega_L = calculate_cone_measure(bcone_L);
const float M_Omega_R = calculate_cone_measure(bcone_R);
const float K = extent_max * extent_inv;
cost[i] = K * (energy_L * M_Omega_L * bbox_L.area() + energy_R * M_Omega_R * bbox_R.area()) /
(node_M_Omega * node_bbox.area());
}
/* update minimum cost, dim and bucket */
for (int i = 0; i < num_buckets - 1; ++i) {
if (cost[i] < min_cost) {
min_cost = cost[i];
min_dim = dim;
min_bucket = i;
}
}
}
}
BVHBuildNode *LightTree::recursive_build(const uint start,
const uint end,
vector<BVHPrimitiveInfo> &build_data,
uint &total_nodes,
vector<Primitive> &ordered_prims)
{
if (build_data.size() == 0)
return NULL;
total_nodes++;
BVHBuildNode *node = new BVHBuildNode();
/* compute bounds and energy for all emissive primitives in node */
BoundBox node_bbox = BoundBox::empty;
vector<Orientation> bcones;
bcones.reserve(end - start);
double node_energy = 0.0;
double node_energy_sum_squared = 0.0;
uint num_lights = end - start;
for (unsigned int i = start; i < end; ++i) {
const BVHPrimitiveInfo &light = build_data.at(i);
node_bbox.grow(light.bbox);
bcones.push_back(light.bcone);
double energy = (double)light.energy;
node_energy += energy;
node_energy_sum_squared += energy * energy;
}
/* pre-calculate energy variance for the splitting heuristic */
double node_energy_mean = node_energy / (double)num_lights;
double node_energy_variance = node_energy_sum_squared / (double)num_lights - // E[e^2]
node_energy_mean * node_energy_mean; // E[e]^2
node_energy_variance = max(node_energy_variance, 0.0);
Orientation node_bcone = combine_bounding_cones(bcones);
bcones.clear();
const float node_M_Omega = calculate_cone_measure(node_bcone);
if (num_lights == 1) {
/* create leaf */
int first_prim_offset = ordered_prims.size();
int prim = build_data.at(start).primitive_offset;
ordered_prims.push_back(primitives.at(prim));
node->init_leaf(
first_prim_offset, num_lights, node_bbox, node_bcone, node_energy, node_energy_variance);
return node;
}
else {
/* compute spatial bound for primitive centroids */
BoundBox centroid_bbox = BoundBox::empty;
for (unsigned int i = start; i < end; ++i) {
centroid_bbox.grow(build_data.at(i).centroid);
}
/* find dimension of bounding box with maximum extent */
float3 diag = centroid_bbox.size();
int max_dim;
if (diag[0] > diag[1] && diag[0] > diag[2]) {
max_dim = 0;
}
else if (diag[1] > diag[2]) {
max_dim = 1;
}
else {
max_dim = 2;
}
/* checks special case if all lights are in the same place */
if (centroid_bbox.max[max_dim] == centroid_bbox.min[max_dim]) {
/* create leaf */
int first_prim_offset = ordered_prims.size();
for (int i = start; i < end; ++i) {
int prim = build_data.at(i).primitive_offset;
ordered_prims.push_back(primitives.at(prim));
}
node->init_leaf(
first_prim_offset, num_lights, node_bbox, node_bcone, node_energy, node_energy_variance);
return node;
}
else {
/* find dimension and bucket with smallest SAOH cost */
const int num_buckets = 12;
float min_cost;
int min_dim, min_bucket;
split_saoh(centroid_bbox,
build_data,
start,
end,
num_buckets,
node_M_Omega,
node_bbox,
min_cost,
min_dim,
min_bucket);
assert(min_dim != -1);
int mid = 0;
if (num_lights > max_lights_in_node || min_cost < (float)node_energy) {
/* partition primitives */
BVHPrimitiveInfo *mid_ptr = std::partition(
&build_data[start],
&build_data[end - 1] + 1,
CompareToBucket(min_bucket, num_buckets, min_dim, centroid_bbox));
mid = mid_ptr - &build_data[0];
}
else {
/* create leaf */
int first_prim_offset = ordered_prims.size();
for (int i = start; i < end; ++i) {
int prim = build_data.at(i).primitive_offset;
ordered_prims.push_back(primitives.at(prim));
}
node->init_leaf(first_prim_offset,
num_lights,
node_bbox,
node_bcone,
node_energy,
node_energy_variance);
return node;
}
/* build children */
BVHBuildNode *left = recursive_build(start, mid, build_data, total_nodes, ordered_prims);
BVHBuildNode *right = recursive_build(mid, end, build_data, total_nodes, ordered_prims);
node->init_interior(left, right, node_bcone, num_lights, node_energy, node_energy_variance);
}
}
return node;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,356 @@
/*
* Copyright 2011-2018 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __LIGHT_TREE_H__
#define __LIGHT_TREE_H__
#include "util/util_boundbox.h"
CCL_NAMESPACE_BEGIN
class Light;
class Object;
class Scene;
#define LIGHT_TREE_NODE_SIZE 4
/* Data structure to represent orientation bounds. It consists of two bounding
* cones represented by a direction(axis) and two angles out from this axis.
* This can be thought of as two cones.
*/
struct Orientation {
Orientation()
{
axis = make_float3(0.0f, 0.0f, 0.0f);
theta_o = 0;
theta_e = 0;
}
Orientation(const float3 &a, float o, float e) : axis(a), theta_o(o), theta_e(e)
{
}
/* orientation/direction of the cones */
float3 axis;
/* angle bounding light orientations */
float theta_o;
/* angle bounding the directions light can be emitted in */
float theta_e;
};
/* Temporary data structure for nodes during construction.
* After the construction is complete a final step converts the tree consisting
* of these nodes into a tree consisting of CompactNode:s. */
struct BVHBuildNode {
BVHBuildNode()
{
children[0] = children[1] = NULL;
bbox = BoundBox::empty;
}
/* initializes this node as a leaf node */
void init_leaf(
uint first, uint n, const BoundBox &b, const Orientation &c, double e, double e_var)
{
first_prim_offset = first;
num_lights = n;
bbox = b;
bcone = c;
energy = (float)e;
energy_variance = (float)e_var;
is_leaf = true;
}
/* initializes this node as an interior node */
void init_interior(
BVHBuildNode *c0, BVHBuildNode *c1, const Orientation &c, uint n, double e, double e_var)
{
bbox = merge(c0->bbox, c1->bbox);
bcone = c;
children[0] = c0;
children[1] = c1;
num_lights = n;
energy = (float)e;
energy_variance = (float)e_var;
is_leaf = false;
}
/* spatial and orientation bounds */
BoundBox bbox;
Orientation bcone;
/* total energy and energy variance for the lights in the node */
float energy, energy_variance;
/* pointers to the two children */
BVHBuildNode *children[2];
/* each leaf node contains one or more lights. lights that are contained in
* the same node are stored next to each other in the ordered primitives
* array. this offset points to the first of these lights. num_lights below
* can be used to find the last light for this node */
uint first_prim_offset;
/* total number of lights contained in this node */
uint num_lights;
/* if this node is a leaf or not */
bool is_leaf;
};
// TODO: Have this struct in kernel_types.h instead?
/* A more memory efficient representation of BVHBuildNode above. This is the
* structure of the nodes on the device. */
struct CompactNode {
CompactNode()
: right_child_offset(-1),
first_prim_offset(-1),
num_lights(-1),
bounds_s(BoundBox::empty),
energy(0.0f),
energy_variance(0.0f)
{
bounds_o.axis = make_float3(0.0f);
bounds_o.theta_o = 0.0f;
bounds_o.theta_e = 0.0f;
}
/* All compact nodes are stored in a single array. interior nodes can find
* their two child nodes as follows:
* - the left child node is directly after its parent in the nodes array
* - the right child node is at the offset below in the nodes array.
*
* This offset is default initialized to -1 and will only change if this is
* and interior node. this therefore used to see if a node is a leaf/interior
* node as well. */
int right_child_offset;
/* see comment in BVHBuildNode for the variable with the same name */
int first_prim_offset;
/* total number of lights contained in this node */
int num_lights;
/* spatial and orientation bounds */
BoundBox bounds_s;
Orientation bounds_o;
/* total energy and energy variance for the lights in the node */
float energy, energy_variance;
};
/* Helper struct that is only used during the construction of the tree */
struct BVHPrimitiveInfo {
BVHPrimitiveInfo()
{
bbox = BoundBox::empty;
}
BVHPrimitiveInfo(uint offset, const BoundBox &bounds, const Orientation &oBounds, float e)
: primitive_offset(offset), bbox(bounds), centroid(bounds.center()), energy(e)
{
bcone.axis = oBounds.axis;
bcone.theta_o = oBounds.theta_o;
bcone.theta_e = oBounds.theta_e;
}
/* this primitives offset into the unordered primtives array */
uint primitive_offset;
/* spatial and orientation bounds */
BoundBox bbox;
float3 centroid;
Orientation bcone;
/* total energy of this emissive primitive */
float energy;
};
/* A custom pointer struct that points to an emissive triangle or a lamp. */
class Primitive {
public:
/* If prim_id >= 0 then the primitive is a triangle and prim_id is a global
* triangle index.
* If prim_id < 0 then the primitive is a lamp and -prim_id-1 is an index
* into the lights array on the device. */
int prim_id;
union {
/* which object the triangle belongs to */
int object_id;
/* index for this lamp in the scene->lights array */
int lamp_id;
};
Primitive(int prim, int object) : prim_id(prim), object_id(object)
{
}
};
/* Compare operator that returns true if the given light is in a lower
* bucket than a given split_bucket. This is used to partition lights into lights
* for the left and right child during tree construction. */
struct CompareToBucket {
CompareToBucket(int split, int num, int d, const BoundBox &b) : centroid_bbox(b)
{
split_bucket = split;
num_buckets = num;
dim = d;
inv_extent = 1.0f / (centroid_bbox.max[dim] - centroid_bbox.min[dim]);
}
bool operator()(const BVHPrimitiveInfo &p) const
{
int bucket_id = (int)((float)num_buckets * (p.centroid[dim] - centroid_bbox.min[dim]) *
inv_extent);
if (bucket_id == num_buckets) {
bucket_id = num_buckets - 1;
}
return bucket_id <= split_bucket;
}
/* everything lower or equal to the split_bucket is considered to be in one
* child and everything above will be considered to belong to the other. */
int split_bucket;
/* the total number of buckets that are considered for this dimension(dim) */
int num_buckets;
/* the construction creates candidate splits along the three dimensions.
* this variable stores which dimension is currently being split along.*/
int dim;
/* storing the inverse extend of the bounding box along the current
* dimension to only have to do the division once instead of everytime the
* operator() is called. */
float inv_extent;
/* bound for the centroids of all lights of the current node being split */
const BoundBox &centroid_bbox;
};
/* This class takes a set of lights as input and organizes them into a light
* hierarchy. This hierarchy is represented as a Bounding Volume Hierarchy(BVH).
* This is the process to acheive this:
* 1. For each given light, important information is gathered
* - Bounding box of the light
* - Bounding cones of the light
* - The energy of the light
* This first calculated and then stored as BVHPrimitiveInfo for each light.
* 2. A top-down recursive build algorithm creates a BVH consisting of
* BVHBuildNode:s which each are allocated randomly on the heap with new.
* This step also reorders the given array of lights such that lights
* belonging to the same node are next to each other in the primitives array.
* 3. A final step converts this BVH into a more memory efficient layout where
* each BVHBuildNode is converted to a CompactNode and all of these nodes
* are placed next to each other in memory in a single nodes array.
*
* This structure is based on PBRTs geometry BVH implementation.
**/
class LightTree {
public:
LightTree(const vector<Primitive> &prims_, Scene *scene_, const uint max_lights_in_node_);
/* returns the ordered emissive primitives */
const vector<Primitive> &get_primitives() const
{
return primitives;
}
/* returns the array of nodes */
const vector<CompactNode> &get_nodes() const
{
return nodes;
}
/* computes the bounding box for the given light */
BoundBox compute_bbox(const Primitive &prim);
/* computes the orientation bounds for the given light. */
Orientation compute_bcone(const Primitive &prim);
/* computes the emitted energy for the given light. this is done by
* integrating the constant emission over the angles of the sphere it emits
* light in. */
float compute_energy(const Primitive &prim);
private:
/* the top-down recursive build algorithm mentioned in step 2 above */
BVHBuildNode *recursive_build(const uint start,
const uint end,
vector<BVHPrimitiveInfo> &build_data,
uint &total_nodes,
vector<Primitive> &ordered_prims);
/* returns the union of two orientation bounds and returns the result */
Orientation cone_union(const Orientation &a, const Orientation &b);
/* returns the union of several orientation bounds */
Orientation combine_bounding_cones(const vector<Orientation> &bcones);
/* calculates the cone measure in the surface area orientation heuristic */
float calculate_cone_measure(const Orientation &bcone);
/* takes a node and the lights contained in it as input and returns a way to
* split the node into two child nodes. This is done as follows:
* 1. A bounding box of all lights centroid is constructed
* 2. A set of candidate splits(proposed left and right child nodes) are
* created.
* - This is done by partitioning the bounding box into two regions.
* All lights in the same region belongs to the same child node. This
* is done for several partions of the bounding box.
* 3. Each such candidate is evaluated using the Surface Area Orientation
* Heuristic(SAOH).
* 4. The candidate split with the minimum cost(heuristic) is returned */
void split_saoh(const BoundBox &centroid_bbox,
const vector<BVHPrimitiveInfo> &build_data,
const int start,
const int end,
const int num_buckets,
const float node_M_Omega,
const BoundBox &node_bbox,
float &min_cost,
int &min_dim,
int &min_bucket);
/* this method performs step 3 above. */
int flattenBVHTree(const BVHBuildNode &node, int &offset);
/* contains all the lights in the scene. when the constructor has finished,
* these will be ordered such that lights belonging to the same node will be
* next to each other in this array. */
vector<Primitive> primitives;
/* the maximum number of allowed lights in each leaf node */
uint max_lights_in_node;
/* pointer to the scene. this is used to access the objects, the lights and
* the shader manager of the scene.*/
Scene *scene;
/* the nodes of the light hierarchy */
vector<CompactNode> nodes;
};
CCL_NAMESPACE_END
#endif // __LIGHT_TREE_H__

View File

@@ -86,6 +86,25 @@ class Mesh : public Geometry {
return tri;
}
float compute_triangle_area(size_t i, const Transform &tfm) const
{
Mesh::Triangle t = get_triangle(i);
if (!t.valid(&verts[0])) {
return 0.0f;
}
float3 p1 = verts[t.v[0]];
float3 p2 = verts[t.v[1]];
float3 p3 = verts[t.v[2]];
if (!transform_applied) {
p1 = transform_point(&tfm, p1);
p2 = transform_point(&tfm, p2);
p3 = transform_point(&tfm, p3);
}
return triangle_area(p1, p2, p3);
}
size_t num_triangles() const
{
return triangles.size() / 3;

View File

@@ -159,7 +159,7 @@ class VolumeMeshBuilder {
bool empty_grid() const;
#ifdef WITH_OPENVDB
template <typename GridType>
template<typename GridType>
void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
{
typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast<GridType>(grid);
@@ -189,7 +189,9 @@ VolumeMeshBuilder::VolumeMeshBuilder()
}
#ifdef WITH_OPENVDB
void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid,
bool do_clipping,
float volume_clipping)
{
/* set the transform of our grid from the first one */
if (first_grid) {
@@ -415,22 +417,38 @@ static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_textu
typename GridType::Ptr sparse = GridType::create(ValueType(0.0f));
openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping));
/* copyFromDense will remove any leaf node that contains constant data and replace it with a tile,
* however, we need to preserve the leaves in order to generate the mesh, so revoxelize the leaves
* that were pruned. This should not affect areas that were skipped due to the volume_clipping parameter. */
/* copyFromDense will remove any leaf node that contains constant data and replace it with a
* tile, however, we need to preserve the leaves in order to generate the mesh, so revoxelize the
* leaves that were pruned. This should not affect areas that were skipped due to the
* volume_clipping parameter. */
sparse->tree().voxelizeActiveTiles();
/* Compute index to world matrix. */
float3 voxel_size = make_float3(1.0f / image_memory->data_width, 1.0f / image_memory->data_height, 1.0f / image_memory->data_depth);
float3 voxel_size = make_float3(1.0f / image_memory->data_width,
1.0f / image_memory->data_height,
1.0f / image_memory->data_depth);
transform_3d = transform_inverse(transform_3d);
openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]), 0.0, 0.0, 0.0,
0.0, (double)(voxel_size.y * transform_3d[1][1]), 0.0, 0.0,
0.0, 0.0, (double)(voxel_size.z * transform_3d[2][2]), 0.0,
(double)transform_3d[0][3], (double)transform_3d[1][3], (double)transform_3d[2][3], 1.0);
openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]),
0.0,
0.0,
0.0,
0.0,
(double)(voxel_size.y * transform_3d[1][1]),
0.0,
0.0,
0.0,
0.0,
(double)(voxel_size.z * transform_3d[2][2]),
0.0,
(double)transform_3d[0][3],
(double)transform_3d[1][3],
(double)transform_3d[2][3],
1.0);
openvdb::math::Transform::Ptr index_to_world_tfm = openvdb::math::Transform::createLinearTransform(index_to_world_mat);
openvdb::math::Transform::Ptr index_to_world_tfm =
openvdb::math::Transform::createLinearTransform(index_to_world_mat);
sparse->setTransform(index_to_world_tfm);
@@ -472,19 +490,16 @@ void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
device_texture *image_memory = handle.image_memory();
if (image_memory->data_elements == 1) {
grid = openvdb_grid_from_device_texture<openvdb::FloatGrid>(image_memory,
mesh->volume_clipping,
handle.metadata().transform_3d);
grid = openvdb_grid_from_device_texture<openvdb::FloatGrid>(
image_memory, mesh->volume_clipping, handle.metadata().transform_3d);
}
else if (image_memory->data_elements == 3) {
grid = openvdb_grid_from_device_texture<openvdb::Vec3fGrid>(image_memory,
mesh->volume_clipping,
handle.metadata().transform_3d);
grid = openvdb_grid_from_device_texture<openvdb::Vec3fGrid>(
image_memory, mesh->volume_clipping, handle.metadata().transform_3d);
}
else if (image_memory->data_elements == 4) {
grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(image_memory,
mesh->volume_clipping,
handle.metadata().transform_3d);
grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(
image_memory, mesh->volume_clipping, handle.metadata().transform_3d);
}
}

View File

@@ -79,7 +79,15 @@ DeviceScene::DeviceScene(Device *device)
shaders(device, "__shaders", MEM_GLOBAL),
lookup_table(device, "__lookup_table", MEM_GLOBAL),
sample_pattern_lut(device, "__sample_pattern_lut", MEM_GLOBAL),
ies_lights(device, "__ies", MEM_GLOBAL)
ies_lights(device, "__ies", MEM_GLOBAL),
light_tree_nodes(device, "__light_tree_nodes", MEM_GLOBAL),
light_distribution_to_node(device, "__light_distribution_to_node", MEM_GLOBAL),
lamp_to_distribution(device, "__lamp_to_distribution", MEM_GLOBAL),
triangle_to_distribution(device, "__triangle_to_distribution", MEM_GLOBAL),
light_group_sample_cdf(device, "__light_group_sample_cdf", MEM_GLOBAL),
light_group_sample_prob(device, "__light_group_sample_prob", MEM_GLOBAL),
leaf_to_first_emitter(device, "__leaf_to_first_emitter", MEM_GLOBAL),
light_tree_leaf_emitters(device, "__light_tree_leaf_emitters", MEM_GLOBAL)
{
memset((void *)&data, 0, sizeof(data));
}

View File

@@ -108,6 +108,14 @@ class DeviceScene {
device_vector<KernelLight> lights;
device_vector<float2> light_background_marginal_cdf;
device_vector<float2> light_background_conditional_cdf;
device_vector<float4> light_tree_nodes;
device_vector<uint> light_distribution_to_node;
device_vector<uint> lamp_to_distribution;
device_vector<uint> triangle_to_distribution;
device_vector<float> light_group_sample_cdf;
device_vector<float> light_group_sample_prob;
device_vector<int> leaf_to_first_emitter;
device_vector<float4> light_tree_leaf_emitters;
/* particles */
device_vector<KernelParticle> particles;

View File

@@ -549,8 +549,7 @@ static bool py_bvhtree_overlap_cb(void *userdata, int index_a, int index_b, int
}
}
return (isect_tri_tri_v3(
UNPACK3(tri_a_co), UNPACK3(tri_b_co), ix_pair[0], ix_pair[1]) &&
return (isect_tri_tri_v3(UNPACK3(tri_a_co), UNPACK3(tri_b_co), ix_pair[0], ix_pair[1]) &&
((verts_shared == 0) || (len_squared_v3v3(ix_pair[0], ix_pair[1]) > data->epsilon)));
}