Compare commits
415 Commits
temp-ghost
...
temp-image
Author | SHA1 | Date | |
---|---|---|---|
c81d830ba0 | |||
3255ddc2f8 | |||
8db9d6bd6b | |||
2dc51fccb8 | |||
4d05a000cb | |||
f8ddf16e5a | |||
6df6e537a3 | |||
cc1ba74ce2 | |||
38f793d349 | |||
0ba5e6a8dd | |||
ff280aba1b | |||
7ccb299fb7 | |||
0b85d6a030 | |||
dcd4eb4c25 | |||
0e90896cba | |||
a5f9f7e2fc | |||
a459018a99 | |||
c1d4105005 | |||
ff324ab716 | |||
fd9b197226 | |||
3124241256 | |||
48b5dcdbe8 | |||
de9f32a666 | |||
38e1b92b7a | |||
7d99c51e17 | |||
bc9548da80 | |||
![]() |
31ccf79011 | ||
658220e815 | |||
64541b242a | |||
87582d95df | |||
995f757049 | |||
16b6116b9d | |||
8f213e7436 | |||
37b02b0432 | |||
adea6681c0 | |||
212b054bb3 | |||
7a6630e5ce | |||
44748e6fe3 | |||
6428c847fd | |||
67dd652557 | |||
979930f8b6 | |||
997ff54d30 | |||
31943d1313 | |||
ea14c48c09 | |||
979b295154 | |||
d486f33d63 | |||
f68e50a263 | |||
7465aa8965 | |||
26f66e1423 | |||
db54b99ee1 | |||
ed0125afe5 | |||
a16ef95ff6 | |||
f450d39ada | |||
644afda7eb | |||
7a12934f1e | |||
e29068aaed | |||
![]() |
587a1b16ae | ||
294e41477b | |||
0808eaf44e | |||
5270610b29 | |||
1af8ddf69f | |||
c82749c0c0 | |||
2ce6ac462b | |||
ca2ca0ce5d | |||
f646a4f22c | |||
ee89f213de | |||
0731d78d00 | |||
ccae00c9e2 | |||
83077d3683 | |||
44ab02fc5c | |||
42b51bf6a9 | |||
9cb061f4f0 | |||
0dee238c8c | |||
2b914a2ecb | |||
cc6bdac921 | |||
997e143a50 | |||
9719fd6964 | |||
![]() |
18e386613c | ||
ce16fa0f4c | |||
2155bdd500 | |||
1c26341464 | |||
ab4926bcff | |||
948f13a8e7 | |||
71071a25a0 | |||
2a33875065 | |||
0302ab4e02 | |||
6b7119f9ed | |||
99dc90accc | |||
5b8e2ebd97 | |||
c5e71cebaa | |||
ab8946f957 | |||
3d9f4012dc | |||
e2f6fb5d35 | |||
2bce3c0ac4 | |||
e028662f78 | |||
6a7917162c | |||
79498d4463 | |||
ea86ec200a | |||
b8c7e93a65 | |||
d57f68616a | |||
6d22aa2f84 | |||
198460f6a4 | |||
caac5686c5 | |||
3d5a4fbcc2 | |||
88c6d824e7 | |||
39615cd3b7 | |||
5f0120cd35 | |||
0197b524e4 | |||
46f991dbae | |||
6b0e769d14 | |||
09ee781a67 | |||
587b213fe1 | |||
4d5e8b7caa | |||
e78cd27565 | |||
b768a2bf2f | |||
8842a8c4c3 | |||
90ea1b7643 | |||
fefe7ddf39 | |||
2466b2e43c | |||
730fd0a257 | |||
4c1b250e17 | |||
25501983bb | |||
009f7de619 | |||
![]() |
b132e3b3ce | ||
b5ebc9bb24 | |||
a179246e1f | |||
f95768d1e0 | |||
5e4dcb8cf0 | |||
1a2e2dcddc | |||
5c580ff457 | |||
9f3b0e41bb | |||
3cebc58936 | |||
4aac5b92c1 | |||
65393add52 | |||
918282d391 | |||
7151c2dc3e | |||
1017b493ed | |||
fde628ddb3 | |||
8fa5206ab0 | |||
222b64fcdc | |||
b25c301c15 | |||
396b407c7d | |||
ac51d331df | |||
db1728096a | |||
31b3b07ad7 | |||
f37e8c2e96 | |||
692474ccf9 | |||
b582028b12 | |||
39c9164ea1 | |||
ccc9eef1b9 | |||
f68da703a5 | |||
cfaca0d9ab | |||
2165136740 | |||
b78b6e3cd7 | |||
7cdcb76815 | |||
507b724056 | |||
0b13e7ce0f | |||
![]() |
c17d7ddabe | ||
![]() |
5c1cc79cf4 | ||
429771fed5 | |||
![]() |
18de712257 | ||
313c2e9105 | |||
19dc2157cd | |||
249acdf529 | |||
![]() |
133dde41bb | ||
ae081b2de1 | |||
d602d4f15f | |||
5ce72bba7e | |||
b99cdf7472 | |||
b7d27ce914 | |||
cb7e36cfa5 | |||
6c4c09b2df | |||
68a0846021 | |||
3f9febcabf | |||
412fdc9698 | |||
9f753e5649 | |||
8fa8cea8e0 | |||
d7f124f06f | |||
71f9fbcf35 | |||
85d9f12339 | |||
7b08298927 | |||
01f4b9b32d | |||
57613630c7 | |||
fdf1837120 | |||
eae2917fde | |||
2b639f671f | |||
2b85151a32 | |||
863cd1ea8e | |||
e97443478e | |||
4067e6bc41 | |||
3d1594417b | |||
6fdddae2b0 | |||
01a38c2be9 | |||
0ed4865fd0 | |||
0ad8f3ff58 | |||
1fc5dc3bf3 | |||
48143ab001 | |||
b1cc2a48f2 | |||
2e1a479e29 | |||
d1d2946f59 | |||
07200eaa85 | |||
745851e26b | |||
1bacd09abb | |||
719ad4f93f | |||
9bdde6ca96 | |||
6d52975019 | |||
7540842ca7 | |||
ca5062071c | |||
008070e16c | |||
1a34bbc27c | |||
da363d831b | |||
bbb389589a | |||
56ae4089eb | |||
7e7c6bc468 | |||
b0bf10889b | |||
33ce83984d | |||
33b3645d97 | |||
9eb1d62ba6 | |||
![]() |
c47b6978e3 | ||
19bb30baf6 | |||
5758d114c1 | |||
d96859c5b1 | |||
7a9fce28c0 | |||
70041ced14 | |||
6e26d0645e | |||
0940719b5a | |||
baba5d2214 | |||
653e3e2689 | |||
bcabd04e32 | |||
a059b1b0f1 | |||
99c5146e9a | |||
4ed649352f | |||
c02ec74405 | |||
143e74c0b8 | |||
e010890e82 | |||
95003c99d9 | |||
d961119563 | |||
57a20b6d52 | |||
5b4efaeeb3 | |||
3a65d2f591 | |||
5a504a0d9d | |||
ea384fc096 | |||
6cd441fdac | |||
41733a9c08 | |||
3ccf4a3944 | |||
b0bcdcdbef | |||
179008f57a | |||
c935a06edb | |||
b0810f788c | |||
ee56c46c15 | |||
5d481d9e55 | |||
![]() |
9fa4ceb340 | ||
![]() |
42485b01d2 | ||
![]() |
beeeb6da30 | ||
3f5dfbf681 | |||
3a41e0f611 | |||
828525b268 | |||
4ecc7cf14a | |||
e47c75aa6e | |||
136ea84d9a | |||
162f0dcb2f | |||
1aff91b1a7 | |||
b43bdd8ba2 | |||
86ade3df56 | |||
8d269a2488 | |||
ed6e1381dc | |||
248ee6270b | |||
afd16c487d | |||
4029cdee7b | |||
680a0fb23b | |||
ed8fee16ac | |||
4a0e19e608 | |||
5ca6965273 | |||
0147e09e0b | |||
f07b09da27 | |||
f83aa1ae08 | |||
d1c21d5673 | |||
b5ca43b2a0 | |||
994e3c6ac5 | |||
dad0f37c1e | |||
118afe1de3 | |||
ae6e35279f | |||
043673ca70 | |||
3cf803cf3c | |||
6422f75088 | |||
b918c079da | |||
6bc3311410 | |||
60ad5f49fa | |||
826535979a | |||
32690cafd1 | |||
64c26d2862 | |||
848dff1e4c | |||
b9c358392d | |||
0ce18561bc | |||
851906744e | |||
c4cfa1b23e | |||
0710ec485e | |||
49129180b2 | |||
cdc1c60f5b | |||
58c8c4fde3 | |||
81754a0fc5 | |||
412642865d | |||
14a0fb0cc6 | |||
959f3cd645 | |||
![]() |
008cc625aa | ||
de27925aea | |||
38c4f40159 | |||
d6d5089c65 | |||
20c1ce3d9b | |||
f47daa7ec9 | |||
6e37f14489 | |||
![]() |
bb665ef8d2 | ||
![]() |
705fe08b61 | ||
1b7b996e16 | |||
17e562311f | |||
2a53e0f437 | |||
5f626ac331 | |||
369914e7b1 | |||
bbf09eb59c | |||
b3daf61ddf | |||
2dcdfab94c | |||
38573d515e | |||
f4e1f62c62 | |||
dc1ed9c1aa | |||
584089879c | |||
38cf48f62b | |||
50aad904b3 | |||
962e221cd3 | |||
f13160d188 | |||
583f19d692 | |||
a87d78860b | |||
c1eeb38f7c | |||
c3d6f5ecf3 | |||
ea57c011de | |||
737d363e02 | |||
fe1b8b671a | |||
687272c409 | |||
460f7ec7aa | |||
![]() |
a819523dff | ||
68a450cbe4 | |||
aa0c2c0f47 | |||
4f02817367 | |||
247d75d2b1 | |||
6396d29779 | |||
ff9606ddc4 | |||
1c00b2ef70 | |||
106277be43 | |||
3250ab31cd | |||
7d44676b5f | |||
11275b7363 | |||
80249ce6e4 | |||
e0c5ff87b7 | |||
571f373155 | |||
c464fd724b | |||
356373ff7a | |||
5938e97a24 | |||
0d73d5c1a2 | |||
01e479b790 | |||
63ae0939ed | |||
db28a8b3d1 | |||
cdcbf05ea8 | |||
2cb6b0b4eb | |||
41ae2c6438 | |||
0e6d893d07 | |||
e3567aad0a | |||
af8a449ca5 | |||
0f3b3ee679 | |||
f5e99abb60 | |||
40ac3776db | |||
acf8f6220d | |||
df788ecfd9 | |||
a777c09d5f | |||
41f58fadae | |||
aa1a51ff9f | |||
3f294a37f5 | |||
461cb550cc | |||
55db44ca2c | |||
fa8a59689b | |||
c3e919b317 | |||
a2a72d89b1 | |||
14d0b57be7 | |||
8e535ee9b4 | |||
772696a1a5 | |||
8990983b07 | |||
a5e7657cee | |||
822dab9a42 | |||
67194fb247 | |||
f8d6bbeb63 | |||
ed82bbfc2c | |||
dd5fdb9370 | |||
51f56e71cb | |||
6d7a067a83 | |||
02c6136958 | |||
b79e5ae4f2 | |||
7dea18b3aa | |||
04dc58df83 | |||
294ff0de43 | |||
f383dfabf7 | |||
d37efe332c | |||
ceaf4779da | |||
88d9ed3c1c | |||
0b251493c8 | |||
ea969ccc02 | |||
b864397201 | |||
4d1a116cdf | |||
1300da6d39 | |||
016f9c2cf5 | |||
953f719e58 | |||
1168665653 | |||
fe38715600 | |||
4d497721ec | |||
3c479b9823 | |||
7411fa4e0d | |||
44d7ec7e80 | |||
![]() |
6dac345a64 |
@@ -1244,9 +1244,6 @@ endif()
|
||||
|
||||
if(WITH_VULKAN_BACKEND)
|
||||
list(APPEND BLENDER_GL_LIBRARIES ${VULKAN_LIBRARIES})
|
||||
if(APPLE)
|
||||
list(APPEND BLENDER_GL_LIBRARIES ${MOLTENVK_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
@@ -101,8 +101,20 @@ if(WITH_USD)
|
||||
endif()
|
||||
|
||||
if(WITH_VULKAN_BACKEND)
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(MoltenVK REQUIRED)
|
||||
|
||||
if(EXISTS ${LIBDIR}/vulkan)
|
||||
set(VULKAN_FOUND On)
|
||||
set(VULKAN_ROOT_DIR ${LIBDIR}/vulkan/macOS)
|
||||
set(VULKAN_INCLUDE_DIR ${VULKAN_ROOT_DIR}/include)
|
||||
set(VULKAN_LIBRARY ${VULKAN_ROOT_DIR}/lib/libvulkan.1.dylib)
|
||||
|
||||
set(VULKAN_INCLUDE_DIRS ${VULKAN_INCLUDE_DIR} ${MOLTENVK_INCLUDE_DIRS})
|
||||
set(VULKAN_LIBRARIES ${VULKAN_LIBRARY} ${MOLTENVK_LIBRARIES})
|
||||
else()
|
||||
message(WARNING "Vulkan SDK was not found, disabling WITH_VULKAN_BACKEND")
|
||||
set(WITH_VULKAN_BACKEND OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_OPENSUBDIV)
|
||||
|
@@ -326,9 +326,10 @@ if(WITH_CYCLES AND WITH_CYCLES_DEVICE_ONEAPI)
|
||||
file(GLOB _sycl_runtime_libraries
|
||||
${SYCL_ROOT_DIR}/lib/libsycl.so
|
||||
${SYCL_ROOT_DIR}/lib/libsycl.so.*
|
||||
${SYCL_ROOT_DIR}/lib/libpi_level_zero.so
|
||||
${SYCL_ROOT_DIR}/lib/libpi_*.so
|
||||
)
|
||||
list(FILTER _sycl_runtime_libraries EXCLUDE REGEX ".*\.py")
|
||||
list(REMOVE_ITEM _sycl_runtime_libraries "${SYCL_ROOT_DIR}/lib/libpi_opencl.so")
|
||||
list(APPEND PLATFORM_BUNDLED_LIBRARIES ${_sycl_runtime_libraries})
|
||||
unset(_sycl_runtime_libraries)
|
||||
endif()
|
||||
@@ -969,16 +970,9 @@ if(WITH_COMPILER_CCACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# On some platforms certain atomic operations are not possible with assembly and/or intrinsics and
|
||||
# they are emulated in software with locks. For example, on armel there is no intrinsics to grant
|
||||
# 64 bit atomic operations and STL library uses libatomic to offload software emulation of atomics
|
||||
# to.
|
||||
# This function will check whether libatomic is required and if so will configure linker flags.
|
||||
# If atomic operations are possible without libatomic then linker flags are left as-is.
|
||||
function(CONFIGURE_ATOMIC_LIB_IF_NEEDED)
|
||||
# Source which is used to enforce situation when software emulation of atomics is required.
|
||||
# Assume that using 64bit integer gives a definitive answer (as in, if 64bit atomic operations
|
||||
# are possible using assembly/intrinsics 8, 16, and 32 bit operations will also be possible.
|
||||
# Always link with libatomic if available, as it is required for data types
|
||||
# which don't have intrinsics.
|
||||
function(configure_atomic_lib_if_needed)
|
||||
set(_source
|
||||
"#include <atomic>
|
||||
#include <cstdint>
|
||||
@@ -989,25 +983,12 @@ function(CONFIGURE_ATOMIC_LIB_IF_NEEDED)
|
||||
)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITHOUT_LIBATOMIC)
|
||||
set(CMAKE_REQUIRED_LIBRARIES atomic)
|
||||
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITH_LIBATOMIC)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
if(NOT ATOMIC_OPS_WITHOUT_LIBATOMIC)
|
||||
# Compilation of the test program has failed.
|
||||
# Try it again with -latomic to see if this is what is needed, or whether something else is
|
||||
# going on.
|
||||
|
||||
set(CMAKE_REQUIRED_LIBRARIES atomic)
|
||||
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITH_LIBATOMIC)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
if(ATOMIC_OPS_WITH_LIBATOMIC)
|
||||
set(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -latomic" PARENT_SCOPE)
|
||||
else()
|
||||
# Atomic operations are required part of Blender and it is not possible to process forward.
|
||||
# We expect that either standard library or libatomic will make atomics to work. If both
|
||||
# cases has failed something fishy o na bigger scope is going on.
|
||||
message(FATAL_ERROR "Failed to detect required configuration for atomic operations")
|
||||
endif()
|
||||
if(ATOMIC_OPS_WITH_LIBATOMIC)
|
||||
set(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -latomic" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
@@ -935,7 +935,7 @@ if(WITH_VULKAN_BACKEND)
|
||||
set(VULKAN_LIBRARY ${VULKAN_ROOT_DIR}/lib/vulkan-1.lib)
|
||||
set(VULKAN_LIBRARIES ${VULKAN_LIBRARY})
|
||||
else()
|
||||
message(WARNING "Vulkan was not found, disabling WITH_VULKAN_BACKEND")
|
||||
message(WARNING "Vulkan SDK was not found, disabling WITH_VULKAN_BACKEND")
|
||||
set(WITH_VULKAN_BACKEND OFF)
|
||||
endif()
|
||||
endif()
|
||||
@@ -972,7 +972,13 @@ if(WITH_CYCLES AND WITH_CYCLES_DEVICE_ONEAPI)
|
||||
endforeach()
|
||||
unset(_sycl_runtime_libraries_glob)
|
||||
|
||||
list(APPEND _sycl_runtime_libraries ${SYCL_ROOT_DIR}/bin/pi_level_zero.dll)
|
||||
file(GLOB _sycl_pi_runtime_libraries_glob
|
||||
${SYCL_ROOT_DIR}/bin/pi_*.dll
|
||||
)
|
||||
list(REMOVE_ITEM _sycl_pi_runtime_libraries_glob "${SYCL_ROOT_DIR}/bin/pi_opencl.dll")
|
||||
list (APPEND _sycl_runtime_libraries ${_sycl_pi_runtime_libraries_glob})
|
||||
unset(_sycl_pi_runtime_libraries_glob)
|
||||
|
||||
list(APPEND PLATFORM_BUNDLED_LIBRARIES ${_sycl_runtime_libraries})
|
||||
unset(_sycl_runtime_libraries)
|
||||
endif()
|
||||
|
@@ -35,10 +35,41 @@ from typing import (
|
||||
Tuple,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Long Description
|
||||
|
||||
long_description = """# Blender
|
||||
|
||||
[Blender](https://www.blender.org) is the free and open source 3D creation suite. It supports the entirety of the 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, even video editing.
|
||||
|
||||
This package provides Blender as a Python module for use in studio pipelines, web services, scientific research, and more.
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Blender Python API](https://docs.blender.org/api/current/)
|
||||
* [Blender as a Python Module](https://docs.blender.org/api/current/info_advanced_blender_as_bpy.html)
|
||||
|
||||
## Requirements
|
||||
|
||||
[System requirements](https://www.blender.org/download/requirements/) are the same as Blender.
|
||||
|
||||
Each Blender release supports one Python version, and the package is only compatible with that version.
|
||||
|
||||
## Source Code
|
||||
|
||||
* [Releases](https://download.blender.org/source/)
|
||||
* Repository: [git.blender.org/blender.git](https://git.blender.org/gitweb/gitweb.cgi/blender.git)
|
||||
|
||||
## Credits
|
||||
|
||||
Created by the [Blender developer community](https://www.blender.org/about/credits/).
|
||||
|
||||
Thanks to Tyler Alden Gubala for maintaining the original version of this package."""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Generic Functions
|
||||
|
||||
|
||||
def find_dominating_file(
|
||||
path: str,
|
||||
search: Sequence[str],
|
||||
@@ -195,6 +226,8 @@ def main() -> None:
|
||||
options={"bdist_wheel": {"plat_name": platform_tag}},
|
||||
|
||||
description="Blender as a Python module",
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
license="GPL-3.0",
|
||||
author="Blender Foundation",
|
||||
author_email="bf-committers@blender.org",
|
||||
|
@@ -870,6 +870,26 @@ an issue but, due to internal implementation details, currently are:
|
||||
thus breaking any current iteration over ``Collection.all_objects``.
|
||||
|
||||
|
||||
.. rubric:: Do not:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# `all_objects` is an iterator. Using it directly while performing operations on its members that will update
|
||||
# the memory accessed by the `all_objects` iterator will lead to invalid memory accesses and crashes.
|
||||
for object in bpy.data.collections["Collection"].all_objects:
|
||||
object.hide_viewport = True
|
||||
|
||||
|
||||
.. rubric:: Do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# `all_objects[:]` is an independent list generated from the iterator. As long as no objects are deleted,
|
||||
# its content will remain valid even if the data accessed by the `all_objects` iterator is modified.
|
||||
for object in bpy.data.collections["Collection"].all_objects[:]:
|
||||
object.hide_viewport = True
|
||||
|
||||
|
||||
sys.exit
|
||||
========
|
||||
|
||||
|
@@ -1294,6 +1294,7 @@ def pycontext2sphinx(basepath):
|
||||
|
||||
type_descr = prop.get_type_description(
|
||||
class_fmt=":class:`bpy.types.%s`",
|
||||
mathutils_fmt=":class:`mathutils.%s`",
|
||||
collection_id=_BPY_PROP_COLLECTION_ID,
|
||||
enum_descr_override=enum_descr_override,
|
||||
)
|
||||
@@ -1446,6 +1447,7 @@ def pyrna2sphinx(basepath):
|
||||
identifier = " %s" % prop.identifier
|
||||
|
||||
kwargs["class_fmt"] = ":class:`%s`"
|
||||
kwargs["mathutils_fmt"] = ":class:`mathutils.%s`"
|
||||
|
||||
kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
|
||||
|
||||
@@ -1565,6 +1567,7 @@ def pyrna2sphinx(basepath):
|
||||
|
||||
type_descr = prop.get_type_description(
|
||||
class_fmt=":class:`%s`",
|
||||
mathutils_fmt=":class:`mathutils.%s`",
|
||||
collection_id=_BPY_PROP_COLLECTION_ID,
|
||||
enum_descr_override=enum_descr_override,
|
||||
)
|
||||
@@ -1631,6 +1634,7 @@ def pyrna2sphinx(basepath):
|
||||
|
||||
type_descr = prop.get_type_description(
|
||||
as_ret=True, class_fmt=":class:`%s`",
|
||||
mathutils_fmt=":class:`mathutils.%s`",
|
||||
collection_id=_BPY_PROP_COLLECTION_ID,
|
||||
enum_descr_override=enum_descr_override,
|
||||
)
|
||||
|
4
extern/CMakeLists.txt
vendored
4
extern/CMakeLists.txt
vendored
@@ -91,3 +91,7 @@ endif()
|
||||
if(WITH_COMPOSITOR_CPU)
|
||||
add_subdirectory(smaa_areatex)
|
||||
endif()
|
||||
|
||||
if(WITH_VULKAN_BACKEND)
|
||||
add_subdirectory(vulkan_memory_allocator)
|
||||
endif()
|
||||
|
24
extern/vulkan_memory_allocator/CMakeLists.txt
vendored
Normal file
24
extern/vulkan_memory_allocator/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Copyright 2022 Blender Foundation. All rights reserved.
|
||||
|
||||
set(INC
|
||||
.
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
${VULKAN_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(SRC
|
||||
vk_mem_alloc_impl.cc
|
||||
|
||||
vk_mem_alloc.h
|
||||
)
|
||||
|
||||
blender_add_lib(extern_vulkan_memory_allocator "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
target_compile_options(extern_vulkan_memory_allocator
|
||||
PRIVATE "-Wno-nullability-completeness"
|
||||
)
|
||||
endif()
|
19
extern/vulkan_memory_allocator/LICENSE.txt
vendored
Normal file
19
extern/vulkan_memory_allocator/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
5
extern/vulkan_memory_allocator/README.blender
vendored
Normal file
5
extern/vulkan_memory_allocator/README.blender
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Project: VulkanMemoryAllocator
|
||||
URL: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
|
||||
License: MIT
|
||||
Upstream version: a6bfc23
|
||||
Local modifications: None
|
175
extern/vulkan_memory_allocator/README.md
vendored
Normal file
175
extern/vulkan_memory_allocator/README.md
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Vulkan Memory Allocator
|
||||
|
||||
Easy to integrate Vulkan memory allocation library.
|
||||
|
||||
**Documentation:** Browse online: [Vulkan Memory Allocator](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/) (generated from Doxygen-style comments in [include/vk_mem_alloc.h](include/vk_mem_alloc.h))
|
||||
|
||||
**License:** MIT. See [LICENSE.txt](LICENSE.txt)
|
||||
|
||||
**Changelog:** See [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
**Product page:** [Vulkan Memory Allocator on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/)
|
||||
|
||||
**Build status:**
|
||||
|
||||
- Windows: [](https://ci.appveyor.com/project/adam-sawicki-amd/vulkanmemoryallocator/branch/master)
|
||||
- Linux: [](https://app.travis-ci.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
|
||||
|
||||
[](http://isitmaintained.com/project/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator "Average time to resolve an issue")
|
||||
|
||||
# Problem
|
||||
|
||||
Memory allocation and resource (buffer and image) creation in Vulkan is difficult (comparing to older graphics APIs, like D3D11 or OpenGL) for several reasons:
|
||||
|
||||
- It requires a lot of boilerplate code, just like everything else in Vulkan, because it is a low-level and high-performance API.
|
||||
- There is additional level of indirection: `VkDeviceMemory` is allocated separately from creating `VkBuffer`/`VkImage` and they must be bound together.
|
||||
- Driver must be queried for supported memory heaps and memory types. Different GPU vendors provide different types of it.
|
||||
- It is recommended to allocate bigger chunks of memory and assign parts of them to particular resources, as there is a limit on maximum number of memory blocks that can be allocated.
|
||||
|
||||
# Features
|
||||
|
||||
This library can help game developers to manage memory allocations and resource creation by offering some higher-level functions:
|
||||
|
||||
1. Functions that help to choose correct and optimal memory type based on intended usage of the memory.
|
||||
- Required or preferred traits of the memory are expressed using higher-level description comparing to Vulkan flags.
|
||||
2. Functions that allocate memory blocks, reserve and return parts of them (`VkDeviceMemory` + offset + size) to the user.
|
||||
- Library keeps track of allocated memory blocks, used and unused ranges inside them, finds best matching unused ranges for new allocations, respects all the rules of alignment and buffer/image granularity.
|
||||
3. Functions that can create an image/buffer, allocate memory for it and bind them together - all in one call.
|
||||
|
||||
Additional features:
|
||||
|
||||
- Well-documented - description of all functions and structures provided, along with chapters that contain general description and example code.
|
||||
- Thread-safety: Library is designed to be used in multithreaded code. Access to a single device memory block referred by different buffers and textures (binding, mapping) is synchronized internally. Memory mapping is reference-counted.
|
||||
- Configuration: Fill optional members of `VmaAllocatorCreateInfo` structure to provide custom CPU memory allocator, pointers to Vulkan functions and other parameters.
|
||||
- Customization and integration with custom engines: Predefine appropriate macros to provide your own implementation of all external facilities used by the library like assert, mutex, atomic.
|
||||
- Support for memory mapping, reference-counted internally. Support for persistently mapped memory: Just allocate with appropriate flag and access the pointer to already mapped memory.
|
||||
- Support for non-coherent memory. Functions that flush/invalidate memory. `nonCoherentAtomSize` is respected automatically.
|
||||
- Support for resource aliasing (overlap).
|
||||
- Support for sparse binding and sparse residency: Convenience functions that allocate or free multiple memory pages at once.
|
||||
- Custom memory pools: Create a pool with desired parameters (e.g. fixed or limited maximum size) and allocate memory out of it.
|
||||
- Linear allocator: Create a pool with linear algorithm and use it for much faster allocations and deallocations in free-at-once, stack, double stack, or ring buffer fashion.
|
||||
- Support for Vulkan 1.0, 1.1, 1.2, 1.3.
|
||||
- Support for extensions (and equivalent functionality included in new Vulkan versions):
|
||||
- VK_KHR_dedicated_allocation: Just enable it and it will be used automatically by the library.
|
||||
- VK_KHR_buffer_device_address: Flag `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR` is automatically added to memory allocations where needed.
|
||||
- VK_EXT_memory_budget: Used internally if available to query for current usage and budget. If not available, it falls back to an estimation based on memory heap sizes.
|
||||
- VK_EXT_memory_priority: Set `priority` of allocations or custom pools and it will be set automatically using this extension.
|
||||
- VK_AMD_device_coherent_memory
|
||||
- Defragmentation of GPU and CPU memory: Let the library move data around to free some memory blocks and make your allocations better compacted.
|
||||
- Statistics: Obtain brief or detailed statistics about the amount of memory used, unused, number of allocated blocks, number of allocations etc. - globally, per memory heap, and per memory type.
|
||||
- Debug annotations: Associate custom `void* pUserData` and debug `char* pName` with each allocation.
|
||||
- JSON dump: Obtain a string in JSON format with detailed map of internal state, including list of allocations, their string names, and gaps between them.
|
||||
- Convert this JSON dump into a picture to visualize your memory. See [tools/GpuMemDumpVis](tools/GpuMemDumpVis/README.md).
|
||||
- Debugging incorrect memory usage: Enable initialization of all allocated memory with a bit pattern to detect usage of uninitialized or freed memory. Enable validation of a magic number after every allocation to detect out-of-bounds memory corruption.
|
||||
- Support for interoperability with OpenGL.
|
||||
- Virtual allocator: Interface for using core allocation algorithm to allocate any custom data, e.g. pieces of one large buffer.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
- Self-contained C++ library in single header file. No external dependencies other than standard C and C++ library and of course Vulkan. Some features of C++14 used. STL containers, RTTI, or C++ exceptions are not used.
|
||||
- Public interface in C, in same convention as Vulkan API. Implementation in C++.
|
||||
- Error handling implemented by returning `VkResult` error codes - same way as in Vulkan.
|
||||
- Interface documented using Doxygen-style comments.
|
||||
- Platform-independent, but developed and tested on Windows using Visual Studio. Continuous integration setup for Windows and Linux. Used also on Android, MacOS, and other platforms.
|
||||
|
||||
# Example
|
||||
|
||||
Basic usage of this library is very simple. Advanced features are optional. After you created global `VmaAllocator` object, a complete code needed to create a buffer may look like this:
|
||||
|
||||
```cpp
|
||||
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
bufferInfo.size = 65536;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo = {};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
|
||||
|
||||
VkBuffer buffer;
|
||||
VmaAllocation allocation;
|
||||
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);
|
||||
```
|
||||
|
||||
With this one function call:
|
||||
|
||||
1. `VkBuffer` is created.
|
||||
2. `VkDeviceMemory` block is allocated if needed.
|
||||
3. An unused region of the memory block is bound to this buffer.
|
||||
|
||||
`VmaAllocation` is an object that represents memory assigned to this buffer. It can be queried for parameters like `VkDeviceMemory` handle and offset.
|
||||
|
||||
# How to build
|
||||
|
||||
On Windows it is recommended to use [CMake UI](https://cmake.org/runningcmake/). Alternatively you can generate a Visual Studio project map using CMake in command line: `cmake -B./build/ -DCMAKE_BUILD_TYPE=Debug -G "Visual Studio 16 2019" -A x64 ./`
|
||||
|
||||
On Linux:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
The following targets are available
|
||||
|
||||
| Target | Description | CMake option | Default setting |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| VmaSample | VMA sample application | `VMA_BUILD_SAMPLE` | `OFF` |
|
||||
| VmaBuildSampleShaders | Shaders for VmaSample | `VMA_BUILD_SAMPLE_SHADERS` | `OFF` |
|
||||
|
||||
Please note that while VulkanMemoryAllocator library is supported on other platforms besides Windows, VmaSample is not.
|
||||
|
||||
These CMake options are available
|
||||
|
||||
| CMake option | Description | Default setting |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| `VMA_RECORDING_ENABLED` | Enable VMA memory recording for debugging | `OFF` |
|
||||
| `VMA_USE_STL_CONTAINERS` | Use C++ STL containers instead of VMA's containers | `OFF` |
|
||||
| `VMA_STATIC_VULKAN_FUNCTIONS` | Link statically with Vulkan API | `OFF` |
|
||||
| `VMA_DYNAMIC_VULKAN_FUNCTIONS` | Fetch pointers to Vulkan functions internally (no static linking) | `ON` |
|
||||
| `VMA_DEBUG_ALWAYS_DEDICATED_MEMORY` | Every allocation will have its own memory block | `OFF` |
|
||||
| `VMA_DEBUG_INITIALIZE_ALLOCATIONS` | Automatically fill new allocations and destroyed allocations with some bit pattern | `OFF` |
|
||||
| `VMA_DEBUG_GLOBAL_MUTEX` | Enable single mutex protecting all entry calls to the library | `OFF` |
|
||||
| `VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT` | Never exceed [VkPhysicalDeviceLimits::maxMemoryAllocationCount](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#limits-maxMemoryAllocationCount) and return error | `OFF` |
|
||||
|
||||
# Binaries
|
||||
|
||||
The release comes with precompiled binary executable for "VulkanSample" application which contains test suite. It is compiled using Visual Studio 2019, so it requires appropriate libraries to work, including "MSVCP140.dll", "VCRUNTIME140.dll", "VCRUNTIME140_1.dll". If the launch fails with error message telling about those files missing, please download and install [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads), "x64" version.
|
||||
|
||||
# Read more
|
||||
|
||||
See **[Documentation](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/)**.
|
||||
|
||||
# Software using this library
|
||||
|
||||
- **[X-Plane](https://x-plane.com/)**
|
||||
- **[Detroit: Become Human](https://gpuopen.com/learn/porting-detroit-3/)**
|
||||
- **[Vulkan Samples](https://github.com/LunarG/VulkanSamples)** - official Khronos Vulkan samples. License: Apache-style.
|
||||
- **[Anvil](https://github.com/GPUOpen-LibrariesAndSDKs/Anvil)** - cross-platform framework for Vulkan. License: MIT.
|
||||
- **[Filament](https://github.com/google/filament)** - physically based rendering engine for Android, Windows, Linux and macOS, from Google. Apache License 2.0.
|
||||
- **[Atypical Games - proprietary game engine](https://developer.samsung.com/galaxy-gamedev/gamedev-blog/infinitejet.html)**
|
||||
- **[Flax Engine](https://flaxengine.com/)**
|
||||
- **[Godot Engine](https://github.com/godotengine/godot/)** - multi-platform 2D and 3D game engine. License: MIT.
|
||||
- **[Lightweight Java Game Library (LWJGL)](https://www.lwjgl.org/)** - includes binding of the library for Java. License: BSD.
|
||||
- **[PowerVR SDK](https://github.com/powervr-graphics/Native_SDK)** - C++ cross-platform 3D graphics SDK, from Imagination. License: MIT.
|
||||
- **[Skia](https://github.com/google/skia)** - complete 2D graphic library for drawing Text, Geometries, and Images, from Google.
|
||||
- **[The Forge](https://github.com/ConfettiFX/The-Forge)** - cross-platform rendering framework. Apache License 2.0.
|
||||
- **[VK9](https://github.com/disks86/VK9)** - Direct3D 9 compatibility layer using Vulkan. Zlib lincese.
|
||||
- **[vkDOOM3](https://github.com/DustinHLand/vkDOOM3)** - Vulkan port of GPL DOOM 3 BFG Edition. License: GNU GPL.
|
||||
- **[vkQuake2](https://github.com/kondrak/vkQuake2)** - vanilla Quake 2 with Vulkan support. License: GNU GPL.
|
||||
- **[Vulkan Best Practice for Mobile Developers](https://github.com/ARM-software/vulkan_best_practice_for_mobile_developers)** from ARM. License: MIT.
|
||||
- **[RPCS3](https://github.com/RPCS3/rpcs3)** - PlayStation 3 emulator/debugger. License: GNU GPLv2.
|
||||
- **[PPSSPP](https://github.com/hrydgard/ppsspp)** - Playstation Portable emulator/debugger. License: GNU GPLv2+.
|
||||
|
||||
[Many other projects on GitHub](https://github.com/search?q=AMD_VULKAN_MEMORY_ALLOCATOR_H&type=Code) and some game development studios that use Vulkan in their games.
|
||||
|
||||
# See also
|
||||
|
||||
- **[D3D12 Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator)** - equivalent library for Direct3D 12. License: MIT.
|
||||
- **[Awesome Vulkan](https://github.com/vinjn/awesome-vulkan)** - a curated list of awesome Vulkan libraries, debuggers and resources.
|
||||
- **[VulkanMemoryAllocator-Hpp](https://github.com/malte-v/VulkanMemoryAllocator-Hpp)** - C++ binding for this library. License: CC0-1.0.
|
||||
- **[PyVMA](https://github.com/realitix/pyvma)** - Python wrapper for this library. Author: Jean-Sébastien B. (@realitix). License: Apache 2.0.
|
||||
- **[vk-mem](https://github.com/gwihlidal/vk-mem-rs)** - Rust binding for this library. Author: Graham Wihlidal. License: Apache 2.0 or MIT.
|
||||
- **[Haskell bindings](https://hackage.haskell.org/package/VulkanMemoryAllocator)**, **[github](https://github.com/expipiplus1/vulkan/tree/master/VulkanMemoryAllocator)** - Haskell bindings for this library. Author: Ellie Hermaszewska (@expipiplus1). License BSD-3-Clause.
|
||||
- **[vma_sample_sdl](https://github.com/rextimmy/vma_sample_sdl)** - SDL port of the sample app of this library (with the goal of running it on multiple platforms, including MacOS). Author: @rextimmy. License: MIT.
|
||||
- **[vulkan-malloc](https://github.com/dylanede/vulkan-malloc)** - Vulkan memory allocation library for Rust. Based on version 1 of this library. Author: Dylan Ede (@dylanede). License: MIT / Apache 2.0.
|
19558
extern/vulkan_memory_allocator/vk_mem_alloc.h
vendored
Normal file
19558
extern/vulkan_memory_allocator/vk_mem_alloc.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
extern/vulkan_memory_allocator/vk_mem_alloc_impl.cc
vendored
Normal file
12
extern/vulkan_memory_allocator/vk_mem_alloc_impl.cc
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Copyright 2022 Blender Foundation. All rights reserved. */
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <MoltenVK/vk_mvk_moltenvk.h>
|
||||
#else
|
||||
# include <vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
|
||||
#include "vk_mem_alloc.h"
|
@@ -204,7 +204,6 @@ def list_render_passes(scene, srl):
|
||||
if crl.use_pass_volume_indirect: yield ("VolumeInd", "RGB", 'COLOR')
|
||||
if srl.use_pass_emit: yield ("Emit", "RGB", 'COLOR')
|
||||
if srl.use_pass_environment: yield ("Env", "RGB", 'COLOR')
|
||||
if srl.use_pass_shadow: yield ("Shadow", "RGB", 'COLOR')
|
||||
if srl.use_pass_ambient_occlusion: yield ("AO", "RGB", 'COLOR')
|
||||
if crl.use_pass_shadow_catcher: yield ("Shadow Catcher", "RGB", 'COLOR')
|
||||
# autopep8: on
|
||||
|
@@ -91,7 +91,7 @@ class AddPresetPerformance(AddPresetBase, Operator):
|
||||
preset_menu = "CYCLES_PT_performance_presets"
|
||||
|
||||
preset_defines = [
|
||||
"render = bpy.context.scene.render"
|
||||
"render = bpy.context.scene.render",
|
||||
"cycles = bpy.context.scene.cycles"
|
||||
]
|
||||
|
||||
|
@@ -86,6 +86,29 @@ enum_sampling_pattern = (
|
||||
('PROGRESSIVE_MULTI_JITTER', "Progressive Multi-Jitter", "Use Progressive Multi-Jitter random sampling pattern", 1),
|
||||
)
|
||||
|
||||
enum_emission_sampling = (
|
||||
('NONE',
|
||||
'None',
|
||||
"Do not use this surface as a light for sampling",
|
||||
0),
|
||||
('AUTO',
|
||||
'Auto',
|
||||
"Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity",
|
||||
1),
|
||||
('FRONT',
|
||||
'Front',
|
||||
"Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible",
|
||||
2),
|
||||
('BACK',
|
||||
'Back',
|
||||
"Treat only back side of the surface as a light for sampling",
|
||||
3),
|
||||
('FRONT_BACK',
|
||||
'Front and Back',
|
||||
"Treat surface as a light for sampling, emitting from both the front and back side",
|
||||
4),
|
||||
)
|
||||
|
||||
enum_volume_sampling = (
|
||||
('DISTANCE',
|
||||
"Distance",
|
||||
@@ -147,7 +170,6 @@ enum_view3d_shading_render_pass = (
|
||||
('EMISSION', "Emission", "Show the Emission render pass"),
|
||||
('BACKGROUND', "Background", "Show the Background render pass"),
|
||||
('AO', "Ambient Occlusion", "Show the Ambient Occlusion render pass"),
|
||||
('SHADOW', "Shadow", "Show the Shadow render pass"),
|
||||
('SHADOW_CATCHER', "Shadow Catcher", "Show the Shadow Catcher render pass"),
|
||||
|
||||
('', "Light", ""),
|
||||
@@ -481,6 +503,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
||||
default='MULTIPLE_IMPORTANCE_SAMPLING',
|
||||
)
|
||||
|
||||
use_light_tree: BoolProperty(
|
||||
name="Light Tree",
|
||||
description="Sample multiple lights more efficiently based on estimated contribution at every shading point",
|
||||
default=True,
|
||||
)
|
||||
|
||||
min_light_bounces: IntProperty(
|
||||
name="Min Light Bounces",
|
||||
description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, "
|
||||
@@ -622,7 +650,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
||||
|
||||
transparent_max_bounces: IntProperty(
|
||||
name="Transparent Max Bounces",
|
||||
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces ",
|
||||
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces",
|
||||
min=0, max=1024,
|
||||
default=8,
|
||||
)
|
||||
@@ -1043,13 +1071,13 @@ class CyclesCameraSettings(bpy.types.PropertyGroup):
|
||||
|
||||
class CyclesMaterialSettings(bpy.types.PropertyGroup):
|
||||
|
||||
sample_as_light: BoolProperty(
|
||||
name="Multiple Importance Sample",
|
||||
description="Use multiple importance sampling for this material, "
|
||||
"disabling may reduce overall noise for large "
|
||||
"objects that emit little light compared to other light sources",
|
||||
default=True,
|
||||
emission_sampling: EnumProperty(
|
||||
name="Emission Sampling",
|
||||
description="Sampling strategy for emissive surfaces",
|
||||
items=enum_emission_sampling,
|
||||
default="AUTO",
|
||||
)
|
||||
|
||||
use_transparent_shadow: BoolProperty(
|
||||
name="Transparent Shadows",
|
||||
description="Use transparent shadows for this material if it contains a Transparent BSDF, "
|
||||
@@ -1642,7 +1670,7 @@ class CyclesPreferences(bpy.types.AddonPreferences):
|
||||
col.label(text="and Windows driver version 101.3430 or newer", icon='BLANK1')
|
||||
elif sys.platform.startswith("linux"):
|
||||
col.label(text="Requires Intel GPU with Xe-HPG architecture and", icon='BLANK1')
|
||||
col.label(text=" - Linux driver version xx.xx.23904 or newer", icon='BLANK1')
|
||||
col.label(text=" - intel-level-zero-gpu version 1.3.23904 or newer", icon='BLANK1')
|
||||
col.label(text=" - oneAPI Level-Zero Loader", icon='BLANK1')
|
||||
elif device_type == 'METAL':
|
||||
col.label(text="Requires Apple Silicon with macOS 12.2 or newer", icon='BLANK1')
|
||||
|
@@ -154,8 +154,9 @@ def use_mnee(context):
|
||||
# The MNEE kernel doesn't compile on macOS < 13.
|
||||
if use_metal(context):
|
||||
import platform
|
||||
v, _, _ = platform.mac_ver()
|
||||
if float(v) < 13.0:
|
||||
version, _, _ = platform.mac_ver()
|
||||
major_version = version.split(".")[0]
|
||||
if int(major_version) < 13:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -382,7 +383,6 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
|
||||
col = layout.column(align=True)
|
||||
col.prop(cscene, "min_light_bounces")
|
||||
col.prop(cscene, "min_transparent_bounces")
|
||||
col.prop(cscene, "light_sampling_threshold", text="Light Threshold")
|
||||
|
||||
for view_layer in scene.view_layers:
|
||||
if view_layer.samples > 0:
|
||||
@@ -391,6 +391,31 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
|
||||
break
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_sampling_lights(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Lights"
|
||||
bl_parent_id = "CYCLES_RENDER_PT_sampling"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(cscene, "use_light_tree")
|
||||
sub = col.row()
|
||||
sub.prop(cscene, "light_sampling_threshold", text="Light Threshold")
|
||||
sub.active = not cscene.use_light_tree
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Subdivision"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
@@ -953,7 +978,6 @@ class CYCLES_RENDER_PT_passes_light(CyclesButtonsPanel, Panel):
|
||||
col = layout.column(heading="Other", align=True)
|
||||
col.prop(view_layer, "use_pass_emit", text="Emission")
|
||||
col.prop(view_layer, "use_pass_environment")
|
||||
col.prop(view_layer, "use_pass_shadow")
|
||||
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
|
||||
col.prop(cycles_view_layer, "use_pass_shadow_catcher")
|
||||
|
||||
@@ -1831,9 +1855,9 @@ class CYCLES_MATERIAL_PT_settings_surface(CyclesButtonsPanel, Panel):
|
||||
cmat = mat.cycles
|
||||
|
||||
col = layout.column()
|
||||
col.prop(cmat, "sample_as_light", text="Multiple Importance")
|
||||
col.prop(cmat, "use_transparent_shadow")
|
||||
col.prop(cmat, "displacement_method", text="Displacement")
|
||||
col.prop(cmat, "emission_sampling")
|
||||
col.prop(cmat, "use_transparent_shadow")
|
||||
|
||||
def draw(self, context):
|
||||
self.draw_shared(self, context.material)
|
||||
@@ -2365,6 +2389,7 @@ classes = (
|
||||
CYCLES_RENDER_PT_sampling_render_denoise,
|
||||
CYCLES_RENDER_PT_sampling_path_guiding,
|
||||
CYCLES_RENDER_PT_sampling_path_guiding_debug,
|
||||
CYCLES_RENDER_PT_sampling_lights,
|
||||
CYCLES_RENDER_PT_sampling_advanced,
|
||||
CYCLES_RENDER_PT_light_paths,
|
||||
CYCLES_RENDER_PT_light_paths_max_bounces,
|
||||
|
@@ -99,7 +99,7 @@ def do_versions(self):
|
||||
library_versions.setdefault(library.version, []).append(library)
|
||||
|
||||
# Do versioning per library, since they might have different versions.
|
||||
max_need_versioning = (3, 0, 25)
|
||||
max_need_versioning = (3, 5, 2)
|
||||
for version, libraries in library_versions.items():
|
||||
if version > max_need_versioning:
|
||||
continue
|
||||
@@ -297,3 +297,8 @@ def do_versions(self):
|
||||
cmat = mat.cycles
|
||||
if not cmat.is_property_set("displacement_method"):
|
||||
cmat.displacement_method = 'DISPLACEMENT'
|
||||
|
||||
if version <= (3, 5, 3):
|
||||
cmat = mat.cycles
|
||||
if not cmat.get("sample_as_light", True):
|
||||
cmat.emission_sampling = 'NONE'
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,10 @@
|
||||
#include "util/unique_ptr.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
typedef struct GPUContext GPUContext;
|
||||
typedef struct GPUFence GPUFence;
|
||||
typedef struct GPUShader GPUShader;
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Base class of shader used for display driver rendering. */
|
||||
@@ -29,7 +33,7 @@ class BlenderDisplayShader {
|
||||
BlenderDisplayShader() = default;
|
||||
virtual ~BlenderDisplayShader() = default;
|
||||
|
||||
virtual void bind(int width, int height) = 0;
|
||||
virtual GPUShader *bind(int width, int height) = 0;
|
||||
virtual void unbind() = 0;
|
||||
|
||||
/* Get attribute location for position and texture coordinate respectively.
|
||||
@@ -40,7 +44,7 @@ class BlenderDisplayShader {
|
||||
protected:
|
||||
/* Get program of this display shader.
|
||||
* NOTE: The shader needs to be bound to have access to this. */
|
||||
virtual uint get_shader_program() = 0;
|
||||
virtual GPUShader *get_shader_program() = 0;
|
||||
|
||||
/* Cached values of various OpenGL resources. */
|
||||
int position_attribute_location_ = -1;
|
||||
@@ -51,16 +55,16 @@ class BlenderDisplayShader {
|
||||
* display space shader. */
|
||||
class BlenderFallbackDisplayShader : public BlenderDisplayShader {
|
||||
public:
|
||||
virtual void bind(int width, int height) override;
|
||||
virtual GPUShader *bind(int width, int height) override;
|
||||
virtual void unbind() override;
|
||||
|
||||
protected:
|
||||
virtual uint get_shader_program() override;
|
||||
virtual GPUShader *get_shader_program() override;
|
||||
|
||||
void create_shader_if_needed();
|
||||
void destroy_shader();
|
||||
|
||||
uint shader_program_ = 0;
|
||||
GPUShader *shader_program_ = 0;
|
||||
int image_texture_location_ = -1;
|
||||
int fullscreen_location_ = -1;
|
||||
|
||||
@@ -73,17 +77,17 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader {
|
||||
public:
|
||||
BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, BL::Scene &b_scene);
|
||||
|
||||
virtual void bind(int width, int height) override;
|
||||
virtual GPUShader *bind(int width, int height) override;
|
||||
virtual void unbind() override;
|
||||
|
||||
protected:
|
||||
virtual uint get_shader_program() override;
|
||||
virtual GPUShader *get_shader_program() override;
|
||||
|
||||
BL::RenderEngine b_engine_;
|
||||
BL::Scene &b_scene_;
|
||||
|
||||
/* Cached values of various OpenGL resources. */
|
||||
uint shader_program_ = 0;
|
||||
GPUShader *shader_program_ = nullptr;
|
||||
};
|
||||
|
||||
/* Display driver implementation which is specific for Blender viewport integration. */
|
||||
@@ -122,6 +126,9 @@ class BlenderDisplayDriver : public DisplayDriver {
|
||||
void gpu_context_lock();
|
||||
void gpu_context_unlock();
|
||||
|
||||
/* Create GPU resources used by the display driver. */
|
||||
bool gpu_resources_create();
|
||||
|
||||
/* Destroy all GPU resources which are being used by this object. */
|
||||
void gpu_resources_destroy();
|
||||
|
||||
@@ -137,8 +144,8 @@ class BlenderDisplayDriver : public DisplayDriver {
|
||||
struct Tiles;
|
||||
unique_ptr<Tiles> tiles_;
|
||||
|
||||
void *gl_render_sync_ = nullptr;
|
||||
void *gl_upload_sync_ = nullptr;
|
||||
GPUFence *gpu_render_sync_ = nullptr;
|
||||
GPUFence *gpu_upload_sync_ = nullptr;
|
||||
|
||||
float2 zoom_ = make_float2(1.0f, 1.0f);
|
||||
};
|
||||
|
@@ -1085,11 +1085,11 @@ static void create_subd_mesh(Scene *scene,
|
||||
const int edges_num = b_mesh.edges.length();
|
||||
|
||||
if (edges_num != 0 && b_mesh.edge_creases.length() > 0) {
|
||||
size_t num_creases = 0;
|
||||
const float *creases = static_cast<float *>(b_mesh.edge_creases[0].ptr.data);
|
||||
BL::MeshEdgeCreaseLayer creases = b_mesh.edge_creases[0];
|
||||
|
||||
size_t num_creases = 0;
|
||||
for (int i = 0; i < edges_num; i++) {
|
||||
if (creases[i] != 0.0f) {
|
||||
if (creases.data[i].value() != 0.0f) {
|
||||
num_creases++;
|
||||
}
|
||||
}
|
||||
@@ -1098,17 +1098,18 @@ static void create_subd_mesh(Scene *scene,
|
||||
|
||||
const MEdge *edges = static_cast<MEdge *>(b_mesh.edges[0].ptr.data);
|
||||
for (int i = 0; i < edges_num; i++) {
|
||||
if (creases[i] != 0.0f) {
|
||||
const float crease = creases.data[i].value();
|
||||
if (crease != 0.0f) {
|
||||
const MEdge &b_edge = edges[i];
|
||||
mesh->add_edge_crease(b_edge.v1, b_edge.v2, creases[i]);
|
||||
mesh->add_edge_crease(b_edge.v1, b_edge.v2, crease);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (BL::MeshVertexCreaseLayer &c : b_mesh.vertex_creases) {
|
||||
for (int i = 0; i < c.data.length(); ++i) {
|
||||
if (c.data[i].value() != 0.0f) {
|
||||
mesh->add_vertex_crease(i, c.data[i].value());
|
||||
}
|
||||
for (BL::MeshVertexCreaseLayer &c : b_mesh.vertex_creases) {
|
||||
for (int i = 0; i < c.data.length(); ++i) {
|
||||
if (c.data[i].value() != 0.0f) {
|
||||
mesh->add_vertex_crease(i, c.data[i].value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@
|
||||
#include "util/guiding.h"
|
||||
#include "util/log.h"
|
||||
#include "util/md5.h"
|
||||
#include "util/opengl.h"
|
||||
#include "util/openimagedenoise.h"
|
||||
#include "util/path.h"
|
||||
#include "util/string.h"
|
||||
@@ -26,6 +25,8 @@
|
||||
#include "util/tbb.h"
|
||||
#include "util/types.h"
|
||||
|
||||
#include "GPU_state.h"
|
||||
|
||||
#ifdef WITH_OSL
|
||||
# include "scene/osl.h"
|
||||
|
||||
@@ -337,7 +338,7 @@ static PyObject *view_draw_func(PyObject * /*self*/, PyObject *args)
|
||||
if (PyLong_AsVoidPtr(pyrv3d)) {
|
||||
/* 3d view drawing */
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
GPU_viewport_size_get_i(viewport);
|
||||
|
||||
session->view_draw(viewport[2], viewport[3]);
|
||||
}
|
||||
@@ -478,6 +479,7 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
|
||||
|
||||
/* Read metadata. */
|
||||
bool is_bool_param = false;
|
||||
bool hide_value = !param->validdefault;
|
||||
ustring param_label = param->name;
|
||||
|
||||
for (const OSL::OSLQuery::Parameter &metadata : param->metadata) {
|
||||
@@ -487,6 +489,9 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
|
||||
if (metadata.sdefault[0] == "boolean" || metadata.sdefault[0] == "checkBox") {
|
||||
is_bool_param = true;
|
||||
}
|
||||
else if (metadata.sdefault[0] == "null") {
|
||||
hide_value = true;
|
||||
}
|
||||
}
|
||||
else if (metadata.name == "label") {
|
||||
/* Socket label. */
|
||||
@@ -596,6 +601,9 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
|
||||
if (b_sock.name() != param_label) {
|
||||
b_sock.name(param_label.string());
|
||||
}
|
||||
if (b_sock.hide_value() != hide_value) {
|
||||
b_sock.hide_value(hide_value);
|
||||
}
|
||||
used_sockets.insert(b_sock.ptr.data);
|
||||
found_existing = true;
|
||||
}
|
||||
@@ -635,6 +643,8 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
|
||||
set_boolean(b_sock.ptr, "default_value", default_boolean);
|
||||
}
|
||||
|
||||
b_sock.hide_value(hide_value);
|
||||
|
||||
used_sockets.insert(b_sock.ptr.data);
|
||||
}
|
||||
}
|
||||
|
@@ -559,11 +559,6 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int
|
||||
0);
|
||||
integrator->set_use_emission((bake_filter & BL::BakeSettings::pass_filter_EMIT) != 0);
|
||||
}
|
||||
/* Shadow pass. */
|
||||
else if (strcmp(bake_type, "SHADOW") == 0) {
|
||||
type = PASS_SHADOW;
|
||||
use_direct_light = true;
|
||||
}
|
||||
/* Light component passes. */
|
||||
else if (strcmp(bake_type, "DIFFUSE") == 0) {
|
||||
if ((bake_filter & BL::BakeSettings::pass_filter_DIRECT) &&
|
||||
|
@@ -61,6 +61,12 @@ static DisplacementMethod get_displacement_method(PointerRNA &ptr)
|
||||
ptr, "displacement_method", DISPLACE_NUM_METHODS, DISPLACE_BUMP);
|
||||
}
|
||||
|
||||
static EmissionSampling get_emission_sampling(PointerRNA &ptr)
|
||||
{
|
||||
return (EmissionSampling)get_enum(
|
||||
ptr, "emission_sampling", EMISSION_SAMPLING_NUM, EMISSION_SAMPLING_AUTO);
|
||||
}
|
||||
|
||||
static int validate_enum_value(int value, int num_values, int default_value)
|
||||
{
|
||||
if (value >= num_values) {
|
||||
@@ -1559,7 +1565,7 @@ void BlenderSync::sync_materials(BL::Depsgraph &b_depsgraph, bool update_all)
|
||||
|
||||
/* settings */
|
||||
PointerRNA cmat = RNA_pointer_get(&b_mat.ptr, "cycles");
|
||||
shader->set_use_mis(get_boolean(cmat, "sample_as_light"));
|
||||
shader->set_emission_sampling_method(get_emission_sampling(cmat));
|
||||
shader->set_use_transparent_shadow(get_boolean(cmat, "use_transparent_shadow"));
|
||||
shader->set_heterogeneous_volume(!get_boolean(cmat, "homogeneous_volume"));
|
||||
shader->set_volume_sampling_method(get_volume_sampling(cmat));
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "util/foreach.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/log.h"
|
||||
#include "util/opengl.h"
|
||||
#include "util/openimagedenoise.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
@@ -348,7 +347,14 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
|
||||
integrator->set_motion_blur(view_layer.use_motion_blur);
|
||||
}
|
||||
|
||||
integrator->set_light_sampling_threshold(get_float(cscene, "light_sampling_threshold"));
|
||||
bool use_light_tree = get_boolean(cscene, "use_light_tree");
|
||||
integrator->set_use_light_tree(use_light_tree);
|
||||
integrator->set_light_sampling_threshold(
|
||||
(use_light_tree) ? 0.0f : get_float(cscene, "light_sampling_threshold"));
|
||||
|
||||
if (integrator->use_light_tree_is_modified()) {
|
||||
scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL);
|
||||
}
|
||||
|
||||
SamplingPattern sampling_pattern = (SamplingPattern)get_enum(
|
||||
cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_PMJ);
|
||||
@@ -617,7 +623,6 @@ static bool get_known_pass_type(BL::RenderPass &b_pass, PassType &type, PassMode
|
||||
MAP_PASS("Emit", PASS_EMISSION, false);
|
||||
MAP_PASS("Env", PASS_BACKGROUND, false);
|
||||
MAP_PASS("AO", PASS_AO, false);
|
||||
MAP_PASS("Shadow", PASS_SHADOW, false);
|
||||
|
||||
MAP_PASS("BakePrimitive", PASS_BAKE_PRIMITIVE, false);
|
||||
MAP_PASS("BakeDifferential", PASS_BAKE_DIFFERENTIAL, false);
|
||||
|
@@ -1,584 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2011-2022 Blender Foundation
|
||||
|
||||
###########################################################################
|
||||
# Helper macros
|
||||
###########################################################################
|
||||
|
||||
macro(_set_default variable value)
|
||||
if(NOT ${variable})
|
||||
set(${variable} ${value})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
###########################################################################
|
||||
# Precompiled libraries detection
|
||||
#
|
||||
# Use precompiled libraries from Blender repository
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(APPLE)
|
||||
if("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64")
|
||||
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/darwin")
|
||||
else()
|
||||
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/darwin_arm64")
|
||||
endif()
|
||||
|
||||
# Always use system zlib
|
||||
find_package(ZLIB REQUIRED)
|
||||
elseif(WIN32)
|
||||
if(CMAKE_CL_64)
|
||||
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/win64_vc15")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported Visual Studio Version")
|
||||
endif()
|
||||
else()
|
||||
# Path to a locally compiled libraries.
|
||||
set(LIBDIR_NAME ${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR})
|
||||
string(TOLOWER ${LIBDIR_NAME} LIBDIR_NAME)
|
||||
set(LIBDIR_NATIVE_ABI ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_NAME})
|
||||
|
||||
# Path to precompiled libraries with known CentOS 7 ABI.
|
||||
set(LIBDIR_CENTOS7_ABI ${CMAKE_SOURCE_DIR}/../lib/linux_centos7_x86_64)
|
||||
|
||||
# Choose the best suitable libraries.
|
||||
if(EXISTS ${LIBDIR_NATIVE_ABI})
|
||||
set(_cycles_lib_dir ${LIBDIR_NATIVE_ABI})
|
||||
elseif(EXISTS ${LIBDIR_CENTOS7_ABI})
|
||||
set(_cycles_lib_dir ${LIBDIR_CENTOS7_ABI})
|
||||
set(WITH_CXX11_ABI OFF)
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC AND
|
||||
CMAKE_C_COMPILER_VERSION VERSION_LESS 9.3)
|
||||
message(FATAL_ERROR "GCC version must be at least 9.3 for precompiled libraries, found ${CMAKE_C_COMPILER_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(DEFINED _cycles_lib_dir)
|
||||
message(STATUS "Using precompiled libraries at ${_cycles_lib_dir}")
|
||||
endif()
|
||||
|
||||
# Avoid namespace pollustion.
|
||||
unset(LIBDIR_NATIVE_ABI)
|
||||
unset(LIBDIR_CENTOS7_ABI)
|
||||
endif()
|
||||
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
_set_default(ALEMBIC_ROOT_DIR "${_cycles_lib_dir}/alembic")
|
||||
_set_default(BOOST_ROOT "${_cycles_lib_dir}/boost")
|
||||
_set_default(BLOSC_ROOT_DIR "${_cycles_lib_dir}/blosc")
|
||||
_set_default(EMBREE_ROOT_DIR "${_cycles_lib_dir}/embree")
|
||||
_set_default(EPOXY_ROOT_DIR "${_cycles_lib_dir}/epoxy")
|
||||
_set_default(IMATH_ROOT_DIR "${_cycles_lib_dir}/imath")
|
||||
_set_default(GLEW_ROOT_DIR "${_cycles_lib_dir}/glew")
|
||||
_set_default(JPEG_ROOT "${_cycles_lib_dir}/jpeg")
|
||||
_set_default(LLVM_ROOT_DIR "${_cycles_lib_dir}/llvm")
|
||||
_set_default(CLANG_ROOT_DIR "${_cycles_lib_dir}/llvm")
|
||||
_set_default(NANOVDB_ROOT_DIR "${_cycles_lib_dir}/openvdb")
|
||||
_set_default(OPENCOLORIO_ROOT_DIR "${_cycles_lib_dir}/opencolorio")
|
||||
_set_default(OPENEXR_ROOT_DIR "${_cycles_lib_dir}/openexr")
|
||||
_set_default(OPENIMAGEDENOISE_ROOT_DIR "${_cycles_lib_dir}/openimagedenoise")
|
||||
_set_default(OPENIMAGEIO_ROOT_DIR "${_cycles_lib_dir}/openimageio")
|
||||
_set_default(OPENJPEG_ROOT_DIR "${_cycles_lib_dir}/openjpeg")
|
||||
_set_default(OPENSUBDIV_ROOT_DIR "${_cycles_lib_dir}/opensubdiv")
|
||||
_set_default(OPENVDB_ROOT_DIR "${_cycles_lib_dir}/openvdb")
|
||||
_set_default(OSL_ROOT_DIR "${_cycles_lib_dir}/osl")
|
||||
_set_default(PNG_ROOT "${_cycles_lib_dir}/png")
|
||||
_set_default(PUGIXML_ROOT_DIR "${_cycles_lib_dir}/pugixml")
|
||||
_set_default(SDL2_ROOT_DIR "${_cycles_lib_dir}/sdl")
|
||||
_set_default(TBB_ROOT_DIR "${_cycles_lib_dir}/tbb")
|
||||
_set_default(TIFF_ROOT "${_cycles_lib_dir}/tiff")
|
||||
_set_default(USD_ROOT_DIR "${_cycles_lib_dir}/usd")
|
||||
_set_default(WEBP_ROOT_DIR "${_cycles_lib_dir}/webp")
|
||||
_set_default(ZLIB_ROOT "${_cycles_lib_dir}/zlib")
|
||||
if(WIN32)
|
||||
set(LEVEL_ZERO_ROOT_DIR ${_cycles_lib_dir}/level_zero)
|
||||
else()
|
||||
set(LEVEL_ZERO_ROOT_DIR ${_cycles_lib_dir}/level-zero)
|
||||
endif()
|
||||
_set_default(SYCL_ROOT_DIR "${_cycles_lib_dir}/dpcpp")
|
||||
|
||||
# Ignore system libraries
|
||||
set(CMAKE_IGNORE_PATH "${CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES};${CMAKE_SYSTEM_INCLUDE_PATH};${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES};${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}")
|
||||
else()
|
||||
unset(_cycles_lib_dir)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Zlib
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(ZLIB_INCLUDE_DIRS ${_cycles_lib_dir}/zlib/include)
|
||||
set(ZLIB_LIBRARIES ${_cycles_lib_dir}/zlib/lib/libz_st.lib)
|
||||
set(ZLIB_INCLUDE_DIR ${_cycles_lib_dir}/zlib/include)
|
||||
set(ZLIB_LIBRARY ${_cycles_lib_dir}/zlib/lib/libz_st.lib)
|
||||
set(ZLIB_DIR ${_cycles_lib_dir}/zlib)
|
||||
set(ZLIB_FOUND ON)
|
||||
elseif(NOT APPLE)
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# PThreads
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(PTHREADS_LIBRARIES "${_cycles_lib_dir}/pthreads/lib/pthreadVC3.lib")
|
||||
include_directories("${_cycles_lib_dir}/pthreads/include")
|
||||
else()
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
set(PTHREADS_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenImageIO and image libraries
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
add_definitions(
|
||||
# OIIO changed the name of this define in newer versions
|
||||
# we define both, so it would work with both old and new
|
||||
# versions.
|
||||
-DOIIO_STATIC_BUILD
|
||||
-DOIIO_STATIC_DEFINE
|
||||
)
|
||||
|
||||
set(OPENIMAGEIO_INCLUDE_DIR ${OPENIMAGEIO_ROOT_DIR}/include)
|
||||
set(OPENIMAGEIO_INCLUDE_DIRS ${OPENIMAGEIO_INCLUDE_DIR} ${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO)
|
||||
# Special exceptions for libraries which needs explicit debug version
|
||||
set(OPENIMAGEIO_LIBRARIES
|
||||
optimized ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO.lib
|
||||
optimized ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_Util.lib
|
||||
debug ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_d.lib
|
||||
debug ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_Util_d.lib
|
||||
)
|
||||
|
||||
set(PUGIXML_INCLUDE_DIR ${PUGIXML_ROOT_DIR}/include)
|
||||
set(PUGIXML_LIBRARIES
|
||||
optimized ${PUGIXML_ROOT_DIR}/lib/pugixml.lib
|
||||
debug ${PUGIXML_ROOT_DIR}/lib/pugixml_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(OpenImageIO REQUIRED)
|
||||
if(OPENIMAGEIO_PUGIXML_FOUND)
|
||||
set(PUGIXML_INCLUDE_DIR "${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO")
|
||||
set(PUGIXML_LIBRARIES "")
|
||||
else()
|
||||
find_package(PugiXML REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Dependencies
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(OPENJPEG_INCLUDE_DIR ${OPENJPEG}/include/openjpeg-2.3)
|
||||
set(OPENJPEG_LIBRARIES ${_cycles_lib_dir}/openjpeg/lib/openjp2${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
else()
|
||||
find_package(OpenJPEG REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(TIFF REQUIRED)
|
||||
find_package(WebP)
|
||||
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
set(PNG_NAMES png16 libpng16 png libpng)
|
||||
endif()
|
||||
find_package(PNG REQUIRED)
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenEXR
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(OPENEXR_INCLUDE_DIR ${OPENEXR_ROOT_DIR}/include)
|
||||
set(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${OPENEXR_ROOT_DIR}/include/OpenEXR ${IMATH_ROOT_DIR}/include ${IMATH_ROOT_DIR}/include/Imath)
|
||||
set(OPENEXR_LIBRARIES
|
||||
optimized ${OPENEXR_ROOT_DIR}/lib/OpenEXR_s.lib
|
||||
optimized ${OPENEXR_ROOT_DIR}/lib/OpenEXRCore_s.lib
|
||||
optimized ${OPENEXR_ROOT_DIR}/lib/Iex_s.lib
|
||||
optimized ${IMATH_ROOT_DIR}/lib/Imath_s.lib
|
||||
optimized ${OPENEXR_ROOT_DIR}/lib/IlmThread_s.lib
|
||||
debug ${OPENEXR_ROOT_DIR}/lib/OpenEXR_s_d.lib
|
||||
debug ${OPENEXR_ROOT_DIR}/lib/OpenEXRCore_s_d.lib
|
||||
debug ${OPENEXR_ROOT_DIR}/lib/Iex_s_d.lib
|
||||
debug ${IMATH_ROOT_DIR}/lib/Imath_s_d.lib
|
||||
debug ${OPENEXR_ROOT_DIR}/lib/IlmThread_s_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(OpenEXR REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenShadingLanguage & LLVM
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OSL)
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
set(LLVM_STATIC ON)
|
||||
endif()
|
||||
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
# TODO(sergey): On Windows llvm-config doesn't give proper results for the
|
||||
# library names, use hardcoded libraries for now.
|
||||
file(GLOB _llvm_libs_release ${LLVM_ROOT_DIR}/lib/*.lib)
|
||||
file(GLOB _llvm_libs_debug ${LLVM_ROOT_DIR}/debug/lib/*.lib)
|
||||
set(_llvm_libs)
|
||||
foreach(_llvm_lib_path ${_llvm_libs_release})
|
||||
get_filename_component(_llvm_lib_name ${_llvm_lib_path} ABSOLUTE)
|
||||
list(APPEND _llvm_libs optimized ${_llvm_lib_name})
|
||||
endforeach()
|
||||
foreach(_llvm_lib_path ${_llvm_libs_debug})
|
||||
get_filename_component(_llvm_lib_name ${_llvm_lib_path} ABSOLUTE)
|
||||
list(APPEND _llvm_libs debug ${_llvm_lib_name})
|
||||
endforeach()
|
||||
set(LLVM_LIBRARY ${_llvm_libs})
|
||||
unset(_llvm_lib_name)
|
||||
unset(_llvm_lib_path)
|
||||
unset(_llvm_libs)
|
||||
unset(_llvm_libs_debug)
|
||||
unset(_llvm_libs_release)
|
||||
|
||||
set(OSL_INCLUDE_DIR ${OSL_ROOT_DIR}/include)
|
||||
set(OSL_LIBRARIES
|
||||
optimized ${OSL_ROOT_DIR}/lib/oslcomp.lib
|
||||
optimized ${OSL_ROOT_DIR}/lib/oslexec.lib
|
||||
optimized ${OSL_ROOT_DIR}/lib/oslquery.lib
|
||||
debug ${OSL_ROOT_DIR}/lib/oslcomp_d.lib
|
||||
debug ${OSL_ROOT_DIR}/lib/oslexec_d.lib
|
||||
debug ${OSL_ROOT_DIR}/lib/oslquery_d.lib
|
||||
${PUGIXML_LIBRARIES}
|
||||
)
|
||||
|
||||
find_program(OSL_COMPILER NAMES oslc PATHS ${OSL_ROOT_DIR}/bin)
|
||||
else()
|
||||
find_package(OSL REQUIRED)
|
||||
find_package(LLVM REQUIRED)
|
||||
find_package(Clang REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenPGL
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_PATH_GUIDING)
|
||||
if(NOT openpgl_DIR AND EXISTS ${_cycles_lib_dir})
|
||||
set(openpgl_DIR ${_cycles_lib_dir}/openpgl/lib/cmake/openpgl)
|
||||
endif()
|
||||
|
||||
find_package(openpgl QUIET)
|
||||
if(openpgl_FOUND)
|
||||
if(WIN32)
|
||||
get_target_property(OPENPGL_LIBRARIES_RELEASE openpgl::openpgl LOCATION_RELEASE)
|
||||
get_target_property(OPENPGL_LIBRARIES_DEBUG openpgl::openpgl LOCATION_DEBUG)
|
||||
set(OPENPGL_LIBRARIES optimized ${OPENPGL_LIBRARIES_RELEASE} debug ${OPENPGL_LIBRARIES_DEBUG})
|
||||
else()
|
||||
get_target_property(OPENPGL_LIBRARIES openpgl::openpgl LOCATION)
|
||||
endif()
|
||||
get_target_property(OPENPGL_INCLUDE_DIR openpgl::openpgl INTERFACE_INCLUDE_DIRECTORIES)
|
||||
else()
|
||||
set_and_warn_library_found("OpenPGL" openpgl_FOUND WITH_CYCLES_PATH_GUIDING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenColorIO
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENCOLORIO)
|
||||
set(WITH_OPENCOLORIO ON)
|
||||
|
||||
if(NOT USD_OVERRIDE_OPENCOLORIO)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(OPENCOLORIO_INCLUDE_DIRS ${OPENCOLORIO_ROOT_DIR}/include)
|
||||
set(OPENCOLORIO_LIBRARIES
|
||||
optimized ${OPENCOLORIO_ROOT_DIR}/lib/OpenColorIO.lib
|
||||
optimized ${OPENCOLORIO_ROOT_DIR}/lib/libyaml-cpp.lib
|
||||
optimized ${OPENCOLORIO_ROOT_DIR}/lib/libexpatMD.lib
|
||||
optimized ${OPENCOLORIO_ROOT_DIR}/lib/pystring.lib
|
||||
debug ${OPENCOLORIO_ROOT_DIR}/lib/OpencolorIO_d.lib
|
||||
debug ${OPENCOLORIO_ROOT_DIR}/lib/libyaml-cpp_d.lib
|
||||
debug ${OPENCOLORIO_ROOT_DIR}/lib/libexpatdMD.lib
|
||||
debug ${OPENCOLORIO_ROOT_DIR}/lib/pystring_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(OpenColorIO REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Boost
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
if(MSVC)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
else()
|
||||
set(BOOST_LIBRARYDIR ${_cycles_lib_dir}/boost/lib)
|
||||
set(Boost_NO_BOOST_CMAKE ON)
|
||||
set(Boost_NO_SYSTEM_PATHS ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(BOOST_INCLUDE_DIR ${BOOST_ROOT}/include)
|
||||
set(BOOST_VERSION_HEADER ${BOOST_INCLUDE_DIR}/boost/version.hpp)
|
||||
if(EXISTS ${BOOST_VERSION_HEADER})
|
||||
file(STRINGS "${BOOST_VERSION_HEADER}" BOOST_LIB_VERSION REGEX "#define BOOST_LIB_VERSION ")
|
||||
if(BOOST_LIB_VERSION MATCHES "#define BOOST_LIB_VERSION \"([0-9_]+)\"")
|
||||
set(BOOST_VERSION "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT BOOST_VERSION)
|
||||
message(FATAL_ERROR "Unable to determine Boost version")
|
||||
endif()
|
||||
set(BOOST_POSTFIX "vc142-mt-x64-${BOOST_VERSION}.lib")
|
||||
set(BOOST_DEBUG_POSTFIX "vc142-mt-gd-x64-${BOOST_VERSION}.lib")
|
||||
set(BOOST_LIBRARIES
|
||||
optimized ${BOOST_ROOT}/lib/libboost_date_time-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_iostreams-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_filesystem-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_regex-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_system-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_thread-${BOOST_POSTFIX}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_chrono-${BOOST_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_date_time-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_iostreams-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_filesystem-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_regex-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_system-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_thread-${BOOST_DEBUG_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_chrono-${BOOST_DEBUG_POSTFIX}
|
||||
)
|
||||
if(WITH_CYCLES_OSL)
|
||||
set(BOOST_LIBRARIES ${BOOST_LIBRARIES}
|
||||
optimized ${BOOST_ROOT}/lib/libboost_wave-${BOOST_POSTFIX}
|
||||
debug ${BOOST_ROOT}/lib/libboost_wave-${BOOST_DEBUG_POSTFIX})
|
||||
endif()
|
||||
else()
|
||||
set(__boost_packages iostreams filesystem regex system thread date_time)
|
||||
if(WITH_CYCLES_OSL)
|
||||
list(APPEND __boost_packages wave)
|
||||
endif()
|
||||
find_package(Boost 1.48 COMPONENTS ${__boost_packages} REQUIRED)
|
||||
if(NOT Boost_FOUND)
|
||||
# Try to find non-multithreaded if -mt not found, this flag
|
||||
# doesn't matter for us, it has nothing to do with thread
|
||||
# safety, but keep it to not disturb build setups.
|
||||
set(Boost_USE_MULTITHREADED OFF)
|
||||
find_package(Boost 1.48 COMPONENTS ${__boost_packages})
|
||||
endif()
|
||||
unset(__boost_packages)
|
||||
|
||||
set(BOOST_INCLUDE_DIR ${Boost_INCLUDE_DIRS})
|
||||
set(BOOST_LIBRARIES ${Boost_LIBRARIES})
|
||||
set(BOOST_LIBPATH ${Boost_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
set(BOOST_DEFINITIONS "-DBOOST_ALL_NO_LIB ${BOOST_DEFINITIONS}")
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Embree
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_EMBREE)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(EMBREE_INCLUDE_DIRS ${EMBREE_ROOT_DIR}/include)
|
||||
set(EMBREE_LIBRARIES
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/embree3.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/embree_avx2.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/embree_avx.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/embree_sse42.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/lexers.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/math.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/simd.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/tasking.lib
|
||||
optimized ${EMBREE_ROOT_DIR}/lib/sys.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/embree3_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/embree_avx2_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/embree_avx_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/embree_sse42_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/lexers_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/math_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/simd_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/sys_d.lib
|
||||
debug ${EMBREE_ROOT_DIR}/lib/tasking_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(Embree 3.8.0 REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Logging
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_LOGGING)
|
||||
find_package(Glog REQUIRED)
|
||||
find_package(Gflags REQUIRED)
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenSubdiv
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENSUBDIV)
|
||||
set(WITH_OPENSUBDIV ON)
|
||||
|
||||
if(NOT USD_OVERRIDE_OPENSUBDIV)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(OPENSUBDIV_INCLUDE_DIRS ${OPENSUBDIV_ROOT_DIR}/include)
|
||||
set(OPENSUBDIV_LIBRARIES
|
||||
optimized ${OPENSUBDIV_ROOT_DIR}/lib/osdCPU.lib
|
||||
optimized ${OPENSUBDIV_ROOT_DIR}/lib/osdGPU.lib
|
||||
debug ${OPENSUBDIV_ROOT_DIR}/lib/osdCPU_d.lib
|
||||
debug ${OPENSUBDIV_ROOT_DIR}/lib/osdGPU_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(OpenSubdiv REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenVDB
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENVDB)
|
||||
set(WITH_OPENVDB ON)
|
||||
set(OPENVDB_DEFINITIONS -DNOMINMAX -D_USE_MATH_DEFINES)
|
||||
|
||||
if(NOT USD_OVERRIDE_OPENVDB)
|
||||
find_package(OpenVDB REQUIRED)
|
||||
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(BLOSC_LIBRARY
|
||||
optimized ${BLOSC_ROOT_DIR}/lib/libblosc.lib
|
||||
debug ${BLOSC_ROOT_DIR}/lib/libblosc_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(Blosc REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# NanoVDB
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_NANOVDB)
|
||||
set(WITH_NANOVDB ON)
|
||||
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(NANOVDB_INCLUDE_DIR ${NANOVDB_ROOT_DIR}/include)
|
||||
set(NANOVDB_INCLUDE_DIRS ${NANOVDB_INCLUDE_DIR})
|
||||
else()
|
||||
find_package(NanoVDB REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenImageDenoise
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENIMAGEDENOISE)
|
||||
set(WITH_OPENIMAGEDENOISE ON)
|
||||
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(OPENIMAGEDENOISE_INCLUDE_DIRS ${OPENIMAGEDENOISE_ROOT_DIR}/include)
|
||||
set(OPENIMAGEDENOISE_LIBRARIES
|
||||
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/OpenImageDenoise.lib
|
||||
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/common.lib
|
||||
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/dnnl.lib
|
||||
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/OpenImageDenoise_d.lib
|
||||
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/common_d.lib
|
||||
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/dnnl_d.lib
|
||||
)
|
||||
else()
|
||||
find_package(OpenImageDenoise REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# TBB
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(NOT USD_OVERRIDE_TBB)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(TBB_INCLUDE_DIRS ${TBB_ROOT_DIR}/include)
|
||||
set(TBB_LIBRARIES
|
||||
optimized ${TBB_ROOT_DIR}/lib/tbb.lib
|
||||
debug ${TBB_ROOT_DIR}/lib/tbb_debug.lib
|
||||
)
|
||||
else()
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Epoxy
|
||||
###########################################################################
|
||||
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if((WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) OR
|
||||
WITH_CYCLES_HYDRA_RENDER_DELEGATE)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(Epoxy_LIBRARIES "${_cycles_lib_dir}/epoxy/lib/epoxy.lib")
|
||||
set(Epoxy_INCLUDE_DIRS "${_cycles_lib_dir}/epoxy/include")
|
||||
else()
|
||||
find_package(Epoxy REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# Alembic
|
||||
###########################################################################
|
||||
|
||||
if(WITH_CYCLES_ALEMBIC)
|
||||
if(CYCLES_STANDALONE_REPOSITORY)
|
||||
if(MSVC AND EXISTS ${_cycles_lib_dir})
|
||||
set(ALEMBIC_INCLUDE_DIRS ${_cycles_lib_dir}/alembic/include)
|
||||
set(ALEMBIC_LIBRARIES
|
||||
optimized ${_cycles_lib_dir}/alembic/lib/Alembic.lib
|
||||
debug ${_cycles_lib_dir}/alembic/lib/Alembic_d.lib)
|
||||
else()
|
||||
find_package(Alembic REQUIRED)
|
||||
endif()
|
||||
|
||||
set(WITH_ALEMBIC ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# System Libraries
|
||||
###########################################################################
|
||||
|
||||
# Detect system libraries again
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
unset(CMAKE_IGNORE_PATH)
|
||||
unset(_cycles_lib_dir)
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# SDL
|
||||
###########################################################################
|
||||
@@ -687,5 +109,3 @@ if(WITH_CYCLES_DEVICE_ONEAPI)
|
||||
set(WITH_CYCLES_DEVICE_ONEAPI OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
unset(_cycles_lib_dir)
|
||||
|
@@ -38,7 +38,7 @@ class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop {
|
||||
CUDADevice *device_ = nullptr;
|
||||
|
||||
/* OpenGL PBO which is currently registered as the destination for the CUDA buffer. */
|
||||
uint opengl_pbo_id_ = 0;
|
||||
int64_t opengl_pbo_id_ = 0;
|
||||
/* Buffer area in pixels of the corresponding PBO. */
|
||||
int64_t buffer_area_ = 0;
|
||||
|
||||
|
@@ -351,6 +351,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
|
||||
info.num = 0;
|
||||
|
||||
info.has_nanovdb = true;
|
||||
info.has_light_tree = true;
|
||||
info.has_osl = true;
|
||||
info.has_guiding = true;
|
||||
info.has_profiling = true;
|
||||
@@ -399,6 +400,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
|
||||
|
||||
/* Accumulate device info. */
|
||||
info.has_nanovdb &= device.has_nanovdb;
|
||||
info.has_light_tree &= device.has_light_tree;
|
||||
info.has_osl &= device.has_osl;
|
||||
info.has_guiding &= device.has_guiding;
|
||||
info.has_profiling &= device.has_profiling;
|
||||
|
@@ -65,6 +65,7 @@ class DeviceInfo {
|
||||
int num;
|
||||
bool display_device; /* GPU is used as a display device. */
|
||||
bool has_nanovdb; /* Support NanoVDB volumes. */
|
||||
bool has_light_tree; /* Support light tree. */
|
||||
bool has_osl; /* Support Open Shading Language. */
|
||||
bool has_guiding; /* Support path guiding. */
|
||||
bool has_profiling; /* Supports runtime collection of profiling info. */
|
||||
@@ -84,6 +85,7 @@ class DeviceInfo {
|
||||
cpu_threads = 0;
|
||||
display_device = false;
|
||||
has_nanovdb = false;
|
||||
has_light_tree = true;
|
||||
has_osl = false;
|
||||
has_guiding = false;
|
||||
has_profiling = false;
|
||||
|
@@ -137,6 +137,7 @@ void device_hip_info(vector<DeviceInfo> &devices)
|
||||
info.num = num;
|
||||
|
||||
info.has_nanovdb = true;
|
||||
info.has_light_tree = false;
|
||||
info.denoisers = 0;
|
||||
|
||||
info.has_gpu_queue = true;
|
||||
|
@@ -36,7 +36,7 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
|
||||
HIPDevice *device_ = nullptr;
|
||||
|
||||
/* OpenGL PBO which is currently registered as the destination for the HIP buffer. */
|
||||
uint opengl_pbo_id_ = 0;
|
||||
int64_t opengl_pbo_id_ = 0;
|
||||
/* Buffer area in pixels of the corresponding PBO. */
|
||||
int64_t buffer_area_ = 0;
|
||||
|
||||
|
@@ -117,6 +117,8 @@ class MetalDevice : public Device {
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* low-level memory management */
|
||||
|
||||
bool max_working_set_exceeded(size_t safety_margin = 8 * 1024 * 1024) const;
|
||||
|
||||
MetalMem *generic_alloc(device_memory &mem);
|
||||
|
||||
void generic_copy_to(device_memory &mem);
|
||||
|
@@ -446,6 +446,14 @@ void MetalDevice::erase_allocation(device_memory &mem)
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalDevice::max_working_set_exceeded(size_t safety_margin) const
|
||||
{
|
||||
/* We're allowed to allocate beyond the safe working set size, but then if all resources are made
|
||||
* resident we will get command buffer failures at render time. */
|
||||
size_t available = [mtlDevice recommendedMaxWorkingSetSize] - safety_margin;
|
||||
return (stats.mem_used > available);
|
||||
}
|
||||
|
||||
MetalDevice::MetalMem *MetalDevice::generic_alloc(device_memory &mem)
|
||||
{
|
||||
size_t size = mem.memory_size();
|
||||
@@ -523,6 +531,11 @@ MetalDevice::MetalMem *MetalDevice::generic_alloc(device_memory &mem)
|
||||
mmem->use_UMA = false;
|
||||
}
|
||||
|
||||
if (max_working_set_exceeded()) {
|
||||
set_error("System is out of GPU memory");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mmem;
|
||||
}
|
||||
|
||||
@@ -921,9 +934,8 @@ void MetalDevice::tex_alloc(device_texture &mem)
|
||||
<< string_human_readable_size(mem.memory_size()) << ")";
|
||||
|
||||
mtlTexture = [mtlDevice newTextureWithDescriptor:desc];
|
||||
assert(mtlTexture);
|
||||
|
||||
if (!mtlTexture) {
|
||||
set_error("System is out of GPU memory");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -955,7 +967,10 @@ void MetalDevice::tex_alloc(device_texture &mem)
|
||||
<< string_human_readable_size(mem.memory_size()) << ")";
|
||||
|
||||
mtlTexture = [mtlDevice newTextureWithDescriptor:desc];
|
||||
assert(mtlTexture);
|
||||
if (!mtlTexture) {
|
||||
set_error("System is out of GPU memory");
|
||||
return;
|
||||
}
|
||||
|
||||
[mtlTexture replaceRegion:MTLRegionMake2D(0, 0, mem.data_width, mem.data_height)
|
||||
mipmapLevel:0
|
||||
@@ -1017,6 +1032,10 @@ void MetalDevice::tex_alloc(device_texture &mem)
|
||||
need_texture_info = true;
|
||||
|
||||
texture_info[slot].data = uint64_t(slot) | (sampler_index << 32);
|
||||
|
||||
if (max_working_set_exceeded()) {
|
||||
set_error("System is out of GPU memory");
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDevice::tex_free(device_texture &mem)
|
||||
@@ -1077,6 +1096,10 @@ void MetalDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (max_working_set_exceeded()) {
|
||||
set_error("System is out of GPU memory");
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -31,6 +31,8 @@ bool device_oneapi_init()
|
||||
* improves stability as of intel/LLVM SYCL-nightly/20220529.
|
||||
* All these env variable can be set beforehand by end-users and
|
||||
* will in that case -not- be overwritten. */
|
||||
/* By default, enable only Level-Zero and if all devices are allowed, also CUDA and HIP.
|
||||
* OpenCL backend isn't currently well supported. */
|
||||
# ifdef _WIN32
|
||||
if (getenv("SYCL_CACHE_PERSISTENT") == nullptr) {
|
||||
_putenv_s("SYCL_CACHE_PERSISTENT", "1");
|
||||
@@ -39,7 +41,12 @@ bool device_oneapi_init()
|
||||
_putenv_s("SYCL_CACHE_THRESHOLD", "0");
|
||||
}
|
||||
if (getenv("SYCL_DEVICE_FILTER") == nullptr) {
|
||||
_putenv_s("SYCL_DEVICE_FILTER", "level_zero");
|
||||
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
|
||||
_putenv_s("SYCL_DEVICE_FILTER", "level_zero");
|
||||
}
|
||||
else {
|
||||
_putenv_s("SYCL_DEVICE_FILTER", "level_zero,cuda,hip");
|
||||
}
|
||||
}
|
||||
if (getenv("SYCL_ENABLE_PCI") == nullptr) {
|
||||
_putenv_s("SYCL_ENABLE_PCI", "1");
|
||||
@@ -50,7 +57,12 @@ bool device_oneapi_init()
|
||||
# elif __linux__
|
||||
setenv("SYCL_CACHE_PERSISTENT", "1", false);
|
||||
setenv("SYCL_CACHE_THRESHOLD", "0", false);
|
||||
setenv("SYCL_DEVICE_FILTER", "level_zero", false);
|
||||
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
|
||||
setenv("SYCL_DEVICE_FILTER", "level_zero", false);
|
||||
}
|
||||
else {
|
||||
setenv("SYCL_DEVICE_FILTER", "level_zero,cuda,hip", false);
|
||||
}
|
||||
setenv("SYCL_ENABLE_PCI", "1", false);
|
||||
setenv("SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE_FOR_IN_ORDER_QUEUE", "0", false);
|
||||
# endif
|
||||
|
@@ -430,9 +430,9 @@ void OneapiDevice::check_usm(SyclQueue *queue_, const void *usm_ptr, bool allow_
|
||||
sycl::usm::alloc usm_type = get_pointer_type(usm_ptr, queue->get_context());
|
||||
(void)usm_type;
|
||||
assert(usm_type == sycl::usm::alloc::device ||
|
||||
((device_type == sycl::info::device_type::cpu || allow_host) &&
|
||||
usm_type == sycl::usm::alloc::host ||
|
||||
usm_type == sycl::usm::alloc::unknown));
|
||||
(usm_type == sycl::usm::alloc::host &&
|
||||
(allow_host || device_type == sycl::info::device_type::cpu)) ||
|
||||
usm_type == sycl::usm::alloc::unknown);
|
||||
# else
|
||||
/* Silence warning about unused arguments. */
|
||||
(void)queue_;
|
||||
|
@@ -66,7 +66,9 @@ struct SocketType {
|
||||
LINK_NORMAL = (1 << 8),
|
||||
LINK_POSITION = (1 << 9),
|
||||
LINK_TANGENT = (1 << 10),
|
||||
DEFAULT_LINK_MASK = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10)
|
||||
LINK_OSL_INITIALIZER = (1 << 11),
|
||||
DEFAULT_LINK_MASK = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) |
|
||||
(1 << 10) | (1 << 11)
|
||||
};
|
||||
|
||||
ustring name;
|
||||
|
@@ -285,10 +285,17 @@ set(SRC_KERNEL_INTEGRATOR_HEADERS
|
||||
)
|
||||
|
||||
set(SRC_KERNEL_LIGHT_HEADERS
|
||||
light/light.h
|
||||
light/area.h
|
||||
light/background.h
|
||||
light/common.h
|
||||
light/distant.h
|
||||
light/distribution.h
|
||||
light/light.h
|
||||
light/point.h
|
||||
light/sample.h
|
||||
light/spot.h
|
||||
light/tree.h
|
||||
light/triangle.h
|
||||
)
|
||||
|
||||
set(SRC_KERNEL_SAMPLE_HEADERS
|
||||
@@ -466,6 +473,7 @@ if(WITH_CYCLES_CUDA_BINARIES)
|
||||
|
||||
if(WITH_CYCLES_DEBUG)
|
||||
set(cuda_flags ${cuda_flags} -D WITH_CYCLES_DEBUG)
|
||||
set(cuda_flags ${cuda_flags} --ptxas-options="-v")
|
||||
endif()
|
||||
|
||||
set(_cuda_nvcc_args
|
||||
@@ -473,7 +481,6 @@ if(WITH_CYCLES_CUDA_BINARIES)
|
||||
${CUDA_NVCC_FLAGS}
|
||||
--${format}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}${cuda_kernel_src}
|
||||
--ptxas-options="-v"
|
||||
${cuda_flags})
|
||||
|
||||
if(WITH_COMPILER_CCACHE AND CCACHE_PROGRAM)
|
||||
|
@@ -69,7 +69,7 @@ ccl_device int bsdf_diffuse_sample(ccl_private const ShaderClosure *sc,
|
||||
ccl_device int bsdf_translucent_setup(ccl_private DiffuseBsdf *bsdf)
|
||||
{
|
||||
bsdf->type = CLOSURE_BSDF_TRANSLUCENT_ID;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
ccl_device Spectrum bsdf_translucent_eval(ccl_private const ShaderClosure *sc,
|
||||
|
@@ -34,7 +34,7 @@ ccl_device int bsdf_hair_transmission_setup(ccl_private HairBsdf *bsdf)
|
||||
bsdf->type = CLOSURE_BSDF_HAIR_TRANSMISSION_ID;
|
||||
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
|
||||
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
ccl_device Spectrum bsdf_hair_reflection_eval(ccl_private const ShaderClosure *sc,
|
||||
|
@@ -196,7 +196,7 @@ ccl_device int bsdf_principled_hair_setup(ccl_private ShaderData *sd,
|
||||
|
||||
bsdf->extra->geom = make_float4(Y.x, Y.y, Y.z, h);
|
||||
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
#endif /* __HAIR__ */
|
||||
|
@@ -346,7 +346,7 @@ ccl_device int bsdf_microfacet_ggx_refraction_setup(ccl_private MicrofacetBsdf *
|
||||
|
||||
bsdf->type = CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID;
|
||||
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
ccl_device void bsdf_microfacet_ggx_blur(ccl_private ShaderClosure *sc, float roughness)
|
||||
@@ -776,7 +776,7 @@ ccl_device int bsdf_microfacet_beckmann_refraction_setup(ccl_private MicrofacetB
|
||||
bsdf->alpha_y = bsdf->alpha_x;
|
||||
|
||||
bsdf->type = CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
ccl_device void bsdf_microfacet_beckmann_blur(ccl_private ShaderClosure *sc, float roughness)
|
||||
|
@@ -559,7 +559,7 @@ ccl_device int bsdf_microfacet_multi_ggx_glass_setup(ccl_private MicrofacetBsdf
|
||||
|
||||
bsdf->type = CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID;
|
||||
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
|
||||
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
|
||||
}
|
||||
|
||||
ccl_device int bsdf_microfacet_multi_ggx_glass_fresnel_setup(ccl_private MicrofacetBsdf *bsdf,
|
||||
|
@@ -60,6 +60,13 @@ KERNEL_DATA_ARRAY(KernelLight, lights)
|
||||
KERNEL_DATA_ARRAY(float2, light_background_marginal_cdf)
|
||||
KERNEL_DATA_ARRAY(float2, light_background_conditional_cdf)
|
||||
|
||||
/* light tree */
|
||||
KERNEL_DATA_ARRAY(KernelLightTreeNode, light_tree_nodes)
|
||||
KERNEL_DATA_ARRAY(KernelLightTreeEmitter, light_tree_emitters)
|
||||
KERNEL_DATA_ARRAY(uint, light_to_tree)
|
||||
KERNEL_DATA_ARRAY(uint, object_lookup_offset)
|
||||
KERNEL_DATA_ARRAY(uint, triangle_to_tree)
|
||||
|
||||
/* particles */
|
||||
KERNEL_DATA_ARRAY(KernelParticle, particles)
|
||||
|
||||
|
@@ -23,24 +23,19 @@ KERNEL_STRUCT_MEMBER(background, int, volume_shader)
|
||||
KERNEL_STRUCT_MEMBER(background, float, volume_step_size)
|
||||
KERNEL_STRUCT_MEMBER(background, int, transparent)
|
||||
KERNEL_STRUCT_MEMBER(background, float, transparent_roughness_squared_threshold)
|
||||
/* Portal sampling. */
|
||||
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
|
||||
KERNEL_STRUCT_MEMBER(background, int, num_portals)
|
||||
KERNEL_STRUCT_MEMBER(background, int, portal_offset)
|
||||
/* Sun sampling. */
|
||||
KERNEL_STRUCT_MEMBER(background, float, sun_weight)
|
||||
/* Importance map sampling. */
|
||||
KERNEL_STRUCT_MEMBER(background, float, map_weight)
|
||||
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
|
||||
KERNEL_STRUCT_MEMBER(background, int, map_res_x)
|
||||
KERNEL_STRUCT_MEMBER(background, int, map_res_y)
|
||||
/* Multiple importance sampling. */
|
||||
KERNEL_STRUCT_MEMBER(background, int, use_mis)
|
||||
/* Lightgroup. */
|
||||
KERNEL_STRUCT_MEMBER(background, int, lightgroup)
|
||||
/* Padding. */
|
||||
KERNEL_STRUCT_MEMBER(background, int, pad1)
|
||||
KERNEL_STRUCT_MEMBER(background, int, pad2)
|
||||
KERNEL_STRUCT_MEMBER(background, int, pad3)
|
||||
/* Light Index. */
|
||||
KERNEL_STRUCT_MEMBER(background, int, light_index)
|
||||
KERNEL_STRUCT_END(KernelBackground)
|
||||
|
||||
/* BVH: own BVH2 if no native device acceleration struct used. */
|
||||
@@ -102,8 +97,6 @@ KERNEL_STRUCT_MEMBER(film, int, pass_emission)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_background)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_ao)
|
||||
KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow)
|
||||
KERNEL_STRUCT_MEMBER(film, float, pass_shadow_scale)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte)
|
||||
@@ -137,9 +130,6 @@ KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness)
|
||||
/* Padding. */
|
||||
KERNEL_STRUCT_MEMBER(film, int, pad1)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pad2)
|
||||
KERNEL_STRUCT_END(KernelFilm)
|
||||
|
||||
/* Integrator. */
|
||||
@@ -147,10 +137,18 @@ KERNEL_STRUCT_END(KernelFilm)
|
||||
KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
|
||||
/* Emission. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_light_tree)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
|
||||
/* Portal sampling. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_portals)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, portal_offset)
|
||||
/* Flat light distribution. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_distribution)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_all_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, pdf_triangles)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, pdf_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_triangles)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, light_inv_rr_threshold)
|
||||
/* Bounces. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, min_bounce)
|
||||
@@ -177,8 +175,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, seed)
|
||||
/* Clamp. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_direct)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_indirect)
|
||||
/* MIS. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_lamp_mis)
|
||||
/* Caustics. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_caustics)
|
||||
/* Sampling pattern. */
|
||||
@@ -195,7 +191,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, filter_closures)
|
||||
/* MIS debugging. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type)
|
||||
|
||||
/* Path Guiding */
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, surface_guiding_probability)
|
||||
KERNEL_STRUCT_MEMBER(integrator, float, volume_guiding_probability)
|
||||
@@ -210,7 +205,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
|
||||
/* Padding. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad1)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad2)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad3)
|
||||
KERNEL_STRUCT_END(KernelIntegrator)
|
||||
|
||||
/* SVM. For shader specialization. */
|
||||
|
@@ -314,11 +314,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
int kernel_index);
|
||||
ccl_gpu_kernel_lambda_pass.kernel_index = kernel_index;
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -333,11 +329,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
int kernel_index);
|
||||
ccl_gpu_kernel_lambda_pass.kernel_index = kernel_index;
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -349,11 +341,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
{
|
||||
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, path, queued_kernel) != 0);
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -366,11 +354,8 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
{
|
||||
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, path, queued_kernel) == 0);
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices + indices_offset,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(
|
||||
num_states, indices + indices_offset, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -383,11 +368,8 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
{
|
||||
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, shadow_path, queued_kernel) == 0);
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices + indices_offset,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(
|
||||
num_states, indices + indices_offset, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -431,11 +413,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
int num_active_paths);
|
||||
ccl_gpu_kernel_lambda_pass.num_active_paths = num_active_paths;
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
@@ -469,11 +447,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
|
||||
int num_active_paths);
|
||||
ccl_gpu_kernel_lambda_pass.num_active_paths = num_active_paths;
|
||||
|
||||
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
|
||||
num_states,
|
||||
indices,
|
||||
num_indices,
|
||||
ccl_gpu_kernel_lambda_pass);
|
||||
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
|
||||
}
|
||||
ccl_gpu_kernel_postfix
|
||||
|
||||
|
@@ -56,7 +56,7 @@ void gpu_parallel_active_index_array_impl(const uint num_states,
|
||||
const uint is_active = (state_index < num_states) ? is_active_op(state_index) : 0;
|
||||
#else /* !__KERNEL__ONEAPI__ */
|
||||
# ifndef __KERNEL_METAL__
|
||||
template<uint blocksize, typename IsActiveOp>
|
||||
template<typename IsActiveOp>
|
||||
__device__
|
||||
# endif
|
||||
void
|
||||
@@ -79,6 +79,10 @@ __device__
|
||||
{
|
||||
extern ccl_gpu_shared int warp_offset[];
|
||||
|
||||
# ifndef __KERNEL_METAL__
|
||||
const uint blocksize = ccl_gpu_block_dim_x;
|
||||
# endif
|
||||
|
||||
const uint thread_index = ccl_gpu_thread_idx_x;
|
||||
const uint thread_warp = thread_index % ccl_gpu_warp_size;
|
||||
|
||||
@@ -149,7 +153,7 @@ __device__
|
||||
|
||||
#ifdef __KERNEL_METAL__
|
||||
|
||||
# define gpu_parallel_active_index_array(dummy, num_states, indices, num_indices, is_active_op) \
|
||||
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
|
||||
const uint is_active = (ccl_gpu_global_id_x() < num_states) ? \
|
||||
is_active_op(ccl_gpu_global_id_x()) : \
|
||||
0; \
|
||||
@@ -167,15 +171,13 @@ __device__
|
||||
simdgroup_offset)
|
||||
#elif defined(__KERNEL_ONEAPI__)
|
||||
|
||||
# define gpu_parallel_active_index_array( \
|
||||
blocksize, num_states, indices, num_indices, is_active_op) \
|
||||
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
|
||||
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
|
||||
|
||||
#else
|
||||
|
||||
# define gpu_parallel_active_index_array( \
|
||||
blocksize, num_states, indices, num_indices, is_active_op) \
|
||||
gpu_parallel_active_index_array_impl<blocksize>(num_states, indices, num_indices, is_active_op)
|
||||
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
|
||||
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
|
||||
|
||||
#endif
|
||||
|
||||
|
@@ -157,4 +157,47 @@ ccl_device_inline void film_write_data_passes(KernelGlobals kg,
|
||||
#endif
|
||||
}
|
||||
|
||||
ccl_device_inline void film_write_data_passes_background(
|
||||
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
|
||||
{
|
||||
#ifdef __PASSES__
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
|
||||
if (!(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't write data passes for paths that were split off for shadow catchers
|
||||
* to avoid double-counting. */
|
||||
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int flag = kernel_data.film.pass_flag;
|
||||
|
||||
if (!(flag & PASS_ANY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(path_flag & PATH_RAY_SINGLE_PASS_DONE)) {
|
||||
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
|
||||
|
||||
if (INTEGRATOR_STATE(state, path, sample) == 0) {
|
||||
if (flag & PASSMASK(DEPTH)) {
|
||||
film_overwrite_pass_float(buffer + kernel_data.film.pass_depth, 0.0f);
|
||||
}
|
||||
if (flag & PASSMASK(OBJECT_ID)) {
|
||||
film_overwrite_pass_float(buffer + kernel_data.film.pass_object_id, 0.0f);
|
||||
}
|
||||
if (flag & PASSMASK(MATERIAL_ID)) {
|
||||
film_overwrite_pass_float(buffer + kernel_data.film.pass_material_id, 0.0f);
|
||||
}
|
||||
if (flag & PASSMASK(POSITION)) {
|
||||
film_overwrite_pass_float3(buffer + kernel_data.film.pass_position, zero_float3());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -527,17 +527,6 @@ ccl_device_inline void film_write_direct_light(KernelGlobals kg,
|
||||
film_write_pass_spectrum(buffer + pass_offset, contribution);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write shadow pass. */
|
||||
if (kernel_data.film.pass_shadow != PASS_UNUSED && (path_flag & PATH_RAY_SHADOW_FOR_LIGHT) &&
|
||||
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
|
||||
const Spectrum unshadowed_throughput = INTEGRATOR_STATE(
|
||||
state, shadow_path, unshadowed_throughput);
|
||||
const Spectrum shadowed_throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
|
||||
const Spectrum shadow = safe_divide(shadowed_throughput, unshadowed_throughput) *
|
||||
kernel_data.film.pass_shadow_scale;
|
||||
film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow, shadow);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -11,10 +11,10 @@
|
||||
#include "kernel/integrator/path_state.h"
|
||||
#include "kernel/integrator/shadow_catcher.h"
|
||||
|
||||
#include "kernel/light/light.h"
|
||||
|
||||
#include "kernel/geom/geom.h"
|
||||
|
||||
#include "kernel/light/light.h"
|
||||
|
||||
#include "kernel/bvh/bvh.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
@@ -387,7 +387,7 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg,
|
||||
#endif /* __MNEE__ */
|
||||
|
||||
/* Light intersection for MIS. */
|
||||
if (kernel_data.integrator.use_lamp_mis) {
|
||||
if (kernel_data.integrator.use_light_mis) {
|
||||
/* NOTE: if we make lights visible to camera rays, we'll need to initialize
|
||||
* these in the path_state_init. */
|
||||
const int last_type = INTEGRATOR_STATE(state, isect, type);
|
||||
|
@@ -108,48 +108,6 @@ ccl_device_inline float mat22_inverse(const float4 m, ccl_private float4 &m_inve
|
||||
return det;
|
||||
}
|
||||
|
||||
/* Update light sample */
|
||||
ccl_device_forceinline void mnee_update_light_sample(KernelGlobals kg,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* correct light sample position/direction and pdf
|
||||
* NOTE: preserve pdf in area measure */
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
|
||||
|
||||
if (ls->type == LIGHT_POINT || ls->type == LIGHT_SPOT) {
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
if (ls->type == LIGHT_SPOT) {
|
||||
/* spot light attenuation */
|
||||
float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
dir, klight->spot.spot_angle, klight->spot.spot_smooth, ls->Ng);
|
||||
}
|
||||
}
|
||||
else if (ls->type == LIGHT_AREA) {
|
||||
float invarea = fabsf(klight->area.invarea);
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->pdf = invarea;
|
||||
if (klight->area.tan_spread > 0.f) {
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
ls->eval_fac *= light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= kernel_data.integrator.pdf_lights;
|
||||
}
|
||||
|
||||
/* Manifold vertex setup from ray and intersection data */
|
||||
ccl_device_forceinline void mnee_setup_manifold_vertex(KernelGlobals kg,
|
||||
ccl_private ManifoldVertex *vtx,
|
||||
@@ -819,7 +777,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
|
||||
|
||||
/* Update light sample with new position / direct.ion
|
||||
* and keep pdf in vertex area measure */
|
||||
mnee_update_light_sample(kg, vertices[vertex_count - 1].p, ls);
|
||||
light_sample_update_position(kg, ls, vertices[vertex_count - 1].p);
|
||||
|
||||
/* Save state path bounce info in case a light path node is used in the refractive interface or
|
||||
* light shader graph. */
|
||||
|
@@ -91,7 +91,10 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg,
|
||||
#endif
|
||||
}
|
||||
|
||||
ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state, int label)
|
||||
ccl_device_inline void path_state_next(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const int label,
|
||||
const int shader_flag)
|
||||
{
|
||||
uint32_t flag = INTEGRATOR_STATE(state, path, flag);
|
||||
|
||||
@@ -120,12 +123,12 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
|
||||
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
|
||||
}
|
||||
|
||||
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP);
|
||||
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP | PATH_RAY_MIS_HAD_TRANSMISSION);
|
||||
|
||||
#ifdef __VOLUME__
|
||||
if (label & LABEL_VOLUME_SCATTER) {
|
||||
/* volume scatter */
|
||||
flag |= PATH_RAY_VOLUME_SCATTER;
|
||||
flag |= PATH_RAY_VOLUME_SCATTER | PATH_RAY_MIS_HAD_TRANSMISSION;
|
||||
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
|
||||
if (!(flag & PATH_RAY_ANY_PASS)) {
|
||||
flag |= PATH_RAY_VOLUME_PASS;
|
||||
@@ -188,6 +191,11 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
|
||||
flag |= PATH_RAY_GLOSSY | PATH_RAY_SINGULAR | PATH_RAY_MIS_SKIP;
|
||||
}
|
||||
|
||||
/* Flag for consistent MIS weights with light tree. */
|
||||
if (shader_flag & SD_BSDF_HAS_TRANSMISSION) {
|
||||
flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
|
||||
}
|
||||
|
||||
/* Render pass categories. */
|
||||
if (!(flag & PATH_RAY_ANY_PASS) && !(flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
|
||||
flag |= PATH_RAY_SURFACE_PASS;
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/film/data_passes.h"
|
||||
#include "kernel/film/light_passes.h"
|
||||
|
||||
#include "kernel/integrator/guiding.h"
|
||||
@@ -68,9 +69,9 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
|
||||
bool eval_background = true;
|
||||
float transparent = 0.0f;
|
||||
|
||||
int path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
const bool is_transparent_background_ray = kernel_data.background.transparent &&
|
||||
(INTEGRATOR_STATE(state, path, flag) &
|
||||
PATH_RAY_TRANSPARENT_BACKGROUND);
|
||||
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND);
|
||||
|
||||
if (is_transparent_background_ray) {
|
||||
transparent = average(INTEGRATOR_STATE(state, path, throughput));
|
||||
@@ -85,7 +86,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
|
||||
#ifdef __MNEE__
|
||||
if (INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_CULL_LIGHT_CONNECTION) {
|
||||
if (kernel_data.background.use_mis) {
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
|
||||
/* This path should have been resolved with mnee, it will
|
||||
* generate a firefly for small lights since it is improbable. */
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
@@ -112,17 +113,10 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
|
||||
|
||||
/* Background MIS weights. */
|
||||
float mis_weight = 1.0f;
|
||||
/* Check if background light exists or if we should skip pdf. */
|
||||
/* Check if background light exists or if we should skip PDF. */
|
||||
if (!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_MIS_SKIP) &&
|
||||
kernel_data.background.use_mis) {
|
||||
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
|
||||
/* multiple importance sampling, get background light pdf for ray
|
||||
* direction, and compute weight with respect to BSDF pdf */
|
||||
const float pdf = background_light_pdf(kg, ray_P, ray_D);
|
||||
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
mis_weight = light_sample_mis_weight_forward_background(kg, state, path_flag);
|
||||
}
|
||||
|
||||
guiding_record_background(kg, state, L, mis_weight);
|
||||
@@ -131,6 +125,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
|
||||
|
||||
/* Write to render buffer. */
|
||||
film_write_background(kg, state, L, transparent, is_transparent_background_ray, render_buffer);
|
||||
film_write_data_passes_background(kg, state, render_buffer);
|
||||
}
|
||||
|
||||
ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
|
||||
@@ -140,8 +135,8 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
|
||||
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
|
||||
const float ray_time = INTEGRATOR_STATE(state, ray, time);
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
|
||||
if (light_sample_from_distant_ray(kg, ray_D, lamp, &ls)) {
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
|
||||
if (distant_light_sample_from_intersection(kg, ray_D, lamp, &ls)) {
|
||||
/* Use visibility flag to skip lights. */
|
||||
#ifdef __PASSES__
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
@@ -180,10 +175,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
|
||||
/* MIS weighting. */
|
||||
float mis_weight = 1.0f;
|
||||
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
|
||||
/* multiple importance sampling, get regular light pdf,
|
||||
* and compute weight with respect to BSDF pdf */
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
|
||||
mis_weight = light_sample_mis_weight_forward_distant(kg, state, path_flag, &ls);
|
||||
}
|
||||
|
||||
/* Write to render buffer. */
|
||||
|
@@ -61,10 +61,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
|
||||
/* MIS weighting. */
|
||||
float mis_weight = 1.0f;
|
||||
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
|
||||
/* multiple importance sampling, get regular light pdf,
|
||||
* and compute weight with respect to BSDF pdf */
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
|
||||
mis_weight = light_sample_mis_weight_forward_lamp(kg, state, path_flag, &ls, ray_P);
|
||||
}
|
||||
|
||||
/* Write to render buffer. */
|
||||
|
@@ -15,7 +15,6 @@
|
||||
#include "kernel/integrator/surface_shader.h"
|
||||
#include "kernel/integrator/volume_stack.h"
|
||||
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/light/sample.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
@@ -113,20 +112,16 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
|
||||
Spectrum L = surface_shader_emission(sd);
|
||||
float mis_weight = 1.0f;
|
||||
|
||||
const bool has_mis = !(path_flag & PATH_RAY_MIS_SKIP) &&
|
||||
(sd->flag & ((sd->flag & SD_BACKFACING) ? SD_MIS_BACK : SD_MIS_FRONT));
|
||||
|
||||
#ifdef __HAIR__
|
||||
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS) &&
|
||||
(sd->type & PRIMITIVE_TRIANGLE))
|
||||
if (has_mis && (sd->type & PRIMITIVE_TRIANGLE))
|
||||
#else
|
||||
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS))
|
||||
if (has_mis)
|
||||
#endif
|
||||
{
|
||||
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
const float t = sd->ray_length;
|
||||
|
||||
/* Multiple importance sampling, get triangle light pdf,
|
||||
* and compute weight with respect to BSDF pdf. */
|
||||
float pdf = triangle_light_pdf(kg, sd, t);
|
||||
mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
|
||||
mis_weight = light_sample_mis_weight_forward_surface(kg, state, path_flag, sd);
|
||||
}
|
||||
|
||||
guiding_record_surface_emission(kg, state, L, mis_weight);
|
||||
@@ -154,8 +149,17 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
|
||||
|
||||
if (!light_distribution_sample_from_position(
|
||||
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, &ls)) {
|
||||
if (!light_sample_from_position(kg,
|
||||
rng_state,
|
||||
rand_light.x,
|
||||
rand_light.y,
|
||||
sd->time,
|
||||
sd->P,
|
||||
sd->N,
|
||||
sd->flag,
|
||||
bounce,
|
||||
path_flag,
|
||||
&ls)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -322,10 +326,6 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
||||
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
|
||||
}
|
||||
|
||||
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
|
||||
INTEGRATOR_STATE_WRITE(
|
||||
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
|
||||
@@ -441,11 +441,12 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
||||
/* Update path state */
|
||||
if (!(label & LABEL_TRANSPARENT)) {
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
}
|
||||
|
||||
path_state_next(kg, state, label);
|
||||
path_state_next(kg, state, label, sd->flag);
|
||||
|
||||
guiding_record_surface_bounce(kg,
|
||||
state,
|
||||
|
@@ -34,6 +34,9 @@ typedef struct VolumeIntegrateResult {
|
||||
Spectrum direct_throughput;
|
||||
float direct_t;
|
||||
ShaderVolumePhases direct_phases;
|
||||
# ifdef __PATH_GUIDING__
|
||||
VolumeSampleMethod direct_sample_method;
|
||||
# endif
|
||||
|
||||
/* Throughput and offset for indirect light scattering. */
|
||||
bool indirect_scatter;
|
||||
@@ -580,6 +583,9 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
|
||||
result.direct_t = volume_equiangular_sample(
|
||||
ray, equiangular_light_P, vstate.rscatter, &vstate.equiangular_pdf);
|
||||
}
|
||||
# ifdef __PATH_GUIDING__
|
||||
result.direct_sample_method = vstate.direct_sample_method;
|
||||
# endif
|
||||
|
||||
# ifdef __DENOISING_FEATURES__
|
||||
const bool write_denoising_features = (INTEGRATOR_STATE(state, path, flag) &
|
||||
@@ -679,14 +685,14 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
|
||||
# endif /* __DENOISING_FEATURES__ */
|
||||
}
|
||||
|
||||
/* Path tracing: sample point on light and evaluate light shader, then
|
||||
* queue shadow ray to be traced. */
|
||||
ccl_device_forceinline bool integrate_volume_sample_light(
|
||||
/* Path tracing: sample point on light for equiangular sampling. */
|
||||
ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private const Ray *ccl_restrict ray,
|
||||
ccl_private const ShaderData *ccl_restrict sd,
|
||||
ccl_private const RNGState *ccl_restrict rng_state,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
ccl_private float3 *ccl_restrict P)
|
||||
{
|
||||
/* Test if there is a light or BSDF that needs direct light. */
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
@@ -698,15 +704,30 @@ ccl_device_forceinline bool integrate_volume_sample_light(
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
|
||||
|
||||
if (!light_distribution_sample_from_volume_segment(
|
||||
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, ls)) {
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
if (!light_sample_from_volume_segment(kg,
|
||||
rand_light.x,
|
||||
rand_light.y,
|
||||
sd->time,
|
||||
sd->P,
|
||||
ray->D,
|
||||
ray->tmax - ray->tmin,
|
||||
bounce,
|
||||
path_flag,
|
||||
&ls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
|
||||
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ls.t == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*P = ls.P;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -719,8 +740,10 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
ccl_private const RNGState *ccl_restrict rng_state,
|
||||
const float3 P,
|
||||
ccl_private const ShaderVolumePhases *ccl_restrict phases,
|
||||
ccl_private const Spectrum throughput,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
# ifdef __PATH_GUIDING__
|
||||
ccl_private const Spectrum unlit_throughput,
|
||||
# endif
|
||||
ccl_private const Spectrum throughput)
|
||||
{
|
||||
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_DIRECT_LIGHT);
|
||||
|
||||
@@ -728,23 +751,38 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sample position on the same light again, now from the shading
|
||||
* point where we scattered.
|
||||
/* Sample position on the same light again, now from the shading point where we scattered.
|
||||
*
|
||||
* TODO: decorrelate random numbers and use light_sample_new_position to
|
||||
* avoid resampling the CDF. */
|
||||
* Note that this means we sample the light tree twice when equiangular sampling is used.
|
||||
* We could consider sampling the light tree just once and use the same light position again.
|
||||
*
|
||||
* This would make the PDFs for MIS weights more complicated due to having to account for
|
||||
* both distance/equiangular and direct/indirect light sampling, but could be more accurate.
|
||||
* Additionally we could end up behind the light or outside a spot light cone, which might
|
||||
* waste a sample. Though on the other hand it would be possible to prevent that with
|
||||
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
{
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
|
||||
|
||||
if (!light_distribution_sample_from_position(
|
||||
kg, rand_light.x, rand_light.y, sd->time, P, bounce, path_flag, ls)) {
|
||||
if (!light_sample_from_position(kg,
|
||||
rng_state,
|
||||
rand_light.x,
|
||||
rand_light.y,
|
||||
sd->time,
|
||||
P,
|
||||
zero_float3(),
|
||||
SD_BSDF_HAS_TRANSMISSION,
|
||||
bounce,
|
||||
path_flag,
|
||||
&ls)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
|
||||
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -756,32 +794,32 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
* non-constant light sources. */
|
||||
ShaderDataTinyStorage emission_sd_storage;
|
||||
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
|
||||
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, ls, sd->time);
|
||||
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, sd->time);
|
||||
if (is_zero(light_eval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Evaluate BSDF. */
|
||||
BsdfEval phase_eval ccl_optional_struct_init;
|
||||
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval);
|
||||
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls.D, &phase_eval);
|
||||
|
||||
if (ls->shader & SHADER_USE_MIS) {
|
||||
float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
|
||||
if (ls.shader & SHADER_USE_MIS) {
|
||||
float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, phase_pdf);
|
||||
bsdf_eval_mul(&phase_eval, mis_weight);
|
||||
}
|
||||
|
||||
bsdf_eval_mul(&phase_eval, light_eval / ls->pdf);
|
||||
bsdf_eval_mul(&phase_eval, light_eval / ls.pdf);
|
||||
|
||||
/* Path termination. */
|
||||
const float terminate = path_state_rng_light_termination(kg, rng_state);
|
||||
if (light_sample_terminate(kg, ls, &phase_eval, terminate)) {
|
||||
if (light_sample_terminate(kg, &ls, &phase_eval, terminate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create shadow ray. */
|
||||
Ray ray ccl_optional_struct_init;
|
||||
light_sample_to_volume_shadow_ray(kg, sd, ls, P, &ray);
|
||||
const bool is_light = light_sample_is_light(ls);
|
||||
light_sample_to_volume_shadow_ray(kg, sd, &ls, P, &ray);
|
||||
const bool is_light = light_sample_is_light(&ls);
|
||||
|
||||
/* Branch off shadow kernel. */
|
||||
IntegratorShadowState shadow_state = integrator_shadow_path_init(
|
||||
@@ -840,18 +878,14 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
state, path, transmission_bounce);
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput_phase;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
|
||||
}
|
||||
|
||||
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
|
||||
INTEGRATOR_STATE_WRITE(
|
||||
shadow_state, shadow_path, lightgroup) = (ls->type != LIGHT_BACKGROUND) ?
|
||||
ls->group + 1 :
|
||||
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
|
||||
ls.group + 1 :
|
||||
kernel_data.background.lightgroup + 1;
|
||||
|
||||
# ifdef __PATH_GUIDING__
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput;
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput;
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
|
||||
state, guiding, path_segment);
|
||||
# endif
|
||||
@@ -949,10 +983,11 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
|
||||
/* Update path state */
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = zero_float3();
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
|
||||
path_state_next(kg, state, label);
|
||||
path_state_next(kg, state, label, sd->flag);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -974,12 +1009,11 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
|
||||
/* Sample light ahead of volume stepping, for equiangular sampling. */
|
||||
/* TODO: distant lights are ignored now, but could instead use even distribution. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
|
||||
float3 equiangular_P = zero_float3();
|
||||
const bool have_equiangular_sample = need_light_sample &&
|
||||
integrate_volume_sample_light(
|
||||
kg, state, &sd, &rng_state, &ls) &&
|
||||
(ls.t != FLT_MAX);
|
||||
integrate_volume_equiangular_sample_light(
|
||||
kg, state, ray, &sd, &rng_state, &equiangular_P);
|
||||
|
||||
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?
|
||||
volume_stack_sample_method(kg, state) :
|
||||
@@ -990,7 +1024,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass);
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
/* The current path throughput which is used later to calculate per-segment throughput.*/
|
||||
const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput);
|
||||
/* The path throughput used to calculate the throughput for direct light. */
|
||||
float3 unlit_throughput = initial_throughput;
|
||||
/* If a new path segment is generated at the direct scatter position.*/
|
||||
bool guiding_generated_new_segment = false;
|
||||
float rand_phase_guiding = 0.5f;
|
||||
# endif
|
||||
|
||||
/* TODO: expensive to zero closures? */
|
||||
@@ -1003,7 +1043,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
render_buffer,
|
||||
step_size,
|
||||
direct_sample_method,
|
||||
ls.P,
|
||||
equiangular_P,
|
||||
result);
|
||||
|
||||
/* Perform path termination. The intersect_closest will have already marked this path
|
||||
@@ -1018,41 +1058,48 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
return VOLUME_PATH_MISSED;
|
||||
}
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
bool guiding_generated_new_segment = false;
|
||||
if (kernel_data.integrator.use_guiding) {
|
||||
/* Record transmittance using change in throughput. */
|
||||
float3 transmittance_weight = spectrum_to_rgb(
|
||||
safe_divide_color(result.indirect_throughput, initial_throughput));
|
||||
guiding_record_volume_transmission(kg, state, transmittance_weight);
|
||||
|
||||
if (result.indirect_scatter) {
|
||||
const float3 P = ray->P + result.indirect_t * ray->D;
|
||||
|
||||
/* Record volume segment up to direct scatter position.
|
||||
* TODO: volume segment is wrong when direct_t and indirect_t. */
|
||||
if (result.direct_scatter && (result.direct_t == result.indirect_t)) {
|
||||
guiding_record_volume_segment(kg, state, P, sd.I);
|
||||
guiding_generated_new_segment = true;
|
||||
}
|
||||
|
||||
# if PATH_GUIDING_LEVEL >= 4
|
||||
/* TODO: this position will be wrong for direct light pdf computation,
|
||||
* since the direct light position may be different? */
|
||||
volume_shader_prepare_guiding(
|
||||
kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method);
|
||||
# endif
|
||||
}
|
||||
else {
|
||||
/* No guiding if we don't scatter. */
|
||||
state->guiding.use_volume_guiding = false;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
/* Direct light. */
|
||||
if (result.direct_scatter) {
|
||||
const float3 direct_P = ray->P + result.direct_t * ray->D;
|
||||
|
||||
# ifdef __PATH_GUIDING__
|
||||
if (kernel_data.integrator.use_guiding) {
|
||||
# if PATH_GUIDING_LEVEL >= 1
|
||||
if (result.direct_sample_method == VOLUME_SAMPLE_DISTANCE) {
|
||||
/* If the direct scatter event is generated using VOLUME_SAMPLE_DISTANCE the direct event
|
||||
* will happen at the same position as the indirect event and the direct light contribution
|
||||
* will contribute to the position of the next path segment.*/
|
||||
float3 transmittance_weight = spectrum_to_rgb(
|
||||
safe_divide_color(result.indirect_throughput, initial_throughput));
|
||||
guiding_record_volume_transmission(kg, state, transmittance_weight);
|
||||
guiding_record_volume_segment(kg, state, direct_P, sd.I);
|
||||
guiding_generated_new_segment = true;
|
||||
unlit_throughput = result.indirect_throughput / continuation_probability;
|
||||
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
|
||||
}
|
||||
else {
|
||||
/* If the direct scatter event is generated using VOLUME_SAMPLE_EQUIANGULAR the direct
|
||||
* event will happen at a separate position as the indirect event and the direct light
|
||||
* contribution will contribute to the position of the current/previous path segment. The
|
||||
* unlit_throughput has to be adjusted to include the scattering at the previous segment.*/
|
||||
float3 scatterEval = one_float3();
|
||||
if (state->guiding.path_segment) {
|
||||
pgl_vec3f scatteringWeight = state->guiding.path_segment->scatteringWeight;
|
||||
scatterEval = make_float3(scatteringWeight.x, scatteringWeight.y, scatteringWeight.z);
|
||||
}
|
||||
unlit_throughput /= scatterEval;
|
||||
unlit_throughput *= continuation_probability;
|
||||
rand_phase_guiding = path_state_rng_1D(
|
||||
kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR);
|
||||
}
|
||||
# endif
|
||||
# if PATH_GUIDING_LEVEL >= 4
|
||||
volume_shader_prepare_guiding(
|
||||
kg, state, &sd, rand_phase_guiding, direct_P, ray->D, &result.direct_phases);
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
result.direct_throughput /= continuation_probability;
|
||||
integrate_volume_direct_light(kg,
|
||||
state,
|
||||
@@ -1060,8 +1107,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
&rng_state,
|
||||
direct_P,
|
||||
&result.direct_phases,
|
||||
result.direct_throughput,
|
||||
&ls);
|
||||
# ifdef __PATH_GUIDING__
|
||||
unlit_throughput,
|
||||
# endif
|
||||
result.direct_throughput);
|
||||
}
|
||||
|
||||
/* Indirect light.
|
||||
@@ -1069,6 +1118,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
* Only divide throughput by continuation_probability if we scatter. For the attenuation
|
||||
* case the next surface will already do this division. */
|
||||
if (result.indirect_scatter) {
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
if (!guiding_generated_new_segment) {
|
||||
float3 transmittance_weight = spectrum_to_rgb(
|
||||
safe_divide_color(result.indirect_throughput, initial_throughput));
|
||||
guiding_record_volume_transmission(kg, state, transmittance_weight);
|
||||
}
|
||||
# endif
|
||||
result.indirect_throughput /= continuation_probability;
|
||||
}
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput;
|
||||
@@ -1076,10 +1132,21 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
if (result.indirect_scatter) {
|
||||
sd.P = ray->P + result.indirect_t * ray->D;
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
# if defined(__PATH_GUIDING__)
|
||||
# if PATH_GUIDING_LEVEL >= 1
|
||||
if (!guiding_generated_new_segment) {
|
||||
guiding_record_volume_segment(kg, state, sd.P, sd.I);
|
||||
}
|
||||
# endif
|
||||
# if PATH_GUIDING_LEVEL >= 4
|
||||
/* If the direct scatter event was generated using VOLUME_SAMPLE_EQUIANGULAR we need to
|
||||
* initialize the guiding distribution at the indirect scatter position. */
|
||||
if (result.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR) {
|
||||
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
|
||||
volume_shader_prepare_guiding(
|
||||
kg, state, &sd, rand_phase_guiding, sd.P, ray->D, &result.indirect_phases);
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
|
||||
if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) {
|
||||
@@ -1090,6 +1157,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
}
|
||||
}
|
||||
else {
|
||||
# if defined(__PATH_GUIDING__)
|
||||
/* No guiding if we don't scatter. */
|
||||
state->guiding.use_volume_guiding = false;
|
||||
# endif
|
||||
return VOLUME_PATH_ATTENUATED;
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, throughput, KERNEL_FEATURE_PAT
|
||||
KERNEL_STRUCT_MEMBER(shadow_path,
|
||||
PackedSpectrum,
|
||||
unshadowed_throughput,
|
||||
KERNEL_FEATURE_SHADOW_PASS | KERNEL_FEATURE_AO_ADDITIVE)
|
||||
KERNEL_FEATURE_AO_ADDITIVE)
|
||||
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
|
||||
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
|
||||
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)
|
||||
|
@@ -41,6 +41,7 @@ KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING)
|
||||
* zero and distance. Note that transparency and volume attenuation increase
|
||||
* the ray tmin but keep P unmodified so that this works. */
|
||||
KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
|
||||
KERNEL_STRUCT_MEMBER(path, packed_float3, mis_origin_n, KERNEL_FEATURE_PATH_TRACING)
|
||||
/* Filter glossy. */
|
||||
KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
|
||||
/* Continuation probability for path termination. */
|
||||
|
@@ -95,11 +95,10 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases
|
||||
ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private ShaderData *sd,
|
||||
ccl_private const RNGState *rng_state,
|
||||
float rand_phase_guiding,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
ccl_private ShaderVolumePhases *phases,
|
||||
const VolumeSampleMethod direct_sample_method)
|
||||
ccl_private ShaderVolumePhases *phases)
|
||||
{
|
||||
/* Have any phase functions to guide? */
|
||||
const int num_phases = phases->num_closure;
|
||||
@@ -109,7 +108,6 @@ ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
|
||||
}
|
||||
|
||||
const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability;
|
||||
float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING);
|
||||
|
||||
/* If we have more than one phase function we select one random based on its
|
||||
* sample weight to calculate the product distribution for guiding. */
|
||||
|
387
intern/cycles/kernel/light/area.h
Normal file
387
intern/cycles/kernel/light/area.h
Normal file
@@ -0,0 +1,387 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Importance sampling.
|
||||
*
|
||||
* An Area-Preserving Parametrization for Spherical Rectangles.
|
||||
* Carlos Urena et al.
|
||||
*
|
||||
* NOTE: light_p is modified when sample_coord is true. */
|
||||
ccl_device_inline float area_light_rect_sample(float3 P,
|
||||
ccl_private float3 *light_p,
|
||||
const float3 axis_u,
|
||||
const float len_u,
|
||||
const float3 axis_v,
|
||||
const float len_v,
|
||||
float randu,
|
||||
float randv,
|
||||
bool sample_coord)
|
||||
{
|
||||
/* In our name system we're using P for the center, which is o in the paper. */
|
||||
float3 corner = *light_p - axis_u * len_u * 0.5f - axis_v * len_v * 0.5f;
|
||||
/* Compute local reference system R. */
|
||||
float3 x = axis_u;
|
||||
float3 y = axis_v;
|
||||
float3 z = cross(x, y);
|
||||
/* Compute rectangle coords in local reference system. */
|
||||
float3 dir = corner - P;
|
||||
float z0 = dot(dir, z);
|
||||
/* Flip 'z' to make it point against Q. */
|
||||
if (z0 > 0.0f) {
|
||||
z *= -1.0f;
|
||||
z0 *= -1.0f;
|
||||
}
|
||||
float x0 = dot(dir, x);
|
||||
float y0 = dot(dir, y);
|
||||
float x1 = x0 + len_u;
|
||||
float y1 = y0 + len_v;
|
||||
/* Compute internal angles (gamma_i). */
|
||||
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
|
||||
float4 nz = make_float4(y0, x1, y1, x0) * diff;
|
||||
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
|
||||
float g0 = safe_acosf(-nz.x * nz.y);
|
||||
float g1 = safe_acosf(-nz.y * nz.z);
|
||||
float g2 = safe_acosf(-nz.z * nz.w);
|
||||
float g3 = safe_acosf(-nz.w * nz.x);
|
||||
/* Compute predefined constants. */
|
||||
float b0 = nz.x;
|
||||
float b1 = nz.z;
|
||||
float b0sq = b0 * b0;
|
||||
float k = M_2PI_F - g2 - g3;
|
||||
/* Compute solid angle from internal angles. */
|
||||
float S = g0 + g1 - k;
|
||||
|
||||
if (sample_coord) {
|
||||
/* Compute cu. */
|
||||
float au = randu * S + k;
|
||||
float fu = (cosf(au) * b0 - b1) / sinf(au);
|
||||
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
|
||||
cu = clamp(cu, -1.0f, 1.0f);
|
||||
/* Compute xu. */
|
||||
float xu = -(cu * z0) / max(sqrtf(1.0f - cu * cu), 1e-7f);
|
||||
xu = clamp(xu, x0, x1);
|
||||
/* Compute yv. */
|
||||
float z0sq = z0 * z0;
|
||||
float y0sq = y0 * y0;
|
||||
float y1sq = y1 * y1;
|
||||
float d = sqrtf(xu * xu + z0sq);
|
||||
float h0 = y0 / sqrtf(d * d + y0sq);
|
||||
float h1 = y1 / sqrtf(d * d + y1sq);
|
||||
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
|
||||
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
|
||||
|
||||
/* Transform (xu, yv, z0) to world coords. */
|
||||
*light_p = P + xu * x + yv * y + z0 * z;
|
||||
}
|
||||
|
||||
/* return pdf */
|
||||
if (S != 0.0f)
|
||||
return 1.0f / S;
|
||||
else
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* Light spread. */
|
||||
|
||||
ccl_device float area_light_spread_attenuation(const float3 D,
|
||||
const float3 lightNg,
|
||||
const float cot_half_spread,
|
||||
const float normalize_spread)
|
||||
{
|
||||
/* Model a soft-box grid, computing the ratio of light not hidden by the
|
||||
* slats of the grid at a given angle. (see D10594). */
|
||||
const float cos_a = -dot(D, lightNg);
|
||||
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
|
||||
const float tan_a = sin_a / cos_a;
|
||||
return max((1.0f - (cot_half_spread * tan_a)) * normalize_spread, 0.0f);
|
||||
}
|
||||
|
||||
/* Compute subset of area light that actually has an influence on the shading point, to
|
||||
* reduce noise with low spread. */
|
||||
ccl_device bool area_light_spread_clamp_area_light(const float3 P,
|
||||
const float3 lightNg,
|
||||
ccl_private float3 *lightP,
|
||||
const float3 axis_u,
|
||||
ccl_private float *len_u,
|
||||
const float3 axis_v,
|
||||
ccl_private float *len_v,
|
||||
const float cot_half_spread)
|
||||
{
|
||||
/* Closest point in area light plane and distance to that plane. */
|
||||
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
|
||||
const float t = len(closest_P - P);
|
||||
|
||||
/* Radius of circle on area light that actually affects the shading point. */
|
||||
const float radius = t / cot_half_spread;
|
||||
|
||||
/* Local uv coordinates of closest point. */
|
||||
const float closest_u = dot(axis_u, closest_P - *lightP);
|
||||
const float closest_v = dot(axis_v, closest_P - *lightP);
|
||||
|
||||
/* Compute rectangle encompassing the circle that affects the shading point,
|
||||
* clamped to the bounds of the area light. */
|
||||
const float min_u = max(closest_u - radius, -*len_u * 0.5f);
|
||||
const float max_u = min(closest_u + radius, *len_u * 0.5f);
|
||||
const float min_v = max(closest_v - radius, -*len_v * 0.5f);
|
||||
const float max_v = min(closest_v + radius, *len_v * 0.5f);
|
||||
|
||||
/* Skip if rectangle is empty. */
|
||||
if (min_u >= max_u || min_v >= max_v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Compute new area light center position and axes from rectangle in local
|
||||
* uv coordinates. */
|
||||
const float new_center_u = 0.5f * (min_u + max_u);
|
||||
const float new_center_v = 0.5f * (min_v + max_v);
|
||||
*len_u = max_u - min_u;
|
||||
*len_v = max_v - min_v;
|
||||
|
||||
*lightP = *lightP + new_center_u * axis_u + new_center_v * axis_v;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Common API. */
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
|
||||
const float randu,
|
||||
const float randv,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
ls->P = klight->co;
|
||||
|
||||
const float3 axis_u = klight->area.axis_u;
|
||||
const float3 axis_v = klight->area.axis_v;
|
||||
const float len_u = klight->area.len_u;
|
||||
const float len_v = klight->area.len_v;
|
||||
float3 Ng = klight->area.dir;
|
||||
float invarea = fabsf(klight->area.invarea);
|
||||
bool is_round = (klight->area.invarea < 0.0f);
|
||||
|
||||
if (!in_volume_segment) {
|
||||
if (dot(ls->P - P, Ng) > 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float3 inplane;
|
||||
|
||||
if (is_round || in_volume_segment) {
|
||||
inplane = ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
|
||||
ls->P += inplane;
|
||||
ls->pdf = invarea;
|
||||
}
|
||||
else {
|
||||
inplane = ls->P;
|
||||
|
||||
float sample_len_u = len_u;
|
||||
float sample_len_v = len_v;
|
||||
|
||||
if (!in_volume_segment && klight->area.cot_half_spread > 0.0f) {
|
||||
if (!area_light_spread_clamp_area_light(P,
|
||||
Ng,
|
||||
&ls->P,
|
||||
axis_u,
|
||||
&sample_len_u,
|
||||
axis_v,
|
||||
&sample_len_v,
|
||||
klight->area.cot_half_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf = area_light_rect_sample(
|
||||
P, &ls->P, axis_u, sample_len_u, axis_v, sample_len_v, randu, randv, true);
|
||||
inplane = ls->P - inplane;
|
||||
}
|
||||
|
||||
const float light_u = dot(inplane, axis_u) / len_u;
|
||||
const float light_v = dot(inplane, axis_v) / len_v;
|
||||
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
ls->u = light_v + 0.5f;
|
||||
ls->v = -light_u - light_v;
|
||||
|
||||
ls->Ng = Ng;
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
}
|
||||
|
||||
if (is_round) {
|
||||
ls->pdf *= lamp_light_pdf(Ng, -ls->D, ls->t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline void area_light_update_position(const ccl_global KernelLight *klight,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
const float invarea = fabsf(klight->area.invarea);
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->pdf = invarea;
|
||||
|
||||
if (klight->area.cot_half_spread > 0.f) {
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device_inline bool area_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t,
|
||||
ccl_private float *u,
|
||||
ccl_private float *v)
|
||||
{
|
||||
/* Area light. */
|
||||
const float invarea = fabsf(klight->area.invarea);
|
||||
const bool is_round = (klight->area.invarea < 0.0f);
|
||||
if (invarea == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float3 inv_extent_u = klight->area.axis_u / klight->area.len_u;
|
||||
const float3 inv_extent_v = klight->area.axis_v / klight->area.len_v;
|
||||
const float3 Ng = klight->area.dir;
|
||||
|
||||
/* One sided. */
|
||||
if (dot(ray->D, Ng) >= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float3 light_P = klight->co;
|
||||
|
||||
float3 P;
|
||||
return ray_quad_intersect(ray->P,
|
||||
ray->D,
|
||||
ray->tmin,
|
||||
ray->tmax,
|
||||
light_P,
|
||||
inv_extent_u,
|
||||
inv_extent_v,
|
||||
Ng,
|
||||
&P,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
is_round);
|
||||
}
|
||||
|
||||
ccl_device_inline bool area_light_sample_from_intersection(
|
||||
const ccl_global KernelLight *klight,
|
||||
ccl_private const Intersection *ccl_restrict isect,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
{
|
||||
|
||||
/* area light */
|
||||
float invarea = fabsf(klight->area.invarea);
|
||||
|
||||
float3 Ng = klight->area.dir;
|
||||
float3 light_P = klight->co;
|
||||
|
||||
ls->u = isect->u;
|
||||
ls->v = isect->v;
|
||||
ls->D = ray_D;
|
||||
ls->Ng = Ng;
|
||||
|
||||
const bool is_round = (klight->area.invarea < 0.0f);
|
||||
if (is_round) {
|
||||
ls->pdf = invarea * lamp_light_pdf(Ng, -ray_D, ls->t);
|
||||
}
|
||||
else {
|
||||
const float3 axis_u = klight->area.axis_u;
|
||||
const float3 axis_v = klight->area.axis_v;
|
||||
float sample_len_u = klight->area.len_u;
|
||||
float sample_len_v = klight->area.len_v;
|
||||
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
if (!area_light_spread_clamp_area_light(ray_P,
|
||||
Ng,
|
||||
&light_P,
|
||||
axis_u,
|
||||
&sample_len_u,
|
||||
axis_v,
|
||||
&sample_len_v,
|
||||
klight->area.cot_half_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf = area_light_rect_sample(
|
||||
ray_P, &light_P, axis_u, sample_len_u, axis_v, sample_len_v, 0, 0, false);
|
||||
}
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const float3 bcone_axis,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (!in_volume_segment) {
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
|
||||
* worth the overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
}
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
const float3 extentu = klight->area.axis_u * klight->area.len_u;
|
||||
const float3 extentv = klight->area.axis_v * klight->area.len_v;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
const float3 corner = ((i & 1) - 0.5f) * extentu + 0.5f * ((i & 2) - 1) * extentv + centroid;
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = dot(bcone_axis, point_to_centroid) < 0;
|
||||
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
|
||||
fabsf(dot(N, extentv)) >
|
||||
0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
@@ -130,11 +131,11 @@ ccl_device float background_map_pdf(KernelGlobals kg, float3 direction)
|
||||
ccl_device_inline bool background_portal_data_fetch_and_check_side(
|
||||
KernelGlobals kg, float3 P, int index, ccl_private float3 *lightpos, ccl_private float3 *dir)
|
||||
{
|
||||
int portal = kernel_data.background.portal_offset + index;
|
||||
int portal = kernel_data.integrator.portal_offset + index;
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
|
||||
|
||||
*lightpos = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
*dir = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
|
||||
*lightpos = klight->co;
|
||||
*dir = klight->area.dir;
|
||||
|
||||
/* Check whether portal is on the right side. */
|
||||
if (dot(*dir, P - *lightpos) > 1e-4f)
|
||||
@@ -149,7 +150,7 @@ ccl_device_inline float background_portal_pdf(
|
||||
float portal_pdf = 0.0f;
|
||||
|
||||
int num_possible = 0;
|
||||
for (int p = 0; p < kernel_data.background.num_portals; p++) {
|
||||
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
|
||||
if (p == ignore_portal)
|
||||
continue;
|
||||
|
||||
@@ -163,12 +164,16 @@ ccl_device_inline float background_portal_pdf(
|
||||
}
|
||||
num_possible++;
|
||||
|
||||
int portal = kernel_data.background.portal_offset + p;
|
||||
int portal = kernel_data.integrator.portal_offset + p;
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
|
||||
float3 axisu = make_float3(
|
||||
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
|
||||
float3 axisv = make_float3(
|
||||
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
|
||||
|
||||
const float3 axis_u = klight->area.axis_u;
|
||||
const float len_u = klight->area.len_u;
|
||||
const float3 axis_v = klight->area.axis_v;
|
||||
const float len_v = klight->area.len_v;
|
||||
const float3 inv_extent_u = axis_u / len_u;
|
||||
const float3 inv_extent_v = axis_v / len_v;
|
||||
|
||||
bool is_round = (klight->area.invarea < 0.0f);
|
||||
|
||||
if (!ray_quad_intersect(P,
|
||||
@@ -176,8 +181,8 @@ ccl_device_inline float background_portal_pdf(
|
||||
1e-4f,
|
||||
FLT_MAX,
|
||||
lightpos,
|
||||
axisu,
|
||||
axisv,
|
||||
inv_extent_u,
|
||||
inv_extent_v,
|
||||
dir,
|
||||
NULL,
|
||||
NULL,
|
||||
@@ -189,10 +194,11 @@ ccl_device_inline float background_portal_pdf(
|
||||
if (is_round) {
|
||||
float t;
|
||||
float3 D = normalize_len(lightpos - P, &t);
|
||||
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
|
||||
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
|
||||
}
|
||||
else {
|
||||
portal_pdf += rect_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
|
||||
portal_pdf += area_light_rect_sample(
|
||||
P, &lightpos, axis_u, len_u, axis_v, len_v, 0.0f, 0.0f, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +213,7 @@ ccl_device_inline float background_portal_pdf(
|
||||
ccl_device int background_num_possible_portals(KernelGlobals kg, float3 P)
|
||||
{
|
||||
int num_possible_portals = 0;
|
||||
for (int p = 0; p < kernel_data.background.num_portals; p++) {
|
||||
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
|
||||
float3 lightpos, dir;
|
||||
if (background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
|
||||
num_possible_portals++;
|
||||
@@ -231,7 +237,7 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
|
||||
/* TODO(sergey): Some smarter way of finding portal to sample
|
||||
* is welcome.
|
||||
*/
|
||||
for (int p = 0; p < kernel_data.background.num_portals; p++) {
|
||||
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
|
||||
/* Search for the sampled portal. */
|
||||
float3 lightpos, dir;
|
||||
if (!background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
|
||||
@@ -239,23 +245,24 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
|
||||
|
||||
if (portal == 0) {
|
||||
/* p is the portal to be sampled. */
|
||||
int portal = kernel_data.background.portal_offset + p;
|
||||
int portal = kernel_data.integrator.portal_offset + p;
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
|
||||
float3 axisu = make_float3(
|
||||
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
|
||||
float3 axisv = make_float3(
|
||||
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
|
||||
const float3 axis_u = klight->area.axis_u;
|
||||
const float3 axis_v = klight->area.axis_v;
|
||||
const float len_u = klight->area.len_u;
|
||||
const float len_v = klight->area.len_v;
|
||||
bool is_round = (klight->area.invarea < 0.0f);
|
||||
|
||||
float3 D;
|
||||
if (is_round) {
|
||||
lightpos += ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
|
||||
lightpos += ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
|
||||
float t;
|
||||
D = normalize_len(lightpos - P, &t);
|
||||
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
|
||||
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
|
||||
}
|
||||
else {
|
||||
*pdf = rect_light_sample(P, &lightpos, axisu, axisv, randu, randv, true);
|
||||
*pdf = area_light_rect_sample(
|
||||
P, &lightpos, axis_u, len_u, axis_v, len_v, randu, randv, true);
|
||||
D = normalize(lightpos - P);
|
||||
}
|
||||
|
||||
@@ -414,7 +421,7 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
|
||||
float pdf_fac = (portal_method_pdf + sun_method_pdf + map_method_pdf);
|
||||
if (pdf_fac == 0.0f) {
|
||||
/* Use uniform as a fallback if we can't use any strategy. */
|
||||
return kernel_data.integrator.pdf_lights / M_4PI_F;
|
||||
return 1.0f / M_4PI_F;
|
||||
}
|
||||
|
||||
pdf_fac = 1.0f / pdf_fac;
|
||||
@@ -430,7 +437,21 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
|
||||
pdf += background_map_pdf(kg, direction) * map_method_pdf;
|
||||
}
|
||||
|
||||
return pdf * kernel_data.integrator.pdf_lights;
|
||||
return pdf;
|
||||
}
|
||||
|
||||
ccl_device_forceinline bool background_light_tree_parameters(const float3 centroid,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* Cover the whole sphere */
|
||||
cos_theta_u = -1.0f;
|
||||
|
||||
distance = make_float2(1.0f, 1.0f);
|
||||
point_to_centroid = -centroid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@@ -7,92 +7,26 @@
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Area light sampling */
|
||||
/* Light Sample Result */
|
||||
|
||||
/* Uses the following paper:
|
||||
*
|
||||
* Carlos Urena et al.
|
||||
* An Area-Preserving Parametrization for Spherical Rectangles.
|
||||
*
|
||||
* https://www.solidangle.com/research/egsr2013_spherical_rectangle.pdf
|
||||
*
|
||||
* NOTE: light_p is modified when sample_coord is true.
|
||||
*/
|
||||
ccl_device_inline float rect_light_sample(float3 P,
|
||||
ccl_private float3 *light_p,
|
||||
float3 axisu,
|
||||
float3 axisv,
|
||||
float randu,
|
||||
float randv,
|
||||
bool sample_coord)
|
||||
{
|
||||
/* In our name system we're using P for the center,
|
||||
* which is o in the paper.
|
||||
*/
|
||||
typedef struct LightSample {
|
||||
float3 P; /* position on light, or direction for distant light */
|
||||
float3 Ng; /* normal on light */
|
||||
float3 D; /* direction from shading point to light */
|
||||
float t; /* distance to light (FLT_MAX for distant light) */
|
||||
float u, v; /* parametric coordinate on primitive */
|
||||
float pdf; /* pdf for selecting light and point on light */
|
||||
float pdf_selection; /* pdf for selecting light */
|
||||
float eval_fac; /* intensity multiplier */
|
||||
int object; /* object id for triangle/curve lights */
|
||||
int prim; /* primitive id for triangle/curve lights */
|
||||
int shader; /* shader id */
|
||||
int lamp; /* lamp id */
|
||||
int group; /* lightgroup */
|
||||
LightType type; /* type of light */
|
||||
} LightSample;
|
||||
|
||||
float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f;
|
||||
float axisu_len, axisv_len;
|
||||
/* Compute local reference system R. */
|
||||
float3 x = normalize_len(axisu, &axisu_len);
|
||||
float3 y = normalize_len(axisv, &axisv_len);
|
||||
float3 z = cross(x, y);
|
||||
/* Compute rectangle coords in local reference system. */
|
||||
float3 dir = corner - P;
|
||||
float z0 = dot(dir, z);
|
||||
/* Flip 'z' to make it point against Q. */
|
||||
if (z0 > 0.0f) {
|
||||
z *= -1.0f;
|
||||
z0 *= -1.0f;
|
||||
}
|
||||
float x0 = dot(dir, x);
|
||||
float y0 = dot(dir, y);
|
||||
float x1 = x0 + axisu_len;
|
||||
float y1 = y0 + axisv_len;
|
||||
/* Compute internal angles (gamma_i). */
|
||||
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
|
||||
float4 nz = make_float4(y0, x1, y1, x0) * diff;
|
||||
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
|
||||
float g0 = safe_acosf(-nz.x * nz.y);
|
||||
float g1 = safe_acosf(-nz.y * nz.z);
|
||||
float g2 = safe_acosf(-nz.z * nz.w);
|
||||
float g3 = safe_acosf(-nz.w * nz.x);
|
||||
/* Compute predefined constants. */
|
||||
float b0 = nz.x;
|
||||
float b1 = nz.z;
|
||||
float b0sq = b0 * b0;
|
||||
float k = M_2PI_F - g2 - g3;
|
||||
/* Compute solid angle from internal angles. */
|
||||
float S = g0 + g1 - k;
|
||||
|
||||
if (sample_coord) {
|
||||
/* Compute cu. */
|
||||
float au = randu * S + k;
|
||||
float fu = (cosf(au) * b0 - b1) / sinf(au);
|
||||
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
|
||||
cu = clamp(cu, -1.0f, 1.0f);
|
||||
/* Compute xu. */
|
||||
float xu = -(cu * z0) / max(sqrtf(1.0f - cu * cu), 1e-7f);
|
||||
xu = clamp(xu, x0, x1);
|
||||
/* Compute yv. */
|
||||
float z0sq = z0 * z0;
|
||||
float y0sq = y0 * y0;
|
||||
float y1sq = y1 * y1;
|
||||
float d = sqrtf(xu * xu + z0sq);
|
||||
float h0 = y0 / sqrtf(d * d + y0sq);
|
||||
float h1 = y1 / sqrtf(d * d + y1sq);
|
||||
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
|
||||
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
|
||||
|
||||
/* Transform (xu, yv, z0) to world coords. */
|
||||
*light_p = P + xu * x + yv * y + z0 * z;
|
||||
}
|
||||
|
||||
/* return pdf */
|
||||
if (S != 0.0f)
|
||||
return 1.0f / S;
|
||||
else
|
||||
return 0.0f;
|
||||
}
|
||||
/* Utilities */
|
||||
|
||||
ccl_device_inline float3 ellipse_sample(float3 ru, float3 rv, float randu, float randv)
|
||||
{
|
||||
@@ -109,99 +43,7 @@ ccl_device float3 disk_light_sample(float3 v, float randu, float randv)
|
||||
return ellipse_sample(ru, rv, randu, randv);
|
||||
}
|
||||
|
||||
ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv)
|
||||
{
|
||||
return normalize(D + disk_light_sample(D, randu, randv) * radius);
|
||||
}
|
||||
|
||||
ccl_device float3
|
||||
sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv)
|
||||
{
|
||||
return disk_light_sample(normalize(P - center), randu, randv) * radius;
|
||||
}
|
||||
|
||||
ccl_device float spot_light_attenuation(float3 dir, float spot_angle, float spot_smooth, float3 N)
|
||||
{
|
||||
float attenuation = dot(dir, N);
|
||||
|
||||
if (attenuation <= spot_angle) {
|
||||
attenuation = 0.0f;
|
||||
}
|
||||
else {
|
||||
float t = attenuation - spot_angle;
|
||||
|
||||
if (t < spot_smooth && spot_smooth != 0.0f)
|
||||
attenuation *= smoothstepf(t / spot_smooth);
|
||||
}
|
||||
|
||||
return attenuation;
|
||||
}
|
||||
|
||||
ccl_device float light_spread_attenuation(const float3 D,
|
||||
const float3 lightNg,
|
||||
const float tan_spread,
|
||||
const float normalize_spread)
|
||||
{
|
||||
/* Model a soft-box grid, computing the ratio of light not hidden by the
|
||||
* slats of the grid at a given angle. (see D10594). */
|
||||
const float cos_a = -dot(D, lightNg);
|
||||
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
|
||||
const float tan_a = sin_a / cos_a;
|
||||
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
|
||||
}
|
||||
|
||||
/* Compute subset of area light that actually has an influence on the shading point, to
|
||||
* reduce noise with low spread. */
|
||||
ccl_device bool light_spread_clamp_area_light(const float3 P,
|
||||
const float3 lightNg,
|
||||
ccl_private float3 *lightP,
|
||||
ccl_private float3 *axisu,
|
||||
ccl_private float3 *axisv,
|
||||
const float tan_spread)
|
||||
{
|
||||
/* Closest point in area light plane and distance to that plane. */
|
||||
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
|
||||
const float t = len(closest_P - P);
|
||||
|
||||
/* Radius of circle on area light that actually affects the shading point. */
|
||||
const float radius = t / tan_spread;
|
||||
|
||||
/* TODO: would be faster to store as normalized vector + length, also in rect_light_sample. */
|
||||
float len_u, len_v;
|
||||
const float3 u = normalize_len(*axisu, &len_u);
|
||||
const float3 v = normalize_len(*axisv, &len_v);
|
||||
|
||||
/* Local uv coordinates of closest point. */
|
||||
const float closest_u = dot(u, closest_P - *lightP);
|
||||
const float closest_v = dot(v, closest_P - *lightP);
|
||||
|
||||
/* Compute rectangle encompassing the circle that affects the shading point,
|
||||
* clamped to the bounds of the area light. */
|
||||
const float min_u = max(closest_u - radius, -len_u * 0.5f);
|
||||
const float max_u = min(closest_u + radius, len_u * 0.5f);
|
||||
const float min_v = max(closest_v - radius, -len_v * 0.5f);
|
||||
const float max_v = min(closest_v + radius, len_v * 0.5f);
|
||||
|
||||
/* Skip if rectangle is empty. */
|
||||
if (min_u >= max_u || min_v >= max_v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Compute new area light center position and axes from rectangle in local
|
||||
* uv coordinates. */
|
||||
const float new_center_u = 0.5f * (min_u + max_u);
|
||||
const float new_center_v = 0.5f * (min_v + max_v);
|
||||
const float new_len_u = max_u - min_u;
|
||||
const float new_len_v = max_v - min_v;
|
||||
|
||||
*lightP = *lightP + new_center_u * u + new_center_v * v;
|
||||
*axisu = u * new_len_u;
|
||||
*axisv = v * new_len_v;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device float lamp_light_pdf(KernelGlobals kg, const float3 Ng, const float3 I, float t)
|
||||
ccl_device float lamp_light_pdf(const float3 Ng, const float3 I, float t)
|
||||
{
|
||||
float cos_pi = dot(Ng, I);
|
||||
|
||||
|
127
intern/cycles/kernel/light/distant.h
Normal file
127
intern/cycles/kernel/light/distant.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/geom/geom.h"
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
ccl_device_inline bool distant_light_sample(const ccl_global KernelLight *klight,
|
||||
const float randu,
|
||||
const float randv,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* distant light */
|
||||
float3 lightD = klight->co;
|
||||
float3 D = lightD;
|
||||
float radius = klight->distant.radius;
|
||||
float invarea = klight->distant.invarea;
|
||||
|
||||
if (radius > 0.0f) {
|
||||
D = normalize(D + disk_light_sample(D, randu, randv) * radius);
|
||||
}
|
||||
|
||||
ls->P = D;
|
||||
ls->Ng = D;
|
||||
ls->D = -D;
|
||||
ls->t = FLT_MAX;
|
||||
|
||||
float costheta = dot(lightD, D);
|
||||
ls->pdf = invarea / (costheta * costheta * costheta);
|
||||
ls->eval_fac = ls->pdf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
|
||||
const float3 ray_D,
|
||||
const int lamp,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
{
|
||||
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
const int shader = klight->shader_id;
|
||||
const float radius = klight->distant.radius;
|
||||
const LightType type = (LightType)klight->type;
|
||||
|
||||
if (type != LIGHT_DISTANT) {
|
||||
return false;
|
||||
}
|
||||
if (!(shader & SHADER_USE_MIS)) {
|
||||
return false;
|
||||
}
|
||||
if (radius == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* a distant light is infinitely far away, but equivalent to a disk
|
||||
* shaped light exactly 1 unit away from the current shading point.
|
||||
*
|
||||
* radius t^2/cos(theta)
|
||||
* <----------> t = sqrt(1^2 + tan(theta)^2)
|
||||
* tan(th) area = radius*radius*pi
|
||||
* <----->
|
||||
* \ | (1 + tan(theta)^2)/cos(theta)
|
||||
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
|
||||
* t \th| 1 simplifies to
|
||||
* \-| 1/(cos(theta)^3)
|
||||
* \| magic!
|
||||
* P
|
||||
*/
|
||||
|
||||
float3 lightD = klight->co;
|
||||
float costheta = dot(-lightD, ray_D);
|
||||
float cosangle = klight->distant.cosangle;
|
||||
|
||||
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
|
||||
* Remove when a compiler fix is available. */
|
||||
#ifdef __HIP__
|
||||
ls->shader = klight->shader_id;
|
||||
#endif
|
||||
|
||||
if (costheta < cosangle)
|
||||
return false;
|
||||
|
||||
ls->type = type;
|
||||
#ifndef __HIP__
|
||||
ls->shader = klight->shader_id;
|
||||
#endif
|
||||
ls->object = PRIM_NONE;
|
||||
ls->prim = PRIM_NONE;
|
||||
ls->lamp = lamp;
|
||||
/* todo: missing texture coordinates */
|
||||
ls->u = 0.0f;
|
||||
ls->v = 0.0f;
|
||||
ls->t = FLT_MAX;
|
||||
ls->P = -ray_D;
|
||||
ls->Ng = -ray_D;
|
||||
ls->D = ray_D;
|
||||
ls->group = lamp_lightgroup(kg, lamp);
|
||||
|
||||
/* compute pdf */
|
||||
float invarea = klight->distant.invarea;
|
||||
ls->pdf = invarea / (costheta * costheta * costheta);
|
||||
ls->eval_fac = ls->pdf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline bool distant_light_tree_parameters(const float3 centroid,
|
||||
const float theta_e,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* Treating it as a disk light 1 unit away */
|
||||
cos_theta_u = fast_cosf(theta_e);
|
||||
|
||||
distance = make_float2(1.0f / cos_theta_u, 1.0f);
|
||||
|
||||
point_to_centroid = -centroid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
80
intern/cycles/kernel/light/distribution.h
Normal file
80
intern/cycles/kernel/light/distribution.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Simple CDF based sampling over all lights in the scene, without taking into
|
||||
* account shading position or normal. */
|
||||
|
||||
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private 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. */
|
||||
int first = 0;
|
||||
int len = kernel_data.integrator.num_distribution + 1;
|
||||
float r = randu;
|
||||
|
||||
do {
|
||||
int half_len = len >> 1;
|
||||
int middle = first + half_len;
|
||||
|
||||
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
|
||||
len = half_len;
|
||||
}
|
||||
else {
|
||||
first = middle + 1;
|
||||
len = len - half_len - 1;
|
||||
}
|
||||
} while (len > 0);
|
||||
|
||||
/* Clamping should not be needed but float rounding errors seem to
|
||||
* make this fail on rare occasions. */
|
||||
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
|
||||
|
||||
/* Rescale to reuse random number. this helps the 2D samples within
|
||||
* each area light be stratified as well. */
|
||||
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
|
||||
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
|
||||
randu = (r - distr_min) / (distr_max - distr_min);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
|
||||
ccl_private float &randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private int &emitter_object,
|
||||
ccl_private int &emitter_prim,
|
||||
ccl_private int &emitter_shader_flag,
|
||||
ccl_private float &emitter_pdf_selection)
|
||||
{
|
||||
/* Sample light index from distribution. */
|
||||
const int index = light_distribution_sample(kg, randu);
|
||||
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
|
||||
index);
|
||||
|
||||
emitter_object = kdistribution->mesh_light.object_id;
|
||||
emitter_prim = kdistribution->prim;
|
||||
emitter_shader_flag = kdistribution->mesh_light.shader_flag;
|
||||
emitter_pdf_selection = kernel_data.integrator.distribution_pdf_lights;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)
|
||||
{
|
||||
return kernel_data.integrator.distribution_pdf_lights;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -3,31 +3,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/geom/geom.h"
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/background.h"
|
||||
#include "kernel/light/distant.h"
|
||||
#include "kernel/light/point.h"
|
||||
#include "kernel/light/spot.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
|
||||
#include "kernel/sample/mapping.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Light Sample result */
|
||||
|
||||
typedef struct LightSample {
|
||||
float3 P; /* position on light, or direction for distant light */
|
||||
float3 Ng; /* normal on light */
|
||||
float3 D; /* direction from shading point to light */
|
||||
float t; /* distance to light (FLT_MAX for distant light) */
|
||||
float u, v; /* parametric coordinate on primitive */
|
||||
float pdf; /* light sampling probability density function */
|
||||
float eval_fac; /* intensity multiplier */
|
||||
int object; /* object id for triangle/curve lights */
|
||||
int prim; /* primitive id for triangle/curve lights */
|
||||
int shader; /* shader id */
|
||||
int lamp; /* lamp id */
|
||||
int group; /* lightgroup */
|
||||
LightType type; /* type of light */
|
||||
} LightSample;
|
||||
|
||||
/* Regular Light */
|
||||
/* Sample point on an individual light. */
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool light_sample(KernelGlobals kg,
|
||||
@@ -63,28 +50,15 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
|
||||
ls->Ng = zero_float3();
|
||||
ls->D = zero_float3();
|
||||
ls->pdf = 1.0f;
|
||||
ls->eval_fac = 0.0f;
|
||||
ls->t = FLT_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == LIGHT_DISTANT) {
|
||||
/* distant light */
|
||||
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
float3 D = lightD;
|
||||
float radius = klight->distant.radius;
|
||||
float invarea = klight->distant.invarea;
|
||||
|
||||
if (radius > 0.0f)
|
||||
D = distant_light_sample(D, radius, randu, randv);
|
||||
|
||||
ls->P = D;
|
||||
ls->Ng = D;
|
||||
ls->D = -D;
|
||||
ls->t = FLT_MAX;
|
||||
|
||||
float costheta = dot(lightD, D);
|
||||
ls->pdf = invarea / (costheta * costheta * costheta);
|
||||
ls->eval_fac = ls->pdf;
|
||||
if (!distant_light_sample(klight, randu, randv, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_BACKGROUND) {
|
||||
/* infinite area light (e.g. light dome or env light) */
|
||||
@@ -96,139 +70,28 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
|
||||
ls->t = FLT_MAX;
|
||||
ls->eval_fac = 1.0f;
|
||||
}
|
||||
else if (type == LIGHT_SPOT) {
|
||||
if (!spot_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
if (!point_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ls->P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
const float radius = klight->spot.radius;
|
||||
const float3 dir = make_float3(
|
||||
klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(P - center);
|
||||
ls->P = center;
|
||||
|
||||
if (radius > 0.0f)
|
||||
/* disk light */
|
||||
ls->P += disk_light_sample(lightN, randu, randv) * radius;
|
||||
|
||||
const float invarea = klight->spot.invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
/* we set the light normal to the outgoing direction to support texturing */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
|
||||
/* spot light attenuation */
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
float radius = klight->spot.radius;
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(P - center);
|
||||
ls->P = center;
|
||||
|
||||
if (radius > 0.0f) {
|
||||
ls->P += disk_light_sample(lightN, randu, randv) * radius;
|
||||
}
|
||||
ls->pdf = klight->spot.invarea;
|
||||
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
/* we set the light normal to the outgoing direction to support texturing */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
|
||||
}
|
||||
else {
|
||||
/* area light */
|
||||
float3 axisu = make_float3(
|
||||
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
|
||||
float3 axisv = make_float3(
|
||||
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
|
||||
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
|
||||
float invarea = fabsf(klight->area.invarea);
|
||||
bool is_round = (klight->area.invarea < 0.0f);
|
||||
|
||||
if (!in_volume_segment) {
|
||||
if (dot(ls->P - P, Ng) > 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float3 inplane;
|
||||
|
||||
if (is_round || in_volume_segment) {
|
||||
inplane = ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
|
||||
ls->P += inplane;
|
||||
ls->pdf = invarea;
|
||||
}
|
||||
else {
|
||||
inplane = ls->P;
|
||||
|
||||
float3 sample_axisu = axisu;
|
||||
float3 sample_axisv = axisv;
|
||||
|
||||
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
|
||||
if (!light_spread_clamp_area_light(
|
||||
P, Ng, &ls->P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf = rect_light_sample(P, &ls->P, sample_axisu, sample_axisv, randu, randv, true);
|
||||
inplane = ls->P - inplane;
|
||||
}
|
||||
|
||||
const float light_u = dot(inplane, axisu) * (1.0f / dot(axisu, axisu));
|
||||
const float light_v = dot(inplane, axisv) * (1.0f / dot(axisv, axisv));
|
||||
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
ls->u = light_v + 0.5f;
|
||||
ls->v = -light_u - light_v;
|
||||
|
||||
ls->Ng = Ng;
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
}
|
||||
|
||||
if (is_round) {
|
||||
ls->pdf *= lamp_light_pdf(kg, Ng, -ls->D, ls->t);
|
||||
}
|
||||
/* area light */
|
||||
if (!area_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= kernel_data.integrator.pdf_lights;
|
||||
|
||||
return in_volume_segment || (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
/* Intersect ray with individual light. */
|
||||
|
||||
ccl_device bool lights_intersect(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private const Ray *ccl_restrict ray,
|
||||
@@ -238,7 +101,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
|
||||
const int last_type,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
|
||||
if (path_flag & PATH_RAY_CAMERA) {
|
||||
@@ -271,76 +134,17 @@ ccl_device bool lights_intersect(KernelGlobals kg,
|
||||
float t = 0.0f, u = 0.0f, v = 0.0f;
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
/* Spot/Disk light. */
|
||||
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
const float radius = klight->spot.radius;
|
||||
if (radius == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(ray->P - lightP);
|
||||
/* One sided. */
|
||||
if (dot(ray->D, lightN) >= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float3 P;
|
||||
if (!ray_disk_intersect(
|
||||
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
|
||||
if (!spot_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
/* Sphere light (aka, aligned disk light). */
|
||||
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
const float radius = klight->spot.radius;
|
||||
if (radius == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(ray->P - lightP);
|
||||
float3 P;
|
||||
if (!ray_disk_intersect(
|
||||
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
|
||||
if (!point_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_AREA) {
|
||||
/* Area light. */
|
||||
const float invarea = fabsf(klight->area.invarea);
|
||||
const bool is_round = (klight->area.invarea < 0.0f);
|
||||
if (invarea == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float3 axisu = make_float3(
|
||||
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
|
||||
const float3 axisv = make_float3(
|
||||
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
|
||||
const float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
|
||||
|
||||
/* One sided. */
|
||||
if (dot(ray->D, Ng) >= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
|
||||
float3 P;
|
||||
if (!ray_quad_intersect(ray->P,
|
||||
ray->D,
|
||||
ray->tmin,
|
||||
ray->tmax,
|
||||
light_P,
|
||||
axisu,
|
||||
axisv,
|
||||
Ng,
|
||||
&P,
|
||||
&t,
|
||||
&u,
|
||||
&v,
|
||||
is_round)) {
|
||||
if (!area_light_intersect(klight, ray, &t, &u, &v)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -362,78 +166,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
|
||||
return isect->prim != PRIM_NONE;
|
||||
}
|
||||
|
||||
ccl_device bool light_sample_from_distant_ray(KernelGlobals kg,
|
||||
const float3 ray_D,
|
||||
const int lamp,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
{
|
||||
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
const int shader = klight->shader_id;
|
||||
const float radius = klight->distant.radius;
|
||||
const LightType type = (LightType)klight->type;
|
||||
|
||||
if (type != LIGHT_DISTANT) {
|
||||
return false;
|
||||
}
|
||||
if (!(shader & SHADER_USE_MIS)) {
|
||||
return false;
|
||||
}
|
||||
if (radius == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* a distant light is infinitely far away, but equivalent to a disk
|
||||
* shaped light exactly 1 unit away from the current shading point.
|
||||
*
|
||||
* radius t^2/cos(theta)
|
||||
* <----------> t = sqrt(1^2 + tan(theta)^2)
|
||||
* tan(th) area = radius*radius*pi
|
||||
* <----->
|
||||
* \ | (1 + tan(theta)^2)/cos(theta)
|
||||
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
|
||||
* t \th| 1 simplifies to
|
||||
* \-| 1/(cos(theta)^3)
|
||||
* \| magic!
|
||||
* P
|
||||
*/
|
||||
|
||||
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
float costheta = dot(-lightD, ray_D);
|
||||
float cosangle = klight->distant.cosangle;
|
||||
|
||||
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
|
||||
* Remove when a compiler fix is available. */
|
||||
#ifdef __HIP__
|
||||
ls->shader = klight->shader_id;
|
||||
#endif
|
||||
|
||||
if (costheta < cosangle)
|
||||
return false;
|
||||
|
||||
ls->type = type;
|
||||
#ifndef __HIP__
|
||||
ls->shader = klight->shader_id;
|
||||
#endif
|
||||
ls->object = PRIM_NONE;
|
||||
ls->prim = PRIM_NONE;
|
||||
ls->lamp = lamp;
|
||||
/* todo: missing texture coordinates */
|
||||
ls->u = 0.0f;
|
||||
ls->v = 0.0f;
|
||||
ls->t = FLT_MAX;
|
||||
ls->P = -ray_D;
|
||||
ls->Ng = -ray_D;
|
||||
ls->D = ray_D;
|
||||
ls->group = lamp_lightgroup(kg, lamp);
|
||||
|
||||
/* compute pdf */
|
||||
float invarea = klight->distant.invarea;
|
||||
ls->pdf = invarea / (costheta * costheta * costheta);
|
||||
ls->eval_fac = ls->pdf;
|
||||
ls->pdf *= kernel_data.integrator.pdf_lights;
|
||||
|
||||
return true;
|
||||
}
|
||||
/* Setup light sample from intersection. */
|
||||
|
||||
ccl_device bool light_sample_from_intersection(KernelGlobals kg,
|
||||
ccl_private const Intersection *ccl_restrict isect,
|
||||
@@ -456,102 +189,18 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
|
||||
ls->group = lamp_lightgroup(kg, lamp);
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
const float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
|
||||
/* the normal of the oriented disk */
|
||||
const float3 lightN = normalize(ray_P - center);
|
||||
/* We set the light normal to the outgoing direction to support texturing. */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
/* spot light attenuation */
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
|
||||
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
/* compute pdf */
|
||||
if (ls->t != FLT_MAX)
|
||||
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
|
||||
else
|
||||
ls->pdf = 0.f;
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
const float3 lighN = normalize(ray_P - center);
|
||||
|
||||
/* We set the light normal to the outgoing direction to support texturing. */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
if (!point_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
/* compute pdf */
|
||||
if (ls->t != FLT_MAX)
|
||||
ls->pdf *= lamp_light_pdf(kg, lighN, -ls->D, ls->t);
|
||||
else
|
||||
ls->pdf = 0.f;
|
||||
}
|
||||
else if (type == LIGHT_AREA) {
|
||||
/* area light */
|
||||
float invarea = fabsf(klight->area.invarea);
|
||||
|
||||
float3 axisu = make_float3(
|
||||
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
|
||||
float3 axisv = make_float3(
|
||||
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
|
||||
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
|
||||
float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
|
||||
|
||||
ls->u = isect->u;
|
||||
ls->v = isect->v;
|
||||
ls->D = ray_D;
|
||||
ls->Ng = Ng;
|
||||
|
||||
const bool is_round = (klight->area.invarea < 0.0f);
|
||||
if (is_round) {
|
||||
ls->pdf = invarea * lamp_light_pdf(kg, Ng, -ray_D, ls->t);
|
||||
}
|
||||
else {
|
||||
float3 sample_axisu = axisu;
|
||||
float3 sample_axisv = axisv;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
if (!light_spread_clamp_area_light(
|
||||
ray_P, Ng, &light_P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf = rect_light_sample(ray_P, &light_P, sample_axisu, sample_axisv, 0, 0, false);
|
||||
}
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
if (!area_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -559,411 +208,33 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
|
||||
return false;
|
||||
}
|
||||
|
||||
ls->pdf *= kernel_data.integrator.pdf_lights;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Triangle Light */
|
||||
/* Update light sample for changed new position, for MNEE. */
|
||||
|
||||
/* returns true if the triangle is has motion blur or an instancing transform applied */
|
||||
ccl_device_inline bool triangle_world_space_vertices(
|
||||
KernelGlobals kg, int object, int prim, float time, float3 V[3])
|
||||
{
|
||||
bool has_motion = false;
|
||||
const int object_flag = kernel_data_fetch(object_flag, object);
|
||||
|
||||
if (object_flag & SD_OBJECT_HAS_VERTEX_MOTION && time >= 0.0f) {
|
||||
motion_triangle_vertices(kg, object, prim, time, V);
|
||||
has_motion = true;
|
||||
}
|
||||
else {
|
||||
triangle_vertices(kg, prim, V);
|
||||
}
|
||||
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
#ifdef __OBJECT_MOTION__
|
||||
float object_time = (time >= 0.0f) ? time : 0.5f;
|
||||
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
|
||||
#else
|
||||
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
|
||||
#endif
|
||||
V[0] = transform_point(&tfm, V[0]);
|
||||
V[1] = transform_point(&tfm, V[1]);
|
||||
V[2] = transform_point(&tfm, V[2]);
|
||||
has_motion = true;
|
||||
}
|
||||
return has_motion;
|
||||
}
|
||||
|
||||
ccl_device_inline float triangle_light_pdf_area(KernelGlobals kg,
|
||||
const float3 Ng,
|
||||
const float3 I,
|
||||
float t)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
float t)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* 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);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
const float3 N = cross(e0, e1);
|
||||
const float distance_to_plane = fabsf(dot(N, sd->I * t)) / dot(N, N);
|
||||
|
||||
if (longest_edge_squared > distance_to_plane * distance_to_plane) {
|
||||
/* sd contains the point on the light source
|
||||
* calculate Px, the point that we're shading */
|
||||
const float3 Px = sd->P + sd->I * t;
|
||||
const float3 v0_p = V[0] - Px;
|
||||
const float3 v1_p = V[1] - Px;
|
||||
const float3 v2_p = V[2] - Px;
|
||||
|
||||
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
|
||||
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
|
||||
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
|
||||
|
||||
const float alpha = fast_acosf(dot(u02, u01));
|
||||
const float beta = fast_acosf(-dot(u01, u12));
|
||||
const float gamma = fast_acosf(dot(u02, u12));
|
||||
const float solid_angle = alpha + beta + gamma - M_PI_F;
|
||||
|
||||
/* pdf_triangles is calculated over triangle area, but we're not sampling over its area */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline void triangle_light_sample(KernelGlobals kg,
|
||||
int prim,
|
||||
int object,
|
||||
float randu,
|
||||
float randv,
|
||||
float time,
|
||||
ccl_device_forceinline void light_update_position(KernelGlobals kg,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* to the length of the edges of the triangle. */
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
|
||||
|
||||
float3 V[3];
|
||||
bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
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_data_fetch(object_flag, object);
|
||||
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
|
||||
ls->Ng = -ls->Ng;
|
||||
if (ls->type == LIGHT_POINT) {
|
||||
point_light_update_position(klight, ls, P);
|
||||
}
|
||||
ls->eval_fac = 1.0f;
|
||||
ls->shader = kernel_data_fetch(tri_shader, prim);
|
||||
ls->object = object;
|
||||
ls->prim = prim;
|
||||
ls->lamp = LAMP_NONE;
|
||||
ls->shader |= SHADER_USE_MIS;
|
||||
ls->type = LIGHT_TRIANGLE;
|
||||
ls->group = object_lightgroup(kg, object);
|
||||
|
||||
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
|
||||
|
||||
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
|
||||
/* see James Arvo, "Stratified Sampling of Spherical Triangles"
|
||||
* http://www.graphics.cornell.edu/pubs/1995/Arv95c.pdf */
|
||||
|
||||
/* project the triangle to the unit sphere
|
||||
* and calculate its edges and angles */
|
||||
const float3 v0_p = V[0] - P;
|
||||
const float3 v1_p = V[1] - P;
|
||||
const float3 v2_p = V[2] - P;
|
||||
|
||||
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
|
||||
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
|
||||
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
|
||||
|
||||
const float3 A = safe_normalize(v0_p);
|
||||
const float3 B = safe_normalize(v1_p);
|
||||
const float3 C = safe_normalize(v2_p);
|
||||
|
||||
const float cos_alpha = dot(u02, u01);
|
||||
const float cos_beta = -dot(u01, u12);
|
||||
const float cos_gamma = dot(u02, u12);
|
||||
|
||||
/* calculate dihedral angles */
|
||||
const float alpha = fast_acosf(cos_alpha);
|
||||
const float beta = fast_acosf(cos_beta);
|
||||
const float gamma = fast_acosf(cos_gamma);
|
||||
/* the area of the unit spherical triangle = solid angle */
|
||||
const float solid_angle = alpha + beta + gamma - M_PI_F;
|
||||
|
||||
/* precompute a few things
|
||||
* these could be re-used to take several samples
|
||||
* as they are independent of randu/randv */
|
||||
const float cos_c = dot(A, B);
|
||||
const float sin_alpha = fast_sinf(alpha);
|
||||
const float product = sin_alpha * cos_c;
|
||||
|
||||
/* Select a random sub-area of the spherical triangle
|
||||
* and calculate the third vertex C_ of that new triangle */
|
||||
const float phi = randu * solid_angle - alpha;
|
||||
float s, t;
|
||||
fast_sincosf(phi, &s, &t);
|
||||
const float u = t - cos_alpha;
|
||||
const float v = s + product;
|
||||
|
||||
const float3 U = safe_normalize(C - dot(C, A) * A);
|
||||
|
||||
float q = 1.0f;
|
||||
const float det = ((v * s + u * t) * sin_alpha);
|
||||
if (det != 0.0f) {
|
||||
q = ((v * t - u * s) * cos_alpha - v) / det;
|
||||
}
|
||||
const float temp = max(1.0f - q * q, 0.0f);
|
||||
|
||||
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * U);
|
||||
|
||||
/* Finally, select a random point along the edge of the new triangle
|
||||
* That point on the spherical triangle is the sampled ray direction */
|
||||
const float z = 1.0f - randv * (1.0f - dot(C_, B));
|
||||
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
|
||||
|
||||
/* calculate intersection with the planar triangle */
|
||||
if (!ray_triangle_intersect(
|
||||
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) {
|
||||
ls->pdf = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
ls->P = P + ls->D * ls->t;
|
||||
|
||||
/* pdf_triangles is calculated over triangle area, but we're sampling over solid angle */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
ls->pdf = 0.0f;
|
||||
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;
|
||||
}
|
||||
else if (ls->type == LIGHT_SPOT) {
|
||||
spot_light_update_position(klight, ls, P);
|
||||
}
|
||||
else {
|
||||
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
|
||||
* and Square" */
|
||||
float u = randu;
|
||||
float v = randv;
|
||||
if (v > u) {
|
||||
u *= 0.5f;
|
||||
v -= u;
|
||||
}
|
||||
else {
|
||||
v *= 0.5f;
|
||||
u -= v;
|
||||
}
|
||||
|
||||
const float t = 1.0f - u - v;
|
||||
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;
|
||||
}
|
||||
ls->u = u;
|
||||
ls->v = v;
|
||||
else if (ls->type == LIGHT_AREA) {
|
||||
area_light_update_position(klight, ls, P);
|
||||
}
|
||||
}
|
||||
|
||||
/* Light Distribution */
|
||||
|
||||
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private 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. */
|
||||
int first = 0;
|
||||
int len = kernel_data.integrator.num_distribution + 1;
|
||||
float r = *randu;
|
||||
|
||||
do {
|
||||
int half_len = len >> 1;
|
||||
int middle = first + half_len;
|
||||
|
||||
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
|
||||
len = half_len;
|
||||
}
|
||||
else {
|
||||
first = middle + 1;
|
||||
len = len - half_len - 1;
|
||||
}
|
||||
} while (len > 0);
|
||||
|
||||
/* Clamping should not be needed but float rounding errors seem to
|
||||
* make this fail on rare occasions. */
|
||||
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
|
||||
|
||||
/* Rescale to reuse random number. this helps the 2D samples within
|
||||
* each area light be stratified as well. */
|
||||
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
|
||||
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
|
||||
*randu = (r - distr_min) / (distr_max - distr_min);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* Generic Light */
|
||||
/* Light info. */
|
||||
|
||||
ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg, int index, int bounce)
|
||||
{
|
||||
return (bounce > kernel_data_fetch(lights, index).max_bounces);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
|
||||
float randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Sample light index from distribution. */
|
||||
const int index = light_distribution_sample(kg, &randu);
|
||||
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
|
||||
index);
|
||||
const int prim = kdistribution->prim;
|
||||
|
||||
if (prim >= 0) {
|
||||
/* Mesh light. */
|
||||
const int object = kdistribution->mesh_light.object_id;
|
||||
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int shader_flag = kdistribution->mesh_light.shader_flag;
|
||||
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
|
||||
ls->shader |= shader_flag;
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
const int lamp = -prim - 1;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls);
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_distribution_sample_from_volume_segment(KernelGlobals kg,
|
||||
float randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_distribution_sample_from_position(KernelGlobals kg,
|
||||
float randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_distribution_sample_new_position(KernelGlobals kg,
|
||||
const float randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Sample a new position on the same light, for volume sampling. */
|
||||
if (ls->type == LIGHT_TRIANGLE) {
|
||||
triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P);
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
else {
|
||||
return light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls);
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
136
intern/cycles/kernel/light/point.h
Normal file
136
intern/cycles/kernel/light/point.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
|
||||
const float randu,
|
||||
const float randv,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
float3 center = klight->co;
|
||||
float radius = klight->spot.radius;
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(P - center);
|
||||
ls->P = center;
|
||||
|
||||
if (radius > 0.0f) {
|
||||
ls->P += disk_light_sample(lightN, randu, randv) * radius;
|
||||
}
|
||||
ls->pdf = klight->spot.invarea;
|
||||
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
/* we set the light normal to the outgoing direction to support texturing */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline void point_light_update_position(const ccl_global KernelLight *klight,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
}
|
||||
|
||||
ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t)
|
||||
{
|
||||
/* Sphere light (aka, aligned disk light). */
|
||||
const float3 lightP = klight->co;
|
||||
const float radius = klight->spot.radius;
|
||||
if (radius == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(ray->P - lightP);
|
||||
float3 P;
|
||||
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
|
||||
}
|
||||
|
||||
ccl_device_inline bool point_light_sample_from_intersection(
|
||||
const ccl_global KernelLight *klight,
|
||||
ccl_private const Intersection *ccl_restrict isect,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
{
|
||||
const float3 lighN = normalize(ray_P - klight->co);
|
||||
|
||||
/* We set the light normal to the outgoing direction to support texturing. */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
/* compute pdf */
|
||||
if (ls->t != FLT_MAX) {
|
||||
ls->pdf *= lamp_light_pdf(lighN, -ls->D, ls->t);
|
||||
}
|
||||
else {
|
||||
ls->pdf = 0.f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (in_volume_segment) {
|
||||
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
|
||||
return true;
|
||||
}
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
distance = make_float2(hypotenus, min_distance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -6,8 +6,13 @@
|
||||
#include "kernel/integrator/path_state.h"
|
||||
#include "kernel/integrator/surface_shader.h"
|
||||
|
||||
#include "kernel/light/distribution.h"
|
||||
#include "kernel/light/light.h"
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
# include "kernel/light/tree.h"
|
||||
#endif
|
||||
|
||||
#include "kernel/sample/mapping.h"
|
||||
#include "kernel/sample/mis.h"
|
||||
|
||||
@@ -277,6 +282,8 @@ ccl_device_inline void light_sample_to_volume_shadow_ray(
|
||||
shadow_ray_setup(sd, ls, P, ray, false);
|
||||
}
|
||||
|
||||
/* Multiple importance sampling weights. */
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward(KernelGlobals kg,
|
||||
const float forward_pdf,
|
||||
const float nee_pdf)
|
||||
@@ -309,4 +316,333 @@ ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
|
||||
return power_heuristic(nee_pdf, forward_pdf);
|
||||
}
|
||||
|
||||
/* Next event estimation sampling.
|
||||
*
|
||||
* Sample a position on a light in the scene, from a position on a surface or
|
||||
* from a volume segment.
|
||||
*
|
||||
* Uses either a flat distribution or light tree. */
|
||||
|
||||
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
|
||||
float randu,
|
||||
float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
const float t,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Select an emitter. */
|
||||
int emitter_object = 0;
|
||||
int emitter_prim = 0;
|
||||
int emitter_shader_flag = 0;
|
||||
float emitter_pdf_selection = 0.0f;
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
if (!light_tree_sample<true>(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
D,
|
||||
t,
|
||||
SD_BSDF_HAS_TRANSMISSION,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (!light_distribution_sample(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set first, triangle light sampling from flat distribution will override. */
|
||||
ls->pdf_selection = emitter_pdf_selection;
|
||||
|
||||
/* Sample a point on the chosen emitter. */
|
||||
if (emitter_prim >= 0) {
|
||||
/* Mesh light. */
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!triangle_light_sample<true>(
|
||||
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Light object. */
|
||||
const int lamp = ~emitter_prim;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<true>(kg, lamp, randu, randv, P, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
ls->shader |= emitter_shader_flag;
|
||||
|
||||
return (ls->pdf > 0);
|
||||
}
|
||||
|
||||
ccl_device bool light_sample_from_position(KernelGlobals kg,
|
||||
ccl_private const RNGState *rng_state,
|
||||
float randu,
|
||||
float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Select an emitter. */
|
||||
int emitter_object = 0;
|
||||
int emitter_prim = 0;
|
||||
int emitter_shader_flag = 0;
|
||||
float emitter_pdf_selection = 0.0f;
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
if (!light_tree_sample<false>(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
N,
|
||||
0,
|
||||
shader_flags,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (!light_distribution_sample(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set first, triangle light sampling from flat distribution will override. */
|
||||
ls->pdf_selection = emitter_pdf_selection;
|
||||
|
||||
/* Sample a point on the chosen emitter.
|
||||
* TODO: deduplicate code with light_sample_from_volume_segment? */
|
||||
if (emitter_prim >= 0) {
|
||||
/* Mesh light. */
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!triangle_light_sample<false>(
|
||||
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Light object. */
|
||||
const int lamp = ~emitter_prim;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<false>(kg, lamp, randu, randv, P, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
ls->shader |= emitter_shader_flag;
|
||||
|
||||
return (ls->pdf > 0);
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
|
||||
const float randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Sample a new position on the same light, for volume sampling. */
|
||||
if (ls->type == LIGHT_TRIANGLE) {
|
||||
if (!triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Handled in triangle_light_sample for efficiency. */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (!light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls)) {
|
||||
return false;
|
||||
}
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device_forceinline void light_sample_update_position(KernelGlobals kg,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
/* Update light sample for new shading point position, while keeping
|
||||
* position on the light fixed. */
|
||||
|
||||
/* NOTE : preserve pdf in area measure. */
|
||||
light_update_position(kg, ls, P);
|
||||
|
||||
/* Re-apply already computed selection pdf. */
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
}
|
||||
|
||||
/* Forward sampling.
|
||||
*
|
||||
* Multiple importance sampling weights for hitting surface, light or background
|
||||
* through indirect light ray.
|
||||
*
|
||||
* The BSDF or phase pdf from the previous bounce was stored in mis_ray_pdf and
|
||||
* is used for balancing with the light sampling pdf. */
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const uint32_t path_flag,
|
||||
const ccl_private ShaderData *sd)
|
||||
{
|
||||
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
const float t = sd->ray_length;
|
||||
float pdf = triangle_light_pdf(kg, sd, t);
|
||||
|
||||
/* Light selection pdf. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
|
||||
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
|
||||
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, sd->prim - prim_offset + lookup_offset);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Handled in triangle_light_pdf for efficiency. */
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const uint32_t path_flag,
|
||||
const ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
float pdf = ls->pdf;
|
||||
|
||||
/* Light selection pdf. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
pdf *= light_tree_pdf(kg, P, N, path_flag, ~ls->lamp);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_distant(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const uint32_t path_flag,
|
||||
const ccl_private LightSample *ls)
|
||||
{
|
||||
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
return light_sample_mis_weight_forward_lamp(kg, state, path_flag, ls, ray_P);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
|
||||
float pdf = background_light_pdf(kg, ray_P, ray_D);
|
||||
|
||||
/* Light selection pdf. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, ~kernel_data.background.light_index);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
179
intern/cycles/kernel/light/spot.h
Normal file
179
intern/cycles/kernel/light/spot.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
ccl_device float spot_light_attenuation(float3 dir,
|
||||
float cos_half_spot_angle,
|
||||
float spot_smooth,
|
||||
float3 N)
|
||||
{
|
||||
float attenuation = dot(dir, N);
|
||||
|
||||
if (attenuation <= cos_half_spot_angle) {
|
||||
attenuation = 0.0f;
|
||||
}
|
||||
else {
|
||||
float t = attenuation - cos_half_spot_angle;
|
||||
|
||||
if (t < spot_smooth && spot_smooth != 0.0f)
|
||||
attenuation *= smoothstepf(t / spot_smooth);
|
||||
}
|
||||
|
||||
return attenuation;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
|
||||
const float randu,
|
||||
const float randv,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
ls->P = klight->co;
|
||||
|
||||
const float3 center = klight->co;
|
||||
const float radius = klight->spot.radius;
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(P - center);
|
||||
ls->P = center;
|
||||
|
||||
if (radius > 0.0f) {
|
||||
/* disk light */
|
||||
ls->P += disk_light_sample(lightN, randu, randv) * radius;
|
||||
}
|
||||
|
||||
const float invarea = klight->spot.invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
/* we set the light normal to the outgoing direction to support texturing */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
|
||||
/* spot light attenuation */
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline void spot_light_update_position(const ccl_global KernelLight *klight,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
/* spot light attenuation */
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, ls->Ng);
|
||||
}
|
||||
|
||||
ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t)
|
||||
{
|
||||
/* Spot/Disk light. */
|
||||
const float3 lightP = klight->co;
|
||||
const float radius = klight->spot.radius;
|
||||
if (radius == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
/* disk oriented normal */
|
||||
const float3 lightN = normalize(ray->P - lightP);
|
||||
/* One sided. */
|
||||
if (dot(ray->D, lightN) >= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 P;
|
||||
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
|
||||
}
|
||||
|
||||
ccl_device_inline bool spot_light_sample_from_intersection(
|
||||
const ccl_global KernelLight *klight,
|
||||
ccl_private const Intersection *ccl_restrict isect,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
{
|
||||
/* the normal of the oriented disk */
|
||||
const float3 lightN = normalize(ray_P - klight->co);
|
||||
/* We set the light normal to the outgoing direction to support texturing. */
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
float invarea = klight->spot.invarea;
|
||||
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
|
||||
ls->pdf = invarea;
|
||||
|
||||
/* spot light attenuation */
|
||||
ls->eval_fac *= spot_light_attenuation(
|
||||
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
|
||||
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float2 uv = map_to_sphere(ls->Ng);
|
||||
ls->u = uv.x;
|
||||
ls->v = uv.y;
|
||||
|
||||
/* compute pdf */
|
||||
if (ls->t != FLT_MAX) {
|
||||
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
|
||||
}
|
||||
else {
|
||||
ls->pdf = 0.f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
float min_distance;
|
||||
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance);
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
if (in_volume_segment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
distance = make_float2(hypotenus, min_distance);
|
||||
point_to_centroid = point_to_centroid_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
691
intern/cycles/kernel/light/tree.h
Normal file
691
intern/cycles/kernel/light/tree.h
Normal file
@@ -0,0 +1,691 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/common.h"
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/light/spot.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* TODO: this seems like a relative expensive computation, and we can make it a lot cheaper
|
||||
* by using a bounding sphere instead of a bounding box. This will be more inaccurate, but it
|
||||
* might be fine when used along with the adaptive splitting. */
|
||||
ccl_device float light_tree_cos_bounding_box_angle(const BoundingBox bbox,
|
||||
const float3 P,
|
||||
const float3 point_to_centroid)
|
||||
{
|
||||
if (P.x > bbox.min.x && P.y > bbox.min.y && P.z > bbox.min.z && P.x < bbox.max.x &&
|
||||
P.y < bbox.max.y && P.z < bbox.max.z) {
|
||||
/* If P is inside the bbox, `theta_u` covers the whole sphere */
|
||||
return -1.0f;
|
||||
}
|
||||
float cos_theta_u = 1.0f;
|
||||
/* Iterate through all 8 possible points of the bounding box. */
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const float3 corner = make_float3((i & 1) ? bbox.max.x : bbox.min.x,
|
||||
(i & 2) ? bbox.max.y : bbox.min.y,
|
||||
(i & 4) ? bbox.max.z : bbox.min.z);
|
||||
|
||||
/* Caculate the bounding box angle. */
|
||||
float3 point_to_corner = normalize(corner - P);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
}
|
||||
return cos_theta_u;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float sin_from_cos(const float c)
|
||||
{
|
||||
return safe_sqrtf(1.0f - sqr(c));
|
||||
}
|
||||
|
||||
/* Compute vector v as in Fig .8. P_v is the corresponding point along the ray ccl_device float3 */
|
||||
ccl_device float3 compute_v(
|
||||
const float3 centroid, const float3 P, const float3 D, const float3 bcone_axis, const float t)
|
||||
{
|
||||
const float3 unnormalized_v0 = P - centroid;
|
||||
float len_v0;
|
||||
const float3 unnormalized_v1 = unnormalized_v0 + D * fminf(t, 1e12f);
|
||||
const float3 v0 = normalize_len(unnormalized_v0, &len_v0);
|
||||
const float3 v1 = normalize(unnormalized_v1);
|
||||
|
||||
const float3 o0 = v0;
|
||||
float3 o1, o2;
|
||||
make_orthonormals_tangent(o0, v1, &o1, &o2);
|
||||
|
||||
const float dot_o0_a = dot(o0, bcone_axis);
|
||||
const float dot_o1_a = dot(o1, bcone_axis);
|
||||
const float cos_phi0 = dot_o0_a / sqrtf(sqr(dot_o0_a) + sqr(dot_o1_a));
|
||||
|
||||
return (dot_o1_a < 0 || dot(v0, v1) > cos_phi0) ? (dot_o0_a > dot(v1, bcone_axis) ? v0 : v1) :
|
||||
cos_phi0 * o0 + sin_from_cos(cos_phi0) * o1;
|
||||
}
|
||||
|
||||
/* This is the general function for calculating the importance of either a cluster or an emitter.
|
||||
* Both of the specialized functions obtain the necessary data before calling this function. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_importance(const float3 N_or_D,
|
||||
const bool has_transmission,
|
||||
const float3 point_to_centroid,
|
||||
const float cos_theta_u,
|
||||
const BoundingCone bcone,
|
||||
const float max_distance,
|
||||
const float min_distance,
|
||||
const float t,
|
||||
const float energy,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
|
||||
const float sin_theta_u = sin_from_cos(cos_theta_u);
|
||||
|
||||
/* cos(theta_i') in the paper, omitted for volume */
|
||||
float cos_min_incidence_angle = 1.0f;
|
||||
float cos_max_incidence_angle = 1.0f;
|
||||
|
||||
/* when sampling the light tree for the second time in `shade_volume.h` and when query the pdf in
|
||||
* `sample.h` */
|
||||
const bool in_volume = is_zero(N_or_D);
|
||||
if (!in_volume_segment && !in_volume) {
|
||||
const float3 N = N_or_D;
|
||||
const float cos_theta_i = has_transmission ? fabsf(dot(point_to_centroid, N)) :
|
||||
dot(point_to_centroid, N);
|
||||
const float sin_theta_i = sin_from_cos(cos_theta_i);
|
||||
|
||||
/* cos_min_incidence_angle = cos(max{theta_i - theta_u, 0}) = cos(theta_i') in the paper */
|
||||
cos_min_incidence_angle = cos_theta_i >= cos_theta_u ?
|
||||
1.0f :
|
||||
cos_theta_i * cos_theta_u + sin_theta_i * sin_theta_u;
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. This is more accurate than the bbox test if we are calculating the importance of
|
||||
* an emitter with radius */
|
||||
if (!has_transmission && cos_min_incidence_angle < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos_max_incidence_angle = cos(min{theta_i + theta_u, pi}) */
|
||||
cos_max_incidence_angle = fmaxf(cos_theta_i * cos_theta_u - sin_theta_i * sin_theta_u, 0.0f);
|
||||
}
|
||||
|
||||
/* cos(theta - theta_u) */
|
||||
const float cos_theta = dot(bcone.axis, -point_to_centroid);
|
||||
const float sin_theta = sin_from_cos(cos_theta);
|
||||
const float cos_theta_minus_theta_u = cos_theta * cos_theta_u + sin_theta * sin_theta_u;
|
||||
|
||||
float cos_theta_o, sin_theta_o;
|
||||
fast_sincosf(bcone.theta_o, &sin_theta_o, &cos_theta_o);
|
||||
|
||||
/* minimum angle an emitter’s axis would form with the direction to the shading point,
|
||||
* cos(theta') in the paper */
|
||||
float cos_min_outgoing_angle;
|
||||
if ((cos_theta >= cos_theta_u) || (cos_theta_minus_theta_u >= cos_theta_o)) {
|
||||
/* theta - theta_o - theta_u <= 0 */
|
||||
kernel_assert((fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u)) < 5e-4f);
|
||||
cos_min_outgoing_angle = 1.0f;
|
||||
}
|
||||
else if ((bcone.theta_o + bcone.theta_e > M_PI_F) ||
|
||||
(cos_theta_minus_theta_u > cos(bcone.theta_o + bcone.theta_e))) {
|
||||
/* theta' = theta - theta_o - theta_u < theta_e */
|
||||
kernel_assert(
|
||||
(fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u) - bcone.theta_e) < 5e-4f);
|
||||
const float sin_theta_minus_theta_u = sin_from_cos(cos_theta_minus_theta_u);
|
||||
cos_min_outgoing_angle = cos_theta_minus_theta_u * cos_theta_o +
|
||||
sin_theta_minus_theta_u * sin_theta_o;
|
||||
}
|
||||
else {
|
||||
/* cluster invisible */
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: find a good approximation for f_a. */
|
||||
const float f_a = 1.0f;
|
||||
/* TODO: also consider t (or theta_a, theta_b) for volume */
|
||||
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle /
|
||||
(in_volume_segment ? min_distance : sqr(min_distance)));
|
||||
|
||||
/* TODO: also min importance for volume? */
|
||||
if (in_volume_segment) {
|
||||
min_importance = max_importance;
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos(theta + theta_o + theta_u) if theta + theta_o + theta_u < theta_e, 0 otherwise */
|
||||
float cos_max_outgoing_angle;
|
||||
const float cos_theta_plus_theta_u = cos_theta * cos_theta_u - sin_theta * sin_theta_u;
|
||||
if (bcone.theta_e - bcone.theta_o < 0 || cos_theta < 0 || cos_theta_u < 0 ||
|
||||
cos_theta_plus_theta_u < cos(bcone.theta_e - bcone.theta_o)) {
|
||||
min_importance = 0.0f;
|
||||
}
|
||||
else {
|
||||
const float sin_theta_plus_theta_u = sin_from_cos(cos_theta_plus_theta_u);
|
||||
cos_max_outgoing_angle = cos_theta_plus_theta_u * cos_theta_o -
|
||||
sin_theta_plus_theta_u * sin_theta_o;
|
||||
min_importance = fabsf(f_a * cos_max_incidence_angle * energy * cos_max_outgoing_angle /
|
||||
sqr(max_distance));
|
||||
}
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
|
||||
ccl_global const KernelLightTreeEmitter *kemitter,
|
||||
const float3 P,
|
||||
ccl_private float3 ¢roid,
|
||||
ccl_private packed_float3 &dir)
|
||||
{
|
||||
const int prim_id = kemitter->prim_id;
|
||||
if (prim_id < 0) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
|
||||
centroid = klight->co;
|
||||
|
||||
switch (klight->type) {
|
||||
case LIGHT_SPOT:
|
||||
dir = klight->spot.dir;
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
/* Disk-oriented normal */
|
||||
dir = safe_normalize(P - centroid);
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
dir = klight->area.dir;
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
/* Aarbitrary centroid and direction */
|
||||
centroid = make_float3(0.0f, 0.0f, 1.0f);
|
||||
dir = make_float3(0.0f, 0.0f, -1.0f);
|
||||
return !in_volume_segment;
|
||||
case LIGHT_DISTANT:
|
||||
dir = centroid;
|
||||
return !in_volume_segment;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int object = kemitter->mesh_light.object_id;
|
||||
float3 vertices[3];
|
||||
triangle_world_space_vertices(kg, object, prim_id, -1.0f, vertices);
|
||||
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
|
||||
|
||||
if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_FRONT) {
|
||||
dir = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
}
|
||||
else if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_BACK) {
|
||||
dir = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
}
|
||||
else {
|
||||
/* Double sided: any vector in the plane. */
|
||||
dir = safe_normalize(vertices[0] - vertices[1]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_emitter_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
int emitter_index,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
emitter_index);
|
||||
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
BoundingCone bcone;
|
||||
bcone.theta_o = kemitter->theta_o;
|
||||
bcone.theta_e = kemitter->theta_e;
|
||||
float cos_theta_u;
|
||||
float2 distance; /* distance.x = max_distance, distance.y = mix_distance */
|
||||
float3 centroid, point_to_centroid, P_c;
|
||||
|
||||
if (!compute_emitter_centroid_and_dir<in_volume_segment>(
|
||||
kg, kemitter, P, centroid, bcone.axis)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int prim_id = kemitter->prim_id;
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
/* Closest point */
|
||||
P_c = P + dot(centroid - P, D) * D;
|
||||
/* minimal distance of the ray to the cluster */
|
||||
distance.x = len(centroid - P_c);
|
||||
distance.y = distance.x;
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
}
|
||||
else {
|
||||
P_c = P;
|
||||
}
|
||||
|
||||
bool is_visible;
|
||||
if (prim_id < 0) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
|
||||
switch (klight->type) {
|
||||
/* Function templates only modifies cos_theta_u when in_volume_segment = true */
|
||||
case LIGHT_SPOT:
|
||||
is_visible = spot_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
is_visible = point_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
|
||||
bcone.theta_o = 0.0f;
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
is_visible = area_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, N_or_D, bcone.axis, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
is_visible = background_light_tree_parameters(
|
||||
centroid, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_DISTANT:
|
||||
is_visible = distant_light_tree_parameters(
|
||||
centroid, bcone.theta_e, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { /* mesh light */
|
||||
is_visible = triangle_light_tree_parameters<in_volume_segment>(
|
||||
kg, kemitter, centroid, P_c, N_or_D, bcone, cos_theta_u, distance, point_to_centroid);
|
||||
}
|
||||
|
||||
is_visible |= has_transmission;
|
||||
if (!is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance.x,
|
||||
distance.y,
|
||||
t,
|
||||
kemitter->energy,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_node_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
if (knode->num_prims == 1) {
|
||||
/* At a leaf node with only one emitter */
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, -knode->child_index, max_importance, min_importance);
|
||||
}
|
||||
else if (knode->num_prims != 0) {
|
||||
const BoundingCone bcone = knode->bcone;
|
||||
const BoundingBox bbox = knode->bbox;
|
||||
|
||||
float3 point_to_centroid;
|
||||
float cos_theta_u;
|
||||
float distance;
|
||||
if (knode->bit_trail == 1) {
|
||||
/* distant light node */
|
||||
if (in_volume_segment) {
|
||||
return;
|
||||
}
|
||||
point_to_centroid = -bcone.axis;
|
||||
cos_theta_u = fast_cosf(bcone.theta_o);
|
||||
distance = 1.0f;
|
||||
}
|
||||
else {
|
||||
const float3 centroid = 0.5f * (bbox.min + bbox.max);
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
const float3 closest_point = P + dot(centroid - P, D) * D;
|
||||
/* minimal distance of the ray to the cluster */
|
||||
distance = len(centroid - closest_point);
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, closest_point, point_to_centroid);
|
||||
}
|
||||
else {
|
||||
const float3 N = N_or_D;
|
||||
const float3 bbox_extent = bbox.max - centroid;
|
||||
const bool bbox_is_visible = has_transmission |
|
||||
(dot(N, centroid - P) + dot(fabs(N), fabs(bbox_extent)) > 0);
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. */
|
||||
if (!bbox_is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
point_to_centroid = normalize_len(centroid - P, &distance);
|
||||
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, P, point_to_centroid);
|
||||
}
|
||||
/* clamp distance to half the radius of the cluster when splitting is disabled */
|
||||
distance = fmaxf(0.5f * len(centroid - bbox.max), distance);
|
||||
}
|
||||
/* TODO: currently max_distance = min_distance, max_importance = min_importance for the
|
||||
* nodes. Do we need better weights for complex scenes? */
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance,
|
||||
distance,
|
||||
t,
|
||||
knode->energy,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device void sample_resevoir(const int current_index,
|
||||
const float current_weight,
|
||||
ccl_private int &selected_index,
|
||||
ccl_private float &selected_weight,
|
||||
ccl_private float &total_weight,
|
||||
ccl_private float &rand)
|
||||
{
|
||||
if (current_weight == 0.0f) {
|
||||
return;
|
||||
}
|
||||
total_weight += current_weight;
|
||||
float thresh = current_weight / total_weight;
|
||||
if (rand <= thresh) {
|
||||
selected_index = current_index;
|
||||
selected_weight = current_weight;
|
||||
rand = rand / thresh;
|
||||
}
|
||||
else {
|
||||
rand = (rand - thresh) / (1.0f - thresh);
|
||||
}
|
||||
kernel_assert(rand >= 0.0f && rand <= 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
/* pick an emitter from a leaf node using resevoir sampling, keep two reservoirs for upper and
|
||||
* lower bounds */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device int light_tree_cluster_select_emitter(KernelGlobals kg,
|
||||
ccl_private float &rand,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float *pdf_factor)
|
||||
{
|
||||
float selected_importance[2] = {0.0f, 0.0f};
|
||||
float total_importance[2] = {0.0f, 0.0f};
|
||||
int selected_index = -1;
|
||||
|
||||
/* Mark emitters with zero importance. Used for resevoir when total minimum importance = 0 */
|
||||
kernel_assert(knode->num_prims <= sizeof(uint) * 8);
|
||||
uint has_importance = 0;
|
||||
|
||||
const bool sample_max = (rand > 0.5f); /* sampling using the maximum importance */
|
||||
rand = rand * 2.0f - float(sample_max);
|
||||
|
||||
for (int i = 0; i < knode->num_prims; i++) {
|
||||
int current_index = -knode->child_index + i;
|
||||
/* maximum importance = importance[0], mininum importance = importance[1] */
|
||||
float importance[2];
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, current_index, importance[0], importance[1]);
|
||||
|
||||
sample_resevoir(current_index,
|
||||
importance[!sample_max],
|
||||
selected_index,
|
||||
selected_importance[!sample_max],
|
||||
total_importance[!sample_max],
|
||||
rand);
|
||||
if (selected_index == current_index) {
|
||||
selected_importance[sample_max] = importance[sample_max];
|
||||
}
|
||||
total_importance[sample_max] += importance[sample_max];
|
||||
|
||||
has_importance |= ((importance[0] > 0) << i);
|
||||
}
|
||||
|
||||
if (total_importance[0] == 0.0f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (total_importance[1] == 0.0f) {
|
||||
/* uniformly sample emitters with positive maximum importance */
|
||||
if (sample_max) {
|
||||
selected_importance[1] = 1.0f;
|
||||
total_importance[1] = float(popcount(has_importance));
|
||||
}
|
||||
else {
|
||||
selected_index = -1;
|
||||
for (int i = 0; i < knode->num_prims; i++) {
|
||||
int current_index = -knode->child_index + i;
|
||||
sample_resevoir(current_index,
|
||||
float(has_importance & 1),
|
||||
selected_index,
|
||||
selected_importance[1],
|
||||
total_importance[1],
|
||||
rand);
|
||||
has_importance >>= 1;
|
||||
}
|
||||
|
||||
float discard;
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, selected_index, selected_importance[0], discard);
|
||||
}
|
||||
}
|
||||
|
||||
*pdf_factor = 0.5f * (selected_importance[0] / total_importance[0] +
|
||||
selected_importance[1] / total_importance[1]);
|
||||
|
||||
return selected_index;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool get_left_probability(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const int left_index,
|
||||
const int right_index,
|
||||
ccl_private float &left_probability)
|
||||
{
|
||||
const ccl_global KernelLightTreeNode *left = &kernel_data_fetch(light_tree_nodes, left_index);
|
||||
const ccl_global KernelLightTreeNode *right = &kernel_data_fetch(light_tree_nodes, right_index);
|
||||
|
||||
float min_left_importance, max_left_importance, min_right_importance, max_right_importance;
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, left, max_left_importance, min_left_importance);
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, right, max_right_importance, min_right_importance);
|
||||
|
||||
const float total_max_importance = max_left_importance + max_right_importance;
|
||||
if (total_max_importance == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
const float total_min_importance = min_left_importance + min_right_importance;
|
||||
|
||||
/* average two probabilities of picking the left child node using lower and upper bounds */
|
||||
const float probability_max = max_left_importance / total_max_importance;
|
||||
const float probability_min = total_min_importance > 0 ?
|
||||
min_left_importance / total_min_importance :
|
||||
0.5f * (float(max_left_importance > 0) +
|
||||
float(max_right_importance == 0.0f));
|
||||
left_probability = 0.5f * (probability_max + probability_min);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
ccl_private float &randu,
|
||||
ccl_private float &randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private int &emitter_object,
|
||||
ccl_private int &emitter_prim,
|
||||
ccl_private int &emitter_shader_flag,
|
||||
ccl_private float &emitter_pdf_selection)
|
||||
{
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
|
||||
float pdf_leaf = 1.0f;
|
||||
float pdf_emitter_from_leaf = 1.0f;
|
||||
int selected_light = -1;
|
||||
|
||||
int node_index = 0; /* root node */
|
||||
|
||||
/* Traverse the light tree until a leaf node is reached. */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (knode->child_index <= 0) {
|
||||
/* At a leaf node, we pick an emitter */
|
||||
selected_light = light_tree_cluster_select_emitter<in_volume_segment>(
|
||||
kg, randv, P, N_or_D, t, has_transmission, knode, &pdf_emitter_from_leaf);
|
||||
break;
|
||||
}
|
||||
|
||||
/* At an interior node, the left child is directly after the parent,
|
||||
* while the right child is stored as the child index. */
|
||||
const int left_index = node_index + 1;
|
||||
const int right_index = knode->child_index;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, left_index, right_index, left_prob)) {
|
||||
return false; /* both child nodes have zero importance */
|
||||
}
|
||||
|
||||
float discard;
|
||||
float total_prob = left_prob;
|
||||
node_index = left_index;
|
||||
sample_resevoir(right_index, 1.0f - left_prob, node_index, discard, total_prob, randu);
|
||||
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
|
||||
}
|
||||
|
||||
if (selected_light < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return info about chosen emitter. */
|
||||
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
selected_light);
|
||||
|
||||
emitter_object = kemitter->mesh_light.object_id;
|
||||
emitter_prim = kemitter->prim_id;
|
||||
emitter_shader_flag = kemitter->mesh_light.shader_flag;
|
||||
emitter_pdf_selection = pdf_leaf * pdf_emitter_from_leaf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We need to be able to find the probability of selecting a given light for MIS. */
|
||||
ccl_device float light_tree_pdf(
|
||||
KernelGlobals kg, const float3 P, const float3 N, const int path_flag, const int prim)
|
||||
{
|
||||
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
|
||||
/* Target emitter info */
|
||||
const int target_emitter = (prim >= 0) ? kernel_data_fetch(triangle_to_tree, prim) :
|
||||
kernel_data_fetch(light_to_tree, ~prim);
|
||||
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
target_emitter);
|
||||
const int target_leaf = kemitter->parent_index;
|
||||
ccl_global const KernelLightTreeNode *kleaf = &kernel_data_fetch(light_tree_nodes, target_leaf);
|
||||
uint bit_trail = kleaf->bit_trail;
|
||||
|
||||
int node_index = 0; /* root node */
|
||||
|
||||
float pdf = 1.0f;
|
||||
|
||||
/* Traverse the light tree until we reach the target leaf node */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (knode->child_index <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Interior node */
|
||||
const int left_index = node_index + 1;
|
||||
const int right_index = knode->child_index;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<false>(
|
||||
kg, P, N, 0, has_transmission, left_index, right_index, left_prob)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const bool go_left = (bit_trail & 1) == 0;
|
||||
bit_trail >>= 1;
|
||||
pdf *= go_left ? left_prob : (1.0f - left_prob);
|
||||
node_index = go_left ? left_index : right_index;
|
||||
|
||||
if (pdf == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
kernel_assert(node_index == target_leaf);
|
||||
|
||||
/* Iterate through leaf node to find the probability of sampling the target emitter. */
|
||||
float target_max_importance = 0.0f;
|
||||
float target_min_importance = 0.0f;
|
||||
float total_max_importance = 0.0f;
|
||||
float total_min_importance = 0.0f;
|
||||
int num_has_importance = 0;
|
||||
for (int i = 0; i < kleaf->num_prims; i++) {
|
||||
const int emitter = -kleaf->child_index + i;
|
||||
float max_importance, min_importance;
|
||||
light_tree_emitter_importance<false>(
|
||||
kg, P, N, 0, has_transmission, emitter, max_importance, min_importance);
|
||||
num_has_importance += (max_importance > 0);
|
||||
if (emitter == target_emitter) {
|
||||
target_max_importance = max_importance;
|
||||
target_min_importance = min_importance;
|
||||
}
|
||||
total_max_importance += max_importance;
|
||||
total_min_importance += min_importance;
|
||||
}
|
||||
|
||||
if (target_max_importance > 0.0f) {
|
||||
return pdf * 0.5f *
|
||||
(target_max_importance / total_max_importance +
|
||||
(total_min_importance > 0 ? target_min_importance / total_min_importance :
|
||||
1.0f / num_has_importance));
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
329
intern/cycles/kernel/light/triangle.h
Normal file
329
intern/cycles/kernel/light/triangle.h
Normal file
@@ -0,0 +1,329 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/geom/geom.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* returns true if the triangle is has motion blur or an instancing transform applied */
|
||||
ccl_device_inline bool triangle_world_space_vertices(
|
||||
KernelGlobals kg, int object, int prim, float time, float3 V[3])
|
||||
{
|
||||
bool has_motion = false;
|
||||
const int object_flag = kernel_data_fetch(object_flag, object);
|
||||
|
||||
if (object_flag & SD_OBJECT_HAS_VERTEX_MOTION && time >= 0.0f) {
|
||||
motion_triangle_vertices(kg, object, prim, time, V);
|
||||
has_motion = true;
|
||||
}
|
||||
else {
|
||||
triangle_vertices(kg, prim, V);
|
||||
}
|
||||
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
#ifdef __OBJECT_MOTION__
|
||||
float object_time = (time >= 0.0f) ? time : 0.5f;
|
||||
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
|
||||
#else
|
||||
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
|
||||
#endif
|
||||
V[0] = transform_point(&tfm, V[0]);
|
||||
V[1] = transform_point(&tfm, V[1]);
|
||||
V[2] = transform_point(&tfm, V[2]);
|
||||
has_motion = true;
|
||||
}
|
||||
return has_motion;
|
||||
}
|
||||
|
||||
ccl_device_inline float triangle_light_pdf_area_sampling(const float3 Ng, const float3 I, float t)
|
||||
{
|
||||
float cos_pi = fabsf(dot(Ng, I));
|
||||
|
||||
if (cos_pi == 0.0f)
|
||||
return 0.0f;
|
||||
|
||||
return t * t / cos_pi;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
float t)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* 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);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
const float3 N = cross(e0, e1);
|
||||
const float distance_to_plane = fabsf(dot(N, sd->I * t)) / dot(N, N);
|
||||
const float area = 0.5f * len(N);
|
||||
|
||||
float pdf;
|
||||
|
||||
if (longest_edge_squared > distance_to_plane * distance_to_plane) {
|
||||
/* sd contains the point on the light source
|
||||
* calculate Px, the point that we're shading */
|
||||
const float3 Px = sd->P + sd->I * t;
|
||||
const float3 v0_p = V[0] - Px;
|
||||
const float3 v1_p = V[1] - Px;
|
||||
const float3 v2_p = V[2] - Px;
|
||||
|
||||
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
|
||||
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
|
||||
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
|
||||
|
||||
const float alpha = fast_acosf(dot(u02, u01));
|
||||
const float beta = fast_acosf(-dot(u01, u12));
|
||||
const float gamma = fast_acosf(dot(u02, u12));
|
||||
const float solid_angle = alpha + beta + gamma - M_PI_F;
|
||||
|
||||
/* distribution_pdf_triangles is calculated over triangle area, but we're not sampling over
|
||||
* its area */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
else {
|
||||
pdf = 1.0f / solid_angle;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (UNLIKELY(area == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
pdf = triangle_light_pdf_area_sampling(sd->Ng, sd->I, t) / area;
|
||||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool triangle_light_sample(KernelGlobals kg,
|
||||
int prim,
|
||||
int object,
|
||||
float randu,
|
||||
float randv,
|
||||
float time,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* to the length of the edges of the triangle. */
|
||||
|
||||
float3 V[3];
|
||||
bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
const float3 N0 = cross(e0, e1);
|
||||
float Nl = 0.0f;
|
||||
ls->Ng = safe_normalize_len(N0, &Nl);
|
||||
const float area = 0.5f * Nl;
|
||||
|
||||
/* flip normal if necessary */
|
||||
const int object_flag = kernel_data_fetch(object_flag, object);
|
||||
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
|
||||
ls->Ng = -ls->Ng;
|
||||
}
|
||||
ls->eval_fac = 1.0f;
|
||||
ls->shader = kernel_data_fetch(tri_shader, prim);
|
||||
ls->object = object;
|
||||
ls->prim = prim;
|
||||
ls->lamp = LAMP_NONE;
|
||||
ls->shader |= SHADER_USE_MIS;
|
||||
ls->type = LIGHT_TRIANGLE;
|
||||
ls->group = object_lightgroup(kg, object);
|
||||
|
||||
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
|
||||
|
||||
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
|
||||
/* see James Arvo, "Stratified Sampling of Spherical Triangles"
|
||||
* http://www.graphics.cornell.edu/pubs/1995/Arv95c.pdf */
|
||||
|
||||
/* project the triangle to the unit sphere
|
||||
* and calculate its edges and angles */
|
||||
const float3 v0_p = V[0] - P;
|
||||
const float3 v1_p = V[1] - P;
|
||||
const float3 v2_p = V[2] - P;
|
||||
|
||||
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
|
||||
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
|
||||
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
|
||||
|
||||
const float3 A = safe_normalize(v0_p);
|
||||
const float3 B = safe_normalize(v1_p);
|
||||
const float3 C = safe_normalize(v2_p);
|
||||
|
||||
const float cos_alpha = dot(u02, u01);
|
||||
const float cos_beta = -dot(u01, u12);
|
||||
const float cos_gamma = dot(u02, u12);
|
||||
|
||||
/* calculate dihedral angles */
|
||||
const float alpha = fast_acosf(cos_alpha);
|
||||
const float beta = fast_acosf(cos_beta);
|
||||
const float gamma = fast_acosf(cos_gamma);
|
||||
/* the area of the unit spherical triangle = solid angle */
|
||||
const float solid_angle = alpha + beta + gamma - M_PI_F;
|
||||
|
||||
/* precompute a few things
|
||||
* these could be re-used to take several samples
|
||||
* as they are independent of randu/randv */
|
||||
const float cos_c = dot(A, B);
|
||||
const float sin_alpha = fast_sinf(alpha);
|
||||
const float product = sin_alpha * cos_c;
|
||||
|
||||
/* Select a random sub-area of the spherical triangle
|
||||
* and calculate the third vertex C_ of that new triangle */
|
||||
const float phi = randu * solid_angle - alpha;
|
||||
float s, t;
|
||||
fast_sincosf(phi, &s, &t);
|
||||
const float u = t - cos_alpha;
|
||||
const float v = s + product;
|
||||
|
||||
const float3 U = safe_normalize(C - dot(C, A) * A);
|
||||
|
||||
float q = 1.0f;
|
||||
const float det = ((v * s + u * t) * sin_alpha);
|
||||
if (det != 0.0f) {
|
||||
q = ((v * t - u * s) * cos_alpha - v) / det;
|
||||
}
|
||||
const float temp = max(1.0f - q * q, 0.0f);
|
||||
|
||||
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * U);
|
||||
|
||||
/* Finally, select a random point along the edge of the new triangle
|
||||
* That point on the spherical triangle is the sampled ray direction */
|
||||
const float z = 1.0f - randv * (1.0f - dot(C_, B));
|
||||
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
|
||||
|
||||
/* calculate intersection with the planar triangle */
|
||||
if (!ray_triangle_intersect(
|
||||
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) {
|
||||
ls->pdf = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
ls->P = P + ls->D * ls->t;
|
||||
|
||||
/* distribution_pdf_triangles is calculated over triangle area, but we're sampling over solid
|
||||
* angle */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
ls->pdf = 0.0f;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
ls->pdf = 1.0f / solid_angle;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (UNLIKELY(area == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
|
||||
* and Square" */
|
||||
float u = randu;
|
||||
float v = randv;
|
||||
if (v > u) {
|
||||
u *= 0.5f;
|
||||
v -= u;
|
||||
}
|
||||
else {
|
||||
v *= 0.5f;
|
||||
u -= v;
|
||||
}
|
||||
|
||||
const float t = 1.0f - u - v;
|
||||
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_sampling(ls->Ng, -ls->D, ls->t) / area;
|
||||
ls->u = u;
|
||||
ls->v = v;
|
||||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool triangle_light_tree_parameters(
|
||||
KernelGlobals kg,
|
||||
const ccl_global KernelLightTreeEmitter *kemitter,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const BoundingCone bcone,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (!in_volume_segment) {
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
|
||||
* worth the overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
}
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
const int object = kemitter->mesh_light.object_id;
|
||||
float3 vertices[3];
|
||||
triangle_world_space_vertices(kg, object, kemitter->prim_id, -1.0f, vertices);
|
||||
|
||||
bool shape_above_surface = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const float3 corner = vertices[i];
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
shape_above_surface |= dot(point_to_corner, N) > 0;
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -60,6 +60,7 @@ CCL_NAMESPACE_BEGIN
|
||||
#define __DENOISING_FEATURES__
|
||||
#define __DPDU__
|
||||
#define __HAIR__
|
||||
#define __LIGHT_TREE__
|
||||
#define __OBJECT_MOTION__
|
||||
#define __PASSES__
|
||||
#define __PATCH_EVAL__
|
||||
@@ -74,6 +75,11 @@ CCL_NAMESPACE_BEGIN
|
||||
#define __VISIBILITY_FLAG__
|
||||
#define __VOLUME__
|
||||
|
||||
/* TODO: solve internal compiler errors and enable light tree on HIP. */
|
||||
#ifdef __KERNEL_HIP__
|
||||
# undef __LIGHT_TREE__
|
||||
#endif
|
||||
|
||||
/* Device specific features */
|
||||
#ifdef WITH_OSL
|
||||
# define __OSL__
|
||||
@@ -160,7 +166,8 @@ enum PathTraceDimension {
|
||||
PRNG_VOLUME_SCATTER_DISTANCE = 5,
|
||||
PRNG_VOLUME_OFFSET = 6,
|
||||
PRNG_VOLUME_SHADE_OFFSET = 7,
|
||||
PRNG_VOLUME_PHASE_GUIDING = 8,
|
||||
PRNG_VOLUME_PHASE_GUIDING_DISTANCE = 8,
|
||||
PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR = 9,
|
||||
|
||||
/* Subsurface random walk bounces */
|
||||
PRNG_SUBSURFACE_BSDF = 0,
|
||||
@@ -208,21 +215,26 @@ enum PathRayFlag : uint32_t {
|
||||
PATH_RAY_SHADOW_TRANSPARENT = (1U << 9U),
|
||||
PATH_RAY_SHADOW = (PATH_RAY_SHADOW_OPAQUE | PATH_RAY_SHADOW_TRANSPARENT),
|
||||
|
||||
/* Special flag to tag unaligned BVH nodes.
|
||||
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
|
||||
* in the node (either it should be intersected as AABB or as OBBU). */
|
||||
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
|
||||
|
||||
/* Subset of flags used for ray visibility for intersection.
|
||||
*
|
||||
* NOTE: SHADOW_CATCHER macros below assume there are no more than
|
||||
* 16 visibility bits. */
|
||||
PATH_RAY_ALL_VISIBILITY = ((1U << 11U) - 1U),
|
||||
PATH_RAY_ALL_VISIBILITY = ((1U << 10U) - 1U),
|
||||
|
||||
/* Special flag to tag unaligned BVH nodes.
|
||||
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
|
||||
* in the node (either it should be intersected as AABB or as OBBU).
|
||||
* So this can overlap with path flags. */
|
||||
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Path flags.
|
||||
*/
|
||||
|
||||
/* Surface had transmission component at previous bounce. Used for light tree
|
||||
* traversal and culling to be consistent with MIS PDF at the next bounce. */
|
||||
PATH_RAY_MIS_HAD_TRANSMISSION = (1U << 10U),
|
||||
|
||||
/* Don't apply multiple importance sampling weights to emission from
|
||||
* lamp or surface hits, because they were not direct light sampled. */
|
||||
PATH_RAY_MIS_SKIP = (1U << 11U),
|
||||
@@ -342,7 +354,6 @@ typedef enum PassType {
|
||||
PASS_EMISSION,
|
||||
PASS_BACKGROUND,
|
||||
PASS_AO,
|
||||
PASS_SHADOW,
|
||||
PASS_DIFFUSE,
|
||||
PASS_DIFFUSE_DIRECT,
|
||||
PASS_DIFFUSE_INDIRECT,
|
||||
@@ -461,6 +472,16 @@ typedef enum ShaderFlag {
|
||||
SHADER_EXCLUDE_ANY)
|
||||
} ShaderFlag;
|
||||
|
||||
enum EmissionSampling {
|
||||
EMISSION_SAMPLING_NONE = 0,
|
||||
EMISSION_SAMPLING_AUTO = 1,
|
||||
EMISSION_SAMPLING_FRONT = 2,
|
||||
EMISSION_SAMPLING_BACK = 3,
|
||||
EMISSION_SAMPLING_FRONT_BACK = 4,
|
||||
|
||||
EMISSION_SAMPLING_NUM
|
||||
};
|
||||
|
||||
/* Light Type */
|
||||
|
||||
typedef enum LightType {
|
||||
@@ -774,14 +795,16 @@ enum ShaderDataFlag {
|
||||
SD_TRANSPARENT = (1 << 9),
|
||||
/* BSDF requires LCG for evaluation. */
|
||||
SD_BSDF_NEEDS_LCG = (1 << 10),
|
||||
/* BSDF has a transmissive component. */
|
||||
SD_BSDF_HAS_TRANSMISSION = (1 << 11),
|
||||
|
||||
SD_CLOSURE_FLAGS = (SD_EMISSION | SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSSRDF | SD_HOLDOUT |
|
||||
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG),
|
||||
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION),
|
||||
|
||||
/* Shader flags. */
|
||||
|
||||
/* direct light sample */
|
||||
SD_USE_MIS = (1 << 16),
|
||||
/* Use front side for direct light sampling. */
|
||||
SD_MIS_FRONT = (1 << 16),
|
||||
/* Has transparent shadow. */
|
||||
SD_HAS_TRANSPARENT_SHADOW = (1 << 17),
|
||||
/* Has volume shader. */
|
||||
@@ -810,12 +833,14 @@ enum ShaderDataFlag {
|
||||
SD_HAS_EMISSION = (1 << 29),
|
||||
/* Shader has raytracing */
|
||||
SD_HAS_RAYTRACE = (1 << 30),
|
||||
/* Use back side for direct light sampling. */
|
||||
SD_MIS_BACK = (1 << 31),
|
||||
|
||||
SD_SHADER_FLAGS = (SD_USE_MIS | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME | SD_HAS_ONLY_VOLUME |
|
||||
SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP | SD_VOLUME_EQUIANGULAR |
|
||||
SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP | SD_HAS_DISPLACEMENT |
|
||||
SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES | SD_HAS_EMISSION |
|
||||
SD_HAS_RAYTRACE)
|
||||
SD_SHADER_FLAGS = (SD_MIS_FRONT | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME |
|
||||
SD_HAS_ONLY_VOLUME | SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP |
|
||||
SD_VOLUME_EQUIANGULAR | SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP |
|
||||
SD_HAS_DISPLACEMENT | SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES |
|
||||
SD_HAS_EMISSION | SD_HAS_RAYTRACE | SD_MIS_BACK)
|
||||
};
|
||||
|
||||
/* Object flags. */
|
||||
@@ -1267,21 +1292,24 @@ static_assert_align(KernelCurveSegment, 8);
|
||||
typedef struct KernelSpotLight {
|
||||
float radius;
|
||||
float invarea;
|
||||
float spot_angle;
|
||||
float cos_half_spot_angle;
|
||||
float spot_smooth;
|
||||
float dir[3];
|
||||
packed_float3 dir;
|
||||
float pad;
|
||||
} KernelSpotLight;
|
||||
|
||||
/* PointLight is SpotLight with only radius and invarea being used. */
|
||||
|
||||
typedef struct KernelAreaLight {
|
||||
float axisu[3];
|
||||
packed_float3 axis_u;
|
||||
float len_u;
|
||||
packed_float3 axis_v;
|
||||
float len_v;
|
||||
packed_float3 dir;
|
||||
float invarea;
|
||||
float axisv[3];
|
||||
float tan_spread;
|
||||
float dir[3];
|
||||
float cot_half_spread;
|
||||
float normalize_spread;
|
||||
float pad[2];
|
||||
} KernelAreaLight;
|
||||
|
||||
typedef struct KernelDistantLight {
|
||||
@@ -1293,7 +1321,7 @@ typedef struct KernelDistantLight {
|
||||
|
||||
typedef struct KernelLight {
|
||||
int type;
|
||||
float co[3];
|
||||
packed_float3 co;
|
||||
int shader_id;
|
||||
float max_bounces;
|
||||
float random;
|
||||
@@ -1313,19 +1341,70 @@ static_assert_align(KernelLight, 16);
|
||||
typedef struct KernelLightDistribution {
|
||||
float totarea;
|
||||
int prim;
|
||||
union {
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
} mesh_light;
|
||||
struct {
|
||||
float pad;
|
||||
float size;
|
||||
} lamp;
|
||||
};
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
} mesh_light;
|
||||
} KernelLightDistribution;
|
||||
static_assert_align(KernelLightDistribution, 16);
|
||||
|
||||
/* Bounding box. */
|
||||
using BoundingBox = struct BoundingBox {
|
||||
packed_float3 min;
|
||||
packed_float3 max;
|
||||
};
|
||||
|
||||
using BoundingCone = struct BoundingCone {
|
||||
packed_float3 axis;
|
||||
float theta_o;
|
||||
float theta_e;
|
||||
};
|
||||
|
||||
typedef struct KernelLightTreeNode {
|
||||
/* Bounding box. */
|
||||
BoundingBox bbox;
|
||||
|
||||
/* Bounding cone. */
|
||||
BoundingCone bcone;
|
||||
|
||||
/* Energy. */
|
||||
float energy;
|
||||
|
||||
/* If this is 0 or less, we're at a leaf node
|
||||
* and the negative value indexes into the first child of the light array.
|
||||
* Otherwise, it's an index to the node's second child. */
|
||||
int child_index;
|
||||
int num_prims; /* leaf nodes need to know the number of primitives stored. */
|
||||
|
||||
/* Bit trail. */
|
||||
uint bit_trail;
|
||||
|
||||
/* Padding. */
|
||||
int pad;
|
||||
} KernelLightTreeNode;
|
||||
static_assert_align(KernelLightTreeNode, 16);
|
||||
|
||||
typedef struct KernelLightTreeEmitter {
|
||||
/* Bounding cone. */
|
||||
float theta_o;
|
||||
float theta_e;
|
||||
|
||||
/* Energy. */
|
||||
float energy;
|
||||
|
||||
/* prim_id denotes the location in the lights or triangles array. */
|
||||
int prim_id;
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
EmissionSampling emission_sampling;
|
||||
} mesh_light;
|
||||
|
||||
/* Parent. */
|
||||
int parent_index;
|
||||
} KernelLightTreeEmitter;
|
||||
static_assert_align(KernelLightTreeEmitter, 16);
|
||||
|
||||
typedef struct KernelParticle {
|
||||
int index;
|
||||
float age;
|
||||
@@ -1525,22 +1604,19 @@ enum KernelFeatureFlag : uint32_t {
|
||||
/* Light render passes. */
|
||||
KERNEL_FEATURE_LIGHT_PASSES = (1U << 21U),
|
||||
|
||||
/* Shadow render pass. */
|
||||
KERNEL_FEATURE_SHADOW_PASS = (1U << 22U),
|
||||
|
||||
/* AO. */
|
||||
KERNEL_FEATURE_AO_PASS = (1U << 23U),
|
||||
KERNEL_FEATURE_AO_ADDITIVE = (1U << 24U),
|
||||
KERNEL_FEATURE_AO_PASS = (1U << 22U),
|
||||
KERNEL_FEATURE_AO_ADDITIVE = (1U << 23U),
|
||||
KERNEL_FEATURE_AO = (KERNEL_FEATURE_AO_PASS | KERNEL_FEATURE_AO_ADDITIVE),
|
||||
|
||||
/* MNEE. */
|
||||
KERNEL_FEATURE_MNEE = (1U << 25U),
|
||||
KERNEL_FEATURE_MNEE = (1U << 24U),
|
||||
|
||||
/* Path guiding. */
|
||||
KERNEL_FEATURE_PATH_GUIDING = (1U << 26U),
|
||||
KERNEL_FEATURE_PATH_GUIDING = (1U << 25U),
|
||||
|
||||
/* OSL. */
|
||||
KERNEL_FEATURE_OSL = (1U << 27U),
|
||||
KERNEL_FEATURE_OSL = (1U << 26U),
|
||||
};
|
||||
|
||||
/* Shader node feature mask, to specialize shader evaluation for kernels. */
|
||||
|
@@ -25,6 +25,7 @@ set(SRC
|
||||
integrator.cpp
|
||||
jitter.cpp
|
||||
light.cpp
|
||||
light_tree.cpp
|
||||
mesh.cpp
|
||||
mesh_displace.cpp
|
||||
mesh_subdivision.cpp
|
||||
@@ -63,6 +64,7 @@ set(SRC_HEADERS
|
||||
image_vdb.h
|
||||
integrator.h
|
||||
light.h
|
||||
light_tree.h
|
||||
jitter.h
|
||||
mesh.h
|
||||
object.h
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "scene/background.h"
|
||||
#include "device/device.h"
|
||||
#include "scene/integrator.h"
|
||||
#include "scene/light.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/shader.h"
|
||||
#include "scene/shader_graph.h"
|
||||
|
@@ -187,7 +187,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
kfilm->pass_transmission_indirect = PASS_UNUSED;
|
||||
kfilm->pass_volume_direct = PASS_UNUSED;
|
||||
kfilm->pass_volume_indirect = PASS_UNUSED;
|
||||
kfilm->pass_shadow = PASS_UNUSED;
|
||||
kfilm->pass_lightgroup = PASS_UNUSED;
|
||||
|
||||
/* Mark passes as unused so that the kernel knows the pass is inaccessible. */
|
||||
@@ -295,9 +294,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
case PASS_AO:
|
||||
kfilm->pass_ao = kfilm->pass_stride;
|
||||
break;
|
||||
case PASS_SHADOW:
|
||||
kfilm->pass_shadow = kfilm->pass_stride;
|
||||
break;
|
||||
|
||||
case PASS_DIFFUSE_COLOR:
|
||||
kfilm->pass_diffuse_color = kfilm->pass_stride;
|
||||
@@ -727,10 +723,6 @@ uint Film::get_kernel_features(const Scene *scene) const
|
||||
kernel_features |= KERNEL_FEATURE_LIGHT_PASSES;
|
||||
}
|
||||
|
||||
if (pass_type == PASS_SHADOW) {
|
||||
kernel_features |= KERNEL_FEATURE_SHADOW_PASS;
|
||||
}
|
||||
|
||||
if (pass_type == PASS_AO) {
|
||||
kernel_features |= KERNEL_FEATURE_AO_PASS;
|
||||
}
|
||||
|
@@ -271,7 +271,7 @@ void Geometry::tag_update(Scene *scene, bool rebuild)
|
||||
else {
|
||||
foreach (Node *node, used_shaders) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->has_surface_emission) {
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
|
||||
break;
|
||||
}
|
||||
|
@@ -102,7 +102,8 @@ NODE_DEFINE(Integrator)
|
||||
SOCKET_FLOAT(adaptive_threshold, "Adaptive Threshold", 0.01f);
|
||||
SOCKET_INT(adaptive_min_samples, "Adaptive Min Samples", 0);
|
||||
|
||||
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.01f);
|
||||
SOCKET_BOOLEAN(use_light_tree, "Use light tree to optimize many light sampling", true);
|
||||
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.0f);
|
||||
|
||||
static NodeEnum sampling_pattern_enum;
|
||||
sampling_pattern_enum.insert("sobol_burley", SAMPLING_PATTERN_SOBOL_BURLEY);
|
||||
@@ -250,6 +251,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
|
||||
kintegrator->sampling_pattern = sampling_pattern;
|
||||
kintegrator->scrambling_distance = scrambling_distance;
|
||||
|
||||
kintegrator->use_light_tree = scene->integrator->use_light_tree;
|
||||
if (light_sampling_threshold > 0.0f) {
|
||||
kintegrator->light_inv_rr_threshold = 1.0f / light_sampling_threshold;
|
||||
}
|
||||
|
@@ -79,6 +79,7 @@ class Integrator : public Node {
|
||||
NODE_SOCKET_API(int, aa_samples)
|
||||
NODE_SOCKET_API(int, start_sample)
|
||||
|
||||
NODE_SOCKET_API(bool, use_light_tree)
|
||||
NODE_SOCKET_API(float, light_sampling_threshold)
|
||||
|
||||
NODE_SOCKET_API(bool, use_adaptive_sampling)
|
||||
|
@@ -110,6 +110,7 @@ NODE_DEFINE(Light)
|
||||
SOCKET_FLOAT(spread, "Spread", M_PI_F);
|
||||
|
||||
SOCKET_INT(map_resolution, "Map Resolution", 0);
|
||||
SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f);
|
||||
|
||||
SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F);
|
||||
SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f);
|
||||
@@ -162,7 +163,9 @@ bool Light::has_contribution(Scene *scene)
|
||||
if (light_type == LIGHT_BACKGROUND) {
|
||||
return true;
|
||||
}
|
||||
return (shader) ? shader->has_surface_emission : scene->default_light->has_surface_emission;
|
||||
|
||||
const Shader *effective_shader = (shader) ? shader : scene->default_light;
|
||||
return !is_zero(effective_shader->emission_estimate);
|
||||
}
|
||||
|
||||
/* Light Manager */
|
||||
@@ -256,7 +259,7 @@ bool LightManager::object_usable_as_light(Object *object)
|
||||
*/
|
||||
foreach (Node *node, geom->get_used_shaders()) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->get_use_mis() && shader->has_surface_emission) {
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -268,25 +271,14 @@ void LightManager::device_update_distribution(Device *,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
|
||||
/* Update CDF over lights. */
|
||||
progress.set_status("Updating Lights", "Computing distribution");
|
||||
|
||||
/* count */
|
||||
size_t num_lights = 0;
|
||||
size_t num_portals = 0;
|
||||
size_t num_background_lights = 0;
|
||||
/* Counts emissive triangles in the scene. */
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Object *object, scene->objects) {
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
@@ -295,29 +287,40 @@ void LightManager::device_update_distribution(Device *,
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Count triangles. */
|
||||
/* Count emissive triangles. */
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
size_t mesh_num_triangles = mesh->num_triangles();
|
||||
|
||||
for (size_t i = 0; i < mesh_num_triangles; i++) {
|
||||
int shader_index = mesh->get_shader()[i];
|
||||
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
|
||||
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
|
||||
scene->default_surface;
|
||||
|
||||
if (shader->get_use_mis() && shader->has_surface_emission) {
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
num_triangles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t num_distribution = num_triangles + num_lights;
|
||||
const size_t num_lights = kintegrator->num_lights;
|
||||
const size_t num_distribution = num_triangles + num_lights;
|
||||
|
||||
/* Distribution size. */
|
||||
kintegrator->num_distribution = num_distribution;
|
||||
|
||||
VLOG_INFO << "Total " << num_distribution << " of light distribution primitives.";
|
||||
|
||||
/* emission area */
|
||||
if (kintegrator->use_light_tree) {
|
||||
dscene->light_distribution.free();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Emission area. */
|
||||
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
|
||||
float totarea = 0.0f;
|
||||
|
||||
/* triangles */
|
||||
/* Triangles. */
|
||||
size_t offset = 0;
|
||||
int j = 0;
|
||||
|
||||
@@ -362,7 +365,7 @@ void LightManager::device_update_distribution(Device *,
|
||||
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
|
||||
scene->default_surface;
|
||||
|
||||
if (shader->get_use_mis() && shader->has_surface_emission) {
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
distribution[offset].totarea = totarea;
|
||||
distribution[offset].prim = i + mesh->prim_offset;
|
||||
distribution[offset].mesh_light.shader_flag = shader_flag;
|
||||
@@ -390,9 +393,9 @@ void LightManager::device_update_distribution(Device *,
|
||||
j++;
|
||||
}
|
||||
|
||||
float trianglearea = totarea;
|
||||
/* point lights */
|
||||
bool use_lamp_mis = false;
|
||||
const float trianglearea = totarea;
|
||||
|
||||
/* Lights. */
|
||||
int light_index = 0;
|
||||
|
||||
if (num_lights > 0) {
|
||||
@@ -403,24 +406,10 @@ void LightManager::device_update_distribution(Device *,
|
||||
|
||||
distribution[offset].totarea = totarea;
|
||||
distribution[offset].prim = ~light_index;
|
||||
distribution[offset].lamp.pad = 1.0f;
|
||||
distribution[offset].lamp.size = light->size;
|
||||
distribution[offset].mesh_light.object_id = OBJECT_NONE;
|
||||
distribution[offset].mesh_light.shader_flag = 0;
|
||||
totarea += lightarea;
|
||||
|
||||
if (light->light_type == LIGHT_DISTANT) {
|
||||
use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
|
||||
}
|
||||
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
|
||||
use_lamp_mis |= (light->size > 0.0f && light->use_mis);
|
||||
}
|
||||
else if (light->light_type == LIGHT_AREA) {
|
||||
use_lamp_mis |= light->use_mis;
|
||||
}
|
||||
else if (light->light_type == LIGHT_BACKGROUND) {
|
||||
num_background_lights++;
|
||||
background_mis |= light->use_mis;
|
||||
}
|
||||
|
||||
light_index++;
|
||||
offset++;
|
||||
}
|
||||
@@ -428,9 +417,9 @@ void LightManager::device_update_distribution(Device *,
|
||||
|
||||
/* normalize cumulative distribution functions */
|
||||
distribution[num_distribution].totarea = totarea;
|
||||
distribution[num_distribution].prim = 0.0f;
|
||||
distribution[num_distribution].lamp.pad = 0.0f;
|
||||
distribution[num_distribution].lamp.size = 0.0f;
|
||||
distribution[num_distribution].prim = 0;
|
||||
distribution[num_distribution].mesh_light.object_id = OBJECT_NONE;
|
||||
distribution[num_distribution].mesh_light.shader_flag = 0;
|
||||
|
||||
if (totarea > 0.0f) {
|
||||
for (size_t i = 0; i < num_distribution; i++)
|
||||
@@ -441,82 +430,221 @@ void LightManager::device_update_distribution(Device *,
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
/* update device */
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
KernelBackground *kbackground = &dscene->data.background;
|
||||
KernelFilm *kfilm = &dscene->data.film;
|
||||
/* Update integrator state. */
|
||||
kintegrator->use_direct_light = (totarea > 0.0f);
|
||||
|
||||
if (kintegrator->use_direct_light) {
|
||||
/* number of emissives */
|
||||
kintegrator->num_distribution = num_distribution;
|
||||
|
||||
/* precompute pdfs */
|
||||
kintegrator->pdf_triangles = 0.0f;
|
||||
kintegrator->pdf_lights = 0.0f;
|
||||
|
||||
/* sample one, with 0.5 probability of light or triangle */
|
||||
kintegrator->num_all_lights = num_lights;
|
||||
|
||||
if (trianglearea > 0.0f) {
|
||||
kintegrator->pdf_triangles = 1.0f / trianglearea;
|
||||
if (num_lights)
|
||||
kintegrator->pdf_triangles *= 0.5f;
|
||||
}
|
||||
/* Precompute pdfs for distribution sampling.
|
||||
* Sample one, with 0.5 probability of light or triangle. */
|
||||
kintegrator->distribution_pdf_triangles = 0.0f;
|
||||
kintegrator->distribution_pdf_lights = 0.0f;
|
||||
|
||||
if (trianglearea > 0.0f) {
|
||||
kintegrator->distribution_pdf_triangles = 1.0f / trianglearea;
|
||||
if (num_lights) {
|
||||
kintegrator->pdf_lights = 1.0f / num_lights;
|
||||
if (trianglearea > 0.0f)
|
||||
kintegrator->pdf_lights *= 0.5f;
|
||||
kintegrator->distribution_pdf_triangles *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_lights) {
|
||||
kintegrator->distribution_pdf_lights = 1.0f / num_lights;
|
||||
if (trianglearea > 0.0f) {
|
||||
kintegrator->distribution_pdf_lights *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy distribution to device. */
|
||||
dscene->light_distribution.copy_to_device();
|
||||
}
|
||||
|
||||
void LightManager::device_update_tree(Device *,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
|
||||
if (!kintegrator->use_light_tree) {
|
||||
dscene->light_tree_nodes.free();
|
||||
dscene->light_tree_emitters.free();
|
||||
dscene->light_to_tree.free();
|
||||
dscene->object_lookup_offset.free();
|
||||
dscene->triangle_to_tree.free();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update light tree. */
|
||||
progress.set_status("Updating Lights", "Computing tree");
|
||||
|
||||
/* Add both lights and emissive triangles to this vector for light tree construction. */
|
||||
vector<LightTreePrimitive> light_prims;
|
||||
light_prims.reserve(kintegrator->num_distribution);
|
||||
vector<LightTreePrimitive> distant_lights;
|
||||
distant_lights.reserve(kintegrator->num_distant_lights);
|
||||
vector<uint> object_lookup_offsets(scene->objects.size());
|
||||
|
||||
/* When we keep track of the light index, only contributing lights will be added to the device.
|
||||
* Therefore, we want to keep track of the light's index on the device.
|
||||
* However, we also need the light's index in the scene when we're constructing the tree. */
|
||||
int device_light_index = 0;
|
||||
int scene_light_index = 0;
|
||||
foreach (Light *light, scene->lights) {
|
||||
if (light->is_enabled) {
|
||||
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
|
||||
distant_lights.emplace_back(scene, ~device_light_index, scene_light_index);
|
||||
}
|
||||
else {
|
||||
light_prims.emplace_back(scene, ~device_light_index, scene_light_index);
|
||||
}
|
||||
|
||||
device_light_index++;
|
||||
}
|
||||
|
||||
kintegrator->use_lamp_mis = use_lamp_mis;
|
||||
scene_light_index++;
|
||||
}
|
||||
|
||||
/* bit of an ugly hack to compensate for emitting triangles influencing
|
||||
* amount of samples we get for this pass */
|
||||
kfilm->pass_shadow_scale = 1.0f;
|
||||
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
|
||||
size_t total_triangles = 0;
|
||||
int object_id = 0;
|
||||
foreach (Object *object, scene->objects) {
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
if (kintegrator->pdf_triangles != 0.0f)
|
||||
kfilm->pass_shadow_scale /= 0.5f;
|
||||
if (!object_usable_as_light(object)) {
|
||||
object_id++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (num_background_lights < num_lights)
|
||||
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
|
||||
object_lookup_offsets[object_id] = total_triangles;
|
||||
|
||||
/* CDF */
|
||||
dscene->light_distribution.copy_to_device();
|
||||
/* Count emissive triangles. */
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
size_t mesh_num_triangles = mesh->num_triangles();
|
||||
|
||||
/* Portals */
|
||||
if (num_portals > 0) {
|
||||
kbackground->portal_offset = light_index;
|
||||
kbackground->num_portals = num_portals;
|
||||
kbackground->portal_weight = 1.0f;
|
||||
for (size_t i = 0; i < mesh_num_triangles; i++) {
|
||||
int shader_index = mesh->get_shader()[i];
|
||||
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
|
||||
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
|
||||
scene->default_surface;
|
||||
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
light_prims.emplace_back(scene, i, object_id);
|
||||
}
|
||||
}
|
||||
|
||||
total_triangles += mesh_num_triangles;
|
||||
object_id++;
|
||||
}
|
||||
|
||||
/* Append distant lights to the end of `light_prims` */
|
||||
std::move(distant_lights.begin(), distant_lights.end(), std::back_inserter(light_prims));
|
||||
|
||||
/* Update integrator state. */
|
||||
kintegrator->use_direct_light = !light_prims.empty();
|
||||
|
||||
/* TODO: For now, we'll start with a smaller number of max lights in a node.
|
||||
* More benchmarking is needed to determine what number works best. */
|
||||
LightTree light_tree(light_prims, kintegrator->num_distant_lights, 8);
|
||||
|
||||
/* We want to create separate arrays corresponding to triangles and lights,
|
||||
* which will be used to index back into the light tree for PDF calculations. */
|
||||
const size_t num_lights = kintegrator->num_lights;
|
||||
uint *light_array = dscene->light_to_tree.alloc(num_lights);
|
||||
uint *object_offsets = dscene->object_lookup_offset.alloc(object_lookup_offsets.size());
|
||||
uint *triangle_array = dscene->triangle_to_tree.alloc(total_triangles);
|
||||
|
||||
for (int i = 0; i < object_lookup_offsets.size(); i++) {
|
||||
object_offsets[i] = object_lookup_offsets[i];
|
||||
}
|
||||
|
||||
/* First initialize the light tree's nodes. */
|
||||
const vector<LightTreeNode> &linearized_bvh = light_tree.get_nodes();
|
||||
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(linearized_bvh.size());
|
||||
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(
|
||||
light_prims.size());
|
||||
for (int index = 0; index < linearized_bvh.size(); index++) {
|
||||
const LightTreeNode &node = linearized_bvh[index];
|
||||
|
||||
light_tree_nodes[index].energy = node.energy;
|
||||
|
||||
light_tree_nodes[index].bbox.min = node.bbox.min;
|
||||
light_tree_nodes[index].bbox.max = node.bbox.max;
|
||||
|
||||
light_tree_nodes[index].bcone.axis = node.bcone.axis;
|
||||
light_tree_nodes[index].bcone.theta_o = node.bcone.theta_o;
|
||||
light_tree_nodes[index].bcone.theta_e = node.bcone.theta_e;
|
||||
|
||||
light_tree_nodes[index].bit_trail = node.bit_trail;
|
||||
light_tree_nodes[index].num_prims = node.num_prims;
|
||||
|
||||
/* Here we need to make a distinction between interior and leaf nodes. */
|
||||
if (node.is_leaf()) {
|
||||
light_tree_nodes[index].child_index = -node.first_prim_index;
|
||||
|
||||
for (int i = 0; i < node.num_prims; i++) {
|
||||
int emitter_index = i + node.first_prim_index;
|
||||
LightTreePrimitive &prim = light_prims[emitter_index];
|
||||
|
||||
light_tree_emitters[emitter_index].energy = prim.energy;
|
||||
light_tree_emitters[emitter_index].theta_o = prim.bcone.theta_o;
|
||||
light_tree_emitters[emitter_index].theta_e = prim.bcone.theta_e;
|
||||
|
||||
if (prim.is_triangle()) {
|
||||
light_tree_emitters[emitter_index].mesh_light.object_id = prim.object_id;
|
||||
|
||||
int shader_flag = 0;
|
||||
Object *object = scene->objects[prim.object_id];
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
Shader *shader = static_cast<Shader *>(
|
||||
mesh->get_used_shaders()[mesh->get_shader()[prim.prim_id]]);
|
||||
|
||||
if (!(object->get_visibility() & PATH_RAY_CAMERA)) {
|
||||
shader_flag |= SHADER_EXCLUDE_CAMERA;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_DIFFUSE)) {
|
||||
shader_flag |= SHADER_EXCLUDE_DIFFUSE;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_GLOSSY)) {
|
||||
shader_flag |= SHADER_EXCLUDE_GLOSSY;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_TRANSMIT)) {
|
||||
shader_flag |= SHADER_EXCLUDE_TRANSMIT;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_VOLUME_SCATTER)) {
|
||||
shader_flag |= SHADER_EXCLUDE_SCATTER;
|
||||
}
|
||||
if (!(object->get_is_shadow_catcher())) {
|
||||
shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER;
|
||||
}
|
||||
|
||||
light_tree_emitters[emitter_index].prim_id = prim.prim_id + mesh->prim_offset;
|
||||
light_tree_emitters[emitter_index].mesh_light.shader_flag = shader_flag;
|
||||
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
|
||||
shader->emission_sampling;
|
||||
triangle_array[prim.prim_id + object_lookup_offsets[prim.object_id]] = emitter_index;
|
||||
}
|
||||
else {
|
||||
light_tree_emitters[emitter_index].prim_id = prim.prim_id;
|
||||
light_tree_emitters[emitter_index].mesh_light.shader_flag = 0;
|
||||
light_tree_emitters[emitter_index].mesh_light.object_id = OBJECT_NONE;
|
||||
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
|
||||
EMISSION_SAMPLING_FRONT_BACK;
|
||||
light_array[~prim.prim_id] = emitter_index;
|
||||
}
|
||||
|
||||
light_tree_emitters[emitter_index].parent_index = index;
|
||||
}
|
||||
}
|
||||
else {
|
||||
kbackground->num_portals = 0;
|
||||
kbackground->portal_offset = 0;
|
||||
kbackground->portal_weight = 0.0f;
|
||||
light_tree_nodes[index].child_index = node.right_child_index;
|
||||
}
|
||||
|
||||
/* Map */
|
||||
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
|
||||
}
|
||||
else {
|
||||
dscene->light_distribution.free();
|
||||
|
||||
kintegrator->num_distribution = 0;
|
||||
kintegrator->num_all_lights = 0;
|
||||
kintegrator->pdf_triangles = 0.0f;
|
||||
kintegrator->pdf_lights = 0.0f;
|
||||
kintegrator->use_lamp_mis = false;
|
||||
|
||||
kbackground->num_portals = 0;
|
||||
kbackground->portal_offset = 0;
|
||||
kbackground->portal_weight = 0.0f;
|
||||
kbackground->sun_weight = 0.0f;
|
||||
kbackground->map_weight = 0.0f;
|
||||
|
||||
kfilm->pass_shadow_scale = 1.0f;
|
||||
}
|
||||
/* Copy arrays to device. */
|
||||
dscene->light_tree_nodes.copy_to_device();
|
||||
dscene->light_tree_emitters.copy_to_device();
|
||||
dscene->light_to_tree.copy_to_device();
|
||||
dscene->object_lookup_offset.copy_to_device();
|
||||
dscene->triangle_to_tree.copy_to_device();
|
||||
}
|
||||
|
||||
static void background_cdf(
|
||||
@@ -564,31 +692,34 @@ void LightManager::device_update_background(Device *device,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
KernelBackground *kbackground = &dscene->data.background;
|
||||
Light *background_light = NULL;
|
||||
|
||||
bool background_mis = false;
|
||||
|
||||
/* find background light */
|
||||
foreach (Light *light, scene->lights) {
|
||||
if (light->light_type == LIGHT_BACKGROUND) {
|
||||
if (light->light_type == LIGHT_BACKGROUND && light->is_enabled) {
|
||||
background_light = light;
|
||||
break;
|
||||
background_mis |= light->use_mis;
|
||||
}
|
||||
}
|
||||
|
||||
kbackground->portal_weight = kintegrator->num_portals > 0 ? 1.0f : 0.0f;
|
||||
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
|
||||
kbackground->sun_weight = 0.0f;
|
||||
|
||||
/* no background light found, signal renderer to skip sampling */
|
||||
if (!background_light || !background_light->is_enabled) {
|
||||
kbackground->map_res_x = 0;
|
||||
kbackground->map_res_y = 0;
|
||||
kbackground->map_weight = 0.0f;
|
||||
kbackground->sun_weight = 0.0f;
|
||||
kbackground->use_mis = (kbackground->portal_weight > 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
progress.set_status("Updating Lights", "Importance map");
|
||||
|
||||
assert(dscene->data.integrator.use_direct_light);
|
||||
|
||||
int2 environment_res = make_int2(0, 0);
|
||||
Shader *shader = scene->background->get_shader(scene);
|
||||
int num_suns = 0;
|
||||
@@ -632,6 +763,7 @@ void LightManager::device_update_background(Device *device,
|
||||
kbackground->sun = make_float4(
|
||||
sun_direction.x, sun_direction.y, sun_direction.z, half_angle);
|
||||
|
||||
/* empirical value */
|
||||
kbackground->sun_weight = 4.0f;
|
||||
environment_res.x = max(environment_res.x, 512);
|
||||
environment_res.y = max(environment_res.y, 256);
|
||||
@@ -701,6 +833,8 @@ void LightManager::device_update_background(Device *device,
|
||||
float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y;
|
||||
marg_cdf[res.y].x = cdf_total;
|
||||
|
||||
background_light->set_average_radiance(cdf_total * M_PI_2_F);
|
||||
|
||||
if (cdf_total > 0.0f)
|
||||
for (int i = 1; i < res.y; i++)
|
||||
marg_cdf[i].y /= cdf_total;
|
||||
@@ -714,27 +848,91 @@ void LightManager::device_update_background(Device *device,
|
||||
dscene->light_background_conditional_cdf.copy_to_device();
|
||||
}
|
||||
|
||||
void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *scene)
|
||||
void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
{
|
||||
int num_scene_lights = scene->lights.size();
|
||||
/* Counts lights in the scene. */
|
||||
size_t num_lights = 0;
|
||||
size_t num_portals = 0;
|
||||
size_t num_background_lights = 0;
|
||||
size_t num_distant_lights = 0;
|
||||
bool use_light_mis = false;
|
||||
|
||||
int num_lights = 0;
|
||||
foreach (Light *light, scene->lights) {
|
||||
if (light->is_enabled || light->is_portal) {
|
||||
if (light->is_enabled) {
|
||||
num_lights++;
|
||||
|
||||
if (light->light_type == LIGHT_DISTANT) {
|
||||
num_distant_lights++;
|
||||
}
|
||||
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
|
||||
use_light_mis |= (light->size > 0.0f && light->use_mis);
|
||||
}
|
||||
else if (light->light_type == LIGHT_AREA) {
|
||||
use_light_mis |= light->use_mis;
|
||||
}
|
||||
else if (light->light_type == LIGHT_BACKGROUND) {
|
||||
num_distant_lights++;
|
||||
num_background_lights++;
|
||||
}
|
||||
}
|
||||
if (light->is_portal) {
|
||||
num_portals++;
|
||||
}
|
||||
}
|
||||
|
||||
KernelLight *klights = dscene->lights.alloc(num_lights);
|
||||
/* Update integrator settings. */
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
kintegrator->use_light_tree = scene->integrator->get_use_light_tree() &&
|
||||
device->info.has_light_tree;
|
||||
kintegrator->num_lights = num_lights;
|
||||
kintegrator->num_distant_lights = num_distant_lights;
|
||||
kintegrator->num_background_lights = num_background_lights;
|
||||
kintegrator->use_light_mis = use_light_mis;
|
||||
|
||||
if (num_lights == 0) {
|
||||
VLOG_WORK << "No effective light, ignoring points update.";
|
||||
return;
|
||||
}
|
||||
kintegrator->num_portals = num_portals;
|
||||
kintegrator->portal_offset = num_lights;
|
||||
|
||||
/* Create KernelLight for every portal and enabled light in the scene. */
|
||||
KernelLight *klights = dscene->lights.alloc(num_lights + num_portals);
|
||||
|
||||
int light_index = 0;
|
||||
int portal_index = num_lights;
|
||||
|
||||
foreach (Light *light, scene->lights) {
|
||||
/* Consider moving portals update to their own function
|
||||
* keeping this one more manageable. */
|
||||
if (light->is_portal) {
|
||||
assert(light->light_type == LIGHT_AREA);
|
||||
|
||||
float3 extentu = light->axisu * (light->sizeu * light->size);
|
||||
float3 extentv = light->axisv * (light->sizev * light->size);
|
||||
|
||||
float len_u, len_v;
|
||||
float3 axis_u = normalize_len(extentu, &len_u);
|
||||
float3 axis_v = normalize_len(extentv, &len_v);
|
||||
float area = len_u * len_v;
|
||||
if (light->round) {
|
||||
area *= -M_PI_4_F;
|
||||
}
|
||||
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
|
||||
float3 dir = light->dir;
|
||||
|
||||
dir = safe_normalize(dir);
|
||||
|
||||
klights[portal_index].co = light->co;
|
||||
klights[portal_index].area.axis_u = axis_u;
|
||||
klights[portal_index].area.len_u = len_u;
|
||||
klights[portal_index].area.axis_v = axis_v;
|
||||
klights[portal_index].area.len_v = len_v;
|
||||
klights[portal_index].area.invarea = invarea;
|
||||
klights[portal_index].area.dir = dir;
|
||||
klights[portal_index].tfm = light->tfm;
|
||||
klights[portal_index].itfm = transform_inverse(light->tfm);
|
||||
|
||||
portal_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!light->is_enabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -781,10 +979,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
if (light->use_mis && radius > 0.0f)
|
||||
shader_id |= SHADER_USE_MIS;
|
||||
|
||||
klights[light_index].co[0] = co.x;
|
||||
klights[light_index].co[1] = co.y;
|
||||
klights[light_index].co[2] = co.z;
|
||||
|
||||
klights[light_index].co = co;
|
||||
klights[light_index].spot.radius = radius;
|
||||
klights[light_index].spot.invarea = invarea;
|
||||
}
|
||||
@@ -803,10 +998,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
if (light->use_mis && area > 0.0f)
|
||||
shader_id |= SHADER_USE_MIS;
|
||||
|
||||
klights[light_index].co[0] = dir.x;
|
||||
klights[light_index].co[1] = dir.y;
|
||||
klights[light_index].co[2] = dir.z;
|
||||
|
||||
klights[light_index].co = dir;
|
||||
klights[light_index].distant.invarea = invarea;
|
||||
klights[light_index].distant.radius = radius;
|
||||
klights[light_index].distant.cosangle = cosangle;
|
||||
@@ -814,6 +1006,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
else if (light->light_type == LIGHT_BACKGROUND) {
|
||||
uint visibility = scene->background->get_visibility();
|
||||
|
||||
dscene->data.background.light_index = light_index;
|
||||
|
||||
shader_id &= ~SHADER_AREA_LIGHT;
|
||||
shader_id |= SHADER_USE_MIS;
|
||||
|
||||
@@ -831,44 +1025,41 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
}
|
||||
}
|
||||
else if (light->light_type == LIGHT_AREA) {
|
||||
float3 axisu = light->axisu * (light->sizeu * light->size);
|
||||
float3 axisv = light->axisv * (light->sizev * light->size);
|
||||
float area = len(axisu) * len(axisv);
|
||||
float3 extentu = light->axisu * (light->sizeu * light->size);
|
||||
float3 extentv = light->axisv * (light->sizev * light->size);
|
||||
|
||||
float len_u, len_v;
|
||||
float3 axis_u = normalize_len(extentu, &len_u);
|
||||
float3 axis_v = normalize_len(extentv, &len_v);
|
||||
float area = len_u * len_v;
|
||||
if (light->round) {
|
||||
area *= -M_PI_4_F;
|
||||
}
|
||||
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
|
||||
float3 dir = light->dir;
|
||||
|
||||
/* Convert from spread angle 0..180 to 90..0, clamping to a minimum
|
||||
* angle to avoid excessive noise. */
|
||||
const float min_spread_angle = 1.0f * M_PI_F / 180.0f;
|
||||
const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle));
|
||||
/* Clamping to a minimum angle to avoid excessive noise. */
|
||||
const float min_spread = 1.0f * M_PI_F / 180.0f;
|
||||
const float half_spread = 0.5f * max(light->spread, min_spread);
|
||||
/* cot_half_spread is h in D10594#269626 */
|
||||
const float cot_half_spread = tanf(M_PI_2_F - half_spread);
|
||||
/* Normalization computed using:
|
||||
* integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */
|
||||
const float tan_spread = tanf(spread_angle);
|
||||
const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread);
|
||||
* integrate cos(x) * (1 - tan(x) / tan(a)) * sin(x) from x = 0 to a, a being half_spread */
|
||||
const float normalize_spread = 1.0f / (1.0f - half_spread * cot_half_spread);
|
||||
|
||||
dir = safe_normalize(dir);
|
||||
|
||||
if (light->use_mis && area != 0.0f)
|
||||
shader_id |= SHADER_USE_MIS;
|
||||
|
||||
klights[light_index].co[0] = co.x;
|
||||
klights[light_index].co[1] = co.y;
|
||||
klights[light_index].co[2] = co.z;
|
||||
|
||||
klights[light_index].area.axisu[0] = axisu.x;
|
||||
klights[light_index].area.axisu[1] = axisu.y;
|
||||
klights[light_index].area.axisu[2] = axisu.z;
|
||||
klights[light_index].area.axisv[0] = axisv.x;
|
||||
klights[light_index].area.axisv[1] = axisv.y;
|
||||
klights[light_index].area.axisv[2] = axisv.z;
|
||||
klights[light_index].co = co;
|
||||
klights[light_index].area.axis_u = axis_u;
|
||||
klights[light_index].area.len_u = len_u;
|
||||
klights[light_index].area.axis_v = axis_v;
|
||||
klights[light_index].area.len_v = len_v;
|
||||
klights[light_index].area.invarea = invarea;
|
||||
klights[light_index].area.dir[0] = dir.x;
|
||||
klights[light_index].area.dir[1] = dir.y;
|
||||
klights[light_index].area.dir[2] = dir.z;
|
||||
klights[light_index].area.tan_spread = tan_spread;
|
||||
klights[light_index].area.dir = dir;
|
||||
klights[light_index].area.cot_half_spread = cot_half_spread;
|
||||
klights[light_index].area.normalize_spread = normalize_spread;
|
||||
}
|
||||
else if (light->light_type == LIGHT_SPOT) {
|
||||
@@ -876,8 +1067,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
|
||||
float radius = light->size;
|
||||
float invarea = (radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : 1.0f;
|
||||
float spot_angle = cosf(light->spot_angle * 0.5f);
|
||||
float spot_smooth = (1.0f - spot_angle) * light->spot_smooth;
|
||||
float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
|
||||
float spot_smooth = (1.0f - cos_half_spot_angle) * light->spot_smooth;
|
||||
float3 dir = light->dir;
|
||||
|
||||
dir = safe_normalize(dir);
|
||||
@@ -885,17 +1076,12 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
if (light->use_mis && radius > 0.0f)
|
||||
shader_id |= SHADER_USE_MIS;
|
||||
|
||||
klights[light_index].co[0] = co.x;
|
||||
klights[light_index].co[1] = co.y;
|
||||
klights[light_index].co[2] = co.z;
|
||||
|
||||
klights[light_index].co = co;
|
||||
klights[light_index].spot.radius = radius;
|
||||
klights[light_index].spot.invarea = invarea;
|
||||
klights[light_index].spot.spot_angle = spot_angle;
|
||||
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
|
||||
klights[light_index].spot.spot_smooth = spot_smooth;
|
||||
klights[light_index].spot.dir[0] = dir.x;
|
||||
klights[light_index].spot.dir[1] = dir.y;
|
||||
klights[light_index].spot.dir[2] = dir.z;
|
||||
klights[light_index].spot.dir = dir;
|
||||
}
|
||||
|
||||
klights[light_index].shader_id = shader_id;
|
||||
@@ -918,49 +1104,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
|
||||
light_index++;
|
||||
}
|
||||
|
||||
/* TODO(sergey): Consider moving portals update to their own function
|
||||
* keeping this one more manageable.
|
||||
*/
|
||||
foreach (Light *light, scene->lights) {
|
||||
if (!light->is_portal)
|
||||
continue;
|
||||
assert(light->light_type == LIGHT_AREA);
|
||||
|
||||
float3 co = light->co;
|
||||
float3 axisu = light->axisu * (light->sizeu * light->size);
|
||||
float3 axisv = light->axisv * (light->sizev * light->size);
|
||||
float area = len(axisu) * len(axisv);
|
||||
if (light->round) {
|
||||
area *= -M_PI_4_F;
|
||||
}
|
||||
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
|
||||
float3 dir = light->dir;
|
||||
|
||||
dir = safe_normalize(dir);
|
||||
|
||||
klights[light_index].co[0] = co.x;
|
||||
klights[light_index].co[1] = co.y;
|
||||
klights[light_index].co[2] = co.z;
|
||||
|
||||
klights[light_index].area.axisu[0] = axisu.x;
|
||||
klights[light_index].area.axisu[1] = axisu.y;
|
||||
klights[light_index].area.axisu[2] = axisu.z;
|
||||
klights[light_index].area.axisv[0] = axisv.x;
|
||||
klights[light_index].area.axisv[1] = axisv.y;
|
||||
klights[light_index].area.axisv[2] = axisv.z;
|
||||
klights[light_index].area.invarea = invarea;
|
||||
klights[light_index].area.dir[0] = dir.x;
|
||||
klights[light_index].area.dir[1] = dir.y;
|
||||
klights[light_index].area.dir[2] = dir.z;
|
||||
klights[light_index].tfm = light->tfm;
|
||||
klights[light_index].itfm = transform_inverse(light->tfm);
|
||||
|
||||
light_index++;
|
||||
}
|
||||
|
||||
VLOG_INFO << "Number of lights sent to the device: " << light_index;
|
||||
|
||||
VLOG_INFO << "Number of lights without contribution: " << num_scene_lights - light_index;
|
||||
VLOG_INFO << "Number of lights sent to the device: " << num_lights;
|
||||
|
||||
dscene->lights.copy_to_device();
|
||||
}
|
||||
@@ -986,11 +1130,7 @@ void LightManager::device_update(Device *device,
|
||||
|
||||
device_free(device, dscene, need_update_background);
|
||||
|
||||
device_update_points(device, dscene, scene);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
device_update_distribution(device, dscene, scene, progress);
|
||||
device_update_lights(device, dscene, scene);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
@@ -1000,6 +1140,14 @@ void LightManager::device_update(Device *device,
|
||||
return;
|
||||
}
|
||||
|
||||
device_update_distribution(device, dscene, scene, progress);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
device_update_tree(device, dscene, scene, progress);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
device_update_ies(dscene);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
@@ -1010,6 +1158,12 @@ void LightManager::device_update(Device *device,
|
||||
|
||||
void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background)
|
||||
{
|
||||
/* to-do: check if the light tree member variables need to be wrapped in a conditional too*/
|
||||
dscene->light_tree_nodes.free();
|
||||
dscene->light_tree_emitters.free();
|
||||
dscene->light_to_tree.free();
|
||||
dscene->triangle_to_tree.free();
|
||||
|
||||
dscene->light_distribution.free();
|
||||
dscene->lights.free();
|
||||
if (free_background) {
|
||||
|
@@ -10,6 +10,7 @@
|
||||
|
||||
/* included as Light::set_shader defined through NODE_SOCKET_API does not select
|
||||
* the right Node::set overload as it does not know that Shader is a Node */
|
||||
#include "scene/light_tree.h"
|
||||
#include "scene/shader.h"
|
||||
|
||||
#include "util/ies.h"
|
||||
@@ -50,6 +51,7 @@ class Light : public Node {
|
||||
NODE_SOCKET_API(Transform, tfm)
|
||||
|
||||
NODE_SOCKET_API(int, map_resolution)
|
||||
NODE_SOCKET_API(float, average_radiance)
|
||||
|
||||
NODE_SOCKET_API(float, spot_angle)
|
||||
NODE_SOCKET_API(float, spot_smooth)
|
||||
@@ -127,11 +129,12 @@ class LightManager {
|
||||
*/
|
||||
void test_enabled_lights(Scene *scene);
|
||||
|
||||
void device_update_points(Device *device, DeviceScene *dscene, Scene *scene);
|
||||
void device_update_lights(Device *device, DeviceScene *dscene, Scene *scene);
|
||||
void device_update_distribution(Device *device,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress);
|
||||
void device_update_tree(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
|
||||
void device_update_background(Device *device,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
|
390
intern/cycles/scene/light_tree.cpp
Normal file
390
intern/cycles/scene/light_tree.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "scene/light_tree.h"
|
||||
#include "scene/mesh.h"
|
||||
#include "scene/object.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
float OrientationBounds::calculate_measure() const
|
||||
{
|
||||
float theta_w = fminf(M_PI_F, theta_o + theta_e);
|
||||
float cos_theta_o = cosf(theta_o);
|
||||
float sin_theta_o = sinf(theta_o);
|
||||
|
||||
return M_2PI_F * (1 - cos_theta_o) +
|
||||
M_PI_2_F * (2 * theta_w * sin_theta_o - cosf(theta_o - 2 * theta_w) -
|
||||
2 * theta_o * sin_theta_o + cos_theta_o);
|
||||
}
|
||||
|
||||
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b)
|
||||
{
|
||||
if (is_zero(cone_a.axis)) {
|
||||
return cone_b;
|
||||
}
|
||||
if (is_zero(cone_b.axis)) {
|
||||
return cone_a;
|
||||
}
|
||||
|
||||
/* Set cone a to always have the greater theta_o. */
|
||||
const OrientationBounds *a = &cone_a;
|
||||
const OrientationBounds *b = &cone_b;
|
||||
if (cone_b.theta_o > cone_a.theta_o) {
|
||||
a = &cone_b;
|
||||
b = &cone_a;
|
||||
}
|
||||
|
||||
float theta_d = safe_acosf(dot(a->axis, b->axis));
|
||||
float theta_e = fmaxf(a->theta_e, b->theta_e);
|
||||
|
||||
/* Return axis and theta_o of a if it already contains b. */
|
||||
/* This should also be called when b is empty. */
|
||||
if (a->theta_o >= fminf(M_PI_F, theta_d + b->theta_o)) {
|
||||
return OrientationBounds({a->axis, a->theta_o, theta_e});
|
||||
}
|
||||
|
||||
/* Compute new theta_o that contains both a and b. */
|
||||
float theta_o = (theta_d + a->theta_o + b->theta_o) * 0.5f;
|
||||
|
||||
if (theta_o >= M_PI_F) {
|
||||
return OrientationBounds({a->axis, M_PI_F, theta_e});
|
||||
}
|
||||
|
||||
/* Rotate new axis to be between a and b. */
|
||||
float theta_r = theta_o - a->theta_o;
|
||||
float3 new_axis = rotate_around_axis(a->axis, cross(a->axis, b->axis), theta_r);
|
||||
new_axis = normalize(new_axis);
|
||||
|
||||
return OrientationBounds({new_axis, theta_o, theta_e});
|
||||
}
|
||||
|
||||
LightTreePrimitive::LightTreePrimitive(Scene *scene, int prim_id, int object_id)
|
||||
: prim_id(prim_id), object_id(object_id)
|
||||
{
|
||||
bcone = OrientationBounds::empty;
|
||||
bbox = BoundBox::empty;
|
||||
|
||||
if (is_triangle()) {
|
||||
float3 vertices[3];
|
||||
Object *object = scene->objects[object_id];
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
Mesh::Triangle triangle = mesh->get_triangle(prim_id);
|
||||
Shader *shader = static_cast<Shader *>(mesh->get_used_shaders()[mesh->get_shader()[prim_id]]);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vertices[i] = mesh->get_verts()[triangle.v[i]];
|
||||
}
|
||||
|
||||
/* 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->get_tfm();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vertices[i] = transform_point(&tfm, vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: need a better way to handle this when textures are used. */
|
||||
float area = triangle_area(vertices[0], vertices[1], vertices[2]);
|
||||
energy = area * average(shader->emission_estimate);
|
||||
|
||||
/* NOTE: the original implementation used the bounding box centroid, but primitive centroid
|
||||
* seems to work fine */
|
||||
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
|
||||
|
||||
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
|
||||
/* Front only. */
|
||||
bcone.axis = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
bcone.theta_o = 0;
|
||||
}
|
||||
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
|
||||
/* Back only. */
|
||||
bcone.axis = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
bcone.theta_o = 0;
|
||||
}
|
||||
else {
|
||||
/* Double sided: any vector in the plane. */
|
||||
bcone.axis = safe_normalize(vertices[0] - vertices[1]);
|
||||
bcone.theta_o = M_PI_2_F;
|
||||
}
|
||||
bcone.theta_e = M_PI_2_F;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
bbox.grow(vertices[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Light *lamp = scene->lights[object_id];
|
||||
LightType type = lamp->get_light_type();
|
||||
const float size = lamp->get_size();
|
||||
float3 strength = lamp->get_strength();
|
||||
|
||||
centroid = scene->lights[object_id]->get_co();
|
||||
bcone.axis = normalize(lamp->get_dir());
|
||||
|
||||
if (type == LIGHT_AREA) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = lamp->get_spread() * 0.5f;
|
||||
|
||||
/* For an area light, sizeu and sizev determine the 2 dimensions of the area light,
|
||||
* while axisu and axisv determine the orientation of the 2 dimensions.
|
||||
* We want to add all 4 corners to our bounding box. */
|
||||
const float3 half_extentu = 0.5f * lamp->get_sizeu() * lamp->get_axisu() * size;
|
||||
const float3 half_extentv = 0.5f * lamp->get_sizev() * lamp->get_axisv() * size;
|
||||
bbox.grow(centroid + half_extentu + half_extentv);
|
||||
bbox.grow(centroid + half_extentu - half_extentv);
|
||||
bbox.grow(centroid - half_extentu + half_extentv);
|
||||
bbox.grow(centroid - half_extentu - half_extentv);
|
||||
|
||||
strength *= 0.25f; /* eval_fac scaling in `area.h` */
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
bcone.theta_o = M_PI_F;
|
||||
bcone.theta_e = M_PI_2_F;
|
||||
|
||||
/* Point and spot lights can emit light from any point within its radius. */
|
||||
const float3 radius = make_float3(size);
|
||||
bbox.grow(centroid - radius);
|
||||
bbox.grow(centroid + radius);
|
||||
|
||||
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
|
||||
}
|
||||
else if (type == LIGHT_SPOT) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = lamp->get_spot_angle() * 0.5f;
|
||||
|
||||
/* Point and spot lights can emit light from any point within its radius. */
|
||||
const float3 radius = make_float3(size);
|
||||
bbox.grow(centroid - radius);
|
||||
bbox.grow(centroid + radius);
|
||||
|
||||
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
|
||||
}
|
||||
else if (type == LIGHT_BACKGROUND) {
|
||||
/* Set an arbitrary direction for the background light. */
|
||||
bcone.axis = make_float3(0.0f, 0.0f, 1.0f);
|
||||
/* TODO: this may depend on portal lights as well. */
|
||||
bcone.theta_o = M_PI_F;
|
||||
bcone.theta_e = 0;
|
||||
|
||||
/* integrate over cosine-weighted hemisphere */
|
||||
strength *= lamp->get_average_radiance() * M_PI_F;
|
||||
}
|
||||
else if (type == LIGHT_DISTANT) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = 0.5f * lamp->get_angle();
|
||||
}
|
||||
|
||||
if (lamp->get_shader()) {
|
||||
strength *= lamp->get_shader()->emission_estimate;
|
||||
}
|
||||
|
||||
energy = average(strength);
|
||||
}
|
||||
}
|
||||
|
||||
LightTree::LightTree(vector<LightTreePrimitive> &prims,
|
||||
const int &num_distant_lights,
|
||||
uint max_lights_in_leaf)
|
||||
{
|
||||
if (prims.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
max_lights_in_leaf_ = max_lights_in_leaf;
|
||||
int num_prims = prims.size();
|
||||
int num_local_lights = num_prims - num_distant_lights;
|
||||
/* The amount of nodes is estimated to be twice the amount of primitives */
|
||||
nodes_.reserve(2 * num_prims);
|
||||
|
||||
nodes_.emplace_back(); /* root node */
|
||||
recursive_build(0, num_local_lights, prims, 0, 1); /* build tree */
|
||||
nodes_[0].make_interior(nodes_.size());
|
||||
|
||||
/* All distant lights are grouped to one node (right child of the root node) */
|
||||
OrientationBounds bcone = OrientationBounds::empty;
|
||||
float energy_total = 0.0;
|
||||
for (int i = num_local_lights; i < num_prims; i++) {
|
||||
const LightTreePrimitive &prim = prims.at(i);
|
||||
bcone = merge(bcone, prim.bcone);
|
||||
energy_total += prim.energy;
|
||||
}
|
||||
nodes_.emplace_back(BoundBox::empty, bcone, energy_total, 1);
|
||||
nodes_.back().make_leaf(num_local_lights, num_distant_lights);
|
||||
|
||||
nodes_.shrink_to_fit();
|
||||
}
|
||||
|
||||
const vector<LightTreeNode> &LightTree::get_nodes() const
|
||||
{
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
int LightTree::recursive_build(
|
||||
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth)
|
||||
{
|
||||
BoundBox bbox = BoundBox::empty;
|
||||
OrientationBounds bcone = OrientationBounds::empty;
|
||||
BoundBox centroid_bounds = BoundBox::empty;
|
||||
float energy_total = 0.0;
|
||||
int num_prims = end - start;
|
||||
int current_index = nodes_.size();
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
const LightTreePrimitive &prim = prims.at(i);
|
||||
bbox.grow(prim.bbox);
|
||||
bcone = merge(bcone, prim.bcone);
|
||||
centroid_bounds.grow(prim.centroid);
|
||||
|
||||
energy_total += prim.energy;
|
||||
}
|
||||
|
||||
nodes_.emplace_back(bbox, bcone, energy_total, bit_trail);
|
||||
|
||||
bool try_splitting = num_prims > 1 && len(centroid_bounds.size()) > 0.0f;
|
||||
int split_dim = -1, split_bucket = 0, num_left_prims = 0;
|
||||
bool should_split = false;
|
||||
if (try_splitting) {
|
||||
/* Find the best place to split the primitives into 2 nodes.
|
||||
* If the best split cost is no better than making a leaf node, make a leaf instead.*/
|
||||
float min_cost = min_split_saoh(
|
||||
centroid_bounds, start, end, bbox, bcone, split_dim, split_bucket, num_left_prims, prims);
|
||||
should_split = num_prims > max_lights_in_leaf_ || min_cost < energy_total;
|
||||
}
|
||||
if (should_split) {
|
||||
int middle;
|
||||
|
||||
if (split_dim != -1) {
|
||||
/* Partition the primitives between start and end based on the split dimension and bucket
|
||||
* calculated by `split_saoh` */
|
||||
middle = start + num_left_prims;
|
||||
std::nth_element(prims.begin() + start,
|
||||
prims.begin() + middle,
|
||||
prims.begin() + end,
|
||||
[split_dim](const LightTreePrimitive &l, const LightTreePrimitive &r) {
|
||||
return l.centroid[split_dim] < r.centroid[split_dim];
|
||||
});
|
||||
}
|
||||
else {
|
||||
/* Degenerate case with many lights in the same place. */
|
||||
middle = (start + end) / 2;
|
||||
}
|
||||
|
||||
[[maybe_unused]] int left_index = recursive_build(start, middle, prims, bit_trail, depth + 1);
|
||||
int right_index = recursive_build(middle, end, prims, bit_trail | (1u << depth), depth + 1);
|
||||
assert(left_index == current_index + 1);
|
||||
nodes_[current_index].make_interior(right_index);
|
||||
}
|
||||
else {
|
||||
nodes_[current_index].make_leaf(start, num_prims);
|
||||
}
|
||||
return current_index;
|
||||
}
|
||||
|
||||
float LightTree::min_split_saoh(const BoundBox ¢roid_bbox,
|
||||
int start,
|
||||
int end,
|
||||
const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
int &split_dim,
|
||||
int &split_bucket,
|
||||
int &num_left_prims,
|
||||
const vector<LightTreePrimitive> &prims)
|
||||
{
|
||||
/* Even though this factor is used for every bucket, we use it to compare
|
||||
* the min_cost and total_energy (when deciding between creating a leaf or interior node. */
|
||||
const float bbox_area = bbox.area();
|
||||
const bool has_area = bbox_area != 0.0f;
|
||||
const float total_area = has_area ? bbox_area : len(bbox.size());
|
||||
const float total_cost = total_area * bcone.calculate_measure();
|
||||
if (total_cost == 0.0f) {
|
||||
return FLT_MAX;
|
||||
}
|
||||
|
||||
const float inv_total_cost = 1.0f / total_cost;
|
||||
const float3 extent = centroid_bbox.size();
|
||||
const float max_extent = max4(extent.x, extent.y, extent.z, 0.0f);
|
||||
|
||||
/* Check each dimension to find the minimum splitting cost. */
|
||||
float min_cost = FLT_MAX;
|
||||
for (int dim = 0; dim < 3; dim++) {
|
||||
/* If the centroid bounding box is 0 along a given dimension, skip it. */
|
||||
if (centroid_bbox.size()[dim] == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float inv_extent = 1 / (centroid_bbox.size()[dim]);
|
||||
|
||||
/* Fill in buckets with primitives. */
|
||||
vector<LightTreeBucketInfo> buckets(LightTreeBucketInfo::num_buckets);
|
||||
for (int i = start; i < end; i++) {
|
||||
const LightTreePrimitive &prim = prims[i];
|
||||
|
||||
/* Place primitive into the appropriate bucket,
|
||||
* where the centroid box is split into equal partitions. */
|
||||
int bucket_idx = LightTreeBucketInfo::num_buckets *
|
||||
(prim.centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
|
||||
if (bucket_idx == LightTreeBucketInfo::num_buckets) {
|
||||
bucket_idx = LightTreeBucketInfo::num_buckets - 1;
|
||||
}
|
||||
|
||||
buckets[bucket_idx].count++;
|
||||
buckets[bucket_idx].energy += prim.energy;
|
||||
buckets[bucket_idx].bbox.grow(prim.bbox);
|
||||
buckets[bucket_idx].bcone = merge(buckets[bucket_idx].bcone, prim.bcone);
|
||||
}
|
||||
|
||||
/* Calculate the cost of splitting at each point between partitions. */
|
||||
vector<float> bucket_costs(LightTreeBucketInfo::num_buckets - 1);
|
||||
float energy_L, energy_R;
|
||||
BoundBox bbox_L, bbox_R;
|
||||
OrientationBounds bcone_L, bcone_R;
|
||||
for (int split = 0; split < LightTreeBucketInfo::num_buckets - 1; split++) {
|
||||
energy_L = 0;
|
||||
energy_R = 0;
|
||||
bbox_L = BoundBox::empty;
|
||||
bbox_R = BoundBox::empty;
|
||||
bcone_L = OrientationBounds::empty;
|
||||
bcone_R = OrientationBounds::empty;
|
||||
|
||||
for (int left = 0; left <= split; left++) {
|
||||
if (buckets[left].bbox.valid()) {
|
||||
energy_L += buckets[left].energy;
|
||||
bbox_L.grow(buckets[left].bbox);
|
||||
bcone_L = merge(bcone_L, buckets[left].bcone);
|
||||
}
|
||||
}
|
||||
|
||||
for (int right = split + 1; right < LightTreeBucketInfo::num_buckets; right++) {
|
||||
if (buckets[right].bbox.valid()) {
|
||||
energy_R += buckets[right].energy;
|
||||
bbox_R.grow(buckets[right].bbox);
|
||||
bcone_R = merge(bcone_R, buckets[right].bcone);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate the cost of splitting using the heuristic as described in the paper. */
|
||||
const float area_L = has_area ? bbox_L.area() : len(bbox_L.size());
|
||||
const float area_R = has_area ? bbox_R.area() : len(bbox_R.size());
|
||||
float left = (bbox_L.valid()) ? energy_L * area_L * bcone_L.calculate_measure() : 0.0f;
|
||||
float right = (bbox_R.valid()) ? energy_R * area_R * bcone_R.calculate_measure() : 0.0f;
|
||||
float regularization = max_extent * inv_extent;
|
||||
bucket_costs[split] = regularization * (left + right) * inv_total_cost;
|
||||
|
||||
if (bucket_costs[split] < min_cost) {
|
||||
min_cost = bucket_costs[split];
|
||||
split_dim = dim;
|
||||
split_bucket = split;
|
||||
num_left_prims = 0;
|
||||
for (int i = 0; i <= split_bucket; i++) {
|
||||
num_left_prims += buckets[i].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_cost;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
160
intern/cycles/scene/light_tree.h
Normal file
160
intern/cycles/scene/light_tree.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#ifndef __LIGHT_TREE_H__
|
||||
#define __LIGHT_TREE_H__
|
||||
|
||||
#include "scene/light.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
#include "util/boundbox.h"
|
||||
#include "util/types.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Orientation Bounds
|
||||
*
|
||||
* Bounds the normal axis of the lights,
|
||||
* along with their emission profiles */
|
||||
struct OrientationBounds {
|
||||
float3 axis; /* normal axis of the light */
|
||||
float theta_o; /* angle bounding the normals */
|
||||
float theta_e; /* angle bounding the light emissions */
|
||||
|
||||
__forceinline OrientationBounds()
|
||||
{
|
||||
}
|
||||
|
||||
__forceinline OrientationBounds(const float3 &axis_, float theta_o_, float theta_e_)
|
||||
: axis(axis_), theta_o(theta_o_), theta_e(theta_e_)
|
||||
{
|
||||
}
|
||||
|
||||
enum empty_t { empty = 0 };
|
||||
|
||||
/* If the orientation bound is set to empty, the values are set to minumums
|
||||
* so that merging it with another non-empty orientation bound guarantees that
|
||||
* the return value is equal to non-empty orientation bound. */
|
||||
__forceinline OrientationBounds(empty_t)
|
||||
: axis(make_float3(0, 0, 0)), theta_o(FLT_MIN), theta_e(FLT_MIN)
|
||||
{
|
||||
}
|
||||
|
||||
float calculate_measure() const;
|
||||
};
|
||||
|
||||
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Light Tree Construction
|
||||
*
|
||||
* The light tree construction is based on PBRT's BVH construction.
|
||||
*/
|
||||
|
||||
/* Light Tree Primitive
|
||||
* Struct that indexes into the scene's triangle and light arrays. */
|
||||
struct LightTreePrimitive {
|
||||
/* `prim_id >= 0` is an index into an object's local triangle index,
|
||||
* otherwise `-prim_id-1`(`~prim`) is an index into device lights array. */
|
||||
int prim_id;
|
||||
int object_id;
|
||||
|
||||
float energy;
|
||||
float3 centroid;
|
||||
OrientationBounds bcone;
|
||||
BoundBox bbox;
|
||||
|
||||
LightTreePrimitive(Scene *scene, int prim_id, int object_id);
|
||||
|
||||
inline bool is_triangle() const
|
||||
{
|
||||
return prim_id >= 0;
|
||||
};
|
||||
};
|
||||
|
||||
/* Light Tree Bucket Info
|
||||
* Struct used to determine splitting costs in the light BVH. */
|
||||
struct LightTreeBucketInfo {
|
||||
LightTreeBucketInfo()
|
||||
: energy(0.0f), bbox(BoundBox::empty), bcone(OrientationBounds::empty), count(0)
|
||||
{
|
||||
}
|
||||
|
||||
float energy; /* Total energy in the partition */
|
||||
BoundBox bbox;
|
||||
OrientationBounds bcone;
|
||||
int count;
|
||||
|
||||
static const int num_buckets = 12;
|
||||
};
|
||||
|
||||
/* Light Tree Node */
|
||||
struct LightTreeNode {
|
||||
BoundBox bbox;
|
||||
OrientationBounds bcone;
|
||||
float energy;
|
||||
uint bit_trail;
|
||||
int num_prims = -1;
|
||||
union {
|
||||
int first_prim_index; /* leaf nodes contain an index to first primitive. */
|
||||
int right_child_index; /* interior nodes contain an index to second child. */
|
||||
};
|
||||
LightTreeNode() = default;
|
||||
|
||||
LightTreeNode(const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
const float &energy,
|
||||
const uint &bit_trial)
|
||||
: bbox(bbox), bcone(bcone), energy(energy), bit_trail(bit_trial)
|
||||
{
|
||||
}
|
||||
|
||||
void make_leaf(const uint &first_prim_index, const int &num_prims)
|
||||
{
|
||||
this->first_prim_index = first_prim_index;
|
||||
this->num_prims = num_prims;
|
||||
}
|
||||
void make_interior(const int &right_child_index)
|
||||
{
|
||||
this->right_child_index = right_child_index;
|
||||
}
|
||||
|
||||
inline bool is_leaf() const
|
||||
{
|
||||
return num_prims >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* Light BVH
|
||||
*
|
||||
* BVH-like data structure that keeps track of lights
|
||||
* and considers additional orientation and energy information */
|
||||
class LightTree {
|
||||
vector<LightTreeNode> nodes_;
|
||||
uint max_lights_in_leaf_;
|
||||
|
||||
public:
|
||||
LightTree(vector<LightTreePrimitive> &prims,
|
||||
const int &num_distant_lights,
|
||||
uint max_lights_in_leaf);
|
||||
|
||||
const vector<LightTreeNode> &get_nodes() const;
|
||||
|
||||
private:
|
||||
int recursive_build(
|
||||
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth);
|
||||
float min_split_saoh(const BoundBox ¢roid_bbox,
|
||||
int start,
|
||||
int end,
|
||||
const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
int &split_dim,
|
||||
int &split_bucket,
|
||||
int &num_left_prims,
|
||||
const vector<LightTreePrimitive> &prims);
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* __LIGHT_TREE_H__ */
|
@@ -231,7 +231,7 @@ void Object::tag_update(Scene *scene)
|
||||
|
||||
foreach (Node *node, geometry->get_used_shaders()) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->get_use_mis() && shader->has_surface_emission)
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
|
||||
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
|
||||
}
|
||||
}
|
||||
@@ -565,10 +565,12 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
|
||||
|
||||
void ObjectManager::device_update_prim_offsets(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
{
|
||||
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
|
||||
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
|
||||
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
|
||||
return;
|
||||
if (!scene->integrator->get_use_light_tree()) {
|
||||
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
|
||||
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
|
||||
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* On MetalRT, primitive / curve segment offsets can't be baked at BVH build time. Intersection
|
||||
|
@@ -137,7 +137,7 @@ void OSLShaderManager::device_update_specific(Device *device,
|
||||
compiler.compile(og, shader);
|
||||
});
|
||||
|
||||
if (shader->get_use_mis() && shader->has_surface_emission)
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
|
||||
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
|
||||
}
|
||||
|
||||
@@ -552,6 +552,7 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
|
||||
|
||||
SocketType::Type socket_type;
|
||||
|
||||
/* Read type and default value. */
|
||||
if (param->isclosure) {
|
||||
socket_type = SocketType::CLOSURE;
|
||||
}
|
||||
@@ -606,7 +607,21 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
|
||||
node->add_output(param->name, socket_type);
|
||||
}
|
||||
else {
|
||||
node->add_input(param->name, socket_type);
|
||||
/* Detect if we should leave parameter initialization to OSL, either though
|
||||
* not constant default or widget metadata. */
|
||||
int socket_flags = 0;
|
||||
if (!param->validdefault) {
|
||||
socket_flags |= SocketType::LINK_OSL_INITIALIZER;
|
||||
}
|
||||
for (const OSL::OSLQuery::Parameter &metadata : param->metadata) {
|
||||
if (metadata.type == TypeDesc::STRING) {
|
||||
if (metadata.name == "widget" && metadata.sdefault[0] == "null") {
|
||||
socket_flags |= SocketType::LINK_OSL_INITIALIZER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node->add_input(param->name, socket_type, socket_flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,8 +746,12 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
|
||||
foreach (ShaderInput *input, node->inputs) {
|
||||
if (!input->link) {
|
||||
/* checks to untangle graphs */
|
||||
if (node_skip_input(node, input))
|
||||
if (node_skip_input(node, input)) {
|
||||
continue;
|
||||
}
|
||||
if ((input->flags() & SocketType::LINK_OSL_INITIALIZER) && !(input->constant_folded_in)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string param_name = compatible_name(node, input);
|
||||
const SocketType &socket = input->socket_type;
|
||||
@@ -800,8 +819,11 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
|
||||
|
||||
if (current_type == SHADER_TYPE_SURFACE) {
|
||||
if (info) {
|
||||
if (info->has_surface_emission)
|
||||
current_shader->has_surface_emission = true;
|
||||
if (info->has_surface_emission && node->special_type == SHADER_SPECIAL_TYPE_OSL) {
|
||||
/* Will be used by Shader::estimate_emission. */
|
||||
OSLNode *oslnode = static_cast<OSLNode *>(node);
|
||||
oslnode->has_emission = true;
|
||||
}
|
||||
if (info->has_surface_transparent)
|
||||
current_shader->has_surface_transparent = true;
|
||||
if (info->has_surface_bssrdf) {
|
||||
@@ -1101,8 +1123,6 @@ void OSLCompiler::generate_nodes(const ShaderNodeSet &nodes)
|
||||
done.insert(node);
|
||||
|
||||
if (current_type == SHADER_TYPE_SURFACE) {
|
||||
if (node->has_surface_emission())
|
||||
current_shader->has_surface_emission = true;
|
||||
if (node->has_surface_transparent())
|
||||
current_shader->has_surface_transparent = true;
|
||||
if (node->get_feature() & KERNEL_FEATURE_NODE_RAYTRACE)
|
||||
@@ -1194,7 +1214,6 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
|
||||
current_shader = shader;
|
||||
|
||||
shader->has_surface = false;
|
||||
shader->has_surface_emission = false;
|
||||
shader->has_surface_transparent = false;
|
||||
shader->has_surface_bssrdf = false;
|
||||
shader->has_bump = has_bump;
|
||||
@@ -1237,6 +1256,9 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
|
||||
}
|
||||
else
|
||||
shader->osl_displacement_ref = OSL::ShaderGroupRef();
|
||||
|
||||
/* Estimate emission for MIS. */
|
||||
shader->estimate_emission();
|
||||
}
|
||||
|
||||
/* push state to array for lookup */
|
||||
|
@@ -52,7 +52,6 @@ const NodeEnum *Pass::get_type_enum()
|
||||
pass_type_enum.insert("emission", PASS_EMISSION);
|
||||
pass_type_enum.insert("background", PASS_BACKGROUND);
|
||||
pass_type_enum.insert("ao", PASS_AO);
|
||||
pass_type_enum.insert("shadow", PASS_SHADOW);
|
||||
pass_type_enum.insert("diffuse", PASS_DIFFUSE);
|
||||
pass_type_enum.insert("diffuse_direct", PASS_DIFFUSE_DIRECT);
|
||||
pass_type_enum.insert("diffuse_indirect", PASS_DIFFUSE_INDIRECT);
|
||||
@@ -209,10 +208,6 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bo
|
||||
case PASS_AO:
|
||||
pass_info.num_components = 3;
|
||||
break;
|
||||
case PASS_SHADOW:
|
||||
pass_info.num_components = 3;
|
||||
pass_info.use_exposure = false;
|
||||
break;
|
||||
|
||||
case PASS_DIFFUSE_COLOR:
|
||||
case PASS_GLOSSY_COLOR:
|
||||
|
@@ -71,6 +71,11 @@ DeviceScene::DeviceScene(Device *device)
|
||||
lights(device, "lights", MEM_GLOBAL),
|
||||
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
|
||||
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
|
||||
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
|
||||
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
|
||||
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
|
||||
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
|
||||
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
|
||||
particles(device, "particles", MEM_GLOBAL),
|
||||
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
|
||||
shaders(device, "shaders", MEM_GLOBAL),
|
||||
@@ -485,6 +490,8 @@ void Scene::update_kernel_features()
|
||||
return;
|
||||
}
|
||||
|
||||
thread_scoped_lock scene_lock(mutex);
|
||||
|
||||
/* These features are not being tweaked as often as shaders,
|
||||
* so could be done selective magic for the viewport as well. */
|
||||
uint kernel_features = shader_manager->get_kernel_features(this);
|
||||
@@ -571,9 +578,6 @@ bool Scene::update(Progress &progress)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load render kernels, before device update where we upload data to the GPU. */
|
||||
load_kernels(progress, false);
|
||||
|
||||
/* Upload scene data to the GPU. */
|
||||
progress.set_status("Updating Scene");
|
||||
MEM_GUARDED_CALL(&progress, device_update, device, progress);
|
||||
@@ -613,13 +617,8 @@ static void log_kernel_features(const uint features)
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
bool Scene::load_kernels(Progress &progress, bool lock_scene)
|
||||
bool Scene::load_kernels(Progress &progress)
|
||||
{
|
||||
thread_scoped_lock scene_lock;
|
||||
if (lock_scene) {
|
||||
scene_lock = thread_scoped_lock(mutex);
|
||||
}
|
||||
|
||||
update_kernel_features();
|
||||
|
||||
const uint kernel_features = dscene.data.kernel_features;
|
||||
|
@@ -111,6 +111,13 @@ class DeviceScene {
|
||||
device_vector<float2> light_background_marginal_cdf;
|
||||
device_vector<float2> light_background_conditional_cdf;
|
||||
|
||||
/* light tree */
|
||||
device_vector<KernelLightTreeNode> light_tree_nodes;
|
||||
device_vector<KernelLightTreeEmitter> light_tree_emitters;
|
||||
device_vector<uint> light_to_tree;
|
||||
device_vector<uint> object_lookup_offset;
|
||||
device_vector<uint> triangle_to_tree;
|
||||
|
||||
/* particles */
|
||||
device_vector<KernelParticle> particles;
|
||||
|
||||
@@ -270,6 +277,7 @@ class Scene : public NodeOwner {
|
||||
|
||||
void enable_update_stats();
|
||||
|
||||
bool load_kernels(Progress &progress);
|
||||
bool update(Progress &progress);
|
||||
|
||||
bool has_shadow_catcher();
|
||||
@@ -333,7 +341,6 @@ class Scene : public NodeOwner {
|
||||
uint loaded_kernel_features;
|
||||
|
||||
void update_kernel_features();
|
||||
bool load_kernels(Progress &progress, bool lock_scene = true);
|
||||
|
||||
bool has_shadow_catcher_ = false;
|
||||
bool shadow_catcher_modified_ = true;
|
||||
|
@@ -147,7 +147,17 @@ NODE_DEFINE(Shader)
|
||||
{
|
||||
NodeType *type = NodeType::add("shader", create);
|
||||
|
||||
SOCKET_BOOLEAN(use_mis, "Use MIS", true);
|
||||
static NodeEnum emission_sampling_method_enum;
|
||||
emission_sampling_method_enum.insert("none", EMISSION_SAMPLING_NONE);
|
||||
emission_sampling_method_enum.insert("auto", EMISSION_SAMPLING_AUTO);
|
||||
emission_sampling_method_enum.insert("front", EMISSION_SAMPLING_FRONT);
|
||||
emission_sampling_method_enum.insert("back", EMISSION_SAMPLING_BACK);
|
||||
emission_sampling_method_enum.insert("front_back", EMISSION_SAMPLING_FRONT_BACK);
|
||||
SOCKET_ENUM(emission_sampling_method,
|
||||
"Emission Sampling Method",
|
||||
emission_sampling_method_enum,
|
||||
EMISSION_SAMPLING_AUTO);
|
||||
|
||||
SOCKET_BOOLEAN(use_transparent_shadow, "Use Transparent Shadow", true);
|
||||
SOCKET_BOOLEAN(heterogeneous_volume, "Heterogeneous Volume", true);
|
||||
|
||||
@@ -189,7 +199,6 @@ Shader::Shader() : Node(get_node_type())
|
||||
|
||||
has_surface = false;
|
||||
has_surface_transparent = false;
|
||||
has_surface_emission = false;
|
||||
has_surface_raytrace = false;
|
||||
has_surface_bssrdf = false;
|
||||
has_volume = false;
|
||||
@@ -203,6 +212,10 @@ Shader::Shader() : Node(get_node_type())
|
||||
has_volume_connected = false;
|
||||
prev_volume_step_rate = 0.0f;
|
||||
|
||||
emission_estimate = zero_float3();
|
||||
emission_sampling = EMISSION_SAMPLING_NONE;
|
||||
emission_is_constant = true;
|
||||
|
||||
displacement_method = DISPLACE_BUMP;
|
||||
|
||||
id = -1;
|
||||
@@ -217,50 +230,141 @@ Shader::~Shader()
|
||||
delete graph;
|
||||
}
|
||||
|
||||
bool Shader::is_constant_emission(float3 *emission)
|
||||
static float3 output_estimate_emission(ShaderOutput *output, bool &is_constant)
|
||||
{
|
||||
/* Only supports a few nodes for now, not arbitrary shader graphs. */
|
||||
ShaderNode *node = (output) ? output->parent : nullptr;
|
||||
|
||||
if (node == nullptr) {
|
||||
return zero_float3();
|
||||
}
|
||||
else if (node->type == EmissionNode::get_node_type() ||
|
||||
node->type == BackgroundNode::get_node_type()) {
|
||||
/* Emission and Background node. */
|
||||
ShaderInput *color_in = node->input("Color");
|
||||
ShaderInput *strength_in = node->input("Strength");
|
||||
|
||||
float3 estimate = one_float3();
|
||||
|
||||
if (color_in->link) {
|
||||
is_constant = false;
|
||||
}
|
||||
else {
|
||||
estimate *= node->get_float3(color_in->socket_type);
|
||||
}
|
||||
|
||||
if (strength_in->link) {
|
||||
is_constant = false;
|
||||
estimate *= output_estimate_emission(strength_in->link, is_constant);
|
||||
}
|
||||
else {
|
||||
estimate *= node->get_float(strength_in->socket_type);
|
||||
}
|
||||
|
||||
return estimate;
|
||||
}
|
||||
else if (node->type == LightFalloffNode::get_node_type()) {
|
||||
/* Light Falloff node. */
|
||||
ShaderInput *strength_in = node->input("Strength");
|
||||
is_constant = false;
|
||||
|
||||
return (strength_in->link) ? output_estimate_emission(strength_in->link, is_constant) :
|
||||
make_float3(node->get_float(strength_in->socket_type));
|
||||
}
|
||||
else if (node->type == AddClosureNode::get_node_type()) {
|
||||
/* Add Closure. */
|
||||
ShaderInput *closure1_in = node->input("Closure1");
|
||||
ShaderInput *closure2_in = node->input("Closure2");
|
||||
|
||||
const float3 estimate1 = (closure1_in->link) ?
|
||||
output_estimate_emission(closure1_in->link, is_constant) :
|
||||
zero_float3();
|
||||
const float3 estimate2 = (closure2_in->link) ?
|
||||
output_estimate_emission(closure2_in->link, is_constant) :
|
||||
zero_float3();
|
||||
|
||||
return estimate1 + estimate2;
|
||||
}
|
||||
else if (node->type == MixClosureNode::get_node_type()) {
|
||||
/* Mix Closure. */
|
||||
ShaderInput *fac_in = node->input("Fac");
|
||||
ShaderInput *closure1_in = node->input("Closure1");
|
||||
ShaderInput *closure2_in = node->input("Closure2");
|
||||
|
||||
const float3 estimate1 = (closure1_in->link) ?
|
||||
output_estimate_emission(closure1_in->link, is_constant) :
|
||||
zero_float3();
|
||||
const float3 estimate2 = (closure2_in->link) ?
|
||||
output_estimate_emission(closure2_in->link, is_constant) :
|
||||
zero_float3();
|
||||
|
||||
if (fac_in->link) {
|
||||
is_constant = false;
|
||||
return estimate1 + estimate2;
|
||||
}
|
||||
else {
|
||||
const float fac = node->get_float(fac_in->socket_type);
|
||||
return (1.0f - fac) * estimate1 + fac * estimate2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Other nodes, potentially OSL nodes with arbitrary code for which all we can
|
||||
* determine is if it has emission or not. */
|
||||
const bool has_emission = node->has_surface_emission();
|
||||
float3 estimate;
|
||||
|
||||
if (output->type() == SocketType::CLOSURE) {
|
||||
if (has_emission) {
|
||||
estimate = one_float3();
|
||||
is_constant = false;
|
||||
}
|
||||
else {
|
||||
estimate = zero_float3();
|
||||
}
|
||||
|
||||
foreach (const ShaderInput *in, node->inputs) {
|
||||
if (in->type() == SocketType::CLOSURE && in->link) {
|
||||
estimate += output_estimate_emission(in->link, is_constant);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
estimate = one_float3();
|
||||
is_constant = false;
|
||||
}
|
||||
|
||||
return estimate;
|
||||
}
|
||||
}
|
||||
|
||||
void Shader::estimate_emission()
|
||||
{
|
||||
/* If the shader has AOVs, they need to be evaluated, so we can't skip the shader. */
|
||||
emission_is_constant = true;
|
||||
|
||||
foreach (ShaderNode *node, graph->nodes) {
|
||||
if (node->special_type == SHADER_SPECIAL_TYPE_OUTPUT_AOV) {
|
||||
return false;
|
||||
emission_is_constant = false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInput *surf = graph->output()->input("Surface");
|
||||
emission_estimate = output_estimate_emission(surf->link, emission_is_constant);
|
||||
|
||||
if (surf->link == NULL) {
|
||||
return false;
|
||||
if (is_zero(emission_estimate)) {
|
||||
emission_sampling = EMISSION_SAMPLING_NONE;
|
||||
}
|
||||
|
||||
if (surf->link->parent->type == EmissionNode::get_node_type()) {
|
||||
EmissionNode *node = (EmissionNode *)surf->link->parent;
|
||||
|
||||
assert(node->input("Color"));
|
||||
assert(node->input("Strength"));
|
||||
|
||||
if (node->input("Color")->link || node->input("Strength")->link) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*emission = node->get_color() * node->get_strength();
|
||||
}
|
||||
else if (surf->link->parent->type == BackgroundNode::get_node_type()) {
|
||||
BackgroundNode *node = (BackgroundNode *)surf->link->parent;
|
||||
|
||||
assert(node->input("Color"));
|
||||
assert(node->input("Strength"));
|
||||
|
||||
if (node->input("Color")->link || node->input("Strength")->link) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*emission = node->get_color() * node->get_strength();
|
||||
else if (emission_sampling_method == EMISSION_SAMPLING_AUTO) {
|
||||
/* Automatically disable MIS when emission is low, to avoid weakly emitting
|
||||
* using a lot of memory in the light tree and potentially wasting samples
|
||||
* where indirect light samples are sufficient.
|
||||
* Possible optimization: estimate front and back emission separately. */
|
||||
emission_sampling = (reduce_max(emission_estimate) > 0.5f) ? EMISSION_SAMPLING_FRONT_BACK :
|
||||
EMISSION_SAMPLING_NONE;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
emission_sampling = emission_sampling_method;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shader::set_graph(ShaderGraph *graph_)
|
||||
@@ -305,7 +409,7 @@ void Shader::tag_update(Scene *scene)
|
||||
/* if the shader previously was emissive, update light distribution,
|
||||
* if the new shader is emissive, a light manager update tag will be
|
||||
* done in the shader manager device update. */
|
||||
if (use_mis && has_surface_emission)
|
||||
if (emission_sampling != EMISSION_SAMPLING_NONE)
|
||||
scene->light_manager->tag_update(scene, LightManager::SHADER_MODIFIED);
|
||||
|
||||
/* Special handle of background MIS light for now: for some reason it
|
||||
@@ -491,9 +595,17 @@ void ShaderManager::device_update_common(Device * /*device*/,
|
||||
foreach (Shader *shader, scene->shaders) {
|
||||
uint flag = 0;
|
||||
|
||||
if (shader->get_use_mis())
|
||||
flag |= SD_USE_MIS;
|
||||
if (shader->has_surface_emission)
|
||||
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
|
||||
flag |= SD_MIS_FRONT;
|
||||
}
|
||||
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
|
||||
flag |= SD_MIS_BACK;
|
||||
}
|
||||
else if (shader->emission_sampling == EMISSION_SAMPLING_FRONT_BACK) {
|
||||
flag |= SD_MIS_FRONT | SD_MIS_BACK;
|
||||
}
|
||||
|
||||
if (!is_zero(shader->emission_estimate))
|
||||
flag |= SD_HAS_EMISSION;
|
||||
if (shader->has_surface_transparent && shader->get_use_transparent_shadow())
|
||||
flag |= SD_HAS_TRANSPARENT_SHADOW;
|
||||
@@ -531,8 +643,7 @@ void ShaderManager::device_update_common(Device * /*device*/,
|
||||
flag |= SD_HAS_DISPLACEMENT;
|
||||
|
||||
/* constant emission check */
|
||||
float3 constant_emission = zero_float3();
|
||||
if (shader->is_constant_emission(&constant_emission))
|
||||
if (shader->emission_is_constant)
|
||||
flag |= SD_HAS_CONSTANT_EMISSION;
|
||||
|
||||
uint32_t cryptomatte_id = util_murmur_hash3(shader->name.c_str(), shader->name.length(), 0);
|
||||
@@ -540,9 +651,9 @@ void ShaderManager::device_update_common(Device * /*device*/,
|
||||
/* regular shader */
|
||||
kshader->flags = flag;
|
||||
kshader->pass_id = shader->get_pass_id();
|
||||
kshader->constant_emission[0] = constant_emission.x;
|
||||
kshader->constant_emission[1] = constant_emission.y;
|
||||
kshader->constant_emission[2] = constant_emission.z;
|
||||
kshader->constant_emission[0] = shader->emission_estimate.x;
|
||||
kshader->constant_emission[1] = shader->emission_estimate.y;
|
||||
kshader->constant_emission[2] = shader->emission_estimate.z;
|
||||
kshader->cryptomatte_id = util_hash_to_float(cryptomatte_id);
|
||||
kshader++;
|
||||
|
||||
@@ -627,8 +738,8 @@ void ShaderManager::add_default(Scene *scene)
|
||||
shader->set_graph(graph);
|
||||
scene->default_volume = shader;
|
||||
shader->tag_update(scene);
|
||||
/* No default reference for the volume to avoid compiling volume kernels if there are no actual
|
||||
* volumes in the scene */
|
||||
/* No default reference for the volume to avoid compiling volume kernels if there are no
|
||||
* actual volumes in the scene */
|
||||
}
|
||||
|
||||
/* default light */
|
||||
|
@@ -34,6 +34,7 @@ struct float3;
|
||||
enum ShadingSystem { SHADINGSYSTEM_OSL, SHADINGSYSTEM_SVM };
|
||||
|
||||
/* Keep those in sync with the python-defined enum. */
|
||||
|
||||
enum VolumeSampling {
|
||||
VOLUME_SAMPLING_DISTANCE = 0,
|
||||
VOLUME_SAMPLING_EQUIANGULAR = 1,
|
||||
@@ -73,7 +74,7 @@ class Shader : public Node {
|
||||
NODE_SOCKET_API(int, pass_id)
|
||||
|
||||
/* sampling */
|
||||
NODE_SOCKET_API(bool, use_mis)
|
||||
NODE_SOCKET_API(EmissionSampling, emission_sampling_method)
|
||||
NODE_SOCKET_API(bool, use_transparent_shadow)
|
||||
NODE_SOCKET_API(bool, heterogeneous_volume)
|
||||
NODE_SOCKET_API(VolumeSampling, volume_sampling_method)
|
||||
@@ -101,7 +102,6 @@ class Shader : public Node {
|
||||
|
||||
/* information about shader after compiling */
|
||||
bool has_surface;
|
||||
bool has_surface_emission;
|
||||
bool has_surface_transparent;
|
||||
bool has_surface_raytrace;
|
||||
bool has_volume;
|
||||
@@ -114,6 +114,10 @@ class Shader : public Node {
|
||||
bool has_volume_attribute_dependency;
|
||||
bool has_integrator_dependency;
|
||||
|
||||
float3 emission_estimate;
|
||||
EmissionSampling emission_sampling;
|
||||
bool emission_is_constant;
|
||||
|
||||
/* requested mesh attributes */
|
||||
AttributeRequestSet attributes;
|
||||
|
||||
@@ -131,11 +135,12 @@ class Shader : public Node {
|
||||
Shader();
|
||||
~Shader();
|
||||
|
||||
/* Checks whether the shader consists of just a emission node with fixed inputs that's connected
|
||||
* directly to the output.
|
||||
* If yes, it sets the content of emission to the constant value (color * strength), which is
|
||||
* then used for speeding up light evaluation. */
|
||||
bool is_constant_emission(float3 *emission);
|
||||
/* Estimate emission of this shader based on the shader graph. This works only in very simple
|
||||
* cases. But it helps improve light importance sampling in common cases.
|
||||
*
|
||||
* If the emission is fully constant, returns true, so that shader evaluation can be skipped
|
||||
* entirely for a light. */
|
||||
void estimate_emission();
|
||||
|
||||
void set_graph(ShaderGraph *graph);
|
||||
void tag_update(Scene *scene);
|
||||
|
@@ -74,15 +74,15 @@ class ShaderInput {
|
||||
{
|
||||
}
|
||||
|
||||
ustring name()
|
||||
ustring name() const
|
||||
{
|
||||
return socket_type.ui_name;
|
||||
}
|
||||
int flags()
|
||||
int flags() const
|
||||
{
|
||||
return socket_type.flags;
|
||||
}
|
||||
SocketType::Type type()
|
||||
SocketType::Type type() const
|
||||
{
|
||||
return socket_type.type;
|
||||
}
|
||||
@@ -119,11 +119,11 @@ class ShaderOutput {
|
||||
{
|
||||
}
|
||||
|
||||
ustring name()
|
||||
ustring name() const
|
||||
{
|
||||
return socket_type.ui_name;
|
||||
}
|
||||
SocketType::Type type()
|
||||
SocketType::Type type() const
|
||||
{
|
||||
return socket_type.type;
|
||||
}
|
||||
|
@@ -7211,6 +7211,7 @@ void SetNormalNode::compile(OSLCompiler &compiler)
|
||||
OSLNode::OSLNode() : ShaderNode(new NodeType(NodeType::SHADER))
|
||||
{
|
||||
special_type = SHADER_SPECIAL_TYPE_OSL;
|
||||
has_emission = false;
|
||||
}
|
||||
|
||||
OSLNode::~OSLNode()
|
||||
@@ -7257,12 +7258,12 @@ char *OSLNode::input_default_value()
|
||||
return (char *)this + align_up(sizeof(OSLNode), 16) + inputs_size;
|
||||
}
|
||||
|
||||
void OSLNode::add_input(ustring name, SocketType::Type socket_type)
|
||||
void OSLNode::add_input(ustring name, SocketType::Type socket_type, const int flags)
|
||||
{
|
||||
char *memory = input_default_value();
|
||||
size_t offset = memory - (char *)this;
|
||||
const_cast<NodeType *>(type)->register_input(
|
||||
name, name, socket_type, offset, memory, NULL, NULL, SocketType::LINKABLE);
|
||||
name, name, socket_type, offset, memory, NULL, NULL, flags | SocketType::LINKABLE);
|
||||
}
|
||||
|
||||
void OSLNode::add_output(ustring name, SocketType::Type socket_type)
|
||||
|
@@ -1525,11 +1525,16 @@ class OSLNode final : public ShaderNode {
|
||||
ShaderNode *clone(ShaderGraph *graph) const;
|
||||
|
||||
char *input_default_value();
|
||||
void add_input(ustring name, SocketType::Type type);
|
||||
void add_input(ustring name, SocketType::Type type, const int flags = 0);
|
||||
void add_output(ustring name, SocketType::Type type);
|
||||
|
||||
SHADER_NODE_NO_CLONE_CLASS(OSLNode)
|
||||
|
||||
bool has_surface_emission()
|
||||
{
|
||||
return has_emission;
|
||||
}
|
||||
|
||||
/* Ideally we could better detect this, but we can't query this now. */
|
||||
bool has_spatial_varying()
|
||||
{
|
||||
@@ -1551,6 +1556,7 @@ class OSLNode final : public ShaderNode {
|
||||
|
||||
string filepath;
|
||||
string bytecode_hash;
|
||||
bool has_emission;
|
||||
};
|
||||
|
||||
class NormalMapNode : public ShaderNode {
|
||||
|
@@ -109,7 +109,7 @@ void SVMShaderManager::device_update_specific(Device *device,
|
||||
Shader *shader = scene->shaders[i];
|
||||
|
||||
shader->clear_modified();
|
||||
if (shader->get_use_mis() && shader->has_surface_emission) {
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
|
||||
}
|
||||
|
||||
@@ -516,8 +516,6 @@ void SVMCompiler::generate_closure_node(ShaderNode *node, CompilerState *state)
|
||||
mix_weight_offset = SVM_STACK_INVALID;
|
||||
|
||||
if (current_type == SHADER_TYPE_SURFACE) {
|
||||
if (node->has_surface_emission())
|
||||
current_shader->has_surface_emission = true;
|
||||
if (node->has_surface_transparent())
|
||||
current_shader->has_surface_transparent = true;
|
||||
if (node->has_surface_bssrdf()) {
|
||||
@@ -873,7 +871,6 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
|
||||
current_shader = shader;
|
||||
|
||||
shader->has_surface = false;
|
||||
shader->has_surface_emission = false;
|
||||
shader->has_surface_transparent = false;
|
||||
shader->has_surface_raytrace = false;
|
||||
shader->has_surface_bssrdf = false;
|
||||
@@ -928,6 +925,9 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
|
||||
summary->peak_stack_usage = max_stack_use;
|
||||
summary->num_svm_nodes = svm_nodes.size() - start_num_svm_nodes;
|
||||
}
|
||||
|
||||
/* Estimate emission for MIS. */
|
||||
shader->estimate_emission();
|
||||
}
|
||||
|
||||
/* Compiler summary implementation. */
|
||||
|
@@ -85,7 +85,7 @@ class DisplayDriver {
|
||||
int buffer_height = 0;
|
||||
|
||||
/* OpenGL pixel buffer object. */
|
||||
int opengl_pbo_id = 0;
|
||||
int64_t opengl_pbo_id = 0;
|
||||
|
||||
/* Clear the entire buffer before doing partial write to it. */
|
||||
bool need_clear = false;
|
||||
|
@@ -378,6 +378,18 @@ RenderWork Session::run_update_for_next_iteration()
|
||||
const int width = max(1, buffer_params_.full_width / resolution);
|
||||
const int height = max(1, buffer_params_.full_height / resolution);
|
||||
|
||||
{
|
||||
/* Load render kernels, before device update where we upload data to the GPU.
|
||||
* Do it outside of the scene mutex since the heavy part of the loading (i.e. kernel
|
||||
* compilation) does not depend on the scene and some other functionality (like display
|
||||
* driver) might be waiting on the scene mutex to synchronize display pass.
|
||||
*
|
||||
* The scene will lock itself for the short period if it needs to update kernel features. */
|
||||
scene_lock.unlock();
|
||||
scene->load_kernels(progress);
|
||||
scene_lock.lock();
|
||||
}
|
||||
|
||||
if (update_scene(width, height)) {
|
||||
profiler.reset(scene->shaders.size(), scene->objects.size());
|
||||
}
|
||||
|
@@ -342,8 +342,8 @@ void TileManager::reset_scheduling(const BufferParams ¶ms, int2 tile_size)
|
||||
|
||||
tile_size_ = tile_size;
|
||||
|
||||
tile_state_.num_tiles_x = divide_up(params.width, tile_size_.x);
|
||||
tile_state_.num_tiles_y = divide_up(params.height, tile_size_.y);
|
||||
tile_state_.num_tiles_x = tile_size_.x ? divide_up(params.width, tile_size_.x) : 0;
|
||||
tile_state_.num_tiles_y = tile_size_.y ? divide_up(params.height, tile_size_.y) : 0;
|
||||
tile_state_.num_tiles = tile_state_.num_tiles_x * tile_state_.num_tiles_y;
|
||||
|
||||
tile_state_.next_tile_index = 0;
|
||||
|
@@ -74,7 +74,6 @@ set(SRC_HEADERS
|
||||
md5.h
|
||||
murmurhash.h
|
||||
openimagedenoise.h
|
||||
opengl.h
|
||||
openvdb.h
|
||||
optimization.h
|
||||
param.h
|
||||
|
@@ -796,11 +796,11 @@ ccl_device float bits_to_01(uint bits)
|
||||
ccl_device_inline uint popcount(uint x)
|
||||
{
|
||||
/* TODO(Stefan): pop-count intrinsic for Windows with fallback for older CPUs. */
|
||||
uint i = x & 0xaaaaaaaa;
|
||||
uint i = x;
|
||||
i = i - ((i >> 1) & 0x55555555);
|
||||
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
|
||||
i = (((i + (i >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||
return i & 1;
|
||||
return i;
|
||||
}
|
||||
# endif
|
||||
#elif defined(__KERNEL_ONEAPI__)
|
||||
|
@@ -58,7 +58,7 @@ ccl_device_inline float4 madd4(const float4 a, const float4 b, const float4 c)
|
||||
ccl_device_inline int fast_rint(float x)
|
||||
{
|
||||
/* used by sin/cos/tan range reduction. */
|
||||
#ifdef __KERNEL_SSE4__
|
||||
#ifdef __KERNEL_SSE41__
|
||||
/* Single `roundps` instruction on SSE4.1+ (for gcc/clang at least). */
|
||||
return float_to_int(rintf(x));
|
||||
#else
|
||||
|
@@ -257,8 +257,8 @@ ccl_device bool ray_quad_intersect(float3 ray_P,
|
||||
float ray_tmin,
|
||||
float ray_tmax,
|
||||
float3 quad_P,
|
||||
float3 quad_u,
|
||||
float3 quad_v,
|
||||
float3 inv_quad_u,
|
||||
float3 inv_quad_v,
|
||||
float3 quad_n,
|
||||
ccl_private float3 *isect_P,
|
||||
ccl_private float *isect_t,
|
||||
@@ -273,11 +273,11 @@ ccl_device bool ray_quad_intersect(float3 ray_P,
|
||||
}
|
||||
const float3 hit = ray_P + t * ray_D;
|
||||
const float3 inplane = hit - quad_P;
|
||||
const float u = dot(inplane, quad_u) / dot(quad_u, quad_u);
|
||||
const float u = dot(inplane, inv_quad_u);
|
||||
if (u < -0.5f || u > 0.5f) {
|
||||
return false;
|
||||
}
|
||||
const float v = dot(inplane, quad_v) / dot(quad_v, quad_v);
|
||||
const float v = dot(inplane, inv_quad_v);
|
||||
if (v < -0.5f || v > 0.5f) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#ifndef __UTIL_OPENGL_H__
|
||||
#define __UTIL_OPENGL_H__
|
||||
|
||||
/* OpenGL header includes, used everywhere we use OpenGL, to deal with
|
||||
* platform differences in one central place. */
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#endif /* __UTIL_OPENGL_H__ */
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user