diff --git a/Changelog.md b/Changelog.md
index 95f1154d..fc6f9f84 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,38 @@
+# Version 3.2
+## New Features:
+- Subsurface Scattering and Volume shaders now work in RPR 2.0. This allows the rendering of organic materials, such as skin, which absorb light into their interior. Volume shaders can now also be used for simple fog boxes. Also the Volume Scatter node is supported.
+- Viewport denoising and upscaling improves the interactivity and speed of Viewport rendering. With the use of the Radeon Image Filter Library, this allows Radeon ProRender to render at half resolution faster, and then upscale to the full size of the Viewport.
+- Deformation motion blur gives accurate motion blur to objects which are being deformed, for example, a flag flapping in the wind or a ball squashing. Besides, motion blur export has been optimized, and a setting for disabling deformation motion blur has been added.
+- A new RPR Toon Shader has been added. This enables cartoon-style shading for a non-photorealistic look. Toon shaders can be used in a “simple” mode for just setting a color or a gradient of different colors for shadow vs lit areas of the object.
+- Support for Blender 2.93 has been added.
+- The look of “blocky” displacements has been significantly improved by enabling subdivision by default on objects with displacement shaders. However, this does render slower, and can be overridden with the RPR Object Subdivision settings.
+- Support has been added for Reflection Catcher and Transparent background in the Viewport in the Full mode.
+- Outline rendering (formerly called Contour rendering) is now moved to the view layer AOV section. Outline rendering can be enabled just as the other AOVs. The rendering process with Outline rendering enabled will take two passes per view layer, with the second doing the Outline rendering.
+- Support for (Shutter) Position in the Motion Blur settings has been added. This uses the cycles setting to control the shutter opening around the frame number.
+- Support for the Voronoi Texture node is added.
+
+## Issues Fixed:
+- Improve prop name readability in Object visibility UI.
+- Texture compression was causing artifacts in some scenes. A “texture compression” setting has been added and defaulted to False. You can enable this setting for faster renders, but make sure that there are no texture artifacts.
+- The issue with the add-on not loading in versions of Blender downloaded from the Windows app store has been fixed.
+- Objects set as Reflection Catchers now work in the Full mode.
+- Overbright edges of objects with Uber shaders in metalness mode ― fixed.
+- Shaders with high roughness could have artifacts with reflection or refraction ― fixed.
+- Tiled rendering with a transparent background in the Full render quality has been fixed.
+- Occasional issues in starting the add-on in certain OSs have been fixed.
+- The option "Viewport Denoising and Upscaling" is saved to the scene settings.
+- Memory leak in Viewport rendering with the Upscale filter enabled has been fixed.
+- Image filters on Ubuntu20 have been fixed.
+- Iterating all of view layers when baking all objects have been fixed.
+- Fixed a crash in the viewport render if an object with hair and modifiers was not enabled for viewport display.
+- Fixed an error with math nodes set to “Smooth min” or “Compare” modes.
+
+## Known Issues:
+- In RPR 2.0, heterogenous volumes, smoke and fire simulations or VDB files are not yet supported.
+- Subsurface scattering and volume shader are currently disabled on macOS due to long compile times.
+- Some AOVs may have artifacts on AMD cards with drivers earlier than 21.6.1
+
+
# Version 3.1
## New Features:
- Support for AMD Radeon™ RX 6700 XT graphics cards has been added.
diff --git a/RPRBlenderHelper/CMakeLists.txt b/RPRBlenderHelper/CMakeLists.txt
index 3ba0fe85..9b44fc71 100644
--- a/RPRBlenderHelper/CMakeLists.txt
+++ b/RPRBlenderHelper/CMakeLists.txt
@@ -8,7 +8,6 @@ set (CMAKE_CXX_STANDARD 11)
set(RPR_SDK_DIR ${CMAKE_SOURCE_DIR}/../.sdk/rpr)
set(RPRTOOLS_DIR ${RPR_SDK_DIR}/rprTools)
set(SHARED_DIR ${CMAKE_SOURCE_DIR}/../RadeonProRenderSharedComponents)
-set(OPENVDB_SDK_PATH ${SHARED_DIR}/OpenVdb)
set(SOURCES
RPRBlenderHelper/RPRBlenderHelper.cpp
@@ -26,32 +25,52 @@ include_directories(
if(WIN32)
list(APPEND SOURCES
- RPRBlenderHelper/OpenVdb.cpp
RPRBlenderHelper/dllmain.cpp
)
include_directories(
- ${OPENVDB_SDK_PATH}/include
${SHARED_DIR}/RadeonProRenderLibs/rprLibs
)
-
set(LIBS
${RPR_SDK_DIR}/lib/RadeonProRender64.lib
- ${OPENVDB_SDK_PATH}/Windows/lib/openvdb.lib
- ${OPENVDB_SDK_PATH}/Windows/lib/Half-2_3.lib
- ${OPENVDB_SDK_PATH}/Windows/lib/tbb.lib
)
elseif(${APPLE})
- list(APPEND SOURCES
- RPRBlenderHelper/OpenVdb.cpp
- )
include_directories(
- ${OPENVDB_SDK_PATH}/include
${SHARED_DIR}/RadeonProRenderLibs/rprLibs
)
-
set(LIBS
${RPR_SDK_DIR}/bin/libRadeonProRender64.dylib
+ )
+
+else() # Linux
+ set(LIBS ${RPR_SDK_DIR}/bin/libRadeonProRender64.so)
+
+endif()
+
+add_library(RPRBlenderHelper SHARED ${SOURCES})
+add_definitions(-DBLENDER_PLUGIN)
+target_link_libraries(RPRBlenderHelper ${LIBS})
+
+
+# Building RPRBlenderHelper with OPENVDB support
+set(OPENVDB_SDK_PATH ${SHARED_DIR}/OpenVdb)
+
+list(APPEND SOURCES
+ RPRBlenderHelper/OpenVdb.cpp
+)
+include_directories(
+ ${OPENVDB_SDK_PATH}/include
+)
+
+if(WIN32)
+ list(APPEND LIBS
+ ${OPENVDB_SDK_PATH}/Windows/lib/openvdb.lib
+ ${OPENVDB_SDK_PATH}/Windows/lib/Half-2_3.lib
+ ${OPENVDB_SDK_PATH}/Windows/lib/tbb.lib
+ )
+
+elseif(${APPLE})
+ list(APPEND LIBS
${OPENVDB_SDK_PATH}/OSX/lib/libopenvdb.a
${OPENVDB_SDK_PATH}/OSX/lib/libz.a
${OPENVDB_SDK_PATH}/OSX/lib/libblosc.a
@@ -60,13 +79,11 @@ elseif(${APPLE})
${OPENVDB_SDK_PATH}/OSX/lib/libtbb.a
)
-else() # Linux
- set(LIBS ${RPR_SDK_DIR}/bin/libRadeonProRender64.so)
+else()
+ return()
endif()
-add_library(RPRBlenderHelper SHARED ${SOURCES})
-
+add_library(RPRBlenderHelper_vdb SHARED ${SOURCES})
add_definitions(-DBLENDER_PLUGIN)
-
-target_link_libraries(RPRBlenderHelper ${LIBS})
+target_link_libraries(RPRBlenderHelper_vdb ${LIBS})
diff --git a/RadeonProImageProcessingSDK b/RadeonProImageProcessingSDK
index 00b1f056..9dc9175c 160000
--- a/RadeonProImageProcessingSDK
+++ b/RadeonProImageProcessingSDK
@@ -1 +1 @@
-Subproject commit 00b1f056d13050ebff02d212b2eff8ac7a4ee2f7
+Subproject commit 9dc9175cd7b2ab99227ed50a76338f35e3c927dd
diff --git a/RadeonProRenderSDK b/RadeonProRenderSDK
index 5ca51cc4..7afdadba 160000
--- a/RadeonProRenderSDK
+++ b/RadeonProRenderSDK
@@ -1 +1 @@
-Subproject commit 5ca51cc43b41f04e812b393ffcb402f9a6b56f9f
+Subproject commit 7afdadba35695f36054a314ccbfec646291fbe39
diff --git a/build.cmd b/build.cmd
index fce396c3..f5241c8b 100644
--- a/build.cmd
+++ b/build.cmd
@@ -76,6 +76,7 @@ py -3.7 src\bindings\pyrpr\src\pyrprapi.py %castxml%
set bindingsOk=.\bindings-ok
if exist %bindingsOk% (
py -3.7 build.py
+ py -3.9 build.py
) else (
echo Compiling bindings failed
)
diff --git a/build.py b/build.py
index afa18c1f..348a9966 100644
--- a/build.py
+++ b/build.py
@@ -20,7 +20,6 @@
import platform
import subprocess
from pathlib import Path
-import shutil
arch = platform.architecture()
@@ -37,18 +36,17 @@
os.chdir(str(pyrpr_path))
pyrpr_build_dir = Path('.build')
-if Path('.build').exists():
- shutil.rmtree(str(pyrpr_build_dir))
-
subprocess.check_call([sys.executable, 'rpr.py'])
subprocess.check_call([sys.executable, 'rpr_load_store.py'])
os.chdir(cwd)
-os.chdir('RPRBlenderHelper')
-os.makedirs('.build', exist_ok=True)
-os.chdir('.build')
-if 'Windows' == platform.system():
- subprocess.check_call(['cmake', '-G', 'Visual Studio 14 2015 Win64', '..'])
-else:
- subprocess.check_call(['cmake', '..'])
-subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--clean-first'])
+if sys.version_info.major == 3 and sys.version_info.minor == 7:
+ # we are going to build RPRBlenderHelper only for python 3.7
+ os.chdir('RPRBlenderHelper')
+ os.makedirs('.build', exist_ok=True)
+ os.chdir('.build')
+ if 'Windows' == platform.system():
+ subprocess.check_call(['cmake', '-G', 'Visual Studio 14 2015 Win64', '..'])
+ else:
+ subprocess.check_call(['cmake', '..'])
+ subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--clean-first'])
diff --git a/build.sh b/build.sh
index 452d41b7..2e1e9d53 100755
--- a/build.sh
+++ b/build.sh
@@ -4,6 +4,7 @@ if [ -f "$cxml" ]; then
python3.7 src/bindings/pyrpr/src/pyrprapi.py $cxml
if [ -f "./bindings-ok" ]; then
python3.7 build.py
+ python3.9 build.py
else
echo Compiling bindings failed
fi
diff --git a/build_osx.sh b/build_osx.sh
index c2513dd9..f811fcef 100755
--- a/build_osx.sh
+++ b/build_osx.sh
@@ -17,6 +17,7 @@ if [ -f "$cxml" ]; then
python3.7 src/bindings/pyrpr/src/pyrprapi.py $cxml
if [ -f "./bindings-ok" ]; then
python3.7 build.py
+ python3.9 build.py
#sh osx/postbuild.sh
else
echo Compiling bindings failed
diff --git a/cmd_tools/create_sdk.py b/cmd_tools/create_sdk.py
index 6f321224..cc4d0103 100644
--- a/cmd_tools/create_sdk.py
+++ b/cmd_tools/create_sdk.py
@@ -37,6 +37,10 @@ def recreate_sdk():
copy_rif_sdk()
+def find_file(path, glob):
+ return next(f for f in path.glob(glob) if not f.is_symlink())
+
+
def copy_rpr_sdk():
rpr_dir = Path("RadeonProRenderSDK/RadeonProRender")
@@ -85,7 +89,7 @@ def copy_rif_sdk():
# getting rif bin_dir
os_str = {
'Windows': "Windows",
- 'Linux': "Ubuntu18",
+ 'Linux': "Ubuntu20",
'Darwin': "OSX"
}[OS]
bin_dir = rif_dir / os_str / "Dynamic"
@@ -109,27 +113,32 @@ def copy_rif_sdk():
shutil.copy(str(lib), str(sdk_lib_dir))
elif OS == 'Linux':
- shutil.copy(str(bin_dir / "libRadeonImageFilters.so.1.6.1"),
+ shutil.copy(str(find_file(bin_dir, "libRadeonImageFilters.so*")),
str(sdk_bin_dir / "libRadeonImageFilters.so"))
- shutil.copy(str(bin_dir / "libRadeonML_MIOpen.so.0.9.8"),
+ shutil.copy(str(find_file(bin_dir, "libRadeonML_MIOpen.so*")),
str(sdk_bin_dir / "libRadeonML_MIOpen.so"))
- shutil.copy(str(bin_dir / "libOpenImageDenoise.so.0.9.0"),
+ shutil.copy(str(find_file(bin_dir, "libOpenImageDenoise.so*")),
str(sdk_bin_dir / "libOpenImageDenoise.so"))
- shutil.copy(str(bin_dir / "libMIOpen.so.2.0.4"),
+ shutil.copy(str(find_file(bin_dir, "libMIOpen.so.2*")),
str(sdk_bin_dir / "libMIOpen.so.2"))
+ shutil.copy(str(find_file(bin_dir, "libRadeonML.so.0*")),
+ str(sdk_bin_dir / "libRadeonML.so.0"))
elif OS == 'Darwin':
- shutil.copy(str(bin_dir / "libRadeonImageFilters.1.6.1.dylib"),
+ shutil.copy(str(find_file(bin_dir, "libRadeonImageFilters*.dylib")),
str(sdk_bin_dir / "libRadeonImageFilters.dylib"))
- shutil.copy(str(bin_dir / "libOpenImageDenoise.0.9.0.dylib"),
+ shutil.copy(str(find_file(bin_dir, "libOpenImageDenoise*.dylib")),
str(sdk_bin_dir / "libOpenImageDenoise.dylib"))
- shutil.copy(str(bin_dir / "libRadeonML_MPS.0.9.8.dylib"),
+ shutil.copy(str(find_file(bin_dir, "libRadeonML_MPS*.dylib")),
str(sdk_bin_dir / "libRadeonML_MPS.dylib"))
+ shutil.copy(str(find_file(bin_dir, "libRadeonML.0*.dylib")),
+ str(sdk_bin_dir / "libRadeonML.0.dylib"))
# adjusting id of RIF libs
install_name_tool('-id', "@rpath/libRadeonImageFilters.dylib", sdk_bin_dir / "libRadeonImageFilters.dylib")
install_name_tool('-id', "@rpath/libOpenImageDenoise.dylib", sdk_bin_dir / "libOpenImageDenoise.dylib")
install_name_tool('-id', "@rpath/libRadeonML_MPS.dylib", sdk_bin_dir / "libRadeonML_MPS.dylib")
+ install_name_tool('-id', "@rpath/libRadeonML.0.dylib", sdk_bin_dir / "libRadeonML.0.dylib")
else:
raise KeyError("Unsupported OS", OS)
diff --git a/run_blender_with_rpr.cmd b/run_blender_with_rpr.cmd
index 468daa08..bdfdc536 100644
--- a/run_blender_with_rpr.cmd
+++ b/run_blender_with_rpr.cmd
@@ -19,6 +19,9 @@ REM *******************************************************************
if ""=="%BLENDER_EXE%" goto error
+REM set Debug Mode flag
+set RPR_BLENDER_DEBUG=1
+
py cmd_tools/run_blender.py "%BLENDER_EXE%" cmd_tools/test_rpr.py
pause
REM it's much easier to get issue traceback on crash if pause is present; remove if not needed
diff --git a/run_blender_with_rpr_Ubuntu.sh b/run_blender_with_rpr_Ubuntu.sh
index 8399eb37..f4dbb175 100755
--- a/run_blender_with_rpr_Ubuntu.sh
+++ b/run_blender_with_rpr_Ubuntu.sh
@@ -65,6 +65,7 @@ function main()
{
init
+ export RPR_BLENDER_DEBUG=1
export LD_LIBRARY_PATH="$WORK_DIR:$LD_LIBRARY_PATH"
python3 cmd_tools/run_blender.py "$BLENDER_EXE" cmd_tools/test_rpr.py
diff --git a/run_blender_with_rpr_osx.sh b/run_blender_with_rpr_osx.sh
index c3cf3d51..cb15be03 100755
--- a/run_blender_with_rpr_osx.sh
+++ b/run_blender_with_rpr_osx.sh
@@ -30,6 +30,8 @@ if [ -x "${BLENDER_EXE}" ]; then
CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DIST_LIB="$CDIR/distlib"
+ # set Debug Mode flag
+ export RPR_BLENDER_DEBUG=1
export LD_LIBRARY_PATH="$DIST_LIB"
python3 cmd_tools/run_blender.py "$BLENDER_EXE" cmd_tools/test_rpr.py "$DEBUGGER_EXE"
diff --git a/src/RPRBlender.pyproj b/src/RPRBlender.pyproj
index a340ab1e..18ca9474 100644
--- a/src/RPRBlender.pyproj
+++ b/src/RPRBlender.pyproj
@@ -47,6 +47,7 @@
+
diff --git a/src/bindings/pyrpr/rpr.py b/src/bindings/pyrpr/rpr.py
index 2773b101..ef8e0210 100644
--- a/src/bindings/pyrpr/rpr.py
+++ b/src/bindings/pyrpr/rpr.py
@@ -42,7 +42,9 @@ def export(json_file_name, dependencies, header_file_name, cffi_name, output_nam
ffi.cdef(Path('rprapi.h').read_text())
- lib_names = ['RadeonProRender64', 'RadeonImageFilters', 'python37']
+
+ lib_names = ['RadeonProRender64', 'RadeonImageFilters',
+ f'python{sys.version_info.major}{sys.version_info.minor}']
inc_dir = [str(base / "src/bindings/pyrpr"),
str(rpr_sdk['inc']),
@@ -89,7 +91,11 @@ def export(json_file_name, dependencies, header_file_name, cffi_name, output_nam
shutil.copy(_cffi_backend.__file__, str(build_dir))
if 'Linux' == platform.system():
- for path in (Path(_cffi_backend.__file__).parent / '.libs_cffi_backend').iterdir():
+ cffi_libs_dir = Path(_cffi_backend.__file__).parent / '.libs_cffi_backend'
+ if not cffi_libs_dir.is_dir():
+ cffi_libs_dir = Path(_cffi_backend.__file__).parent / 'cffi.libs'
+
+ for path in cffi_libs_dir.iterdir():
if '.so' in path.suffixes:
# copy library needed for cffi backend
ffi_lib = str(path)
@@ -133,10 +139,8 @@ def write_api(api_desc_fpath, f, abi_mode):
else:
print('typedef ', t.type, name, ';', file=f)
for name, t in api.functions.items():
- if 'rprxGetLog' == name:
- continue
if 'rifContextExecuteCommandQueue' == name:
- print('rif_int rifContextExecuteCommandQueue(rif_context context, rif_command_queue command_queue, void *executeFinishedCallbackFunction(void* userdata), void* data, float* time);', file=f)
+ print('rif_int rifContextExecuteCommandQueue(rif_context context, rif_command_queue command_queue, void *executeFinishedCallbackFunction(void* userdata), void* data, rif_performance_statistic* statistics);', file=f)
continue
print(name, [(arg.name, arg.type) for arg in t.args])
print(t.restype, name, '(' + ', '.join(arg.type + ' ' + arg.name for arg in t.args) + ');', file=f)
diff --git a/src/bindings/pyrpr/src/pyhybrid.py b/src/bindings/pyrpr/src/pyhybrid.py
index a1e338c0..8f34aab2 100644
--- a/src/bindings/pyrpr/src/pyhybrid.py
+++ b/src/bindings/pyrpr/src/pyhybrid.py
@@ -132,6 +132,16 @@ class Camera(pyrpr.Camera):
pass
+@class_ignore_unsupported
+class ImageData(pyrpr.ImageData):
+ pass
+
+
+@class_ignore_unsupported
+class ImageFile(pyrpr.ImageFile):
+ pass
+
+
@class_ignore_unsupported
class MaterialNode(pyrpr.MaterialNode):
def set_input(self, name, value):
diff --git a/src/bindings/pyrpr/src/pyrpr.py b/src/bindings/pyrpr/src/pyrpr.py
index e3bd96d2..5f80cd03 100644
--- a/src/bindings/pyrpr/src/pyrpr.py
+++ b/src/bindings/pyrpr/src/pyrpr.py
@@ -12,26 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#********************************************************************
-import math
import platform
import traceback
import inspect
import ctypes
-import os
import time
import functools
import sys
import numpy as np
-import bgl
from typing import List
+import bgl
+
import pyrprwrap
from pyrprwrap import *
-lib_wrapped_log_calls = False
-
-
class CoreError(Exception):
def __init__(self, status, func_name, argv, module_name):
super().__init__()
@@ -95,67 +91,34 @@ def wrapped(*argv):
class _init_data:
- _log_fun = None
+ log_fun = None
+ lib_wrapped_log_calls = False
-def init(log_fun, rprsdk_bin_path=None):
+def init(lib_dir, log_fun, lib_wrapped_log_calls):
+ _init_data.log_fun = log_fun
+ _init_data.lib_wrapped_log_calls = lib_wrapped_log_calls
- _module = __import__(__name__)
+ lib_name = {
+ 'Windows': "RadeonProRender64.dll",
+ 'Linux': "libRadeonProRender64.so",
+ 'Darwin': "libRadeonProRender64.dylib"
+ }[platform.system()]
- _init_data._log_fun = log_fun
-
- alternate_relative_paths = []
- if platform.system() == "Windows":
- alternate_relative_paths += ["../../rif/bin"]
- lib_names = [
- 'RadeonProRender64.dll',
- 'RadeonImageFilters.dll',
- ]
-
- elif platform.system() == "Linux":
- lib_names = [
- 'libRadeonProRender64.so',
- ]
-
- elif platform.system() == "Darwin":
- lib_names = [
- 'libRadeonProRender64.dylib',
- ]
-
- else:
- raise ValueError("Not supported OS", platform.system())
-
- for lib_name in lib_names:
- rpr_lib_path = rprsdk_bin_path / lib_name
- if os.path.isfile(str(rpr_lib_path)):
- ctypes.CDLL(str(rpr_lib_path))
- else:
- found = False
- for relpath in alternate_relative_paths:
- rpr_lib_path = rprsdk_bin_path / relpath / lib_name
- if os.path.isfile(str(rpr_lib_path)):
- try:
- ctypes.CDLL(str(rpr_lib_path))
- except OSError as e:
- print(f"Failed to load '{rpr_lib_path}': {str(e)}")
- raise
- found = True
- break
-
- if not found:
- print("Shared lib does not exists \"%s\"\n" % lib_name)
- assert False
+ ctypes.CDLL(str(lib_dir / lib_name))
import __rpr
try:
lib = __rpr.lib
except AttributeError:
- lib = __rpr.ffi.dlopen(str(rprsdk_bin_path/lib_names[0]))
+ lib = __rpr.ffi.dlopen(str(lib_dir / lib_name))
pyrprwrap.lib = lib
pyrprwrap.ffi = __rpr.ffi
global ffi
ffi = __rpr.ffi
+ _module = __import__(__name__)
+
for name in pyrprwrap._constants_names:
setattr(_module, name, getattr(pyrprwrap, name))
@@ -197,38 +160,6 @@ def get_first_gpu_id_used(creation_flags):
raise IndexError("GPU is not used", creation_flags)
-class array:
- def __init__(self, a: np.array):
- self.array = a if a.flags['C_CONTIGUOUS'] else np.ascontiguousarray(a)
-
- def __eq__(self, other):
- return np.array_equal(self.array, other.array)
-
- @property
- def nbytes(self):
- return self.array[0].nbytes
-
- @property
- def len(self):
- return len(self.array)
-
- @property
- def data(self):
- if self.array.dtype == np.float32:
- return ffi.cast('float*', self.array.ctypes.data)
-
- if self.array.dtype == np.int32:
- return ffi.cast('rpr_int*', self.array.ctypes.data)
-
- if self.array.dtype == np.int64:
- return ffi.cast('size_t*', self.array.ctypes.data)
-
- raise KeyError("Not correct dtype of np.array", self.array.dtype)
-
- def __repr__(self):
- return 'pyrpr.' + repr(self.array)
-
-
class Object:
core_type_name = 'void*'
@@ -240,11 +171,11 @@ def __del__(self):
try:
self.delete()
except:
- _init_data._log_fun('EXCEPTION:', traceback.format_exc())
+ _init_data.log_fun('EXCEPTION:', traceback.format_exc())
def delete(self):
- if lib_wrapped_log_calls:
- _init_data._log_fun('delete: ', self.name, self)
+ if _init_data.lib_wrapped_log_calls:
+ _init_data.log_fun('delete: ', self.name, self)
if self._get_handle():
ObjectDelete(self._get_handle())
@@ -644,6 +575,9 @@ def set_vertex_colors(self, colors):
def set_id(self, id):
ShapeSetObjectID(self, id)
+ def set_contour_ignore(self, ignore_in_contour):
+ ShapeSetContourIgnore(self, ignore_in_contour)
+
class Curve(Object):
core_type_name = 'rpr_curve'
@@ -721,11 +655,11 @@ def set_transform(self, transform:np.array, transpose=True): # Blender needs mat
class Mesh(Shape):
def __init__(self, context, vertices, normals, uvs: List[np.array],
vertex_indices, normal_indices, uv_indices: List[np.array],
- num_face_vertices):
+ num_face_vertices, mesh_info):
super().__init__(context)
self.poly_count = len(num_face_vertices)
- if len(uvs) > 1:
+ if len(uvs) > 1 or mesh_info:
# several UVs set present
texcoords_layers_num = len(uvs)
texcoords_uvs = ffi.new("float *[]", texcoords_layers_num)
@@ -741,7 +675,15 @@ def __init__(self, context, vertices, normals, uvs: List[np.array],
texcoords_ind[i] = ffi.cast('rpr_int *', uv_indices[i].ctypes.data)
texcoords_ind_nbytes[i] = uv_indices[i][0].nbytes
- ContextCreateMeshEx(
+ mesh_info_ptr = ffi.new(f"rpr_mesh_info[{2 * len(mesh_info) + 1}]")
+ i = 0
+ for key, val in mesh_info.items():
+ mesh_info_ptr[i] = key
+ mesh_info_ptr[i + 1] = val
+ i += 2
+ mesh_info_ptr[i] = 0
+
+ ContextCreateMeshEx2(
self.context,
ffi.cast("float *", vertices.ctypes.data), len(vertices), vertices[0].nbytes,
ffi.cast("float *", normals.ctypes.data), len(normals), normals[0].nbytes,
@@ -753,6 +695,7 @@ def __init__(self, context, vertices, normals, uvs: List[np.array],
ffi.cast('rpr_int*', normal_indices.ctypes.data), normal_indices[0].nbytes,
texcoords_ind, ffi.cast('rpr_int*', texcoords_ind_nbytes.ctypes.data),
ffi.cast('rpr_int*', num_face_vertices.ctypes.data), len(num_face_vertices),
+ mesh_info_ptr,
self
)
@@ -1391,6 +1334,9 @@ def set_wrap(self, wrap_type):
def set_colorspace(self, colorspace):
ImageSetOcioColorspace(self, encode(colorspace))
+ def set_compression(self, compression):
+ ImageSetInternalCompression(self, compression)
+
@property
def size_byte(self):
if self._size_byte is None:
diff --git a/src/bindings/pyrpr/src/pyrpr2.py b/src/bindings/pyrpr/src/pyrpr2.py
index 01307515..53845ce0 100644
--- a/src/bindings/pyrpr/src/pyrpr2.py
+++ b/src/bindings/pyrpr/src/pyrpr2.py
@@ -39,6 +39,12 @@ def render_update_callback(progress, data):
self.render_update_callback = None
+class Camera(pyrpr.Camera):
+ def set_motion_transform(self, transform, transpose=True): # Blender needs matrix to be transposed
+ pyrpr.CameraSetMotionTransformCount(self, 1)
+ pyrpr.CameraSetMotionTransform(self, transpose, pyrpr.ffi.cast('float*', transform.ctypes.data), 1)
+
+
class Shape(pyrpr.Shape):
def set_motion_transform(self, transform, transpose=True): # Blender needs matrix to be transposed
pyrpr.ShapeSetMotionTransformCount(self, 1)
diff --git a/src/bindings/pyrpr/src/pyrpr_load_store.py b/src/bindings/pyrpr/src/pyrpr_load_store.py
index e420a9ea..333d9427 100644
--- a/src/bindings/pyrpr/src/pyrpr_load_store.py
+++ b/src/bindings/pyrpr/src/pyrpr_load_store.py
@@ -19,12 +19,16 @@
lib = None
-def init(rpr_sdk_bin_path):
-
+def init(lib_dir):
global lib
- path = get_library_path(rpr_sdk_bin_path)
- lib = ffi.dlopen(path)
+ lib_name = {
+ 'Windows': "RprLoadStore64.dll",
+ 'Linux': "libRprLoadStore64.so",
+ 'Darwin': "libRprLoadStore64.dylib"
+ }[platform.system()]
+
+ lib = ffi.dlopen(str(lib_dir / lib_name))
def export(name, context, scene, flags):
@@ -36,17 +40,3 @@ def export(name, context, scene, flags):
# note: without any of above flags images will not be exported.
return lib.rprsExport(pyrpr.encode(name), context._get_handle(), scene._get_handle(),
0, ffi.NULL, ffi.NULL, 0, ffi.NULL, ffi.NULL, flags)
-
-
-def get_library_path(rpr_sdk_bin_path):
-
- os = platform.system()
-
- if os == "Windows":
- return str(rpr_sdk_bin_path / 'RprLoadStore64.dll')
- elif os == "Linux":
- return str(rpr_sdk_bin_path / 'libRprLoadStore64.so')
- elif os == "Darwin":
- return str(rpr_sdk_bin_path / 'libRprLoadStore64.dylib')
- else:
- assert False
diff --git a/src/bindings/pyrpr/src/pyrprapi.py b/src/bindings/pyrpr/src/pyrprapi.py
index 9fa6ddf3..c2a90155 100644
--- a/src/bindings/pyrpr/src/pyrprapi.py
+++ b/src/bindings/pyrpr/src/pyrprapi.py
@@ -670,7 +670,12 @@ def get_rif_sdk(base=Path()):
'rprContextCreateCompressedImage_func',
'rpr_compressed_format',
'rpr_comressed_image_desc',
- 'RPR_CONTEXT_CREATE_COMPRESSED_IMAGE',]
+ 'RPR_CONTEXT_CREATE_COMPRESSED_IMAGE',
+ 'rpr_framebuffer_type',
+ 'RPR_UV_CAMERA_SET_CHART_INDEX_FUNC_NAME',
+ 'RPR_CONTEXT_CREATE_FRAMEBUFFER_TYPED_FUNC_NAME',
+ 'RPR_MATERIAL_SET_INPUT_BY_S_KEY_FUNC_NAME',
+ 'RPR_MATERIALX_SET_ADDRESS_FUNC_NAME',]
)
export(
@@ -681,7 +686,9 @@ def get_rif_sdk(base=Path()):
'constant': ['RIF_', 'VERSION_', 'COMMIT_'],
},
castxml,
- exclude=['RIF_DEPRECATED', 'RIF_MAKE_VERSION', 'RIF_API_VERSION', 'VERSION_BUILD']
+ exclude=['RIF_DEPRECATED', 'RIF_MAKE_VERSION', 'RIF_API_VERSION', 'VERSION_BUILD',
+ 'RIF_STRINGIFY2(s)', 'RIF_STRINGIFY(s)',
+ 'rif_logger_desc', 'rifLoggerAttach']
)
# export(rpr_header_gltf, includes_gltf, json_file_name_gltf,
diff --git a/src/bindings/pyrpr/src/pyrprimagefilters.py b/src/bindings/pyrpr/src/pyrprimagefilters.py
index d49b6d8c..012b94f1 100644
--- a/src/bindings/pyrpr/src/pyrprimagefilters.py
+++ b/src/bindings/pyrpr/src/pyrprimagefilters.py
@@ -14,8 +14,8 @@
#********************************************************************
import platform
import traceback
-import os
-from abc import ABCMeta, abstractmethod
+import ctypes
+from abc import ABCMeta
import numpy as np
import pyrprimagefilterswrap
@@ -25,19 +25,15 @@
import bgl
-lib_wrapped_log_calls = False
-
class _init_data:
- _log_fun = None
-
-
-def init(log_fun, rprsdk_bin_path):
- _module = __import__(__name__)
+ log_fun = None
+ lib_wrapped_log_calls = False
- _init_data._log_fun = log_fun
- rel_path = "../../rif/bin"
+def init(lib_dir, log_fun, lib_wrapped_log_calls):
+ _init_data.log_fun = log_fun
+ _init_data.lib_wrapped_log_calls = lib_wrapped_log_calls
lib_name = {
'Windows': "RadeonImageFilters.dll",
@@ -45,21 +41,23 @@ def init(log_fun, rprsdk_bin_path):
'Darwin': "libRadeonImageFilters.dylib"
}[platform.system()]
- import __imagefilters
+ if platform.system() == 'Windows':
+ ctypes.CDLL(str(lib_dir / "RadeonML.dll"))
+ ctypes.CDLL(str(lib_dir / lib_name))
+ import __imagefilters
try:
lib = __imagefilters.lib
except AttributeError:
- lib_path = str(rprsdk_bin_path / lib_name)
- if not os.path.isfile(lib_path):
- lib_path = str(rprsdk_bin_path / rel_path / lib_name)
- lib = __imagefilters.ffi.dlopen(lib_path)
+ lib = __imagefilters.ffi.dlopen(str(lib_dir / lib_name))
pyrprimagefilterswrap.lib = lib
pyrprimagefilterswrap.ffi = __imagefilters.ffi
global ffi
ffi = __imagefilters.ffi
+ _module = __import__(__name__)
+
for name in pyrprimagefilterswrap._constants_names:
setattr(_module, name, getattr(pyrprimagefilterswrap, name))
@@ -89,13 +87,13 @@ def __del__(self):
try:
self.delete()
except:
- _init_data._log_fun('EXCEPTION:', traceback.format_exc())
+ _init_data.log_fun('EXCEPTION:', traceback.format_exc())
def delete(self):
if self._handle_ptr and self._get_handle():
- if lib_wrapped_log_calls:
- assert _init_data._log_fun
- _init_data._log_fun('delete: ', self)
+ if _init_data.lib_wrapped_log_calls:
+ assert _init_data.log_fun
+ _init_data.log_fun('delete: ', self)
ObjectDelete(self._get_handle())
self._reset_handle()
diff --git a/src/rprblender/__init__.py b/src/rprblender/__init__.py
index 12ca7aec..a45511dd 100644
--- a/src/rprblender/__init__.py
+++ b/src/rprblender/__init__.py
@@ -20,7 +20,7 @@
bl_info = {
"name": "Radeon ProRender",
"author": "AMD",
- "version": (3, 1, 0),
+ "version": (3, 2, 2),
"blender": (2, 80, 0),
"location": "Info header, render engine menu",
"description": "Radeon ProRender rendering plugin for Blender 2.8x",
@@ -211,6 +211,9 @@ def do_register_pass(aov):
for i in range(3,6):
do_register_pass(cryptomatte_aovs[i])
+ if layer.rpr.use_contour_render:
+ do_register_pass(layer.rpr.contour_info)
+
@bpy.app.handlers.persistent
def on_version_update(*args, **kwargs):
diff --git a/src/rprblender/config.py b/src/rprblender/config.py
index b466aa45..40be584c 100644
--- a/src/rprblender/config.py
+++ b/src/rprblender/config.py
@@ -28,8 +28,6 @@
disable_athena_report = False
clean_athena_files = True
-disable_athena_report = False
-clean_athena_files = True
try:
# configdev.py example for logging setup:
diff --git a/src/rprblender/engine/__init__.py b/src/rprblender/engine/__init__.py
index a4d7a9cf..997002d8 100644
--- a/src/rprblender/engine/__init__.py
+++ b/src/rprblender/engine/__init__.py
@@ -12,9 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#********************************************************************
-
+import os
import sys
-import traceback
from rprblender import config
from rprblender import utils
@@ -23,80 +22,53 @@
log = logging.Log(tag='engine.init')
-def pyrpr_init(bindings_import_path, rprsdk_bin_path):
- log("pyrpr_init: bindings_path=%s, rpr_bin_path=%s" % (bindings_import_path, rprsdk_bin_path))
-
- if bindings_import_path not in sys.path:
- sys.path.append(bindings_import_path)
-
- try:
- import pyrpr
- import pyhybrid
- import pyrpr2
-
- rpr_version = utils.core_ver_str(full=True)
-
- log.info(f"RPR Core version: {rpr_version}")
- pyrpr.lib_wrapped_log_calls = config.pyrpr_log_calls
- pyrpr.init(logging.Log(tag='core'), rprsdk_bin_path=rprsdk_bin_path)
-
- import pyrpr_load_store
- pyrpr_load_store.init(rprsdk_bin_path)
-
- import pyrprimagefilters
- rif_version = utils.rif_ver_str(full=True)
- log.info(f"Image Filters version {rif_version}")
- pyrprimagefilters.lib_wrapped_log_calls = config.pyrprimagefilters_log_calls
- pyrprimagefilters.init(log, rprsdk_bin_path=rprsdk_bin_path)
-
- # import pyrprgltf
- # pyrprgltf.lib_wrapped_log_calls = config.pyrprgltf_log_calls
- # pyrprgltf.init(log, rprsdk_bin_path=rprsdk_bin_path)
-
- except:
- logging.critical(traceback.format_exc(), tag='')
- return False
-
- finally:
- sys.path.remove(bindings_import_path)
+if utils.IS_DEBUG_MODE:
+ project_root = utils.package_root_dir().parent.parent
+ rpr_lib_dir = project_root / '.sdk/rpr/bin'
+ rif_lib_dir = project_root / '.sdk/rif/bin'
- return True
+ if utils.IS_WIN:
+ os.environ['PATH'] = f"{rpr_lib_dir};{rif_lib_dir};" \
+ f"{os.environ.get('PATH', '')}"
+ else:
+ os.environ['LD_LIBRARY_PATH'] = f"{rpr_lib_dir}:{rif_lib_dir}:" \
+ f"{os.environ.get('LD_LIBRARY_PATH', '')}"
+ sys.path.append(str(project_root / "src/bindings/pyrpr/.build"))
+ sys.path.append(str(project_root / "src/bindings/pyrpr/src"))
-if 'pyrpr' not in sys.modules:
+else:
+ rpr_lib_dir = rif_lib_dir = utils.package_root_dir()
+ if utils.IS_WIN:
+ os.environ['PATH'] = f"{rpr_lib_dir};{os.environ.get('PATH', '')}"
+ else:
+ os.environ['LD_LIBRARY_PATH'] = f"{rpr_lib_dir}:{os.environ.get('LD_LIBRARY_PATH', '')}"
- # try loading pyrpr for installed addon
- bindings_import_path = str(utils.package_root_dir())
- rprsdk_bin_path = utils.package_root_dir()
- if not pyrpr_init(bindings_import_path, rprsdk_bin_path):
- logging.warn("Failed to load rpr from %s. One more attempt will be provided." % bindings_import_path)
+ sys.path.append(str(utils.package_root_dir()))
- # try loading pyrpr from source
- src = utils.package_root_dir().parent
- project_root = src.parent
- rprsdk_bin_path = project_root / ".sdk/rpr/bin"
+import pyrpr
+import pyhybrid
+import pyrpr2
- bindings_import_path = str(src / 'bindings/pyrpr/.build')
- pyrpr_import_path = str(src / 'bindings/pyrpr/src')
+pyrpr.init(rpr_lib_dir, logging.Log(tag='core'), config.pyrpr_log_calls)
+log.info("Core version:", utils.core_ver_str(full=True))
- if bindings_import_path not in sys.path:
- sys.path.append(pyrpr_import_path)
+import pyrpr_load_store
+pyrpr_load_store.init(rpr_lib_dir)
- try:
- assert pyrpr_init(bindings_import_path, rprsdk_bin_path)
- finally:
- sys.path.remove(pyrpr_import_path)
+import pyrprimagefilters
+pyrprimagefilters.init(rif_lib_dir, logging.Log(tag='rif'), config.pyrprimagefilters_log_calls)
+log.info("RIF version:", utils.rif_ver_str(full=True))
- logging.info('rprsdk_bin_path:', rprsdk_bin_path)
-
-
-import pyrpr
-import pyhybrid
-import pyrpr2
+from rprblender.utils import helper_lib
+helper_lib.init()
def register_plugins():
+ rprsdk_bin_path = utils.package_root_dir() if not utils.IS_DEBUG_MODE else \
+ utils.package_root_dir().parent.parent / '.sdk/rpr/bin'
+
def register_plugin(ContextCls, lib_name, cache_path):
lib_path = rprsdk_bin_path / lib_name
ContextCls.register_plugin(lib_path, cache_path)
@@ -133,9 +105,6 @@ def register_plugin(ContextCls, lib_name, cache_path):
except RuntimeError as err:
log.warn(err)
-# we do import of helper_lib just to load RPRBlenderHelper.dll at this stage
-import rprblender.utils.helper_lib
-
register_plugins()
diff --git a/src/rprblender/engine/context.py b/src/rprblender/engine/context.py
index 366eaf1c..4ee58ed1 100644
--- a/src/rprblender/engine/context.py
+++ b/src/rprblender/engine/context.py
@@ -25,6 +25,8 @@ class RPRContext:
_Scene = pyrpr.Scene
_MaterialNode = pyrpr.MaterialNode
+ _ImageData = pyrpr.ImageData
+ _ImageFile = pyrpr.ImageFile
_PointLight = pyrpr.PointLight
_SphereLight = pyrpr.PointLight # RPR 2.0 only feature, use PointLight instead
@@ -63,6 +65,10 @@ def __init__(self):
self.do_motion_blur = False
self.engine_type = None
+
+ # motion data cache. each object key has a {transform: ... , deformation: ...}
+ self.transform_cache = {}
+ self.deformation_cache = {}
# TODO: probably better make nodes more close to materials in one data structure
self.material_nodes = {}
@@ -80,7 +86,10 @@ def __init__(self):
self.use_reflection_catcher = False
self.use_transparent_background = False
- def init(self, context_flags, context_props, use_contour_integrator=False):
+ # texture compression used when images created
+ self.texture_compression = False
+
+ def init(self, context_flags, context_props):
self.context = self._Context(context_flags, context_props)
self.material_system = pyrpr.MaterialSystem(self.context)
self.gl_interop = pyrpr.CREATION_FLAGS_ENABLE_GL_INTEROP in context_flags
@@ -94,9 +103,6 @@ def init(self, context_flags, context_props, use_contour_integrator=False):
# self.context.set_parameter('metalperformanceshader', True)
#self.context.set_parameter('ooctexcache', helpers.get_ooc_cache_size(is_preview))
- if use_contour_integrator:
- self.context.set_parameter(pyrpr.CONTEXT_GPUINTEGRATOR, "gpucontour")
-
self.post_effect = self._PostEffect(self.context, pyrpr.POST_EFFECT_NORMALIZATION)
self.scene = self._Scene(self.context)
@@ -122,6 +128,9 @@ def clear_scene(self):
self.images = {}
+ self.transform_cache = {}
+ self.deformation_cache = {}
+
def render(self, restart=False, tile=None):
if restart:
self.clear_frame_buffers()
@@ -137,6 +146,10 @@ def abort_render(self):
def get_image(self, aov_type=None):
return self.get_frame_buffer(aov_type).get_data()
+ def set_integrator(self, use_contour_integrator):
+ integrator = "gpucontour" if use_contour_integrator else "gpusimple"
+ self.context.set_parameter(pyrpr.CONTEXT_GPUINTEGRATOR, integrator)
+
def get_frame_buffer(self, aov_type=None):
if aov_type is not None:
return self.frame_buffers_aovs[aov_type]['res']
@@ -158,14 +171,7 @@ def resolve(self, aovs=None):
for aov, fbs in self.frame_buffers_aovs.items():
fbs['aov'].resolve(fbs['res'], aov != pyrpr.AOV_SHADOW_CATCHER)
- if self.composite:
- if aovs and pyrpr.AOV_COLOR not in aovs:
- return
-
- color_aov = self.frame_buffers_aovs[pyrpr.AOV_COLOR]
- self.composite.compute(color_aov['composite'])
- if self.gl_interop:
- color_aov['composite'].resolve(color_aov['gl'])
+ self.apply_filters()
def enable_aov(self, aov_type):
if self.is_aov_enabled(aov_type):
@@ -245,10 +251,10 @@ def sync_catchers(self, use_transparent_background=None):
return False
def _enable_catchers(self):
- # Experimentally found the max value of shadow catcher,
- # we'll need it to normalize shadow catcher AOV
- SHADOW_CATCHER_MAX_VALUE = 2.0
+ self.enable_catcher_aovs()
+ self.create_filter_composite()
+ def enable_catcher_aovs(self):
# Enable required AOVs
self.enable_aov(pyrpr.AOV_COLOR)
self.enable_aov(pyrpr.AOV_OPACITY)
@@ -258,6 +264,11 @@ def _enable_catchers(self):
if self.use_reflection_catcher:
self.enable_aov(pyrpr.AOV_REFLECTION_CATCHER)
+ def create_filter_composite(self):
+ # Experimentally found the max value of shadow catcher,
+ # we'll need it to normalize shadow catcher AOV
+ SHADOW_CATCHER_MAX_VALUE = 2.0
+
# Composite frame buffer
self.frame_buffers_aovs[pyrpr.AOV_COLOR]['composite'] = pyrpr.FrameBuffer(
self.context, self.width, self.height)
@@ -267,17 +278,14 @@ def _enable_catchers(self):
self.frame_buffers_aovs[pyrpr.AOV_COLOR]['res'] = pyrpr.FrameBuffer(
self.context, self.width, self.height)
self.frame_buffers_aovs[pyrpr.AOV_COLOR]['res'].set_name('default_res')
-
# Composite calculation elements frame buffers
color = self.create_composite(pyrpr.COMPOSITE_FRAMEBUFFER, {
'framebuffer.input': self.frame_buffers_aovs[pyrpr.AOV_COLOR]['res']
})
-
alpha = self.create_composite(pyrpr.COMPOSITE_FRAMEBUFFER, {
'framebuffer.input': self.frame_buffers_aovs[pyrpr.AOV_OPACITY]['res']
}).get_channel(0)
full_alpha = alpha
-
if self.use_reflection_catcher or self.use_shadow_catcher:
if self.use_reflection_catcher:
reflection_catcher = self.create_composite(pyrpr.COMPOSITE_FRAMEBUFFER, {
@@ -399,7 +407,8 @@ def create_area_light(
self.context,
vertices, normals, uvs,
vertex_indices, normal_indices, uv_indices,
- num_face_vertices
+ num_face_vertices,
+ {}
)
light = self._AreaLight(mesh, self.material_system)
self.objects[key] = light
@@ -409,13 +418,15 @@ def create_mesh(
self, key,
vertices, normals, uvs,
vertex_indices, normal_indices, uv_indices,
- num_face_vertices
+ num_face_vertices,
+ mesh_info={}
):
mesh = self._Mesh(
self.context,
vertices, normals, uvs,
vertex_indices, normal_indices, uv_indices,
- num_face_vertices
+ num_face_vertices,
+ mesh_info
)
self.objects[key] = mesh
return mesh
@@ -451,13 +462,15 @@ def set_material_node_as_material(self, key, material_node):
self.materials[key] = material_node
def create_image_file(self, key, filepath):
- image = pyrpr.ImageFile(self.context, filepath)
+ image = self._ImageFile(self.context, filepath)
+ image.set_compression(self.texture_compression)
if key:
self.images[key] = image
return image
def create_image_data(self, key, data):
- image = pyrpr.ImageData(self.context, data)
+ image = self._ImageData(self.context, data)
+ image.set_compression(self.texture_compression)
if key:
self.images[key] = image
return image
@@ -564,6 +577,13 @@ def remove_material(self, key):
del self.materials[key]
+ def apply_filters(self):
+ if self.composite:
+ color_aov = self.frame_buffers_aovs[pyrpr.AOV_COLOR]
+ self.composite.compute(color_aov['composite'])
+ if self.gl_interop:
+ color_aov['composite'].resolve(color_aov['gl'])
+
class RPRContext2(RPRContext):
""" Manager of pyrpr calls """
@@ -571,6 +591,8 @@ class RPRContext2(RPRContext):
# Classes
_Context = pyrpr2.Context
+ _Camera = pyrpr2.Camera
+
_Mesh = pyrpr2.Mesh
_Instance = pyrpr2.Instance
@@ -579,11 +601,17 @@ class RPRContext2(RPRContext):
_DiskLight = pyrpr2.DiskLight
_PostEffect = pyrpr2.PostEffect
- def init(self, context_flags, context_props, use_contour_integrator=False):
+ def init(self, context_flags, context_props):
context_flags -= {pyrpr.CREATION_FLAGS_ENABLE_GL_INTEROP}
- super().init(context_flags, context_props, use_contour_integrator)
+ super().init(context_flags, context_props)
+
+ def _enable_catchers(self):
+ pass
+
+ def _disable_catchers(self):
+ pass
- def sync_catchers(self, use_transparent_background=False):
+ def apply_filters(self):
pass
def sync_auto_adapt_subdivision(self, width=0, height=0):
diff --git a/src/rprblender/engine/context_hybrid.py b/src/rprblender/engine/context_hybrid.py
index dcf0aef5..5e639aee 100644
--- a/src/rprblender/engine/context_hybrid.py
+++ b/src/rprblender/engine/context_hybrid.py
@@ -29,6 +29,8 @@ class RPRContext(context.RPRContext):
_Scene = pyhybrid.Scene
_MaterialNode = pyhybrid.MaterialNode
+ _ImageData = pyhybrid.ImageData
+ _ImageFile = pyhybrid.ImageFile
_PointLight = pyhybrid.PointLight
_SphereLight = pyhybrid.PointLight
@@ -49,7 +51,7 @@ class RPRContext(context.RPRContext):
_PostEffect = pyhybrid.PostEffect
- def init(self, context_flags, context_props, use_contour_integrator=False):
+ def init(self, context_flags, context_props):
context_flags -= {pyrpr.CREATION_FLAGS_ENABLE_GL_INTEROP}
if context_props[0] == pyrpr.CONTEXT_SAMPLER_TYPE:
context_props = context_props[2:]
diff --git a/src/rprblender/engine/engine.py b/src/rprblender/engine/engine.py
index 66da230b..418b2fb4 100644
--- a/src/rprblender/engine/engine.py
+++ b/src/rprblender/engine/engine.py
@@ -21,15 +21,12 @@
''' main Render object '''
import weakref
-import numpy as np
import bpy
-import mathutils
import pyrpr
from .context import RPRContext
from rprblender.export import object, instance
-from rprblender.properties.view_layer import RPR_ViewLayerProperites
from . import image_filter
from rprblender.utils import logging
@@ -61,69 +58,7 @@ def stop_render(self):
self.rpr_context = None
self.image_filter = None
self.background_filter = None
-
- def _set_render_result(self, render_passes: bpy.types.RenderPasses, apply_image_filter):
- """
- Sets render result to render passes
- :param render_passes: render passes to collect
- :return: images
- """
- def zeros_image(channels):
- return np.zeros((self.rpr_context.height, self.rpr_context.width, channels), dtype=np.float32)
-
- images = []
-
- for p in render_passes:
- # finding corresponded aov
-
- if p.name == "Combined":
- if apply_image_filter and self.image_filter:
- image = self.image_filter.get_data()
-
- if self.background_filter:
- self.update_background_filter_inputs(color_image=image)
- self.background_filter.run()
- image = self.background_filter.get_data()
- else:
- # copying alpha component from rendered image to final denoised image,
- # because image filter changes it to 1.0
- image[:, :, 3] = self.rpr_context.get_image()[:, :, 3]
-
- elif self.background_filter:
- self.update_background_filter_inputs()
- self.background_filter.run()
- image = self.background_filter.get_data()
- else:
- image = self.rpr_context.get_image()
-
- elif p.name == "Color":
- image = self.rpr_context.get_image(pyrpr.AOV_COLOR)
-
- else:
- aovs_info = RPR_ViewLayerProperites.cryptomatte_aovs_info \
- if "Cryptomatte" in p.name else RPR_ViewLayerProperites.aovs_info
- aov = next((aov for aov in aovs_info
- if aov['name'] == p.name), None)
- if aov and self.rpr_context.is_aov_enabled(aov['rpr']):
- image = self.rpr_context.get_image(aov['rpr'])
- else:
- log.warn(f"AOV '{p.name}' is not enabled in rpr_context "
- f"or not found in aovs_info")
- image = zeros_image(p.channels)
-
- if p.channels != image.shape[2]:
- image = image[:, :, 0:p.channels]
-
- images.append(image.flatten())
-
- # efficient way to copy all AOV images
- render_passes.foreach_set('rect', np.concatenate(images))
-
- def update_render_result(self, tile_pos, tile_size, layer_name="",
- apply_image_filter=False):
- result = self.rpr_engine.begin_result(*tile_pos, *tile_size, layer=layer_name)
- self._set_render_result(result.layers[0].passes, apply_image_filter)
- self.rpr_engine.end_result(result)
+ self.upscale_filter = None
def depsgraph_objects(self, depsgraph: bpy.types.Depsgraph, with_camera=False):
""" Iterates evaluated objects in depsgraph with ITERATED_OBJECT_TYPES """
@@ -143,81 +78,33 @@ def depsgraph_instances(self, depsgraph: bpy.types.Depsgraph):
if instance.is_instance and instance.object.type in ITERATED_OBJECT_TYPES:
yield instance
- def sync_motion_blur(self, depsgraph: bpy.types.Depsgraph):
-
- def set_motion_blur(rpr_object, prev_matrix, cur_matrix):
- if hasattr(rpr_object, 'set_motion_transform'):
- rpr_object.set_motion_transform(
- np.array(prev_matrix, dtype=np.float32).reshape(4, 4))
- else:
- velocity = (prev_matrix - cur_matrix).to_translation()
- rpr_object.set_linear_motion(*velocity)
-
- mul_diff = prev_matrix @ cur_matrix.inverted()
-
- quaternion = mul_diff.to_quaternion()
- if quaternion.axis.length > 0.5:
- rpr_object.set_angular_motion(*quaternion.axis, quaternion.angle)
- else:
- rpr_object.set_angular_motion(1.0, 0.0, 0.0, 0.0)
-
- if not isinstance(rpr_object, pyrpr.Camera):
- scale_motion = mul_diff.to_scale() - mathutils.Vector((1, 1, 1))
- rpr_object.set_scale_motion(*scale_motion)
-
- cur_matrices = {}
-
- # getting current frame matrices
- for obj in self.depsgraph_objects(depsgraph, with_camera=True):
- if not obj.rpr.motion_blur:
- continue
+ def cache_blur_data(self, depsgraph: bpy.types.Depsgraph):
+ scene = depsgraph.scene
+ position = scene.cycles.motion_blur_position
- key = object.key(obj)
- rpr_object = self.rpr_context.objects.get(key, None)
- if not rpr_object or not isinstance(rpr_object, (pyrpr.Shape, pyrpr.AreaLight, pyrpr.Camera)):
- continue
+ if position == 'END': # shutter closes at the current frame, so [N-1 .. N]
+ start_frame = scene.frame_current - 1
+ subframe = 0.0
+ elif position == 'START': # shutter opens at the current frame, [N .. N+1]
+ start_frame = scene.frame_current
+ subframe = 0.0
+ else: # 'CENTER' # shutter is opened during current frame, [N-0.5 .. N+0.5]
+ start_frame = scene.frame_current - 1
+ subframe = 0.5
+ end_frame = start_frame + 1
- cur_matrices[key] = obj.matrix_world.copy()
+ # set to next frame and cache blur data
+ self._set_scene_frame(scene, end_frame, subframe)
- for inst in self.depsgraph_instances(depsgraph):
- if not inst.parent.rpr.motion_blur:
- continue
-
- key = instance.key(inst)
- rpr_object = self.rpr_context.objects.get(key, None)
- if not rpr_object or not isinstance(rpr_object, (pyrpr.Shape, pyrpr.AreaLight)):
- continue
-
- cur_matrices[key] = inst.matrix_world.copy()
-
- if not cur_matrices:
- return
-
- cur_frame = depsgraph.scene.frame_current
- prev_frame = cur_frame - 1
-
- # set to previous frame and calculate motion blur data
- self._set_scene_frame(depsgraph.scene, prev_frame, 0.0)
try:
for obj in self.depsgraph_objects(depsgraph, with_camera=True):
- key = object.key(obj)
- cur_matrix = cur_matrices.get(key, None)
- if cur_matrix is None:
- continue
-
- set_motion_blur(self.rpr_context.objects[key], obj.matrix_world, cur_matrix)
+ object.cache_blur_data(self.rpr_context, obj)
for inst in self.depsgraph_instances(depsgraph):
- key = instance.key(inst)
- cur_matrix = cur_matrices.get(key, None)
- if cur_matrix is None:
- continue
-
- set_motion_blur(self.rpr_context.objects[key], inst.matrix_world, cur_matrix)
+ instance.cache_blur_data(self.rpr_context, inst)
finally:
- # restore current frame
- self._set_scene_frame(depsgraph.scene, cur_frame, 0.0)
+ self._set_scene_frame(scene, start_frame, subframe)
def _set_scene_frame(self, scene, frame, subframe=0.0):
self.rpr_engine.frame_set(frame, subframe)
@@ -431,29 +318,67 @@ def setup_background_filter(self, settings):
def _enable_background_filter(self, settings):
width, height = settings['resolution']
+ use_background = settings['use_background']
+ use_shadow = settings['use_shadow']
+ use_reflection = settings['use_reflection']
self.rpr_context.enable_aov(pyrpr.AOV_COLOR)
self.rpr_context.enable_aov(pyrpr.AOV_OPACITY)
inputs = {'color', 'opacity'}
- self.background_filter = image_filter.ImageFilterTransparentBackground(
- self.rpr_context.context, inputs, {}, {}, width, height)
+ if not use_background and not use_reflection:
+ # The RPR2 applies a lonely Shadow catcher as a part of Color AOV, nothing to do here
+ return
+
+ if use_shadow:
+ self.rpr_context.enable_aov(pyrpr.AOV_SHADOW_CATCHER)
+ inputs.add('shadow_catcher')
+ if use_reflection:
+ self.rpr_context.enable_aov(pyrpr.AOV_REFLECTION_CATCHER)
+ inputs.add('reflection_catcher')
+ if use_reflection or use_shadow:
+ self.rpr_context.enable_aov(pyrpr.AOV_BACKGROUND)
+ inputs.add('background')
+
+ params = {'use_background': use_background, 'use_shadow': use_shadow, 'use_reflection': use_reflection}
+
+ self.background_filter = image_filter.ImageFilterTransparentShadowReflectionCatcher(
+ self.rpr_context.context, inputs, {}, params, width, height
+ )
self.background_filter.settings = settings
def _disable_background_filter(self):
self.background_filter = None
- def update_background_filter_inputs(self, tile_pos=(0, 0), color_image=None, opacity_image=None):
+ def update_background_filter_inputs(
+ self, tile_pos=(0, 0),
+ color_image=None, opacity_image=None):
+ """
+ Update background filter input images.
+ Use color_image and opacity_image as source if passed, get from AOV otherwise.
+ Update catchers from AOVs if usage flags are set.
+ """
if color_image is None:
color_image = self.rpr_context.get_image(pyrpr.AOV_COLOR)
+ self.background_filter.update_input('color', color_image, tile_pos)
+
if opacity_image is None:
opacity_image = self.rpr_context.get_image(pyrpr.AOV_OPACITY)
-
- self.background_filter.update_input('color', color_image, tile_pos)
self.background_filter.update_input('opacity', opacity_image, tile_pos)
+ # Catchers are taken directly from AOVs only when needed
+ if self.rpr_context.use_shadow_catcher:
+ shadow_catcher_image = self.rpr_context.get_image(pyrpr.AOV_SHADOW_CATCHER)
+ self.background_filter.update_input('shadow_catcher', shadow_catcher_image, tile_pos)
+ if self.rpr_context.use_reflection_catcher:
+ reflection_catcher_image = self.rpr_context.get_image(pyrpr.AOV_REFLECTION_CATCHER)
+ self.background_filter.update_input('reflection_catcher', reflection_catcher_image, tile_pos)
+ if self.rpr_context.use_shadow_catcher or self.rpr_context.use_reflection_catcher:
+ background_image = self.rpr_context.get_image(pyrpr.AOV_BACKGROUND)
+ self.background_filter.update_input('background', background_image, tile_pos)
+
def setup_upscale_filter(self, settings):
if self.upscale_filter and self.upscale_filter.settings == settings:
return False
diff --git a/src/rprblender/engine/export_engine.py b/src/rprblender/engine/export_engine.py
index 9dd2f79a..351cf8e3 100644
--- a/src/rprblender/engine/export_engine.py
+++ b/src/rprblender/engine/export_engine.py
@@ -49,9 +49,7 @@ def sync(self, context):
self.rpr_context.blender_data['depsgraph'] = depsgraph
scene = depsgraph.scene
- use_contour = scene.rpr.is_contour_used()
-
- scene.rpr.init_rpr_context(self.rpr_context, use_contour_integrator=use_contour)
+ scene.rpr.init_rpr_context(self.rpr_context)
self.rpr_context.scene.set_name(scene.name)
self.rpr_context.width = int(scene.render.resolution_x * scene.render.resolution_percentage / 100)
@@ -59,6 +57,13 @@ def sync(self, context):
world.sync(self.rpr_context, scene.world)
+ # cache blur data
+ self.rpr_context.do_motion_blur = scene.render.use_motion_blur and \
+ not math.isclose(scene.camera.data.rpr.motion_blur_exposure, 0.0)
+ if self.rpr_context.do_motion_blur:
+ self.cache_blur_data(depsgraph)
+ self.set_motion_blur_mode(scene)
+
# camera, objects, particles
for obj in self.depsgraph_objects(depsgraph, with_camera=True):
indirect_only = obj.original.indirect_only_get(view_layer=depsgraph.view_layer)
@@ -74,6 +79,7 @@ def sync(self, context):
# rpr_context parameters
self.rpr_context.set_parameter(pyrpr.CONTEXT_PREVIEW, False)
scene.rpr.export_ray_depth(self.rpr_context)
+ self.rpr_context.texture_compression = scene.rpr.texture_compression
# EXPORT CAMERA
camera_key = object.key(scene.camera) # current camera key
@@ -87,15 +93,11 @@ def sync(self, context):
self.rpr_context.width / self.rpr_context.height)
camera_data.export(rpr_camera)
- # sync Motion Blur
- self.rpr_context.do_motion_blur = scene.render.use_motion_blur and \
- not math.isclose(scene.camera.data.rpr.motion_blur_exposure, 0.0)
-
if self.rpr_context.do_motion_blur:
- self.sync_motion_blur(depsgraph)
rpr_camera.set_exposure(scene.camera.data.rpr.motion_blur_exposure)
- self.set_motion_blur_mode(scene)
-
+ object.export_motion_blur(self.rpr_context, camera_key,
+ object.get_transform(camera_obj))
+
# adaptive subdivision will be limited to the current scene render size
self.rpr_context.enable_aov(pyrpr.AOV_COLOR)
self.rpr_context.sync_auto_adapt_subdivision()
diff --git a/src/rprblender/engine/image_filter.py b/src/rprblender/engine/image_filter.py
index 624526c3..abf24768 100644
--- a/src/rprblender/engine/image_filter.py
+++ b/src/rprblender/engine/image_filter.py
@@ -122,9 +122,7 @@ def setup_alpha_filter(self, alpha):
GET_COORD_OR_RETURN(coord, GET_BUFFER_SIZE(outputImage));
vec4 pixel = ReadPixelTyped(inputImage, coord.x, coord.y);
vec4 pixel_alpha = ReadPixelTyped(alphaBuf, coord.x, coord.y);
- pixel.x *= pixel_alpha.x;
- pixel.y *= pixel_alpha.x;
- pixel.z *= pixel_alpha.x;
+ pixel.xyz *= pixel_alpha.x;
pixel.w = pixel_alpha.x;
WritePixelTyped(outputImage, coord.x, coord.y, pixel);
"""
@@ -200,7 +198,7 @@ def _create_filter(self):
if not models_path.is_dir():
# set alternative path
models_path = utils.package_root_dir() / '../../.sdk/rif/models'
- self.filter.set_parameter('modelPath', str(models_path))
+ self.filter.set_parameter('modelPath', str(models_path.resolve()))
ml_output_image = self.context.create_image(self.width, self.height, 3)
@@ -310,9 +308,75 @@ def _create_filter(self):
if not models_path.is_dir():
# set alternative path
models_path = utils.package_root_dir() / '../../.sdk/rif/models'
- self.filter.set_parameter('modelPath', str(models_path))
+ self.filter.set_parameter('modelPath', str(models_path.resolve()))
- self.filter.set_parameter('mode', rif.AI_UPSCALE_MODE_BEST_2X)
+ self.filter.set_parameter('mode', rif.AI_UPSCALE_MODE_FAST_2X)
+ self.filter.set_parameter('useHDR', True)
self.output_image = self.context.create_image(self.width * 2, self.height * 2)
self.command_queue.attach_image_filter(self.filter, self.inputs['color'], self.output_image)
+
+
+class ImageFilterTransparentShadowReflectionCatcher(ImageFilter):
+ """ Calculate combination of shadow and reflection catchers, applies transparent background if needed """
+
+ def _create_filter(self):
+ """ Calculate reflection using reflection catcher and integrate it to color result """
+
+ use_background = self.params.get('use_background', False)
+ use_shadow = self.params.get('use_shadow', False)
+ use_reflection = self.params.get('use_reflection', False)
+
+ self.filter = self.context.create_filter(rif.IMAGE_FILTER_USER_DEFINED)
+
+ # only the outputImage is opened for writing in the USER_DEFINED filter, so work will be done in a single pass
+ # for this to work the filter code multi-string is combined here
+ code = """
+int2 coord;
+GET_COORD_OR_RETURN(coord, GET_BUFFER_SIZE(outputImage));
+vec4 pixel = ReadPixelTyped(inputImage, coord.x, coord.y);
+vec4 alpha = ReadPixelTyped(alphaImage, coord.x, coord.y);
+ """
+ self.filter.set_parameter('alphaImage', self.inputs['opacity'])
+
+ if use_reflection or use_shadow:
+ code += """
+vec4 background = ReadPixelTyped(backgroundImage, coord.x, coord.y);
+ """
+ self.filter.set_parameter('backgroundImage', self.inputs['background'])
+
+ if use_reflection:
+ code += """
+vec4 reflection = ReadPixelTyped(reflectionImage, coord.x, coord.y);
+alpha.x += reflection.x;
+ """
+ self.filter.set_parameter('reflectionImage', self.inputs['reflection_catcher'])
+
+ code += """
+pixel.xyz = background.xyz * (1.0f - alpha.x) + pixel.xyz * alpha.x;
+ """
+
+ if use_shadow:
+ # note: "shadow.x / 2.0f" doesn't work correctly, used "* 0.5f" instead
+ code += """
+vec4 shadow = ReadPixelTyped(shadowImage, coord.x, coord.y);
+float normalized = min(shadow.x * 0.5f, 1.0f);
+pixel.xyz = pixel.xyz * (1.0f - normalized);
+alpha.x = min(alpha.x + normalized, 1.0f);
+ """
+ self.filter.set_parameter('shadowImage', self.inputs['shadow_catcher'])
+
+ # apply transparent background if needed
+ if use_background:
+ code += """
+pixel.xyz *= alpha.x;
+pixel.w = alpha.x;
+ """
+
+ # save calculations result to output
+ code += """
+WritePixelTyped(outputImage, coord.x, coord.y, pixel);
+ """
+
+ self.filter.set_parameter('code', code)
+ self.command_queue.attach_image_filter(self.filter, self.inputs['color'], self.output_image)
diff --git a/src/rprblender/engine/preview_engine.py b/src/rprblender/engine/preview_engine.py
index 212420a9..701b3e4e 100644
--- a/src/rprblender/engine/preview_engine.py
+++ b/src/rprblender/engine/preview_engine.py
@@ -22,6 +22,7 @@
log = logging.Log(tag='PreviewEngine')
+
CONTEXT_LIFETIME = 300.0 # 5 minutes in seconds
@@ -61,21 +62,28 @@ def render(self):
return
log(f"Start render [{self.rpr_context.width}, {self.rpr_context.height}]")
+ result = self.rpr_engine.begin_result(0, 0, self.rpr_context.width, self.rpr_context.height)
sample = 0
- while sample < self.render_samples:
- if self.rpr_engine.test_break():
- break
- update_samples = min(self.render_update_samples, self.render_samples - sample)
+ try:
+ while sample < self.render_samples:
+ if self.rpr_engine.test_break():
+ break
+
+ update_samples = min(self.render_update_samples, self.render_samples - sample)
+
+ log(f" samples: {sample} +{update_samples} / {self.render_samples}")
+ self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, update_samples)
+ self.rpr_context.render(restart=(sample == 0))
+ self.rpr_context.resolve()
- log(f" samples: {sample} +{update_samples} / {self.render_samples}")
- self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, update_samples)
- self.rpr_context.render(restart=(sample == 0))
- self.rpr_context.resolve()
- self.update_render_result((0, 0), (self.rpr_context.width,
- self.rpr_context.height))
+ image = self.rpr_context.get_image()
+ result.layers[0].passes.foreach_set('rect', image.flatten())
+ self.rpr_engine.update_result(result)
- sample += update_samples
+ sample += update_samples
+ finally:
+ self.rpr_engine.end_result(result)
# clearing scene after finishing render
self.rpr_context.clear_scene()
@@ -113,6 +121,7 @@ def sync(self, depsgraph):
self.rpr_context.set_parameter(pyrpr.CONTEXT_PREVIEW, True)
settings_scene.rpr.export_ray_depth(self.rpr_context)
settings_scene.rpr.export_pixel_filter(self.rpr_context)
+ self.rpr_context.texture_compression = settings_scene.rpr.texture_compression
self.render_samples = settings_scene.rpr.viewport_limits.preview_samples
self.render_update_samples = settings_scene.rpr.viewport_limits.preview_update_samples
diff --git a/src/rprblender/engine/render_engine.py b/src/rprblender/engine/render_engine.py
index f7985856..282e511c 100644
--- a/src/rprblender/engine/render_engine.py
+++ b/src/rprblender/engine/render_engine.py
@@ -17,6 +17,7 @@
import datetime
import math
import numpy as np
+import bpy
import pyrpr
@@ -27,6 +28,8 @@
from rprblender.utils.conversion import perfcounter_to_str
from rprblender.utils.user_settings import get_user_settings
from rprblender import bl_info
+from rprblender.properties.view_layer import RPR_ViewLayerProperites
+
from rprblender.utils import logging
log = logging.Log(tag='RenderEngine')
@@ -62,7 +65,11 @@ def __init__(self, rpr_engine):
self.camera_data: camera.CameraData = None
self.tile_order = None
- self.use_contour = False
+ # settings to crontrol the contour render pass
+ # needs_contour_pass means this engine should execute it
+ self.needs_contour_pass = False
+ self.cached_rendered_images = {}
+ self.contour_pass_samples = 0
self.world_backplate = None
@@ -76,6 +83,110 @@ def notify_status(self, progress, info):
self.rpr_engine.update_progress(progress)
self.rpr_engine.update_stats(self.status_title, info)
+ def _update_render_result(self, tile_pos, tile_size, layer_name="",
+ apply_image_filter=False):
+
+ def zeros_image(channels):
+ return np.zeros((self.rpr_context.height, self.rpr_context.width, channels),
+ dtype=np.float32)
+
+ def set_render_result(render_passes: bpy.types.RenderPasses):
+ images = []
+
+ x1, y1 = tile_pos
+ x2, y2 = x1 + tile_size[0], y1 + tile_size[1]
+
+ for p in render_passes:
+ if p.name == "Combined":
+ if apply_image_filter and self.image_filter:
+ image = self.image_filter.get_data()
+
+ if self.background_filter:
+ # calculate background effects on denoised image and cut out by tile size
+ self.update_background_filter_inputs(tile_pos=tile_pos,
+ color_image=image)
+ self.background_filter.run()
+ image = self.background_filter.get_data()[y1:y2, x1:x2, :]
+ else:
+ # copying alpha component from rendered image to final denoised image,
+ # because image filter changes it to 1.0
+ image[:, :, 3] = self.rpr_context.get_image()[:, :, 3]
+
+ elif self.background_filter:
+ # calculate background effects and cut out by tile size
+ self.update_background_filter_inputs(tile_pos=tile_pos)
+ self.background_filter.run()
+ image = self.background_filter.get_data()[y1:y2, x1:x2, :]
+ else:
+ image = self.rpr_context.get_image()
+
+ elif p.name == "Color":
+ image = self.rpr_context.get_image(pyrpr.AOV_COLOR)
+
+ elif p.name == "Outline":
+ image = zeros_image(p.channels)
+
+ else:
+ aovs_info = RPR_ViewLayerProperites.cryptomatte_aovs_info \
+ if "Cryptomatte" in p.name else RPR_ViewLayerProperites.aovs_info
+ aov = next((aov for aov in aovs_info
+ if aov['name'] == p.name), None)
+ if aov and self.rpr_context.is_aov_enabled(aov['rpr']):
+ image = self.rpr_context.get_image(aov['rpr'])
+ elif p.name != 'Outline':
+ log.warn(f"AOV '{p.name}' is not enabled in rpr_context "
+ f"or not found in aovs_info")
+ image = zeros_image(p.channels)
+
+ if p.channels != image.shape[2]:
+ image = image[:, :, 0:p.channels]
+
+ if self.needs_contour_pass:
+ # saving rendered image into cache_rendered_images
+ if p.name not in self.cached_rendered_images:
+ self.cached_rendered_images[p.name] = np.zeros(
+ (self.height, self.width, p.channels), dtype=np.float32)
+
+ self.cached_rendered_images[p.name][y1:y2, x1:x2] = image
+
+ images.append(image.flatten())
+
+ # efficient way to copy all AOV images
+ render_passes.foreach_set('rect', np.concatenate(images))
+
+ result = self.rpr_engine.begin_result(*tile_pos, *tile_size, layer=layer_name, view="")
+ try:
+ set_render_result(result.layers[0].passes)
+
+ finally:
+ self.rpr_engine.end_result(result)
+
+ def _update_render_result_contour(self, tile_pos, tile_size, layer_name=""):
+ def set_render_result(render_passes: bpy.types.RenderPasses):
+ images = []
+
+ x1, y1 = tile_pos
+ x2, y2 = x1 + tile_size[0], y1 + tile_size[1]
+
+ for p in render_passes:
+ if p.name == "Outline":
+ image = self.rpr_context.get_image(pyrpr.AOV_COLOR)
+ else:
+ # getting required rendered image from cached_rendered_images
+ image = self.cached_rendered_images[p.name][y1:y2, x1:x2]
+
+ images.append(image.flatten())
+
+ # efficient way to copy all AOV images
+ render_passes.foreach_set('rect', np.concatenate(images))
+
+ result = self.rpr_engine.begin_result(*tile_pos, *tile_size, layer=layer_name, view="")
+ try:
+ set_render_result(result.layers[0].passes)
+
+ finally:
+ self.rpr_engine.end_result(result)
+
def _render(self):
athena_data = {}
@@ -88,6 +199,8 @@ def _render(self):
if is_adaptive:
all_pixels = active_pixels = self.rpr_context.width * self.rpr_context.height
+ render_update_samples = self.render_update_samples
+
while True:
if self.rpr_engine.test_break():
athena_data['End Status'] = "cancelled"
@@ -98,7 +211,7 @@ def _render(self):
self.rpr_context.get_parameter(pyrpr.CONTEXT_ADAPTIVE_SAMPLING_MIN_SPP)
# if less than update_samples left, use the remainder
- update_samples = min(self.render_update_samples,
+ update_samples = min(render_update_samples,
self.render_samples - self.current_sample)
# we report time/iterations left as fractions if limit enabled
@@ -135,8 +248,8 @@ def _render(self):
if self.background_filter:
self.update_background_filter_inputs()
self.background_filter.run()
- self.update_render_result((0, 0), (self.width, self.height),
- layer_name=self.render_layer_name)
+ self._update_render_result((0, 0), (self.width, self.height),
+ layer_name=self.render_layer_name)
# stop at whichever comes first:
# max samples or max time if enabled or active_pixels == 0
@@ -152,17 +265,25 @@ def _render(self):
break
self.render_iteration += 1
- if self.render_iteration > 1 and self.render_update_samples < MAX_RENDER_ITERATIONS and not self.use_contour:
+ if self.render_iteration > 1 and render_update_samples < MAX_RENDER_ITERATIONS:
# progressively increase update samples up to 32
- self.render_update_samples *= 2
+ render_update_samples *= 2
if self.image_filter:
- self.notify_status(1.0, "Applying denoising final image")
+ self.notify_status(1.0, "Denoising final image")
self.update_image_filter_inputs()
self.image_filter.run()
- self.update_render_result((0, 0), (self.width, self.height),
- layer_name=self.render_layer_name,
- apply_image_filter=True)
+ color_source = self.image_filter.get_data()
+
+ # restore alpha channel
+ alpha_source = self.rpr_context.get_image()
+ color_source[:, :, 3] = alpha_source[:, :, 3]
+ if self.background_filter:
+ self.update_background_filter_inputs(color_image=color_source)
+ self.background_filter.run()
+ self._update_render_result((0, 0), (self.width, self.height),
+ layer_name=self.render_layer_name,
+ apply_image_filter=True)
self.apply_render_stamp_to_image()
@@ -187,6 +308,8 @@ def _render_tiles(self):
athena_data['End Status'] = "successful"
progress = 0.0
+ render_update_samples = self.render_update_samples
+
for tile_index, (tile_pos, tile_size) in enumerate(tile_iterator()):
if self.rpr_engine.test_break():
athena_data['End Status'] = "cancelled"
@@ -213,7 +336,7 @@ def _render_tiles(self):
if self.rpr_engine.test_break():
break
- update_samples = min(self.render_update_samples, self.render_samples - sample)
+ update_samples = min(render_update_samples, self.render_samples - sample)
self.current_render_time = time.perf_counter() - time_begin
progress = (tile_index + sample/self.render_samples) / tiles_number
info_str = f"Render Time: {self.current_render_time:.1f} sec"\
@@ -240,8 +363,8 @@ def _render_tiles(self):
sample += update_samples
self.rpr_context.resolve()
- self.update_render_result(tile_pos, tile_size,
- layer_name=self.render_layer_name)
+ self._update_render_result(tile_pos, tile_size,
+ layer_name=self.render_layer_name)
# store maximum actual number of used samples for render stamp info
self.current_sample = max(self.current_sample, sample)
@@ -255,14 +378,17 @@ def _render_tiles(self):
break
render_iteration += 1
- if render_iteration > 1 and self.render_update_samples < MAX_RENDER_ITERATIONS and not self.use_contour:
+ if render_iteration > 1 and render_update_samples < MAX_RENDER_ITERATIONS:
# progressively increase update samples up to 32
- self.render_update_samples *= 2
+ render_update_samples *= 2
- if self.image_filter and not self.rpr_engine.test_break():
- self.update_image_filter_inputs(tile_pos)
+ if not self.rpr_engine.test_break():
+ if self.image_filter:
+ self.update_image_filter_inputs(tile_pos=tile_pos)
+ if self.background_filter:
+ self.update_background_filter_inputs(tile_pos=tile_pos)
- if self.image_filter and not self.rpr_engine.test_break():
+ if (self.image_filter or self.background_filter) and not self.rpr_engine.test_break():
self.notify_status(1.0, "Applying denoising final image")
# getting already rendered images for every render pass
@@ -284,9 +410,19 @@ def _render_tiles(self):
# we will update only Combined pass
if p.name == "Combined":
- self.image_filter.run()
- image = self.image_filter.get_data()
- images[pos: pos + length] = image.flatten()
+ image = None
+ if self.image_filter:
+ self.image_filter.run()
+ image = self.image_filter.get_data()
+
+ if self.background_filter:
+ if image is not None:
+ self.background_filter.update_input('color', image)
+ self.background_filter.run()
+ image = self.background_filter.get_data()
+
+ if image is not None:
+ images[pos: pos + length] = image.flatten()
break
pos += length
@@ -306,6 +442,85 @@ def _render_tiles(self):
self.athena_send(athena_data)
+ def _render_contour(self):
+ log(f"Doing Outline Pass")
+
+ # set contour settings
+ self.rpr_context.set_parameter(pyrpr.CONTEXT_GPUINTEGRATOR, "gpucontour")
+
+ # enable contour aovs
+ self.rpr_context.disable_aovs()
+ self.rpr_context.resize(self.width, self.height)
+
+ self.rpr_context.enable_aov(pyrpr.AOV_COLOR)
+ self.rpr_context.enable_aov(pyrpr.AOV_OBJECT_ID)
+ self.rpr_context.enable_aov(pyrpr.AOV_MATERIAL_ID)
+ self.rpr_context.enable_aov(pyrpr.AOV_SHADING_NORMAL)
+
+ # setting camera
+ self.camera_data.export(self.rpr_context.scene.camera)
+
+ athena_data = {}
+
+ time_begin = time.perf_counter()
+ athena_data['Start Time'] = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")
+ athena_data['End Status'] = "successful"
+
+ self.current_sample = 0
+
+ while True:
+ if self.rpr_engine.test_break():
+ athena_data['End Status'] = "cancelled"
+ break
+
+ self.current_render_time = time.perf_counter() - time_begin
+
+ # if less than update_samples left, use the remainder
+ update_samples = 1
+
+ # we report time/iterations left as fractions if limit enabled
+ time_str = f"{self.current_render_time:.1f}/{self.render_time}" if self.render_time \
+ else f"{self.current_render_time:.1f}"
+
+ # percent done is one of percent iterations or percent time so pick whichever is greater
+ progress = max(
+ self.current_sample / self.contour_pass_samples,
+ self.current_render_time / self.render_time if self.render_time else 0
+ )
+ info_str = f"Outline Pass | Render Time: {time_str} sec | "\
+ f"Samples: {self.current_sample}/{self.contour_pass_samples}"
+ log_str = f" samples: {self.current_sample} +{update_samples} / {self.contour_pass_samples}"\
+ f", progress: {progress * 100:.1f}%, time: {self.current_render_time:.2f}"
+
+ self.notify_status(progress, info_str)
+
+ log(log_str)
+
+ self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, update_samples)
+ self.rpr_context.set_parameter(pyrpr.CONTEXT_FRAMECOUNT, self.render_iteration)
+ self.rpr_context.render(restart=(self.current_sample == 0))
+
+ self.current_sample += update_samples
+
+ self.rpr_context.resolve()
+ self._update_render_result_contour((0, 0), (self.width, self.height),
+ layer_name=self.render_layer_name)
+
+ if self.current_sample == self.contour_pass_samples:
+ break
+
+ if self.render_time and self.current_render_time >= self.render_time:
+ break
+
+ self.render_iteration += 1
+
+ athena_data['Stop Time'] = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")
+ athena_data['Samples'] = self.current_sample
+
+ log.info(f"Scene synchronization time:", perfcounter_to_str(self.sync_time))
+ log.info(f"Render time:", perfcounter_to_str(self.current_render_time))
+ self.athena_send(athena_data)
+
def render(self):
if not self.is_synced:
return
@@ -321,11 +536,15 @@ def render(self):
else:
self._render()
+ # contour or "Outline" rendering is done as a separate render pass.
+ if self.needs_contour_pass:
+ self._render_contour()
+
self.notify_status(1, "Finish render")
log('Finish render')
def _init_rpr_context(self, scene):
- scene.rpr.init_rpr_context(self.rpr_context, use_contour_integrator=self.use_contour)
+ scene.rpr.init_rpr_context(self.rpr_context)
self.rpr_context.scene.set_name(scene.name)
@@ -346,7 +565,6 @@ def sync(self, depsgraph):
self.notify_status(0, "Start syncing")
- self.use_contour = scene.rpr.is_contour_used()
self._init_rpr_context(scene)
border = ((0, 0), (1, 1)) if not scene.render.use_border else \
@@ -361,45 +579,58 @@ def sync(self, depsgraph):
self.rpr_context.resize(self.width, self.height)
- if self.use_contour:
- scene.rpr.export_contour_mode(self.rpr_context)
+ self.needs_contour_pass = view_layer.rpr.use_contour_render and scene.rpr.render_quality == 'FULL2'
+ if self.needs_contour_pass:
+ view_layer.rpr.contour.export_contour_settings(self.rpr_context)
self.rpr_context.blender_data['depsgraph'] = depsgraph
- # EXPORT OBJECTS
- objects_len = len(depsgraph.objects)
- for i, obj in enumerate(self.depsgraph_objects(depsgraph)):
- self.notify_status(0, "Syncing object (%d/%d): %s" % (i, objects_len, obj.name))
+ # CACHE BLUR DATA
+ self.rpr_context.do_motion_blur = scene.render.use_motion_blur and \
+ not math.isclose(scene.camera.data.rpr.motion_blur_exposure, 0.0)
- # the correct collection visibility info is stored in original object
- indirect_only = obj.original.indirect_only_get(view_layer=view_layer)
- object.sync(self.rpr_context, obj,
- indirect_only=indirect_only, material_override=material_override,
- frame_current=scene.frame_current, use_contour=self.use_contour)
+ cur_frame = scene.frame_current
+ try:
+ if self.rpr_context.do_motion_blur:
+ self.cache_blur_data(depsgraph)
+ self.set_motion_blur_mode(scene)
- if self.rpr_engine.test_break():
- log.warn("Syncing stopped by user termination")
- return
+ # EXPORT OBJECTS
+ objects_len = len(depsgraph.objects)
+ for i, obj in enumerate(self.depsgraph_objects(depsgraph)):
+ self.notify_status(0, "Syncing object (%d/%d): %s" % (i, objects_len, obj.name))
+
+ # the correct collection visibility info is stored in original object
+ indirect_only = obj.original.indirect_only_get(view_layer=view_layer)
+ object.sync(self.rpr_context, obj,
+ indirect_only=indirect_only, material_override=material_override,
+ frame_current=scene.frame_current)
- # EXPORT INSTANCES
- instances_len = len(depsgraph.object_instances)
- last_instances_percent = 0
- self.notify_status(0, "Syncing instances 0%")
+ if self.rpr_engine.test_break():
+ log.warn("Syncing stopped by user termination")
+ return
- for i, inst in enumerate(self.depsgraph_instances(depsgraph)):
- instances_percent = (i * 100) // instances_len
- if instances_percent > last_instances_percent:
- self.notify_status(0, f"Syncing instances {instances_percent}%")
- last_instances_percent = instances_percent
+ # EXPORT INSTANCES
+ instances_len = len(depsgraph.object_instances)
+ last_instances_percent = 0
+ self.notify_status(0, "Syncing instances 0%")
- indirect_only = inst.parent.original.indirect_only_get(view_layer=view_layer)
- instance.sync(self.rpr_context, inst,
- indirect_only=indirect_only, material_override=material_override,
- frame_current=scene.frame_current, use_contour=self.use_contour)
+ for i, inst in enumerate(self.depsgraph_instances(depsgraph)):
+ instances_percent = (i * 100) // instances_len
+ if instances_percent > last_instances_percent:
+ self.notify_status(0, f"Syncing instances {instances_percent}%")
+ last_instances_percent = instances_percent
- if self.rpr_engine.test_break():
- log.warn("Syncing stopped by user termination")
- return
+ indirect_only = inst.parent.original.indirect_only_get(view_layer=view_layer)
+ instance.sync(self.rpr_context, inst,
+ indirect_only=indirect_only, material_override=material_override,
+ frame_current=scene.frame_current)
+
+ if self.rpr_engine.test_break():
+ log.warn("Syncing stopped by user termination")
+ return
+ finally:
+ self._set_scene_frame(scene, cur_frame, 0.0)
self.notify_status(0, "Syncing instances 100%")
@@ -414,8 +645,13 @@ def sync(self, depsgraph):
if not camera_obj:
camera_obj = scene.camera
- self.camera_data = camera.CameraData.init_from_camera(camera_obj.data, camera_obj.matrix_world,
- screen_width / screen_height, border)
+ self.camera_data = camera.CameraData.init_from_camera(
+ camera_obj.data, camera_obj.matrix_world, screen_width / screen_height, border)
+
+ if self.rpr_context.do_motion_blur:
+ rpr_camera.set_exposure(scene.camera.data.rpr.motion_blur_exposure)
+ object.export_motion_blur(self.rpr_context, camera_key,
+ object.get_transform(camera_obj))
if scene.rpr.is_tile_render_available:
if scene.camera.data.type == 'PANO':
@@ -443,25 +679,22 @@ def sync(self, depsgraph):
world_settings = world.sync(self.rpr_context, world_data)
self.world_backplate = world_settings.backplate
- # SYNC MOTION BLUR
- self.rpr_context.do_motion_blur = scene.render.use_motion_blur and \
- not math.isclose(scene.camera.data.rpr.motion_blur_exposure, 0.0)
-
- if self.rpr_context.do_motion_blur:
- self.sync_motion_blur(depsgraph)
- rpr_camera.set_exposure(scene.camera.data.rpr.motion_blur_exposure)
- self.set_motion_blur_mode(scene)
-
# EXPORT PARTICLES
# Note: particles should be exported after motion blur,
# otherwise prev_location of particle will be (0, 0, 0)
self.notify_status(0, "Syncing particles")
for obj in self.depsgraph_objects(depsgraph):
particle.sync(self.rpr_context, obj)
+ if self.rpr_engine.test_break():
+ log.warn("Syncing stopped by user termination")
+ return
# objects linked to scene as a collection are instanced, so walk thru them for particles
for entry in self.depsgraph_instances(depsgraph):
particle.sync(self.rpr_context, entry.instance_object)
+ if self.rpr_engine.test_break():
+ log.warn("Syncing stopped by user termination")
+ return
# EXPORT: AOVS, adaptive sampling, shadow catcher, denoiser
enable_adaptive = scene.rpr.limits.noise_threshold > 0.0
@@ -480,8 +713,12 @@ def sync(self, depsgraph):
# Shadow catcher
if scene.rpr.render_quality != 'FULL':
self.rpr_context.sync_catchers(False)
+ bg_filter_enabled = scene.render.film_transparent or self.rpr_context.use_reflection_catcher # single Shadow Catcher AOV is handled by core
background_filter_settings = {
- 'enable': scene.render.film_transparent,
+ 'enable': bg_filter_enabled,
+ 'use_background': scene.render.film_transparent,
+ 'use_shadow': self.rpr_context.use_shadow_catcher,
+ 'use_reflection': self.rpr_context.use_reflection_catcher,
'resolution': (self.width, self.height),
}
self.setup_background_filter(background_filter_settings)
@@ -492,14 +729,13 @@ def sync(self, depsgraph):
self.rpr_context.set_parameter(pyrpr.CONTEXT_PREVIEW, False)
scene.rpr.export_ray_depth(self.rpr_context)
scene.rpr.export_pixel_filter(self.rpr_context)
+ self.rpr_context.texture_compression = scene.rpr.texture_compression
self.render_samples, self.render_time = (scene.rpr.limits.max_samples, scene.rpr.limits.seconds)
+ self.contour_pass_samples = scene.rpr.limits.contour_render_samples
if scene.rpr.render_quality == 'FULL2':
- if self.use_contour:
- self.render_update_samples = 1
- else:
- self.render_update_samples = scene.rpr.limits.update_samples_rpr2
+ self.render_update_samples = scene.rpr.limits.update_samples_rpr2
else:
self.render_update_samples = scene.rpr.limits.update_samples
diff --git a/src/rprblender/engine/render_engine_2.py b/src/rprblender/engine/render_engine_2.py
index b5fd33bd..3360f422 100644
--- a/src/rprblender/engine/render_engine_2.py
+++ b/src/rprblender/engine/render_engine_2.py
@@ -75,12 +75,13 @@ def do_resolve():
break
self.rpr_context.resolve()
- self.update_render_result((0, 0), (self.width, self.height),
- layer_name=self.render_layer_name)
+ self._update_render_result((0, 0), (self.width, self.height),
+ layer_name=self.render_layer_name)
log('Finish do_resolve')
self.rpr_context.set_render_update_callback(render_update_callback)
+
resolve_thread = threading.Thread(target=do_resolve)
resolve_thread.start()
diff --git a/src/rprblender/engine/viewport_engine.py b/src/rprblender/engine/viewport_engine.py
index 7657e8a1..df3d64de 100644
--- a/src/rprblender/engine/viewport_engine.py
+++ b/src/rprblender/engine/viewport_engine.py
@@ -214,7 +214,6 @@ def __init__(self, rpr_engine):
self.render_time = 0
self.view_mode = None
- self.use_contour = False
self.space_data = None
self.selected_objects = None
@@ -227,6 +226,7 @@ def stop_render(self):
self.rpr_context = None
self.image_filter = None
+ self.upscale_filter = None
def _resolve(self):
self.rpr_context.resolve()
@@ -245,8 +245,6 @@ def _do_sync(self, depsgraph):
self.notify_status("Starting...", "Sync")
time_begin = time.perf_counter()
- self.use_contour = depsgraph.scene.rpr.is_contour_used(is_final_engine=False)
-
# exporting objects
frame_current = depsgraph.scene.frame_current
material_override = depsgraph.view_layer.material_override
@@ -262,7 +260,7 @@ def _do_sync(self, depsgraph):
indirect_only = obj.original.indirect_only_get(view_layer=depsgraph.view_layer)
object.sync(self.rpr_context, obj,
indirect_only=indirect_only, material_override=material_override,
- frame_current=frame_current, use_contour=self.use_contour)
+ frame_current=frame_current)
# exporting instances
instances_len = len(depsgraph.object_instances)
@@ -281,10 +279,22 @@ def _do_sync(self, depsgraph):
indirect_only = inst.parent.original.indirect_only_get(view_layer=depsgraph.view_layer)
instance.sync(self.rpr_context, inst,
indirect_only=indirect_only, material_override=material_override,
- frame_current=frame_current, use_contour=self.use_contour)
+ frame_current=frame_current)
# shadow catcher
- self.rpr_context.sync_catchers(depsgraph.scene.render.film_transparent)
+ if depsgraph.scene.rpr.render_quality != 'FULL': # non-Legacy modes
+ self.rpr_context.sync_catchers(False)
+ bg_filter_enabled = self.rpr_context.use_reflection_catcher or self.rpr_context.use_shadow_catcher
+ background_filter_settings = {
+ 'enable': bg_filter_enabled,
+ 'use_background': depsgraph.scene.render.film_transparent,
+ 'use_shadow': self.rpr_context.use_shadow_catcher,
+ 'use_reflection': self.rpr_context.use_reflection_catcher,
+ 'resolution': (self.width, self.height),
+ }
+ self.setup_background_filter(background_filter_settings)
+ else:
+ self.rpr_context.sync_catchers(depsgraph.scene.render.film_transparent)
self.is_synced = True
@@ -469,10 +479,8 @@ def sync(self, context, depsgraph):
settings = get_user_settings()
use_gl_interop = settings.use_gl_interop and not scene.render.film_transparent
- use_contour = scene.rpr.is_contour_used(is_final_engine=False)
scene.rpr.init_rpr_context(self.rpr_context, is_final_engine=False,
- use_gl_interop=use_gl_interop,
- use_contour_integrator=use_contour)
+ use_gl_interop=use_gl_interop)
self.rpr_context.blender_data['depsgraph'] = depsgraph
@@ -495,19 +503,16 @@ def sync(self, context, depsgraph):
self.world_settings = self._get_world_settings(depsgraph)
self.world_settings.export(self.rpr_context)
- if scene.rpr.is_contour_used(is_final_engine=False):
- scene.rpr.export_contour_mode(self.rpr_context)
-
rpr_camera = self.rpr_context.create_camera()
rpr_camera.set_name("Camera")
self.rpr_context.scene.set_camera(rpr_camera)
# image filter
- self.setup_image_filter(self._get_image_filter_settings())
+ self.setup_image_filter(self._get_image_filter_settings(scene))
# upscale filter
self.setup_upscale_filter({
- 'enable': settings.viewport_denoiser_upscale,
+ 'enable': scene.rpr.viewport_upscale,
'resolution': (self.width, self.height),
})
@@ -516,6 +521,7 @@ def sync(self, context, depsgraph):
self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, 1)
scene.rpr.export_render_mode(self.rpr_context)
scene.rpr.export_ray_depth(self.rpr_context)
+ self.rpr_context.texture_compression = scene.rpr.texture_compression
scene.rpr.export_pixel_filter(self.rpr_context)
self.render_iterations, self.render_time = (viewport_limits.max_samples, 0)
@@ -524,7 +530,6 @@ def sync(self, context, depsgraph):
self.restart_render_event.clear()
self.view_mode = context.mode
- self.use_contour = scene.rpr.is_contour_used(is_final_engine=False)
self.space_data = context.space_data
self.selected_objects = context.selected_objects
self.sync_render_thread = threading.Thread(target=self._do_sync_render, args=(depsgraph,))
@@ -570,11 +575,9 @@ def sync_update(self, context, depsgraph):
self.rpr_context.blender_data['depsgraph'] = depsgraph
# if view mode changed need to sync collections
- use_contour = depsgraph.scene.rpr.is_contour_used(is_final_engine=False)
mode_updated = False
- if self.view_mode != context.mode or self.use_contour != use_contour:
+ if self.view_mode != context.mode:
self.view_mode = context.mode
- self.use_contour = use_contour
mode_updated = True
if not updates and not sync_world and not sync_collection:
@@ -612,8 +615,7 @@ def sync_update(self, context, depsgraph):
update.is_updated_transform,
indirect_only=indirect_only,
material_override=material_override,
- frame_current=frame_current,
- use_contour=self.use_contour)
+ frame_current=frame_current)
is_obj_updated |= is_updated
continue
@@ -641,7 +643,16 @@ def sync_update(self, context, depsgraph):
is_updated |= self.sync_objects_collection(depsgraph)
if is_obj_updated:
- self.rpr_context.sync_catchers()
+ if self.background_filter:
+ self.rpr_context.sync_catchers(False)
+ bg_filter_enabled = self.rpr_context.use_reflection_catcher or self.rpr_context.use_shadow_catcher
+ background_filter_settings = {'enable': bg_filter_enabled, 'use_background': False,
+ 'use_shadow': self.rpr_context.use_shadow_catcher,
+ 'use_reflection': self.rpr_context.use_reflection_catcher,
+ 'resolution': (self.width, self.height)}
+ self.setup_background_filter(background_filter_settings)
+ else:
+ self.rpr_context.sync_catchers()
if is_updated:
self.restart_render_event.set()
@@ -932,8 +943,6 @@ def sync_collection_objects(self, depsgraph, object_keys_to_export, material_ove
res = False
frame_current = depsgraph.scene.frame_current
- use_contour = depsgraph.scene.rpr.is_contour_used(is_final_engine=False)
-
for obj in self.depsgraph_objects(depsgraph):
obj_key = object.key(obj)
if obj_key not in object_keys_to_export:
@@ -944,7 +953,7 @@ def sync_collection_objects(self, depsgraph, object_keys_to_export, material_ove
indirect_only = obj.original.indirect_only_get(view_layer=depsgraph.view_layer)
object.sync(self.rpr_context, obj,
indirect_only=indirect_only, material_override=material_override,
- frame_current=frame_current, use_contour=use_contour)
+ frame_current=frame_current)
else:
assign_materials(self.rpr_context, rpr_obj, obj, material_override)
@@ -957,8 +966,6 @@ def sync_collection_instances(self, depsgraph, object_keys_to_export, material_o
res = False
frame_current = depsgraph.scene.frame_current
- use_contour = depsgraph.scene.rpr.is_contour_used(is_final_engine=False)
-
for inst in self.depsgraph_instances(depsgraph):
instance_key = instance.key(inst)
if instance_key not in object_keys_to_export:
@@ -969,7 +976,7 @@ def sync_collection_instances(self, depsgraph, object_keys_to_export, material_o
indirect_only = inst.parent.original.indirect_only_get(view_layer=depsgraph.view_layer)
instance.sync(self.rpr_context, inst,
indirect_only=indirect_only, material_override=material_override,
- frame_current=frame_current, use_contour=use_contour)
+ frame_current=frame_current)
else:
assign_materials(self.rpr_context, inst_obj, inst.object, material_override=material_override)
@@ -982,8 +989,6 @@ def update_material_on_scene_objects(self, mat, depsgraph):
material_override = depsgraph.view_layer.material_override
frame_current = depsgraph.scene.frame_current
- use_contour = depsgraph.scene.rpr.is_contour_used(is_final_engine=False)
-
if material_override and material_override.name == mat.name:
objects = self.depsgraph_objects(depsgraph)
active_mat = material_override
@@ -1005,15 +1010,14 @@ def update_material_on_scene_objects(self, mat, depsgraph):
if object.key(obj) not in self.rpr_context.objects:
object.sync(self.rpr_context, obj, indirect_only=indirect_only,
- frame_current=frame_current, use_contour=use_contour)
+ frame_current=frame_current)
updated = True
continue
updated |= object.sync_update(self.rpr_context, obj, False, False,
indirect_only=indirect_only,
material_override=material_override,
- frame_current=frame_current,
- use_contour=use_contour)
+ frame_current=frame_current)
return updated
@@ -1033,12 +1037,12 @@ def update_render(self, scene: bpy.types.Scene, view_layer: bpy.types.ViewLayer)
restart |= scene.rpr.viewport_limits.set_adaptive_params(self.rpr_context)
# image filter
- if self.setup_image_filter(self._get_image_filter_settings()):
+ if self.setup_image_filter(self._get_image_filter_settings(scene)):
self.denoised_image = None
restart = True
restart |= self.setup_upscale_filter({
- 'enable': get_user_settings().viewport_denoiser_upscale,
+ 'enable': scene.rpr.viewport_upscale,
'resolution': (self.width, self.height),
})
@@ -1050,9 +1054,9 @@ def _get_world_settings(self, depsgraph):
return world.WorldData.init_from_shading_data(self.shading_data)
- def _get_image_filter_settings(self):
+ def _get_image_filter_settings(self, scene):
return {
- 'enable': get_user_settings().viewport_denoiser_upscale,
+ 'enable': scene.rpr.viewport_denoiser,
'resolution': (self.width, self.height),
'filter_type': 'ML',
'ml_color_only': False,
diff --git a/src/rprblender/engine/viewport_engine_2.py b/src/rprblender/engine/viewport_engine_2.py
index 1f3fa6bf..c810c53b 100644
--- a/src/rprblender/engine/viewport_engine_2.py
+++ b/src/rprblender/engine/viewport_engine_2.py
@@ -47,6 +47,7 @@ def stop_render(self):
self.rpr_context.set_render_update_callback(None)
self.rpr_context = None
self.image_filter = None
+ self.upscale_filter = None
def _resolve(self):
self.rpr_context.resolve(None if self.image_filter and self.is_last_iteration else
@@ -69,6 +70,11 @@ def _resize(self, width, height):
image_filter_settings['resolution'] = self.width, self.height
self.setup_image_filter(image_filter_settings)
+ if self.background_filter:
+ background_filter_settings = self.background_filter.settings.copy()
+ background_filter_settings['resolution'] = self.width, self.height
+ self.setup_background_filter(background_filter_settings)
+
if self.upscale_filter:
upscale_filter_settings = self.upscale_filter.settings.copy()
upscale_filter_settings['resolution'] = self.width, self.height
@@ -150,7 +156,7 @@ def render_update(progress):
self.rpr_context.set_parameter(pyrpr.CONTEXT_FRAMECOUNT, iteration)
update_iterations = 1
- if not self.use_contour and iteration > 1:
+ if iteration > 1:
update_iterations = min(32, self.render_iterations - iteration)
self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, update_iterations)
@@ -222,26 +228,33 @@ def render_update(progress):
self._resolve()
time_render = time.perf_counter() - time_begin
- if self.image_filter:
- self.notify_status(f"Time: {time_render:.1f} sec | Iteration: {iteration}"
- f" | Denoising...", "Render")
+ with self.render_lock:
+ if self.image_filter:
+ self.notify_status(f"Time: {time_render:.1f} sec | Iteration: {iteration}"
+ f" | Denoising...", "Render")
- # applying denoising
- self.update_image_filter_inputs()
- self.image_filter.run()
- self.rendered_image = self.image_filter.get_data()
+ # applying denoising
+ self.update_image_filter_inputs()
+ self.image_filter.run()
+ image = self.image_filter.get_data()
- time_render = time.perf_counter() - time_begin
- status_str = f"Time: {time_render:.1f} sec | Iteration: {iteration} | Denoised"
- else:
- self.rendered_image = self.rpr_context.get_image()
- status_str = f"Time: {time_render:.1f} sec | Iteration: {iteration}"
+ time_render = time.perf_counter() - time_begin
+ status_str = f"Time: {time_render:.1f} sec | Iteration: {iteration} | Denoised"
+ else:
+ image = self.rpr_context.get_image()
+ status_str = f"Time: {time_render:.1f} sec | Iteration: {iteration}"
+
+ if self.background_filter:
+ with self.resolve_lock:
+ self.rendered_image = self.resolve_background_aovs(self.rendered_image)
+ else:
+ self.rendered_image = image
- if self.upscale_filter:
- self.upscale_filter.update_input('color', self.rendered_image)
- self.upscale_filter.run()
- self.rendered_image = self.upscale_filter.get_data()
- status_str += " | Upscaled"
+ if self.upscale_filter:
+ self.upscale_filter.update_input('color', self.rendered_image)
+ self.upscale_filter.run()
+ self.rendered_image = self.upscale_filter.get_data()
+ status_str += " | Upscaled"
self.notify_status(status_str, "Rendering Done")
@@ -260,10 +273,30 @@ def _do_resolve(self):
with self.resolve_lock:
self._resolve()
- self.rendered_image = self.rpr_context.get_image()
+ image = self.rpr_context.get_image()
+
+ if self.background_filter:
+ image = self.resolve_background_aovs(image)
+ self.rendered_image = image
+ else:
+ self.rendered_image = image
log("Finish _do_resolve")
+ def resolve_background_aovs(self, color_image):
+ settings = self.background_filter.settings
+ self.rpr_context.resolve((pyrpr.AOV_OPACITY,))
+ alpha = self.rpr_context.get_image(pyrpr.AOV_OPACITY)
+ if settings['use_shadow']:
+ self.rpr_context.resolve((pyrpr.AOV_SHADOW_CATCHER,))
+ if settings['use_reflection']:
+ self.rpr_context.resolve((pyrpr.AOV_REFLECTION_CATCHER,))
+ if settings['use_shadow'] or settings['use_reflection']:
+ self.rpr_context.resolve((pyrpr.AOV_BACKGROUND,))
+ self.update_background_filter_inputs(color_image=color_image, opacity_image=alpha)
+ self.background_filter.run()
+ return self.background_filter.get_data()
+
def draw(self, context):
log("Draw")
diff --git a/src/rprblender/export/camera.py b/src/rprblender/export/camera.py
index cb2b4bab..72d34fed 100644
--- a/src/rprblender/export/camera.py
+++ b/src/rprblender/export/camera.py
@@ -224,8 +224,10 @@ def export(self, rpr_camera: pyrpr.Camera, tile=((0.0, 0.0), (1.0, 1.0))):
def sync(rpr_context: RPRContext, obj: bpy.types.Object):
- """ Creates pyrpr.Camera from obj.data: bpy.types.Camera. Created camera sets to scene as default """
-
+ """
+ Creates pyrpr.Camera from obj.data: bpy.types.Camera.
+ Created camera sets to scene as default
+ """
camera = obj.data
log("sync", camera)
@@ -237,3 +239,7 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object):
# set scene's camera
rpr_context.scene.set_camera(rpr_camera)
+
+
+def cache_blur_data(rpr_context, obj: bpy.types.Object):
+ rpr_context.transform_cache[object.key(obj)] = object.get_transform(obj)
diff --git a/src/rprblender/export/hair.py b/src/rprblender/export/hair.py
index d74190b0..c4cb26c3 100644
--- a/src/rprblender/export/hair.py
+++ b/src/rprblender/export/hair.py
@@ -100,8 +100,9 @@ def shape_f(x, shape):
# finding corresponded active ParticleSystemModifier
p_modifier = next((modifier for modifier in obj.modifiers
if modifier.type == 'PARTICLE_SYSTEM' and
- modifier.show_render and
- modifier.particle_system.name == p_sys.name),
+ (modifier.show_render if use_final_settings
+ else modifier.show_viewport)
+ and modifier.particle_system.name == p_sys.name),
None)
if not p_modifier:
diff --git a/src/rprblender/export/instance.py b/src/rprblender/export/instance.py
index c686ed9c..b5df22c8 100644
--- a/src/rprblender/export/instance.py
+++ b/src/rprblender/export/instance.py
@@ -52,7 +52,10 @@ def sync(rpr_context, instance: bpy.types.DepsgraphObjectInstance, **kwargs):
rpr_shape = rpr_context.create_instance(instance_key, rpr_mesh)
rpr_shape.set_name(str(instance_key))
- rpr_shape.set_transform(get_transform(instance))
+
+ transform = get_transform(instance)
+ rpr_shape.set_transform(transform)
+ object.export_motion_blur(rpr_context, instance_key, transform)
# exporting visibility from source object
indirect_only = kwargs.get("indirect_only", False)
@@ -68,3 +71,8 @@ def sync(rpr_context, instance: bpy.types.DepsgraphObjectInstance, **kwargs):
else:
raise ValueError("Unsupported object type for instance", instance, obj, obj.type)
+
+
+def cache_blur_data(rpr_context, inst: bpy.types.DepsgraphObjectInstance):
+ if inst.parent.rpr.motion_blur:
+ rpr_context.transform_cache[key(inst)] = get_transform(inst)
diff --git a/src/rprblender/export/mesh.py b/src/rprblender/export/mesh.py
index bb29d3e1..c711b889 100644
--- a/src/rprblender/export/mesh.py
+++ b/src/rprblender/export/mesh.py
@@ -21,7 +21,7 @@
import mathutils
import pyrpr
-from rprblender.engine.context import RPRContext
+from rprblender.engine.context import RPRContext, RPRContext2
from . import object, material, volume
from rprblender.utils import get_data_from_collection
@@ -82,12 +82,13 @@ def init_from_mesh(mesh: bpy.types.Mesh, calc_area=False, obj=None):
data.uvs.append(uvs)
data.uv_indices.append(uv_indices)
- secondary_uv = mesh.rpr.secondary_uv_layer(obj)
- if secondary_uv:
- uvs = get_data_from_collection(secondary_uv.data, 'uv', (len(secondary_uv.data), 2))
- if len(uvs) > 0:
- data.uvs.append(uvs)
- data.uv_indices.append(uv_indices)
+ if obj:
+ secondary_uv = mesh.rpr.secondary_uv_layer(obj)
+ if secondary_uv:
+ uvs = get_data_from_collection(secondary_uv.data, 'uv', (len(secondary_uv.data), 2))
+ if len(uvs) > 0:
+ data.uvs.append(uvs)
+ data.uv_indices.append(uv_indices)
data.num_face_vertices = np.full((tris_len,), 3, dtype=np.int32)
data.vertex_indices = get_data_from_collection(mesh.loop_triangles, 'vertices',
@@ -241,6 +242,14 @@ def assign_materials(rpr_context: RPRContext, rpr_shape: pyrpr.Shape, obj: bpy.t
if mat.cycles.displacement_method in {'DISPLACEMENT', 'BOTH'}:
rpr_displacement = material.sync(rpr_context, mat, 'Displacement', obj=obj)
rpr_shape.set_displacement_material(rpr_displacement)
+ # if no subdivision set that up to 'high' so displacement looks good
+ # note subdivision is capped to resolution
+ if rpr_shape.subdivision is None:
+ rpr_shape.subdivision = {
+ 'level': 10,
+ 'boundary': pyrpr.SUBDIV_BOUNDARY_INTERFOP_TYPE_EDGE_AND_CORNER,
+ 'crease_weight': 10
+ }
else:
rpr_shape.set_displacement_material(None)
@@ -274,7 +283,7 @@ def export_visibility(obj, rpr_shape, indirect_only):
obj.rpr.set_catchers(rpr_shape)
-def sync_visibility(rpr_context, obj: bpy.types.Object, rpr_shape: pyrpr.Shape, indirect_only: bool = False, use_contour: bool = False):
+def sync_visibility(rpr_context, obj: bpy.types.Object, rpr_shape: pyrpr.Shape, indirect_only: bool = False):
from rprblender.engine.viewport_engine import ViewportEngine
rpr_shape.set_visibility(
@@ -287,8 +296,7 @@ def sync_visibility(rpr_context, obj: bpy.types.Object, rpr_shape: pyrpr.Shape,
export_visibility(obj, rpr_shape, indirect_only)
obj.rpr.export_subdivision(rpr_shape)
- if use_contour:
- pyrpr.ShapeSetContourIgnore(rpr_shape, not obj.rpr.visibility_contour)
+ rpr_shape.set_contour_ignore(not obj.rpr.visibility_contour)
if obj.rpr.portal_light:
# Register mesh as a portal light, set "Environment" light group
@@ -306,7 +314,6 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs):
mesh = kwargs.get("mesh", obj.data)
material_override = kwargs.get("material_override", None)
indirect_only = kwargs.get("indirect_only", False)
- use_contour = kwargs.get("use_contour", False)
log("sync", mesh, obj, "IndirectOnly" if indirect_only else "")
obj_key = object.key(obj)
@@ -315,12 +322,26 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs):
rpr_context.create_empty_object(obj_key)
return
- rpr_shape = rpr_context.create_mesh(
- obj_key,
- data.vertices, data.normals, data.uvs,
- data.vertex_indices, data.normal_indices, data.uv_indices,
- data.num_face_vertices
- )
+ deformation_data = rpr_context.deformation_cache.get(obj_key)
+ if deformation_data and np.any(data.vertices != deformation_data.vertices) and \
+ np.any(data.normals != deformation_data.normals):
+ vertices = np.concatenate((data.vertices, deformation_data.vertices))
+ normals = np.concatenate((data.normals, deformation_data.normals))
+ rpr_shape = rpr_context.create_mesh(
+ obj_key,
+ np.ascontiguousarray(vertices), np.ascontiguousarray(normals), data.uvs,
+ data.vertex_indices, data.normal_indices, data.uv_indices,
+ data.num_face_vertices,
+ {pyrpr.MESH_MOTION_DIMENSION: 2}
+ )
+ else:
+ rpr_shape = rpr_context.create_mesh(
+ obj_key,
+ data.vertices, data.normals, data.uvs,
+ data.vertex_indices, data.normal_indices, data.uv_indices,
+ data.num_face_vertices
+ )
+
rpr_shape.set_name(obj.name)
rpr_shape.set_id(obj.pass_index)
rpr_context.set_aov_index_lookup(obj.pass_index, obj.pass_index,
@@ -332,9 +353,12 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs):
assign_materials(rpr_context, rpr_shape, obj, material_override)
rpr_context.scene.attach(rpr_shape)
- rpr_shape.set_transform(object.get_transform(obj))
- sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only, use_contour=use_contour)
+ transform = object.get_transform(obj)
+ rpr_shape.set_transform(transform)
+ object.export_motion_blur(rpr_context, obj_key, transform)
+
+ sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only)
def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geometry, is_updated_transform, **kwargs):
@@ -356,11 +380,19 @@ def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geome
indirect_only = kwargs.get("indirect_only", False)
material_override = kwargs.get("material_override", None)
- use_contour = kwargs.get("use_contour", False)
-
- sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only, use_contour=use_contour)
+
+ sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only)
assign_materials(rpr_context, rpr_shape, obj, material_override)
return True
sync(rpr_context, obj, **kwargs)
return True
+
+
+def cache_blur_data(rpr_context, obj: bpy.types.Object, mesh=None):
+ obj_key = object.key(obj)
+ if obj.rpr.motion_blur:
+ rpr_context.transform_cache[obj_key] = object.get_transform(obj)
+
+ if obj.rpr.deformation_blur and isinstance(rpr_context, RPRContext2):
+ rpr_context.deformation_cache[obj_key] = MeshData.init_from_mesh(mesh if mesh else obj.data)
diff --git a/src/rprblender/export/object.py b/src/rprblender/export/object.py
index 31a1b8ba..fcee5ec4 100644
--- a/src/rprblender/export/object.py
+++ b/src/rprblender/export/object.py
@@ -16,6 +16,7 @@
import numpy as np
import bpy
+import mathutils
from . import mesh, light, camera, to_mesh, volume, openvdb, particle, hair
from rprblender.utils import logging
@@ -106,3 +107,47 @@ def sync_update(rpr_context, obj: bpy.types.Object, is_updated_geometry, is_upda
updated |= particle.sync_update(rpr_context, obj, is_updated_geometry, is_updated_transform)
return updated
+
+
+def cache_blur_data(rpr_context, obj: bpy.types.Object):
+ if obj.type == 'MESH':
+ if obj.mode == 'OBJECT':
+ # if in edit mode use to_mesh
+ mesh.cache_blur_data(rpr_context, obj)
+ else:
+ to_mesh.cache_blur_data(rpr_context, obj)
+
+ elif obj.type == 'CAMERA':
+ camera.cache_blur_data(rpr_context, obj)
+
+ elif obj.type in ('CURVE', 'FONT', 'SURFACE', 'META'):
+ to_mesh.cache_blur_data(rpr_context, obj)
+
+
+def export_motion_blur(rpr_context, obj_key, transform):
+ """Use the motion_blur_cache to set the transform motion"""
+ next_transform = rpr_context.transform_cache.get(obj_key)
+ if next_transform is None or np.all(transform == next_transform):
+ return
+
+ rpr_object = rpr_context.objects[obj_key]
+ if hasattr(rpr_object, 'set_motion_transform'):
+ rpr_object.set_motion_transform(next_transform)
+
+ else:
+ m_from = mathutils.Matrix(transform)
+ m_to = mathutils.Matrix(next_transform)
+ sub = m_to - m_from
+ div = m_from @ m_to.inverted()
+ quat = div.to_quaternion()
+
+ linear = sub.to_translation()
+ angular = (*quat.axis, quat.angle) if quat.axis.length > 0.5 else (1.0, 0.0, 0.0, 0.0)
+ scale = div.to_scale() - mathutils.Vector((1, 1, 1))
+
+ rpr_object.set_linear_motion(*linear)
+ rpr_object.set_angular_motion(*angular)
+ if hasattr(rpr_object, 'set_scale_motion'):
+ rpr_object.set_scale_motion(*scale)
+
+
diff --git a/src/rprblender/export/openvdb.py b/src/rprblender/export/openvdb.py
index e3bf4376..b54a242d 100644
--- a/src/rprblender/export/openvdb.py
+++ b/src/rprblender/export/openvdb.py
@@ -19,7 +19,7 @@
import bpy
import pyrpr
-from rprblender.utils import helper_lib, IS_WIN, IS_MAC, is_zero
+from rprblender.utils import helper_lib, IS_OPENVDB_SUPPORT, is_zero
from rprblender.utils import get_sequence_frame_file_path
from . import mesh, object, material
@@ -102,7 +102,8 @@ def get_volume_file_path(volume, scene_frame):
def sync(rpr_context, obj: bpy.types.Object, **kwargs):
- if not (IS_WIN or IS_MAC):
+ if not IS_OPENVDB_SUPPORT:
+ log.warn("OpenVDB is not supported")
return
# getting openvdb grid data
diff --git a/src/rprblender/export/particle.py b/src/rprblender/export/particle.py
index 67e9ea7b..1a7c6ec4 100644
--- a/src/rprblender/export/particle.py
+++ b/src/rprblender/export/particle.py
@@ -103,8 +103,8 @@ def sync(rpr_context, emitter: bpy.types.Object):
if rpr_context.do_motion_blur:
if hasattr(instance, 'set_motion_transform'):
prev_loc = mathutils.Matrix.Translation(particle.prev_location)
- prev_mat = np.array(prev_loc @ rot.to_matrix().to_4x4() @ scale, dtype=np.float32)\
- .reshape(4, 4)
+ prev_mat = np.array(prev_loc @ rot.to_matrix().to_4x4() @ scale,
+ dtype=np.float32).reshape(4, 4)
instance.set_motion_transform(prev_mat)
else:
velocity = (particle.location[i] - particle.prev_location[i] for i in range(3))
diff --git a/src/rprblender/export/to_mesh.py b/src/rprblender/export/to_mesh.py
index 9fab3702..b924ecd2 100644
--- a/src/rprblender/export/to_mesh.py
+++ b/src/rprblender/export/to_mesh.py
@@ -19,6 +19,7 @@
import bpy
+from rprblender.engine.context import RPRContext2
from . import object, mesh
from rprblender.utils import logging
@@ -29,8 +30,8 @@ def sync(rpr_context, obj: bpy.types.Object, **kwargs):
""" Converts object into blender's mesh and exports it as mesh """
try:
- # This operation adds new mesh into bpy.data.meshes, that's why it should be removed after usage.
- # obj.to_mesh() could also return None for META objects.
+ # This operation adds new mesh into bpy.data.meshes, that's why it should be removed
+ # after usage. obj.to_mesh() could also return None for META objects.
new_mesh = obj.to_mesh()
log("sync", obj, new_mesh)
@@ -67,3 +68,25 @@ def sync_update(rpr_context, obj: bpy.types.Object, is_updated_geometry, is_upda
material_override = kwargs.get('material_override', None)
return mesh.assign_materials(rpr_context, rpr_shape, obj, material_override=material_override)
+
+
+def cache_blur_data(rpr_context, obj: bpy.types.Object):
+ if obj.rpr.deformation_blur and isinstance(rpr_context, RPRContext2):
+ try:
+ # This operation adds new mesh into bpy.data.meshes, that's why it should be removed
+ # after usage. obj.to_mesh() could also return None for META objects.
+ new_mesh = obj.to_mesh()
+ log("sync", obj, new_mesh)
+
+ if new_mesh:
+ mesh.cache_blur_data(rpr_context, obj, new_mesh)
+ return True
+
+ return False
+
+ finally:
+ # it's important to clear created mesh
+ obj.to_mesh_clear()
+
+ else:
+ mesh.cache_blur_data(rpr_context, obj)
diff --git a/src/rprblender/nodes/__init__.py b/src/rprblender/nodes/__init__.py
index 11f97be2..421b2c7c 100644
--- a/src/rprblender/nodes/__init__.py
+++ b/src/rprblender/nodes/__init__.py
@@ -61,12 +61,14 @@ def poll(cls, context):
NodeItem('RPRShaderNodeUber'),
NodeItem('RPRShaderNodePassthrough'),
NodeItem('RPRShaderNodeLayered'),
+ NodeItem('RPRShaderNodeToon'),
]),
RPR_ShaderNodeCategory("RPR_TEXTURES", "Texture", items=[
NodeItem('ShaderNodeTexChecker'),
NodeItem('ShaderNodeTexGradient'),
NodeItem('ShaderNodeTexImage'),
NodeItem('ShaderNodeTexNoise'),
+ NodeItem('ShaderNodeTexVoronoi'),
NodeItem('RPRTextureNodeLayered'),
],),
RPR_ShaderNodeCategory('RPR_COLOR', "Color", items=[
@@ -139,6 +141,7 @@ def func(cls, context):
rpr_nodes.RPRShaderProceduralUVNode,
rpr_nodes.RPRShaderNodeLayered,
rpr_nodes.RPRTextureNodeLayered,
+ rpr_nodes.RPRShaderNodeToon,
])
diff --git a/src/rprblender/nodes/blender_nodes.py b/src/rprblender/nodes/blender_nodes.py
index 2dc9ae6f..f0d2371b 100644
--- a/src/rprblender/nodes/blender_nodes.py
+++ b/src/rprblender/nodes/blender_nodes.py
@@ -1383,12 +1383,29 @@ def export(self):
res = in1 > in2
elif op == 'MODULO':
res = self.create_arithmetic(pyrpr.MATERIAL_NODE_OP_MOD, in1, in2)
+
else:
in3 = self.get_input_value(2)
if op == 'MULTIPLY_ADD':
res = in1 * in2 + in3
+ elif op == 'COMPARE':
+ # Descriptions from Cycles: Outputs 1.0 if the difference
+ # between the two input values is less than or equal to Epsilon.
+ res = abs(in1 - in2) <= in3
+ elif op == 'SMOOTH_MIN':
+ # Descriptions from Cycles: https://en.wikipedia.org/wiki/Smooth_maximum
+ f1 = math.e ** (in1 * in3)
+ f2 = math.e ** (in2 * in3)
+ res = (in1 * f2 + in2 * f1) / (f1 + f2)
+ elif op == 'SMOOTH_MAX':
+ # Descriptions from Cycles: https://en.wikipedia.org/wiki/Smooth_maximum
+ f1 = math.e ** (in1 * in3)
+ f2 = math.e ** (in2 * in3)
+ res = (in1 * f1 + in2 * f2) / (f1 + f2)
+
else:
- raise ValueError("Incorrect math operation", op)
+ res = in1
+ log.warn("Unsupported math operation", op)
if self.node.use_clamp:
res = res.clamp()
@@ -1884,6 +1901,57 @@ def export_hybrid(self):
return None
+class ShaderNodeTexVoronoi(NodeParser):
+ """Create RPR Voronoi node"""
+
+ def export_rpr2(self):
+ if self.node.voronoi_dimensions in ('1D', '4D'):
+ log.warn("Unsupported dimension type", self.node.voronoi_dimensions, self.node,
+ self.material)
+ return None
+
+ if self.node.feature != 'F1':
+ log.warn("Unsupported feature type", self.node.feature, self.node, self.material)
+ return None
+
+ if self.node.distance != 'EUCLIDEAN':
+ log.warn("Unsupported distance type", self.node.distance, self.node, self.material)
+ return None
+
+ scale = self.get_input_value('Scale')
+ scale *= 3.5 # RPR Voronoi texture visually is about 350% of Blender Voronoi
+ randomness = self.get_input_value('Randomness')
+ dimensions = 2 if self.node.voronoi_dimensions == '2D' else 3
+
+ mapping = self.get_input_link('Vector')
+ if not mapping: # use default mapping if no external mapping nodes attached
+ mapping = self.create_node(pyrpr.MATERIAL_NODE_INPUT_LOOKUP, {
+ pyrpr.MATERIAL_INPUT_VALUE: pyrpr.MATERIAL_NODE_LOOKUP_UV
+ })
+
+ out_type = {
+ 'Distance': pyrpr.VORONOI_OUT_TYPE_DISTANCE,
+ 'Color': pyrpr.VORONOI_OUT_TYPE_COLOR,
+ 'Position': pyrpr.VORONOI_OUT_TYPE_POSITION
+ }[self.socket_out.name]
+
+ voronoi = self.create_node(pyrpr.MATERIAL_NODE_VORONOI_TEXTURE, {
+ pyrpr.MATERIAL_INPUT_UV: mapping,
+ pyrpr.MATERIAL_INPUT_SCALE: scale,
+ pyrpr.MATERIAL_INPUT_RANDOMNESS: randomness,
+ pyrpr.MATERIAL_INPUT_DIMENSION: dimensions,
+ pyrpr.MATERIAL_INPUT_OUTTYPE: out_type,
+ })
+
+ return voronoi
+
+ def export(self):
+ return None
+
+ def export_hybrid(self):
+ return None
+
+
class ShaderNodeMapping(NodeParser):
"""Creating mix of lookup and math nodes to adjust texture coordinates mapping in a way Cycles do"""
diff --git a/src/rprblender/nodes/rpr_nodes.py b/src/rprblender/nodes/rpr_nodes.py
index 18e8c139..7f4252f9 100644
--- a/src/rprblender/nodes/rpr_nodes.py
+++ b/src/rprblender/nodes/rpr_nodes.py
@@ -1513,3 +1513,98 @@ def export_hybrid(self):
return None
return self.export()
+
+
+class RPRShaderNodeToon(RPRShaderNode):
+ ''' A toon shader using both the RPR Toon Shader and Ramp node '''
+ bl_label = 'RPR Toon'
+
+ def advanced_changed(self, context):
+ ramp_sockets = ['Shadow Color', "Mid Level","Mid Color", "Highlight Level", "Highlight Color"]
+ mix_sockets = ["Mid Level Mix", "Highlight Level Mix"]
+ if self.show_advanced:
+ self.inputs['Color'].enabled = False
+ for socket in ramp_sockets:
+ self.inputs[socket].enabled = True
+ for socket in mix_sockets:
+ self.inputs[socket].enabled = self.show_mix_levels
+ else:
+ for socket in ramp_sockets + mix_sockets:
+ self.inputs[socket].enabled = False
+ self.inputs['Color'].enabled = True
+
+ show_advanced: BoolProperty(name="Advanced", default=False, update=advanced_changed)
+ show_mix_levels: BoolProperty(name="Mix Levels", default=False, update=advanced_changed)
+
+ def init(self, context):
+ # Adding input sockets with default_value or hide_value properties.
+ # Here we use Blender's native node sockets
+ self.inputs.new('rpr_socket_color', "Color").default_value = (0.8, 0.8, 0.8, 1.0) # Corresponds to Cycles diffuse
+ self.inputs.new('rpr_socket_weight', "Roughness").default_value = 1.0
+ self.inputs.new('NodeSocketVector', "Normal").hide_value = True
+
+ inp = self.inputs.new('rpr_socket_color', "Shadow Color")
+ inp.default_value = (0.0, 0.0, 0.0, 1.0) # Corresponds to Cycles diffuse
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_weight', "Mid Level")
+ inp.default_value = 0.5
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_weight', "Mid Level Mix")
+ inp.default_value = 0.05
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_color', "Mid Color")
+ inp.default_value = (0.4, 0.4, 0.4, 1.0) # Corresponds to Cycles diffuse
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_weight', "Highlight Level")
+ inp.default_value = 0.8
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_weight', "Highlight Level Mix")
+ inp.default_value = 0.05
+ inp.enabled = False
+ inp = self.inputs.new('rpr_socket_color', "Highlight Color")
+ inp.default_value = (0.8, 0.8, 0.8, 1.0) # Corresponds to Cycles diffuse
+ inp.enabled = False
+
+ # adding output socket
+ self.outputs.new('NodeSocketShader', "Shader")
+
+ def draw_buttons(self, context, layout):
+ col = layout.column()
+
+ col.prop(self, 'show_advanced')
+ if self.show_advanced:
+ col.prop(self, 'show_mix_levels')
+
+ class Exporter(RuleNodeParser):
+ def export(self):
+ if self.node.show_advanced:
+ # build the toon ramp node
+ interpolation_mode = pyrpr.INTERPOLATION_MODE_LINEAR if self.node.show_mix_levels \
+ else pyrpr.INTERPOLATION_MODE_NONE
+ ramp = self.create_node(pyrpr.MATERIAL_NODE_TOON_RAMP, {
+ pyrpr.MATERIAL_INPUT_SHADOW: self.get_input_value('Shadow Color'),
+ pyrpr.MATERIAL_INPUT_MID: self.get_input_value('Mid Color'),
+ pyrpr.MATERIAL_INPUT_HIGHLIGHT: self.get_input_value('Highlight Color'),
+ pyrpr.MATERIAL_INPUT_POSITION1: self.get_input_value('Mid Level'),
+ pyrpr.MATERIAL_INPUT_POSITION2: self.get_input_value('Highlight Level'),
+ pyrpr.MATERIAL_INPUT_RANGE1: self.get_input_value('Mid Level Mix'),
+ pyrpr.MATERIAL_INPUT_RANGE2: self.get_input_value('Highlight Level Mix'),
+ pyrpr.MATERIAL_INPUT_INTERPOLATION: interpolation_mode,
+ })
+
+ toon_shader = self.create_node(pyrpr.MATERIAL_NODE_TOON_CLOSURE, {
+ pyrpr.MATERIAL_INPUT_COLOR: (1.0, 1.0, 1.0, 1.0),
+ pyrpr.MATERIAL_INPUT_ROUGHNESS: self.get_input_value('Roughness'),
+ pyrpr.MATERIAL_INPUT_DIFFUSE_RAMP: ramp
+ })
+ else:
+ toon_shader = self.create_node(pyrpr.MATERIAL_NODE_TOON_CLOSURE, {
+ pyrpr.MATERIAL_INPUT_COLOR: self.get_input_value('Color'),
+ pyrpr.MATERIAL_INPUT_ROUGHNESS: self.get_input_value('Roughness')
+ })
+
+ normal = self.get_input_link('Normal')
+ if normal:
+ toon_shader.set_input(pyrpr.MATERIAL_INPUT_NORMAL, normal)
+
+ return toon_shader
diff --git a/src/rprblender/operators/export_scene.py b/src/rprblender/operators/export_scene.py
index 8e9737ed..ee59bea3 100644
--- a/src/rprblender/operators/export_scene.py
+++ b/src/rprblender/operators/export_scene.py
@@ -175,7 +175,7 @@ def save_json(self, filepath, scene, view_layer, engine_lib_name):
output_base = os.path.splitext(filepath)[0]
devices = get_user_settings().final_devices
- use_contour = scene.rpr.is_contour_used() and not devices.cpu_state
+ use_contour = view_layer.rpr.use_contour_render and not devices.cpu_state
data = {
'width': int(scene.render.resolution_x * scene.render.resolution_percentage / 100),
@@ -239,16 +239,16 @@ def save_json(self, filepath, scene, view_layer, engine_lib_name):
device_settings[f'gpu{i}'] = int(gpu_state)
if use_contour:
+ contour = view_layer.rpr.contour
data['contour'] = {
- "object.id": int(scene.rpr.contour_use_object_id),
- "material.id": int(scene.rpr.contour_use_material_id),
- "normal": int(scene.rpr.contour_use_shading_normal),
- "threshold.normal": scene.rpr.contour_normal_threshold,
- "linewidth.objid": scene.rpr.contour_object_id_line_width,
- "linewidth.matid": scene.rpr.contour_material_id_line_width,
- "linewidth.normal": scene.rpr.contour_shading_normal_line_width,
- "antialiasing": scene.rpr.contour_antialiasing,
- "debug": int(scene.rpr.contour_use_shading_normal)
+ "object.id": int(contour.use_object_id),
+ "material.id": int(contour.use_material_id),
+ "normal": int(contour.use_shading_normal),
+ "threshold.normal": contour.normal_threshold,
+ "linewidth.objid": contour.object_id_line_width,
+ "linewidth.matid": contour.material_id_line_width,
+ "linewidth.normal": contour.shading_normal_line_width,
+ "antialiasing": contour.antialiasing,
}
data['context'] = device_settings
diff --git a/src/rprblender/operators/nodes.py b/src/rprblender/operators/nodes.py
index 4226b9a4..14f9ff14 100644
--- a/src/rprblender/operators/nodes.py
+++ b/src/rprblender/operators/nodes.py
@@ -110,37 +110,43 @@ def poll(cls, context):
def execute(self, context):
# iterate over all objects and find unsupported nodes
baked_materials = []
+ baked_objs = []
selected_object = context.active_object
+ selected_layer = context.window.view_layer
- for obj in context.scene.objects:
- if obj.type != 'MESH':
- continue
-
- for material_slot in obj.material_slots:
- if material_slot.material.name in baked_materials:
- continue
- nt = material_slot.material.node_tree
- if nt is None:
+ for layer in context.scene.view_layers:
+ context.window.view_layer = layer
+ for obj in layer.objects:
+ if obj.type != 'MESH' or obj.name in baked_objs:
continue
- nodes_to_bake = []
- for node in nt.nodes:
- if not get_node_parser_class(node.bl_idname):
- nodes_to_bake.append(node)
+ baked_objs.append(obj.name)
- settings = get_user_settings()
- resolution = settings.bake_resolution
+ for material_slot in obj.material_slots:
+ if material_slot.material.name in baked_materials:
+ continue
+ nt = material_slot.material.node_tree
+ if nt is None:
+ continue
- old_selection = obj.select_get()
- obj.select_set(True)
- bake_nodes(nt, nodes_to_bake, material_slot.material, int(resolution), obj)
- obj.select_set(old_selection)
+ nodes_to_bake = []
+ for node in nt.nodes:
+ if not get_node_parser_class(node.bl_idname):
+ nodes_to_bake.append(node)
- baked_materials.append(material_slot.material.name)
+ settings = get_user_settings()
+ resolution = settings.bake_resolution
- selected_object.select_set(True)
+ old_selection = obj.select_get()
+ obj.select_set(True)
+ bake_nodes(nt, nodes_to_bake, material_slot.material, int(resolution), obj)
+ obj.select_set(old_selection)
- return {'FINISHED'}
+ baked_materials.append(material_slot.material.name)
+
+ context.window.view_layer = selected_layer
+ selected_object.select_set(True)
+ return {'FINISHED'}
class RPR_NODE_OP_bake_selected_nodes(RPR_Operator):
diff --git a/src/rprblender/properties/__init__.py b/src/rprblender/properties/__init__.py
index 4e29c7e6..956e3952 100644
--- a/src/rprblender/properties/__init__.py
+++ b/src/rprblender/properties/__init__.py
@@ -71,6 +71,7 @@ def sync(self, rpr_context):
world.RPR_EnvironmentProperties,
view_layer.RPR_DenoiserProperties,
+ view_layer.RPR_ContourProperties,
view_layer.RPR_ViewLayerProperites,
material_browser.RPR_MaterialBrowserProperties,
diff --git a/src/rprblender/properties/object.py b/src/rprblender/properties/object.py
index a69d1171..98f99f67 100644
--- a/src/rprblender/properties/object.py
+++ b/src/rprblender/properties/object.py
@@ -79,12 +79,17 @@ class RPR_ObjectProperites(RPR_Properties):
default=True,
)
- # Motion Blur
+ # Motion and Deformation Blur
motion_blur: BoolProperty(
name="Motion Blur",
description="Enable Motion Blur",
default=True,
)
+ deformation_blur: BoolProperty(
+ name="Deformation Blur",
+ description="Enable Deformation Blur",
+ default=False,
+ )
# Subdivision
subdivision: BoolProperty(
diff --git a/src/rprblender/properties/render.py b/src/rprblender/properties/render.py
index 74b31efe..de2a4a95 100644
--- a/src/rprblender/properties/render.py
+++ b/src/rprblender/properties/render.py
@@ -61,6 +61,12 @@ class RPR_RenderLimits(bpy.types.PropertyGroup):
min=16, default=128,
)
+ contour_render_samples: IntProperty(
+ name="Outline Samples",
+ description="Number of samples for Outline Rendering",
+ min=1, default=16,
+ )
+
noise_threshold: FloatProperty(
name="Noise Threshold",
description="Cutoff for adaptive sampling. Once pixels are below this amount of noise, "
@@ -466,70 +472,29 @@ def update_render_quality(self, context):
default=False,
)
+ texture_compression: BoolProperty(
+ name="Texture Compression",
+ description="Enables Texture compression for faster rendering (with lossier textures)",
+ default=False,
+ )
+
motion_blur_in_velocity_aov: BoolProperty(
name="Only in Velocity AOV",
description="Apply Motion Blur in Velocity AOV only\nOnly for Full render quality",
default=False,
)
- # CONTOUR render mode settings
- use_contour_render: BoolProperty(
- name="Contour",
- description="Use Contour rendering mode. Final render only",
- default=False
- )
-
- contour_use_object_id: BoolProperty(
- name="Use Object ID",
- description="Use Object ID for Contour rendering",
- default=True,
- )
- contour_use_material_id: BoolProperty(
- name="Use Material Index",
- description="Use Material Index for Contour rendering",
+ viewport_denoiser: BoolProperty(
+ name="Viewport Denoising",
+ description="Denoise rendered image with Machine Learning denoiser",
default=True,
)
- contour_use_shading_normal: BoolProperty(
- name="Use Shading Normal",
- description="Use Shading Normal for Contour rendering",
- default=True,
- )
-
- contour_object_id_line_width: FloatProperty(
- name="Line Width Object",
- description="Line width for Object ID contours",
- min=1.0, max=10.0,
- default=1.0,
- )
- contour_material_id_line_width: FloatProperty(
- name="Line Width Material",
- description="Line width for Material Index contours",
- min=1.0, max=10.0,
- default=1.0,
- )
- contour_shading_normal_line_width: FloatProperty(
- name="Line Width Normal",
- description="Line width for Shading Normal contours",
- min=1.0, max=10.0,
- default=1.0,
- )
- contour_normal_threshold: FloatProperty(
- name="Normal Threshold",
- description="Threshold for normals, in degrees",
- subtype='ANGLE',
- min=0.0, max=math.radians(180.0),
- default=math.radians(45.0),
- )
- contour_antialiasing: FloatProperty(
- name="Antialiasing",
- min=0.0, max=1.0,
- default=1.0,
- )
-
- contour_debug_flag: BoolProperty(
- name="Feature Debug",
- default=False,
+ viewport_upscale: BoolProperty(
+ name="Viewport Upscaling",
+ description="Rendering at 2 times lower resoluting then upscaling rendered image "
+ "in the end of render",
+ default=True,
)
def init_rpr_context(self, rpr_context, is_final_engine=True, use_gl_interop=False, use_contour_integrator=False):
@@ -580,7 +545,7 @@ def init_rpr_context(self, rpr_context, is_final_engine=True, use_gl_interop=Fal
else:
pyrpr.Context.set_parameter(None, pyrpr.CONTEXT_TRACING_ENABLED, False)
- rpr_context.init(context_flags, context_props, use_contour_integrator=use_contour_integrator)
+ rpr_context.init(context_flags, context_props)
if metal_enabled:
mac_vers_major = platform.mac_ver()[0].split('.')[1]
@@ -637,28 +602,6 @@ def is_contour_available(self, is_final_engine):
devices = self.get_devices(is_final_engine=is_final_engine)
return self.render_quality == 'FULL2' and not devices.cpu_state
- def is_contour_used(self, is_final_engine=True):
- return self.is_contour_available(is_final_engine) and self.use_contour_render
-
- def export_contour_mode(self, rpr_context):
- """ set Contour render mode parameters """
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_OBJECTID, self.contour_use_object_id)
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_MATERIALID, self.contour_use_material_id)
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_NORMAL, self.contour_use_shading_normal)
-
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_OBJECTID, self.contour_object_id_line_width)
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_MATERIALID, self.contour_material_id_line_width)
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_NORMAL, self.contour_shading_normal_line_width)
-
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_NORMAL_THRESHOLD, math.degrees(self.contour_normal_threshold))
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_ANTIALIASING, self.contour_antialiasing)
-
- rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_DEBUG_ENABLED, self.contour_debug_flag)
-
- rpr_context.enable_aov(pyrpr.AOV_OBJECT_ID)
- rpr_context.enable_aov(pyrpr.AOV_MATERIAL_ID)
- rpr_context.enable_aov(pyrpr.AOV_SHADING_NORMAL)
-
def export_pixel_filter(self, rpr_context):
""" Exports pixel filter settings """
filter_type = getattr(pyrpr, f"FILTER_{self.pixel_filter}")
diff --git a/src/rprblender/properties/view_layer.py b/src/rprblender/properties/view_layer.py
index 894acab4..f65d526b 100644
--- a/src/rprblender/properties/view_layer.py
+++ b/src/rprblender/properties/view_layer.py
@@ -23,6 +23,7 @@
)
import pyrpr
+import math
from rprblender.utils import logging
from . import RPR_Properties
@@ -31,6 +32,77 @@
log = logging.Log(tag='properties.view_layer')
+class RPR_ContourProperties(RPR_Properties):
+ """ Propoerties to do a contour pass """
+ # CONTOUR render mode settings
+
+ use_object_id: BoolProperty(
+ name="Use Object ID",
+ description="Use Object ID for Contour rendering",
+ default=True,
+ )
+
+ use_material_id: BoolProperty(
+ name="Use Material Index",
+ description="Use Material Index for Contour rendering",
+ default=True,
+ )
+
+ use_shading_normal: BoolProperty(
+ name="Use Shading Normal",
+ description="Use Shading Normal for Contour rendering",
+ default=True,
+ )
+
+ object_id_line_width: FloatProperty(
+ name="Line Width Object",
+ description="Line width for Object ID contours",
+ min=1.0, max=10.0,
+ default=1.0,
+ )
+
+ material_id_line_width: FloatProperty(
+ name="Line Width Material",
+ description="Line width for Material Index contours",
+ min=1.0, max=10.0,
+ default=1.0,
+ )
+
+ shading_normal_line_width: FloatProperty(
+ name="Line Width Normal",
+ description="Line width for Shading Normal contours",
+ min=1.0, max=10.0,
+ default=1.0,
+ )
+
+ normal_threshold: FloatProperty(
+ name="Normal Threshold",
+ description="Threshold for normals, in degrees",
+ subtype='ANGLE',
+ min=0.0, max=math.radians(180.0),
+ default=math.radians(45.0),
+ )
+
+ antialiasing: FloatProperty(
+ name="Antialiasing",
+ min=0.0, max=1.0,
+ default=1.0,
+ )
+
+ def export_contour_settings(self, rpr_context):
+ """ set Contour render mode parameters """
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_OBJECTID, self.use_object_id)
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_MATERIALID, self.use_material_id)
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_USE_NORMAL, self.use_shading_normal)
+
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_OBJECTID, self.object_id_line_width)
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_MATERIALID, self.material_id_line_width)
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_LINEWIDTH_NORMAL, self.shading_normal_line_width)
+
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_NORMAL_THRESHOLD, math.degrees(self.normal_threshold))
+ rpr_context.set_parameter(pyrpr.CONTEXT_CONTOUR_ANTIALIASING, self.antialiasing)
+
+
class RPR_DenoiserProperties(RPR_Properties):
""" Denoiser properties. This is a child property in RPR_ViewLayerProperties """
enable: BoolProperty(
@@ -342,6 +414,13 @@ class RPR_ViewLayerProperites(RPR_Properties):
},
)
+ contour_info = {
+ 'rpr': pyrpr.AOV_COLOR,
+ 'name': "Outline",
+ 'channel': 'RGBA'
+ }
+
+
def aov_enabled_changed(self, context):
""" Request update of active render passes for Render Layers compositor input node """
context.view_layer.update_render_passes()
@@ -370,6 +449,13 @@ def aov_enabled_changed(self, context):
# TODO: Probably better to create each aov separately like: aov_depth: BoolProperty(...)
denoiser: PointerProperty(type=RPR_DenoiserProperties)
+
+ use_contour_render: BoolProperty(
+ name="Contour",
+ description="Use Contour rendering mode. Final render only",
+ default=False
+ )
+ contour: PointerProperty(type=RPR_ContourProperties)
def export_aovs(self, view_layer: bpy.types.ViewLayer, rpr_context, rpr_engine, enable_adaptive, cryptomatte_allowed):
"""
@@ -410,6 +496,10 @@ def export_aovs(self, view_layer: bpy.types.ViewLayer, rpr_context, rpr_engine,
aov = self.cryptomatte_aovs_info[i]
rpr_engine.add_pass(aov['name'], len(aov['channel']), aov['channel'], layer=view_layer.name)
rpr_context.enable_aov(aov['rpr'])
+
+ if self.use_contour_render:
+ aov = self.contour_info
+ rpr_engine.add_pass(aov['name'], len(aov['channel']), aov['channel'], layer=view_layer.name)
def enable_aov_by_name(self, name):
''' Enables a give aov name '''
diff --git a/src/rprblender/ui/__init__.py b/src/rprblender/ui/__init__.py
index 2997a322..7ba1b9df 100644
--- a/src/rprblender/ui/__init__.py
+++ b/src/rprblender/ui/__init__.py
@@ -134,7 +134,6 @@ def get_panels():
render.RPR_RENDER_PT_viewport_limits,
render.RPR_RENDER_PT_quality,
render.RPR_RENDER_PT_max_ray_depth,
- render.RPR_RENDER_PT_contour_rendering,
render.RPR_RENDER_PT_pixel_filter,
render.RPR_RENDER_PT_light_clamping,
render.RPR_RENDER_PT_bake_textures,
@@ -176,6 +175,7 @@ def get_panels():
view_layer.RPR_VIEWLAYER_PT_aovs,
view_layer.RPR_RENDER_PT_override,
view_layer.RPR_RENDER_PT_denoiser,
+ view_layer.RPR_RENDER_PT_contour_rendering,
view3d.RPR_VIEW3D_MT_menu,
view3d.RPR_VIEW3D_PT_panel,
diff --git a/src/rprblender/ui/object.py b/src/rprblender/ui/object.py
index 6b0f9992..79bbabd2 100644
--- a/src/rprblender/ui/object.py
+++ b/src/rprblender/ui/object.py
@@ -41,6 +41,7 @@ def draw(self, context):
col = self.layout.column()
col.active = context.scene.render.use_motion_blur
col.prop(rpr, "motion_blur")
+ col.prop(rpr, "deformation_blur")
class RPR_OBJECT_PT_visibility(RPR_Panel):
@@ -54,13 +55,12 @@ def draw(self, context):
rpr = context.object.rpr
cycles_vis = context.object.cycles_visibility
- flow = self.layout.grid_flow(row_major=True, even_columns=True)
- flow.column().prop(cycles_vis, 'camera')
- flow.column().prop(cycles_vis, 'glossy')
- flow.column().prop(cycles_vis, 'transmission')
- flow.column().prop(cycles_vis, 'diffuse')
- flow.column().prop(cycles_vis, 'shadow')
- flow.column().prop(rpr, 'visibility_contour')
+ self.layout.prop(cycles_vis, 'camera')
+ self.layout.prop(cycles_vis, 'diffuse')
+ self.layout.prop(cycles_vis, 'glossy')
+ self.layout.prop(cycles_vis, 'transmission')
+ self.layout.prop(cycles_vis, 'shadow')
+ self.layout.prop(rpr, 'visibility_contour')
class RPR_OBJECT_PT_subdivision(RPR_Panel):
diff --git a/src/rprblender/ui/render.py b/src/rprblender/ui/render.py
index cd77eec4..d7f22c2f 100644
--- a/src/rprblender/ui/render.py
+++ b/src/rprblender/ui/render.py
@@ -136,6 +136,10 @@ def draw(self, context):
col.prop(rpr, 'tile_y')
col.prop(rpr, 'tile_order')
+ col = self.layout.column(align=True)
+ col.enabled = context.view_layer.rpr.use_contour_render and context.scene.rpr.render_quality == 'FULL2'
+ col.prop(limits, 'contour_render_samples', slider=False)
+
class RPR_RENDER_PT_viewport_limits(RPR_Panel):
bl_label = "Viewport & Preview Sampling"
@@ -170,7 +174,8 @@ def draw(self, context):
col.prop(settings, 'use_gl_interop')
- col.prop(settings, 'viewport_denoiser_upscale')
+ col.prop(context.scene.rpr, 'viewport_denoiser')
+ col.prop(context.scene.rpr, 'viewport_upscale')
col.separator()
col.prop(limits, 'preview_samples')
@@ -196,6 +201,9 @@ def draw(self, context):
if rpr.render_quality in ('LOW', 'MEDIUM', 'HIGH'):
self.layout.prop(rpr, 'hybrid_low_mem')
+ if rpr.render_quality == 'FULL2':
+ self.layout.prop(rpr, 'texture_compression')
+
class RPR_RENDER_PT_max_ray_depth(RPR_Panel):
bl_label = "Max Ray Depth"
@@ -219,48 +227,6 @@ def draw(self, context):
self.layout.prop(rpr_scene, 'ray_cast_epsilon', slider=True)
-class RPR_RENDER_PT_contour_rendering(RPR_Panel):
- bl_label = "Contour Rendering"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw_header(self, context):
- self.layout.prop(context.scene.rpr, 'use_contour_render', text="")
- self.layout.enabled = context.scene.rpr.render_quality == 'FULL2'
-
- def draw(self, context):
- self.layout.use_property_split = True
- self.layout.use_property_decorate = False
-
- rpr_scene = context.scene.rpr
-
- main_column = self.layout.column()
- main_column.enabled = context.scene.rpr.is_contour_used()
-
- col = main_column.column(align=True)
- col.prop(rpr_scene, 'contour_use_object_id')
- args = col.column(align=True)
- args.enabled = rpr_scene.contour_use_object_id
- args.prop(rpr_scene, 'contour_object_id_line_width', slider=True)
-
- col = main_column.column(align=True)
- col.prop(rpr_scene, 'contour_use_material_id')
- args = col.column(align=True)
- args.enabled = rpr_scene.contour_use_material_id
- args.prop(rpr_scene, 'contour_material_id_line_width', slider=True)
-
- col = main_column.column(align=True)
- col.prop(rpr_scene, 'contour_use_shading_normal')
- args = col.column(align=True)
- args.enabled = rpr_scene.contour_use_shading_normal
- args.prop(rpr_scene, 'contour_normal_threshold', slider=True)
- args.prop(rpr_scene, 'contour_shading_normal_line_width', slider=True)
-
- col = main_column.column(align=True)
- col.prop(rpr_scene, 'contour_antialiasing', slider=True)
-
- main_column.prop(rpr_scene, 'contour_debug_flag')
-
-
class RPR_RENDER_PT_bake_textures(RPR_Panel):
bl_label = "Node Baking"
bl_parent_id = 'RPR_RENDER_PT_quality'
@@ -349,6 +315,7 @@ def draw(self, context):
col = layout.column()
col.enabled = context.scene.render.use_motion_blur
+ col.prop(context.scene.cycles, 'motion_blur_position', text="Position", slider=True)
col.prop(context.scene.camera.data.rpr, 'motion_blur_exposure', text="Shutter Opening ratio", slider=True)
col = layout.column()
diff --git a/src/rprblender/ui/view_layer.py b/src/rprblender/ui/view_layer.py
index edb6224c..91b0ac90 100644
--- a/src/rprblender/ui/view_layer.py
+++ b/src/rprblender/ui/view_layer.py
@@ -100,3 +100,46 @@ def draw(self, context):
view_layer = context.view_layer
layout.prop(view_layer, "material_override")
+
+
+class RPR_RENDER_PT_contour_rendering(RPR_Panel):
+ bl_label = "Outline Rendering"
+ bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "view_layer"
+
+ def draw_header(self, context):
+ self.layout.prop(context.view_layer.rpr, 'use_contour_render', text="")
+ self.layout.enabled = context.scene.rpr.render_quality == 'FULL2'
+
+ def draw(self, context):
+ self.layout.use_property_split = True
+ self.layout.use_property_decorate = False
+
+ contour_settings = context.view_layer.rpr.contour
+
+ main_column = self.layout.column()
+ main_column.enabled = context.view_layer.rpr.use_contour_render and context.scene.rpr.render_quality == 'FULL2'
+
+ col = main_column.column(align=True)
+ col.prop(contour_settings, 'use_object_id')
+ args = col.column(align=True)
+ args.enabled = contour_settings.use_object_id
+ args.prop(contour_settings, 'object_id_line_width', slider=True)
+
+ col = main_column.column(align=True)
+ col.prop(contour_settings, 'use_material_id')
+ args = col.column(align=True)
+ args.enabled = contour_settings.use_material_id
+ args.prop(contour_settings, 'material_id_line_width', slider=True)
+
+ col = main_column.column(align=True)
+ col.prop(contour_settings, 'use_shading_normal')
+ args = col.column(align=True)
+ args.enabled = contour_settings.use_shading_normal
+ args.prop(contour_settings, 'normal_threshold', slider=True)
+ args.prop(contour_settings, 'shading_normal_line_width', slider=True)
+
+ col = main_column.column(align=True)
+ col.prop(contour_settings, 'antialiasing', slider=True)
+
+ #main_column.prop(view_layer, 'contour_debug_flag')
\ No newline at end of file
diff --git a/src/rprblender/utils/__init__.py b/src/rprblender/utils/__init__.py
index 0df086b9..9611e65e 100644
--- a/src/rprblender/utils/__init__.py
+++ b/src/rprblender/utils/__init__.py
@@ -187,6 +187,11 @@ def get_tiles_number():
BLENDER_VERSION = f'{bpy.app.version[0]}.{bpy.app.version[1]}'
+IS_DEBUG_MODE = bool(int(os.environ.get('RPR_BLENDER_DEBUG', 0)))
+
+IS_OPENVDB_SUPPORT = (BLENDER_VERSION <= "2.92") and (IS_WIN or IS_MAC)
+
+
from . import logging
log = logging.Log(tag='utils')
diff --git a/src/rprblender/utils/helper_lib.py b/src/rprblender/utils/helper_lib.py
index e7e4b3a1..7c92b7ed 100644
--- a/src/rprblender/utils/helper_lib.py
+++ b/src/rprblender/utils/helper_lib.py
@@ -13,13 +13,11 @@
# limitations under the License.
#********************************************************************
import ctypes
-import platform
import numpy as np
import math
import os
-from . import package_root_dir, IS_WIN, IS_MAC, core_ver_str, rif_ver_str
-from .. import bl_info
+from . import package_root_dir, OS, IS_WIN, IS_DEBUG_MODE, IS_OPENVDB_SUPPORT
from . import logging
log = logging.Log(tag='utils.helper_lib')
@@ -36,40 +34,50 @@ class VdbGridData(ctypes.Structure):
def init():
global lib
- root_dir = package_root_dir()
- OS = platform.system()
-
- paths = [root_dir]
- if OS == 'Windows':
- lib_name = "RPRBlenderHelper.dll"
- paths.append(root_dir / "../../RPRBlenderHelper/.build/Release")
+ if IS_OPENVDB_SUPPORT:
+ lib_name = {
+ 'Windows': "RPRBlenderHelper_vdb.dll",
+ 'Linux': "libRPRBlenderHelper.so",
+ 'Darwin': "libRPRBlenderHelper_vdb.dylib"
+ }[OS]
+ else:
+ lib_name = {
+ 'Windows': "RPRBlenderHelper.dll",
+ 'Linux': "libRPRBlenderHelper.so",
+ 'Darwin': "libRPRBlenderHelper.dylib"
+ }[OS]
+
+ if IS_DEBUG_MODE:
+ root_dir = package_root_dir().parent.parent
+ if IS_WIN:
+ lib_dir = root_dir / 'RPRBlenderHelper/.build/Release'
+ if IS_OPENVDB_SUPPORT:
+ os.environ['PATH'] = \
+ f"{root_dir / 'RadeonProRenderSharedComponents/OpenVdb/Windows/bin'};" \
+ f"{os.environ.get('PATH', '')}"
+ # NOTE for python 3.8+ we have to use os.add_dll_directory()
+ # https://docs.python.org/3.8/library/os.html#os.add_dll_directory
- if (root_dir / "openvdb.dll").is_file():
- os.environ['PATH'] += ";" + str(root_dir)
else:
- os.environ['PATH'] += ";" + str((root_dir / "../../RadeonProRenderSharedComponents/OpenVdb/Windows/bin")
- .absolute())
-
- elif OS == 'Darwin':
- lib_name = "libRPRBlenderHelper.dylib"
- paths.append(root_dir / "../../RPRBlenderHelper/.build")
+ lib_dir = root_dir / 'RPRBlenderHelper/.build'
+ if IS_OPENVDB_SUPPORT:
+ os.environ['LD_LIBRARY_PATH'] = \
+ f"{root_dir / 'RadeonProRenderSharedComponents/OpenVdb/OSX/lib'}:" \
+ f"{os.environ.get('LD_LIBRARY_PATH', '')}"
else:
- lib_name = "libRPRBlenderHelper.so"
- paths.append(root_dir / "../../RPRBlenderHelper/.build")
-
- lib_path = next(p / lib_name for p in paths if (p / lib_name).is_file())
- log('Load lib', lib_path)
- try:
- lib = ctypes.cdll.LoadLibrary(str(lib_path))
- except OSError as e: # expand the traceback info with the exact addon version and library name
- raise Exception(f"Failed to load library {lib_path}",
- f"addon version {bl_info['version']}",
- f"core {core_ver_str(True)}",
- f"rif {rif_ver_str(True)}",
- str(e)) \
- from e
+ root_dir = package_root_dir()
+ lib_dir = package_root_dir()
+
+ if IS_WIN and IS_OPENVDB_SUPPORT:
+ if IS_DEBUG_MODE:
+ ctypes.CDLL(str(root_dir /
+ 'RadeonProRenderSharedComponents/OpenVdb/Windows/bin/Half-2_3.dll'))
+ else:
+ ctypes.CDLL(str(root_dir / 'Half-2_3.dll'))
+
+ lib = ctypes.CDLL(str(lib_dir / lib_name))
# Sun & Sky functions
lib.set_sun_horizontal_coordinate.argtypes = [ctypes.c_float, ctypes.c_float]
@@ -89,7 +97,7 @@ def init():
lib.get_sun_azimuth.restype = ctypes.c_float
lib.get_sun_altitude.restype = ctypes.c_float
- if IS_WIN or IS_MAC:
+ if IS_OPENVDB_SUPPORT:
# OpenVdb functions
lib.vdb_read_grids_list.argtypes = [ctypes.c_char_p]
lib.vdb_read_grids_list.restype = ctypes.c_char_p
@@ -178,6 +186,3 @@ def vdb_read_grid_data(vdb_file, grid_name):
lib.vdb_free_grid_data(ctypes.byref(data))
return res
-
-
-init()
diff --git a/src/rprblender/utils/install_libs.py b/src/rprblender/utils/install_libs.py
index e2b0e97c..f9c4db64 100644
--- a/src/rprblender/utils/install_libs.py
+++ b/src/rprblender/utils/install_libs.py
@@ -15,14 +15,20 @@
import sys
import site
import subprocess
+from datetime import datetime, timedelta
import bpy
-from . import IS_MAC, IS_LINUX
+from . import IS_MAC, IS_LINUX, package_root_dir
+from rprblender import config
from rprblender.utils.logging import Log
log = Log(tag="install_libs")
+PIP_CHECK_FILENAME = "pip_check.txt"
+NEXT_TIME_CHECK_DELTA = 5 # 5 days
+
+
# adding user site-packages path to sys.path
if site.getusersitepackages() not in sys.path:
sys.path.append(site.getusersitepackages())
@@ -47,10 +53,20 @@ def ensure_boto3():
Use this snippet to install boto3 library with all the dependencies if absent at the addon launch time
Note: still it will be available at the next Blender launch only
"""
+ pip_check_file = package_root_dir() / PIP_CHECK_FILENAME
+
try:
import boto3
except ImportError:
+ # checking if we need to install boto3
+ if pip_check_file.is_file():
+ str_time = pip_check_file.read_text()
+ next_time_check = datetime.fromisoformat(str_time)
+ if datetime.now() < next_time_check:
+ config.disable_athena_report = True
+ return
+
log.info("Installing boto3 library...")
try:
if IS_MAC or IS_LINUX:
@@ -64,3 +80,12 @@ def ensure_boto3():
except subprocess.SubprocessError as e:
log.warn("Something went wrong, unable to install boto3 library.", e)
+
+ # after failing installation of boto3 set next date to try install boto3
+ next_time_check = datetime.now() + timedelta(NEXT_TIME_CHECK_DELTA)
+ pip_check_file.write_text(next_time_check.isoformat())
+ config.disable_athena_report = True
+ return
+
+ if pip_check_file.is_file():
+ pip_check_file.unlink()