diff --git a/cmake/macros/testWrapper.py b/cmake/macros/testWrapper.py index 9e4104028d..8e0481bbf9 100644 --- a/cmake/macros/testWrapper.py +++ b/cmake/macros/testWrapper.py @@ -157,11 +157,21 @@ def _diff(fileName, baselineDir, verbose, failuresDir=None): # Use the diff program or equivalent, rather than filecmp or similar # because it's possible we might want to specify other diff programs # in the future. + import platform - if platform.system() == 'Windows': - diff = 'fc.exe' - else: - diff = '/usr/bin/diff' + isWindows = platform.system() == 'Windows' + + diffTool = shutil.which('diff') + diffToolBaseArgs = ['--strip-trailing-cr'] + if not diffTool and isWindows: + diffTool = shutil.which('fc.exe') + diffToolBaseArgs = ['/t'] + + if not diffTool: + sys.stderr.write( + "Error: could not find \"diff\" or \"fc.exe\" tool. " + "Make sure it's in your PATH.\n") + return False filesToDiff = glob.glob(fileName) if not filesToDiff: @@ -171,7 +181,7 @@ def _diff(fileName, baselineDir, verbose, failuresDir=None): for fileToDiff in filesToDiff: baselineFile = _resolvePath(baselineDir, fileToDiff) - cmd = [diff, baselineFile, fileToDiff] + cmd = [diffTool, *diffToolBaseArgs, baselineFile, fileToDiff] if verbose: print("diffing with {0}".format(cmd)) diff --git a/pxr/imaging/hdSt/CMakeLists.txt b/pxr/imaging/hdSt/CMakeLists.txt index 407f022a3e..7b47723de0 100644 --- a/pxr/imaging/hdSt/CMakeLists.txt +++ b/pxr/imaging/hdSt/CMakeLists.txt @@ -2325,7 +2325,7 @@ pxr_register_test(testHdStBufferArray COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStBufferArray" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT testHdStBufferArray-Run1-stdout.txt - POST_COMMAND "diff -I drawingShader -I computeShader -I gpuMemoryUsed -I meshTopology -I basisCurvesTopology -I uboSize -I ssboSize testHdStBufferArray-Run1-stdout.txt ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStBufferArray/baseline/testHdStBufferArray-Run1-stdout.txt" TESTENV testHdStBufferArray + DIFF_COMPARE testHdStBufferArray-Run1-stdout.txt TESTENV testHdStBufferArray ENV TF_DEBUG=HD_SAFE_MODE @@ -2336,7 +2336,7 @@ pxr_register_test(testHdStBufferArrayInstancingDisabled COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStBufferArray" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT testHdStBufferArrayInstancingDisabled-Run1-stdout.txt - POST_COMMAND "diff -I drawingShader -I computeShader -I gpuMemoryUsed -I meshTopology -I basisCurvesTopology -I uboSize -I ssboSize testHdStBufferArrayInstancingDisabled-Run1-stdout.txt ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStBufferArrayInstancingDisabled/baseline/testHdStBufferArrayInstancingDisabled-Run1-stdout.txt" + DIFF_COMPARE testHdStBufferArrayInstancingDisabled-Run1-stdout.txt TESTENV testHdStBufferArrayInstancingDisabled ENV HDST_ENABLE_RESOURCE_INSTANCING=0 @@ -2378,90 +2378,90 @@ pxr_register_test(testHdStClipPlanes2 function(register_codegen_tests suffix hgi_env) pxr_register_test(testHdStCodeGen${suffix}_Mesh_Indirect - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_indirect.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_indirect.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_indirect.out" + DIFF_COMPARE codegen_mesh_indirect.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_Indirect_SmoothNormals - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --smoothNormals | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --smoothNormals" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_indirect_smoothNormals.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_indirect_smoothNormals.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_indirect_smoothNormals.out" + DIFF_COMPARE codegen_mesh_indirect_smoothNormals.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_Indirect_DoubleSided - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --doubleSided | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --doubleSided" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_indirect_doubleSided.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_indirect_doubleSided.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_indirect_doubleSided.out" + DIFF_COMPARE codegen_mesh_indirect_doubleSided.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_Indirect_FaceVarying - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --faceVarying | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --faceVarying" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_indirect_faceVarying.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_indirect_faceVarying.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_indirect_faceVarying.out" + DIFF_COMPARE codegen_mesh_indirect_faceVarying.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_Indirect_Instance - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --instance | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --instance" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_indirect_instance.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_indirect_instance.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_indirect_instance.out" + DIFF_COMPARE codegen_mesh_indirect_instance.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_EdgeOnly - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --edgeOnly | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --edgeOnly" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_edgeonly.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_edgeonly.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_edgeonly.out" + DIFF_COMPARE codegen_mesh_edgeonly.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Mesh_EdgeOnly_BlendWireframe - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --edgeOnly --blendWireframe | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --mesh --edgeOnly --blendWireframe" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_mesh_edgeonly_blendwireframe.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_mesh_edgeonly_blendwireframe.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_mesh_edgeonly_blendwireframe.out" + DIFF_COMPARE codegen_mesh_edgeonly_blendwireframe.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Curves_Indirect - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --curves | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --curves" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_curves_indirect.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_curves_indirect.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_curves_indirect.out" + DIFF_COMPARE codegen_curves_indirect.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE ${hgi_env} ) pxr_register_test(testHdStCodeGen${suffix}_Points_Indirect - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --points | grep -v '// line'" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStCodeGen --points" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT codegen_points_indirect.out - POST_COMMAND "diff -I '\".*\.glslfx\"' codegen_points_indirect.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStCodeGen${suffix}/baseline/codegen_points_indirect.out" + DIFF_COMPARE codegen_points_indirect.out TESTENV testHdStCodeGen${suffix} ENV TF_DEBUG=HD_SAFE_MODE @@ -2571,7 +2571,7 @@ pxr_register_test(testHdStDrawBatching COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStDrawBatching" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT testHdStDrawBatching-Run1-stdout.txt - POST_COMMAND "diff -I computeShader -I drawingShader -I gpuMemoryUsed testHdStDrawBatching-Run1-stdout.txt ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStDrawBatching/baseline/testHdStDrawBatching-Run1-stdout.txt" + DIFF_COMPARE testHdStDrawBatching-Run1-stdout.txt TESTENV testHdStDrawBatching ENV TF_DEBUG=HD_SAFE_MODE @@ -2736,7 +2736,7 @@ pxr_register_test(testHdStHWFaceCulling COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStHWFaceCulling" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT testHdStHWFaceCulling-Run1-stdout.txt - POST_COMMAND "diff -I computeShader -I drawingShader -I gpuMemoryUsed -I uboSize -I ssboSize testHdStHWFaceCulling-Run1-stdout.txt ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStHWFaceCulling/baseline/testHdStHWFaceCulling-Run1-stdout.txt" + DIFF_COMPARE testHdStHWFaceCulling-Run1-stdout.txt TESTENV testHdStHWFaceCulling ) pxr_register_test(testHdStIndirectDrawBatchCodeGen @@ -2880,65 +2880,65 @@ pxr_register_test(testHdStInstancingUnbalancedNoBindless if (${PXR_ENABLE_MATERIALX_SUPPORT}) pxr_register_test(testHdStMaterialXShaderGen_SSdefault COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename standard_surface_default.mtlx" - STDOUT_REDIRECT shadergen_SSdefault.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_SSdefault.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_SSdefault.out" + STDOUT_REDIRECT shadergen_SSdefault.out + DIFF_COMPARE shadergen_SSdefault.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_UsdPSdefault COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename usd_preview_surface_default.mtlx" - STDOUT_REDIRECT shadergen_UsdPSdefault.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_UsdPSdefault.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out" + STDOUT_REDIRECT shadergen_UsdPSdefault.out + DIFF_COMPARE shadergen_UsdPSdefault.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_SSmarble COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename standard_surface_marble_solid.mtlx" - STDOUT_REDIRECT shadergen_SSmarble.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_SSmarble.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_SSmarble.out" + STDOUT_REDIRECT shadergen_SSmarble.out + DIFF_COMPARE shadergen_SSmarble.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_SScopper COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename standard_surface_copper.mtlx" - STDOUT_REDIRECT shadergen_SScopper.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_SScopper.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_SScopper.out" + STDOUT_REDIRECT shadergen_SScopper.out + DIFF_COMPARE shadergen_SScopper.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_UsdPStextured COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename usd_preview_surface_brass_tiled.mtlx --textureMap image_color_file:diffuseColor --textureMap image_roughness_file:roughness" - STDOUT_REDIRECT shadergen_UsdPStextured.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_UsdPStextured.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out" + STDOUT_REDIRECT shadergen_UsdPStextured.out + DIFF_COMPARE shadergen_UsdPStextured.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_SStextured COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename standard_surface_brass_tiled.mtlx --textureMap image_color_file:diffuseColor --textureMap image_roughness_file:roughness --primvarMap uv:vector2" - STDOUT_REDIRECT shadergen_SStextured.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_SStextured.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_SStextured.out" + STDOUT_REDIRECT shadergen_SStextured.out + DIFF_COMPARE shadergen_SStextured.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_UsdPSglass COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename usd_preview_surface_glass.mtlx" - STDOUT_REDIRECT shadergen_UsdPSglass.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_UsdPSglass.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out" + STDOUT_REDIRECT shadergen_UsdPSglass.out + DIFF_COMPARE shadergen_UsdPSglass.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen8_SStextured_bindless COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename standard_surface_brass_tiled.mtlx --textureMap image_color_file:diffuseColor --textureMap image_roughness_file:roughness --primvarMap uv:vector2 --bindless" - STDOUT_REDIRECT shadergen_SStextured_bindless.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_SStextured_bindless.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_SStextured_bindless.out" + STDOUT_REDIRECT shadergen_SStextured_bindless.out + DIFF_COMPARE shadergen_SStextured_bindless.out TESTENV testHdStMaterialXShaderGen ) pxr_register_test(testHdStMaterialXShaderGen_texcoord COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStMaterialXShaderGen --filename texcoord.mtlx --primvarMap st:vector2" - STDOUT_REDIRECT shadergen_texcoord.out EXPECTED_RETURN_CODE 0 - POST_COMMAND "diff -B -b shadergen_texcoord.out ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out" + STDOUT_REDIRECT shadergen_texcoord.out + DIFF_COMPARE shadergen_texcoord.out TESTENV testHdStMaterialXShaderGen ) endif() @@ -2996,7 +2996,7 @@ pxr_register_test(testHdStPrimvars COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testHdStPrimvars" EXPECTED_RETURN_CODE 0 STDOUT_REDIRECT testHdStPrimvars-Run1-stdout.txt - POST_COMMAND "diff -I computeShader -I drawingShader -I gpuMemoryUsed -I uboSize -I ssboSize testHdStPrimvars-Run1-stdout.txt ${CMAKE_INSTALL_PREFIX}/tests/ctest/testHdStPrimvars/baseline/testHdStPrimvars-Run1-stdout.txt" + DIFF_COMPARE testHdStPrimvars-Run1-stdout.txt TESTENV testHdStPrimvars ENV HDST_ENABLE_MATERIAL_PRIMVAR_FILTERING=0 @@ -3362,4 +3362,4 @@ pxr_register_test(testHdStShaders ENV TF_DEBUG=HD_SAFE_MODE ) -endif() # TARGET shared_libs \ No newline at end of file +endif() # TARGET shared_libs diff --git a/pxr/imaging/hdSt/testenv/testHdStBufferArray.cpp b/pxr/imaging/hdSt/testenv/testHdStBufferArray.cpp index 91eb5bbae5..6b3813d91f 100644 --- a/pxr/imaging/hdSt/testenv/testHdStBufferArray.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStBufferArray.cpp @@ -40,15 +40,26 @@ PrintPerfCounter(HdPerfLog &perfLog, TfToken const &token) static void Dump(std::string const &message, VtDictionary dict, HdPerfLog &perfLog) { - // Get the keys in sorted order. This ensures consistent reporting + // These vary between platforms and runs, we don't want them in the diff. + static const std::unordered_set skippedKeys = { + HdTokens->drawingShader, + HdTokens->computeShader, + HdPerfTokens->gpuMemoryUsed, + HdPerfTokens->uboSize, + HdPerfTokens->ssboSize, + }; + + // Get the keys in sorted order. This ensures consistent reporting // regardless of the sort order of dict. std::set keys; - for (auto v: dict) { - keys.insert(v.first); + for (const auto& [key, _] : dict) { + if (!skippedKeys.count(key)) { + keys.insert(key); + } } std::cout << message; - for (auto key: keys) { + for (const auto& key: keys) { std::cout << key << ", "; const VtValue& value = dict[key]; if (value.IsHolding()) { @@ -57,8 +68,6 @@ Dump(std::string const &message, VtDictionary dict, HdPerfLog &perfLog) std::cout << "\n"; } PrintPerfCounter(perfLog, HdPerfTokens->garbageCollected); - PrintPerfCounter(perfLog, HdPerfTokens->meshTopology); - PrintPerfCounter(perfLog, HdPerfTokens->basisCurvesTopology); PrintPerfCounter(perfLog, HdPerfTokens->instMeshTopology); PrintPerfCounter(perfLog, HdPerfTokens->instBasisCurvesTopology); PrintPerfCounter(perfLog, HdPerfTokens->instVertexAdjacency); @@ -345,4 +354,3 @@ int main() return EXIT_FAILURE; } } - diff --git a/pxr/imaging/hdSt/testenv/testHdStBufferArray/baseline/testHdStBufferArray-Run1-stdout.txt b/pxr/imaging/hdSt/testenv/testHdStBufferArray/baseline/testHdStBufferArray-Run1-stdout.txt index 3b89c10e57..314b08dd70 100644 --- a/pxr/imaging/hdSt/testenv/testHdStBufferArray/baseline/testHdStBufferArray-Run1-stdout.txt +++ b/pxr/imaging/hdSt/testenv/testHdStBufferArray/baseline/testHdStBufferArray-Run1-stdout.txt @@ -1,61 +1,42 @@ ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 instMeshTopologyRange = 0 instBasisCurvesTopologyRange = 0 ----- allocated ----- -computeShader, 11231 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 69937 -gpuMemoryUsed, 926320 nonUniformSize, 55136 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 16592 singleBufferSize, 0 -ssboSize, 2624 textureMemory, 786432 topology, 41168 -uboSize, 768 garbageCollected = 0 -meshTopology = 4 -basisCurvesTopology = 1 instMeshTopology = 4 instBasisCurvesTopology = 1 instVertexAdjacency = 4 instMeshTopologyRange = 4 instBasisCurvesTopologyRange = 1 ----- delete a prim ----- -computeShader, 11231 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 69937 -gpuMemoryUsed, 926320 nonUniformSize, 55136 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 16592 singleBufferSize, 0 -ssboSize, 2624 textureMemory, 786432 topology, 41168 -uboSize, 768 garbageCollected = 0 -meshTopology = 4 -basisCurvesTopology = 1 instMeshTopology = 4 instBasisCurvesTopology = 1 instVertexAdjacency = 4 @@ -64,20 +45,14 @@ instBasisCurvesTopologyRange = 1 ----- garbage collected ----- drawIndirect, 900 drawIndirectCull, 900 -drawingShader, 69937 -gpuMemoryUsed, 915609 nonUniformSize, 55136 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 16272 singleBufferSize, 0 -ssboSize, 2304 textureMemory, 786432 topology, 41168 -uboSize, 768 garbageCollected = 1 -meshTopology = 4 -basisCurvesTopology = 1 instMeshTopology = 4 instBasisCurvesTopology = 1 instVertexAdjacency = 4 @@ -86,20 +61,14 @@ instBasisCurvesTopologyRange = 1 ----- delete more prims ----- drawIndirect, 660 drawIndirectCull, 660 -drawingShader, 69937 -gpuMemoryUsed, 913353 nonUniformSize, 54352 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 14992 singleBufferSize, 0 -ssboSize, 1312 textureMemory, 786432 topology, 40672 -uboSize, 768 garbageCollected = 2 -meshTopology = 3 -basisCurvesTopology = 1 instMeshTopology = 3 instBasisCurvesTopology = 1 instVertexAdjacency = 3 @@ -108,18 +77,12 @@ instBasisCurvesTopologyRange = 1 ----- clear all ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856849 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 2 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 3 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -128,19 +91,13 @@ instBasisCurvesTopologyRange = 0 ----- 3 points ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856885 nonUniformSize, 36 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 36 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -149,19 +106,13 @@ instBasisCurvesTopologyRange = 0 ----- 5 points ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856909 nonUniformSize, 60 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 60 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -170,19 +121,13 @@ instBasisCurvesTopologyRange = 0 ----- 4 points before GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856897 nonUniformSize, 48 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 48 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -191,19 +136,13 @@ instBasisCurvesTopologyRange = 0 ----- 4 points after GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856897 nonUniformSize, 48 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 48 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 1 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -212,19 +151,13 @@ instBasisCurvesTopologyRange = 0 ----- 0 points after GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856849 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 1 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 diff --git a/pxr/imaging/hdSt/testenv/testHdStBufferArrayInstancingDisabled/baseline/testHdStBufferArrayInstancingDisabled-Run1-stdout.txt b/pxr/imaging/hdSt/testenv/testHdStBufferArrayInstancingDisabled/baseline/testHdStBufferArrayInstancingDisabled-Run1-stdout.txt index 378e49c30d..9d71563844 100644 --- a/pxr/imaging/hdSt/testenv/testHdStBufferArrayInstancingDisabled/baseline/testHdStBufferArrayInstancingDisabled-Run1-stdout.txt +++ b/pxr/imaging/hdSt/testenv/testHdStBufferArrayInstancingDisabled/baseline/testHdStBufferArrayInstancingDisabled-Run1-stdout.txt @@ -1,61 +1,42 @@ ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 instMeshTopologyRange = 0 instBasisCurvesTopologyRange = 0 ----- allocated ----- -computeShader, 11231 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 69937 -gpuMemoryUsed, 938152 nonUniformSize, 66968 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 19688 singleBufferSize, 0 -ssboSize, 2624 textureMemory, 786432 topology, 49904 -uboSize, 768 garbageCollected = 0 -meshTopology = 6 -basisCurvesTopology = 2 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 instMeshTopologyRange = 0 instBasisCurvesTopologyRange = 0 ----- delete a prim ----- -computeShader, 11231 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 69937 -gpuMemoryUsed, 938152 nonUniformSize, 66968 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 19688 singleBufferSize, 0 -ssboSize, 2624 textureMemory, 786432 topology, 49904 -uboSize, 768 garbageCollected = 0 -meshTopology = 5 -basisCurvesTopology = 2 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -64,20 +45,14 @@ instBasisCurvesTopologyRange = 0 ----- garbage collected ----- drawIndirect, 900 drawIndirectCull, 900 -drawingShader, 69937 -gpuMemoryUsed, 926753 nonUniformSize, 66280 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 19176 singleBufferSize, 0 -ssboSize, 2304 textureMemory, 786432 topology, 49408 -uboSize, 768 garbageCollected = 1 -meshTopology = 5 -basisCurvesTopology = 2 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -86,20 +61,14 @@ instBasisCurvesTopologyRange = 0 ----- delete more prims ----- drawIndirect, 660 drawIndirectCull, 660 -drawingShader, 69937 -gpuMemoryUsed, 913353 nonUniformSize, 54352 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 14992 singleBufferSize, 0 -ssboSize, 1312 textureMemory, 786432 topology, 40672 -uboSize, 768 garbageCollected = 2 -meshTopology = 3 -basisCurvesTopology = 1 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -108,18 +77,12 @@ instBasisCurvesTopologyRange = 0 ----- clear all ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856849 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 2 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 3 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -128,19 +91,13 @@ instBasisCurvesTopologyRange = 0 ----- 3 points ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856885 nonUniformSize, 36 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 36 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -149,19 +106,13 @@ instBasisCurvesTopologyRange = 0 ----- 5 points ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856909 nonUniformSize, 60 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 60 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -170,19 +121,13 @@ instBasisCurvesTopologyRange = 0 ----- 4 points before GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856897 nonUniformSize, 48 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 48 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 0 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -191,19 +136,13 @@ instBasisCurvesTopologyRange = 0 ----- 4 points after GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856897 nonUniformSize, 48 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 48 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 1 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 @@ -212,19 +151,13 @@ instBasisCurvesTopologyRange = 0 ----- 0 points after GC ----- drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 69937 -gpuMemoryUsed, 856849 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 786432 -uboSize, 768 garbageCollected = 1 -meshTopology = 0 -basisCurvesTopology = 0 instMeshTopology = 0 instBasisCurvesTopology = 0 instVertexAdjacency = 0 diff --git a/pxr/imaging/hdSt/testenv/testHdStCodeGen.cpp b/pxr/imaging/hdSt/testenv/testHdStCodeGen.cpp index c44c5867fb..4663b2a442 100644 --- a/pxr/imaging/hdSt/testenv/testHdStCodeGen.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStCodeGen.cpp @@ -64,6 +64,18 @@ TF_DEFINE_PRIVATE_TOKENS( (dmat4) ); +static void +DumpShaderSource(const std::string& source) +{ + std::istringstream stream{source}; + for (std::string line; std::getline(stream, line); ) { + // Remove glslfx source line comments + if (!TfStringStartsWith(line, "// line ")) { + std::cout << line << '\n'; + } + } +} + static bool CodeGenTest(HdStResourceRegistrySharedPtr const ®istry, HdSt_ShaderKey const &key, bool instance, bool smoothNormals) @@ -275,28 +287,28 @@ CodeGenTest(HdStResourceRegistrySharedPtr const ®istry, std::cout << "=======================================================\n" " VERTEX SHADER \n" - "=======================================================\n" - << codeGen.GetVertexShaderSource(); + "=======================================================\n"; + DumpShaderSource(codeGen.GetVertexShaderSource()); std::cout << "=======================================================\n" " TESS CONTROL SHADER \n" - "=======================================================\n" - << codeGen.GetTessControlShaderSource(); + "=======================================================\n"; + DumpShaderSource(codeGen.GetTessControlShaderSource()); std::cout << "=======================================================\n" " TESS EVAL SHADER \n" - "=======================================================\n" - << codeGen.GetTessEvalShaderSource(); + "=======================================================\n"; + DumpShaderSource(codeGen.GetTessEvalShaderSource()); std::cout << "=======================================================\n" " GEOMETRY SHADER \n" - "=======================================================\n" - << codeGen.GetGeometryShaderSource(); + "=======================================================\n"; + DumpShaderSource(codeGen.GetGeometryShaderSource()); std::cout << "=======================================================\n" " FRAGMENT SHADER \n" - "=======================================================\n" - << codeGen.GetFragmentShaderSource(); + "=======================================================\n"; + DumpShaderSource(codeGen.GetFragmentShaderSource()); return TF_VERIFY(mark.IsClean()); } diff --git a/pxr/imaging/hdSt/testenv/testHdStDrawBatching.cpp b/pxr/imaging/hdSt/testenv/testHdStDrawBatching.cpp index 7f5e9ef24a..ad47772ade 100644 --- a/pxr/imaging/hdSt/testenv/testHdStDrawBatching.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStDrawBatching.cpp @@ -400,15 +400,26 @@ PrintPerfCounter(HdPerfLog &perfLog, TfToken const &token) static void Dump(std::string const &message, VtDictionary dict, HdPerfLog &perfLog) { + // These vary between platforms and runs, we don't want them in the diff. + static const std::unordered_set skippedKeys = { + HdTokens->drawingShader, + HdTokens->computeShader, + HdPerfTokens->gpuMemoryUsed, + HdPerfTokens->uboSize, + HdPerfTokens->ssboSize, + }; + // Get the keys in sorted order. This ensures consistent reporting // regardless of the sort order of dict. std::set keys; - for (auto v: dict) { - keys.insert(v.first); + for (const auto& [key, _] : dict) { + if (!skippedKeys.count(key)) { + keys.insert(key); + } } std::cout << message; - for (auto key: keys) { + for (const auto& key: keys) { std::cout << key << ", "; const VtValue& value = dict[key]; if (value.IsHolding()) { @@ -778,4 +789,3 @@ int main() return EXIT_FAILURE; } } - diff --git a/pxr/imaging/hdSt/testenv/testHdStDrawBatching/baseline/testHdStDrawBatching-Run1-stdout.txt b/pxr/imaging/hdSt/testenv/testHdStDrawBatching/baseline/testHdStDrawBatching-Run1-stdout.txt index 41ea48baa3..a11218c4ad 100644 --- a/pxr/imaging/hdSt/testenv/testHdStDrawBatching/baseline/testHdStDrawBatching-Run1-stdout.txt +++ b/pxr/imaging/hdSt/testenv/testHdStDrawBatching/baseline/testHdStDrawBatching-Run1-stdout.txt @@ -1,88 +1,67 @@ ==== IndirectDrawBatchTest: ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 num batches: 5 ----- batched ----- -gpuMemoryUsed, 4712 nonUniformSize, 1512 numberOfTextureHandles, 0 numberOfTextureObjects, 0 primvar, 4240 singleBufferSize, 0 -ssboSize, 3200 textureMemory, 0 topology, 472 -uboSize, 0 garbageCollected = 0 drawCalls = 0 ----- executed ----- drawIndirect, 600 drawIndirectCull, 600 drawIndirectResult, 20 -drawingShader, 114898 -gpuMemoryUsed, 120830 nonUniformSize, 1512 numberOfTextureHandles, 0 numberOfTextureObjects, 0 primvar, 4240 singleBufferSize, 0 -ssboSize, 3200 textureMemory, 0 topology, 472 -uboSize, 0 garbageCollected = 0 drawCalls = 5 ----- clear all ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 1 drawCalls = 5 ==== IndirectDrawBatchMigrationTest: ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 rebuildBatches = 0 bufferArrayRangeMigrated = 0 ----- draw flat ----- -computeShader, 14308 drawIndirect, 240 drawIndirectCull, 240 drawIndirectResult, 4 -drawingShader, 42470 -gpuMemoryUsed, 845934 nonUniformSize, 960 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1760 singleBufferSize, 0 -ssboSize, 1280 textureMemory, 786432 topology, 480 -uboSize, 768 garbageCollected = 0 drawCalls = 1 drawBatches = 1 @@ -94,17 +73,13 @@ bufferArrayRangeMigrated = 0 drawIndirect, 480 drawIndirectCull, 480 drawIndirectResult, 12 -drawingShader, 105357 -gpuMemoryUsed, 895353 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1856 singleBufferSize, 0 -ssboSize, 1280 textureMemory, 786432 topology, 736 -uboSize, 768 garbageCollected = 1 drawCalls = 2 drawBatches = 2 @@ -116,17 +91,13 @@ bufferArrayRangeMigrated = 1 drawIndirect, 720 drawIndirectCull, 720 drawIndirectResult, 20 -drawingShader, 133057 -gpuMemoryUsed, 923541 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1856 singleBufferSize, 0 -ssboSize, 1280 textureMemory, 786432 topology, 736 -uboSize, 768 garbageCollected = 1 drawCalls = 2 drawBatches = 2 @@ -138,17 +109,13 @@ bufferArrayRangeMigrated = 1 drawIndirect, 480 drawIndirectCull, 480 drawIndirectResult, 20 -drawingShader, 133057 -gpuMemoryUsed, 923061 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1856 singleBufferSize, 0 -ssboSize, 1280 textureMemory, 786432 topology, 736 -uboSize, 768 garbageCollected = 1 drawCalls = 2 drawBatches = 2 @@ -158,61 +125,47 @@ rebuildBatches = 3 bufferArrayRangeMigrated = 1 ==== EmptyDrawBatchTest: ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 ----- batched ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 primvar, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 topology, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 ----- executed ----- drawIndirect, 72 drawIndirectCull, 72 drawIndirectResult, 4 -drawingShader, 28423 -gpuMemoryUsed, 28907 nonUniformSize, 16 numberOfTextureHandles, 0 numberOfTextureObjects, 0 primvar, 336 singleBufferSize, 0 -ssboSize, 320 textureMemory, 0 topology, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 1 ----- clear all ----- drawIndirect, 72 drawIndirectCull, 72 drawIndirectResult, 4 -drawingShader, 28423 -gpuMemoryUsed, 28907 nonUniformSize, 16 numberOfTextureHandles, 0 numberOfTextureObjects, 0 primvar, 336 singleBufferSize, 0 -ssboSize, 320 textureMemory, 0 topology, 0 -uboSize, 0 garbageCollected = 1 drawCalls = 1 diff --git a/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling.cpp b/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling.cpp index 6beef1b058..4e52a310ae 100644 --- a/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling.cpp @@ -46,15 +46,26 @@ PrintPerfCounter(HdPerfLog &perfLog, TfToken const &token) static void Dump(std::string const &message, VtDictionary dict, HdPerfLog &perfLog) { - // Get the keys in sorted order. This ensures consistent reporting + // These vary between platforms and runs, we don't want them in the diff. + static const std::unordered_set skippedKeys = { + HdTokens->drawingShader, + HdTokens->computeShader, + HdPerfTokens->gpuMemoryUsed, + HdPerfTokens->uboSize, + HdPerfTokens->ssboSize, + }; + + // Get the keys in sorted order. This ensures consistent reporting // regardless of the sort order of dict. std::set keys; - for (auto v: dict) { - keys.insert(v.first); + for (const auto& [key, _] : dict) { + if (!skippedKeys.count(key)) { + keys.insert(key); + } } std::cout << message; - for (auto key: keys) { + for (const auto& key: keys) { std::cout << key << ", "; const VtValue& value = dict[key]; if (value.IsHolding()) { @@ -196,4 +207,3 @@ int main() return EXIT_FAILURE; } } - diff --git a/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling/baseline/testHdStHWFaceCulling-Run1-stdout.txt b/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling/baseline/testHdStHWFaceCulling-Run1-stdout.txt index 55c7c66c04..c81210feaa 100644 --- a/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling/baseline/testHdStHWFaceCulling-Run1-stdout.txt +++ b/pxr/imaging/hdSt/testenv/testHdStHWFaceCulling/baseline/testHdStHWFaceCulling-Run1-stdout.txt @@ -1,32 +1,24 @@ ==== FaceCullingTest: ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 rebuildBatches = 0 ----- draw initial state ----- -computeShader, 11474 drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 37048 -gpuMemoryUsed, 836826 nonUniformSize, 624 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 896 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 496 -uboSize, 512 garbageCollected = 0 drawCalls = 1 drawBatches = 1 @@ -35,20 +27,15 @@ drawItemsFetched = 1 rebuildBatches = 1 ----- add left handed transform to cube2 ----- -computeShader, 11474 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 37048 -gpuMemoryUsed, 837306 nonUniformSize, 624 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 896 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 496 -uboSize, 512 garbageCollected = 0 drawCalls = 2 drawBatches = 2 @@ -57,20 +44,15 @@ drawItemsFetched = 1 rebuildBatches = 2 ----- change cube3's repr to cull front ----- -computeShader, 26025 drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 77907 -gpuMemoryUsed, 892740 nonUniformSize, 648 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 920 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 496 -uboSize, 512 garbageCollected = 0 drawCalls = 3 drawBatches = 3 @@ -81,17 +63,13 @@ rebuildBatches = 3 ----- add instancer to cube4 ----- drawIndirect, 500 drawIndirectCull, 500 -drawingShader, 131963 -gpuMemoryUsed, 920875 nonUniformSize, 712 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 960 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 520 -uboSize, 512 garbageCollected = 1 drawCalls = 4 drawBatches = 4 @@ -102,17 +80,13 @@ rebuildBatches = 4 ----- add additional left-handed cube ----- drawIndirect, 580 drawIndirectCull, 580 -drawingShader, 186019 -gpuMemoryUsed, 975283 nonUniformSize, 712 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1152 singleBufferSize, 0 -ssboSize, 960 textureMemory, 786432 topology, 520 -uboSize, 512 garbageCollected = 1 drawCalls = 4 drawBatches = 4 diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SScopper.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SScopper.out index 293e97d25d..09d8d8b480 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SScopper.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SScopper.out @@ -22,22 +22,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -270,738 +270,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -1051,581 +1051,581 @@ void NG_srgb_texture_to_lin_rec709_color3(vec3 in1, out vec3 out1) out1 = mix_out; } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - - -// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf -// Equation 2 -float mx_imageworks_sheen_NDF(float NdotH, float roughness) -{ - float invRoughness = 1.0 / max(roughness, 0.005); - float cos2 = NdotH * NdotH; - float sin2 = 1.0 - cos2; - return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); -} - -float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) -{ - // Microfacet distribution. - float D = mx_imageworks_sheen_NDF(NdotH, roughness); - - // Fresnel and geometry terms are ignored. - float F = 1.0; - float G = 1.0; - - // We use a smoother denominator, as in: - // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf - return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); -} - -// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. -float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) -{ - vec2 r = vec2(13.67300, 1.0) + - vec2(-68.78018, 61.57746) * NdotV + - vec2(799.08825, 442.78211) * roughness + - vec2(-905.00061, 2597.49308) * NdotV * roughness + - vec2(60.28956, 121.81241) * mx_square(NdotV) + - vec2(1086.96473, 3045.55075) * mx_square(roughness); - return r.x / r.y; -} - -float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - return texture(u_albedoTable, vec2(NdotV, roughness)).b; - } -#endif - return 0.0; -} - -float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); - - float radiance = 0.0; - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the incoming light direction and half vector. - vec3 L = mx_uniform_sample_hemisphere(Xi); - vec3 H = normalize(L + V); - - // Compute dot products for this sample. - float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); - - // Compute sheen reflectance. - float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - - // Add the radiance contribution of this sample. - // uniform_pdf = 1 / (2 * PI) - // radiance = reflectance * NdotL / uniform_pdf; - radiance += reflectance * NdotL * 2.0 * M_PI; - } - - // Return the final directional albedo. - return radiance / float(SAMPLE_COUNT); -} - -float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); -#else - float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); -#endif - return clamp(dirAlbedo, 0.0, 1.0); -} - -void mx_sheen_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); - - 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); - - vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - // We need to include NdotL from the light integral here - // as in this case it's not cancelled out by the BRDF denominator. - bsdf.response = fr * NdotL * occlusion * weight; -} - -void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - vec3 Li = mx_environment_irradiance(N); - bsdf.response = Li * color * dirAlbedo * weight; -} - -void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) -{ - result = vec3(dot(_in, lumacoeffs)); -} - -mat4 mx_rotationMatrix(vec3 axis, float angle) -{ - axis = normalize(axis); - float s = sin(angle); - float c = cos(angle); - float oc = 1.0 - c; - - return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, - oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, - oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, - 0.0, 0.0, 0.0, 1.0); -} - -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - float rotationRadians = radians(amount); - mat4 m = mx_rotationMatrix(axis, rotationRadians); - result = (m * vec4(_in, 1.0)).xyz; -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} - -// We fake diffuse transmission by using diffuse reflection from the opposite side. -// So this BTDF is really a BRDF. -void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - // Invert normal since we're transmitting light from the other side - float NdotL = dot(L, -normal); - if (NdotL <= 0.0 || weight < M_FLOAT_EPS) - { - return; - } - - bsdf.response = color * weight * NdotL * M_PI_INV; -} - -void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - // Invert normal since we're transmitting light from the other side - vec3 Li = mx_environment_irradiance(-normal); - bsdf.response = Li * color * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); - bsdf.response = sss * visibleOcclusion * weight; -} - -void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - // For now, we render indirect subsurface as simple indirect diffuse. - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) -{ - N = mx_forward_facing_normal(N, V); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); - result = base * f; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + + +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +float mx_imageworks_sheen_NDF(float NdotH, float roughness) +{ + float invRoughness = 1.0 / max(roughness, 0.005); + float cos2 = NdotH * NdotH; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) +{ + // Microfacet distribution. + float D = mx_imageworks_sheen_NDF(NdotH, roughness); + + // Fresnel and geometry terms are ignored. + float F = 1.0; + float G = 1.0; + + // We use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); +} + +// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. +float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(13.67300, 1.0) + + vec2(-68.78018, 61.57746) * NdotV + + vec2(799.08825, 442.78211) * roughness + + vec2(-905.00061, 2597.49308) * NdotV * roughness + + vec2(60.28956, 121.81241) * mx_square(NdotV) + + vec2(1086.96473, 3045.55075) * mx_square(roughness); + return r.x / r.y; +} + +float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + return texture(u_albedoTable, vec2(NdotV, roughness)).b; + } +#endif + return 0.0; +} + +float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); + + float radiance = 0.0; + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the incoming light direction and half vector. + vec3 L = mx_uniform_sample_hemisphere(Xi); + vec3 H = normalize(L + V); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); + + // Compute sheen reflectance. + float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL * 2.0 * M_PI; + } + + // Return the final directional albedo. + return radiance / float(SAMPLE_COUNT); +} + +float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); +#else + float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +void mx_sheen_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); + + 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + // We need to include NdotL from the light integral here + // as in this case it's not cancelled out by the BRDF denominator. + bsdf.response = fr * NdotL * occlusion * weight; +} + +void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; +} + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + +mat4 mx_rotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + float rotationRadians = radians(amount); + mat4 m = mx_rotationMatrix(axis, rotationRadians); + result = (m * vec4(_in, 1.0)).xyz; +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} + +// We fake diffuse transmission by using diffuse reflection from the opposite side. +// So this BTDF is really a BRDF. +void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return; + } + + bsdf.response = color * weight * NdotL * M_PI_INV; +} + +void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + // Invert normal since we're transmitting light from the other side + vec3 Li = mx_environment_irradiance(-normal); + bsdf.response = Li * color * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; +} + +void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) +{ + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); + result = base * f; +} void NG_standard_surface_surfaceshader_100(float base, vec3 base_color, float diffuse_roughness, float metalness, float specular, vec3 specular_color, float specular_roughness, float specular_IOR, float specular_anisotropy, float specular_rotation, float transmission, vec3 transmission_color, float transmission_depth, vec3 transmission_scatter, float transmission_scatter_anisotropy, float transmission_dispersion, float transmission_extra_roughness, float subsurface, vec3 subsurface_color, vec3 subsurface_radius, float subsurface_scale, float subsurface_anisotropy, float sheen, vec3 sheen_color, float sheen_roughness, float coat, vec3 coat_color, float coat_roughness, float coat_anisotropy, float coat_rotation, float coat_IOR, vec3 coat_normal, float coat_affect_color, float coat_affect_roughness, float thin_film_thickness, float thin_film_IOR, float emission, vec3 emission_color, vec3 opacity, bool thin_walled, vec3 normal, vec3 tangent, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSdefault.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSdefault.out index 6e00379dd0..c736f1866c 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSdefault.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSdefault.out @@ -22,22 +22,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -270,738 +270,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -1022,581 +1022,581 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - - -// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf -// Equation 2 -float mx_imageworks_sheen_NDF(float NdotH, float roughness) -{ - float invRoughness = 1.0 / max(roughness, 0.005); - float cos2 = NdotH * NdotH; - float sin2 = 1.0 - cos2; - return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); -} - -float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) -{ - // Microfacet distribution. - float D = mx_imageworks_sheen_NDF(NdotH, roughness); - - // Fresnel and geometry terms are ignored. - float F = 1.0; - float G = 1.0; - - // We use a smoother denominator, as in: - // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf - return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); -} - -// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. -float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) -{ - vec2 r = vec2(13.67300, 1.0) + - vec2(-68.78018, 61.57746) * NdotV + - vec2(799.08825, 442.78211) * roughness + - vec2(-905.00061, 2597.49308) * NdotV * roughness + - vec2(60.28956, 121.81241) * mx_square(NdotV) + - vec2(1086.96473, 3045.55075) * mx_square(roughness); - return r.x / r.y; -} - -float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - return texture(u_albedoTable, vec2(NdotV, roughness)).b; - } -#endif - return 0.0; -} - -float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); - - float radiance = 0.0; - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the incoming light direction and half vector. - vec3 L = mx_uniform_sample_hemisphere(Xi); - vec3 H = normalize(L + V); - - // Compute dot products for this sample. - float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); - - // Compute sheen reflectance. - float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - - // Add the radiance contribution of this sample. - // uniform_pdf = 1 / (2 * PI) - // radiance = reflectance * NdotL / uniform_pdf; - radiance += reflectance * NdotL * 2.0 * M_PI; - } - - // Return the final directional albedo. - return radiance / float(SAMPLE_COUNT); -} - -float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); -#else - float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); -#endif - return clamp(dirAlbedo, 0.0, 1.0); -} - -void mx_sheen_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); - - 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); - - vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - // We need to include NdotL from the light integral here - // as in this case it's not cancelled out by the BRDF denominator. - bsdf.response = fr * NdotL * occlusion * weight; -} - -void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - vec3 Li = mx_environment_irradiance(N); - bsdf.response = Li * color * dirAlbedo * weight; -} - -void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) -{ - result = vec3(dot(_in, lumacoeffs)); -} - -mat4 mx_rotationMatrix(vec3 axis, float angle) -{ - axis = normalize(axis); - float s = sin(angle); - float c = cos(angle); - float oc = 1.0 - c; - - return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, - oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, - oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, - 0.0, 0.0, 0.0, 1.0); -} - -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - float rotationRadians = radians(amount); - mat4 m = mx_rotationMatrix(axis, rotationRadians); - result = (m * vec4(_in, 1.0)).xyz; -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} - -// We fake diffuse transmission by using diffuse reflection from the opposite side. -// So this BTDF is really a BRDF. -void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - // Invert normal since we're transmitting light from the other side - float NdotL = dot(L, -normal); - if (NdotL <= 0.0 || weight < M_FLOAT_EPS) - { - return; - } - - bsdf.response = color * weight * NdotL * M_PI_INV; -} - -void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - // Invert normal since we're transmitting light from the other side - vec3 Li = mx_environment_irradiance(-normal); - bsdf.response = Li * color * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); - bsdf.response = sss * visibleOcclusion * weight; -} - -void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - // For now, we render indirect subsurface as simple indirect diffuse. - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) -{ - N = mx_forward_facing_normal(N, V); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); - result = base * f; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + + +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +float mx_imageworks_sheen_NDF(float NdotH, float roughness) +{ + float invRoughness = 1.0 / max(roughness, 0.005); + float cos2 = NdotH * NdotH; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) +{ + // Microfacet distribution. + float D = mx_imageworks_sheen_NDF(NdotH, roughness); + + // Fresnel and geometry terms are ignored. + float F = 1.0; + float G = 1.0; + + // We use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); +} + +// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. +float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(13.67300, 1.0) + + vec2(-68.78018, 61.57746) * NdotV + + vec2(799.08825, 442.78211) * roughness + + vec2(-905.00061, 2597.49308) * NdotV * roughness + + vec2(60.28956, 121.81241) * mx_square(NdotV) + + vec2(1086.96473, 3045.55075) * mx_square(roughness); + return r.x / r.y; +} + +float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + return texture(u_albedoTable, vec2(NdotV, roughness)).b; + } +#endif + return 0.0; +} + +float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); + + float radiance = 0.0; + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the incoming light direction and half vector. + vec3 L = mx_uniform_sample_hemisphere(Xi); + vec3 H = normalize(L + V); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); + + // Compute sheen reflectance. + float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL * 2.0 * M_PI; + } + + // Return the final directional albedo. + return radiance / float(SAMPLE_COUNT); +} + +float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); +#else + float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +void mx_sheen_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); + + 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + // We need to include NdotL from the light integral here + // as in this case it's not cancelled out by the BRDF denominator. + bsdf.response = fr * NdotL * occlusion * weight; +} + +void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; +} + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + +mat4 mx_rotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + float rotationRadians = radians(amount); + mat4 m = mx_rotationMatrix(axis, rotationRadians); + result = (m * vec4(_in, 1.0)).xyz; +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} + +// We fake diffuse transmission by using diffuse reflection from the opposite side. +// So this BTDF is really a BRDF. +void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return; + } + + bsdf.response = color * weight * NdotL * M_PI_INV; +} + +void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + // Invert normal since we're transmitting light from the other side + vec3 Li = mx_environment_irradiance(-normal); + bsdf.response = Li * color * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; +} + +void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) +{ + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); + result = base * f; +} void NG_standard_surface_surfaceshader_100(float base, vec3 base_color, float diffuse_roughness, float metalness, float specular, vec3 specular_color, float specular_roughness, float specular_IOR, float specular_anisotropy, float specular_rotation, float transmission, vec3 transmission_color, float transmission_depth, vec3 transmission_scatter, float transmission_scatter_anisotropy, float transmission_dispersion, float transmission_extra_roughness, float subsurface, vec3 subsurface_color, vec3 subsurface_radius, float subsurface_scale, float subsurface_anisotropy, float sheen, vec3 sheen_color, float sheen_roughness, float coat, vec3 coat_color, float coat_roughness, float coat_anisotropy, float coat_rotation, float coat_IOR, vec3 coat_normal, float coat_affect_color, float coat_affect_roughness, float thin_film_thickness, float thin_film_IOR, float emission, vec3 emission_color, vec3 opacity, bool thin_walled, vec3 normal, vec3 tangent, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSmarble.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSmarble.out index 7c8e3f32a2..a200879c8e 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSmarble.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SSmarble.out @@ -22,22 +22,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -295,1976 +295,1976 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) -int numActiveLightSources() +float mx_pow5(float x) { - return min(u_numActiveLightSources, MAX_LIGHT_SOURCES) ; + return mx_square(mx_square(x)) * x; } -void sampleLightSource(LightData light, vec3 position, out lightshader result) +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) { - result.intensity = vec3(0.0); - result.direction = vec3(0.0); - if (light.type == 1) + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) { - mx_point_light(light, position, result); + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; } - else if (light.type == 2) +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) { - mx_directional_light(light, position, result); + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); } } -/* -Noise Library. - -This library is a modified version of the noise library found in -Open Shading Language: -github.com/imageworks/OpenShadingLanguage/blob/master/src/include/OSL/oslnoise.h - -It contains the subset of noise types needed to implement the MaterialX -standard library. The modifications are mainly conversions from C++ to GLSL. -Produced results should be identical to the OSL noise functions. - -Original copyright notice: ------------------------------------------------------------------------- -Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. -All Rights Reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -* Neither the name of Sony Pictures Imageworks nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------- -*/ - -float mx_select(bool b, float t, float f) -{ - return b ? t : f; -} - -float mx_negate_if(float val, bool b) -{ - return b ? -val : val; -} - -int mx_floor(float x) -{ - return int(floor(x)); -} - -// return mx_floor as well as the fractional remainder -float mx_floorfrac(float x, out int i) -{ - i = mx_floor(x); - return x - float(i); -} - -float mx_bilerp(float v0, float v1, float v2, float v3, float s, float t) -{ - float s1 = 1.0 - s; - return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); -} -vec3 mx_bilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, float s, float t) -{ - float s1 = 1.0 - s; - return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); -} -float mx_trilerp(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float s, float t, float r) -{ - float s1 = 1.0 - s; - float t1 = 1.0 - t; - float r1 = 1.0 - r; - return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + - r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); -} -vec3 mx_trilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, vec3 v4, vec3 v5, vec3 v6, vec3 v7, float s, float t, float r) -{ - float s1 = 1.0 - s; - float t1 = 1.0 - t; - float r1 = 1.0 - r; - return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + - r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); -} - -// 2 and 3 dimensional gradient functions - perform a dot product against a -// randomly chosen vector. Note that the gradient vector is not normalized, but -// this only affects the overal "scale" of the result, so we simply account for -// the scale by multiplying in the corresponding "perlin" function. -float mx_gradient_float(uint hash, float x, float y) -{ - // 8 possible directions (+-1,+-2) and (+-2,+-1) - uint h = hash & 7u; - float u = mx_select(h<4u, x, y); - float v = 2.0 * mx_select(h<4u, y, x); - // compute the dot product with (x,y). - return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); -} -float mx_gradient_float(uint hash, float x, float y, float z) -{ - // use vectors pointing to the edges of the cube - uint h = hash & 15u; - float u = mx_select(h<8u, x, y); - float v = mx_select(h<4u, y, mx_select((h==12u)||(h==14u), x, z)); - return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); -} -vec3 mx_gradient_vec3(uvec3 hash, float x, float y) -{ - return vec3(mx_gradient_float(hash.x, x, y), mx_gradient_float(hash.y, x, y), mx_gradient_float(hash.z, x, y)); -} -vec3 mx_gradient_vec3(uvec3 hash, float x, float y, float z) -{ - return vec3(mx_gradient_float(hash.x, x, y, z), mx_gradient_float(hash.y, x, y, z), mx_gradient_float(hash.z, x, y, z)); -} -// Scaling factors to normalize the result of gradients above. -// These factors were experimentally calculated to be: -// 2D: 0.6616 -// 3D: 0.9820 -float mx_gradient_scale2d(float v) { return 0.6616 * v; } -float mx_gradient_scale3d(float v) { return 0.9820 * v; } -vec3 mx_gradient_scale2d(vec3 v) { return 0.6616 * v; } -vec3 mx_gradient_scale3d(vec3 v) { return 0.9820 * v; } - -/// Bitwise circular rotation left by k bits (for 32 bit unsigned integers) -uint mx_rotl32(uint x, int k) -{ - return (x<>(32-k)); -} - -void mx_bjmix(inout uint a, inout uint b, inout uint c) -{ - a -= c; a ^= mx_rotl32(c, 4); c += b; - b -= a; b ^= mx_rotl32(a, 6); a += c; - c -= b; c ^= mx_rotl32(b, 8); b += a; - a -= c; a ^= mx_rotl32(c,16); c += b; - b -= a; b ^= mx_rotl32(a,19); a += c; - c -= b; c ^= mx_rotl32(b, 4); b += a; -} - -// Mix up and combine the bits of a, b, and c (doesn't change them, but -// returns a hash of those three original values). -uint mx_bjfinal(uint a, uint b, uint c) -{ - c ^= b; c -= mx_rotl32(b,14); - a ^= c; a -= mx_rotl32(c,11); - b ^= a; b -= mx_rotl32(a,25); - c ^= b; c -= mx_rotl32(b,16); - a ^= c; a -= mx_rotl32(c,4); - b ^= a; b -= mx_rotl32(a,14); - c ^= b; c -= mx_rotl32(b,24); - return c; -} - -// Convert a 32 bit integer into a floating point number in [0,1] -float mx_bits_to_01(uint bits) -{ - return float(bits) / float(uint(0xffffffff)); -} - -float mx_fade(float t) -{ - return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); -} - -uint mx_hash_int(int x) -{ - uint len = 1u; - uint seed = uint(0xdeadbeef) + (len << 2u) + 13u; - return mx_bjfinal(seed+uint(x), seed, seed); -} - -uint mx_hash_int(int x, int y) -{ - uint len = 2u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z) -{ - uint len = 3u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z, int xx) -{ - uint len = 4u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - mx_bjmix(a, b, c); - a += uint(xx); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z, int xx, int yy) -{ - uint len = 5u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - mx_bjmix(a, b, c); - a += uint(xx); - b += uint(yy); - return mx_bjfinal(a, b, c); -} - -uvec3 mx_hash_vec3(int x, int y) -{ - uint h = mx_hash_int(x, y); - // we only need the low-order bits to be random, so split out - // the 32 bit result into 3 parts for each channel - uvec3 result; - result.x = (h ) & 0xFFu; - result.y = (h >> 8 ) & 0xFFu; - result.z = (h >> 16) & 0xFFu; - return result; -} - -uvec3 mx_hash_vec3(int x, int y, int z) -{ - uint h = mx_hash_int(x, y, z); - // we only need the low-order bits to be random, so split out - // the 32 bit result into 3 parts for each channel - uvec3 result; - result.x = (h ) & 0xFFu; - result.y = (h >> 8 ) & 0xFFu; - result.z = (h >> 16) & 0xFFu; - return result; -} - -float mx_perlin_noise_float(vec2 p) -{ - int X, Y; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float u = mx_fade(fx); - float v = mx_fade(fy); - float result = mx_bilerp( - mx_gradient_float(mx_hash_int(X , Y ), fx , fy ), - mx_gradient_float(mx_hash_int(X+1, Y ), fx-1.0, fy ), - mx_gradient_float(mx_hash_int(X , Y+1), fx , fy-1.0), - mx_gradient_float(mx_hash_int(X+1, Y+1), fx-1.0, fy-1.0), - u, v); - return mx_gradient_scale2d(result); -} - -float mx_perlin_noise_float(vec3 p) -{ - int X, Y, Z; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float fz = mx_floorfrac(p.z, Z); - float u = mx_fade(fx); - float v = mx_fade(fy); - float w = mx_fade(fz); - float result = mx_trilerp( - mx_gradient_float(mx_hash_int(X , Y , Z ), fx , fy , fz ), - mx_gradient_float(mx_hash_int(X+1, Y , Z ), fx-1.0, fy , fz ), - mx_gradient_float(mx_hash_int(X , Y+1, Z ), fx , fy-1.0, fz ), - mx_gradient_float(mx_hash_int(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), - mx_gradient_float(mx_hash_int(X , Y , Z+1), fx , fy , fz-1.0), - mx_gradient_float(mx_hash_int(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), - mx_gradient_float(mx_hash_int(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), - mx_gradient_float(mx_hash_int(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), - u, v, w); - return mx_gradient_scale3d(result); -} - -vec3 mx_perlin_noise_vec3(vec2 p) -{ - int X, Y; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float u = mx_fade(fx); - float v = mx_fade(fy); - vec3 result = mx_bilerp( - mx_gradient_vec3(mx_hash_vec3(X , Y ), fx , fy ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y ), fx-1.0, fy ), - mx_gradient_vec3(mx_hash_vec3(X , Y+1), fx , fy-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1), fx-1.0, fy-1.0), - u, v); - return mx_gradient_scale2d(result); -} - -vec3 mx_perlin_noise_vec3(vec3 p) -{ - int X, Y, Z; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float fz = mx_floorfrac(p.z, Z); - float u = mx_fade(fx); - float v = mx_fade(fy); - float w = mx_fade(fz); - vec3 result = mx_trilerp( - mx_gradient_vec3(mx_hash_vec3(X , Y , Z ), fx , fy , fz ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z ), fx-1.0, fy , fz ), - mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z ), fx , fy-1.0, fz ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), - mx_gradient_vec3(mx_hash_vec3(X , Y , Z+1), fx , fy , fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), - u, v, w); - return mx_gradient_scale3d(result); -} - -float mx_cell_noise_float(float p) -{ - int ix = mx_floor(p); - return mx_bits_to_01(mx_hash_int(ix)); -} - -float mx_cell_noise_float(vec2 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - return mx_bits_to_01(mx_hash_int(ix, iy)); -} - -float mx_cell_noise_float(vec3 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - return mx_bits_to_01(mx_hash_int(ix, iy, iz)); -} - -float mx_cell_noise_float(vec4 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - int iw = mx_floor(p.w); - return mx_bits_to_01(mx_hash_int(ix, iy, iz, iw)); -} - -vec3 mx_cell_noise_vec3(float p) -{ - int ix = mx_floor(p); - return vec3( - mx_bits_to_01(mx_hash_int(ix, 0)), - mx_bits_to_01(mx_hash_int(ix, 1)), - mx_bits_to_01(mx_hash_int(ix, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec2 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec3 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, iz, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec4 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - int iw = mx_floor(p.w); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 2)) - ); -} - -float mx_fractal_noise_float(vec3 p, int octaves, float lacunarity, float diminish) -{ - float result = 0.0; - float amplitude = 1.0; - for (int i = 0; i < octaves; ++i) - { - result += amplitude * mx_perlin_noise_float(p); - amplitude *= diminish; - p *= lacunarity; - } - return result; -} - -vec3 mx_fractal_noise_vec3(vec3 p, int octaves, float lacunarity, float diminish) -{ - vec3 result = vec3(0.0); - float amplitude = 1.0; - for (int i = 0; i < octaves; ++i) - { - result += amplitude * mx_perlin_noise_vec3(p); - amplitude *= diminish; - p *= lacunarity; - } - return result; -} - -vec2 mx_fractal_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) -{ - return vec2(mx_fractal_noise_float(p, octaves, lacunarity, diminish), - mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); -} - -vec4 mx_fractal_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) -{ - vec3 c = mx_fractal_noise_vec3(p, octaves, lacunarity, diminish); - float f = mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); - return vec4(c, f); -} - -float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) -{ - vec3 tmp = mx_cell_noise_vec3(vec2(x+xoff, y+yoff)); - vec2 off = vec2(tmp.x, tmp.y); - - off -= 0.5f; - off *= jitter; - off += 0.5f; - - vec2 cellpos = vec2(float(x), float(y)) + off; - vec2 diff = cellpos - p; - if (metric == 2) - return abs(diff.x) + abs(diff.y); // Manhattan distance - if (metric == 3) - return max(abs(diff.x), abs(diff.y)); // Chebyshev distance - // Either Euclidian or Distance^2 - return dot(diff, diff); -} - -float mx_worley_distance(vec3 p, int x, int y, int z, int xoff, int yoff, int zoff, float jitter, int metric) -{ - vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); - - off -= 0.5f; - off *= jitter; - off += 0.5f; - - vec3 cellpos = vec3(float(x), float(y), float(z)) + off; - vec3 diff = cellpos - p; - if (metric == 2) - return abs(diff.x) + abs(diff.y) + abs(diff.z); // Manhattan distance - if (metric == 3) - return max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); // Chebyshev distance - // Either Euclidian or Distance^2 - return dot(diff, diff); -} - -float mx_worley_noise_float(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - float sqdist = 1e6f; // Some big number for jitter > 1 (not all GPUs may be IEEE) - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - sqdist = min(sqdist, dist); - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec2 mx_worley_noise_vec2(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - vec2 sqdist = vec2(1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - if (dist < sqdist.x) - { - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.y = dist; - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec3 mx_worley_noise_vec3(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - if (dist < sqdist.x) - { - sqdist.z = sqdist.y; - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.z = sqdist.y; - sqdist.y = dist; - } - else if (dist < sqdist.z) - { - sqdist.z = dist; - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -float mx_worley_noise_float(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - float sqdist = 1e6f; - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - sqdist = min(sqdist, dist); - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec2 mx_worley_noise_vec2(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - vec2 sqdist = vec2(1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - if (dist < sqdist.x) - { - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.y = dist; - } - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec3 mx_worley_noise_vec3(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - if (dist < sqdist.x) - { - sqdist.z = sqdist.y; - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.z = sqdist.y; - sqdist.y = dist; - } - else if (dist < sqdist.z) - { - sqdist.z = dist; - } - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -void mx_fractal3d_float(float amplitude, int octaves, float lacunarity, float diminish, vec3 position, out float result) -{ - float value = mx_fractal_noise_float(position, octaves, lacunarity, diminish); - result = value * amplitude; -} - -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - - -// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf -// Equation 2 -float mx_imageworks_sheen_NDF(float NdotH, float roughness) -{ - float invRoughness = 1.0 / max(roughness, 0.005); - float cos2 = NdotH * NdotH; - float sin2 = 1.0 - cos2; - return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); -} - -float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) -{ - // Microfacet distribution. - float D = mx_imageworks_sheen_NDF(NdotH, roughness); - - // Fresnel and geometry terms are ignored. - float F = 1.0; - float G = 1.0; - - // We use a smoother denominator, as in: - // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf - return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); -} - -// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. -float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) -{ - vec2 r = vec2(13.67300, 1.0) + - vec2(-68.78018, 61.57746) * NdotV + - vec2(799.08825, 442.78211) * roughness + - vec2(-905.00061, 2597.49308) * NdotV * roughness + - vec2(60.28956, 121.81241) * mx_square(NdotV) + - vec2(1086.96473, 3045.55075) * mx_square(roughness); - return r.x / r.y; -} - -float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - return texture(u_albedoTable, vec2(NdotV, roughness)).b; - } -#endif - return 0.0; -} - -float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); - - float radiance = 0.0; - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the incoming light direction and half vector. - vec3 L = mx_uniform_sample_hemisphere(Xi); - vec3 H = normalize(L + V); - - // Compute dot products for this sample. - float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); - - // Compute sheen reflectance. - float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - - // Add the radiance contribution of this sample. - // uniform_pdf = 1 / (2 * PI) - // radiance = reflectance * NdotL / uniform_pdf; - radiance += reflectance * NdotL * 2.0 * M_PI; - } - - // Return the final directional albedo. - return radiance / float(SAMPLE_COUNT); -} - -float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); -#else - float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); -#endif - return clamp(dirAlbedo, 0.0, 1.0); -} - -void mx_sheen_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); - - 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); - - vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - // We need to include NdotL from the light integral here - // as in this case it's not cancelled out by the BRDF denominator. - bsdf.response = fr * NdotL * occlusion * weight; -} - -void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - vec3 Li = mx_environment_irradiance(N); - bsdf.response = Li * color * dirAlbedo * weight; -} - -void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) -{ - result = vec3(dot(_in, lumacoeffs)); -} - -mat4 mx_rotationMatrix(vec3 axis, float angle) -{ - axis = normalize(axis); - float s = sin(angle); - float c = cos(angle); - float oc = 1.0 - c; - - return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, - oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, - oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, - 0.0, 0.0, 0.0, 1.0); -} - -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - float rotationRadians = radians(amount); - mat4 m = mx_rotationMatrix(axis, rotationRadians); - result = (m * vec4(_in, 1.0)).xyz; -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} - -// We fake diffuse transmission by using diffuse reflection from the opposite side. -// So this BTDF is really a BRDF. -void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - // Invert normal since we're transmitting light from the other side - float NdotL = dot(L, -normal); - if (NdotL <= 0.0 || weight < M_FLOAT_EPS) - { - return; - } - - bsdf.response = color * weight * NdotL * M_PI_INV; -} - -void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - // Invert normal since we're transmitting light from the other side - vec3 Li = mx_environment_irradiance(-normal); - bsdf.response = Li * color * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); - bsdf.response = sss * visibleOcclusion * weight; -} - -void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - // For now, we render indirect subsurface as simple indirect diffuse. - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) -{ - N = mx_forward_facing_normal(N, V); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); - result = base * f; -} +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} + +int numActiveLightSources() +{ + return min(u_numActiveLightSources, MAX_LIGHT_SOURCES) ; +} + +void sampleLightSource(LightData light, vec3 position, out lightshader result) +{ + result.intensity = vec3(0.0); + result.direction = vec3(0.0); + if (light.type == 1) + { + mx_point_light(light, position, result); + } + else if (light.type == 2) + { + mx_directional_light(light, position, result); + } +} + +/* +Noise Library. + +This library is a modified version of the noise library found in +Open Shading Language: +github.com/imageworks/OpenShadingLanguage/blob/master/src/include/OSL/oslnoise.h + +It contains the subset of noise types needed to implement the MaterialX +standard library. The modifications are mainly conversions from C++ to GLSL. +Produced results should be identical to the OSL noise functions. + +Original copyright notice: +------------------------------------------------------------------------ +Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of Sony Pictures Imageworks nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------ +*/ + +float mx_select(bool b, float t, float f) +{ + return b ? t : f; +} + +float mx_negate_if(float val, bool b) +{ + return b ? -val : val; +} + +int mx_floor(float x) +{ + return int(floor(x)); +} + +// return mx_floor as well as the fractional remainder +float mx_floorfrac(float x, out int i) +{ + i = mx_floor(x); + return x - float(i); +} + +float mx_bilerp(float v0, float v1, float v2, float v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); +} +vec3 mx_bilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); +} +float mx_trilerp(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + + r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); +} +vec3 mx_trilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, vec3 v4, vec3 v5, vec3 v6, vec3 v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + + r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); +} + +// 2 and 3 dimensional gradient functions - perform a dot product against a +// randomly chosen vector. Note that the gradient vector is not normalized, but +// this only affects the overal "scale" of the result, so we simply account for +// the scale by multiplying in the corresponding "perlin" function. +float mx_gradient_float(uint hash, float x, float y) +{ + // 8 possible directions (+-1,+-2) and (+-2,+-1) + uint h = hash & 7u; + float u = mx_select(h<4u, x, y); + float v = 2.0 * mx_select(h<4u, y, x); + // compute the dot product with (x,y). + return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); +} +float mx_gradient_float(uint hash, float x, float y, float z) +{ + // use vectors pointing to the edges of the cube + uint h = hash & 15u; + float u = mx_select(h<8u, x, y); + float v = mx_select(h<4u, y, mx_select((h==12u)||(h==14u), x, z)); + return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); +} +vec3 mx_gradient_vec3(uvec3 hash, float x, float y) +{ + return vec3(mx_gradient_float(hash.x, x, y), mx_gradient_float(hash.y, x, y), mx_gradient_float(hash.z, x, y)); +} +vec3 mx_gradient_vec3(uvec3 hash, float x, float y, float z) +{ + return vec3(mx_gradient_float(hash.x, x, y, z), mx_gradient_float(hash.y, x, y, z), mx_gradient_float(hash.z, x, y, z)); +} +// Scaling factors to normalize the result of gradients above. +// These factors were experimentally calculated to be: +// 2D: 0.6616 +// 3D: 0.9820 +float mx_gradient_scale2d(float v) { return 0.6616 * v; } +float mx_gradient_scale3d(float v) { return 0.9820 * v; } +vec3 mx_gradient_scale2d(vec3 v) { return 0.6616 * v; } +vec3 mx_gradient_scale3d(vec3 v) { return 0.9820 * v; } + +/// Bitwise circular rotation left by k bits (for 32 bit unsigned integers) +uint mx_rotl32(uint x, int k) +{ + return (x<>(32-k)); +} + +void mx_bjmix(inout uint a, inout uint b, inout uint c) +{ + a -= c; a ^= mx_rotl32(c, 4); c += b; + b -= a; b ^= mx_rotl32(a, 6); a += c; + c -= b; c ^= mx_rotl32(b, 8); b += a; + a -= c; a ^= mx_rotl32(c,16); c += b; + b -= a; b ^= mx_rotl32(a,19); a += c; + c -= b; c ^= mx_rotl32(b, 4); b += a; +} + +// Mix up and combine the bits of a, b, and c (doesn't change them, but +// returns a hash of those three original values). +uint mx_bjfinal(uint a, uint b, uint c) +{ + c ^= b; c -= mx_rotl32(b,14); + a ^= c; a -= mx_rotl32(c,11); + b ^= a; b -= mx_rotl32(a,25); + c ^= b; c -= mx_rotl32(b,16); + a ^= c; a -= mx_rotl32(c,4); + b ^= a; b -= mx_rotl32(a,14); + c ^= b; c -= mx_rotl32(b,24); + return c; +} + +// Convert a 32 bit integer into a floating point number in [0,1] +float mx_bits_to_01(uint bits) +{ + return float(bits) / float(uint(0xffffffff)); +} + +float mx_fade(float t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +uint mx_hash_int(int x) +{ + uint len = 1u; + uint seed = uint(0xdeadbeef) + (len << 2u) + 13u; + return mx_bjfinal(seed+uint(x), seed, seed); +} + +uint mx_hash_int(int x, int y) +{ + uint len = 2u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z) +{ + uint len = 3u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z, int xx) +{ + uint len = 4u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + mx_bjmix(a, b, c); + a += uint(xx); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z, int xx, int yy) +{ + uint len = 5u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + mx_bjmix(a, b, c); + a += uint(xx); + b += uint(yy); + return mx_bjfinal(a, b, c); +} + +uvec3 mx_hash_vec3(int x, int y) +{ + uint h = mx_hash_int(x, y); + // we only need the low-order bits to be random, so split out + // the 32 bit result into 3 parts for each channel + uvec3 result; + result.x = (h ) & 0xFFu; + result.y = (h >> 8 ) & 0xFFu; + result.z = (h >> 16) & 0xFFu; + return result; +} + +uvec3 mx_hash_vec3(int x, int y, int z) +{ + uint h = mx_hash_int(x, y, z); + // we only need the low-order bits to be random, so split out + // the 32 bit result into 3 parts for each channel + uvec3 result; + result.x = (h ) & 0xFFu; + result.y = (h >> 8 ) & 0xFFu; + result.z = (h >> 16) & 0xFFu; + return result; +} + +float mx_perlin_noise_float(vec2 p) +{ + int X, Y; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float u = mx_fade(fx); + float v = mx_fade(fy); + float result = mx_bilerp( + mx_gradient_float(mx_hash_int(X , Y ), fx , fy ), + mx_gradient_float(mx_hash_int(X+1, Y ), fx-1.0, fy ), + mx_gradient_float(mx_hash_int(X , Y+1), fx , fy-1.0), + mx_gradient_float(mx_hash_int(X+1, Y+1), fx-1.0, fy-1.0), + u, v); + return mx_gradient_scale2d(result); +} + +float mx_perlin_noise_float(vec3 p) +{ + int X, Y, Z; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float fz = mx_floorfrac(p.z, Z); + float u = mx_fade(fx); + float v = mx_fade(fy); + float w = mx_fade(fz); + float result = mx_trilerp( + mx_gradient_float(mx_hash_int(X , Y , Z ), fx , fy , fz ), + mx_gradient_float(mx_hash_int(X+1, Y , Z ), fx-1.0, fy , fz ), + mx_gradient_float(mx_hash_int(X , Y+1, Z ), fx , fy-1.0, fz ), + mx_gradient_float(mx_hash_int(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), + mx_gradient_float(mx_hash_int(X , Y , Z+1), fx , fy , fz-1.0), + mx_gradient_float(mx_hash_int(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), + mx_gradient_float(mx_hash_int(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), + mx_gradient_float(mx_hash_int(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), + u, v, w); + return mx_gradient_scale3d(result); +} + +vec3 mx_perlin_noise_vec3(vec2 p) +{ + int X, Y; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float u = mx_fade(fx); + float v = mx_fade(fy); + vec3 result = mx_bilerp( + mx_gradient_vec3(mx_hash_vec3(X , Y ), fx , fy ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y ), fx-1.0, fy ), + mx_gradient_vec3(mx_hash_vec3(X , Y+1), fx , fy-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1), fx-1.0, fy-1.0), + u, v); + return mx_gradient_scale2d(result); +} + +vec3 mx_perlin_noise_vec3(vec3 p) +{ + int X, Y, Z; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float fz = mx_floorfrac(p.z, Z); + float u = mx_fade(fx); + float v = mx_fade(fy); + float w = mx_fade(fz); + vec3 result = mx_trilerp( + mx_gradient_vec3(mx_hash_vec3(X , Y , Z ), fx , fy , fz ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z ), fx-1.0, fy , fz ), + mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z ), fx , fy-1.0, fz ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), + mx_gradient_vec3(mx_hash_vec3(X , Y , Z+1), fx , fy , fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), + u, v, w); + return mx_gradient_scale3d(result); +} + +float mx_cell_noise_float(float p) +{ + int ix = mx_floor(p); + return mx_bits_to_01(mx_hash_int(ix)); +} + +float mx_cell_noise_float(vec2 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + return mx_bits_to_01(mx_hash_int(ix, iy)); +} + +float mx_cell_noise_float(vec3 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + return mx_bits_to_01(mx_hash_int(ix, iy, iz)); +} + +float mx_cell_noise_float(vec4 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + int iw = mx_floor(p.w); + return mx_bits_to_01(mx_hash_int(ix, iy, iz, iw)); +} + +vec3 mx_cell_noise_vec3(float p) +{ + int ix = mx_floor(p); + return vec3( + mx_bits_to_01(mx_hash_int(ix, 0)), + mx_bits_to_01(mx_hash_int(ix, 1)), + mx_bits_to_01(mx_hash_int(ix, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec2 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec3 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, iz, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec4 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + int iw = mx_floor(p.w); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 2)) + ); +} + +float mx_fractal_noise_float(vec3 p, int octaves, float lacunarity, float diminish) +{ + float result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mx_perlin_noise_float(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +vec3 mx_fractal_noise_vec3(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 result = vec3(0.0); + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mx_perlin_noise_vec3(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +vec2 mx_fractal_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal_noise_float(p, octaves, lacunarity, diminish), + mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec3 tmp = mx_cell_noise_vec3(vec2(x+xoff, y+yoff)); + vec2 off = vec2(tmp.x, tmp.y); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + vec2 cellpos = vec2(float(x), float(y)) + off; + vec2 diff = cellpos - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y); // Manhattan distance + if (metric == 3) + return max(abs(diff.x), abs(diff.y)); // Chebyshev distance + // Either Euclidian or Distance^2 + return dot(diff, diff); +} + +float mx_worley_distance(vec3 p, int x, int y, int z, int xoff, int yoff, int zoff, float jitter, int metric) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + vec3 cellpos = vec3(float(x), float(y), float(z)) + off; + vec3 diff = cellpos - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y) + abs(diff.z); // Manhattan distance + if (metric == 3) + return max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); // Chebyshev distance + // Either Euclidian or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + float sqdist = 1e6f; // Some big number for jitter > 1 (not all GPUs may be IEEE) + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + sqdist = min(sqdist, dist); + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +float mx_worley_noise_float(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + float sqdist = 1e6f; + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + sqdist = min(sqdist, dist); + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + vec2 sqdist = vec2(1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +void mx_fractal3d_float(float amplitude, int octaves, float lacunarity, float diminish, vec3 position, out float result) +{ + float value = mx_fractal_noise_float(position, octaves, lacunarity, diminish); + result = value * amplitude; +} + +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + + +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +float mx_imageworks_sheen_NDF(float NdotH, float roughness) +{ + float invRoughness = 1.0 / max(roughness, 0.005); + float cos2 = NdotH * NdotH; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) +{ + // Microfacet distribution. + float D = mx_imageworks_sheen_NDF(NdotH, roughness); + + // Fresnel and geometry terms are ignored. + float F = 1.0; + float G = 1.0; + + // We use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); +} + +// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. +float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(13.67300, 1.0) + + vec2(-68.78018, 61.57746) * NdotV + + vec2(799.08825, 442.78211) * roughness + + vec2(-905.00061, 2597.49308) * NdotV * roughness + + vec2(60.28956, 121.81241) * mx_square(NdotV) + + vec2(1086.96473, 3045.55075) * mx_square(roughness); + return r.x / r.y; +} + +float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + return texture(u_albedoTable, vec2(NdotV, roughness)).b; + } +#endif + return 0.0; +} + +float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); + + float radiance = 0.0; + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the incoming light direction and half vector. + vec3 L = mx_uniform_sample_hemisphere(Xi); + vec3 H = normalize(L + V); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); + + // Compute sheen reflectance. + float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL * 2.0 * M_PI; + } + + // Return the final directional albedo. + return radiance / float(SAMPLE_COUNT); +} + +float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); +#else + float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +void mx_sheen_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); + + 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + // We need to include NdotL from the light integral here + // as in this case it's not cancelled out by the BRDF denominator. + bsdf.response = fr * NdotL * occlusion * weight; +} + +void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; +} + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + +mat4 mx_rotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + float rotationRadians = radians(amount); + mat4 m = mx_rotationMatrix(axis, rotationRadians); + result = (m * vec4(_in, 1.0)).xyz; +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} + +// We fake diffuse transmission by using diffuse reflection from the opposite side. +// So this BTDF is really a BRDF. +void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return; + } + + bsdf.response = color * weight * NdotL * M_PI_INV; +} + +void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + // Invert normal since we're transmitting light from the other side + vec3 Li = mx_environment_irradiance(-normal); + bsdf.response = Li * color * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; +} + +void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) +{ + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); + result = base * f; +} void NG_standard_surface_surfaceshader_100(float base, vec3 base_color, float diffuse_roughness, float metalness, float specular, vec3 specular_color, float specular_roughness, float specular_IOR, float specular_anisotropy, float specular_rotation, float transmission, vec3 transmission_color, float transmission_depth, vec3 transmission_scatter, float transmission_scatter_anisotropy, float transmission_dispersion, float transmission_extra_roughness, float subsurface, vec3 subsurface_color, vec3 subsurface_radius, float subsurface_scale, float subsurface_anisotropy, float sheen, vec3 sheen_color, float sheen_roughness, float coat, vec3 coat_color, float coat_roughness, float coat_anisotropy, float coat_rotation, float coat_IOR, vec3 coat_normal, float coat_affect_color, float coat_affect_roughness, float thin_film_thickness, float thin_film_IOR, float emission, vec3 emission_color, vec3 opacity, bool thin_walled, vec3 normal, vec3 tangent, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured.out index 8db11fbf6f..38fa1eeea0 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured.out @@ -33,22 +33,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -320,738 +320,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -1072,17 +1072,17 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) -{ - uv = uv * uv_scale + uv_offset; - return uv; -} - -void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).r; -} +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return uv; +} + +void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).r; +} void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out float out1) { @@ -1095,12 +1095,12 @@ void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uv out1 = N_img_float_out; } - -void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).rgb; -} + +void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).rgb; +} void NG_tiledimage_color3(sampler2D file, vec3 default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out vec3 out1) { @@ -1142,581 +1142,581 @@ void NG_srgb_texture_to_lin_rec709_color3(vec3 in1, out vec3 out1) out1 = mix_out; } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - - -// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf -// Equation 2 -float mx_imageworks_sheen_NDF(float NdotH, float roughness) -{ - float invRoughness = 1.0 / max(roughness, 0.005); - float cos2 = NdotH * NdotH; - float sin2 = 1.0 - cos2; - return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); -} - -float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) -{ - // Microfacet distribution. - float D = mx_imageworks_sheen_NDF(NdotH, roughness); - - // Fresnel and geometry terms are ignored. - float F = 1.0; - float G = 1.0; - - // We use a smoother denominator, as in: - // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf - return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); -} - -// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. -float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) -{ - vec2 r = vec2(13.67300, 1.0) + - vec2(-68.78018, 61.57746) * NdotV + - vec2(799.08825, 442.78211) * roughness + - vec2(-905.00061, 2597.49308) * NdotV * roughness + - vec2(60.28956, 121.81241) * mx_square(NdotV) + - vec2(1086.96473, 3045.55075) * mx_square(roughness); - return r.x / r.y; -} - -float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - return texture(u_albedoTable, vec2(NdotV, roughness)).b; - } -#endif - return 0.0; -} - -float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); - - float radiance = 0.0; - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the incoming light direction and half vector. - vec3 L = mx_uniform_sample_hemisphere(Xi); - vec3 H = normalize(L + V); - - // Compute dot products for this sample. - float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); - - // Compute sheen reflectance. - float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - - // Add the radiance contribution of this sample. - // uniform_pdf = 1 / (2 * PI) - // radiance = reflectance * NdotL / uniform_pdf; - radiance += reflectance * NdotL * 2.0 * M_PI; - } - - // Return the final directional albedo. - return radiance / float(SAMPLE_COUNT); -} - -float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); -#else - float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); -#endif - return clamp(dirAlbedo, 0.0, 1.0); -} - -void mx_sheen_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); - - 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); - - vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - // We need to include NdotL from the light integral here - // as in this case it's not cancelled out by the BRDF denominator. - bsdf.response = fr * NdotL * occlusion * weight; -} - -void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - vec3 Li = mx_environment_irradiance(N); - bsdf.response = Li * color * dirAlbedo * weight; -} - -void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) -{ - result = vec3(dot(_in, lumacoeffs)); -} - -mat4 mx_rotationMatrix(vec3 axis, float angle) -{ - axis = normalize(axis); - float s = sin(angle); - float c = cos(angle); - float oc = 1.0 - c; - - return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, - oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, - oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, - 0.0, 0.0, 0.0, 1.0); -} - -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - float rotationRadians = radians(amount); - mat4 m = mx_rotationMatrix(axis, rotationRadians); - result = (m * vec4(_in, 1.0)).xyz; -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} - -// We fake diffuse transmission by using diffuse reflection from the opposite side. -// So this BTDF is really a BRDF. -void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - // Invert normal since we're transmitting light from the other side - float NdotL = dot(L, -normal); - if (NdotL <= 0.0 || weight < M_FLOAT_EPS) - { - return; - } - - bsdf.response = color * weight * NdotL * M_PI_INV; -} - -void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - // Invert normal since we're transmitting light from the other side - vec3 Li = mx_environment_irradiance(-normal); - bsdf.response = Li * color * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); - bsdf.response = sss * visibleOcclusion * weight; -} - -void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - // For now, we render indirect subsurface as simple indirect diffuse. - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) -{ - N = mx_forward_facing_normal(N, V); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); - result = base * f; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + + +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +float mx_imageworks_sheen_NDF(float NdotH, float roughness) +{ + float invRoughness = 1.0 / max(roughness, 0.005); + float cos2 = NdotH * NdotH; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) +{ + // Microfacet distribution. + float D = mx_imageworks_sheen_NDF(NdotH, roughness); + + // Fresnel and geometry terms are ignored. + float F = 1.0; + float G = 1.0; + + // We use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); +} + +// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. +float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(13.67300, 1.0) + + vec2(-68.78018, 61.57746) * NdotV + + vec2(799.08825, 442.78211) * roughness + + vec2(-905.00061, 2597.49308) * NdotV * roughness + + vec2(60.28956, 121.81241) * mx_square(NdotV) + + vec2(1086.96473, 3045.55075) * mx_square(roughness); + return r.x / r.y; +} + +float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + return texture(u_albedoTable, vec2(NdotV, roughness)).b; + } +#endif + return 0.0; +} + +float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); + + float radiance = 0.0; + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the incoming light direction and half vector. + vec3 L = mx_uniform_sample_hemisphere(Xi); + vec3 H = normalize(L + V); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); + + // Compute sheen reflectance. + float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL * 2.0 * M_PI; + } + + // Return the final directional albedo. + return radiance / float(SAMPLE_COUNT); +} + +float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); +#else + float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +void mx_sheen_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); + + 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + // We need to include NdotL from the light integral here + // as in this case it's not cancelled out by the BRDF denominator. + bsdf.response = fr * NdotL * occlusion * weight; +} + +void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; +} + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + +mat4 mx_rotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + float rotationRadians = radians(amount); + mat4 m = mx_rotationMatrix(axis, rotationRadians); + result = (m * vec4(_in, 1.0)).xyz; +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} + +// We fake diffuse transmission by using diffuse reflection from the opposite side. +// So this BTDF is really a BRDF. +void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return; + } + + bsdf.response = color * weight * NdotL * M_PI_INV; +} + +void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + // Invert normal since we're transmitting light from the other side + vec3 Li = mx_environment_irradiance(-normal); + bsdf.response = Li * color * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; +} + +void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) +{ + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); + result = base * f; +} void NG_standard_surface_surfaceshader_100(float base, vec3 base_color, float diffuse_roughness, float metalness, float specular, vec3 specular_color, float specular_roughness, float specular_IOR, float specular_anisotropy, float specular_rotation, float transmission, vec3 transmission_color, float transmission_depth, vec3 transmission_scatter, float transmission_scatter_anisotropy, float transmission_dispersion, float transmission_extra_roughness, float subsurface, vec3 subsurface_color, vec3 subsurface_radius, float subsurface_scale, float subsurface_anisotropy, float sheen, vec3 sheen_color, float sheen_roughness, float coat, vec3 coat_color, float coat_roughness, float coat_anisotropy, float coat_rotation, float coat_IOR, vec3 coat_normal, float coat_affect_color, float coat_affect_roughness, float thin_film_thickness, float thin_film_IOR, float emission, vec3 emission_color, vec3 opacity, bool thin_walled, vec3 normal, vec3 tangent, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured_bindless.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured_bindless.out index 1f828f1dc8..d8ea43bf57 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured_bindless.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_SStextured_bindless.out @@ -33,22 +33,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -323,738 +323,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -1075,17 +1075,17 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) -{ - uv = uv * uv_scale + uv_offset; - return uv; -} - -void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).r; -} +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return uv; +} + +void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).r; +} void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out float out1) { @@ -1098,12 +1098,12 @@ void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uv out1 = N_img_float_out; } - -void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).rgb; -} + +void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).rgb; +} void NG_tiledimage_color3(sampler2D file, vec3 default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out vec3 out1) { @@ -1145,581 +1145,581 @@ void NG_srgb_texture_to_lin_rec709_color3(vec3 in1, out vec3 out1) out1 = mix_out; } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - - -// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf -// Equation 2 -float mx_imageworks_sheen_NDF(float NdotH, float roughness) -{ - float invRoughness = 1.0 / max(roughness, 0.005); - float cos2 = NdotH * NdotH; - float sin2 = 1.0 - cos2; - return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); -} - -float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) -{ - // Microfacet distribution. - float D = mx_imageworks_sheen_NDF(NdotH, roughness); - - // Fresnel and geometry terms are ignored. - float F = 1.0; - float G = 1.0; - - // We use a smoother denominator, as in: - // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf - return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); -} - -// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. -float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) -{ - vec2 r = vec2(13.67300, 1.0) + - vec2(-68.78018, 61.57746) * NdotV + - vec2(799.08825, 442.78211) * roughness + - vec2(-905.00061, 2597.49308) * NdotV * roughness + - vec2(60.28956, 121.81241) * mx_square(NdotV) + - vec2(1086.96473, 3045.55075) * mx_square(roughness); - return r.x / r.y; -} - -float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - return texture(u_albedoTable, vec2(NdotV, roughness)).b; - } -#endif - return 0.0; -} - -float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); - - float radiance = 0.0; - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the incoming light direction and half vector. - vec3 L = mx_uniform_sample_hemisphere(Xi); - vec3 H = normalize(L + V); - - // Compute dot products for this sample. - float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); - - // Compute sheen reflectance. - float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - - // Add the radiance contribution of this sample. - // uniform_pdf = 1 / (2 * PI) - // radiance = reflectance * NdotL / uniform_pdf; - radiance += reflectance * NdotL * 2.0 * M_PI; - } - - // Return the final directional albedo. - return radiance / float(SAMPLE_COUNT); -} - -float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); -#else - float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); -#endif - return clamp(dirAlbedo, 0.0, 1.0); -} - -void mx_sheen_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); - - 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); - - vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); - float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - // We need to include NdotL from the light integral here - // as in this case it's not cancelled out by the BRDF denominator. - bsdf.response = fr * NdotL * occlusion * weight; -} - -void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); - bsdf.throughput = vec3(1.0 - dirAlbedo * weight); - - vec3 Li = mx_environment_irradiance(N); - bsdf.response = Li * color * dirAlbedo * weight; -} - -void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) -{ - result = vec3(dot(_in, lumacoeffs)); -} - -mat4 mx_rotationMatrix(vec3 axis, float angle) -{ - axis = normalize(axis); - float s = sin(angle); - float c = cos(angle); - float oc = 1.0 - c; - - return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, - oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, - oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, - 0.0, 0.0, 0.0, 1.0); -} - -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - float rotationRadians = radians(amount); - mat4 m = mx_rotationMatrix(axis, rotationRadians); - result = (m * vec4(_in, 1.0)).xyz; -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} - -// We fake diffuse transmission by using diffuse reflection from the opposite side. -// So this BTDF is really a BRDF. -void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - // Invert normal since we're transmitting light from the other side - float NdotL = dot(L, -normal); - if (NdotL <= 0.0 || weight < M_FLOAT_EPS) - { - return; - } - - bsdf.response = color * weight * NdotL * M_PI_INV; -} - -void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - // Invert normal since we're transmitting light from the other side - vec3 Li = mx_environment_irradiance(-normal); - bsdf.response = Li * color * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); - bsdf.response = sss * visibleOcclusion * weight; -} - -void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - // For now, we render indirect subsurface as simple indirect diffuse. - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) -{ - N = mx_forward_facing_normal(N, V); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); - result = base * f; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + + +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +float mx_imageworks_sheen_NDF(float NdotH, float roughness) +{ + float invRoughness = 1.0 / max(roughness, 0.005); + float cos2 = NdotH * NdotH; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +float mx_imageworks_sheen_brdf(float NdotL, float NdotV, float NdotH, float roughness) +{ + // Microfacet distribution. + float D = mx_imageworks_sheen_NDF(NdotH, roughness); + + // Fresnel and geometry terms are ignored. + float F = 1.0; + float G = 1.0; + + // We use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + return D * F * G / (4.0 * (NdotL + NdotV - NdotL*NdotV)); +} + +// Rational quadratic fit to Monte Carlo data for Imageworks sheen directional albedo. +float mx_imageworks_sheen_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(13.67300, 1.0) + + vec2(-68.78018, 61.57746) * NdotV + + vec2(799.08825, 442.78211) * roughness + + vec2(-905.00061, 2597.49308) * NdotV * roughness + + vec2(60.28956, 121.81241) * mx_square(NdotV) + + vec2(1086.96473, 3045.55075) * mx_square(roughness); + return r.x / r.y; +} + +float mx_imageworks_sheen_dir_albedo_table_lookup(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + return texture(u_albedoTable, vec2(NdotV, roughness)).b; + } +#endif + return 0.0; +} + +float mx_imageworks_sheen_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0f - mx_square(NdotV)), 0, NdotV); + + float radiance = 0.0; + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the incoming light direction and half vector. + vec3 L = mx_uniform_sample_hemisphere(Xi); + vec3 H = normalize(L + V); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); + + // Compute sheen reflectance. + float reflectance = mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL * 2.0 * M_PI; + } + + // Return the final directional albedo. + return radiance / float(SAMPLE_COUNT); +} + +float mx_imageworks_sheen_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_analytic(NdotV, roughness); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + float dirAlbedo = mx_imageworks_sheen_dir_albedo_table_lookup(NdotV, roughness); +#else + float dirAlbedo = mx_imageworks_sheen_dir_albedo_monte_carlo(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +void mx_sheen_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); + + 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 NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + vec3 fr = color * mx_imageworks_sheen_brdf(NdotL, NdotV, NdotH, roughness); + float dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + // We need to include NdotL from the light integral here + // as in this case it's not cancelled out by the BRDF denominator. + bsdf.response = fr * NdotL * occlusion * weight; +} + +void mx_sheen_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 = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; +} + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + +mat4 mx_rotationMatrix(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + float rotationRadians = radians(amount); + mat4 m = mx_rotationMatrix(axis, rotationRadians); + result = (m * vec4(_in, 1.0)).xyz; +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} + +// We fake diffuse transmission by using diffuse reflection from the opposite side. +// So this BTDF is really a BRDF. +void mx_translucent_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return; + } + + bsdf.response = color * weight * NdotL * M_PI_INV; +} + +void mx_translucent_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + // Invert normal since we're transmitting light from the other side + vec3 Li = mx_environment_irradiance(-normal); + bsdf.response = Li * color * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_subsurface_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 sss = mx_subsurface_scattering_approx(normal, L, P, color, radius); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; +} + +void mx_subsurface_bsdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_generalized_schlick_edf(vec3 N, vec3 V, vec3 color0, vec3 color90, float exponent, EDF base, out EDF result) +{ + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + vec3 f = mx_fresnel_schlick(NdotV, color0, color90, exponent); + result = base * f; +} void NG_standard_surface_surfaceshader_100(float base, vec3 base_color, float diffuse_roughness, float metalness, float specular, vec3 specular_color, float specular_roughness, float specular_IOR, float specular_anisotropy, float specular_rotation, float transmission, vec3 transmission_color, float transmission_depth, vec3 transmission_scatter, float transmission_scatter_anisotropy, float transmission_dispersion, float transmission_extra_roughness, float subsurface, vec3 subsurface_color, vec3 subsurface_radius, float subsurface_scale, float subsurface_anisotropy, float sheen, vec3 sheen_color, float sheen_roughness, float coat, vec3 coat_color, float coat_roughness, float coat_anisotropy, float coat_rotation, float coat_IOR, vec3 coat_normal, float coat_affect_color, float coat_affect_roughness, float thin_film_thickness, float thin_film_IOR, float emission, vec3 emission_color, vec3 opacity, bool thin_walled, vec3 normal, vec3 tangent, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out index 184143842f..84e55c95fb 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSdefault.out @@ -22,22 +22,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -220,738 +220,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -972,490 +972,490 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - -void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) -{ - // Decode the normal map. - value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; - - // Transform from tangent space if needed. - if (map_space == 0) - { - vec3 B = normalize(cross(N, T)); - value.xy *= normal_scale; - value = T * value.x + B * value.y + N * value.z; - } - - // Normalize the result. - result = normalize(value); -} - -void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) -{ - mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); -} - - -void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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(mx_f0_to_ior(avgF0)); - bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; - } -} - -void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * comp * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + +void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) +{ + // Decode the normal map. + value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + + // Transform from tangent space if needed. + if (map_space == 0) + { + vec3 B = normalize(cross(N, T)); + value.xy *= normal_scale; + value = T * value.x + B * value.y + N * value.z; + } + + // Normalize the result. + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) +{ + mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); +} + + +void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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(mx_f0_to_ior(avgF0)); + bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; + } +} + +void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * comp * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} void IMP_UsdPreviewSurface_surfaceshader(vec3 diffuseColor, vec3 emissiveColor, int useSpecularWorkflow, vec3 specularColor, float metallic, float roughness, float clearcoat, float clearcoatRoughness, float opacity, float opacityThreshold, float ior, vec3 normal, float displacement, float occlusion, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out index 0fa3ad960a..8e4d5c85e7 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPSglass.out @@ -22,22 +22,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -221,738 +221,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -973,490 +973,490 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - -void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) -{ - // Decode the normal map. - value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; - - // Transform from tangent space if needed. - if (map_space == 0) - { - vec3 B = normalize(cross(N, T)); - value.xy *= normal_scale; - value = T * value.x + B * value.y + N * value.z; - } - - // Normalize the result. - result = normalize(value); -} - -void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) -{ - mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); -} - - -void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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(mx_f0_to_ior(avgF0)); - bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; - } -} - -void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * comp * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + +void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) +{ + // Decode the normal map. + value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + + // Transform from tangent space if needed. + if (map_space == 0) + { + vec3 B = normalize(cross(N, T)); + value.xy *= normal_scale; + value = T * value.x + B * value.y + N * value.z; + } + + // Normalize the result. + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) +{ + mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); +} + + +void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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(mx_f0_to_ior(avgF0)); + bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; + } +} + +void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * comp * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} void IMP_UsdPreviewSurface_surfaceshader(vec3 diffuseColor, vec3 emissiveColor, int useSpecularWorkflow, vec3 specularColor, float metallic, float roughness, float clearcoat, float clearcoatRoughness, float opacity, float opacityThreshold, float ior, vec3 normal, float displacement, float occlusion, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out index cf1733aabe..f506957599 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_UsdPStextured.out @@ -28,22 +28,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -269,738 +269,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -1021,17 +1021,17 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) -{ - uv = uv * uv_scale + uv_offset; - return uv; -} - -void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).rgb; -} +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return uv; +} + +void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).rgb; +} void NG_tiledimage_color3(sampler2D file, vec3 default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out vec3 out1) { @@ -1044,12 +1044,12 @@ void NG_tiledimage_color3(sampler2D file, vec3 default1, vec2 texcoord1, vec2 uv out1 = N_img_color3_out; } - -void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) -{ - vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); - result = texture(tex_sampler, uv).r; -} + +void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv).r; +} void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uvtiling, vec2 uvoffset, vec2 realworldimagesize, vec2 realworldtilesize, int filtertype, int framerange, int frameoffset, int frameendaction, out float out1) { @@ -1062,490 +1062,490 @@ void NG_tiledimage_float(sampler2D file, float default1, vec2 texcoord1, vec2 uv out1 = N_img_float_out; } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - -void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) -{ - // Decode the normal map. - value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; - - // Transform from tangent space if needed. - if (map_space == 0) - { - vec3 B = normalize(cross(N, T)); - value.xy *= normal_scale; - value = T * value.x + B * value.y + N * value.z; - } - - // Normalize the result. - result = normalize(value); -} - -void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) -{ - mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); -} - - -void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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(mx_f0_to_ior(avgF0)); - bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; - } -} - -void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * comp * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + +void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) +{ + // Decode the normal map. + value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + + // Transform from tangent space if needed. + if (map_space == 0) + { + vec3 B = normalize(cross(N, T)); + value.xy *= normal_scale; + value = T * value.x + B * value.y + N * value.z; + } + + // Normalize the result. + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) +{ + mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); +} + + +void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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(mx_f0_to_ior(avgF0)); + bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; + } +} + +void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * comp * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} void IMP_UsdPreviewSurface_surfaceshader(vec3 diffuseColor, vec3 emissiveColor, int useSpecularWorkflow, vec3 specularColor, float metallic, float roughness, float clearcoat, float clearcoatRoughness, float opacity, float opacityThreshold, float ior, vec3 normal, float displacement, float occlusion, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out index 2c8c34ea4a..8494394faa 100644 --- a/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out +++ b/pxr/imaging/hdSt/testenv/testHdStMaterialXShaderGen/baseline/shadergen_texcoord.out @@ -27,22 +27,22 @@ -- glsl MaterialX.Surface -#define M_FLOAT_EPS 1e-8 - -float mx_square(float x) -{ - return x*x; -} - -vec2 mx_square(vec2 x) -{ - return x*x; -} - -vec3 mx_square(vec3 x) -{ - return x*x; -} +#define M_FLOAT_EPS 1e-8 + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} struct BSDF { vec3 response; vec3 throughput; float thickness; float ior; }; #define EDF vec3 struct surfaceshader { vec3 color; vec3 transparency; }; @@ -236,738 +236,738 @@ void mxInit(vec4 Peye, vec3 Neye) u_envMatrix = u_envMatrix * hdTransformationMatrix; } -#define M_PI 3.1415926535897932 -#define M_PI_INV (1.0 / M_PI) - -float mx_pow5(float x) -{ - return mx_square(mx_square(x)) * x; -} - -// Standard Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return F0 + (1.0 - F0) * x5; -} - -// Generalized Schlick Fresnel -float mx_fresnel_schlick(float cosTheta, float F0, float F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - float x5 = mx_pow5(x); - return mix(F0, F90, x5); -} - -// Generalized Schlick Fresnel with a variable exponent -float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} -vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) -{ - float x = clamp(1.0 - cosTheta, 0.0, 1.0); - return mix(F0, F90, pow(x, exponent)); -} - -// Enforce that the given normal is forward-facing from the specified view direction. -vec3 mx_forward_facing_normal(vec3 N, vec3 V) -{ - return (dot(N, V) < 0.0) ? -N : N; -} - -// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf -float mx_golden_ratio_sequence(int i) -{ - const float GOLDEN_RATIO = 1.6180339887498948; - return fract((float(i) + 1.0) * GOLDEN_RATIO); -} - -// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf -vec2 mx_spherical_fibonacci(int i, int numSamples) -{ - return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); -} - -// Generate a uniform-weighted sample in the unit hemisphere. -vec3 mx_uniform_sample_hemisphere(vec2 Xi) -{ - float phi = 2.0 * M_PI * Xi.x; - float cosTheta = 1.0 - Xi.y; - float sinTheta = sqrt(1.0 - mx_square(cosTheta)); - return vec3(cos(phi) * sinTheta, - sin(phi) * sinTheta, - cosTheta); -} - -// Fresnel model options. -const int FRESNEL_MODEL_DIELECTRIC = 0; -const int FRESNEL_MODEL_CONDUCTOR = 1; -const int FRESNEL_MODEL_SCHLICK = 2; -const int FRESNEL_MODEL_AIRY = 3; -const int FRESNEL_MODEL_SCHLICK_AIRY = 4; - -// 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); - -// Parameters for Fresnel calculations. -struct FresnelData -{ - int model; - - // Physical Fresnel - vec3 ior; - vec3 extinction; - - // Generalized Schlick Fresnel - vec3 F0; - vec3 F90; - float exponent; - - // Thin film - float tf_thickness; - float tf_ior; - - // Refraction - bool refraction; - -#ifdef __METAL__ -FresnelData(int _model = 0, - vec3 _ior = vec3(0.0f), - vec3 _extinction = vec3(0.0f), - vec3 _F0 = vec3(0.0f), - vec3 _F90 = vec3(0.0f), - float _exponent = 0.0f, - float _tf_thickness = 0.0f, - float _tf_ior = 0.0f, - bool _refraction = false) : - model(_model), - ior(_ior), - extinction(_extinction), - F0(_F0), F90(_F90), exponent(_exponent), - tf_thickness(_tf_thickness), - tf_ior(_tf_ior), - refraction(_refraction) {} -#endif - -}; - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 13 -float mx_ggx_NDF(vec3 H, vec2 alpha) -{ - vec2 He = H.xy / alpha; - float denom = dot(He, He) + mx_square(H.z); - return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); -} - -// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html -vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) -{ - // Transform the view direction to the hemisphere configuration. - V = normalize(vec3(V.xy * alpha, V.z)); - - // Sample a spherical cap in (-V.z, 1]. - float phi = 2.0 * M_PI * Xi.x; - float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; - float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); - float x = sinTheta * cos(phi); - float y = sinTheta * sin(phi); - vec3 c = vec3(x, y, z); - - // Compute the microfacet normal. - vec3 H = c + V; - - // Transform the microfacet normal back to the ellipsoid configuration. - H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); - - return H; -} - -// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf -// Equation 34 -float mx_ggx_smith_G1(float cosTheta, float alpha) -{ - float cosTheta2 = mx_square(cosTheta); - float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; - return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); -} - -// Height-correlated Smith masking-shadowing -// http://jcgt.org/published/0003/02/03/paper.pdf -// Equations 72 and 99 -float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) -{ - float alpha2 = mx_square(alpha); - float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); - float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); - return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); -} - -// Rational quadratic fit to Monte Carlo data for GGX directional albedo. -vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - float x = NdotV; - float y = alpha; - float x2 = mx_square(x); - float y2 = mx_square(y); - vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + - vec4(-0.6303, -2.323, -1.765, 0.2281) * x + - vec4(9.748, 2.229, 8.263, 15.94) * y + - vec4(-2.038, -3.748, 11.53, -55.83) * x * y + - vec4(29.34, 1.424, 28.96, 13.08) * x2 + - vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + - vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + - vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + - vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; - vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 1 - if (textureSize(u_albedoTable, 0).x > 1) - { - vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; - return F0 * AB.x + F90 * AB.y; - } -#endif - return vec3(0.0); -} - -// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf -vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ - NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); - vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); - - vec2 AB = vec2(0.0); - const int SAMPLE_COUNT = 64; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); - - // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); - vec3 L = -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); - - // Compute the Fresnel term. - float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); - - // Compute the per-sample geometric term. - // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 - float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); - - // Add the contribution of this sample. - AB += vec2(G2 * (1.0 - Fc), G2 * Fc); - } - - // Apply the global component of the geometric term and normalize. - AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); - - // Return the final directional albedo. - return F0 * AB.x + F90 * AB.y; -} - -vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) -{ -#if DIRECTIONAL_ALBEDO_METHOD == 0 - return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); -#elif DIRECTIONAL_ALBEDO_METHOD == 1 - return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); -#else - return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); -#endif -} - -float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) -{ - return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; -} - -// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf -// Equations 14 and 16 -vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) -{ - float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); - return 1.0 + Fss * (1.0 - Ess) / Ess; -} - -float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) -{ - return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; -} - -// Compute the average of an anisotropic alpha pair. -float mx_average_alpha(vec2 alpha) -{ - return sqrt(alpha.x * alpha.y); -} - -// Convert a real-valued index of refraction to normal-incidence reflectivity. -float mx_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 mx_f0_to_ior(float F0) -{ - float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (1.0 + sqrtF0) / (1.0 - sqrtF0); -} - -vec3 mx_f0_to_ior_colored(vec3 F0) -{ - vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); - return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); -} - -// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ -float mx_fresnel_dielectric(float cosTheta, float ior) -{ - if (cosTheta < 0.0) - return 1.0; - - float g = ior*ior + cosTheta*cosTheta - 1.0; - // Check for total internal reflection - if (g < 0.0) - return 1.0; - - g = sqrt(g); - float gmc = g - cosTheta; - float gpc = g + cosTheta; - float x = gmc / gpc; - float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); - return 0.5 * x * x * (1.0 + y * y); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) -{ - if (cosTheta < 0.0) { - Rp = 1.0; - Rs = 1.0; - return; - } - - float cosTheta2 = cosTheta * cosTheta; - float sinTheta2 = 1.0 - cosTheta2; - float n2 = n * n; - - float t0 = n2 - sinTheta2; - float a2plusb2 = sqrt(t0 * t0); - float t1 = a2plusb2 + cosTheta2; - float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); - float t2 = 2.0 * a * cosTheta; - Rs = (t1 - t2) / (t1 + t2); - - float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; - float t4 = t2 * sinTheta2; - Rp = Rs * (t3 - t4) / (t3 + t4); -} - -void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) -{ - float n = eta2 / eta1; - mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); -} - -void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) -{ - cosTheta = clamp(cosTheta, 0.0, 1.0); - float cosTheta2 = cosTheta * cosTheta; - 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); -} - -void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) -{ - vec3 n = eta2 / eta1; - vec3 k = kappa2 / eta1; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); -} - -vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) -{ - vec3 Rp, Rs; - mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); - return 0.5 * (Rp + Rs); -} - -// Phase shift due to a dielectric material -void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) -{ - float cosB = cos(atan(eta2 / eta1)); // Brewster's angle - if (eta2 > eta1) { - phiP = cosTheta < cosB ? M_PI : 0.0f; - phiS = 0.0f; - } else { - phiP = cosTheta < cosB ? 0.0f : M_PI; - phiS = M_PI; - } -} - -// Phase shift due to a conducting material -void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) -{ - if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { - // Use dielectric formula to increase performance - float phiPx, phiSx; - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); - phiP = vec3(phiPx, phiPx, phiPx); - phiS = vec3(phiSx, phiSx, phiSx); - return; - } - 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)); -} - -// Evaluation XYZ sensitivity curves in Fourier space -vec3 mx_eval_sensitivity(float opd, vec3 shift) -{ - // Use Gaussian fits, given by 3 parameters: val, pos and var - float phase = 2.0*M_PI * opd; - vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); - vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); - vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); - vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); - xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); - return xyz / 1.0685e-7; -} - -// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, - vec3 f0, vec3 f90, float exponent, bool use_schlick) -{ - // Convert nm -> m - float d = tf_thickness * 1.0e-9; - - // Assume vacuum on the outside - float eta1 = 1.0; - float eta2 = max(tf_ior, eta1); - vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; - vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; - - // Compute the Spectral versions of the Fresnel reflectance and - // transmitance for each interface. - float R12p, T121p, R12s, T121s; - vec3 R23p, R23s; - - // Reflected and transmitted parts in the thin film - mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); - - // Reflected part by the base - float scale = eta1 / eta2; - float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; - float cosTheta2 = sqrt(cosThetaTSqr); - if (use_schlick) - { - vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); - R23p = 0.5 * f; - R23s = 0.5 * f; - } - else - { - mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); - } - - // Check for total internal reflection - if (cosThetaTSqr <= 0.0f) - { - R12s = 1.0; - R12p = 1.0; - } - - // Compute the transmission coefficients - T121p = 1.0 - R12p; - T121s = 1.0 - R12s; - - // Optical path difference - float D = 2.0 * eta2 * d * cosTheta2; - - float phi21p, phi21s; - vec3 phi23p, phi23s, r123s, r123p; - - // Evaluate the phase shift - mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); - if (use_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 - { - mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); - } - - phi21p = M_PI - phi21p; - phi21s = M_PI - phi21s; - - r123p = max(vec3(0.0), sqrt(R12p*R23p)); - r123s = max(vec3(0.0), sqrt(R12s*R23s)); - - // Evaluate iridescence term - vec3 I = vec3(0.0); - vec3 C0, Cm, Sm; - - // Iridescence term using spectral antialiasing for Parallel polarization - - vec3 S0 = vec3(1.0); - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); - C0 = R12p + Rs; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rs - T121p; - for (int m=1; m<=2; ++m) - { - Cm *= r123p; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); - I += Cm*Sm; - } - - // Iridescence term using spectral antialiasing for Perpendicular polarization - - // Reflectance term for m=0 (DC term amplitude) - vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); - C0 = R12s + Rp; - I += C0 * S0; - - // Reflectance term for m>0 (pairs of diracs) - Cm = Rp - T121s ; - for (int m=1; m<=2; ++m) - { - Cm *= r123s; - Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); - I += Cm*Sm; - } - - // Average parallel and perpendicular polarization - I *= 0.5; - - // Convert back to RGB reflectance - I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); - - return I; -} - -FresnelData mx_init_fresnel_data(int model) -{ - return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); -} - -FresnelData mx_init_fresnel_dielectric(float ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); - fd.ior = vec3(ior); - return fd; -} - -FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); - fd.ior = ior; - fd.extinction = extinction; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = vec3(1.0); - fd.exponent = 5.0f; - return fd; -} - -FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - return fd; -} - -FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); - fd.F0 = F0; - fd.F90 = F90; - fd.exponent = exponent; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = vec3(ior); - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) -{ - FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); - fd.ior = ior; - fd.extinction = extinction; - fd.tf_thickness = tf_thickness; - fd.tf_ior = tf_ior; - return fd; -} - -vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) -{ - if (fd.model == FRESNEL_MODEL_DIELECTRIC) - { - return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); - } - else if (fd.model == FRESNEL_MODEL_CONDUCTOR) - { - return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); - } - else if (fd.model == FRESNEL_MODEL_SCHLICK) - { - return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); - } - else - { - return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, - fd.F0, fd.F90, fd.exponent, - fd.model == FRESNEL_MODEL_SCHLICK_AIRY); - } -} - -// Compute the refraction of a ray through a solid sphere. -vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) -{ - R = refract(R, N, 1.0 / ior); - vec3 N1 = normalize(R * dot(R, N) - N * 0.5); - return refract(R, N1, ior); -} - -vec2 mx_latlong_projection(vec3 dir) -{ - float latitude = -asin(dir.y) * M_PI_INV + 0.5; - float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; - return vec2(longitude, latitude); -} - -vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) -{ - vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); - vec2 uv = mx_latlong_projection(envDir); - return textureLod(envSampler, uv, lod).rgb; -} - -// Return the mip level with the appropriate coverage for a filtered importance sample. -// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html -// Section 20.4 Equation 13 -float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) -{ - const float MIP_LEVEL_OFFSET = 1.5; - float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; - float distortion = sqrt(1.0 - mx_square(dir.y)); - return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); -} - -// Return the mip level associated with the given alpha in a prefiltered environment. -float mx_latlong_alpha_to_lod(float alpha) -{ - float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; - return lodBias * float(u_envRadianceMips - 1); -} - -vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); - return Li * FG * u_envLightIntensity; -} - -vec3 mx_environment_irradiance(vec3 N) -{ - vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); - return Li * u_envLightIntensity; -} - - -vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) -{ - return tint; -} - -void mx_point_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = light.position - position; - float distance = length(result.direction) + M_FLOAT_EPS; - float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); - result.intensity = light.color * light.intensity / attenuation; - result.direction /= distance; -} - -void mx_directional_light(LightData light, vec3 position, out lightshader result) -{ - result.direction = -light.direction; - result.intensity = light.color * light.intensity; -} +#define M_PI 3.1415926535897932 +#define M_PI_INV (1.0 / M_PI) + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// Generalized Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0, float F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return mix(F0, F90, x5); +} + +// Generalized Schlick Fresnel with a variable exponent +float mx_fresnel_schlick(float cosTheta, float F0, float F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} +vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + return mix(F0, F90, pow(x, exponent)); +} + +// Enforce that the given normal is forward-facing from the specified view direction. +vec3 mx_forward_facing_normal(vec3 N, vec3 V) +{ + return (dot(N, V) < 0.0) ? -N : N; +} + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + const float GOLDEN_RATIO = 1.6180339887498948; + return fract((float(i) + 1.0) * GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} + +// Generate a uniform-weighted sample in the unit hemisphere. +vec3 mx_uniform_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = 1.0 - Xi.y; + float sinTheta = sqrt(1.0 - mx_square(cosTheta)); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Fresnel model options. +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; +const int FRESNEL_MODEL_AIRY = 3; +const int FRESNEL_MODEL_SCHLICK_AIRY = 4; + +// 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); + +// Parameters for Fresnel calculations. +struct FresnelData +{ + int model; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; + +#ifdef __METAL__ +FresnelData(int _model = 0, + vec3 _ior = vec3(0.0f), + vec3 _extinction = vec3(0.0f), + vec3 _F0 = vec3(0.0f), + vec3 _F90 = vec3(0.0f), + float _exponent = 0.0f, + float _tf_thickness = 0.0f, + float _tf_ior = 0.0f, + bool _refraction = false) : + model(_model), + ior(_ior), + extinction(_extinction), + F0(_F0), F90(_F90), exponent(_exponent), + tf_thickness(_tf_thickness), + tf_ior(_tf_ior), + refraction(_refraction) {} +#endif + +}; + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(vec3 H, vec2 alpha) +{ + vec2 He = H.xy / alpha; + float denom = dot(He, He) + mx_square(H.z); + return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); +} + +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html +vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) +{ + // Transform the view direction to the hemisphere configuration. + V = normalize(vec3(V.xy * alpha, V.z)); + + // Sample a spherical cap in (-V.z, 1]. + float phi = 2.0 * M_PI * Xi.x; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; + + // Transform the microfacet normal back to the ellipsoid configuration. + H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); + + return H; +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf +// Equation 34 +float mx_ggx_smith_G1(float cosTheta, float alpha) +{ + float cosTheta2 = mx_square(cosTheta); + float tanTheta2 = (1.0 - cosTheta2) / cosTheta2; + return 2.0 / (1.0 + sqrt(1.0 + mx_square(alpha) * tanTheta2)); +} + +// Height-correlated Smith masking-shadowing +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G2(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// Rational quadratic fit to Monte Carlo data for GGX directional albedo. +vec3 mx_ggx_dir_albedo_analytic(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + float x = NdotV; + float y = alpha; + float x2 = mx_square(x); + float y2 = mx_square(y); + vec4 r = vec4(0.1003, 0.9345, 1.0, 1.0) + + vec4(-0.6303, -2.323, -1.765, 0.2281) * x + + vec4(9.748, 2.229, 8.263, 15.94) * y + + vec4(-2.038, -3.748, 11.53, -55.83) * x * y + + vec4(29.34, 1.424, 28.96, 13.08) * x2 + + vec4(-8.245, -0.7684, -7.507, 41.26) * y2 + + vec4(-26.44, 1.436, -36.11, 54.9) * x2 * y + + vec4(19.99, 0.2913, 15.86, 300.2) * x * y2 + + vec4(-5.448, 0.6286, 33.37, -285.1) * x2 * y2; + vec2 AB = clamp(r.xy / r.zw, 0.0, 1.0); + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo_table_lookup(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 1 + if (textureSize(u_albedoTable, 0).x > 1) + { + vec2 AB = texture(u_albedoTable, vec2(NdotV, alpha)).rg; + return F0 * AB.x + F90 * AB.y; + } +#endif + return vec3(0.0); +} + +// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf +vec3 mx_ggx_dir_albedo_monte_carlo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - mx_square(NdotV)), 0, NdotV); + + vec2 AB = vec2(0.0); + const int SAMPLE_COUNT = 64; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, SAMPLE_COUNT); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, vec2(alpha)); + vec3 L = -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); + + // Compute the Fresnel term. + float Fc = mx_fresnel_schlick(VdotH, 0.0, 1.0); + + // Compute the per-sample geometric term. + // https://hal.inria.fr/hal-00996995v2/document, Algorithm 2 + float G2 = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Add the contribution of this sample. + AB += vec2(G2 * (1.0 - Fc), G2 * Fc); + } + + // Apply the global component of the geometric term and normalize. + AB /= mx_ggx_smith_G1(NdotV, alpha) * float(SAMPLE_COUNT); + + // Return the final directional albedo. + return F0 * AB.x + F90 * AB.y; +} + +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, vec3 F0, vec3 F90) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 0 + return mx_ggx_dir_albedo_analytic(NdotV, alpha, F0, F90); +#elif DIRECTIONAL_ALBEDO_METHOD == 1 + return mx_ggx_dir_albedo_table_lookup(NdotV, alpha, F0, F90); +#else + return mx_ggx_dir_albedo_monte_carlo(NdotV, alpha, F0, F90); +#endif +} + +float mx_ggx_dir_albedo(float NdotV, float alpha, float F0, float F90) +{ + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(F90)).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +vec3 mx_ggx_energy_compensation(float NdotV, float alpha, vec3 Fss) +{ + float Ess = mx_ggx_dir_albedo(NdotV, alpha, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float alpha, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, alpha, vec3(Fss)).x; +} + +// Compute the average of an anisotropic alpha pair. +float mx_average_alpha(vec2 alpha) +{ + return sqrt(alpha.x * alpha.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_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 mx_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} + +vec3 mx_f0_to_ior_colored(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + if (cosTheta < 0.0) + return 1.0; + + float g = ior*ior + cosTheta*cosTheta - 1.0; + // Check for total internal reflection + if (g < 0.0) + return 1.0; + + g = sqrt(g); + float gmc = g - cosTheta; + float gpc = g + cosTheta; + float x = gmc / gpc; + float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0); + return 0.5 * x * x * (1.0 + y * y); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs) +{ + if (cosTheta < 0.0) { + Rp = 1.0; + Rs = 1.0; + return; + } + + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.0 - cosTheta2; + float n2 = n * n; + + float t0 = n2 - sinTheta2; + float a2plusb2 = sqrt(t0 * t0); + float t1 = a2plusb2 + cosTheta2; + float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + float t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs) +{ + float n = eta2 / eta1; + mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs); +} + +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + cosTheta = clamp(cosTheta, 0.0, 1.0); + float cosTheta2 = cosTheta * cosTheta; + 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); +} + +void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs) +{ + vec3 n = eta2 / eta1; + vec3 k = kappa2 / eta1; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); +} + +vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// Phase shift due to a dielectric material +void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS) +{ + float cosB = cos(atan(eta2 / eta1)); // Brewster's angle + if (eta2 > eta1) { + phiP = cosTheta < cosB ? M_PI : 0.0f; + phiS = 0.0f; + } else { + phiP = cosTheta < cosB ? 0.0f : M_PI; + phiS = M_PI; + } +} + +// Phase shift due to a conducting material +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) { + // Use dielectric formula to increase performance + float phiPx, phiSx; + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx); + phiP = vec3(phiPx, phiPx, phiPx); + phiS = vec3(phiSx, phiSx, phiSx); + return; + } + 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)); +} + +// Evaluation XYZ sensitivity curves in Fourier space +vec3 mx_eval_sensitivity(float opd, vec3 shift) +{ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0*M_PI * opd; + vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0*M_PI * var) * cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(- 4.5282e+09 * phase*phase); + return xyz / 1.0685e-7; +} + +// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior, + vec3 f0, vec3 f90, float exponent, bool use_schlick) +{ + // Convert nm -> m + float d = tf_thickness * 1.0e-9; + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(tf_ior, eta1); + vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(f0) : ior; + vec3 kappa3 = use_schlick ? vec3(0.0) : extinction; + + // Compute the Spectral versions of the Fresnel reflectance and + // transmitance for each interface. + float R12p, T121p, R12s, T121s; + vec3 R23p, R23s; + + // Reflected and transmitted parts in the thin film + mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s); + + // Reflected part by the base + float scale = eta1 / eta2; + float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale; + float cosTheta2 = sqrt(cosThetaTSqr); + if (use_schlick) + { + vec3 f = mx_fresnel_schlick(cosTheta2, f0, f90, exponent); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s); + } + + // Check for total internal reflection + if (cosThetaTSqr <= 0.0f) + { + R12s = 1.0; + R12p = 1.0; + } + + // Compute the transmission coefficients + T121p = 1.0 - R12p; + T121s = 1.0 - R12s; + + // Optical path difference + float D = 2.0 * eta2 * d * cosTheta2; + + float phi21p, phi21s; + vec3 phi23p, phi23s, r123s, r123p; + + // Evaluate the phase shift + mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s); + if (use_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 + { + mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s); + } + + phi21p = M_PI - phi21p; + phi21s = M_PI - phi21s; + + r123p = max(vec3(0.0), sqrt(R12p*R23p)); + r123s = max(vec3(0.0), sqrt(R12s*R23s)); + + // Evaluate iridescence term + vec3 I = vec3(0.0); + vec3 C0, Cm, Sm; + + // Iridescence term using spectral antialiasing for Parallel polarization + + vec3 S0 = vec3(1.0); + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s); + C0 = R12s + Rp; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121s ; + for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, vec3(0.0), vec3(1.0)); + + return I; +} + +FresnelData mx_init_fresnel_data(int model) +{ + return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false); +} + +FresnelData mx_init_fresnel_dielectric(float ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_DIELECTRIC); + fd.ior = vec3(ior); + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_CONDUCTOR); + fd.ior = ior; + fd.extinction = extinction; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = vec3(1.0); + fd.exponent = 5.0f; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F90, float exponent) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + return fd; +} + +FresnelData mx_init_fresnel_schlick_airy(vec3 F0, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK_AIRY); + fd.F0 = F0; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_dielectric_airy(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = vec3(ior); + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +FresnelData mx_init_fresnel_conductor_airy(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_AIRY); + fd.ior = ior; + fd.extinction = extinction; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + return mx_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else if (fd.model == FRESNEL_MODEL_SCHLICK) + { + return mx_fresnel_schlick(cosTheta, fd.F0, fd.F90, fd.exponent); + } + else + { + return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior, + fd.F0, fd.F90, fd.exponent, + fd.model == FRESNEL_MODEL_SCHLICK_AIRY); + } +} + +// Compute the refraction of a ray through a solid sphere. +vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior) +{ + R = refract(R, N, 1.0 / ior); + vec3 N1 = normalize(R * dot(R, N) - N * 0.5); + return refract(R, N1, ior); +} + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(envSampler, uv, lod).rgb; +} + +// Return the mip level with the appropriate coverage for a filtered importance sample. +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(float(envSamples) * pdf * distortion), 0.0); +} + +// Return the mip level associated with the given alpha in a prefiltered environment. +float mx_latlong_alpha_to_lod(float alpha) +{ + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float(u_envRadianceMips - 1); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData 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 = mx_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, u_envMatrix, mx_latlong_alpha_to_lod(avgAlpha), u_envRadiance); + return Li * FG * u_envLightIntensity; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + vec3 Li = mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); + return Li * u_envLightIntensity; +} + + +vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd, vec3 tint) +{ + return tint; +} + +void mx_point_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = light.position - position; + float distance = length(result.direction) + M_FLOAT_EPS; + float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS); + result.intensity = light.color * light.intensity / attenuation; + result.direction /= distance; +} + +void mx_directional_light(LightData light, vec3 position, out lightshader result) +{ + result.direction = -light.direction; + result.intensity = light.color * light.intensity; +} int numActiveLightSources() { @@ -988,490 +988,490 @@ void sampleLightSource(LightData light, vec3 position, out lightshader result) } } -void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) -{ - float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); - if (anisotropy > 0.0) - { - float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); - result.x = min(roughness_sqr / aspect, 1.0); - result.y = roughness_sqr * aspect; - } - else - { - result.x = roughness_sqr; - result.y = roughness_sqr; - } -} - -void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) -{ - // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 - // http://jcgt.org/published/0003/04/03/paper.pdf - - vec3 r = clamp(reflectivity, 0.0, 0.99); - vec3 r_sqrt = sqrt(r); - vec3 n_min = (1.0 - r) / (1.0 + r); - vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); - ior = mix(n_max, n_min, edge_color); - - vec3 np1 = ior + 1.0; - vec3 nm1 = ior - 1.0; - vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); - k2 = max(k2, 0.0); - extinction = sqrt(k2); -} - -void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) -{ - result = color; -} - -void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) -{ - // Decode the normal map. - value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; - - // Transform from tangent space if needed. - if (map_space == 0) - { - vec3 B = normalize(cross(N, T)); - value.xy *= normal_scale; - value = T * value.x + B * value.y + N * value.z; - } - - // Normalize the result. - result = normalize(value); -} - -void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) -{ - mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); -} - - -void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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(mx_f0_to_ior(avgF0)); - bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; - } -} - -void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); - - FresnelData fd; - vec3 safeColor0 = max(color0, 0.0); - vec3 safeColor90 = max(color90, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); - } - vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * comp * weight; -} - - -// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn -// based on https://mimosa-pudica.net/improved-oren-nayar.html. -float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - float s = LdotV - NdotL * NdotV; - float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; - - float sigma2 = mx_square(roughness * M_PI); - float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); - float B = 0.45 * sigma2 / (sigma2 + 0.09); - - return A + B * stinv; -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Section 5.3 -float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) -{ - vec3 H = normalize(L + V); - float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); - float refL = mx_fresnel_schlick(NdotL, 1.0, F90); - float refV = mx_fresnel_schlick(NdotV, 1.0, F90); - return refL * refV; -} - -// Compute the directional albedo component of Burley diffuse for the given -// view angle and roughness. Curve fit provided by Stephen Hill. -float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) -{ - float x = NdotV; - float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); - float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; - return mix(fit0, fit1, roughness); -} - -// Evaluate the Burley diffusion profile for the given distance and diffusion shape. -// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ -vec3 mx_burley_diffusion_profile(float dist, vec3 shape) -{ - vec3 num1 = exp(-shape * dist); - vec3 num2 = exp(-shape * dist / 3.0); - float denom = max(dist, M_FLOAT_EPS); - return (num1 + num2) / denom; -} - -// Integrate the Burley diffusion profile over a sphere of the given radius. -// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ -vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) -{ - float theta = acos(dot(N, L)); - - // Estimate the Burley diffusion shape from mean free path. - vec3 shape = vec3(1.0) / max(mfp, 0.1); - - // Integrate the profile over the sphere. - vec3 sumD = vec3(0.0); - vec3 sumR = vec3(0.0); - const int SAMPLE_COUNT = 32; - const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); - for (int i = 0; i < SAMPLE_COUNT; i++) - { - float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; - float dist = radius * abs(2.0 * sin(x * 0.5)); - vec3 R = mx_burley_diffusion_profile(dist, shape); - sumD += R * max(cos(theta + x), 0.0); - sumR += R; - } - - return sumD / sumR; -} - -vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) -{ - float curvature = length(fwidth(N)) / length(fwidth(P)); - float radius = 1.0 / max(curvature, 0.01); - return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); -} - -void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); - - bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; - if (roughness > 0.0) - { - bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); - } -} - -void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - normal = mx_forward_facing_normal(normal, V); - - vec3 Li = mx_environment_irradiance(normal); - bsdf.response = Li * color * weight; -} - - -void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(VdotH, fd); - float D = mx_ggx_NDF(Ht, safeAlpha); - float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); - - float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; - } -} - -void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); - - FresnelData fd; - vec3 safeTint = max(tint, 0.0); - if (bsdf.thickness > 0.0) - { - fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); - } - else - { - fd = mx_init_fresnel_dielectric(ior); - } - vec3 F = mx_compute_fresnel(NdotV, fd); - - vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); - float avgAlpha = mx_average_alpha(safeAlpha); - - float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - bsdf.response = Li * safeTint * comp * weight; -} - - -void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - 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)); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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); - - // Note: NdotL is cancelled out - bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); -} - -void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) -{ - bsdf.throughput = vec3(0.0); - - if (weight < M_FLOAT_EPS) - { - return; - } - - N = mx_forward_facing_normal(N, V); - - float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); - - FresnelData fd; - if (bsdf.thickness > 0.0) - fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); - else - fd = mx_init_fresnel_conductor(ior_n, ior_k); - - vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); - - bsdf.response = Li * comp * weight; -} +void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) +{ + float roughness_sqr = clamp(roughness*roughness, M_FLOAT_EPS, 1.0); + if (anisotropy > 0.0) + { + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + result.x = roughness_sqr; + result.y = roughness_sqr; + } +} + +void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 extinction) +{ + // "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014 + // http://jcgt.org/published/0003/04/03/paper.pdf + + vec3 r = clamp(reflectivity, 0.0, 0.99); + vec3 r_sqrt = sqrt(r); + vec3 n_min = (1.0 - r) / (1.0 + r); + vec3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = mix(n_max, n_min, edge_color); + + vec3 np1 = ior + 1.0; + vec3 nm1 = ior - 1.0; + vec3 k2 = (np1*np1 * r - nm1*nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +void mx_uniform_edf(vec3 N, vec3 L, vec3 color, out EDF result) +{ + result = color; +} + +void mx_normalmap_vector2(vec3 value, int map_space, vec2 normal_scale, vec3 N, vec3 T, out vec3 result) +{ + // Decode the normal map. + value = (value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + + // Transform from tangent space if needed. + if (map_space == 0) + { + vec3 B = normalize(cross(N, T)); + value.xy *= normal_scale; + value = T * value.x + B * value.y + N * value.z; + } + + // Normalize the result. + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result) +{ + mx_normalmap_vector2(value, map_space, vec2(normal_scale), N, T, result); +} + + +void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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(mx_f0_to_ior(avgF0)); + bsdf.response = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; + } +} + +void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, 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); + + FresnelData fd; + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor90 = max(color90, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_schlick_airy(safeColor0, safeColor90, exponent, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_schlick(safeColor0, safeColor90, exponent); + } + vec3 F = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * comp * weight; +} + + +// Based on the OSL implementation of Oren-Nayar diffuse, which is in turn +// based on https://mimosa-pudica.net/improved-oren-nayar.html. +float mx_oren_nayar_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0f) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(vec3 L, vec3 V, vec3 N, float NdotL, float roughness) +{ + vec3 H = normalize(L + V); + float LdotH = clamp(dot(L, H), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float F90 = 0.5 + (2.0 * roughness * mx_square(LdotH)); + float refL = mx_fresnel_schlick(NdotL, 1.0, F90); + float refV = mx_fresnel_schlick(NdotV, 1.0, F90); + return refL * refV; +} + +// Compute the directional albedo component of Burley diffuse for the given +// view angle and roughness. Curve fit provided by Stephen Hill. +float mx_burley_diffuse_dir_albedo(float NdotV, float roughness) +{ + float x = NdotV; + float fit0 = 0.97619 - 0.488095 * mx_pow5(1.0 - x); + float fit1 = 1.55754 + (-2.02221 + (2.56283 - 1.06244 * x) * x) * x; + return mix(fit0, fit1, roughness); +} + +// Evaluate the Burley diffusion profile for the given distance and diffusion shape. +// Based on https://graphics.pixar.com/library/ApproxBSSRDF/ +vec3 mx_burley_diffusion_profile(float dist, vec3 shape) +{ + vec3 num1 = exp(-shape * dist); + vec3 num2 = exp(-shape * dist / 3.0); + float denom = max(dist, M_FLOAT_EPS); + return (num1 + num2) / denom; +} + +// Integrate the Burley diffusion profile over a sphere of the given radius. +// Inspired by Eric Penner's presentation in http://advances.realtimerendering.com/s2011/ +vec3 mx_integrate_burley_diffusion(vec3 N, vec3 L, float radius, vec3 mfp) +{ + float theta = acos(dot(N, L)); + + // Estimate the Burley diffusion shape from mean free path. + vec3 shape = vec3(1.0) / max(mfp, 0.1); + + // Integrate the profile over the sphere. + vec3 sumD = vec3(0.0); + vec3 sumR = vec3(0.0); + const int SAMPLE_COUNT = 32; + const float SAMPLE_WIDTH = (2.0 * M_PI) / float(SAMPLE_COUNT); + for (int i = 0; i < SAMPLE_COUNT; i++) + { + float x = -M_PI + (float(i) + 0.5) * SAMPLE_WIDTH; + float dist = radius * abs(2.0 * sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(cos(theta + x), 0.0); + sumR += R; + } + + return sumD / sumR; +} + +vec3 mx_subsurface_scattering_approx(vec3 N, vec3 L, vec3 P, vec3 albedo, vec3 mfp) +{ + float curvature = length(fwidth(N)) / length(fwidth(P)); + float radius = 1.0 / max(curvature, 0.01); + return albedo * mx_integrate_burley_diffusion(N, L, radius, mfp) / vec3(M_PI); +} + +void mx_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + + bsdf.response = color * occlusion * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + bsdf.response *= mx_oren_nayar_diffuse(L, V, normal, NdotL, roughness); + } +} + +void mx_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * color * weight; +} + + +void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, 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)); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx_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 mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, 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); + + FresnelData fd; + vec3 safeTint = max(tint, 0.0); + if (bsdf.thickness > 0.0) + { + fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior); + } + else + { + fd = mx_init_fresnel_dielectric(ior); + } + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx_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 = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} + + +void mx_conductor_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + 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)); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx_conductor_bsdf_indirect(vec3 V, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd; + if (bsdf.thickness > 0.0) + fd = mx_init_fresnel_conductor_airy(ior_n, ior_k, bsdf.thickness, bsdf.ior); + else + fd = mx_init_fresnel_conductor(ior_n, ior_k); + + vec3 F = mx_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 Li = mx_environment_radiance(N, V, X, safeAlpha, distribution, fd); + + bsdf.response = Li * comp * weight; +} void IMP_UsdPreviewSurface_surfaceshader(vec3 diffuseColor, vec3 emissiveColor, int useSpecularWorkflow, vec3 specularColor, float metallic, float roughness, float clearcoat, float clearcoatRoughness, float opacity, float opacityThreshold, float ior, vec3 normal, float displacement, float occlusion, out surfaceshader out1) { diff --git a/pxr/imaging/hdSt/testenv/testHdStPrimvars.cpp b/pxr/imaging/hdSt/testenv/testHdStPrimvars.cpp index 9e3fb4c75e..fc4a757819 100644 --- a/pxr/imaging/hdSt/testenv/testHdStPrimvars.cpp +++ b/pxr/imaging/hdSt/testenv/testHdStPrimvars.cpp @@ -40,15 +40,26 @@ PrintPerfCounter(HdPerfLog &perfLog, TfToken const &token) static void Dump(std::string const &message, VtDictionary dict, HdPerfLog &perfLog) { - // Get the keys in sorted order. This ensures consistent reporting + // These vary between platforms and runs, we don't want them in the diff. + static const std::unordered_set skippedKeys = { + HdTokens->drawingShader, + HdTokens->computeShader, + HdPerfTokens->gpuMemoryUsed, + HdPerfTokens->uboSize, + HdPerfTokens->ssboSize, + }; + + // Get the keys in sorted order. This ensures consistent reporting // regardless of the sort order of dict. std::set keys; - for (auto v: dict) { - keys.insert(v.first); + for (const auto& [key, _] : dict) { + if (!skippedKeys.count(key)) { + keys.insert(key); + } } std::cout << message; - for (auto key: keys) { + for (const auto& key: keys) { std::cout << key << ", "; const VtValue& value = dict[key]; if (value.IsHolding()) { @@ -231,4 +242,3 @@ int main() return EXIT_FAILURE; } } - diff --git a/pxr/imaging/hdSt/testenv/testHdStPrimvars/baseline/testHdStPrimvars-Run1-stdout.txt b/pxr/imaging/hdSt/testenv/testHdStPrimvars/baseline/testHdStPrimvars-Run1-stdout.txt index e278d100d7..885942395b 100644 --- a/pxr/imaging/hdSt/testenv/testHdStPrimvars/baseline/testHdStPrimvars-Run1-stdout.txt +++ b/pxr/imaging/hdSt/testenv/testHdStPrimvars/baseline/testHdStPrimvars-Run1-stdout.txt @@ -1,33 +1,25 @@ ==== PrimvarsTest: ----- begin ----- -gpuMemoryUsed, 0 nonUniformSize, 0 numberOfTextureHandles, 0 numberOfTextureObjects, 0 singleBufferSize, 0 -ssboSize, 0 textureMemory, 0 -uboSize, 0 garbageCollected = 0 drawCalls = 0 rebuildBatches = 0 bufferArrayRangeMigrated = 0 ----- draw flat ----- -computeShader, 14551 drawIndirect, 240 drawIndirectCull, 240 -drawingShader, 41374 -gpuMemoryUsed, 844373 nonUniformSize, 768 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1056 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 480 -uboSize, 512 garbageCollected = 0 drawCalls = 1 drawBatches = 1 @@ -39,17 +31,13 @@ bufferArrayRangeMigrated = 0 ----- draw flat : primvars added ----- drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 123273 -gpuMemoryUsed, 912441 nonUniformSize, 992 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1296 singleBufferSize, 0 -ssboSize, 784 textureMemory, 786432 topology, 480 -uboSize, 512 garbageCollected = 1 drawCalls = 3 drawBatches = 3 @@ -61,17 +49,13 @@ bufferArrayRangeMigrated = 3 ----- draw smooth ----- drawIndirect, 480 drawIndirectCull, 480 -drawingShader, 245583 -gpuMemoryUsed, 1035071 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1360 singleBufferSize, 0 -ssboSize, 784 textureMemory, 786432 topology, 736 -uboSize, 512 garbageCollected = 2 drawCalls = 4 drawBatches = 4 @@ -83,17 +67,13 @@ bufferArrayRangeMigrated = 5 ----- draw smooth : primvar resized ----- drawIndirect, 540 drawIndirectCull, 540 -drawingShader, 282570 -gpuMemoryUsed, 1072194 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1376 singleBufferSize, 0 -ssboSize, 800 textureMemory, 786432 topology, 736 -uboSize, 512 garbageCollected = 3 drawCalls = 4 drawBatches = 4 @@ -105,17 +85,13 @@ bufferArrayRangeMigrated = 6 ----- draw smooth : primvar removed ----- drawIndirect, 720 drawIndirectCull, 720 -drawingShader, 245589 -gpuMemoryUsed, 1035541 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1344 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 736 -uboSize, 512 garbageCollected = 4 drawCalls = 4 drawBatches = 4 @@ -127,17 +103,13 @@ bufferArrayRangeMigrated = 7 ----- draw smooth : facevarying primvar added ----- drawIndirect, 540 drawIndirectCull, 540 -drawingShader, 246038 -gpuMemoryUsed, 1036062 nonUniformSize, 1744 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1776 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 736 -uboSize, 512 garbageCollected = 5 drawCalls = 4 drawBatches = 4 @@ -149,17 +121,13 @@ bufferArrayRangeMigrated = 8 ----- draw smooth : facevarying primvar removed ----- drawIndirect, 540 drawIndirectCull, 540 -drawingShader, 246038 -gpuMemoryUsed, 1035630 nonUniformSize, 1312 numberOfTextureHandles, 0 numberOfTextureObjects, 2 primvar, 1344 singleBufferSize, 0 -ssboSize, 768 textureMemory, 786432 topology, 736 -uboSize, 512 garbageCollected = 6 drawCalls = 4 drawBatches = 4