diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 0c42bf93ad..2b79bfaffe 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -18,85 +18,85 @@ jobs:
fail-fast: false
matrix:
include:
- - name: Linux_GCC_9_Python37
- os: ubuntu-20.04
+ - name: Linux_GCC_10_Python37
+ os: ubuntu-22.04
compiler: gcc
- compiler_version: "9"
+ compiler_version: "10"
python: 3.7
- cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON
+ cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON -DMATERIALX_BUILD_MONOLITHIC=ON
- - name: Linux_GCC_12_Python311
- os: ubuntu-22.04
+ - name: Linux_GCC_13_Python311
+ os: ubuntu-24.04
compiler: gcc
- compiler_version: "12"
+ compiler_version: "13"
python: 3.11
build_javascript: ON
- - name: Linux_GCC_13_Python312
- os: ubuntu-22.04
+ - name: Linux_GCC_14_Python312
+ os: ubuntu-24.04
compiler: gcc
- compiler_version: "13"
+ compiler_version: "14"
python: 3.12
- static_analysis: ON
- cmake_config: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Linux_GCC_CoverageAnalysis
- os: ubuntu-22.04
+ os: ubuntu-24.04
compiler: gcc
compiler_version: "None"
python: None
coverage_analysis: ON
cmake_config: -DMATERIALX_COVERAGE_ANALYSIS=ON -DMATERIALX_BUILD_RENDER=OFF -DMATERIALX_BUILD_PYTHON=OFF
- - name: Linux_Clang_10_Python37
- os: ubuntu-20.04
+ - name: Linux_Clang_13_Python37
+ os: ubuntu-22.04
compiler: clang
- compiler_version: "10"
+ compiler_version: "13"
python: 3.7
cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON
- - name: Linux_Clang_15_Python312
- os: ubuntu-22.04
+ - name: Linux_Clang_18_Python312
+ os: ubuntu-24.04
compiler: clang
- compiler_version: "15"
+ compiler_version: "18"
python: 3.12
test_render: ON
clang_format: ON
- - name: Linux_Clang_DynamicAnalysis
- os: ubuntu-22.04
- compiler: clang
- compiler_version: "15"
- python: None
- cmake_config: -DMATERIALX_DYNAMIC_ANALYSIS=ON
- dynamic_analysis: ON
-
- - name: MacOS_Xcode_13_Python37
- os: macos-12
+ - name: MacOS_Xcode_14_Python39
+ os: macos-13
compiler: xcode
- compiler_version: "13.1"
+ compiler_version: "14.1"
cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON
- python: 3.7
+ python: 3.9
- name: MacOS_Xcode_14_Python311
- os: macos-13
+ os: macos-14
compiler: xcode
compiler_version: "14.3"
python: 3.11
+ static_analysis: ON
+ cmake_config: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: MacOS_Xcode_15_Python312
os: macos-14
compiler: xcode
- compiler_version: "15.0"
+ compiler_version: "15.4"
python: 3.12
test_shaders: ON
+ - name: MacOS_Xcode_DynamicAnalysis
+ os: macos-14
+ compiler: xcode
+ compiler_version: "15.4"
+ python: None
+ dynamic_analysis: ON
+ cmake_config: -DMATERIALX_DYNAMIC_ANALYSIS=ON
+
- name: iOS_Xcode_15
os: macos-14
compiler: xcode
- compiler_version: "15.0"
+ compiler_version: "15.4"
python: None
- cmake_config: -DMATERIALX_BUILD_IOS=ON -DCMAKE_OSX_SYSROOT=`xcrun --sdk iphoneos --show-sdk-path` -DCMAKE_OSX_ARCHITECTURES=arm64
+ cmake_config: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=`xcrun --sdk iphoneos --show-sdk-path` -DCMAKE_OSX_ARCHITECTURES=arm64
- name: Windows_VS2019_Win32_Python37
os: windows-2019
@@ -128,14 +128,14 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
- sudo apt-get install xorg-dev mesa-utils
+ sudo apt-get install xorg-dev
if [ "${{ matrix.compiler_version }}" != 'None' ]; then
if [ "${{ matrix.compiler }}" = "gcc" ]; then
- sudo apt-get install -y g++-${{ matrix.compiler_version }} g++-${{ matrix.compiler_version }}-multilib
+ sudo apt-get install -y g++-${{ matrix.compiler_version }}
echo "CC=gcc-${{ matrix.compiler_version }}" >> $GITHUB_ENV
echo "CXX=g++-${{ matrix.compiler_version }}" >> $GITHUB_ENV
else
- sudo apt-get install -y clang-${{ matrix.compiler_version }} libc++-${{ matrix.compiler_version }}-dev libc++abi-${{ matrix.compiler_version }}-dev
+ sudo apt-get install -y clang-${{ matrix.compiler_version }}
echo "CC=clang-${{ matrix.compiler_version }}" >> $GITHUB_ENV
echo "CXX=clang++-${{ matrix.compiler_version }}" >> $GITHUB_ENV
fi
@@ -198,7 +198,7 @@ jobs:
run: find source \( -name *.h -o -name *.cpp -o -name *.mm -o -name *.inl \) ! -path "*/External/*" ! -path "*/NanoGUI/*" | xargs clang-format -i --verbose
- name: CMake Generate
- run: cmake -S . -B build -DMATERIALX_BUILD_PYTHON=ON -DMATERIALX_BUILD_VIEWER=ON -DMATERIALX_BUILD_GRAPH_EDITOR=ON -DMATERIALX_TEST_RENDER=OFF -DMATERIALX_WARNINGS_AS_ERRORS=ON ${{matrix.cmake_config}}
+ run: cmake -S . -B build -DMATERIALX_BUILD_PYTHON=ON -DMATERIALX_BUILD_VIEWER=ON -DMATERIALX_BUILD_GRAPH_EDITOR=ON -DMATERIALX_BUILD_TESTS=ON -DMATERIALX_TEST_RENDER=OFF -DMATERIALX_WARNINGS_AS_ERRORS=ON ${{matrix.cmake_config}}
- name: CMake Build
run: cmake --build build --target install --config Release --parallel 2
@@ -220,6 +220,10 @@ jobs:
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --target osl
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --target mdl
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --target msl
+ python Scripts/generateshader.py ../resources/Materials/Examples/OpenPbr --target glsl
+ python Scripts/generateshader.py ../resources/Materials/Examples/OpenPbr --target osl
+ python Scripts/generateshader.py ../resources/Materials/Examples/OpenPbr --target mdl
+ python Scripts/generateshader.py ../resources/Materials/Examples/OpenPbr --target msl
working-directory: python
- name: Shader Validation Tests (Windows)
@@ -227,13 +231,14 @@ jobs:
run: |
vcpkg/vcpkg install glslang --triplet=x64-windows
glslangValidator.exe -v
- python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target glsl --validator glslangValidator.exe --vulkanGlsl True --validatorArgs="-V --aml"
- python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target essl --validator glslangValidator.exe
+ python python/Scripts/generateshader.py resources/Materials/Examples --target glsl --validator glslangValidator.exe
+ python python/Scripts/generateshader.py resources/Materials/Examples --target essl --validator glslangValidator.exe
- name: Shader Validation Tests (MacOS)
if: matrix.test_shaders == 'ON' && runner.os == 'macOS'
run: |
- python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --target msl --validator "xcrun metal --language=metal" --validatorArgs="-w"
+ python python/Scripts/generateshader.py resources/Materials/Examples --target msl --validator "xcrun metal --language=metal" --validatorArgs="-w"
+ python python/Scripts/generateshader.py resources/Materials/TestSuite/stdlib --target msl --validator "xcrun metal --language=metal" --validatorArgs="-w"
- name: Coverage Analysis Tests
if: matrix.coverage_analysis == 'ON'
@@ -244,10 +249,14 @@ jobs:
working-directory: build
- name: Static Analysis Tests
- if: matrix.static_analysis == 'ON' && runner.os == 'Linux'
+ if: matrix.static_analysis == 'ON'
run: |
- sudo apt-get install cppcheck
- cppcheck --project=build/compile_commands.json --error-exitcode=1 --suppress=*:*/External/* --suppress=*:*/NanoGUI/*
+ if [ "${{ runner.os }}" = "Linux" ]; then
+ sudo apt-get install cppcheck
+ else
+ brew install cppcheck
+ fi
+ cppcheck --project=build/compile_commands.json --error-exitcode=1 --suppress=normalCheckLevelMaxBranches --suppress=*:*/External/* --suppress=*:*/NanoGUI/*
- name: Initialize Virtual Framebuffer
if: matrix.test_render == 'ON' && runner.os == 'Linux'
@@ -255,7 +264,6 @@ jobs:
Xvfb :1 -screen 0 1280x960x24 &
echo "DISPLAY=:1" >> $GITHUB_ENV
echo "LIBGL_ALWAYS_SOFTWARE=1" >> $GITHUB_ENV
- echo "GALLIUM_DRIVER=llvmpipe" >> $GITHUB_ENV
- name: Render Script Tests
if: matrix.test_render == 'ON'
@@ -269,6 +277,7 @@ jobs:
run: |
../installed/bin/MaterialXView --material brass_average_baked.mtlx --mesh ../../resources/Geometry/sphere.obj --screenWidth 128 --screenHeight 128 --cameraZoom 1.4 --shadowMap false --captureFilename Viewer_BrassAverage.png
../installed/bin/MaterialXView --material usd_preview_surface_carpaint.mtlx --mesh ../../resources/Geometry/sphere.obj --screenWidth 128 --screenHeight 128 --cameraZoom 1.4 --shadowMap false --captureFilename Viewer_CarpaintTranslated.png
+ ../installed/bin/MaterialXView --material open_pbr_carpaint.mtlx --mesh ../../resources/Geometry/sphere.obj --screenWidth 128 --screenHeight 128 --cameraZoom 1.4 --shadowMap false --captureFilename Viewer_OpenPBRCarpaintTranslated.png
../installed/bin/MaterialXGraphEditor --material ../../resources/Materials/Examples/StandardSurface/standard_surface_marble_solid.mtlx --viewWidth 128 --viewHeight 128 --captureFilename GraphEditor_MarbleSolid.png
working-directory: build/render
@@ -331,8 +340,8 @@ jobs:
working-directory: javascript/MaterialXView
- name: Deploy Web Viewer
- if: matrix.build_javascript == 'ON' && github.ref == 'refs/heads/main'
- uses: JamesIves/github-pages-deploy-action@v4
+ if: matrix.build_javascript == 'ON' && github.event_name != 'pull_request'
+ uses: JamesIves/github-pages-deploy-action@v4.6.4
with:
branch: gh-pages
folder: javascript/MaterialXView/dist
@@ -344,11 +353,10 @@ jobs:
with:
name: MaterialX_JavaScript
path: javascript/build/installed/JavaScript/MaterialX
- if-no-files-found: ignore
sdist:
name: Python SDist
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
if: github.repository == 'AcademySoftwareFoundation/MaterialX'
outputs:
sdist_filename: ${{ steps.generate.outputs.filename }}
@@ -383,8 +391,8 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-minor: ['7', '8', '9', '10', '11']
- os: ['ubuntu-latest', 'macos-latest', 'windows-latest']
+ python-minor: ['7', '8', '9', '10', '11', '12']
+ os: ['ubuntu-22.04', 'windows-2022', 'macos-13']
steps:
- name: Sync Repository
@@ -402,7 +410,7 @@ jobs:
path: sdist
- name: Build Wheel
- uses: pypa/cibuildwheel@v2.16.5
+ uses: pypa/cibuildwheel@v2.19.2
with:
package-dir: ${{ github.workspace }}/sdist/${{ needs.sdist.outputs.sdist_filename }}
env:
@@ -413,7 +421,6 @@ jobs:
# manylinux2014 is CentOS 7 based. Which means GCC 10 and glibc 2.17.
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_BEFORE_ALL_LINUX: yum install -y libXt-devel
- CIBW_BEFORE_ALL_MACOS: sudo xcode-select -switch /Applications/Xcode_13.4.app
CIBW_BUILD_VERBOSITY: 1
CIBW_ENVIRONMENT: CMAKE_BUILD_PARALLEL_LEVEL=2
# CIBW_BUILD_FRONTEND: build # https://github.com/pypa/build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..6ddb62f2b4
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,43 @@
+name: release
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: read
+
+jobs:
+
+ release:
+ name: Release Signing
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_TAG: ${{ github.ref_name }}
+ permissions:
+ contents: write
+ id-token: write
+ repository-projects: write
+
+ steps:
+ - name: Sync Repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Create Archive Name
+ run: echo "MATERIALX_ARCHIVE=MaterialX-${RELEASE_TAG//v}" >> $GITHUB_ENV
+
+ - name: Generate Archives
+ run: |
+ git archive --prefix ${MATERIALX_ARCHIVE}/ --output ${MATERIALX_ARCHIVE}.zip ${RELEASE_TAG}
+ git archive --prefix ${MATERIALX_ARCHIVE}/ --output ${MATERIALX_ARCHIVE}.tar.gz ${RELEASE_TAG}
+
+ - name: Sign and Upload Archives
+ uses: sigstore/gh-action-sigstore-python@v3.0.0
+ with:
+ inputs: |
+ ${{ env.MATERIALX_ARCHIVE }}.zip
+ ${{ env.MATERIALX_ARCHIVE }}.tar.gz
+ upload-signing-artifacts: true
+ release-signing-artifacts: false
diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt
index d60a54cf3b..f9d7b55565 100644
--- a/libraries/CMakeLists.txt
+++ b/libraries/CMakeLists.txt
@@ -8,9 +8,13 @@ if(NOT SKBUILD)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "${MATERIALX_INSTALL_STDLIB_PATH}"
PATTERN "CMakeLists.txt" EXCLUDE
- PATTERN "pbrlib_genosl_impl.*" EXCLUDE)
+ PATTERN "pbrlib_genosl_impl.*" EXCLUDE
+ PATTERN "mx39_pbrlib_genosl_impl.*" EXCLUDE)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pbrlib/genosl/pbrlib_genosl_impl.${PBRLIB_SUFFIX}"
DESTINATION "${MATERIALX_INSTALL_STDLIB_PATH}/pbrlib/genosl/" RENAME pbrlib_genosl_impl.mtlx)
+ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pbrlib/genosl/mx39_pbrlib_genosl_impl.${PBRLIB_SUFFIX}"
+ DESTINATION "${MATERIALX_INSTALL_STDLIB_PATH}/pbrlib/genosl/" RENAME mx39_pbrlib_genosl_impl.mtlx)
+
endif()
set(MATERIALX_PYTHON_LIBRARIES_PATH "${MATERIALX_PYTHON_FOLDER_NAME}/${MATERIALX_INSTALL_STDLIB_PATH}")
@@ -22,7 +26,10 @@ if(MATERIALX_BUILD_PYTHON)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "${MATERIALX_PYTHON_LIBRARIES_PATH}"
PATTERN "CMakeLists.txt" EXCLUDE
- PATTERN "pbrlib_genosl_impl.*" EXCLUDE)
+ PATTERN "pbrlib_genosl_impl.*" EXCLUDE
+ PATTERN "mx39_pbrlib_genosl_impl.*" EXCLUDE)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pbrlib/genosl/pbrlib_genosl_impl.${PBRLIB_SUFFIX}"
DESTINATION "${MATERIALX_PYTHON_LIBRARIES_PATH}/pbrlib/genosl/" RENAME pbrlib_genosl_impl.mtlx)
+ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pbrlib/genosl/mx39_pbrlib_genosl_impl.${PBRLIB_SUFFIX}"
+ DESTINATION "${MATERIALX_PYTHON_LIBRARIES_PATH}/pbrlib/genosl/" RENAME mx39_pbrlib_genosl_impl.mtlx)
endif()
diff --git a/libraries/bxdf/mx39_open_pbr_surface.mtlx b/libraries/bxdf/mx39_open_pbr_surface.mtlx
new file mode 100644
index 0000000000..42df378691
--- /dev/null
+++ b/libraries/bxdf/mx39_open_pbr_surface.mtlx
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl
new file mode 100644
index 0000000000..5637f388a0
--- /dev/null
+++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl
@@ -0,0 +1,30 @@
+#include "mx_microfacet.glsl"
+
+float mx39_pow6(float x)
+{
+ float x2 = mx_square(x);
+ return mx_square(x2) * x2;
+}
+
+// Generate a cosine-weighted sample on the unit hemisphere.
+vec3 mx39_cosine_sample_hemisphere(vec2 Xi)
+{
+ float phi = 2.0 * M_PI * Xi.x;
+ float cosTheta = sqrt(Xi.y);
+ float sinTheta = sqrt(1.0 - Xi.y);
+ return vec3(cos(phi) * sinTheta,
+ sin(phi) * sinTheta,
+ cosTheta);
+}
+
+// Construct an orthonormal basis from a unit vector.
+// https://graphics.pixar.com/library/OrthonormalB/paper.pdf
+mat3 mx39_orthonormal_basis(vec3 N)
+{
+ float sign = (N.z < 0.0) ? -1.0 : 1.0;
+ float a = -1.0 / (sign + N.z);
+ float b = N.x * N.y * a;
+ vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x);
+ vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y);
+ return mat3(X, Y, N);
+}
diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl
new file mode 100644
index 0000000000..c7ef22b78a
--- /dev/null
+++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl
@@ -0,0 +1,88 @@
+#include "mx39_microfacet.glsl"
+#include "mx_microfacet_diffuse.glsl"
+
+const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI);
+const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI);
+
+// Qualitative Oren-Nayar diffuse with simplified math:
+// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf
+float mx39_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness)
+{
+ float s = LdotV - NdotL * NdotV;
+ float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0;
+
+ float sigma2 = mx_square(roughness);
+ float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33));
+ float B = 0.45 * sigma2 / (sigma2 + 0.09);
+
+ return A + B * stinv;
+}
+
+// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo.
+float mx39_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness)
+{
+ vec2 r = vec2(1.0, 1.0) +
+ vec2(-0.4297, -0.6076) * roughness +
+ vec2(-0.7632, -0.4993) * NdotV * roughness +
+ vec2(1.4385, 2.0315) * mx_square(roughness);
+ return r.x / r.y;
+}
+
+float mx39_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness)
+{
+ float dirAlbedo = mx39_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness);
+ return clamp(dirAlbedo, 0.0, 1.0);
+}
+
+// Improved Oren-Nayar diffuse from Fujii:
+// https://mimosa-pudica.net/improved-oren-nayar.html
+float mx39_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness)
+{
+ float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness);
+ float B = roughness * A;
+ float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta)));
+ float G = Si * (acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) +
+ 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0;
+ return A + (B * G * M_PI_INV);
+}
+
+float mx39_oren_nayar_fujii_diffuse_avg_albedo(float roughness)
+{
+ float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness);
+ return A * (1.0 + FUJII_CONSTANT_2 * roughness);
+}
+
+// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface:
+// https://academysoftwarefoundation.github.io/OpenPBR/
+vec3 mx39_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color)
+{
+ float s = LdotV - NdotL * NdotV;
+ float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s;
+
+ // Compute the single-scatter lobe.
+ float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness);
+ vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv);
+
+ // Compute the multi-scatter lobe.
+ float dirAlbedoV = mx39_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness);
+ float dirAlbedoL = mx39_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness);
+ float avgAlbedo = mx39_oren_nayar_fujii_diffuse_avg_albedo(roughness);
+ vec3 colorMultiScatter = mx_square(color) * avgAlbedo /
+ (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo));
+ vec3 lobeMultiScatter = colorMultiScatter *
+ max(M_FLOAT_EPS, 1.0 - dirAlbedoV) *
+ max(M_FLOAT_EPS, 1.0 - dirAlbedoL) /
+ max(M_FLOAT_EPS, 1.0 - avgAlbedo);
+
+ // Return the sum.
+ return lobeSingleScatter + lobeMultiScatter;
+}
+
+vec3 mx39_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color)
+{
+ float dirAlbedo = mx39_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness);
+ float avgAlbedo = mx39_oren_nayar_fujii_diffuse_avg_albedo(roughness);
+ vec3 colorMultiScatter = mx_square(color) * avgAlbedo /
+ (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo));
+ return mix(colorMultiScatter, color, dirAlbedo);
+}
diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl
new file mode 100644
index 0000000000..4f94e9e0e1
--- /dev/null
+++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl
@@ -0,0 +1,108 @@
+#include "mx39_microfacet.glsl"
+#include "mx_microfacet_sheen.glsl"
+
+float mx_inversesqrt(float x)
+{
+#ifdef __METAL_VERSION__
+ return ::rsqrt(x);
+#else
+ return inversesqrt(x);
+#endif
+}
+
+// The following functions are adapted from https://github.com/tizian/ltc-sheen.
+// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al.
+
+// Gaussian fit to directional albedo table.
+float mx39_zeltner_sheen_dir_albedo(float x, float y)
+{
+ float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y));
+ float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y);
+ float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y));
+ return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o;
+}
+
+// Rational fits to LTC matrix coefficients.
+float mx39_zeltner_sheen_ltc_aInv(float x, float y)
+{
+ return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y);
+}
+
+float mx39_zeltner_sheen_ltc_bInv(float x, float y)
+{
+ return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y);
+}
+
+// V and N are assumed to be unit vectors.
+mat3 mx39_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV)
+{
+ // Generate a tangent vector in the plane of V and N.
+ // This required to correctly orient the LTC lobe.
+ vec3 X = V - N*NdotV;
+ float lenSqr = dot(X, X);
+ if (lenSqr > 0.0)
+ {
+ X *= mx_inversesqrt(lenSqr);
+ vec3 Y = cross(N, X);
+ return mat3(X, Y, N);
+ }
+
+ // If lenSqr == 0, then V == N, so any orthonormal basis will do.
+ return mx39_orthonormal_basis(N);
+}
+
+// Multiplication by directional albedo is handled by the calling function.
+float mx39_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness)
+{
+ mat3 toLTC = transpose(mx39_orthonormal_basis_ltc(V, N, NdotV));
+ vec3 w = toLTC * L;
+
+ float aInv = mx39_zeltner_sheen_ltc_aInv(NdotV, roughness);
+ float bInv = mx39_zeltner_sheen_ltc_bInv(NdotV, roughness);
+
+ // Transform w to original configuration (clamped cosine).
+ // |aInv 0 bInv|
+ // wo = M^-1 . w = | 0 aInv 0| . w
+ // | 0 0 1|
+ vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z);
+ float lenSqr = dot(wo, wo);
+
+ // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3
+ // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4
+ // = Do(wo) . |M^-1| / dot(wo, wo)^2
+ // = Do(wo) . aInv^2 / dot(wo, wo)^2
+ // = Do(wo) . (aInv / dot(wo, wo))^2
+ return max(wo.z, 0.0) * M_PI_INV * mx_square(aInv / lenSqr);
+}
+
+vec3 mx39_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf)
+{
+ float NdotV = clamp(dot(N, V), 0.0, 1.0);
+ roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl.
+
+ vec3 wo = mx39_cosine_sample_hemisphere(Xi);
+
+ float aInv = mx39_zeltner_sheen_ltc_aInv(NdotV, roughness);
+ float bInv = mx39_zeltner_sheen_ltc_bInv(NdotV, roughness);
+
+ // Transform wo from original configuration (clamped cosine).
+ // |1/aInv 0 -bInv/aInv|
+ // w = M . wo = | 0 1/aInv 0| . wo
+ // | 0 0 1|
+ vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z);
+
+ float lenSqr = dot(w, w);
+ w *= mx_inversesqrt(lenSqr);
+
+ // D(w) = Do(wo) . ||M.wo||^3 / |M|
+ // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M|
+ // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component)
+ // = Do(w) . dot(w, w)^2 * aInv^2
+ // = Do(w) . (aInv * dot(w, w))^2
+ pdf = max(w.z, 0.0) * M_PI_INV * mx_square(aInv * lenSqr);
+
+ mat3 fromLTC = mx39_orthonormal_basis_ltc(V, N, NdotV);
+ w = fromLTC * w;
+
+ return w;
+}
diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl
new file mode 100644
index 0000000000..fa4badc7e5
--- /dev/null
+++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl
@@ -0,0 +1,419 @@
+#include "mx39_microfacet.glsl"
+#include "mx_microfacet_specular.glsl"
+
+const int MX39_FRESNEL_MODEL_DIELECTRIC = 0;
+const int MX39_FRESNEL_MODEL_CONDUCTOR = 1;
+const int MX39_FRESNEL_MODEL_SCHLICK = 2;
+
+// Parameters for Fresnel calculations
+struct Mx39FresnelData
+{
+ // Fresnel model
+ int model;
+ bool airy;
+
+ // Physical Fresnel
+ vec3 ior;
+ vec3 extinction;
+
+ // Generalized Schlick Fresnel
+ vec3 F0;
+ vec3 F82;
+ vec3 F90;
+ float exponent;
+
+ // Thin film
+ float tf_thickness;
+ float tf_ior;
+
+ // Refraction
+ bool refraction;
+};
+
+// Convert a real-valued index of refraction to normal-incidence reflectivity.
+float mx39_ior_to_f0(float ior)
+{
+ return mx_square((ior - 1.0) / (ior + 1.0));
+}
+
+// Convert normal-incidence reflectivity to real-valued index of refraction.
+float mx39_f0_to_ior(float F0)
+{
+ float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99));
+ return (1.0 + sqrtF0) / (1.0 - sqrtF0);
+}
+vec3 mx39_f0_to_ior(vec3 F0)
+{
+ vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99));
+ return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0);
+}
+
+// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf
+vec3 mx39_fresnel_hoffman_schlick(float cosTheta, Mx39FresnelData fd)
+{
+ const float COS_THETA_MAX = 1.0 / 7.0;
+ const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0));
+
+ float x = clamp(cosTheta, 0.0, 1.0);
+ vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR;
+ return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx39_pow6(1.0 - x);
+}
+
+// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
+float mx39_fresnel_dielectric(float cosTheta, float ior)
+{
+ float c = cosTheta;
+ float g2 = ior*ior + c*c - 1.0;
+ if (g2 < 0.0)
+ {
+ // Total internal reflection
+ return 1.0;
+ }
+
+ float g = sqrt(g2);
+ return 0.5 * mx_square((g - c) / (g + c)) *
+ (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0)));
+}
+
+// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
+vec2 mx39_fresnel_dielectric_polarized(float cosTheta, float ior)
+{
+ float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0));
+ float sinTheta2 = 1.0 - cosTheta2;
+
+ float t0 = max(ior * ior - sinTheta2, 0.0);
+ float t1 = t0 + cosTheta2;
+ float t2 = 2.0 * sqrt(t0) * cosTheta;
+ float Rs = (t1 - t2) / (t1 + t2);
+
+ float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2;
+ float t4 = t2 * sinTheta2;
+ float Rp = Rs * (t3 - t4) / (t3 + t4);
+
+ return vec2(Rp, Rs);
+}
+
+// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
+void mx39_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs)
+{
+ float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0));
+ float sinTheta2 = 1.0 - cosTheta2;
+ vec3 n2 = n * n;
+ vec3 k2 = k * k;
+
+ vec3 t0 = n2 - k2 - vec3(sinTheta2);
+ vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2);
+ vec3 t1 = a2plusb2 + vec3(cosTheta2);
+ vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0));
+ vec3 t2 = 2.0 * a * cosTheta;
+ Rs = (t1 - t2) / (t1 + t2);
+
+ vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2);
+ vec3 t4 = t2 * sinTheta2;
+ Rp = Rs * (t3 - t4) / (t3 + t4);
+}
+
+vec3 mx39_fresnel_conductor(float cosTheta, vec3 n, vec3 k)
+{
+ vec3 Rp, Rs;
+ mx39_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs);
+ return 0.5 * (Rp + Rs);
+}
+
+// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html
+void mx39_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS)
+{
+ vec3 k2 = kappa2 / eta2;
+ vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta;
+ vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr;
+ vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2));
+ vec3 U = sqrt((A+B)/2.0);
+ vec3 V = max(vec3(0.0), sqrt((B-A)/2.0));
+
+ phiS = atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta));
+ phiP = atan(2.0*eta1*eta2*eta2*cosTheta * (2.0*k2*U - (vec3(1.0)-k2*k2) * V),
+ mx_square(eta2*eta2*(vec3(1.0)+k2*k2)*cosTheta) - eta1*eta1*(U*U+V*V));
+}
+
+// A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence
+// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html
+vec3 mx39_fresnel_airy(float cosTheta, Mx39FresnelData fd)
+{
+ // XYZ to CIE 1931 RGB color space (using neutral E illuminant)
+ const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968);
+
+ // Assume vacuum on the outside
+ float eta1 = 1.0;
+ float eta2 = max(fd.tf_ior, eta1);
+ vec3 eta3 = (fd.model == MX39_FRESNEL_MODEL_SCHLICK) ? mx39_f0_to_ior(fd.F0) : fd.ior;
+ vec3 kappa3 = (fd.model == MX39_FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction;
+ float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2));
+
+ // First interface
+ vec2 R12 = mx39_fresnel_dielectric_polarized(cosTheta, eta2 / eta1);
+ if (cosThetaT <= 0.0)
+ {
+ // Total internal reflection
+ R12 = vec2(1.0);
+ }
+ vec2 T121 = vec2(1.0) - R12;
+
+ // Second interface
+ vec3 R23p, R23s;
+ if (fd.model == MX39_FRESNEL_MODEL_SCHLICK)
+ {
+ vec3 f = mx39_fresnel_hoffman_schlick(cosThetaT, fd);
+ R23p = 0.5 * f;
+ R23s = 0.5 * f;
+ }
+ else
+ {
+ mx39_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s);
+ }
+
+ // Phase shift
+ float cosB = cos(atan(eta2 / eta1));
+ vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI);
+ vec3 phi23p, phi23s;
+ if (fd.model == MX39_FRESNEL_MODEL_SCHLICK)
+ {
+ phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0,
+ (eta3[1] < eta2) ? M_PI : 0.0,
+ (eta3[2] < eta2) ? M_PI : 0.0);
+ phi23s = phi23p;
+ }
+ else
+ {
+ mx39_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s);
+ }
+ vec3 r123p = max(sqrt(R12.x*R23p), 0.0);
+ vec3 r123s = max(sqrt(R12.y*R23s), 0.0);
+
+ // Iridescence term
+ vec3 I = vec3(0.0);
+ vec3 Cm, Sm;
+
+ // Optical path difference
+ float distMeters = fd.tf_thickness * 1.0e-9;
+ float opd = 2.0 * eta2 * cosThetaT * distMeters;
+
+ // Iridescence term using spectral antialiasing for Parallel polarization
+
+ // Reflectance term for m=0 (DC term amplitude)
+ vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p);
+ I += R12.x + Rs;
+
+ // Reflectance term for m>0 (pairs of diracs)
+ Cm = Rs - T121.x;
+ for (int m=1; m<=2; m++)
+ {
+ Cm *= r123p;
+ Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x)));
+ I += Cm*Sm;
+ }
+
+ // Iridescence term using spectral antialiasing for Perpendicular polarization
+
+ // Reflectance term for m=0 (DC term amplitude)
+ vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s);
+ I += R12.y + Rp;
+
+ // Reflectance term for m>0 (pairs of diracs)
+ Cm = Rp - T121.y;
+ for (int m=1; m<=2; m++)
+ {
+ Cm *= r123s;
+ Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y)));
+ I += Cm*Sm;
+ }
+
+ // Average parallel and perpendicular polarization
+ I *= 0.5;
+
+ // Convert back to RGB reflectance
+ I = clamp(XYZ_TO_RGB * I, 0.0, 1.0);
+
+ return I;
+}
+
+Mx39FresnelData mx39_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior)
+{
+ Mx39FresnelData fd;
+ fd.model = MX39_FRESNEL_MODEL_DIELECTRIC;
+ fd.airy = tf_thickness > 0.0;
+ fd.ior = vec3(ior);
+ fd.extinction = vec3(0.0);
+ fd.F0 = vec3(0.0);
+ fd.F82 = vec3(0.0);
+ fd.F90 = vec3(0.0);
+ fd.exponent = 0.0;
+ fd.tf_thickness = tf_thickness;
+ fd.tf_ior = tf_ior;
+ fd.refraction = false;
+ return fd;
+}
+
+Mx39FresnelData mx39_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior)
+{
+ Mx39FresnelData fd;
+ fd.model = MX39_FRESNEL_MODEL_CONDUCTOR;
+ fd.airy = tf_thickness > 0.0;
+ fd.ior = ior;
+ fd.extinction = extinction;
+ fd.F0 = vec3(0.0);
+ fd.F82 = vec3(0.0);
+ fd.F90 = vec3(0.0);
+ fd.exponent = 0.0;
+ fd.tf_thickness = tf_thickness;
+ fd.tf_ior = tf_ior;
+ fd.refraction = false;
+ return fd;
+}
+
+Mx39FresnelData mx39_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior)
+{
+ Mx39FresnelData fd;
+ fd.model = MX39_FRESNEL_MODEL_SCHLICK;
+ fd.airy = tf_thickness > 0.0;
+ fd.ior = vec3(0.0);
+ fd.extinction = vec3(0.0);
+ fd.F0 = F0;
+ fd.F82 = F82;
+ fd.F90 = F90;
+ fd.exponent = exponent;
+ fd.tf_thickness = tf_thickness;
+ fd.tf_ior = tf_ior;
+ fd.refraction = false;
+ return fd;
+}
+
+vec3 mx39_compute_fresnel(float cosTheta, Mx39FresnelData fd)
+{
+ if (fd.airy)
+ {
+ return mx39_fresnel_airy(cosTheta, fd);
+ }
+ else if (fd.model == MX39_FRESNEL_MODEL_DIELECTRIC)
+ {
+ return vec3(mx39_fresnel_dielectric(cosTheta, fd.ior.x));
+ }
+ else if (fd.model == MX39_FRESNEL_MODEL_CONDUCTOR)
+ {
+ return mx39_fresnel_conductor(cosTheta, fd.ior, fd.extinction);
+ }
+ else
+ {
+ return mx39_fresnel_hoffman_schlick(cosTheta, fd);
+ }
+}
+
+#ifdef MX39_USING_ENVIRONMENT_NONE
+vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd)
+{
+ return vec3(0.0);
+}
+#endif
+
+#ifdef MX39_USING_ENVIRONMENT_PREFILTER
+vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd)
+{
+ N = mx_forward_facing_normal(N, V);
+ vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, N, fd.ior.x) : -reflect(V, N);
+
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ float avgAlpha = mx_average_alpha(alpha);
+ vec3 F = mx39_compute_fresnel(NdotV, fd);
+ float G = mx_ggx_smith_G2(NdotV, NdotV, avgAlpha);
+ vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G;
+
+ vec3 Li = mx_latlong_map_lookup(L, $envMatrix, mx_latlong_alpha_to_lod(avgAlpha), $envRadiance);
+ return Li * FG * $envLightIntensity;
+}
+#endif
+
+#ifdef MX39_USING_ENVIRONMENT_FIS
+vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd)
+{
+ // Generate tangent frame.
+ X = normalize(X - dot(X, N) * N);
+ vec3 Y = cross(N, X);
+ mat3 tangentToWorld = mat3(X, Y, N);
+
+ // Transform the view vector to tangent space.
+ V = vec3(dot(V, X), dot(V, Y), dot(V, N));
+
+ // Compute derived properties.
+ float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(alpha);
+ float G1V = mx_ggx_smith_G1(NdotV, avgAlpha);
+
+ // Integrate outgoing radiance using filtered importance sampling.
+ // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf
+ vec3 radiance = vec3(0.0);
+ int envRadianceSamples = $envRadianceSamples;
+ for (int i = 0; i < envRadianceSamples; i++)
+ {
+ vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples);
+
+ // Compute the half vector and incoming light direction.
+ vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha);
+ vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H);
+
+ // Compute dot products for this sample.
+ float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0);
+ float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0);
+
+ // Sample the environment light from the given direction.
+ vec3 Lw = tangentToWorld * L;
+ float pdf = mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV);
+ float lod = mx_latlong_compute_lod(Lw, pdf, float($envRadianceMips - 1), envRadianceSamples);
+ vec3 sampleColor = mx_latlong_map_lookup(Lw, $envMatrix, lod, $envRadiance);
+
+ // Compute the Fresnel term.
+ vec3 F = mx39_compute_fresnel(VdotH, fd);
+
+ // Compute the geometric term.
+ float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha);
+
+ // Compute the combined FG term, which is inverted for refraction.
+ vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G;
+
+ // Add the radiance contribution of this sample.
+ // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
+ // incidentLight = sampleColor * NdotL
+ // microfacetSpecular = D * F * G / (4 * NdotL * NdotV)
+ // pdf = D * G1V / (4 * NdotV);
+ // radiance = incidentLight * microfacetSpecular / pdf
+ radiance += sampleColor * FG;
+ }
+
+ // Apply the global component of the geometric term and normalize.
+ radiance /= G1V * float(envRadianceSamples);
+
+ // Return the final radiance.
+ return radiance * $envLightIntensity;
+}
+#endif
+
+#ifdef MX39_USING_TRANSMISSION_OPACITY
+vec3 mx39_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd, vec3 tint)
+{
+ return tint;
+}
+#endif
+
+#ifdef MX39_USING_TRANSMISSION_REFRACT
+vec3 mx39_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd, vec3 tint)
+{
+ // Approximate the appearance of surface transmission as glossy
+ // environment map refraction, ignoring any scene geometry that might
+ // be visible through the surface.
+ fd.refraction = true;
+ if ($refractionTwoSided)
+ {
+ tint = mx_square(tint);
+ }
+ return mx39_environment_radiance(N, V, X, alpha, distribution, fd) * tint;
+}
+#endif
diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
index 0b28f3645f..6bfc64571e 100644
--- a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
@@ -67,3 +67,5 @@ vec3 mx_environment_irradiance(vec3 N)
vec3 Li = mx_latlong_map_lookup(N, $envMatrix, 0.0, $envIrradiance);
return Li * $envLightIntensity;
}
+
+#define MX39_USING_ENVIRONMENT_FIS
diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl
index f0a1da5989..f535c84757 100644
--- a/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl
@@ -9,3 +9,5 @@ vec3 mx_environment_irradiance(vec3 N)
{
return vec3(0.0);
}
+
+#define MX39_USING_ENVIRONMENT_NONE
diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl
index 778742c449..89e898a1e1 100644
--- a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl
@@ -28,3 +28,5 @@ vec3 mx_environment_irradiance(vec3 N)
vec3 Li = mx_latlong_map_lookup(N, $envMatrix, 0.0, $envIrradiance);
return Li * $envLightIntensity;
}
+
+#define MX39_USING_ENVIRONMENT_PREFILTER
diff --git a/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl b/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl
index 2861d06194..54bef92ff4 100644
--- a/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl
@@ -4,3 +4,5 @@ vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio
{
return tint;
}
+
+#define MX39_USING_TRANSMISSION_OPACITY
diff --git a/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl b/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl
index 64e496a384..4493321b4b 100644
--- a/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl
@@ -12,3 +12,5 @@ vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio
}
return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint;
}
+
+#define MX39_USING_TRANSMISSION_REFRACT
diff --git a/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl
new file mode 100644
index 0000000000..b93b5b8cd0
--- /dev/null
+++ b/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl
@@ -0,0 +1,42 @@
+#include "lib/mx39_microfacet_diffuse.glsl"
+
+void mx39_compensating_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, bool energy_compensation, inout BSDF bsdf)
+{
+ bsdf.throughput = vec3(0.0);
+
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ normal = mx_forward_facing_normal(normal, V);
+
+ float NdotV = clamp(dot(normal, V), M_FLOAT_EPS, 1.0);
+ float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0);
+ float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0);
+
+ vec3 diffuse = energy_compensation ?
+ mx39_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) :
+ mx39_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color;
+ bsdf.response = diffuse * occlusion * weight * NdotL * M_PI_INV;
+}
+
+void mx39_compensating_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, bool energy_compensation, inout BSDF bsdf)
+{
+ bsdf.throughput = vec3(0.0);
+
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ normal = mx_forward_facing_normal(normal, V);
+
+ float NdotV = clamp(dot(normal, V), M_FLOAT_EPS, 1.0);
+
+ vec3 diffuse = energy_compensation ?
+ mx39_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) :
+ mx39_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color;
+ vec3 Li = mx_environment_irradiance(normal);
+ bsdf.response = Li * diffuse * weight;
+}
diff --git a/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl
new file mode 100644
index 0000000000..3dc668c4fa
--- /dev/null
+++ b/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl
@@ -0,0 +1,92 @@
+#include "lib/mx39_microfacet_specular.glsl"
+
+void mx39_dielectric_tf_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+
+ X = normalize(X - dot(X, N) * N);
+ vec3 Y = cross(N, X);
+ vec3 H = normalize(L + V);
+
+ float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+ float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+ vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N));
+
+ vec3 safeTint = max(tint, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(VdotH, fd);
+ float D = mx_ggx_NDF(Ht, safeAlpha);
+ float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha);
+
+ float F0 = mx39_ior_to_f0(ior);
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;
+ bsdf.throughput = 1.0 - dirAlbedo * weight;
+
+ // Note: NdotL is cancelled out
+ bsdf.response = D * F * G * comp * safeTint * occlusion * weight / (4.0 * NdotV);
+}
+
+void mx39_dielectric_tf_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ vec3 safeTint = max(tint, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(NdotV, fd);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+
+ float F0 = mx39_ior_to_f0(ior);
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;
+ bsdf.throughput = 1.0 - dirAlbedo * weight;
+
+ if (scatter_mode != 0)
+ {
+ bsdf.response = mx39_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight;
+ }
+}
+
+void mx39_dielectric_tf_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ vec3 safeTint = max(tint, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(NdotV, fd);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+
+ float F0 = mx39_ior_to_f0(ior);
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;
+ bsdf.throughput = 1.0 - dirAlbedo * weight;
+
+ vec3 Li = mx39_environment_radiance(N, V, X, safeAlpha, distribution, fd);
+ bsdf.response = Li * safeTint * comp * weight;
+}
diff --git a/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl
new file mode 100644
index 0000000000..512245784a
--- /dev/null
+++ b/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl
@@ -0,0 +1,98 @@
+#include "lib/mx39_microfacet_specular.glsl"
+
+void mx39_generalized_schlick_tf_82_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+
+ X = normalize(X - dot(X, N) * N);
+ vec3 Y = cross(N, X);
+ vec3 H = normalize(L + V);
+
+ float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+ float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+ vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N));
+
+ vec3 safeColor0 = max(color0, 0.0);
+ vec3 safeColor82 = max(color82, 0.0);
+ vec3 safeColor90 = max(color90, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(VdotH, fd);
+ float D = mx_ggx_NDF(Ht, safeAlpha);
+ float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha);
+
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp;
+ float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));
+ bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight);
+
+ // Note: NdotL is cancelled out
+ bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV);
+}
+
+void mx39_generalized_schlick_tf_82_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ vec3 safeColor0 = max(color0, 0.0);
+ vec3 safeColor82 = max(color82, 0.0);
+ vec3 safeColor90 = max(color90, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(NdotV, fd);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp;
+ float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));
+ bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight);
+
+ if (scatter_mode != 0)
+ {
+ float avgF0 = dot(safeColor0, vec3(1.0 / 3.0));
+ fd.ior = vec3(mx39_f0_to_ior(avgF0));
+ bsdf.response = mx39_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight;
+ }
+}
+
+void mx39_generalized_schlick_tf_82_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ vec3 safeColor0 = max(color0, 0.0);
+ vec3 safeColor82 = max(color82, 0.0);
+ vec3 safeColor90 = max(color90, 0.0);
+ Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior);
+ vec3 F = mx39_compute_fresnel(NdotV, fd);
+
+ vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+ vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+ vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp;
+ float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));
+ bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight);
+
+ vec3 Li = mx39_environment_radiance(N, V, X, safeAlpha, distribution, fd);
+ bsdf.response = Li * comp * weight;
+}
diff --git a/libraries/pbrlib/genglsl/mx39_pbrlib_genglsl_impl.mtlx b/libraries/pbrlib/genglsl/mx39_pbrlib_genglsl_impl.mtlx
new file mode 100644
index 0000000000..3626896222
--- /dev/null
+++ b/libraries/pbrlib/genglsl/mx39_pbrlib_genglsl_impl.mtlx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl
new file mode 100644
index 0000000000..c6a874a5da
--- /dev/null
+++ b/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl
@@ -0,0 +1,38 @@
+#include "lib/mx39_microfacet_sheen.glsl"
+
+void mx39_sheen_zeltner_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 N, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl.
+
+ vec3 fr = color * mx39_zeltner_sheen_brdf(L, V, N, NdotV, roughness);
+ float dirAlbedo = mx39_zeltner_sheen_dir_albedo(NdotV, roughness);
+ bsdf.throughput = vec3(1.0 - dirAlbedo * weight);
+ bsdf.response = dirAlbedo * fr * occlusion * weight;
+}
+
+void mx39_sheen_zeltner_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 N, inout BSDF bsdf)
+{
+ if (weight < M_FLOAT_EPS)
+ {
+ return;
+ }
+
+ N = mx_forward_facing_normal(N, V);
+ float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
+
+ float dirAlbedo;
+ roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl.
+ dirAlbedo = mx39_zeltner_sheen_dir_albedo(NdotV, roughness);
+
+ vec3 Li = mx_environment_irradiance(N);
+ bsdf.throughput = vec3(1.0 - dirAlbedo * weight);
+ bsdf.response = Li * color * dirAlbedo * weight;
+}
diff --git a/libraries/pbrlib/genmdl/mx39_pbrlib_genmdl_impl.mtlx b/libraries/pbrlib/genmdl/mx39_pbrlib_genmdl_impl.mtlx
new file mode 100644
index 0000000000..5bc13eb9fd
--- /dev/null
+++ b/libraries/pbrlib/genmdl/mx39_pbrlib_genmdl_impl.mtlx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/genmdl/pbrlib_genmdl_impl.mtlx b/libraries/pbrlib/genmdl/pbrlib_genmdl_impl.mtlx
index a4277bf675..b642c9343c 100644
--- a/libraries/pbrlib/genmdl/pbrlib_genmdl_impl.mtlx
+++ b/libraries/pbrlib/genmdl/pbrlib_genmdl_impl.mtlx
@@ -23,7 +23,7 @@
-
+
diff --git a/libraries/pbrlib/genmsl/mx39_pbrlib_genmsl_impl.mtlx b/libraries/pbrlib/genmsl/mx39_pbrlib_genmsl_impl.mtlx
new file mode 100644
index 0000000000..3576620dde
--- /dev/null
+++ b/libraries/pbrlib/genmsl/mx39_pbrlib_genmsl_impl.mtlx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/genosl/legacy/mx39_compensating_oren_nayar_diffuse_bsdf.osl b/libraries/pbrlib/genosl/legacy/mx39_compensating_oren_nayar_diffuse_bsdf.osl
new file mode 100644
index 0000000000..2ce4dd07b1
--- /dev/null
+++ b/libraries/pbrlib/genosl/legacy/mx39_compensating_oren_nayar_diffuse_bsdf.osl
@@ -0,0 +1,5 @@
+void mx39_compensating_oren_nayar_diffuse_bsdf(float weight, color _color, float roughness, normal N, int energy_compensation, output BSDF bsdf)
+{
+ bsdf.response = _color * weight * oren_nayar(N, roughness);
+ bsdf.throughput = color(0.0);
+}
diff --git a/libraries/pbrlib/genosl/legacy/mx39_dielectric_tf_bsdf.osl b/libraries/pbrlib/genosl/legacy/mx39_dielectric_tf_bsdf.osl
new file mode 100644
index 0000000000..5d6c81fd36
--- /dev/null
+++ b/libraries/pbrlib/genosl/legacy/mx39_dielectric_tf_bsdf.osl
@@ -0,0 +1,36 @@
+#include "../lib/mx_microfacet_specular.osl"
+
+void mx39_dielectric_tf_bsdf(float weight, color tint, float ior, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
+{
+ if (scatter_mode == "T")
+ {
+ bsdf.response = tint * weight * microfacet(distribution, N, U, roughness.x, roughness.y, ior, 1);
+ bsdf.throughput = tint * weight;
+ return;
+ }
+
+ float NdotV = clamp(dot(N,-I), M_FLOAT_EPS, 1.0);
+ float F0 = mx_ior_to_f0(ior);
+ float F = mx_fresnel_schlick(NdotV, F0);
+
+ // Calculate compensation for multiple scattering.
+ // This should normally be done inside the closure
+ // but since vanilla OSL doesen't support this we
+ // add it here in shader code instead.
+ vector2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+ float comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+
+ // Calculate throughput from directional albedo.
+ float dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, ior) * comp;
+ bsdf.throughput = 1.0 - dirAlbedo * weight;
+
+ if (scatter_mode == "R")
+ {
+ bsdf.response = tint * weight * comp * microfacet(distribution, N, U, safeAlpha.x, safeAlpha.y, ior, 0);
+ }
+ else
+ {
+ bsdf.response = tint * weight * comp * microfacet(distribution, N, U, safeAlpha.x, safeAlpha.y, ior, 2);
+ }
+}
diff --git a/libraries/pbrlib/genosl/legacy/mx39_generalized_schlick_tf_82_bsdf.osl b/libraries/pbrlib/genosl/legacy/mx39_generalized_schlick_tf_82_bsdf.osl
new file mode 100644
index 0000000000..ac0e3928e5
--- /dev/null
+++ b/libraries/pbrlib/genosl/legacy/mx39_generalized_schlick_tf_82_bsdf.osl
@@ -0,0 +1,38 @@
+#include "../lib/mx_microfacet_specular.osl"
+
+void mx39_generalized_schlick_tf_82_bsdf(float weight, color color0, color color82, color color90, float exponent, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
+{
+ float avgF0 = dot(color0, color(1.0 / 3.0));
+ float ior = mx_f0_to_ior(avgF0);
+
+ if (scatter_mode == "T")
+ {
+ bsdf.response = weight * microfacet(distribution, N, U, roughness.x, roughness.y, ior, 1);
+ bsdf.throughput = weight;
+ return;
+ }
+
+ float NdotV = fabs(dot(N,-I));
+ color F = mx_fresnel_schlick(NdotV, color0, color90, exponent);
+
+ // Calculate compensation for multiple scattering.
+ // This should normally be done inside the closure
+ // but since vanilla OSL doesen't support this we
+ // add it here in shader code instead.
+ vector2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
+ float avgAlpha = mx_average_alpha(safeAlpha);
+ color comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
+
+ // Calculate throughput from directional albedo.
+ color dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, color0, color90) * comp;
+ float avgDirAlbedo = dot(dirAlbedo, color(1.0 / 3.0));
+ bsdf.throughput = 1.0 - avgDirAlbedo * weight;
+
+ // Calculate the reflection response, setting IOR to zero to disable internal Fresnel.
+ bsdf.response = F * comp * weight * microfacet(distribution, N, U, safeAlpha.x, safeAlpha.y, 0.0, 0);
+
+ if (scatter_mode == "RT")
+ {
+ bsdf.response += bsdf.throughput * microfacet(distribution, N, U, safeAlpha.x, safeAlpha.y, ior, 1);
+ }
+}
diff --git a/libraries/pbrlib/genosl/mx39_dielectric_tf_bsdf.osl b/libraries/pbrlib/genosl/mx39_dielectric_tf_bsdf.osl
new file mode 100644
index 0000000000..5b4243506c
--- /dev/null
+++ b/libraries/pbrlib/genosl/mx39_dielectric_tf_bsdf.osl
@@ -0,0 +1,15 @@
+void mx39_dielectric_tf_bsdf(float weight, color tint, float ior, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
+{
+ if (scatter_mode == "R")
+ {
+ bsdf = weight * dielectric_bsdf(N, U, tint, color(0.0), roughness.x, roughness.y, ior, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+ else if (scatter_mode == "T")
+ {
+ bsdf = weight * dielectric_bsdf(N, U, color(0.0), tint, roughness.x, roughness.y, ior, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+ else
+ {
+ bsdf = weight * dielectric_bsdf(N, U, tint, tint, roughness.x, roughness.y, ior, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+}
diff --git a/libraries/pbrlib/genosl/mx39_generalized_schlick_tf_82_bsdf.osl b/libraries/pbrlib/genosl/mx39_generalized_schlick_tf_82_bsdf.osl
new file mode 100644
index 0000000000..4fb9eac0c3
--- /dev/null
+++ b/libraries/pbrlib/genosl/mx39_generalized_schlick_tf_82_bsdf.osl
@@ -0,0 +1,15 @@
+void mx39_generalized_schlick_tf_82_bsdf(float weight, color color0, color color82, color color90, float exponent, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
+{
+ if (scatter_mode == "R")
+ {
+ bsdf = weight * generalized_schlick_bsdf(N, U, color(1.0), color(0.0), roughness.x, roughness.y, color0, color90, exponent, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+ else if (scatter_mode == "T")
+ {
+ bsdf = weight * generalized_schlick_bsdf(N, U, color(0.0), color(1.0), roughness.x, roughness.y, color0, color90, exponent, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+ else
+ {
+ bsdf = weight * generalized_schlick_bsdf(N, U, color(1.0), color(1.0), roughness.x, roughness.y, color0, color90, exponent, distribution, "thinfilm_thickness", thinfilm_thickness, "thinfilm_ior", thinfilm_ior);
+ }
+}
diff --git a/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.legacy b/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.legacy
new file mode 100644
index 0000000000..95af5056f4
--- /dev/null
+++ b/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.legacy
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.mtlx b/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.mtlx
new file mode 100644
index 0000000000..df8d6a59e9
--- /dev/null
+++ b/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.mtlx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/pbrlib/pbrlib_defs.mtlx b/libraries/pbrlib/pbrlib_defs.mtlx
index e63625a45d..5fe6a6a773 100644
--- a/libraries/pbrlib/pbrlib_defs.mtlx
+++ b/libraries/pbrlib/pbrlib_defs.mtlx
@@ -417,4 +417,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_aluminum_brushed.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_aluminum_brushed.mtlx
new file mode 100644
index 0000000000..7f7072d416
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_aluminum_brushed.mtlx
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_carpaint.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_carpaint.mtlx
new file mode 100644
index 0000000000..fd42fdf48d
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_carpaint.mtlx
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_default.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_default.mtlx
new file mode 100644
index 0000000000..8125541a99
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_default.mtlx
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_glass.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_glass.mtlx
new file mode 100644
index 0000000000..a661b9d1f6
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_glass.mtlx
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_honey.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_honey.mtlx
new file mode 100644
index 0000000000..41e076d231
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_honey.mtlx
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_ketchup.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_ketchup.mtlx
new file mode 100644
index 0000000000..cda2ebdf7e
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_ketchup.mtlx
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_lightbulb.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_lightbulb.mtlx
new file mode 100644
index 0000000000..a915f842a4
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_lightbulb.mtlx
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_pearl.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_pearl.mtlx
new file mode 100644
index 0000000000..83db774b8e
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_pearl.mtlx
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_soapbubble.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_soapbubble.mtlx
new file mode 100644
index 0000000000..af0faccb2a
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_soapbubble.mtlx
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/OpenPbr/open_pbr_velvet.mtlx b/resources/Materials/Examples/OpenPbr/open_pbr_velvet.mtlx
new file mode 100644
index 0000000000..6fbc907f3d
--- /dev/null
+++ b/resources/Materials/Examples/OpenPbr/open_pbr_velvet.mtlx
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/TestSuite/_options.mtlx b/resources/Materials/TestSuite/_options.mtlx
index 99db2a9f50..e12a5b564b 100644
--- a/resources/Materials/TestSuite/_options.mtlx
+++ b/resources/Materials/TestSuite/_options.mtlx
@@ -74,7 +74,7 @@
-
+
diff --git a/source/MaterialXTest/CMakeLists.txt b/source/MaterialXTest/CMakeLists.txt
index 862a176b50..9d2bd578dd 100644
--- a/source/MaterialXTest/CMakeLists.txt
+++ b/source/MaterialXTest/CMakeLists.txt
@@ -98,6 +98,9 @@ if(MATERIALX_OSL_LEGACY_CLOSURES)
add_custom_command(TARGET MaterialXTest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rename
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libraries/pbrlib/genosl/pbrlib_genosl_impl.legacy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libraries/pbrlib/genosl/pbrlib_genosl_impl.mtlx)
+ add_custom_command(TARGET MaterialXTest POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E rename
+ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.legacy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libraries/pbrlib/genosl/mx39_pbrlib_genosl_impl.mtlx)
endif()
if(MATERIALX_BUILD_GEN_MDL)
diff --git a/source/MaterialXTest/MaterialXGenGlsl/GenGlsl.cpp b/source/MaterialXTest/MaterialXGenGlsl/GenGlsl.cpp
index 2cc4d7ccf4..e2857f1eb2 100644
--- a/source/MaterialXTest/MaterialXGenGlsl/GenGlsl.cpp
+++ b/source/MaterialXTest/MaterialXGenGlsl/GenGlsl.cpp
@@ -158,6 +158,7 @@ static void generateGlslCode(GlslType type = GlslType::Glsl400)
mx::FilePathVec testRootPaths;
testRootPaths.push_back(searchPath.find("resources/Materials/TestSuite"));
testRootPaths.push_back(searchPath.find("resources/Materials/Examples/StandardSurface"));
+ testRootPaths.push_back(searchPath.find("resources/Materials/Examples/OpenPbr"));
const mx::FilePath logPath("genglsl_" + GlslTypeToString(type) + "_generate_test.txt");
diff --git a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp
index 8da7e22fe5..2cd48dd4c4 100644
--- a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp
+++ b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp
@@ -357,6 +357,7 @@ TEST_CASE("GenShader: MDL Shader Generation", "[genmdl]")
mx::FilePathVec testRootPaths;
testRootPaths.push_back(searchPath.find("resources/Materials/TestSuite"));
testRootPaths.push_back(searchPath.find("resources/Materials/Examples/StandardSurface"));
+ testRootPaths.push_back(searchPath.find("resources/Materials/Examples/OpenPbr"));
const mx::FilePath logPath("genmdl_mdl_generate_test.txt");
diff --git a/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp
index 03dbcbde69..62c108deca 100644
--- a/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp
+++ b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp
@@ -124,6 +124,7 @@ static void generateMslCode()
mx::FilePathVec testRootPaths;
testRootPaths.push_back(searchPath.find("resources/Materials/TestSuite"));
testRootPaths.push_back(searchPath.find("resources/Materials/Examples/StandardSurface"));
+ testRootPaths.push_back(searchPath.find("resources/Materials/Examples/OpenPbr"));
const mx::FilePath logPath("genmsl_msl23_layout_generate_test.txt");
diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp
index 13a8b5ffdf..28156a41fe 100644
--- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp
+++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp
@@ -183,6 +183,7 @@ static void generateOslCode()
mx::FilePathVec testRootPaths;
testRootPaths.push_back(searchPath.find("resources/Materials/TestSuite"));
testRootPaths.push_back(searchPath.find("resources/Materials/Examples/StandardSurface"));
+ testRootPaths.push_back(searchPath.find("resources/Materials/Examples/OpenPbr"));
const mx::FilePath logPath("genosl_vanilla_generate_test.txt");
diff --git a/source/MaterialXTest/MaterialXGenShader/GenShaderUtil.cpp b/source/MaterialXTest/MaterialXGenShader/GenShaderUtil.cpp
index 5aa1d7aa2d..3ed23848e6 100644
--- a/source/MaterialXTest/MaterialXGenShader/GenShaderUtil.cpp
+++ b/source/MaterialXTest/MaterialXGenShader/GenShaderUtil.cpp
@@ -355,6 +355,7 @@ void shaderGenPerformanceTest(mx::GenContext& context)
// Read mtlx documents
mx::FilePathVec testRootPaths;
testRootPaths.push_back("resources/Materials/Examples/StandardSurface");
+ testRootPaths.push_back("resources/Materials/Examples/OpenPbr");
std::vector loadedDocuments;
mx::StringVec documentsPaths;
diff --git a/source/MaterialXView/Main.cpp b/source/MaterialXView/Main.cpp
index 904466c065..b2a9403f0f 100644
--- a/source/MaterialXView/Main.cpp
+++ b/source/MaterialXView/Main.cpp
@@ -72,7 +72,7 @@ int main(int argc, char* const argv[])
tokens.emplace_back(argv[i]);
}
- std::string materialFilename = "resources/Materials/Examples/StandardSurface/standard_surface_default.mtlx";
+ std::string materialFilename = "resources/Materials/Examples/OpenPbr/open_pbr_default.mtlx";
std::string meshFilename = "resources/Geometry/shaderball.glb";
std::string envRadianceFilename = "resources/Lights/san_giuseppe_bridge_split.hdr";
mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath();