Compare commits
325 Commits
tmp-gpu-co
...
soc-2020-p
Author | SHA1 | Date | |
---|---|---|---|
9dd50b39a4 | |||
35624cdfe5 | |||
c74b4caa72 | |||
0fae682d61 | |||
4429c39264 | |||
83c78529b9 | |||
97dc370c50 | |||
![]() |
04ca28a6e0 | ||
762e4cf221 | |||
d5b5b228e4 | |||
2452031268 | |||
47c6f41b89 | |||
83e3d25bca | |||
20a8edaa72 | |||
aba46371a1 | |||
6df4b00f5f | |||
0b49fdd0ee | |||
567e333ea4 | |||
f94e322ab7 | |||
d1057890c4 | |||
6978635622 | |||
9085fb8073 | |||
e157573fab | |||
![]() |
1c892e6814 | ||
9c6ef48315 | |||
4b69e55da3 | |||
2b896fc481 | |||
c7a7a38b65 | |||
8010cbe620 | |||
59bc71d69b | |||
73fb445b8d | |||
c300fc56c3 | |||
114a5a8ce8 | |||
9ab3b8f7f4 | |||
04f703fca6 | |||
b3c08a3a0a | |||
c074943dfd | |||
77e4905b17 | |||
d958ab62e7 | |||
690d76c624 | |||
cb578ca104 | |||
ab3a651515 | |||
4c625df759 | |||
a5cf71fa5d | |||
3b49ca465f | |||
c5519d4b6f | |||
9813778a2d | |||
7c38d008de | |||
4e103101f7 | |||
4f209fab2c | |||
ada98869df | |||
0843d5b024 | |||
28c1300115 | |||
29c28ac022 | |||
ad05e1100f | |||
9bf8b6799d | |||
ee909783de | |||
494560f19d | |||
a45f2fd1fb | |||
12c24ecf6e | |||
51a461dcab | |||
f0285e347e | |||
![]() |
bb2908472c | ||
cc86e03fd5 | |||
ba2d1c8898 | |||
b3165fb8b5 | |||
ac0bab2d43 | |||
54c2c14921 | |||
58a457da3d | |||
6f502136c7 | |||
bff91b32aa | |||
2218b61e8e | |||
879ed5a165 | |||
bea79e0c7b | |||
1eab858dbc | |||
2b042d885a | |||
cfbea0e09d | |||
21118fb610 | |||
08286ef8ba | |||
![]() |
9280fb19e4 | ||
da95fa8344 | |||
cbb1bb90fd | |||
d070d33aa3 | |||
4e31b5b173 | |||
b3702f5918 | |||
9b58a41a92 | |||
dbf5bb7af2 | |||
2fc2a0a56b | |||
aec0cfaf08 | |||
74556a5a17 | |||
7219abc5bd | |||
d2c150772a | |||
eca062b9cb | |||
e84e6e12ed | |||
1c294fb1fd | |||
18c9f7ef72 | |||
6b573d9877 | |||
e11aa3eddd | |||
6d888133da | |||
bb5cc3bccf | |||
9adedb2605 | |||
23a6b5d91e | |||
fc5ff99770 | |||
bc5d144855 | |||
8ef05d3180 | |||
3b08cb3236 | |||
9c093a5d9a | |||
c0340ec893 | |||
ab2dbafd8b | |||
2e5c877056 | |||
f1cb3dfbaa | |||
342a6b5f93 | |||
f15d33d585 | |||
6640ed92c0 | |||
00162e6b7b | |||
a93be410c9 | |||
8fa42f0bd4 | |||
c2691c93d5 | |||
1b1129f82a | |||
91694b9b58 | |||
58909abc68 | |||
4f59e4bddc | |||
3dcaca93a0 | |||
3d35012a05 | |||
8123b12006 | |||
![]() |
8fbfc150a0 | ||
cc3cb52b23 | |||
96e460ed9b | |||
bc8168f4a2 | |||
45e6ca3661 | |||
5e6119ddca | |||
77d71cc113 | |||
e4f400f0d6 | |||
ba20da7214 | |||
73a43c9d8a | |||
574bd866c8 | |||
![]() |
b313710c10 | ||
769ec7ffe6 | |||
82150f5641 | |||
c872e87bd4 | |||
d4804f00fb | |||
![]() |
e9c4325515 | ||
c5b6b3d82f | |||
59861db763 | |||
29ef7142dd | |||
5249a813f2 | |||
315ae005c3 | |||
c606044157 | |||
![]() |
c323f3e90a | ||
f3e724b93d | |||
1b593edf1d | |||
38e9a349de | |||
cf3431e0e8 | |||
a316d3b6c6 | |||
d84dce85f3 | |||
531a3f6c4e | |||
adfde60831 | |||
87062d4d67 | |||
ee351cb74d | |||
![]() |
f7d38e2e64 | ||
56d7e39b92 | |||
c961bf8975 | |||
229fe01a15 | |||
f9aba4f6e1 | |||
396abbbfe7 | |||
f96afde3bd | |||
02ccc37144 | |||
c3113724ed | |||
de53178b26 | |||
4a289081a5 | |||
fce71a255c | |||
6390b530d0 | |||
6be8b6af40 | |||
d1b3da697d | |||
de947c5c50 | |||
3a522f7a7f | |||
3ebe97c06b | |||
50e6d56e4e | |||
97be726f93 | |||
e4370eccdf | |||
e818f1274b | |||
a4a814a180 | |||
9538e38704 | |||
16c7b6a7fe | |||
e8483f9bd7 | |||
e1a10b5e53 | |||
701a9d3917 | |||
164f40c50a | |||
f921ae4665 | |||
a9e0aeaf65 | |||
8d3b8bc835 | |||
52c2f296bc | |||
51af20b856 | |||
7f6530e5bd | |||
9306037ed3 | |||
b016e7f258 | |||
c1386795a9 | |||
9de5adc6a1 | |||
fbc3c1b24d | |||
e9bcf09819 | |||
473d9507fa | |||
2bb7378779 | |||
3f94f47113 | |||
14b77b37cb | |||
33e6562a8a | |||
![]() |
b5e3451540 | ||
057f3f2f3d | |||
fe70605a2a | |||
8f22feefbc | |||
d406edf1ee | |||
0264f53e30 | |||
189eb2550d | |||
![]() |
cc0d8ab036 | ||
![]() |
c442ff0017 | ||
f349a53c08 | |||
70d7805fa9 | |||
ba6ed01058 | |||
72b422c1e1 | |||
732434cb1f | |||
71683be4bb | |||
cab9673bed | |||
1ff1a2be9c | |||
feaed44ef6 | |||
c565f16afb | |||
627b294317 | |||
de68880e79 | |||
1e7afea2bb | |||
9ea6228b07 | |||
eb8cbb6d7c | |||
cc63897c22 | |||
3305a94fa1 | |||
44b32e0e4a | |||
de21ddf821 | |||
cd579c4996 | |||
298d5eb669 | |||
8c375113b5 | |||
df8bbe98c4 | |||
76870830e2 | |||
![]() |
8d399eef67 | ||
![]() |
4608e5ac26 | ||
![]() |
2d839a08e4 | ||
![]() |
0216daeb80 | ||
![]() |
8e36d94be2 | ||
![]() |
94af4326e3 | ||
![]() |
f6305047f4 | ||
![]() |
e8e0669785 | ||
![]() |
5d344f4360 | ||
![]() |
108594d8c8 | ||
![]() |
84fec21522 | ||
b1837d5f69 | |||
b3c375e37e | |||
![]() |
1fe1e742a6 | ||
![]() |
6045fc40f4 | ||
![]() |
00b90ede1b | ||
![]() |
beef4874d3 | ||
![]() |
ff4833a6b3 | ||
![]() |
0db119ad11 | ||
![]() |
8a0ff32abd | ||
![]() |
260b2e9e20 | ||
![]() |
2d6e1ddb94 | ||
![]() |
c6bd7a974b | ||
![]() |
36cfc9e9fd | ||
![]() |
5c10bd4f19 | ||
![]() |
136991be1a | ||
![]() |
2cdb08f4ff | ||
![]() |
8b24cf8c83 | ||
![]() |
118731d7d4 | ||
![]() |
2f85295be6 | ||
![]() |
6f2d885f7e | ||
![]() |
df7f4d1e26 | ||
![]() |
0028f7cf0f | ||
![]() |
a3826bf41c | ||
![]() |
4f55918a5e | ||
![]() |
bcb89f0b96 | ||
![]() |
2e17c3f3a1 | ||
![]() |
e558b1afef | ||
![]() |
458104ee0b | ||
3342114d31 | |||
ed1b92cf8e | |||
59e40721dd | |||
8e462b0f48 | |||
68b0cf6da0 | |||
55e96e1955 | |||
c5803441f3 | |||
3e3a44dcfa | |||
ac4eec8cb9 | |||
dac0b7c0eb | |||
8b5f8c0131 | |||
81406e39f8 | |||
88fa8610ef | |||
6c68384c76 | |||
5522d28d54 | |||
cbeb9f2c35 | |||
975194b237 | |||
8519814b89 | |||
0347abee2d | |||
2b21ce73e9 | |||
c0e1765c3a | |||
68fce6c4db | |||
a554255c13 | |||
ea1322c64d | |||
f383c3ce78 | |||
1d5ecd2a6b | |||
f60520306c | |||
efb19da3e8 | |||
49496a460a | |||
2f12b83dbd | |||
fef9647db4 | |||
f914311949 | |||
d9b2b82532 | |||
584cb4a9b0 | |||
9bccf1425d | |||
4e7eef02bc | |||
3633221c9b | |||
db14acaf22 | |||
95d786f598 | |||
cd502b8287 | |||
72d9ca0db6 | |||
deb5c1dae9 | |||
ed657c9407 | |||
72ba23c7fc | |||
96b252d90e | |||
922b77f142 | |||
f024ae11e3 | |||
d01207e4fb |
@@ -236,6 +236,7 @@ ForEachMacros:
|
||||
- LISTBASE_CIRCULAR_FORWARD_BEGIN
|
||||
- LISTBASE_FOREACH
|
||||
- LISTBASE_FOREACH_BACKWARD
|
||||
- LISTBASE_FOREACH_INDEX
|
||||
- LISTBASE_FOREACH_MUTABLE
|
||||
- LISTBASE_FOREACH_BACKWARD_MUTABLE
|
||||
- MAN_ITER_AXES_BEGIN
|
||||
|
@@ -428,6 +428,10 @@ if(WIN32)
|
||||
option(WITH_TBB_MALLOC_PROXY "Enable the TBB malloc replacement" ON)
|
||||
endif()
|
||||
|
||||
# This should be turned off when Blender enter beta/rc/release
|
||||
option(WITH_EXPERIMENTAL_FEATURES "Enable experimental features (still need to enable them in the user preferences)" OFF)
|
||||
mark_as_advanced(WITH_EXPERIMENTAL_FEATURES)
|
||||
|
||||
# Unit testsing
|
||||
option(WITH_GTESTS "Enable GTest unit testing" OFF)
|
||||
option(WITH_OPENGL_RENDER_TESTS "Enable OpenGL render related unit testing (Experimental)" OFF)
|
||||
|
@@ -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
|
||||
|
@@ -33,10 +33,11 @@ def is_tool(name):
|
||||
return which(name) is not None
|
||||
|
||||
class Builder:
|
||||
def __init__(self, name, branch):
|
||||
def __init__(self, name, branch, codesign):
|
||||
self.name = name
|
||||
self.branch = branch
|
||||
self.is_release_branch = re.match("^blender-v(.*)-release$", branch) is not None
|
||||
self.codesign = codesign
|
||||
|
||||
# Buildbot runs from build/ directory
|
||||
self.blender_dir = os.path.abspath(os.path.join('..', 'blender.git'))
|
||||
@@ -67,8 +68,9 @@ def create_builder_from_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('builder_name')
|
||||
parser.add_argument('branch', default='master', nargs='?')
|
||||
parser.add_argument("--codesign", action="store_true")
|
||||
args = parser.parse_args()
|
||||
return Builder(args.builder_name, args.branch)
|
||||
return Builder(args.builder_name, args.branch, args.codesign)
|
||||
|
||||
|
||||
class VersionInfo:
|
||||
|
@@ -82,6 +82,10 @@ def create_argument_parser():
|
||||
type=Path,
|
||||
help="Optional path to applescript to set up folder looks of DMG."
|
||||
"If not provided default Blender's one is used.")
|
||||
parser.add_argument(
|
||||
'--codesign',
|
||||
action="store_true",
|
||||
help="Code sign and notarize DMG contents.")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -395,7 +399,8 @@ def create_final_dmg(app_bundles: List[Path],
|
||||
dmg_filepath: Path,
|
||||
background_image_filepath: Path,
|
||||
volume_name: str,
|
||||
applescript: Path) -> None:
|
||||
applescript: Path,
|
||||
codesign: bool) -> None:
|
||||
"""
|
||||
Create DMG with all app bundles
|
||||
|
||||
@@ -421,7 +426,8 @@ def create_final_dmg(app_bundles: List[Path],
|
||||
#
|
||||
# This allows to recurs into the content of bundles without worrying about
|
||||
# possible interfereice of Application symlink.
|
||||
codesign_app_bundles_in_dmg(mount_directory)
|
||||
if codesign:
|
||||
codesign_app_bundles_in_dmg(mount_directory)
|
||||
|
||||
copy_background_if_needed(background_image_filepath, mount_directory)
|
||||
create_applications_link(mount_directory)
|
||||
@@ -434,7 +440,8 @@ def create_final_dmg(app_bundles: List[Path],
|
||||
compress_dmg(writable_dmg_filepath, dmg_filepath)
|
||||
writable_dmg_filepath.unlink()
|
||||
|
||||
codesign_and_notarize_dmg(dmg_filepath)
|
||||
if codesign:
|
||||
codesign_and_notarize_dmg(dmg_filepath)
|
||||
|
||||
|
||||
def ensure_dmg_extension(filepath: Path) -> Path:
|
||||
@@ -521,6 +528,7 @@ def main():
|
||||
source_dir = args.source_dir.absolute()
|
||||
background_image_filepath = get_background_image(args.background_image)
|
||||
applescript = get_applescript(args.applescript)
|
||||
codesign = args.codesign
|
||||
|
||||
app_bundles = collect_and_log_app_bundles(source_dir)
|
||||
if not app_bundles:
|
||||
@@ -535,7 +543,8 @@ def main():
|
||||
dmg_filepath,
|
||||
background_image_filepath,
|
||||
volume_name,
|
||||
applescript)
|
||||
applescript,
|
||||
codesign)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@@ -24,7 +24,7 @@ import shutil
|
||||
import buildbot_utils
|
||||
|
||||
def get_cmake_options(builder):
|
||||
post_install_script = os.path.join(
|
||||
codesign_script = os.path.join(
|
||||
builder.blender_dir, 'build_files', 'buildbot', 'worker_codesign.cmake')
|
||||
|
||||
config_file = "build_files/cmake/config/blender_release.cmake"
|
||||
@@ -36,7 +36,8 @@ def get_cmake_options(builder):
|
||||
options.append('-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9')
|
||||
elif builder.platform == 'win':
|
||||
options.extend(['-G', 'Visual Studio 16 2019', '-A', 'x64'])
|
||||
options.extend(['-DPOSTINSTALL_SCRIPT:PATH=' + post_install_script])
|
||||
if builder.codesign:
|
||||
options.extend(['-DPOSTINSTALL_SCRIPT:PATH=' + codesign_script])
|
||||
elif builder.platform == 'linux':
|
||||
config_file = "build_files/buildbot/config/blender_linux.cmake"
|
||||
|
||||
|
@@ -117,6 +117,8 @@ def pack_mac(builder):
|
||||
if info.is_development_build:
|
||||
background_image = os.path.join(release_dir, 'buildbot', 'background.tif')
|
||||
command += ['--background-image', background_image]
|
||||
if builder.codesign:
|
||||
command += ['--codesign']
|
||||
command += [builder.install_dir]
|
||||
buildbot_utils.call(command)
|
||||
|
||||
@@ -150,7 +152,8 @@ def pack_win(builder):
|
||||
|
||||
package_filename = package_name + '.msi'
|
||||
package_filepath = os.path.join(builder.build_dir, package_filename)
|
||||
sign_file_or_directory(package_filepath)
|
||||
if builder.codesign:
|
||||
sign_file_or_directory(package_filepath)
|
||||
|
||||
package_files += [(package_filepath, package_filename)]
|
||||
|
||||
|
@@ -12,12 +12,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2016 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If ALEMBIC_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -11,6 +11,13 @@
|
||||
# AUDASPACE_PY_INCLUDE_DIRS - the audaspace's python binding include directories
|
||||
# AUDASPACE_PY_LIBRARIES - link these to use audaspace's python binding
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
IF(NOT AUDASPACE_ROOT_DIR AND NOT $ENV{AUDASPACE_ROOT_DIR} STREQUAL "")
|
||||
SET(AUDASPACE_ROOT_DIR $ENV{AUDASPACE_ROOT_DIR})
|
||||
ENDIF()
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2018 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If BLOSC_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -17,12 +17,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2020 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If CLANG_TIDY_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -10,12 +10,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If EIGEN3_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2018 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If EMBREE_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If FFTW3_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -13,12 +13,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If GLEW_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -12,12 +12,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2012 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If ICU_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If JACK_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If JEMALLOC_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -13,12 +13,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
if(LLVM_ROOT_DIR)
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If LZO_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# note about include paths, there are 2 ways includes are set
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2012 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENCOLORIO_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -21,12 +21,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENEXR_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -10,6 +10,13 @@
|
||||
# OPENGLES_LIBRARIES - all libraries needed for OpenGLES
|
||||
# OPENGLES_INCLUDES - all includes needed for OpenGLES
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENGLES_ROOT_DIR was defined in the environment, use it.
|
||||
IF(NOT OPENGLES_ROOT_DIR AND NOT $ENV{OPENGLES_ROOT_DIR} STREQUAL "")
|
||||
SET(OPENGLES_ROOT_DIR $ENV{OPENGLES_ROOT_DIR})
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2019 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENIMAGEDENOISE_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -16,12 +16,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENIMAGEIO_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENJPEG_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OSL_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2013 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENSUBDIV_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPENVDB_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -10,12 +10,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2019 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If OPTIX_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If PCRE_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If PUGIXML_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -25,12 +25,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If PYTHON_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -13,12 +13,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If SDL2_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If LIBSNDFILE_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If SPACENAV_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2016 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If TBB_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -12,12 +12,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2019 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If USD_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -14,12 +14,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2011 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If XML2_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -20,12 +20,8 @@
|
||||
# XR_OPENXR_SDK_LOADER_LIBRARY, where to find the OpenXR-SDK loader library.
|
||||
|
||||
#=============================================================================
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#=============================================================================
|
||||
|
||||
# If XR_OPENXR_SDK_ROOT_DIR was defined in the environment, use it.
|
||||
|
@@ -1,12 +1,8 @@
|
||||
#=============================================================================
|
||||
# Copyright 2014 Blender Foundation.
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
# Distributed under the OSI-approved BSD 3-Clause License,
|
||||
# see accompanying file BSD-3-Clause-license.txt for details.
|
||||
#
|
||||
# Inspired on the Testing.cmake from Libmv
|
||||
#
|
||||
|
@@ -397,7 +397,7 @@ function(setup_heavy_lib_pool)
|
||||
list(APPEND _HEAVY_LIBS "cycles_device" "cycles_kernel")
|
||||
endif()
|
||||
if(WITH_LIBMV)
|
||||
list(APPEND _HEAVY_LIBS "bf_intern_libmv")
|
||||
list(APPEND _HEAVY_LIBS "extern_ceres" "bf_intern_libmv")
|
||||
endif()
|
||||
if(WITH_OPENVDB)
|
||||
list(APPEND _HEAVY_LIBS "bf_intern_openvdb")
|
||||
|
@@ -373,8 +373,9 @@ if(WITH_CYCLES_OSL)
|
||||
list(APPEND OSL_LIBRARIES ${OSL_LIB_COMP} -force_load ${OSL_LIB_EXEC} ${OSL_LIB_QUERY})
|
||||
find_path(OSL_INCLUDE_DIR OSL/oslclosure.h PATHS ${CYCLES_OSL}/include)
|
||||
find_program(OSL_COMPILER NAMES oslc PATHS ${CYCLES_OSL}/bin)
|
||||
find_path(OSL_SHADER_DIR NAMES stdosl.h PATHS ${CYCLES_OSL}/shaders)
|
||||
|
||||
if(OSL_INCLUDE_DIR AND OSL_LIBRARIES AND OSL_COMPILER)
|
||||
if(OSL_INCLUDE_DIR AND OSL_LIBRARIES AND OSL_COMPILER AND OSL_SHADER_DIR)
|
||||
set(OSL_FOUND TRUE)
|
||||
else()
|
||||
message(STATUS "OSL not found")
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
26
doc/license/BSD-3-Clause-license.txt
Normal file
26
doc/license/BSD-3-Clause-license.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -20,6 +20,7 @@ from gpu_extras.presets import draw_circle_2d
|
||||
offscreen = gpu.types.GPUOffScreen(512, 512)
|
||||
|
||||
with offscreen.bind():
|
||||
bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||
bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
|
||||
with gpu.matrix.push_pop():
|
||||
# reset matrices -> use normalized device coordinates [-1, 1]
|
||||
|
@@ -25,6 +25,7 @@ RING_AMOUNT = 10
|
||||
offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)
|
||||
|
||||
with offscreen.bind():
|
||||
bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||
bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
|
||||
with gpu.matrix.push_pop():
|
||||
# reset matrices -> use normalized device coordinates [-1, 1]
|
||||
|
6
extern/audaspace/bindings/python/PySound.cpp
vendored
6
extern/audaspace/bindings/python/PySound.cpp
vendored
@@ -1395,9 +1395,9 @@ PyDoc_STRVAR(M_aud_Sound_threshold_doc,
|
||||
" all between to 0.\n\n"
|
||||
" :arg threshold: Threshold value over which an amplitude counts\n"
|
||||
" non-zero.\n\n"
|
||||
":type threshold: float\n"
|
||||
":return: The created :class:`Sound` object.\n"
|
||||
":rtype: :class:`Sound`");
|
||||
" :type threshold: float\n"
|
||||
" :return: The created :class:`Sound` object.\n"
|
||||
" :rtype: :class:`Sound`");
|
||||
|
||||
static PyObject *
|
||||
Sound_threshold(Sound* self, PyObject* args)
|
||||
|
@@ -119,8 +119,8 @@ void JOSResampleReader::updateBuffer(int size, double factor, int samplesize)
|
||||
P = int_to_fp(m_L) - P;\
|
||||
\
|
||||
end = std::floor((m_len - 1) / double(m_L) + m_P) - 1;\
|
||||
if(m_cache_valid - m_n - 2 < end)\
|
||||
end = m_cache_valid - m_n - 2;\
|
||||
if(m_cache_valid - int(m_n) - 2 < end)\
|
||||
end = m_cache_valid - int(m_n) - 2;\
|
||||
\
|
||||
data = buf + (m_n + 2 + end) * m_channels - 1;\
|
||||
l = fp_to_int(P);\
|
||||
@@ -166,8 +166,8 @@ void JOSResampleReader::updateBuffer(int size, double factor, int samplesize)
|
||||
P = 0 - P;\
|
||||
\
|
||||
end = (int_to_fp(m_len) - P) / P_increment - 1;\
|
||||
if(m_cache_valid - m_n - 2 < end)\
|
||||
end = m_cache_valid - m_n - 2;\
|
||||
if(m_cache_valid - int(m_n) - 2 < end)\
|
||||
end = m_cache_valid - int(m_n) - 2;\
|
||||
\
|
||||
P += P_increment * end;\
|
||||
data = buf + (m_n + 2 + end) * m_channels - 1;\
|
||||
|
2
extern/mantaflow/preprocessed/gitinfo.h
vendored
2
extern/mantaflow/preprocessed/gitinfo.h
vendored
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
#define MANTA_GIT_VERSION "commit 841bfd09c068dfb95637c0ec14fa78305286a433"
|
||||
#define MANTA_GIT_VERSION "commit e2f6e59e3679f88e5100ae2145410cca4971b9df"
|
||||
|
102
extern/mantaflow/preprocessed/grid.h
vendored
102
extern/mantaflow/preprocessed/grid.h
vendored
@@ -355,6 +355,38 @@ class GridBase : public PbClass {
|
||||
return isInBounds(Vec3i(i, j, k), bnd);
|
||||
}
|
||||
|
||||
#ifdef BLENDER
|
||||
//! expose name field to Python for Blender
|
||||
void setName(const std::string &name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
static PyObject *_W_9(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
GridBase *pbo = dynamic_cast<GridBase *>(Pb::objFromPy(_self));
|
||||
bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
|
||||
pbPreparePlugin(pbo->getParent(), "GridBase::setName", !noTiming);
|
||||
PyObject *_retval = 0;
|
||||
{
|
||||
ArgLocker _lock;
|
||||
const std::string &name = _args.get<std::string>("name", 0, &_lock);
|
||||
pbo->_args.copy(_args);
|
||||
_retval = getPyNone();
|
||||
pbo->setName(name);
|
||||
pbo->_args.check();
|
||||
}
|
||||
pbFinalizePlugin(pbo->getParent(), "GridBase::setName", !noTiming);
|
||||
return _retval;
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
pbSetError("GridBase::setName", e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
protected:
|
||||
GridType mType;
|
||||
Vec3i mSize;
|
||||
@@ -373,7 +405,7 @@ template<class T> class Grid : public GridBase {
|
||||
public:
|
||||
//! init new grid, values are set to zero
|
||||
Grid(FluidSolver *parent, bool show = true);
|
||||
static int _W_9(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static int _W_10(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
PbClass *obj = Pb::objFromPy(_self);
|
||||
if (obj)
|
||||
@@ -410,7 +442,7 @@ template<class T> class Grid : public GridBase {
|
||||
typedef GridBase BASETYPE_GRID;
|
||||
|
||||
int save(std::string name);
|
||||
static PyObject *_W_10(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_11(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -435,7 +467,7 @@ template<class T> class Grid : public GridBase {
|
||||
}
|
||||
|
||||
int load(std::string name);
|
||||
static PyObject *_W_11(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_12(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -461,7 +493,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! set all cells to zero
|
||||
void clear();
|
||||
static PyObject *_W_12(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_13(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -588,7 +620,7 @@ template<class T> class Grid : public GridBase {
|
||||
// Grid<T>& operator=(const Grid<T>& a);
|
||||
//! copy content from other grid (use this one instead of operator= !)
|
||||
Grid<T> ©From(const Grid<T> &a, bool copyType = true);
|
||||
static PyObject *_W_13(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_14(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -618,7 +650,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! get grid type
|
||||
int getGridType();
|
||||
static PyObject *_W_14(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_15(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -643,7 +675,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! add/subtract other grid
|
||||
void add(const Grid<T> &a);
|
||||
static PyObject *_W_15(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_16(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -669,7 +701,7 @@ template<class T> class Grid : public GridBase {
|
||||
}
|
||||
|
||||
void sub(const Grid<T> &a);
|
||||
static PyObject *_W_16(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_17(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -696,7 +728,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! set all cells to constant value
|
||||
void setConst(T s);
|
||||
static PyObject *_W_17(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_18(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -723,7 +755,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! add constant to all grid cells
|
||||
void addConst(T s);
|
||||
static PyObject *_W_18(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_19(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -750,7 +782,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! add scaled other grid to current one (note, only "Real" factor, "T" type not supported here!)
|
||||
void addScaled(const Grid<T> &a, const T &factor);
|
||||
static PyObject *_W_19(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_20(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -778,7 +810,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! multiply contents of grid
|
||||
void mult(const Grid<T> &a);
|
||||
static PyObject *_W_20(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_21(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -805,7 +837,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! multiply each cell by a constant scalar value
|
||||
void multConst(T s);
|
||||
static PyObject *_W_21(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_22(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -832,7 +864,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! safely divide contents of grid (with zero check)
|
||||
Grid<T> &safeDivide(const Grid<T> &a);
|
||||
static PyObject *_W_22(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_23(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -858,7 +890,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! clamp content to range (for vec3, clamps each component separately)
|
||||
void clamp(Real min, Real max);
|
||||
static PyObject *_W_23(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_24(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -886,7 +918,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! reduce small values to zero
|
||||
void stomp(const T &threshold);
|
||||
static PyObject *_W_24(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_25(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -913,7 +945,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! permute grid axes, e.g. switch y with z (0,2,1)
|
||||
void permuteAxes(int axis0, int axis1, int axis2);
|
||||
static PyObject *_W_25(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_26(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -942,7 +974,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! permute grid axes, e.g. switch y with z (0,2,1)
|
||||
void permuteAxesCopyToGrid(int axis0, int axis1, int axis2, Grid<T> &out);
|
||||
static PyObject *_W_26(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_27(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -972,7 +1004,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! join other grid by either keeping min or max value at cell
|
||||
void join(const Grid<T> &a, bool keepMax = true);
|
||||
static PyObject *_W_27(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_28(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1001,7 +1033,7 @@ template<class T> class Grid : public GridBase {
|
||||
// common compound operators
|
||||
//! get absolute max value in grid
|
||||
Real getMaxAbs() const;
|
||||
static PyObject *_W_28(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_29(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1026,7 +1058,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! get max value in grid
|
||||
Real getMax() const;
|
||||
static PyObject *_W_29(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_30(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1051,7 +1083,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! get min value in grid
|
||||
Real getMin() const;
|
||||
static PyObject *_W_30(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_31(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1076,7 +1108,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! calculate L1 norm of grid content
|
||||
Real getL1(int bnd = 0);
|
||||
static PyObject *_W_31(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_32(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1102,7 +1134,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! calculate L2 norm of grid content
|
||||
Real getL2(int bnd = 0);
|
||||
static PyObject *_W_32(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_33(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1128,7 +1160,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! set all boundary cells to constant value (Dirichlet)
|
||||
void setBound(T value, int boundaryWidth = 1);
|
||||
static PyObject *_W_33(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_34(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1156,7 +1188,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! set all boundary cells to last inner value (Neumann)
|
||||
void setBoundNeumann(int boundaryWidth = 1);
|
||||
static PyObject *_W_34(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_35(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1183,7 +1215,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! get data pointer of grid
|
||||
std::string getDataPointer();
|
||||
static PyObject *_W_35(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_36(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1208,7 +1240,7 @@ template<class T> class Grid : public GridBase {
|
||||
|
||||
//! debugging helper, print grid from python. skip boundary of width bnd
|
||||
void printGrid(int zSlice = -1, bool printIndex = false, int bnd = 1);
|
||||
static PyObject *_W_36(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_37(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1276,7 +1308,7 @@ class MACGrid : public Grid<Vec3> {
|
||||
{
|
||||
mType = (GridType)(TypeMAC | TypeVec3);
|
||||
}
|
||||
static int _W_37(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static int _W_38(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
PbClass *obj = Pb::objFromPy(_self);
|
||||
if (obj)
|
||||
@@ -1358,7 +1390,7 @@ class MACGrid : public Grid<Vec3> {
|
||||
//! set all boundary cells of a MAC grid to certain value (Dirchlet). Respects staggered grid
|
||||
//! locations optionally, only set normal components
|
||||
void setBoundMAC(Vec3 value, int boundaryWidth, bool normalOnly = false);
|
||||
static PyObject *_W_38(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_39(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1399,7 +1431,7 @@ class FlagGrid : public Grid<int> {
|
||||
{
|
||||
mType = (GridType)(TypeFlags | TypeInt);
|
||||
}
|
||||
static int _W_39(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static int _W_40(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
PbClass *obj = Pb::objFromPy(_self);
|
||||
if (obj)
|
||||
@@ -1579,7 +1611,7 @@ class FlagGrid : public Grid<int> {
|
||||
const std::string &inflow = " ",
|
||||
const std::string &outflow = " ",
|
||||
Grid<Real> *phiWalls = 0x00);
|
||||
static PyObject *_W_40(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_41(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1613,7 +1645,7 @@ class FlagGrid : public Grid<int> {
|
||||
|
||||
//! set fluid flags inside levelset (liquids)
|
||||
void updateFromLevelset(LevelsetGrid &levelset);
|
||||
static PyObject *_W_41(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_42(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1640,7 +1672,7 @@ class FlagGrid : public Grid<int> {
|
||||
|
||||
//! set all cells (except obs/in/outflow) to type (fluid by default)
|
||||
void fillGrid(int type = TypeFluid);
|
||||
static PyObject *_W_42(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_43(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
@@ -1669,7 +1701,7 @@ class FlagGrid : public Grid<int> {
|
||||
//! warning for large grids! only regular int returned (due to python interface)
|
||||
//! optionally creates mask in RealGrid (1 where flag matches, 0 otherwise)
|
||||
int countCells(int flag, int bnd = 0, Grid<Real> *mask = NULL);
|
||||
static PyObject *_W_43(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
static PyObject *_W_44(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
|
||||
{
|
||||
try {
|
||||
PbArgs _args(_linargs, _kwds);
|
||||
|
186
extern/mantaflow/preprocessed/grid.h.reg.cpp
vendored
186
extern/mantaflow/preprocessed/grid.h.reg.cpp
vendored
@@ -8,103 +8,103 @@ namespace Manta {
|
||||
#ifdef _C_FlagGrid
|
||||
static const Pb::Register _R_26("FlagGrid", "FlagGrid", "Grid<int>");
|
||||
template<> const char *Namify<FlagGrid>::S = "FlagGrid";
|
||||
static const Pb::Register _R_27("FlagGrid", "FlagGrid", FlagGrid::_W_39);
|
||||
static const Pb::Register _R_28("FlagGrid", "initDomain", FlagGrid::_W_40);
|
||||
static const Pb::Register _R_29("FlagGrid", "updateFromLevelset", FlagGrid::_W_41);
|
||||
static const Pb::Register _R_30("FlagGrid", "fillGrid", FlagGrid::_W_42);
|
||||
static const Pb::Register _R_31("FlagGrid", "countCells", FlagGrid::_W_43);
|
||||
static const Pb::Register _R_27("FlagGrid", "FlagGrid", FlagGrid::_W_40);
|
||||
static const Pb::Register _R_28("FlagGrid", "initDomain", FlagGrid::_W_41);
|
||||
static const Pb::Register _R_29("FlagGrid", "updateFromLevelset", FlagGrid::_W_42);
|
||||
static const Pb::Register _R_30("FlagGrid", "fillGrid", FlagGrid::_W_43);
|
||||
static const Pb::Register _R_31("FlagGrid", "countCells", FlagGrid::_W_44);
|
||||
#endif
|
||||
#ifdef _C_Grid
|
||||
static const Pb::Register _R_32("Grid<int>", "Grid<int>", "GridBase");
|
||||
template<> const char *Namify<Grid<int>>::S = "Grid<int>";
|
||||
static const Pb::Register _R_33("Grid<int>", "Grid", Grid<int>::_W_9);
|
||||
static const Pb::Register _R_34("Grid<int>", "save", Grid<int>::_W_10);
|
||||
static const Pb::Register _R_35("Grid<int>", "load", Grid<int>::_W_11);
|
||||
static const Pb::Register _R_36("Grid<int>", "clear", Grid<int>::_W_12);
|
||||
static const Pb::Register _R_37("Grid<int>", "copyFrom", Grid<int>::_W_13);
|
||||
static const Pb::Register _R_38("Grid<int>", "getGridType", Grid<int>::_W_14);
|
||||
static const Pb::Register _R_39("Grid<int>", "add", Grid<int>::_W_15);
|
||||
static const Pb::Register _R_40("Grid<int>", "sub", Grid<int>::_W_16);
|
||||
static const Pb::Register _R_41("Grid<int>", "setConst", Grid<int>::_W_17);
|
||||
static const Pb::Register _R_42("Grid<int>", "addConst", Grid<int>::_W_18);
|
||||
static const Pb::Register _R_43("Grid<int>", "addScaled", Grid<int>::_W_19);
|
||||
static const Pb::Register _R_44("Grid<int>", "mult", Grid<int>::_W_20);
|
||||
static const Pb::Register _R_45("Grid<int>", "multConst", Grid<int>::_W_21);
|
||||
static const Pb::Register _R_46("Grid<int>", "safeDivide", Grid<int>::_W_22);
|
||||
static const Pb::Register _R_47("Grid<int>", "clamp", Grid<int>::_W_23);
|
||||
static const Pb::Register _R_48("Grid<int>", "stomp", Grid<int>::_W_24);
|
||||
static const Pb::Register _R_49("Grid<int>", "permuteAxes", Grid<int>::_W_25);
|
||||
static const Pb::Register _R_50("Grid<int>", "permuteAxesCopyToGrid", Grid<int>::_W_26);
|
||||
static const Pb::Register _R_51("Grid<int>", "join", Grid<int>::_W_27);
|
||||
static const Pb::Register _R_52("Grid<int>", "getMaxAbs", Grid<int>::_W_28);
|
||||
static const Pb::Register _R_53("Grid<int>", "getMax", Grid<int>::_W_29);
|
||||
static const Pb::Register _R_54("Grid<int>", "getMin", Grid<int>::_W_30);
|
||||
static const Pb::Register _R_55("Grid<int>", "getL1", Grid<int>::_W_31);
|
||||
static const Pb::Register _R_56("Grid<int>", "getL2", Grid<int>::_W_32);
|
||||
static const Pb::Register _R_57("Grid<int>", "setBound", Grid<int>::_W_33);
|
||||
static const Pb::Register _R_58("Grid<int>", "setBoundNeumann", Grid<int>::_W_34);
|
||||
static const Pb::Register _R_59("Grid<int>", "getDataPointer", Grid<int>::_W_35);
|
||||
static const Pb::Register _R_60("Grid<int>", "printGrid", Grid<int>::_W_36);
|
||||
static const Pb::Register _R_33("Grid<int>", "Grid", Grid<int>::_W_10);
|
||||
static const Pb::Register _R_34("Grid<int>", "save", Grid<int>::_W_11);
|
||||
static const Pb::Register _R_35("Grid<int>", "load", Grid<int>::_W_12);
|
||||
static const Pb::Register _R_36("Grid<int>", "clear", Grid<int>::_W_13);
|
||||
static const Pb::Register _R_37("Grid<int>", "copyFrom", Grid<int>::_W_14);
|
||||
static const Pb::Register _R_38("Grid<int>", "getGridType", Grid<int>::_W_15);
|
||||
static const Pb::Register _R_39("Grid<int>", "add", Grid<int>::_W_16);
|
||||
static const Pb::Register _R_40("Grid<int>", "sub", Grid<int>::_W_17);
|
||||
static const Pb::Register _R_41("Grid<int>", "setConst", Grid<int>::_W_18);
|
||||
static const Pb::Register _R_42("Grid<int>", "addConst", Grid<int>::_W_19);
|
||||
static const Pb::Register _R_43("Grid<int>", "addScaled", Grid<int>::_W_20);
|
||||
static const Pb::Register _R_44("Grid<int>", "mult", Grid<int>::_W_21);
|
||||
static const Pb::Register _R_45("Grid<int>", "multConst", Grid<int>::_W_22);
|
||||
static const Pb::Register _R_46("Grid<int>", "safeDivide", Grid<int>::_W_23);
|
||||
static const Pb::Register _R_47("Grid<int>", "clamp", Grid<int>::_W_24);
|
||||
static const Pb::Register _R_48("Grid<int>", "stomp", Grid<int>::_W_25);
|
||||
static const Pb::Register _R_49("Grid<int>", "permuteAxes", Grid<int>::_W_26);
|
||||
static const Pb::Register _R_50("Grid<int>", "permuteAxesCopyToGrid", Grid<int>::_W_27);
|
||||
static const Pb::Register _R_51("Grid<int>", "join", Grid<int>::_W_28);
|
||||
static const Pb::Register _R_52("Grid<int>", "getMaxAbs", Grid<int>::_W_29);
|
||||
static const Pb::Register _R_53("Grid<int>", "getMax", Grid<int>::_W_30);
|
||||
static const Pb::Register _R_54("Grid<int>", "getMin", Grid<int>::_W_31);
|
||||
static const Pb::Register _R_55("Grid<int>", "getL1", Grid<int>::_W_32);
|
||||
static const Pb::Register _R_56("Grid<int>", "getL2", Grid<int>::_W_33);
|
||||
static const Pb::Register _R_57("Grid<int>", "setBound", Grid<int>::_W_34);
|
||||
static const Pb::Register _R_58("Grid<int>", "setBoundNeumann", Grid<int>::_W_35);
|
||||
static const Pb::Register _R_59("Grid<int>", "getDataPointer", Grid<int>::_W_36);
|
||||
static const Pb::Register _R_60("Grid<int>", "printGrid", Grid<int>::_W_37);
|
||||
static const Pb::Register _R_61("Grid<Real>", "Grid<Real>", "GridBase");
|
||||
template<> const char *Namify<Grid<Real>>::S = "Grid<Real>";
|
||||
static const Pb::Register _R_62("Grid<Real>", "Grid", Grid<Real>::_W_9);
|
||||
static const Pb::Register _R_63("Grid<Real>", "save", Grid<Real>::_W_10);
|
||||
static const Pb::Register _R_64("Grid<Real>", "load", Grid<Real>::_W_11);
|
||||
static const Pb::Register _R_65("Grid<Real>", "clear", Grid<Real>::_W_12);
|
||||
static const Pb::Register _R_66("Grid<Real>", "copyFrom", Grid<Real>::_W_13);
|
||||
static const Pb::Register _R_67("Grid<Real>", "getGridType", Grid<Real>::_W_14);
|
||||
static const Pb::Register _R_68("Grid<Real>", "add", Grid<Real>::_W_15);
|
||||
static const Pb::Register _R_69("Grid<Real>", "sub", Grid<Real>::_W_16);
|
||||
static const Pb::Register _R_70("Grid<Real>", "setConst", Grid<Real>::_W_17);
|
||||
static const Pb::Register _R_71("Grid<Real>", "addConst", Grid<Real>::_W_18);
|
||||
static const Pb::Register _R_72("Grid<Real>", "addScaled", Grid<Real>::_W_19);
|
||||
static const Pb::Register _R_73("Grid<Real>", "mult", Grid<Real>::_W_20);
|
||||
static const Pb::Register _R_74("Grid<Real>", "multConst", Grid<Real>::_W_21);
|
||||
static const Pb::Register _R_75("Grid<Real>", "safeDivide", Grid<Real>::_W_22);
|
||||
static const Pb::Register _R_76("Grid<Real>", "clamp", Grid<Real>::_W_23);
|
||||
static const Pb::Register _R_77("Grid<Real>", "stomp", Grid<Real>::_W_24);
|
||||
static const Pb::Register _R_78("Grid<Real>", "permuteAxes", Grid<Real>::_W_25);
|
||||
static const Pb::Register _R_79("Grid<Real>", "permuteAxesCopyToGrid", Grid<Real>::_W_26);
|
||||
static const Pb::Register _R_80("Grid<Real>", "join", Grid<Real>::_W_27);
|
||||
static const Pb::Register _R_81("Grid<Real>", "getMaxAbs", Grid<Real>::_W_28);
|
||||
static const Pb::Register _R_82("Grid<Real>", "getMax", Grid<Real>::_W_29);
|
||||
static const Pb::Register _R_83("Grid<Real>", "getMin", Grid<Real>::_W_30);
|
||||
static const Pb::Register _R_84("Grid<Real>", "getL1", Grid<Real>::_W_31);
|
||||
static const Pb::Register _R_85("Grid<Real>", "getL2", Grid<Real>::_W_32);
|
||||
static const Pb::Register _R_86("Grid<Real>", "setBound", Grid<Real>::_W_33);
|
||||
static const Pb::Register _R_87("Grid<Real>", "setBoundNeumann", Grid<Real>::_W_34);
|
||||
static const Pb::Register _R_88("Grid<Real>", "getDataPointer", Grid<Real>::_W_35);
|
||||
static const Pb::Register _R_89("Grid<Real>", "printGrid", Grid<Real>::_W_36);
|
||||
static const Pb::Register _R_62("Grid<Real>", "Grid", Grid<Real>::_W_10);
|
||||
static const Pb::Register _R_63("Grid<Real>", "save", Grid<Real>::_W_11);
|
||||
static const Pb::Register _R_64("Grid<Real>", "load", Grid<Real>::_W_12);
|
||||
static const Pb::Register _R_65("Grid<Real>", "clear", Grid<Real>::_W_13);
|
||||
static const Pb::Register _R_66("Grid<Real>", "copyFrom", Grid<Real>::_W_14);
|
||||
static const Pb::Register _R_67("Grid<Real>", "getGridType", Grid<Real>::_W_15);
|
||||
static const Pb::Register _R_68("Grid<Real>", "add", Grid<Real>::_W_16);
|
||||
static const Pb::Register _R_69("Grid<Real>", "sub", Grid<Real>::_W_17);
|
||||
static const Pb::Register _R_70("Grid<Real>", "setConst", Grid<Real>::_W_18);
|
||||
static const Pb::Register _R_71("Grid<Real>", "addConst", Grid<Real>::_W_19);
|
||||
static const Pb::Register _R_72("Grid<Real>", "addScaled", Grid<Real>::_W_20);
|
||||
static const Pb::Register _R_73("Grid<Real>", "mult", Grid<Real>::_W_21);
|
||||
static const Pb::Register _R_74("Grid<Real>", "multConst", Grid<Real>::_W_22);
|
||||
static const Pb::Register _R_75("Grid<Real>", "safeDivide", Grid<Real>::_W_23);
|
||||
static const Pb::Register _R_76("Grid<Real>", "clamp", Grid<Real>::_W_24);
|
||||
static const Pb::Register _R_77("Grid<Real>", "stomp", Grid<Real>::_W_25);
|
||||
static const Pb::Register _R_78("Grid<Real>", "permuteAxes", Grid<Real>::_W_26);
|
||||
static const Pb::Register _R_79("Grid<Real>", "permuteAxesCopyToGrid", Grid<Real>::_W_27);
|
||||
static const Pb::Register _R_80("Grid<Real>", "join", Grid<Real>::_W_28);
|
||||
static const Pb::Register _R_81("Grid<Real>", "getMaxAbs", Grid<Real>::_W_29);
|
||||
static const Pb::Register _R_82("Grid<Real>", "getMax", Grid<Real>::_W_30);
|
||||
static const Pb::Register _R_83("Grid<Real>", "getMin", Grid<Real>::_W_31);
|
||||
static const Pb::Register _R_84("Grid<Real>", "getL1", Grid<Real>::_W_32);
|
||||
static const Pb::Register _R_85("Grid<Real>", "getL2", Grid<Real>::_W_33);
|
||||
static const Pb::Register _R_86("Grid<Real>", "setBound", Grid<Real>::_W_34);
|
||||
static const Pb::Register _R_87("Grid<Real>", "setBoundNeumann", Grid<Real>::_W_35);
|
||||
static const Pb::Register _R_88("Grid<Real>", "getDataPointer", Grid<Real>::_W_36);
|
||||
static const Pb::Register _R_89("Grid<Real>", "printGrid", Grid<Real>::_W_37);
|
||||
static const Pb::Register _R_90("Grid<Vec3>", "Grid<Vec3>", "GridBase");
|
||||
template<> const char *Namify<Grid<Vec3>>::S = "Grid<Vec3>";
|
||||
static const Pb::Register _R_91("Grid<Vec3>", "Grid", Grid<Vec3>::_W_9);
|
||||
static const Pb::Register _R_92("Grid<Vec3>", "save", Grid<Vec3>::_W_10);
|
||||
static const Pb::Register _R_93("Grid<Vec3>", "load", Grid<Vec3>::_W_11);
|
||||
static const Pb::Register _R_94("Grid<Vec3>", "clear", Grid<Vec3>::_W_12);
|
||||
static const Pb::Register _R_95("Grid<Vec3>", "copyFrom", Grid<Vec3>::_W_13);
|
||||
static const Pb::Register _R_96("Grid<Vec3>", "getGridType", Grid<Vec3>::_W_14);
|
||||
static const Pb::Register _R_97("Grid<Vec3>", "add", Grid<Vec3>::_W_15);
|
||||
static const Pb::Register _R_98("Grid<Vec3>", "sub", Grid<Vec3>::_W_16);
|
||||
static const Pb::Register _R_99("Grid<Vec3>", "setConst", Grid<Vec3>::_W_17);
|
||||
static const Pb::Register _R_100("Grid<Vec3>", "addConst", Grid<Vec3>::_W_18);
|
||||
static const Pb::Register _R_101("Grid<Vec3>", "addScaled", Grid<Vec3>::_W_19);
|
||||
static const Pb::Register _R_102("Grid<Vec3>", "mult", Grid<Vec3>::_W_20);
|
||||
static const Pb::Register _R_103("Grid<Vec3>", "multConst", Grid<Vec3>::_W_21);
|
||||
static const Pb::Register _R_104("Grid<Vec3>", "safeDivide", Grid<Vec3>::_W_22);
|
||||
static const Pb::Register _R_105("Grid<Vec3>", "clamp", Grid<Vec3>::_W_23);
|
||||
static const Pb::Register _R_106("Grid<Vec3>", "stomp", Grid<Vec3>::_W_24);
|
||||
static const Pb::Register _R_107("Grid<Vec3>", "permuteAxes", Grid<Vec3>::_W_25);
|
||||
static const Pb::Register _R_108("Grid<Vec3>", "permuteAxesCopyToGrid", Grid<Vec3>::_W_26);
|
||||
static const Pb::Register _R_109("Grid<Vec3>", "join", Grid<Vec3>::_W_27);
|
||||
static const Pb::Register _R_110("Grid<Vec3>", "getMaxAbs", Grid<Vec3>::_W_28);
|
||||
static const Pb::Register _R_111("Grid<Vec3>", "getMax", Grid<Vec3>::_W_29);
|
||||
static const Pb::Register _R_112("Grid<Vec3>", "getMin", Grid<Vec3>::_W_30);
|
||||
static const Pb::Register _R_113("Grid<Vec3>", "getL1", Grid<Vec3>::_W_31);
|
||||
static const Pb::Register _R_114("Grid<Vec3>", "getL2", Grid<Vec3>::_W_32);
|
||||
static const Pb::Register _R_115("Grid<Vec3>", "setBound", Grid<Vec3>::_W_33);
|
||||
static const Pb::Register _R_116("Grid<Vec3>", "setBoundNeumann", Grid<Vec3>::_W_34);
|
||||
static const Pb::Register _R_117("Grid<Vec3>", "getDataPointer", Grid<Vec3>::_W_35);
|
||||
static const Pb::Register _R_118("Grid<Vec3>", "printGrid", Grid<Vec3>::_W_36);
|
||||
static const Pb::Register _R_91("Grid<Vec3>", "Grid", Grid<Vec3>::_W_10);
|
||||
static const Pb::Register _R_92("Grid<Vec3>", "save", Grid<Vec3>::_W_11);
|
||||
static const Pb::Register _R_93("Grid<Vec3>", "load", Grid<Vec3>::_W_12);
|
||||
static const Pb::Register _R_94("Grid<Vec3>", "clear", Grid<Vec3>::_W_13);
|
||||
static const Pb::Register _R_95("Grid<Vec3>", "copyFrom", Grid<Vec3>::_W_14);
|
||||
static const Pb::Register _R_96("Grid<Vec3>", "getGridType", Grid<Vec3>::_W_15);
|
||||
static const Pb::Register _R_97("Grid<Vec3>", "add", Grid<Vec3>::_W_16);
|
||||
static const Pb::Register _R_98("Grid<Vec3>", "sub", Grid<Vec3>::_W_17);
|
||||
static const Pb::Register _R_99("Grid<Vec3>", "setConst", Grid<Vec3>::_W_18);
|
||||
static const Pb::Register _R_100("Grid<Vec3>", "addConst", Grid<Vec3>::_W_19);
|
||||
static const Pb::Register _R_101("Grid<Vec3>", "addScaled", Grid<Vec3>::_W_20);
|
||||
static const Pb::Register _R_102("Grid<Vec3>", "mult", Grid<Vec3>::_W_21);
|
||||
static const Pb::Register _R_103("Grid<Vec3>", "multConst", Grid<Vec3>::_W_22);
|
||||
static const Pb::Register _R_104("Grid<Vec3>", "safeDivide", Grid<Vec3>::_W_23);
|
||||
static const Pb::Register _R_105("Grid<Vec3>", "clamp", Grid<Vec3>::_W_24);
|
||||
static const Pb::Register _R_106("Grid<Vec3>", "stomp", Grid<Vec3>::_W_25);
|
||||
static const Pb::Register _R_107("Grid<Vec3>", "permuteAxes", Grid<Vec3>::_W_26);
|
||||
static const Pb::Register _R_108("Grid<Vec3>", "permuteAxesCopyToGrid", Grid<Vec3>::_W_27);
|
||||
static const Pb::Register _R_109("Grid<Vec3>", "join", Grid<Vec3>::_W_28);
|
||||
static const Pb::Register _R_110("Grid<Vec3>", "getMaxAbs", Grid<Vec3>::_W_29);
|
||||
static const Pb::Register _R_111("Grid<Vec3>", "getMax", Grid<Vec3>::_W_30);
|
||||
static const Pb::Register _R_112("Grid<Vec3>", "getMin", Grid<Vec3>::_W_31);
|
||||
static const Pb::Register _R_113("Grid<Vec3>", "getL1", Grid<Vec3>::_W_32);
|
||||
static const Pb::Register _R_114("Grid<Vec3>", "getL2", Grid<Vec3>::_W_33);
|
||||
static const Pb::Register _R_115("Grid<Vec3>", "setBound", Grid<Vec3>::_W_34);
|
||||
static const Pb::Register _R_116("Grid<Vec3>", "setBoundNeumann", Grid<Vec3>::_W_35);
|
||||
static const Pb::Register _R_117("Grid<Vec3>", "getDataPointer", Grid<Vec3>::_W_36);
|
||||
static const Pb::Register _R_118("Grid<Vec3>", "printGrid", Grid<Vec3>::_W_37);
|
||||
#endif
|
||||
#ifdef _C_GridBase
|
||||
static const Pb::Register _R_119("GridBase", "GridBase", "PbClass");
|
||||
@@ -118,12 +118,13 @@ static const Pb::Register _R_125("GridBase", "is3D", GridBase::_W_5);
|
||||
static const Pb::Register _R_126("GridBase", "is4D", GridBase::_W_6);
|
||||
static const Pb::Register _R_127("GridBase", "getSizeT", GridBase::_W_7);
|
||||
static const Pb::Register _R_128("GridBase", "getStrideT", GridBase::_W_8);
|
||||
static const Pb::Register _R_129("GridBase", "setName", GridBase::_W_9);
|
||||
#endif
|
||||
#ifdef _C_MACGrid
|
||||
static const Pb::Register _R_129("MACGrid", "MACGrid", "Grid<Vec3>");
|
||||
static const Pb::Register _R_130("MACGrid", "MACGrid", "Grid<Vec3>");
|
||||
template<> const char *Namify<MACGrid>::S = "MACGrid";
|
||||
static const Pb::Register _R_130("MACGrid", "MACGrid", MACGrid::_W_37);
|
||||
static const Pb::Register _R_131("MACGrid", "setBoundMAC", MACGrid::_W_38);
|
||||
static const Pb::Register _R_131("MACGrid", "MACGrid", MACGrid::_W_38);
|
||||
static const Pb::Register _R_132("MACGrid", "setBoundMAC", MACGrid::_W_39);
|
||||
#endif
|
||||
static const Pb::Register _R_7("GridType_TypeNone", 0);
|
||||
static const Pb::Register _R_8("GridType_TypeReal", 1);
|
||||
@@ -253,6 +254,7 @@ void PbRegister_file_7()
|
||||
KEEP_UNUSED(_R_129);
|
||||
KEEP_UNUSED(_R_130);
|
||||
KEEP_UNUSED(_R_131);
|
||||
KEEP_UNUSED(_R_132);
|
||||
}
|
||||
}
|
||||
} // namespace Manta
|
@@ -109,6 +109,10 @@ if(WITH_OPENIMAGEDENOISE)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_EXPERIMENTAL_FEATURES)
|
||||
add_definitions(-DWITH_HAIR_NODES)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_intern_cycles "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
# avoid link failure with clang 3.4 debug
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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,
|
||||
|
@@ -628,6 +628,7 @@ void BlenderSync::sync_particle_hair(
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_HAIR_NODES
|
||||
static float4 hair_point_as_float4(BL::HairPoint b_point)
|
||||
{
|
||||
float4 mP = float3_to_float4(get_float3(b_point.co()));
|
||||
@@ -806,6 +807,15 @@ void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motio
|
||||
export_hair_curves(scene, hair, b_hair);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void BlenderSync::sync_hair(Hair *hair, BL::Object &b_ob, bool motion, int motion_step)
|
||||
{
|
||||
(void)hair;
|
||||
(void)b_ob;
|
||||
(void)motion;
|
||||
(void)motion_step;
|
||||
}
|
||||
#endif
|
||||
|
||||
void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph,
|
||||
BL::Object b_ob,
|
||||
|
@@ -458,15 +458,19 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render,
|
||||
python_thread_state_restore(python_thread_state);
|
||||
b_engine.frame_set(frame, subframe);
|
||||
python_thread_state_save(python_thread_state);
|
||||
sync_camera_motion(b_render, b_cam, width, height, 0.0f);
|
||||
if (b_cam) {
|
||||
sync_camera_motion(b_render, b_cam, width, height, 0.0f);
|
||||
}
|
||||
sync_objects(b_depsgraph, b_v3d, 0.0f);
|
||||
}
|
||||
|
||||
/* Insert motion times from camera. Motion times from other objects
|
||||
* have already been added in a sync_objects call. */
|
||||
uint camera_motion_steps = object_motion_steps(b_cam, b_cam);
|
||||
for (size_t step = 0; step < camera_motion_steps; step++) {
|
||||
motion_times.insert(scene->camera->motion_time(step));
|
||||
if (b_cam) {
|
||||
uint camera_motion_steps = object_motion_steps(b_cam, b_cam);
|
||||
for (size_t step = 0; step < camera_motion_steps; step++) {
|
||||
motion_times.insert(scene->camera->motion_time(step));
|
||||
}
|
||||
}
|
||||
|
||||
/* note iteration over motion_times set happens in sorted order */
|
||||
|
@@ -59,6 +59,7 @@ BlenderSession::BlenderSession(BL::RenderEngine &b_engine,
|
||||
BL::BlendData &b_data,
|
||||
bool preview_osl)
|
||||
: session(NULL),
|
||||
scene(NULL),
|
||||
sync(NULL),
|
||||
b_engine(b_engine),
|
||||
b_userpref(b_userpref),
|
||||
@@ -88,6 +89,7 @@ BlenderSession::BlenderSession(BL::RenderEngine &b_engine,
|
||||
int width,
|
||||
int height)
|
||||
: session(NULL),
|
||||
scene(NULL),
|
||||
sync(NULL),
|
||||
b_engine(b_engine),
|
||||
b_userpref(b_userpref),
|
||||
@@ -970,7 +972,8 @@ void BlenderSession::update_status_progress()
|
||||
remaining_time = (1.0 - (double)progress) * (render_time / (double)progress);
|
||||
|
||||
if (background) {
|
||||
scene_status += " | " + scene->name;
|
||||
if (scene)
|
||||
scene_status += " | " + scene->name;
|
||||
if (b_rlay_name != "")
|
||||
scene_status += ", " + b_rlay_name;
|
||||
|
||||
|
@@ -678,7 +678,7 @@ static ShaderNode *add_node(Scene *scene,
|
||||
* builtin names for packed images and movies
|
||||
*/
|
||||
int scene_frame = b_scene.frame_current();
|
||||
int image_frame = image_user_frame_number(b_image_user, scene_frame);
|
||||
int image_frame = image_user_frame_number(b_image_user, b_image, scene_frame);
|
||||
image->handle = scene->image_manager->add_image(
|
||||
new BlenderImageLoader(b_image, image_frame), image->image_params());
|
||||
}
|
||||
@@ -713,7 +713,7 @@ static ShaderNode *add_node(Scene *scene,
|
||||
|
||||
if (is_builtin) {
|
||||
int scene_frame = b_scene.frame_current();
|
||||
int image_frame = image_user_frame_number(b_image_user, scene_frame);
|
||||
int image_frame = image_user_frame_number(b_image_user, b_image, scene_frame);
|
||||
env->handle = scene->image_manager->add_image(new BlenderImageLoader(b_image, image_frame),
|
||||
env->image_params());
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -238,7 +238,7 @@ static inline string image_user_file_path(BL::ImageUser &iuser,
|
||||
{
|
||||
char filepath[1024];
|
||||
iuser.tile(0);
|
||||
BKE_image_user_frame_calc(NULL, iuser.ptr.data, cfra);
|
||||
BKE_image_user_frame_calc(ima.ptr.data, iuser.ptr.data, cfra);
|
||||
BKE_image_user_file_path(iuser.ptr.data, ima.ptr.data, filepath);
|
||||
|
||||
string filepath_str = string(filepath);
|
||||
@@ -248,9 +248,9 @@ static inline string image_user_file_path(BL::ImageUser &iuser,
|
||||
return filepath_str;
|
||||
}
|
||||
|
||||
static inline int image_user_frame_number(BL::ImageUser &iuser, int cfra)
|
||||
static inline int image_user_frame_number(BL::ImageUser &iuser, BL::Image &ima, int cfra)
|
||||
{
|
||||
BKE_image_user_frame_calc(NULL, iuser.ptr.data, cfra);
|
||||
BKE_image_user_frame_calc(ima.ptr.data, iuser.ptr.data, cfra);
|
||||
return iuser.frame_current();
|
||||
}
|
||||
|
||||
|
@@ -217,43 +217,29 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float
|
||||
class BlenderVolumeLoader : public VDBImageLoader {
|
||||
public:
|
||||
BlenderVolumeLoader(BL::BlendData &b_data, BL::Volume &b_volume, const string &grid_name)
|
||||
: VDBImageLoader(grid_name), b_data(b_data), b_volume(b_volume), unload(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool load_metadata(ImageMetaData &metadata) override
|
||||
: VDBImageLoader(grid_name), b_volume(b_volume)
|
||||
{
|
||||
b_volume.grids.load(b_data.ptr.data);
|
||||
BL::VolumeGrid b_volume_grid = find_grid();
|
||||
|
||||
if (!b_volume_grid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unload = !b_volume_grid.is_loaded();
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
Volume *volume = (Volume *)b_volume.ptr.data;
|
||||
VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data;
|
||||
grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
|
||||
#endif
|
||||
BL::Volume::grids_iterator b_grid_iter;
|
||||
for (b_volume.grids.begin(b_grid_iter); b_grid_iter != b_volume.grids.end(); ++b_grid_iter) {
|
||||
BL::VolumeGrid b_volume_grid(*b_grid_iter);
|
||||
if (b_volume_grid.name() == grid_name) {
|
||||
const bool unload = !b_volume_grid.is_loaded();
|
||||
|
||||
return VDBImageLoader::load_metadata(metadata);
|
||||
}
|
||||
Volume *volume = (Volume *)b_volume.ptr.data;
|
||||
VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data;
|
||||
grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
|
||||
|
||||
bool load_pixels(const ImageMetaData &metadata,
|
||||
void *pixels,
|
||||
const size_t pixel_size,
|
||||
const bool associate_alpha) override
|
||||
{
|
||||
b_volume.grids.load(b_data.ptr.data);
|
||||
BL::VolumeGrid b_volume_grid = find_grid();
|
||||
if (unload) {
|
||||
b_volume_grid.unload();
|
||||
}
|
||||
|
||||
if (!b_volume_grid) {
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VDBImageLoader::load_pixels(metadata, pixels, pixel_size, associate_alpha);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool equals(const ImageLoader &other) const override
|
||||
@@ -263,36 +249,7 @@ class BlenderVolumeLoader : public VDBImageLoader {
|
||||
return b_volume == other_loader.b_volume && grid_name == other_loader.grid_name;
|
||||
}
|
||||
|
||||
void cleanup() override
|
||||
{
|
||||
VDBImageLoader::cleanup();
|
||||
|
||||
BL::VolumeGrid b_volume_grid = find_grid();
|
||||
if (b_volume_grid && unload) {
|
||||
b_volume_grid.unload();
|
||||
}
|
||||
}
|
||||
|
||||
/* Find grid with matching name. Grid point not stored in the class since
|
||||
* grids may be unloaded before we load the pixels, for example for motion
|
||||
* blur where we move between frames. */
|
||||
BL::VolumeGrid find_grid()
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
BL::Volume::grids_iterator b_grid_iter;
|
||||
for (b_volume.grids.begin(b_grid_iter); b_grid_iter != b_volume.grids.end(); ++b_grid_iter) {
|
||||
if (b_grid_iter->name() == grid_name) {
|
||||
return *b_grid_iter;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return BL::VolumeGrid(PointerRNA_NULL);
|
||||
}
|
||||
|
||||
BL::BlendData b_data;
|
||||
BL::Volume b_volume;
|
||||
bool unload;
|
||||
};
|
||||
|
||||
static void sync_volume_object(BL::BlendData &b_data, BL::Object &b_ob, Scene *scene, Mesh *mesh)
|
||||
@@ -342,7 +299,7 @@ static void sync_volume_object(BL::BlendData &b_data, BL::Object &b_ob, Scene *s
|
||||
ImageParams params;
|
||||
params.frame = b_volume.grids.frame();
|
||||
|
||||
attr->data_voxel() = scene->image_manager->add_image(loader, params);
|
||||
attr->data_voxel() = scene->image_manager->add_image(loader, params, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -126,9 +126,13 @@ static void rtc_filter_occluded_func(const RTCFilterFunctionNArguments *args)
|
||||
}
|
||||
else {
|
||||
kernel_embree_convert_hit(kg, ray, hit, ¤t_isect);
|
||||
if (ctx->local_object_id != current_isect.object) {
|
||||
int object = (current_isect.object == OBJECT_NONE) ?
|
||||
kernel_tex_fetch(__prim_object, current_isect.prim) :
|
||||
current_isect.object;
|
||||
if (ctx->local_object_id != object) {
|
||||
/* This tells Embree to continue tracing. */
|
||||
*args->valid = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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 */
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -137,7 +137,7 @@ ccl_device float3 sky_radiance_nishita(KernelGlobals *kg,
|
||||
float sun_rotation = nishita_data[7];
|
||||
float angular_diameter = nishita_data[8];
|
||||
float sun_intensity = nishita_data[9];
|
||||
bool sun_disc = (angular_diameter > 0.0f);
|
||||
bool sun_disc = (angular_diameter >= 0.0f);
|
||||
float3 xyz;
|
||||
/* convert dir to spherical coordinates */
|
||||
float2 direction = direction_to_spherical(dir);
|
||||
|
@@ -30,6 +30,7 @@ set(SRC
|
||||
integrator.cpp
|
||||
jitter.cpp
|
||||
light.cpp
|
||||
light_tree.cpp
|
||||
merge.cpp
|
||||
mesh.cpp
|
||||
mesh_displace.cpp
|
||||
|
@@ -1201,9 +1201,13 @@ void GeometryManager::device_update_volume_images(Device *device, Scene *scene,
|
||||
}
|
||||
|
||||
ImageHandle &handle = attr.data_voxel();
|
||||
const int slot = handle.svm_slot();
|
||||
if (slot != -1) {
|
||||
volume_images.insert(slot);
|
||||
/* We can build directly from OpenVDB data structures, no need to
|
||||
* load such images early. */
|
||||
if (!handle.vdb_loader()) {
|
||||
const int slot = handle.svm_slot();
|
||||
if (slot != -1) {
|
||||
volume_images.insert(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#include "device/device.h"
|
||||
#include "render/colorspace.h"
|
||||
#include "render/image_oiio.h"
|
||||
#include "render/image_vdb.h"
|
||||
#include "render/scene.h"
|
||||
#include "render/stats.h"
|
||||
|
||||
@@ -172,6 +173,31 @@ device_texture *ImageHandle::image_memory(const int tile_index) const
|
||||
return img ? img->mem : NULL;
|
||||
}
|
||||
|
||||
VDBImageLoader *ImageHandle::vdb_loader(const int tile_index) const
|
||||
{
|
||||
if (tile_index >= tile_slots.size()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImageManager::Image *img = manager->images[tile_slots[tile_index]];
|
||||
|
||||
if (img == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImageLoader *loader = img->loader;
|
||||
|
||||
if (loader == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (loader->is_vdb_loader()) {
|
||||
return dynamic_cast<VDBImageLoader *>(loader);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ImageHandle::operator==(const ImageHandle &other) const
|
||||
{
|
||||
return manager == other.manager && tile_slots == other.tile_slots;
|
||||
@@ -258,6 +284,11 @@ bool ImageLoader::equals(const ImageLoader *a, const ImageLoader *b)
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageLoader::is_vdb_loader() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Image Manager */
|
||||
|
||||
ImageManager::ImageManager(const DeviceInfo &info)
|
||||
@@ -362,9 +393,11 @@ ImageHandle ImageManager::add_image(const string &filename,
|
||||
return handle;
|
||||
}
|
||||
|
||||
ImageHandle ImageManager::add_image(ImageLoader *loader, const ImageParams ¶ms)
|
||||
ImageHandle ImageManager::add_image(ImageLoader *loader,
|
||||
const ImageParams ¶ms,
|
||||
const bool builtin)
|
||||
{
|
||||
const int slot = add_image_slot(loader, params, true);
|
||||
const int slot = add_image_slot(loader, params, builtin);
|
||||
|
||||
ImageHandle handle;
|
||||
handle.tile_slots.push_back(slot);
|
||||
|
@@ -39,6 +39,7 @@ class Progress;
|
||||
class RenderStats;
|
||||
class Scene;
|
||||
class ColorSpaceProcessor;
|
||||
class VDBImageLoader;
|
||||
|
||||
/* Image Parameters */
|
||||
class ImageParams {
|
||||
@@ -124,6 +125,8 @@ class ImageLoader {
|
||||
virtual bool equals(const ImageLoader &other) const = 0;
|
||||
static bool equals(const ImageLoader *a, const ImageLoader *b);
|
||||
|
||||
virtual bool is_vdb_loader() const;
|
||||
|
||||
/* Work around for no RTTI. */
|
||||
};
|
||||
|
||||
@@ -149,6 +152,8 @@ class ImageHandle {
|
||||
int svm_slot(const int tile_index = 0) const;
|
||||
device_texture *image_memory(const int tile_index = 0) const;
|
||||
|
||||
VDBImageLoader *vdb_loader(const int tile_index = 0) const;
|
||||
|
||||
protected:
|
||||
vector<int> tile_slots;
|
||||
ImageManager *manager;
|
||||
@@ -169,7 +174,7 @@ class ImageManager {
|
||||
ImageHandle add_image(const string &filename,
|
||||
const ImageParams ¶ms,
|
||||
const vector<int> &tiles);
|
||||
ImageHandle add_image(ImageLoader *loader, const ImageParams ¶ms);
|
||||
ImageHandle add_image(ImageLoader *loader, const ImageParams ¶ms, const bool builtin = true);
|
||||
|
||||
void device_update(Device *device, Scene *scene, Progress &progress);
|
||||
void device_update_slot(Device *device, Scene *scene, int slot, Progress *progress);
|
||||
|
@@ -185,4 +185,16 @@ void VDBImageLoader::cleanup()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VDBImageLoader::is_vdb_loader() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::GridBase::ConstPtr VDBImageLoader::get_grid()
|
||||
{
|
||||
return grid;
|
||||
}
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -43,6 +43,12 @@ class VDBImageLoader : public ImageLoader {
|
||||
|
||||
virtual void cleanup() override;
|
||||
|
||||
virtual bool is_vdb_loader() const override;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::GridBase::ConstPtr get_grid();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
string grid_name;
|
||||
#ifdef WITH_OPENVDB
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -634,7 +985,7 @@ void LightManager::device_update_background(Device *device,
|
||||
sun_direction = transform_direction(&sky_transform, sun_direction);
|
||||
|
||||
/* Pack sun direction and size. */
|
||||
float half_angle = sky->sun_size * 0.5f;
|
||||
float half_angle = sky->get_sun_size() * 0.5f;
|
||||
kbackground->sun = make_float4(
|
||||
sun_direction.x, sun_direction.y, sun_direction.z, half_angle);
|
||||
|
||||
@@ -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
|
||||
|
@@ -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;
|
||||
|
601
intern/cycles/render/light_tree.cpp
Normal file
601
intern/cycles/render/light_tree.cpp
Normal 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 ¢roid_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
|
356
intern/cycles/render/light_tree.h
Normal file
356
intern/cycles/render/light_tree.h
Normal 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 ¢roid_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 ¢roid_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__
|
@@ -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;
|
||||
|
@@ -15,34 +15,25 @@
|
||||
*/
|
||||
|
||||
#include "render/attribute.h"
|
||||
#include "render/image_vdb.h"
|
||||
#include "render/mesh.h"
|
||||
#include "render/scene.h"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include <openvdb/tools/Dense.h>
|
||||
# include <openvdb/tools/GridTransformer.h>
|
||||
# include <openvdb/tools/Morphology.h>
|
||||
#endif
|
||||
|
||||
#include "util/util_foreach.h"
|
||||
#include "util/util_hash.h"
|
||||
#include "util/util_logging.h"
|
||||
#include "util/util_openvdb.h"
|
||||
#include "util/util_progress.h"
|
||||
#include "util/util_types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
const int64_t VOXEL_INDEX_NONE = -1;
|
||||
|
||||
static int64_t compute_voxel_index(const int3 &resolution, int64_t x, int64_t y, int64_t z)
|
||||
{
|
||||
if (x < 0 || x >= resolution.x) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
else if (y < 0 || y >= resolution.y) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
else if (z < 0 || z >= resolution.z) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
|
||||
return x + y * resolution.x + z * resolution.x * resolution.y;
|
||||
}
|
||||
|
||||
struct QuadData {
|
||||
int v0, v1, v2, v3;
|
||||
|
||||
@@ -123,122 +114,148 @@ static void create_quad(int3 corners[8],
|
||||
quads.push_back(quad);
|
||||
}
|
||||
|
||||
struct VolumeParams {
|
||||
int3 resolution;
|
||||
float3 cell_size;
|
||||
float3 start_point;
|
||||
int pad_size;
|
||||
};
|
||||
|
||||
static const int CUBE_SIZE = 8;
|
||||
|
||||
/* Create a mesh from a volume.
|
||||
*
|
||||
* The way the algorithm works is as follows:
|
||||
*
|
||||
* - The coordinates of active voxels from a dense volume (or 3d image) are
|
||||
* gathered inside an auxiliary volume.
|
||||
* - Each set of coordinates of an CUBE_SIZE cube are mapped to the same
|
||||
* coordinate of the auxiliary volume.
|
||||
* - Quads are created between active and non-active voxels in the auxiliary
|
||||
* volume to generate a tight mesh around the volume.
|
||||
* - The topologies of input OpenVDB grids are merged into a temporary grid.
|
||||
* - Voxels of the temporary grid are dilated to account for the padding necessary for volume
|
||||
* sampling.
|
||||
* - Quads are created on the boundary between active and inactive leaf nodes of the temporary
|
||||
* grid.
|
||||
*/
|
||||
class VolumeMeshBuilder {
|
||||
/* Auxiliary volume that is used to check if a node already added. */
|
||||
vector<char> grid;
|
||||
|
||||
/* The resolution of the auxiliary volume, set to be equal to 1/CUBE_SIZE
|
||||
* of the original volume on each axis. */
|
||||
int3 res;
|
||||
|
||||
size_t number_of_nodes;
|
||||
|
||||
/* Offset due to padding in the original grid. Padding will transform the
|
||||
* coordinates of the original grid from 0...res to -padding...res+padding,
|
||||
* so some coordinates are negative, and we need to properly account for
|
||||
* them. */
|
||||
int3 pad_offset;
|
||||
|
||||
VolumeParams *params;
|
||||
|
||||
public:
|
||||
VolumeMeshBuilder(VolumeParams *volume_params);
|
||||
#ifdef WITH_OPENVDB
|
||||
/* use a MaskGrid to store the topology to save memory */
|
||||
openvdb::MaskGrid::Ptr topology_grid;
|
||||
openvdb::CoordBBox bbox;
|
||||
#endif
|
||||
bool first_grid;
|
||||
|
||||
void add_node(int x, int y, int z);
|
||||
VolumeMeshBuilder();
|
||||
|
||||
void add_node_with_padding(int x, int y, int z);
|
||||
#ifdef WITH_OPENVDB
|
||||
void add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping);
|
||||
#endif
|
||||
|
||||
void create_mesh(vector<float3> &vertices, vector<int> &indices, vector<float3> &face_normals);
|
||||
void add_padding(int pad_size);
|
||||
|
||||
void create_mesh(vector<float3> &vertices,
|
||||
vector<int> &indices,
|
||||
vector<float3> &face_normals,
|
||||
const float face_overlap_avoidance);
|
||||
|
||||
private:
|
||||
void generate_vertices_and_quads(vector<int3> &vertices_is, vector<QuadData> &quads);
|
||||
|
||||
void convert_object_space(const vector<int3> &vertices, vector<float3> &out_vertices);
|
||||
void convert_object_space(const vector<int3> &vertices,
|
||||
vector<float3> &out_vertices,
|
||||
const float face_overlap_avoidance);
|
||||
|
||||
void convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
vector<int> &tris,
|
||||
vector<float3> &face_normals);
|
||||
|
||||
bool empty_grid() const;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
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);
|
||||
|
||||
if (do_clipping) {
|
||||
using ValueType = typename GridType::ValueType;
|
||||
typename GridType::Ptr copy = typed_grid->deepCopy();
|
||||
typename GridType::ValueOnIter iter = copy->beginValueOn();
|
||||
|
||||
for (; iter; ++iter) {
|
||||
if (iter.getValue() < ValueType(volume_clipping)) {
|
||||
iter.setValueOff();
|
||||
}
|
||||
}
|
||||
|
||||
typed_grid = copy;
|
||||
}
|
||||
|
||||
topology_grid->topologyUnion(*typed_grid);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
VolumeMeshBuilder::VolumeMeshBuilder(VolumeParams *volume_params)
|
||||
VolumeMeshBuilder::VolumeMeshBuilder()
|
||||
{
|
||||
params = volume_params;
|
||||
number_of_nodes = 0;
|
||||
|
||||
const int64_t x = divide_up(params->resolution.x, CUBE_SIZE);
|
||||
const int64_t y = divide_up(params->resolution.y, CUBE_SIZE);
|
||||
const int64_t z = divide_up(params->resolution.z, CUBE_SIZE);
|
||||
|
||||
/* Adding 2*pad_size since we pad in both positive and negative directions
|
||||
* along the axis. */
|
||||
const int64_t px = divide_up(params->resolution.x + 2 * params->pad_size, CUBE_SIZE);
|
||||
const int64_t py = divide_up(params->resolution.y + 2 * params->pad_size, CUBE_SIZE);
|
||||
const int64_t pz = divide_up(params->resolution.z + 2 * params->pad_size, CUBE_SIZE);
|
||||
|
||||
res = make_int3(px, py, pz);
|
||||
pad_offset = make_int3(px - x, py - y, pz - z);
|
||||
|
||||
grid.resize(px * py * pz, 0);
|
||||
first_grid = true;
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::add_node(int x, int y, int z)
|
||||
#ifdef WITH_OPENVDB
|
||||
void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid,
|
||||
bool do_clipping,
|
||||
float volume_clipping)
|
||||
{
|
||||
/* Map coordinates to index space. */
|
||||
const int index_x = (x / CUBE_SIZE) + pad_offset.x;
|
||||
const int index_y = (y / CUBE_SIZE) + pad_offset.y;
|
||||
const int index_z = (z / CUBE_SIZE) + pad_offset.z;
|
||||
|
||||
assert((index_x >= 0) && (index_y >= 0) && (index_z >= 0));
|
||||
|
||||
const int64_t index = compute_voxel_index(res, index_x, index_y, index_z);
|
||||
if (index == VOXEL_INDEX_NONE) {
|
||||
return;
|
||||
/* set the transform of our grid from the first one */
|
||||
if (first_grid) {
|
||||
topology_grid = openvdb::MaskGrid::create();
|
||||
topology_grid->setTransform(grid->transform().copy());
|
||||
first_grid = false;
|
||||
}
|
||||
/* if the transforms do not match, we need to resample one of the grids so that
|
||||
* its index space registers with that of the other, here we resample our mask
|
||||
* grid so memory usage is kept low */
|
||||
else if (topology_grid->transform() != grid->transform()) {
|
||||
openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree();
|
||||
temp_grid->setTransform(grid->transform().copy());
|
||||
openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(*topology_grid, *temp_grid);
|
||||
topology_grid = temp_grid;
|
||||
topology_grid->setTransform(grid->transform().copy());
|
||||
}
|
||||
|
||||
/* We already have a node here. */
|
||||
if (grid[index] == 1) {
|
||||
return;
|
||||
if (grid->isType<openvdb::FloatGrid>()) {
|
||||
merge_grid<openvdb::FloatGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3fGrid>()) {
|
||||
merge_grid<openvdb::Vec3fGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec4fGrid>()) {
|
||||
merge_grid<openvdb::Vec4fGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::BoolGrid>()) {
|
||||
merge_grid<openvdb::BoolGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::DoubleGrid>()) {
|
||||
merge_grid<openvdb::DoubleGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Int32Grid>()) {
|
||||
merge_grid<openvdb::Int32Grid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Int64Grid>()) {
|
||||
merge_grid<openvdb::Int64Grid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3IGrid>()) {
|
||||
merge_grid<openvdb::Vec3IGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3dGrid>()) {
|
||||
merge_grid<openvdb::Vec3dGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::MaskGrid>()) {
|
||||
topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::MaskGrid>(grid));
|
||||
}
|
||||
|
||||
++number_of_nodes;
|
||||
|
||||
grid[index] = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void VolumeMeshBuilder::add_node_with_padding(int x, int y, int z)
|
||||
void VolumeMeshBuilder::add_padding(int pad_size)
|
||||
{
|
||||
for (int px = x - params->pad_size; px < x + params->pad_size; ++px) {
|
||||
for (int py = y - params->pad_size; py < y + params->pad_size; ++py) {
|
||||
for (int pz = z - params->pad_size; pz < z + params->pad_size; ++pz) {
|
||||
add_node(px, py, pz);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::tools::dilateVoxels(topology_grid->tree(), pad_size);
|
||||
#else
|
||||
(void)pad_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
vector<int> &indices,
|
||||
vector<float3> &face_normals)
|
||||
vector<float3> &face_normals,
|
||||
const float face_overlap_avoidance)
|
||||
{
|
||||
/* We create vertices in index space (is), and only convert them to object
|
||||
* space when done. */
|
||||
@@ -247,7 +264,7 @@ void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
|
||||
generate_vertices_and_quads(vertices_is, quads);
|
||||
|
||||
convert_object_space(vertices_is, vertices);
|
||||
convert_object_space(vertices_is, vertices, face_overlap_avoidance);
|
||||
|
||||
convert_quads_to_tris(quads, indices, face_normals);
|
||||
}
|
||||
@@ -255,85 +272,97 @@ void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_is,
|
||||
vector<QuadData> &quads)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
const openvdb::MaskGrid::TreeType &tree = topology_grid->tree();
|
||||
tree.evalLeafBoundingBox(bbox);
|
||||
|
||||
const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z());
|
||||
|
||||
unordered_map<size_t, int> used_verts;
|
||||
|
||||
for (int z = 0; z < res.z; ++z) {
|
||||
for (int y = 0; y < res.y; ++y) {
|
||||
for (int x = 0; x < res.x; ++x) {
|
||||
int64_t voxel_index = compute_voxel_index(res, x, y, z);
|
||||
if (grid[voxel_index] == 0) {
|
||||
continue;
|
||||
}
|
||||
for (auto iter = tree.cbeginLeaf(); iter; ++iter) {
|
||||
openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox();
|
||||
/* +1 to convert from exclusive to include bounds. */
|
||||
leaf_bbox.max() = leaf_bbox.max().offsetBy(1);
|
||||
|
||||
/* Compute min and max coords of the node in index space. */
|
||||
int3 min = make_int3((x - pad_offset.x) * CUBE_SIZE,
|
||||
(y - pad_offset.y) * CUBE_SIZE,
|
||||
(z - pad_offset.z) * CUBE_SIZE);
|
||||
int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z());
|
||||
int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z());
|
||||
|
||||
/* Maximum is just CUBE_SIZE voxels away from minimum on each axis. */
|
||||
int3 max = make_int3(min.x + CUBE_SIZE, min.y + CUBE_SIZE, min.z + CUBE_SIZE);
|
||||
int3 corners[8] = {
|
||||
make_int3(min[0], min[1], min[2]),
|
||||
make_int3(max[0], min[1], min[2]),
|
||||
make_int3(max[0], max[1], min[2]),
|
||||
make_int3(min[0], max[1], min[2]),
|
||||
make_int3(min[0], min[1], max[2]),
|
||||
make_int3(max[0], min[1], max[2]),
|
||||
make_int3(max[0], max[1], max[2]),
|
||||
make_int3(min[0], max[1], max[2]),
|
||||
};
|
||||
|
||||
int3 corners[8] = {
|
||||
make_int3(min[0], min[1], min[2]),
|
||||
make_int3(max[0], min[1], min[2]),
|
||||
make_int3(max[0], max[1], min[2]),
|
||||
make_int3(min[0], max[1], min[2]),
|
||||
make_int3(min[0], min[1], max[2]),
|
||||
make_int3(max[0], min[1], max[2]),
|
||||
make_int3(max[0], max[1], max[2]),
|
||||
make_int3(min[0], max[1], max[2]),
|
||||
};
|
||||
/* Only create a quad if on the border between an active and an inactive leaf.
|
||||
*
|
||||
* We verify that a leaf exists by probing a coordinate that is at its center,
|
||||
* to do so we compute the center of the current leaf and offset this coordinate
|
||||
* by the size of a leaf in each direction.
|
||||
*/
|
||||
static const int LEAF_DIM = openvdb::MaskGrid::TreeType::LeafNodeType::DIM;
|
||||
auto center = leaf_bbox.min() + openvdb::Coord(LEAF_DIM / 2);
|
||||
|
||||
/* Only create a quad if on the border between an active and
|
||||
* an inactive node.
|
||||
*/
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x - 1, y, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MIN);
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x + 1, y, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MAX);
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y - 1, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MIN);
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y + 1, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MAX);
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y, z - 1);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y, z + 1);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MAX);
|
||||
}
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)vertices_is;
|
||||
(void)quads;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::convert_object_space(const vector<int3> &vertices,
|
||||
vector<float3> &out_vertices)
|
||||
vector<float3> &out_vertices,
|
||||
const float face_overlap_avoidance)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
/* compute the offset for the face overlap avoidance */
|
||||
bbox = topology_grid->evalActiveVoxelBoundingBox();
|
||||
openvdb::Coord dim = bbox.dim();
|
||||
|
||||
float3 cell_size = make_float3(1.0f / dim.x(), 1.0f / dim.y(), 1.0f / dim.z());
|
||||
float3 point_offset = cell_size * face_overlap_avoidance;
|
||||
|
||||
out_vertices.reserve(vertices.size());
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
float3 vertex = make_float3(vertices[i].x, vertices[i].y, vertices[i].z);
|
||||
vertex *= params->cell_size;
|
||||
vertex += params->start_point;
|
||||
|
||||
out_vertices.push_back(vertex);
|
||||
openvdb::math::Vec3d p = topology_grid->indexToWorld(
|
||||
openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z));
|
||||
float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z());
|
||||
out_vertices.push_back(vertex + point_offset);
|
||||
}
|
||||
#else
|
||||
(void)vertices;
|
||||
(void)out_vertices;
|
||||
(void)face_overlap_avoidance;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
@@ -359,57 +388,128 @@ void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
bool VolumeMeshBuilder::empty_grid() const
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
return !topology_grid || topology_grid->tree().leafCount() == 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct VoxelAttributeGrid {
|
||||
float *data;
|
||||
int channels;
|
||||
};
|
||||
#ifdef WITH_OPENVDB
|
||||
template<typename GridType>
|
||||
static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory,
|
||||
float volume_clipping,
|
||||
Transform transform_3d)
|
||||
{
|
||||
using ValueType = typename GridType::ValueType;
|
||||
|
||||
openvdb::CoordBBox dense_bbox(0,
|
||||
0,
|
||||
0,
|
||||
image_memory->data_width - 1,
|
||||
image_memory->data_height - 1,
|
||||
image_memory->data_depth - 1);
|
||||
openvdb::tools::Dense<ValueType, openvdb::tools::MemoryLayout::LayoutXYZ> dense(
|
||||
dense_bbox, static_cast<ValueType *>(image_memory->host_pointer));
|
||||
|
||||
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. */
|
||||
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);
|
||||
|
||||
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::math::Transform::Ptr index_to_world_tfm =
|
||||
openvdb::math::Transform::createLinearTransform(index_to_world_mat);
|
||||
|
||||
sparse->setTransform(index_to_world_tfm);
|
||||
|
||||
return sparse;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
{
|
||||
string msg = string_printf("Computing Volume Mesh %s", mesh->name.c_str());
|
||||
progress.set_status("Updating Mesh", msg);
|
||||
|
||||
vector<VoxelAttributeGrid> voxel_grids;
|
||||
|
||||
/* Compute volume parameters. */
|
||||
VolumeParams volume_params;
|
||||
volume_params.resolution = make_int3(0, 0, 0);
|
||||
|
||||
Transform transform = transform_identity();
|
||||
VolumeMeshBuilder builder;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
foreach (Attribute &attr, mesh->attributes.attributes) {
|
||||
if (attr.element != ATTR_ELEMENT_VOXEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool do_clipping = false;
|
||||
|
||||
ImageHandle &handle = attr.data_voxel();
|
||||
device_texture *image_memory = handle.image_memory();
|
||||
int3 resolution = make_int3(
|
||||
image_memory->data_width, image_memory->data_height, image_memory->data_depth);
|
||||
|
||||
if (volume_params.resolution == make_int3(0, 0, 0)) {
|
||||
volume_params.resolution = resolution;
|
||||
}
|
||||
else if (volume_params.resolution != resolution) {
|
||||
/* TODO: support this as it's common for OpenVDB. */
|
||||
VLOG(1) << "Can't create accurate volume mesh, all voxel grid resolutions must be equal\n";
|
||||
continue;
|
||||
/* Try building from OpenVDB grid directly. */
|
||||
VDBImageLoader *vdb_loader = handle.vdb_loader();
|
||||
openvdb::GridBase::ConstPtr grid;
|
||||
if (vdb_loader) {
|
||||
grid = vdb_loader->get_grid();
|
||||
|
||||
/* If building from an OpenVDB grid, we need to manually clip the values. */
|
||||
do_clipping = true;
|
||||
}
|
||||
|
||||
VoxelAttributeGrid voxel_grid;
|
||||
voxel_grid.data = static_cast<float *>(image_memory->host_pointer);
|
||||
voxel_grid.channels = image_memory->data_elements;
|
||||
voxel_grids.push_back(voxel_grid);
|
||||
/* Else fall back to creating an OpenVDB grid from the dense volume data. */
|
||||
if (!grid) {
|
||||
device_texture *image_memory = handle.image_memory();
|
||||
|
||||
/* TODO: support multiple transforms. */
|
||||
if (image_memory->info.use_transform_3d) {
|
||||
transform = image_memory->info.transform_3d;
|
||||
if (image_memory->data_elements == 1) {
|
||||
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);
|
||||
}
|
||||
else if (image_memory->data_elements == 4) {
|
||||
grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(
|
||||
image_memory, mesh->volume_clipping, handle.metadata().transform_3d);
|
||||
}
|
||||
}
|
||||
|
||||
if (grid) {
|
||||
builder.add_grid(grid, do_clipping, mesh->volume_clipping);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (voxel_grids.empty()) {
|
||||
if (builder.empty_grid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,60 +538,24 @@ void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute start point and cell size from transform. */
|
||||
const int3 resolution = volume_params.resolution;
|
||||
float3 start_point = make_float3(0.0f, 0.0f, 0.0f);
|
||||
float3 cell_size = make_float3(1.0f / resolution.x, 1.0f / resolution.y, 1.0f / resolution.z);
|
||||
|
||||
/* TODO: support arbitrary transforms, not just scale + translate. */
|
||||
const Transform itfm = transform_inverse(transform);
|
||||
start_point = transform_point(&itfm, start_point);
|
||||
cell_size = transform_direction(&itfm, cell_size);
|
||||
builder.add_padding(pad_size);
|
||||
|
||||
/* Slightly offset vertex coordinates to avoid overlapping faces with other
|
||||
* volumes or meshes. The proper solution would be to improve intersection in
|
||||
* the kernel to support robust handling of multiple overlapping faces or use
|
||||
* an all-hit intersection similar to shadows. */
|
||||
const float3 face_overlap_avoidance = cell_size * 0.1f *
|
||||
hash_uint_to_float(hash_string(mesh->name.c_str()));
|
||||
|
||||
volume_params.start_point = start_point + face_overlap_avoidance;
|
||||
volume_params.cell_size = cell_size;
|
||||
volume_params.pad_size = pad_size;
|
||||
|
||||
/* Build bounding mesh around non-empty volume cells. */
|
||||
VolumeMeshBuilder builder(&volume_params);
|
||||
const float clipping = mesh->volume_clipping;
|
||||
|
||||
for (int z = 0; z < resolution.z; ++z) {
|
||||
for (int y = 0; y < resolution.y; ++y) {
|
||||
for (int x = 0; x < resolution.x; ++x) {
|
||||
int64_t voxel_index = compute_voxel_index(resolution, x, y, z);
|
||||
|
||||
for (size_t i = 0; i < voxel_grids.size(); ++i) {
|
||||
const VoxelAttributeGrid &voxel_grid = voxel_grids[i];
|
||||
const int channels = voxel_grid.channels;
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
if (voxel_grid.data[voxel_index * channels + c] >= clipping) {
|
||||
builder.add_node_with_padding(x, y, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const float face_overlap_avoidance = 0.1f * hash_uint_to_float(hash_string(mesh->name.c_str()));
|
||||
|
||||
/* Create mesh. */
|
||||
vector<float3> vertices;
|
||||
vector<int> indices;
|
||||
vector<float3> face_normals;
|
||||
builder.create_mesh(vertices, indices, face_normals);
|
||||
builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance);
|
||||
|
||||
mesh->clear(true);
|
||||
mesh->reserve_mesh(vertices.size(), indices.size() / 3);
|
||||
mesh->used_shaders.push_back(volume_shader);
|
||||
mesh->need_update_rebuild = true;
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
mesh->add_vertex(vertices[i]);
|
||||
@@ -514,10 +578,6 @@ void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
indices.size() * sizeof(int)) /
|
||||
(1024.0 * 1024.0)
|
||||
<< "Mb.";
|
||||
|
||||
VLOG(1) << "Memory usage volume grid: "
|
||||
<< (resolution.x * resolution.y * resolution.z * sizeof(float)) / (1024.0 * 1024.0)
|
||||
<< "Mb.";
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -776,7 +776,7 @@ static void sky_texture_precompute_nishita(SunSky *sunsky,
|
||||
sunsky->nishita_data[5] = pixel_top[2];
|
||||
sunsky->nishita_data[6] = sun_elevation;
|
||||
sunsky->nishita_data[7] = sun_rotation;
|
||||
sunsky->nishita_data[8] = sun_disc ? sun_size : 0.0f;
|
||||
sunsky->nishita_data[8] = sun_disc ? sun_size : -1.0f;
|
||||
sunsky->nishita_data[9] = sun_intensity;
|
||||
}
|
||||
|
||||
@@ -834,7 +834,7 @@ void SkyTextureNode::compile(SVMCompiler &compiler)
|
||||
|
||||
sky_texture_precompute_nishita(&sunsky,
|
||||
sun_disc,
|
||||
sun_size,
|
||||
get_sun_size(),
|
||||
sun_intensity,
|
||||
sun_elevation,
|
||||
sun_rotation,
|
||||
@@ -930,7 +930,7 @@ void SkyTextureNode::compile(OSLCompiler &compiler)
|
||||
|
||||
sky_texture_precompute_nishita(&sunsky,
|
||||
sun_disc,
|
||||
sun_size,
|
||||
get_sun_size(),
|
||||
sun_intensity,
|
||||
sun_elevation,
|
||||
sun_rotation,
|
||||
|
@@ -179,6 +179,12 @@ class SkyTextureNode : public TextureNode {
|
||||
float ozone_density;
|
||||
float3 vector;
|
||||
ImageHandle handle;
|
||||
|
||||
float get_sun_size()
|
||||
{
|
||||
/* Clamping for numerical precision. */
|
||||
return fmaxf(sun_size, 0.0005f);
|
||||
}
|
||||
};
|
||||
|
||||
class OutputNode : public ShaderNode {
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -945,8 +945,14 @@ void Session::set_pause(bool pause_)
|
||||
}
|
||||
}
|
||||
|
||||
if (notify)
|
||||
pause_cond.notify_all();
|
||||
if (session_thread) {
|
||||
if (notify) {
|
||||
pause_cond.notify_all();
|
||||
}
|
||||
}
|
||||
else if (pause_) {
|
||||
update_status_time(pause_);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::set_denoising(const DenoiseParams &denoising)
|
||||
@@ -1150,8 +1156,15 @@ bool Session::render_need_denoise(bool &delayed)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Immediately denoise when we reach the start sample or last sample. */
|
||||
const int num_samples_finished = tile_manager.state.sample + 1;
|
||||
if (num_samples_finished == params.denoising.start_sample ||
|
||||
num_samples_finished == params.samples) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Do not denoise until the sample at which denoising should start is reached. */
|
||||
if (tile_manager.state.sample < min(params.denoising.start_sample, params.samples - 1)) {
|
||||
if (num_samples_finished < params.denoising.start_sample) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user