From c4395143e73ee2b13fbb06911c061d582ee402d6 Mon Sep 17 00:00:00 2001 From: Pavlo Penenko Date: Tue, 25 Aug 2026 15:43:26 -0400 Subject: [PATCH] Enable all Adsk materials in Metashade Standard Surface tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the Metashade submodule to include: - GLSL float intrinsics: sin, cos, radians (metashade/metashade#231) - Inlined private rotate helper to avoid duplicate definitions when materials use rotate3d nodes (metashade/metashade#232) Remove all entries from _ADSK_METASHADE_EXCLUDE — every Adsk and ASWF material now compiles and renders successfully through the Metashade Standard Surface codegen path. Regenerate all render reference shaders (63 modified, 22 new). Signed-off-by: Pavlo Penenko --- contrib/metashade | 2 +- ...de_standard_surface_bsdf_genglsl_impl.glsl | 22 +- .../Fusion/glazing/Glazing_test_ps.glsl | 34 +- .../Fusion/heightmap/Gold_pass_ps.glsl | 34 +- .../Fusion/layered/Granite_Blue_Pearl_ps.glsl | 34 +- .../layered/Paint_Metallic_Green_ps.glsl | 34 +- .../Fusion/layered/Paint_Metallic_Red_ps.glsl | 34 +- .../Fusion/metal/Aluminum_Cast_ps.glsl | 2002 +++++++++ .../Fusion/metal/Aluminum_Cast_vs.glsl | 80 + .../metal/Aluminum_Diamond_Plate_ps.glsl | 1933 +++++++++ .../metal/Aluminum_Diamond_Plate_vs.glsl | 80 + .../Fusion/metal/Anodized_Blue_Glossy_ps.glsl | 1815 +++++++++ .../Fusion/metal/Anodized_Blue_Glossy_vs.glsl | 77 + .../Fusion/metal/Anodized_Red_Rough_ps.glsl | 1815 +++++++++ .../Fusion/metal/Anodized_Red_Rough_vs.glsl | 77 + .../Fusion/metal/Brass_Wire_Mesh_ps.glsl | 1990 +++++++++ .../Fusion/metal/Brass_Wire_Mesh_vs.glsl | 80 + .../Fusion/metal/Copper_Polished_ps.glsl | 1815 +++++++++ .../Fusion/metal/Copper_Polished_vs.glsl | 77 + .../Examples/Fusion/normalmap/Gold_ps.glsl | 34 +- .../opaque/Plastic_Glossy_White_ps.glsl | 34 +- .../Fusion/opaque/Walnut_Semigloss_ps.glsl | 34 +- .../Examples/Fusion/opaque/Walnut_ps.glsl | 34 +- .../transparent/Gemstone_Diamond_ps.glsl | 34 +- .../Fusion/transparent/Gemstone_Ruby_ps.glsl | 34 +- .../Revit/ceramic/M_legacy_ceramic_ps.glsl | 34 +- .../Revit/checker/M_legacy_checker_ps.glsl | 34 +- .../Revit/concrete/M_legacy_concrete_ps.glsl | 34 +- .../Revit/generic/M_legacy_generic_ps.glsl | 34 +- .../Revit/glass/M_legacy_glass_ps.glsl | 34 +- .../Revit/glazing/M_legacy_glazing_ps.glsl | 34 +- .../Revit/gradient/M_legacy_gradient_ps.glsl | 34 +- .../Revit/hardwood/M_legacy_hardwood_ps.glsl | 34 +- .../Revit/knurl/M_legacy_knurl_ps.glsl | 1981 +++++++++ .../Revit/knurl/M_legacy_knurl_vs.glsl | 82 + .../Revit/marble/M_legacy_marble_ps.glsl | 2773 +++++++++++++ .../Revit/marble/M_legacy_marble_vs.glsl | 79 + .../Revit/masonry/M_legacy_masonry_ps.glsl | 34 +- .../Revit/metal/M_legacy_metal_ps.glsl | 3600 +++++++++++++++++ .../Revit/metal/M_legacy_metal_vs.glsl | 84 + .../M_legacy_metallicpaint_ps.glsl | 2803 +++++++++++++ .../M_legacy_metallicpaint_vs.glsl | 81 + .../Revit/mirror/M_legacy_mirror_ps.glsl | 34 +- .../Revit/noise/M_legacy_noise_ps.glsl | 2949 ++++++++++++++ .../Revit/noise/M_legacy_noise_vs.glsl | 79 + .../Revit/plastic/M_legacy_plastic_ps.glsl | 34 +- .../Revit/speckle/M_legacy_speckle_ps.glsl | 2776 +++++++++++++ .../Revit/speckle/M_legacy_speckle_vs.glsl | 79 + .../Revit/stone/M_legacy_stone_ps.glsl | 34 +- .../Revit/tiles/M_legacy_tiles_ps.glsl | 34 +- .../wallpaint/M_legacy_wallpaint_ps.glsl | 34 +- .../Revit/water/M_legacy_water_ps.glsl | 34 +- .../Revit/waves/M_legacy_waves_ps.glsl | 2851 +++++++++++++ .../Revit/waves/M_legacy_waves_vs.glsl | 77 + .../Examples/Revit/wood/M_legacy_wood_ps.glsl | 2733 +++++++++++++ .../Examples/Revit/wood/M_legacy_wood_vs.glsl | 79 + .../Tiled_Brass_ps.glsl | 34 +- .../M_BrickPattern_ps.glsl | 34 +- .../Car_Paint_ps.glsl | 34 +- .../M_Bishop_B_ps.glsl | 34 +- .../M_Bishop_W_ps.glsl | 34 +- .../M_Castle_B_ps.glsl | 34 +- .../M_Castle_W_ps.glsl | 34 +- .../M_Chessboard_ps.glsl | 34 +- .../M_King_B_ps.glsl | 34 +- .../M_King_W_ps.glsl | 34 +- .../M_Knight_B_ps.glsl | 34 +- .../M_Knight_W_ps.glsl | 34 +- .../M_Pawn_Body_B_ps.glsl | 34 +- .../M_Pawn_Body_W_ps.glsl | 34 +- .../M_Pawn_Top_B_ps.glsl | 34 +- .../M_Pawn_Top_W_ps.glsl | 34 +- .../M_Queen_B_ps.glsl | 34 +- .../M_Queen_W_ps.glsl | 34 +- .../standard_surface_chrome/Chrome_ps.glsl | 34 +- .../standard_surface_copper/Copper_ps.glsl | 34 +- .../standard_surface_default/Default_ps.glsl | 34 +- .../aswf/standard_surface_glass/Glass_ps.glsl | 34 +- .../GlassTinted_ps.glsl | 34 +- .../aswf/standard_surface_gold/Gold_ps.glsl | 34 +- .../Greysphere_ps.glsl | 34 +- .../Greysphere_Calibration_ps.glsl | 34 +- .../aswf/standard_surface_jade/Jade_ps.glsl | 34 +- .../Greysphere_Calibration_ps.glsl | 34 +- .../Tiled_Brass_ps.glsl | 34 +- .../Greysphere_Calibration_ps.glsl | 34 +- .../Tiled_Wood_ps.glsl | 34 +- .../Marble_3D_ps.glsl | 34 +- .../Metal_Brushed_ps.glsl | 34 +- .../M_OnyxHextiled_ps.glsl | 34 +- .../standard_surface_plastic/Plastic_ps.glsl | 34 +- .../ThinFilm_ps.glsl | 34 +- .../standard_surface_velvet/Velvet_ps.glsl | 34 +- .../Tiled_Wood_ps.glsl | 34 +- contrib/tests/test_render_metashade.py | 24 +- 95 files changed, 36185 insertions(+), 986 deletions(-) create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_vs.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_ps.glsl create mode 100644 contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_vs.glsl diff --git a/contrib/metashade b/contrib/metashade index 0ce571b68d..e1f1fe608d 160000 --- a/contrib/metashade +++ b/contrib/metashade @@ -1 +1 @@ -Subproject commit 0ce571b68d63f2feed3297f842be1502027bfdd6 +Subproject commit e1f1fe608dc370a3ea6a05bec3fa93c41abc16cd diff --git a/contrib/tests/metashade_ref/libraries/standard_surface/mx_metashade_standard_surface_bsdf_genglsl_impl.glsl b/contrib/tests/metashade_ref/libraries/standard_surface/mx_metashade_standard_surface_bsdf_genglsl_impl.glsl index f24b9931c1..2927e0e9dc 100644 --- a/contrib/tests/metashade_ref/libraries/standard_surface/mx_metashade_standard_surface_bsdf_genglsl_impl.glsl +++ b/contrib/tests/metashade_ref/libraries/standard_surface/mx_metashade_standard_surface_bsdf_genglsl_impl.glsl @@ -1,5 +1,4 @@ #include "mx_roughness_anisotropy.glsl" -#include "mx_rotate_vector3.glsl" #include "mx_oren_nayar_diffuse_bsdf.glsl" #include "mx_translucent_bsdf.glsl" #include "mx_subsurface_bsdf.glsl" @@ -7,6 +6,23 @@ #include "mx_dielectric_bsdf.glsl" #include "mx_conductor_bsdf.glsl" #include "mx_artistic_ior.glsl" +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -24,7 +40,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -34,7 +50,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/glazing/Glazing_test_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/glazing/Glazing_test_ps.glsl index aea869522b..916761b46a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/glazing/Glazing_test_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/glazing/Glazing_test_ps.glsl @@ -867,19 +867,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1581,6 +1568,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1598,7 +1602,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1608,7 +1612,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/heightmap/Gold_pass_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/heightmap/Gold_pass_ps.glsl index e457154046..a3d9e50945 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/heightmap/Gold_pass_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/heightmap/Gold_pass_ps.glsl @@ -1025,19 +1025,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1739,6 +1726,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1756,7 +1760,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1766,7 +1770,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Granite_Blue_Pearl_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Granite_Blue_Pearl_ps.glsl index 47a1355c43..8e9a8a8005 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Granite_Blue_Pearl_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Granite_Blue_Pearl_ps.glsl @@ -1065,19 +1065,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1779,6 +1766,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1796,7 +1800,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1806,7 +1810,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Green_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Green_ps.glsl index 90a1886257..6ecad2571c 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Green_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Green_ps.glsl @@ -864,19 +864,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1578,6 +1565,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1595,7 +1599,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1605,7 +1609,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Red_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Red_ps.glsl index 15ba6602d2..24406a98f9 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Red_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/layered/Paint_Metallic_Red_ps.glsl @@ -864,19 +864,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1578,6 +1565,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1595,7 +1599,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1605,7 +1609,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_ps.glsl new file mode 100644 index 0000000000..e3f23c8a24 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_ps.glsl @@ -0,0 +1,2002 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform int geomprop_UV0_index = 0; +uniform vec2 heightnormal_texturemap_realworld_offset_unit_in1 = vec2(0.000000, 0.000000); +uniform float heightnormal_texturemap_realworld_offset_unit_in2 = 2.540000; +uniform vec2 heightnormal_texturemap_realworld_scale_unit_in1 = vec2(30.000000, 30.000000); +uniform float heightnormal_texturemap_realworld_scale_unit_in2 = 2.540000; +uniform vec2 surfaceroughness_texturemap_realworld_offset_unit_in1 = vec2(0.000000, 0.000000); +uniform float surfaceroughness_texturemap_realworld_offset_unit_in2 = 2.540000; +uniform vec2 surfaceroughness_texturemap_realworld_scale_unit_in1 = vec2(30.000000, 30.000000); +uniform float surfaceroughness_texturemap_realworld_scale_unit_in2 = 2.540000; +uniform sampler2D heightnormal_texturemap_file; +uniform vec2 heightnormal_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 heightnormal_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float heightnormal_texturemap_rotation_angle = 0.000000; +uniform float heightnormal_texturemap_depth = 0.075000; +uniform int heightnormal_texturemap_uaddressmode = 2; +uniform int heightnormal_texturemap_vaddressmode = 2; +uniform sampler2D surfaceroughness_texturemap_file; +uniform vec2 surfaceroughness_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 surfaceroughness_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float surfaceroughness_texturemap_rotation_angle = 0.000000; +uniform float surfaceroughness_texturemap_rgbamount = 1.000000; +uniform bool surfaceroughness_texturemap_invert = false; +uniform int surfaceroughness_texturemap_uaddressmode = 2; +uniform int surfaceroughness_texturemap_vaddressmode = 2; +uniform vec3 shaderref4_metal_f0 = vec3(0.959822, 0.962260, 0.965052); +uniform vec3 shaderref4_surface_albedo = vec3(1.000000, 1.000000, 1.000000); +uniform float shaderref4_surface_anisotropy = 0.000000; +uniform float shaderref4_surface_rotation = 0.000000; +uniform vec3 shaderref4_surface_cutout = vec3(1.000000, 1.000000, 1.000000); + +in VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void mx_rotate_vector2(vec2 _in, float amount, out vec2 result) +{ + float rotationRadians = mx_radians(amount); + float sa = mx_sin(rotationRadians); + float ca = mx_cos(rotationRadians); + result = vec2(ca*_in.x + sa*_in.y, -sa*_in.x + ca*_in.y); +} + +void NG_switch_vector2I(vec2 in1, vec2 in2, vec2 in3, vec2 in4, vec2 in5, vec2 in6, vec2 in7, vec2 in8, vec2 in9, vec2 in10, int which, out vec2 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec2 ifgreater_10_in2_tmp = vec2(0.000000, 0.000000); + vec2 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec2 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec2 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec2 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec2 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec2 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec2 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec2 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec2 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec2 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_place2d_vector2(vec2 texcoord, vec2 pivot, vec2 scale, float rotate, vec2 offset, int operationorder, out vec2 out1) +{ + vec2 N_subpivot_out = texcoord - pivot; + vec2 N_applyscale_out = N_subpivot_out / scale; + vec2 N_applyoffset2_out = N_subpivot_out - offset; + vec2 N_applyrot_out = vec2(0.0); + mx_rotate_vector2(N_applyscale_out, rotate, N_applyrot_out); + vec2 N_applyrot2_out = vec2(0.0); + mx_rotate_vector2(N_applyoffset2_out, rotate, N_applyrot2_out); + vec2 N_applyoffset_out = N_applyrot_out - offset; + vec2 N_applyscale2_out = N_applyrot2_out / scale; + vec2 N_addpivot_out = N_applyoffset_out + pivot; + vec2 N_addpivot2_out = N_applyscale2_out + pivot; + vec2 N_switch_operationorder_out = vec2(0.0); + NG_switch_vector2I(N_addpivot_out, N_addpivot2_out, vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), operationorder, N_switch_operationorder_out); + out1 = N_switch_operationorder_out; +} + +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return vec2(uv.x, 1.0 - uv.y); +} + +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_heighttonormal_vector3(float height, float scale, vec2 texcoord, out vec3 result) +{ + // Scale factor for parity with traditional Sobel filtering. + const float SOBEL_SCALE_FACTOR = 1.0 / 16.0; + + // Compute screen-space gradients of the heightfield and texture coordinates. + vec2 dHdS = vec2(dFdx(height), dFdy(height)) * scale * SOBEL_SCALE_FACTOR; + vec2 dUdS = vec2(dFdx(texcoord.x), dFdy(texcoord.x)); + vec2 dVdS = vec2(dFdx(texcoord.y), dFdy(texcoord.y)); + + // Construct a screen-space tangent frame. + vec3 tangent = vec3(dUdS.x, dVdS.x, dHdS.x); + vec3 bitangent = vec3(dUdS.y, dVdS.y, dHdS.y); + vec3 n = cross(tangent, bitangent); + + // Handle invalid and mirrored texture coordinates. + if (dot(n, n) < M_FLOAT_EPS * M_FLOAT_EPS) + { + n = vec3(0, 0, 1); + } + else if (n.z < 0.0) + { + n *= -1.0; + } + + // Normalize and encode the results. + result = normalize(n) * 0.5 + 0.5; +} + +void mx_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, result); +} + +void adsk_NG_adsk_height_map(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float depth, int uaddressmode, int vaddressmode, vec2 texcoord, vec3 normal, vec3 tangent, out vec3 out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec3 normalmap_cross_out = cross(normal, tangent); + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 normalmap_cross_norm_out = normalize(normalmap_cross_out); + float b_image_out = 0.0; + mx_image_float(file, 0, 0.000000, a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + vec3 impl_heighttonormalmap_out = vec3(0.0); + mx_heighttonormal_vector3(b_image_out, 1.000000, a_place2d_out, impl_heighttonormalmap_out); + vec3 impl_normalmap_out = vec3(0.0); + mx_normalmap_float(impl_heighttonormalmap_out, depth, normal, tangent, normalmap_cross_norm_out, impl_normalmap_out); + out1 = impl_normalmap_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 adsk_NG_adsk_bitmap_float(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float rgbamount, bool invert, int uaddressmode, int vaddressmode, vec2 texcoord, out float out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 b_image_out = vec3(0.0); + mx_image_color3(file, 0, vec3(0.000000, 0.000000, 0.000000), a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + vec3 image_brightness_out = b_image_out * rgbamount; + const vec3 image_invert_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 image_invert_out = image_invert_amount_tmp - image_brightness_out; + const bool image_convert_value2_tmp = true; + vec3 image_convert_out = (invert == image_convert_value2_tmp) ? image_invert_out : image_brightness_out; + const int extract1_index_tmp = 0; + float extract1_out = image_convert_out[extract1_index_tmp]; + out1 = extract1_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec2 heightnormal_texturemap_realworld_offset_unit_out = heightnormal_texturemap_realworld_offset_unit_in1 * heightnormal_texturemap_realworld_offset_unit_in2; + vec2 heightnormal_texturemap_realworld_scale_unit_out = heightnormal_texturemap_realworld_scale_unit_in1 * heightnormal_texturemap_realworld_scale_unit_in2; + vec2 surfaceroughness_texturemap_realworld_offset_unit_out = surfaceroughness_texturemap_realworld_offset_unit_in1 * surfaceroughness_texturemap_realworld_offset_unit_in2; + vec2 surfaceroughness_texturemap_realworld_scale_unit_out = surfaceroughness_texturemap_realworld_scale_unit_in1 * surfaceroughness_texturemap_realworld_scale_unit_in2; + vec3 heightnormal_texturemap_out = vec3(0.0); + adsk_NG_adsk_height_map(heightnormal_texturemap_file, heightnormal_texturemap_realworld_offset_unit_out, heightnormal_texturemap_realworld_scale_unit_out, heightnormal_texturemap_uv_offset, heightnormal_texturemap_uv_scale, heightnormal_texturemap_rotation_angle, heightnormal_texturemap_depth, heightnormal_texturemap_uaddressmode, heightnormal_texturemap_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, heightnormal_texturemap_out); + float surfaceroughness_texturemap_out = 0.0; + adsk_NG_adsk_bitmap_float(surfaceroughness_texturemap_file, surfaceroughness_texturemap_realworld_offset_unit_out, surfaceroughness_texturemap_realworld_scale_unit_out, surfaceroughness_texturemap_uv_offset, surfaceroughness_texturemap_uv_scale, surfaceroughness_texturemap_rotation_angle, surfaceroughness_texturemap_rgbamount, surfaceroughness_texturemap_invert, surfaceroughness_texturemap_uaddressmode, surfaceroughness_texturemap_vaddressmode, geomprop_UV0_out1, surfaceroughness_texturemap_out); + surfaceshader shaderref4_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref4_metal_f0, shaderref4_surface_albedo, surfaceroughness_texturemap_out, shaderref4_surface_anisotropy, shaderref4_surface_rotation, heightnormal_texturemap_out, shaderref4_surface_cutout, shaderref4_out); + material Aluminum_Cast_out = shaderref4_out; + out1 = vec4(Aluminum_Cast_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_vs.glsl new file mode 100644 index 0000000000..4334509193 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Cast_vs.glsl @@ -0,0 +1,80 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec2 i_texcoord_0; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.texcoord_0 = i_texcoord_0; + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_ps.glsl new file mode 100644 index 0000000000..d47a833180 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_ps.glsl @@ -0,0 +1,1933 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform int geomprop_UV0_index = 0; +uniform vec2 normal_texturemap_realworld_offset_unit_in1 = vec2(0.000000, 0.000000); +uniform float normal_texturemap_realworld_offset_unit_in2 = 2.540000; +uniform vec2 normal_texturemap_realworld_scale_unit_in1 = vec2(30.000000, 30.000000); +uniform float normal_texturemap_realworld_scale_unit_in2 = 2.540000; +uniform sampler2D normal_texturemap_file; +uniform vec2 normal_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float normal_texturemap_rotation_angle = 0.000000; +uniform float normal_texturemap_normal_scale = 1.000000; +uniform int normal_texturemap_uaddressmode = 2; +uniform int normal_texturemap_vaddressmode = 2; +uniform vec3 shaderref5_metal_f0 = vec3(0.959822, 0.962260, 0.965052); +uniform vec3 shaderref5_surface_albedo = vec3(1.000000, 1.000000, 1.000000); +uniform float shaderref5_surface_roughness = 0.122474; +uniform float shaderref5_surface_anisotropy = 0.000000; +uniform float shaderref5_surface_rotation = 0.000000; +uniform vec3 shaderref5_surface_cutout = vec3(1.000000, 1.000000, 1.000000); + +in VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void mx_rotate_vector2(vec2 _in, float amount, out vec2 result) +{ + float rotationRadians = mx_radians(amount); + float sa = mx_sin(rotationRadians); + float ca = mx_cos(rotationRadians); + result = vec2(ca*_in.x + sa*_in.y, -sa*_in.x + ca*_in.y); +} + +void NG_switch_vector2I(vec2 in1, vec2 in2, vec2 in3, vec2 in4, vec2 in5, vec2 in6, vec2 in7, vec2 in8, vec2 in9, vec2 in10, int which, out vec2 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec2 ifgreater_10_in2_tmp = vec2(0.000000, 0.000000); + vec2 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec2 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec2 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec2 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec2 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec2 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec2 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec2 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec2 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec2 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_place2d_vector2(vec2 texcoord, vec2 pivot, vec2 scale, float rotate, vec2 offset, int operationorder, out vec2 out1) +{ + vec2 N_subpivot_out = texcoord - pivot; + vec2 N_applyscale_out = N_subpivot_out / scale; + vec2 N_applyoffset2_out = N_subpivot_out - offset; + vec2 N_applyrot_out = vec2(0.0); + mx_rotate_vector2(N_applyscale_out, rotate, N_applyrot_out); + vec2 N_applyrot2_out = vec2(0.0); + mx_rotate_vector2(N_applyoffset2_out, rotate, N_applyrot2_out); + vec2 N_applyoffset_out = N_applyrot_out - offset; + vec2 N_applyscale2_out = N_applyrot2_out / scale; + vec2 N_addpivot_out = N_applyoffset_out + pivot; + vec2 N_addpivot2_out = N_applyscale2_out + pivot; + vec2 N_switch_operationorder_out = vec2(0.0); + NG_switch_vector2I(N_addpivot_out, N_addpivot2_out, vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), operationorder, N_switch_operationorder_out); + out1 = N_switch_operationorder_out; +} + +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return vec2(uv.x, 1.0 - uv.y); +} + +void mx_image_vector3(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_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, result); +} + +void adsk_NG_adsk_normal_map(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float normal_scale, int uaddressmode, int vaddressmode, vec2 texcoord, vec3 normal, vec3 tangent, out vec3 out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec3 normalmap_cross_out = cross(normal, tangent); + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 normalmap_cross_norm_out = normalize(normalmap_cross_out); + vec3 b_image_out = vec3(0.0); + mx_image_vector3(file, 0, vec3(0.000000, 0.000000, 0.000000), a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + const vec3 b_image_negate_y_in2_tmp = vec3(1.000000, -1.000000, 1.000000); + vec3 b_image_negate_y_out = b_image_out * b_image_negate_y_in2_tmp; + const vec3 b_image_invert_y_in1_tmp = vec3(0.000000, 1.000000, 0.000000); + vec3 b_image_invert_y_out = b_image_invert_y_in1_tmp + b_image_negate_y_out; + vec3 impl_normalmap_out = vec3(0.0); + mx_normalmap_float(b_image_invert_y_out, normal_scale, normal, tangent, normalmap_cross_norm_out, impl_normalmap_out); + out1 = impl_normalmap_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec2 normal_texturemap_realworld_offset_unit_out = normal_texturemap_realworld_offset_unit_in1 * normal_texturemap_realworld_offset_unit_in2; + vec2 normal_texturemap_realworld_scale_unit_out = normal_texturemap_realworld_scale_unit_in1 * normal_texturemap_realworld_scale_unit_in2; + vec3 normal_texturemap_out = vec3(0.0); + adsk_NG_adsk_normal_map(normal_texturemap_file, normal_texturemap_realworld_offset_unit_out, normal_texturemap_realworld_scale_unit_out, normal_texturemap_uv_offset, normal_texturemap_uv_scale, normal_texturemap_rotation_angle, normal_texturemap_normal_scale, normal_texturemap_uaddressmode, normal_texturemap_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, normal_texturemap_out); + surfaceshader shaderref5_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref5_metal_f0, shaderref5_surface_albedo, shaderref5_surface_roughness, shaderref5_surface_anisotropy, shaderref5_surface_rotation, normal_texturemap_out, shaderref5_surface_cutout, shaderref5_out); + material Aluminum_Diamond_Plate_out = shaderref5_out; + out1 = vec4(Aluminum_Diamond_Plate_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_vs.glsl new file mode 100644 index 0000000000..4334509193 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Aluminum_Diamond_Plate_vs.glsl @@ -0,0 +1,80 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec2 i_texcoord_0; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.texcoord_0 = i_texcoord_0; + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_ps.glsl new file mode 100644 index 0000000000..f8d2f5fd7a --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_ps.glsl @@ -0,0 +1,1815 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 shaderref0_metal_f0 = vec3(0.100000, 0.300000, 0.700000); +uniform vec3 shaderref0_surface_albedo = vec3(1.000000, 1.000000, 1.000000); +uniform float shaderref0_surface_roughness = 0.122474; +uniform float shaderref0_surface_anisotropy = 0.000000; +uniform float shaderref0_surface_rotation = 0.000000; +uniform vec3 shaderref0_surface_cutout = vec3(1.000000, 1.000000, 1.000000); + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + surfaceshader shaderref0_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref0_metal_f0, shaderref0_surface_albedo, shaderref0_surface_roughness, shaderref0_surface_anisotropy, shaderref0_surface_rotation, geomprop_Nworld_out1, shaderref0_surface_cutout, shaderref0_out); + material Anodized_Blue_Glossy_out = shaderref0_out; + out1 = vec4(Anodized_Blue_Glossy_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_vs.glsl new file mode 100644 index 0000000000..6a98f2a08b --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Blue_Glossy_vs.glsl @@ -0,0 +1,77 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_ps.glsl new file mode 100644 index 0000000000..f6459b4a2c --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_ps.glsl @@ -0,0 +1,1815 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 shaderref2_metal_f0 = vec3(0.700000, 0.100000, 0.100000); +uniform vec3 shaderref2_surface_albedo = vec3(1.000000, 1.000000, 1.000000); +uniform float shaderref2_surface_roughness = 0.547723; +uniform float shaderref2_surface_anisotropy = 0.000000; +uniform float shaderref2_surface_rotation = 0.000000; +uniform vec3 shaderref2_surface_cutout = vec3(1.000000, 1.000000, 1.000000); + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + surfaceshader shaderref2_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref2_metal_f0, shaderref2_surface_albedo, shaderref2_surface_roughness, shaderref2_surface_anisotropy, shaderref2_surface_rotation, geomprop_Nworld_out1, shaderref2_surface_cutout, shaderref2_out); + material Anodized_Red_Rough_out = shaderref2_out; + out1 = vec4(Anodized_Red_Rough_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_vs.glsl new file mode 100644 index 0000000000..6a98f2a08b --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Anodized_Red_Rough_vs.glsl @@ -0,0 +1,77 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_ps.glsl new file mode 100644 index 0000000000..54d25a7832 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_ps.glsl @@ -0,0 +1,1990 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform float u_alphaThreshold = 0.001000; +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform int geomprop_UV0_index = 0; +uniform vec2 normal_texturemap_realworld_offset_unit_in1 = vec2(0.000000, 0.000000); +uniform float normal_texturemap_realworld_offset_unit_in2 = 2.540000; +uniform vec2 normal_texturemap_realworld_scale_unit_in1 = vec2(60.000000, 60.000000); +uniform float normal_texturemap_realworld_scale_unit_in2 = 2.540000; +uniform vec2 surfacecutout_texturemap_realworld_offset_unit_in1 = vec2(0.000000, 0.000000); +uniform float surfacecutout_texturemap_realworld_offset_unit_in2 = 2.540000; +uniform vec2 surfacecutout_texturemap_realworld_scale_unit_in1 = vec2(60.000000, 60.000000); +uniform float surfacecutout_texturemap_realworld_scale_unit_in2 = 2.540000; +uniform sampler2D surfacealbedo_texturemap_file; +uniform vec2 surfacealbedo_texturemap_realworld_offset = vec2(0.000000, 0.000000); +uniform vec2 surfacealbedo_texturemap_realworld_scale = vec2(1.000000, 1.000000); +uniform vec2 surfacealbedo_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 surfacealbedo_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float surfacealbedo_texturemap_rotation_angle = 0.000000; +uniform float surfacealbedo_texturemap_rgbamount = 1.000000; +uniform bool surfacealbedo_texturemap_invert = false; +uniform int surfacealbedo_texturemap_uaddressmode = 2; +uniform int surfacealbedo_texturemap_vaddressmode = 2; +uniform sampler2D normal_texturemap_file; +uniform vec2 normal_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float normal_texturemap_rotation_angle = 0.000000; +uniform float normal_texturemap_normal_scale = 1.000000; +uniform int normal_texturemap_uaddressmode = 2; +uniform int normal_texturemap_vaddressmode = 2; +uniform sampler2D surfacecutout_texturemap_file; +uniform vec2 surfacecutout_texturemap_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 surfacecutout_texturemap_uv_scale = vec2(1.000000, 1.000000); +uniform float surfacecutout_texturemap_rotation_angle = 0.000000; +uniform float surfacecutout_texturemap_rgbamount = 1.000000; +uniform bool surfacecutout_texturemap_invert = false; +uniform int surfacecutout_texturemap_uaddressmode = 2; +uniform int surfacecutout_texturemap_vaddressmode = 2; +uniform vec3 shaderref3_metal_f0 = vec3(0.900000, 0.600000, 0.200000); +uniform float shaderref3_surface_roughness = 0.122474; +uniform float shaderref3_surface_anisotropy = 0.950000; +uniform float shaderref3_surface_rotation = 0.000000; + +in VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void mx_rotate_vector2(vec2 _in, float amount, out vec2 result) +{ + float rotationRadians = mx_radians(amount); + float sa = mx_sin(rotationRadians); + float ca = mx_cos(rotationRadians); + result = vec2(ca*_in.x + sa*_in.y, -sa*_in.x + ca*_in.y); +} + +void NG_switch_vector2I(vec2 in1, vec2 in2, vec2 in3, vec2 in4, vec2 in5, vec2 in6, vec2 in7, vec2 in8, vec2 in9, vec2 in10, int which, out vec2 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec2 ifgreater_10_in2_tmp = vec2(0.000000, 0.000000); + vec2 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec2 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec2 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec2 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec2 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec2 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec2 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec2 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec2 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec2 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_place2d_vector2(vec2 texcoord, vec2 pivot, vec2 scale, float rotate, vec2 offset, int operationorder, out vec2 out1) +{ + vec2 N_subpivot_out = texcoord - pivot; + vec2 N_applyscale_out = N_subpivot_out / scale; + vec2 N_applyoffset2_out = N_subpivot_out - offset; + vec2 N_applyrot_out = vec2(0.0); + mx_rotate_vector2(N_applyscale_out, rotate, N_applyrot_out); + vec2 N_applyrot2_out = vec2(0.0); + mx_rotate_vector2(N_applyoffset2_out, rotate, N_applyrot2_out); + vec2 N_applyoffset_out = N_applyrot_out - offset; + vec2 N_applyscale2_out = N_applyrot2_out / scale; + vec2 N_addpivot_out = N_applyoffset_out + pivot; + vec2 N_addpivot2_out = N_applyscale2_out + pivot; + vec2 N_switch_operationorder_out = vec2(0.0); + NG_switch_vector2I(N_addpivot_out, N_addpivot2_out, vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), operationorder, N_switch_operationorder_out); + out1 = N_switch_operationorder_out; +} + +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return vec2(uv.x, 1.0 - uv.y); +} + +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 adsk_NG_adsk_bitmap_color3(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float rgbamount, bool invert, int uaddressmode, int vaddressmode, vec2 texcoord, out vec3 out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 b_image_out = vec3(0.0); + mx_image_color3(file, 0, vec3(0.000000, 0.000000, 0.000000), a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + vec3 image_brightness_out = b_image_out * rgbamount; + const vec3 image_invert_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 image_invert_out = image_invert_amount_tmp - image_brightness_out; + const bool image_convert_value2_tmp = true; + vec3 image_convert_out = (invert == image_convert_value2_tmp) ? image_invert_out : image_brightness_out; + out1 = image_convert_out; +} + + +void mx_image_vector3(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_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, result); +} + +void adsk_NG_adsk_normal_map(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float normal_scale, int uaddressmode, int vaddressmode, vec2 texcoord, vec3 normal, vec3 tangent, out vec3 out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec3 normalmap_cross_out = cross(normal, tangent); + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 normalmap_cross_norm_out = normalize(normalmap_cross_out); + vec3 b_image_out = vec3(0.0); + mx_image_vector3(file, 0, vec3(0.000000, 0.000000, 0.000000), a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + const vec3 b_image_negate_y_in2_tmp = vec3(1.000000, -1.000000, 1.000000); + vec3 b_image_negate_y_out = b_image_out * b_image_negate_y_in2_tmp; + const vec3 b_image_invert_y_in1_tmp = vec3(0.000000, 1.000000, 0.000000); + vec3 b_image_invert_y_out = b_image_invert_y_in1_tmp + b_image_negate_y_out; + vec3 impl_normalmap_out = vec3(0.0); + mx_normalmap_float(b_image_invert_y_out, normal_scale, normal, tangent, normalmap_cross_norm_out, impl_normalmap_out); + out1 = impl_normalmap_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec2 normal_texturemap_realworld_offset_unit_out = normal_texturemap_realworld_offset_unit_in1 * normal_texturemap_realworld_offset_unit_in2; + vec2 normal_texturemap_realworld_scale_unit_out = normal_texturemap_realworld_scale_unit_in1 * normal_texturemap_realworld_scale_unit_in2; + vec2 surfacecutout_texturemap_realworld_offset_unit_out = surfacecutout_texturemap_realworld_offset_unit_in1 * surfacecutout_texturemap_realworld_offset_unit_in2; + vec2 surfacecutout_texturemap_realworld_scale_unit_out = surfacecutout_texturemap_realworld_scale_unit_in1 * surfacecutout_texturemap_realworld_scale_unit_in2; + vec3 surfacealbedo_texturemap_out = vec3(0.0); + adsk_NG_adsk_bitmap_color3(surfacealbedo_texturemap_file, surfacealbedo_texturemap_realworld_offset, surfacealbedo_texturemap_realworld_scale, surfacealbedo_texturemap_uv_offset, surfacealbedo_texturemap_uv_scale, surfacealbedo_texturemap_rotation_angle, surfacealbedo_texturemap_rgbamount, surfacealbedo_texturemap_invert, surfacealbedo_texturemap_uaddressmode, surfacealbedo_texturemap_vaddressmode, geomprop_UV0_out1, surfacealbedo_texturemap_out); + vec3 normal_texturemap_out = vec3(0.0); + adsk_NG_adsk_normal_map(normal_texturemap_file, normal_texturemap_realworld_offset_unit_out, normal_texturemap_realworld_scale_unit_out, normal_texturemap_uv_offset, normal_texturemap_uv_scale, normal_texturemap_rotation_angle, normal_texturemap_normal_scale, normal_texturemap_uaddressmode, normal_texturemap_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, normal_texturemap_out); + vec3 surfacecutout_texturemap_out = vec3(0.0); + adsk_NG_adsk_bitmap_color3(surfacecutout_texturemap_file, surfacecutout_texturemap_realworld_offset_unit_out, surfacecutout_texturemap_realworld_scale_unit_out, surfacecutout_texturemap_uv_offset, surfacecutout_texturemap_uv_scale, surfacecutout_texturemap_rotation_angle, surfacecutout_texturemap_rgbamount, surfacecutout_texturemap_invert, surfacecutout_texturemap_uaddressmode, surfacecutout_texturemap_vaddressmode, geomprop_UV0_out1, surfacecutout_texturemap_out); + surfaceshader shaderref3_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref3_metal_f0, surfacealbedo_texturemap_out, shaderref3_surface_roughness, shaderref3_surface_anisotropy, shaderref3_surface_rotation, normal_texturemap_out, surfacecutout_texturemap_out, shaderref3_out); + material Brass_Wire_Mesh_out = shaderref3_out; + float outAlpha = clamp(1.0 - dot(Brass_Wire_Mesh_out.transparency, vec3(0.3333)), 0.0, 1.0); + out1 = vec4(Brass_Wire_Mesh_out.color, outAlpha); + if (outAlpha < u_alphaThreshold) + { + discard; + } +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_vs.glsl new file mode 100644 index 0000000000..4334509193 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Brass_Wire_Mesh_vs.glsl @@ -0,0 +1,80 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec2 i_texcoord_0; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec2 texcoord_0; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.texcoord_0 = i_texcoord_0; + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_ps.glsl new file mode 100644 index 0000000000..0e23b8e3b6 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_ps.glsl @@ -0,0 +1,1815 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 shaderref1_metal_f0 = vec3(0.848522, 0.614431, 0.546826); +uniform vec3 shaderref1_surface_albedo = vec3(1.000000, 1.000000, 1.000000); +uniform float shaderref1_surface_roughness = 0.077460; +uniform float shaderref1_surface_anisotropy = 0.000000; +uniform float shaderref1_surface_rotation = 0.000000; +uniform vec3 shaderref1_surface_cutout = vec3(1.000000, 1.000000, 1.000000); + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void adsk_NG_adsk_metal(vec3 metal_f0, vec3 surface_albedo, float surface_roughness, float surface_anisotropy, float surface_rotation, vec3 surface_normal, vec3 surface_cutout, out surfaceshader out1) +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 base_color_value_out = surface_albedo * metal_f0; + const float oneminus_surfaniso0_in1_tmp = 1.000000; + float oneminus_surfaniso0_out = oneminus_surfaniso0_in1_tmp - surface_anisotropy; + const float twotimes_surfaniso0_in1_tmp = 2.000000; + float twotimes_surfaniso0_out = twotimes_surfaniso0_in1_tmp * surface_anisotropy; + float pow2_surfaniso0_out = surface_anisotropy * surface_anisotropy; + const float div_rotation0_in2_tmp = 360.000000; + float div_rotation0_out = surface_rotation / div_rotation0_in2_tmp; + float sqrt1minus_surfaniso0_out = sqrt(oneminus_surfaniso0_out); + float specular_anisotropy_value_out = twotimes_surfaniso0_out - pow2_surfaniso0_out; + const float specular_rotation_value_in2_tmp = 1.000000; + float specular_rotation_value_out = mx_mod(div_rotation0_out, specular_rotation_value_in2_tmp); + float specular_roughness_value_out = sqrt1minus_surfaniso0_out * surface_roughness; + surfaceshader standard_surface0_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, base_color_value_out, 0.000000, 1.000000, 1.000000, surface_albedo, specular_roughness_value_out, 1.500000, specular_anisotropy_value_out, specular_rotation_value_out, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), surface_cutout, false, surface_normal, geomprop_Tworld_out1, standard_surface0_out); + out1 = standard_surface0_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + surfaceshader shaderref1_out = surfaceshader(vec3(0.0),vec3(0.0)); + adsk_NG_adsk_metal(shaderref1_metal_f0, shaderref1_surface_albedo, shaderref1_surface_roughness, shaderref1_surface_anisotropy, shaderref1_surface_rotation, geomprop_Nworld_out1, shaderref1_surface_cutout, shaderref1_out); + material Copper_Polished_out = shaderref1_out; + out1 = vec4(Copper_Polished_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_vs.glsl new file mode 100644 index 0000000000..6a98f2a08b --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/metal/Copper_Polished_vs.glsl @@ -0,0 +1,77 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/normalmap/Gold_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/normalmap/Gold_ps.glsl index fdb3ef1d32..5ab076e01b 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/normalmap/Gold_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/normalmap/Gold_ps.glsl @@ -997,19 +997,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1711,6 +1698,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1728,7 +1732,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1738,7 +1742,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Plastic_Glossy_White_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Plastic_Glossy_White_ps.glsl index 1661a9c1bc..50c7084b4d 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Plastic_Glossy_White_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Plastic_Glossy_White_ps.glsl @@ -859,19 +859,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1573,6 +1560,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1590,7 +1594,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1600,7 +1604,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_Semigloss_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_Semigloss_ps.glsl index b173458204..94859a241b 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_Semigloss_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_Semigloss_ps.glsl @@ -1065,19 +1065,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1779,6 +1766,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1796,7 +1800,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1806,7 +1810,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_ps.glsl index 9bd047d9da..04c312934b 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/opaque/Walnut_ps.glsl @@ -1066,19 +1066,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1780,6 +1767,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1797,7 +1801,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1807,7 +1811,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Diamond_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Diamond_ps.glsl index ee868005b5..89bdc3d683 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Diamond_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Diamond_ps.glsl @@ -855,19 +855,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1569,6 +1556,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1586,7 +1590,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1596,7 +1600,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Ruby_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Ruby_ps.glsl index 23e59782c1..1af2b6d516 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Ruby_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Fusion/transparent/Gemstone_Ruby_ps.glsl @@ -855,19 +855,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1569,6 +1556,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1586,7 +1590,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1596,7 +1600,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/ceramic/M_legacy_ceramic_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/ceramic/M_legacy_ceramic_ps.glsl index 9972460320..f9b411bc03 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/ceramic/M_legacy_ceramic_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/ceramic/M_legacy_ceramic_ps.glsl @@ -1053,19 +1053,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1767,6 +1754,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1784,7 +1788,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1794,7 +1798,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/checker/M_legacy_checker_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/checker/M_legacy_checker_ps.glsl index d8fecb750a..543c6ee358 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/checker/M_legacy_checker_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/checker/M_legacy_checker_ps.glsl @@ -1066,19 +1066,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1780,6 +1767,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1797,7 +1801,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1807,7 +1811,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/concrete/M_legacy_concrete_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/concrete/M_legacy_concrete_ps.glsl index fb932d8e8c..ac24400a24 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/concrete/M_legacy_concrete_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/concrete/M_legacy_concrete_ps.glsl @@ -1123,19 +1123,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1837,6 +1824,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1854,7 +1858,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1864,7 +1868,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/generic/M_legacy_generic_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/generic/M_legacy_generic_ps.glsl index 41a7997cc5..0766b0fd1f 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/generic/M_legacy_generic_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/generic/M_legacy_generic_ps.glsl @@ -1027,19 +1027,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1741,6 +1728,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1758,7 +1762,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1768,7 +1772,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glass/M_legacy_glass_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glass/M_legacy_glass_ps.glsl index 7a0f4f3c18..acac01ef23 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glass/M_legacy_glass_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glass/M_legacy_glass_ps.glsl @@ -1979,19 +1979,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -2693,6 +2680,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -2710,7 +2714,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -2720,7 +2724,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glazing/M_legacy_glazing_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glazing/M_legacy_glazing_ps.glsl index 66a259c24e..a351e0bc94 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glazing/M_legacy_glazing_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/glazing/M_legacy_glazing_ps.glsl @@ -927,19 +927,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1641,6 +1628,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1658,7 +1662,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1668,7 +1672,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/gradient/M_legacy_gradient_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/gradient/M_legacy_gradient_ps.glsl index 9b11a4f4d1..0a91735bec 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/gradient/M_legacy_gradient_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/gradient/M_legacy_gradient_ps.glsl @@ -2130,19 +2130,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -2844,6 +2831,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -2861,7 +2865,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -2871,7 +2875,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/hardwood/M_legacy_hardwood_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/hardwood/M_legacy_hardwood_ps.glsl index 23996a024f..de67af119a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/hardwood/M_legacy_hardwood_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/hardwood/M_legacy_hardwood_ps.glsl @@ -1144,19 +1144,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1858,6 +1845,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1875,7 +1879,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1885,7 +1889,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_ps.glsl new file mode 100644 index 0000000000..50c48f7219 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_ps.glsl @@ -0,0 +1,1981 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform int geomprop_UV0_index = 0; +uniform vec2 legacy_knurl_realworld_scale_unit_in1 = vec2(0.100000, 0.200000); +uniform float legacy_knurl_realworld_scale_unit_in2 = 2.540000; +uniform float legacy_knurl_amount = 0.250000; +uniform float legacy_knurl_rotation_angle = 45.000000; +uniform vec2 legacy_knurl_realworld_offset = vec2(0.000000, 0.000000); +uniform bool legacy_knurl_diagonal = true; +uniform float SR_legacy_knurl_base = 1.000000; +uniform vec3 SR_legacy_knurl_base_color = vec3(0.800000, 0.800000, 0.800000); +uniform float SR_legacy_knurl_diffuse_roughness = 0.000000; +uniform float SR_legacy_knurl_metalness = 1.000000; +uniform float SR_legacy_knurl_specular = 1.000000; +uniform vec3 SR_legacy_knurl_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_knurl_specular_roughness = 0.100000; +uniform float SR_legacy_knurl_specular_IOR = 1.500000; +uniform float SR_legacy_knurl_specular_anisotropy = 0.000000; +uniform float SR_legacy_knurl_specular_rotation = 0.000000; +uniform float SR_legacy_knurl_transmission = 0.000000; +uniform vec3 SR_legacy_knurl_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_knurl_transmission_depth = 0.000000; +uniform vec3 SR_legacy_knurl_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_legacy_knurl_transmission_scatter_anisotropy = 0.000000; +uniform float SR_legacy_knurl_transmission_dispersion = 0.000000; +uniform float SR_legacy_knurl_transmission_extra_roughness = 0.000000; +uniform float SR_legacy_knurl_subsurface = 0.400000; +uniform vec3 SR_legacy_knurl_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_legacy_knurl_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_knurl_subsurface_scale = 1.000000; +uniform float SR_legacy_knurl_subsurface_anisotropy = 0.000000; +uniform float SR_legacy_knurl_sheen = 0.000000; +uniform vec3 SR_legacy_knurl_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_knurl_sheen_roughness = 0.300000; +uniform float SR_legacy_knurl_coat = 0.000000; +uniform vec3 SR_legacy_knurl_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_knurl_coat_roughness = 0.100000; +uniform float SR_legacy_knurl_coat_anisotropy = 0.000000; +uniform float SR_legacy_knurl_coat_rotation = 0.000000; +uniform float SR_legacy_knurl_coat_IOR = 1.500000; +uniform float SR_legacy_knurl_coat_affect_color = 0.000000; +uniform float SR_legacy_knurl_coat_affect_roughness = 0.000000; +uniform float SR_legacy_knurl_thin_film_thickness = 0.000000; +uniform float SR_legacy_knurl_thin_film_IOR = 1.500000; +uniform float SR_legacy_knurl_emission = 0.000000; +uniform vec3 SR_legacy_knurl_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_legacy_knurl_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_legacy_knurl_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec2 texcoord_0; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void mx_rotate_vector2(vec2 _in, float amount, out vec2 result) +{ + float rotationRadians = mx_radians(amount); + float sa = mx_sin(rotationRadians); + float ca = mx_cos(rotationRadians); + result = vec2(ca*_in.x + sa*_in.y, -sa*_in.x + ca*_in.y); +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +void NG_separate2_vector2(vec2 in1, out float outx, out float outy) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void NG_convert_vector3_color3(vec3 in1, out vec3 out1) +{ + float separate_outx = 0.0; + float separate_outy = 0.0; + float separate_outz = 0.0; + NG_separate3_vector3(in1, separate_outx, separate_outy, separate_outz); + vec3 combine_out = vec3(separate_outx,separate_outy,separate_outz); + out1 = combine_out; +} + +void mx_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, result); +} + +void NG_legacy_knurl(vec2 texcoord, float amount, float rotation_angle, vec2 realworld_scale, vec2 realworld_offset, bool diagonal, out vec3 out_rgb, out vec3 out_vec3) +{ + vec2 offset_uv_out = texcoord - realworld_offset; + const float lagacy_rot_direction_amount_tmp = 0.000000; + float lagacy_rot_direction_out = lagacy_rot_direction_amount_tmp - rotation_angle; + const float invert_amount_amount_tmp = 1.000000; + float invert_amount_out = invert_amount_amount_tmp - amount; + const float diag_adjustment_in2_tmp = 45.000000; + float diag_adjustment_out = rotation_angle - diag_adjustment_in2_tmp; + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Bworld_out1 = normalize(vd.bitangentWorld); + vec2 rotate2d_pattern_out = vec2(0.0); + mx_rotate_vector2(offset_uv_out, lagacy_rot_direction_out, rotate2d_pattern_out); + const float amount_to_degrees_in2_tmp = 90.000000; + float amount_to_degrees_out = invert_amount_out * amount_to_degrees_in2_tmp; + const bool diag_select2_value2_tmp = true; + float diag_select2_out = (diagonal == diag_select2_value2_tmp) ? diag_adjustment_out : rotation_angle; + vec2 divide1_out = rotate2d_pattern_out / realworld_scale; + vec3 rotate_Y1_out = vec3(0.0); + mx_rotate_vector3(vec3(1.000000, 0.000000, 0.000000), amount_to_degrees_out, vec3(0.000000, 1.000000, 0.000000), rotate_Y1_out); + const float invert_180_amount_tmp = 180.000000; + float invert_180_out = invert_180_amount_tmp - amount_to_degrees_out; + vec3 rotate_X2_out = vec3(0.0); + mx_rotate_vector3(vec3(0.000000, -1.000000, 0.000000), amount_to_degrees_out, vec3(1.000000, 0.000000, 0.000000), rotate_X2_out); + const float uv_modulo_in2_tmp = 1.000000; + vec2 uv_modulo_out = mx_mod(divide1_out, uv_modulo_in2_tmp); + vec3 rotate_Y2_out = vec3(0.0); + mx_rotate_vector3(vec3(1.000000, 0.000000, 0.000000), invert_180_out, vec3(0.000000, 1.000000, 0.000000), rotate_Y2_out); + vec3 rotate_X1_out = vec3(0.0); + mx_rotate_vector3(vec3(0.000000, -1.000000, 0.000000), invert_180_out, vec3(1.000000, 0.000000, 0.000000), rotate_X1_out); + const float diag_scale_in2_tmp = 1.414210; + vec2 diag_scale_out = uv_modulo_out * diag_scale_in2_tmp; + vec2 rotate_knurl_out = vec2(0.0); + mx_rotate_vector2(diag_scale_out, 45.000000, rotate_knurl_out); + const vec2 diag_shift_in2_tmp = vec2(0.500000, 0.500000); + vec2 diag_shift_out = rotate_knurl_out + diag_shift_in2_tmp; + const float modulo_knurl_in2_tmp = 1.000000; + vec2 modulo_knurl_out = mx_mod(diag_shift_out, modulo_knurl_in2_tmp); + const bool diag_select1_value2_tmp = true; + vec2 diag_select1_out = (diagonal == diag_select1_value2_tmp) ? modulo_knurl_out : uv_modulo_out; + float separate_uv_outx = 0.0; + float separate_uv_outy = 0.0; + NG_separate2_vector2(diag_select1_out, separate_uv_outx, separate_uv_outy); + const float invert_x_amount_tmp = 1.000000; + float invert_x_out = invert_x_amount_tmp - separate_uv_outx; + const float ifgreater_half_x_value2_tmp = 0.500000; + vec3 ifgreater_half_x_out = (separate_uv_outx > ifgreater_half_x_value2_tmp) ? rotate_Y1_out : rotate_Y2_out; + const float ifgreater_xy_in1_tmp = 1.000000; + const float ifgreater_xy_in2_tmp = 0.000000; + float ifgreater_xy_out = (separate_uv_outx > separate_uv_outy) ? ifgreater_xy_in1_tmp : ifgreater_xy_in2_tmp; + const float ifgreater_half_y_value2_tmp = 0.500000; + vec3 ifgreater_half_y_out = (separate_uv_outy > ifgreater_half_y_value2_tmp) ? rotate_X1_out : rotate_X2_out; + const float ifgreater_yx_in1_tmp = 1.000000; + const float ifgreater_yx_in2_tmp = 0.000000; + float ifgreater_yx_out = (separate_uv_outy > invert_x_out) ? ifgreater_yx_in1_tmp : ifgreater_yx_in2_tmp; + vec3 select_plane_out = (ifgreater_xy_out == ifgreater_yx_out) ? ifgreater_half_x_out : ifgreater_half_y_out; + vec3 rotate_normal_out = vec3(0.0); + mx_rotate_vector3(select_plane_out, diag_select2_out, vec3(0.000000, 0.000000, 1.000000), rotate_normal_out); + const float divide_half_in2_tmp = 2.000000; + vec3 divide_half_out = rotate_normal_out / divide_half_in2_tmp; + const float offset_half_in2_tmp = 0.500000; + vec3 offset_half_out = divide_half_out + offset_half_in2_tmp; + vec3 convert_vec_col_out = vec3(0.0); + NG_convert_vector3_color3(offset_half_out, convert_vec_col_out); + vec3 normalmap1_out = vec3(0.0); + mx_normalmap_float(offset_half_out, 1.000000, geomprop_Nworld_out1, geomprop_Tworld_out1, geomprop_Bworld_out1, normalmap1_out); + out_rgb = convert_vec_col_out; + out_vec3 = normalmap1_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; + vec2 legacy_knurl_realworld_scale_unit_out = legacy_knurl_realworld_scale_unit_in1 * legacy_knurl_realworld_scale_unit_in2; + vec3 legacy_knurl_out_rgb = vec3(0.0); + vec3 legacy_knurl_out_vec3 = vec3(0.0); + NG_legacy_knurl(geomprop_UV0_out1, legacy_knurl_amount, legacy_knurl_rotation_angle, legacy_knurl_realworld_scale_unit_out, legacy_knurl_realworld_offset, legacy_knurl_diagonal, legacy_knurl_out_rgb, legacy_knurl_out_vec3); + surfaceshader SR_legacy_knurl_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_legacy_knurl_base, SR_legacy_knurl_base_color, SR_legacy_knurl_diffuse_roughness, SR_legacy_knurl_metalness, SR_legacy_knurl_specular, SR_legacy_knurl_specular_color, SR_legacy_knurl_specular_roughness, SR_legacy_knurl_specular_IOR, SR_legacy_knurl_specular_anisotropy, SR_legacy_knurl_specular_rotation, SR_legacy_knurl_transmission, SR_legacy_knurl_transmission_color, SR_legacy_knurl_transmission_depth, SR_legacy_knurl_transmission_scatter, SR_legacy_knurl_transmission_scatter_anisotropy, SR_legacy_knurl_transmission_dispersion, SR_legacy_knurl_transmission_extra_roughness, SR_legacy_knurl_subsurface, SR_legacy_knurl_subsurface_color, SR_legacy_knurl_subsurface_radius, SR_legacy_knurl_subsurface_scale, SR_legacy_knurl_subsurface_anisotropy, SR_legacy_knurl_sheen, SR_legacy_knurl_sheen_color, SR_legacy_knurl_sheen_roughness, SR_legacy_knurl_coat, SR_legacy_knurl_coat_color, SR_legacy_knurl_coat_roughness, SR_legacy_knurl_coat_anisotropy, SR_legacy_knurl_coat_rotation, SR_legacy_knurl_coat_IOR, geomprop_Nworld_out1, SR_legacy_knurl_coat_affect_color, SR_legacy_knurl_coat_affect_roughness, SR_legacy_knurl_thin_film_thickness, SR_legacy_knurl_thin_film_IOR, SR_legacy_knurl_emission, SR_legacy_knurl_emission_color, SR_legacy_knurl_opacity, SR_legacy_knurl_thin_walled, legacy_knurl_out_vec3, geomprop_Tworld_out1, SR_legacy_knurl_out); + material M_legacy_knurl_out = SR_legacy_knurl_out; + out1 = vec4(M_legacy_knurl_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_vs.glsl new file mode 100644 index 0000000000..626531bbbb --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/knurl/M_legacy_knurl_vs.glsl @@ -0,0 +1,82 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; +in vec2 i_texcoord_0; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec2 texcoord_0; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.texcoord_0 = i_texcoord_0; + vd.bitangentWorld = cross(vd.normalWorld, vd.tangentWorld); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_ps.glsl new file mode 100644 index 0000000000..d21361c7f8 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_ps.glsl @@ -0,0 +1,2773 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform float legacy_marble_color3_size_unit_in1 = 4.600000; +uniform float legacy_marble_color3_size_unit_in2 = 2.540000; +uniform float legacy_marble_color3_width = 0.100000; +uniform vec3 legacy_marble_color3_stone_color = vec3(0.030300, 0.032100, 0.012200); +uniform vec3 legacy_marble_color3_vein_color = vec3(0.783200, 0.783200, 0.575500); +uniform vec3 legacy_marble_color3_realworld_offset = vec3(1.100000, 0.800000, 0.400000); +uniform vec3 legacy_marble_color3_rotation_angle = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_base = 1.000000; +uniform float SR_testmat1_diffuse_roughness = 0.000000; +uniform float SR_testmat1_metalness = 0.000000; +uniform float SR_testmat1_specular = 1.000000; +uniform vec3 SR_testmat1_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_specular_roughness = 0.100000; +uniform float SR_testmat1_specular_IOR = 1.500000; +uniform float SR_testmat1_specular_anisotropy = 0.000000; +uniform float SR_testmat1_specular_rotation = 0.000000; +uniform float SR_testmat1_transmission = 0.000000; +uniform vec3 SR_testmat1_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_transmission_depth = 0.000000; +uniform vec3 SR_testmat1_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_transmission_scatter_anisotropy = 0.000000; +uniform float SR_testmat1_transmission_dispersion = 0.000000; +uniform float SR_testmat1_transmission_extra_roughness = 0.000000; +uniform float SR_testmat1_subsurface = 0.400000; +uniform vec3 SR_testmat1_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_subsurface_scale = 1.000000; +uniform float SR_testmat1_subsurface_anisotropy = 0.000000; +uniform float SR_testmat1_sheen = 0.000000; +uniform vec3 SR_testmat1_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_sheen_roughness = 0.300000; +uniform float SR_testmat1_coat = 0.000000; +uniform vec3 SR_testmat1_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_coat_roughness = 0.100000; +uniform float SR_testmat1_coat_anisotropy = 0.000000; +uniform float SR_testmat1_coat_rotation = 0.000000; +uniform float SR_testmat1_coat_IOR = 1.500000; +uniform float SR_testmat1_coat_affect_color = 0.000000; +uniform float SR_testmat1_coat_affect_roughness = 0.000000; +uniform float SR_testmat1_thin_film_thickness = 0.000000; +uniform float SR_testmat1_thin_film_IOR = 1.500000; +uniform float SR_testmat1_emission = 0.000000; +uniform vec3 SR_testmat1_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_testmat1_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_noise3d_float(float amplitude, float pivot, vec3 position, out float result) +{ + float value = mx_perlin_noise_float(position); + result = value * amplitude + pivot; +} + +void NG_util_specklenoise_float(vec3 position, out float out1) +{ + float noise3d_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d_out); + const float multiply1_in2_tmp = 1.650000; + float multiply1_out = noise3d_out * multiply1_in2_tmp; + const float clamp1_low_tmp = -1.000000; + const float clamp1_high_tmp = 1.000000; + float clamp1_out = clamp(multiply1_out, clamp1_low_tmp, clamp1_high_tmp); + const float add1_in2_tmp = 1.000000; + float add1_out = clamp1_out + add1_in2_tmp; + const float multiply2_in2_tmp = 0.500000; + float multiply2_out = add1_out * multiply2_in2_tmp; + out1 = multiply2_out; +} + +void NG_switch_float(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, float which, out float out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_legacy_marble_color3(vec3 position, float size, float width, vec3 stone_color, vec3 vein_color, vec3 realworld_offset, vec3 rotation_angle, out vec3 out1) +{ + const float check_zero_value2_tmp = 0.000100; + const float check_zero_in2_tmp = 0.000100; + float check_zero_out = (size >= check_zero_value2_tmp) ? size : check_zero_in2_tmp; + float separate_rotation_xyz_outx = 0.0; + float separate_rotation_xyz_outy = 0.0; + float separate_rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, separate_rotation_xyz_outx, separate_rotation_xyz_outy, separate_rotation_xyz_outz); + const float divide5_in1_tmp = 500.000000; + float divide5_out = divide5_in1_tmp / check_zero_out; + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(position, separate_rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, separate_rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, separate_rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + vec3 offset_xyz_out = rotate3d_z_out - realworld_offset; + vec3 multiply16_out = offset_xyz_out * divide5_out; + const vec3 r1_in2_tmp = vec3(100.000000, 200.000000, 200.000000); + vec3 r1_out = multiply16_out / r1_in2_tmp; + float separate3_1_outx = 0.0; + float separate3_1_outy = 0.0; + float separate3_1_outz = 0.0; + NG_separate3_vector3(multiply16_out, separate3_1_outx, separate3_1_outy, separate3_1_outz); + const vec3 r2_in2_tmp = vec3(70.000000, 50.000000, 50.000000); + vec3 r2_out = r1_out / r2_in2_tmp; + float separate3_2_outx = 0.0; + float separate3_2_outy = 0.0; + float separate3_2_outz = 0.0; + NG_separate3_vector3(r1_out, separate3_2_outx, separate3_2_outy, separate3_2_outz); + float util_specklenoise1_out = 0.0; + NG_util_specklenoise_float(r1_out, util_specklenoise1_out); + const float add2_in2_tmp = 10000.000000; + float add2_out = separate3_1_outx + add2_in2_tmp; + float util_specklenoise2_out = 0.0; + NG_util_specklenoise_float(r2_out, util_specklenoise2_out); + const float divide3_in2_tmp = 100.000000; + float divide3_out = separate3_2_outx / divide3_in2_tmp; + const float divide6_in2_tmp = 100.000000; + float divide6_out = separate3_2_outy / divide6_in2_tmp; + const float multiply4_in2_tmp = 7.000000; + float multiply4_out = util_specklenoise1_out * multiply4_in2_tmp; + float multiply3_out = add2_out * width; + const float multiply8_in2_tmp = 0.200000; + float multiply8_out = util_specklenoise2_out * multiply8_in2_tmp; + vec3 r3_out = vec3(divide3_out,divide6_out,divide3_out); + float d1_out = multiply3_out + multiply4_out; + const float i1_in2_tmp = 0.700000; + float i1_out = multiply8_out + i1_in2_tmp; + float util_specklenoise3_out = 0.0; + NG_util_specklenoise_float(r3_out, util_specklenoise3_out); + float sign2_out = sign(d1_out); + float ceil2_out = ceil(d1_out); + float floor4_out = floor(d1_out); + const float divide2_in2_tmp = 17.000000; + float divide2_out = d1_out / divide2_in2_tmp; + const float multiply14_in2_tmp = 0.200000; + float multiply14_out = util_specklenoise3_out * multiply14_in2_tmp; + const float add7_in2_tmp = 1.000000; + float add7_out = util_specklenoise3_out + add7_in2_tmp; + const float trunc1_value2_tmp = 0.000000; + float trunc1_out = (sign2_out >= trunc1_value2_tmp) ? floor4_out : ceil2_out; + float sign1_out = sign(divide2_out); + float ceil1_out = ceil(divide2_out); + float floor2_out = floor(divide2_out); + const float i2_in2_tmp = 0.200000; + float i2_out = add7_out * i2_in2_tmp; + float subtract3_out = d1_out - trunc1_out; + const float ifgreatereq1_value2_tmp = 0.000000; + float ifgreatereq1_out = (sign1_out >= ifgreatereq1_value2_tmp) ? floor2_out : ceil1_out; + const float subtract2_in2_tmp = 10.500000; + float subtract2_out = subtract3_out - subtract2_in2_tmp; + const float multiply5_in2_tmp = 17.000000; + float multiply5_out = ifgreatereq1_out * multiply5_in2_tmp; + float absval1_out = abs(subtract2_out); + float id2_out = d1_out - multiply5_out; + const float multiply13_in2_tmp = 0.153846; + float multiply13_out = absval1_out * multiply13_in2_tmp; + const float id_4_or_less_value2_tmp = 4.000000; + const float id_4_or_less_in1_tmp = 1.000000; + const float id_4_or_less_in2_tmp = 0.000000; + float id_4_or_less_out = (id2_out >= id_4_or_less_value2_tmp) ? id_4_or_less_in1_tmp : id_4_or_less_in2_tmp; + const float multiply15_in2_tmp = 0.300000; + float multiply15_out = multiply13_out * multiply15_in2_tmp; + const float id_9_or_above_value2_tmp = 9.000000; + const float id_9_or_above_in1_tmp = 2.000000; + float id_9_or_above_out = (id2_out >= id_9_or_above_value2_tmp) ? id_9_or_above_in1_tmp : id_4_or_less_out; + float add8_out = multiply14_out + multiply15_out; + const float id_12_or_above_value2_tmp = 12.000000; + const float id_12_or_above_in1_tmp = 3.000000; + float id_12_or_above_out = (id2_out >= id_12_or_above_value2_tmp) ? id_12_or_above_in1_tmp : id_9_or_above_out; + const float i3_in2_tmp = 0.400000; + float i3_out = add8_out + i3_in2_tmp; + float slices_switch_out = 0.0; + NG_switch_float(i1_out, i3_out, i2_out, i3_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, id_12_or_above_out, slices_switch_out); + vec3 color_mix_out = mix(vein_color, stone_color, slices_switch_out); + out1 = color_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Pobject_out1 = vd.positionObject; + float legacy_marble_color3_size_unit_out = legacy_marble_color3_size_unit_in1 * legacy_marble_color3_size_unit_in2; + vec3 legacy_marble_color3_out = vec3(0.0); + NG_legacy_marble_color3(geomprop_Pobject_out1, legacy_marble_color3_size_unit_out, legacy_marble_color3_width, legacy_marble_color3_stone_color, legacy_marble_color3_vein_color, legacy_marble_color3_realworld_offset, legacy_marble_color3_rotation_angle, legacy_marble_color3_out); + surfaceshader SR_testmat1_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_testmat1_base, legacy_marble_color3_out, SR_testmat1_diffuse_roughness, SR_testmat1_metalness, SR_testmat1_specular, SR_testmat1_specular_color, SR_testmat1_specular_roughness, SR_testmat1_specular_IOR, SR_testmat1_specular_anisotropy, SR_testmat1_specular_rotation, SR_testmat1_transmission, SR_testmat1_transmission_color, SR_testmat1_transmission_depth, SR_testmat1_transmission_scatter, SR_testmat1_transmission_scatter_anisotropy, SR_testmat1_transmission_dispersion, SR_testmat1_transmission_extra_roughness, SR_testmat1_subsurface, SR_testmat1_subsurface_color, SR_testmat1_subsurface_radius, SR_testmat1_subsurface_scale, SR_testmat1_subsurface_anisotropy, SR_testmat1_sheen, SR_testmat1_sheen_color, SR_testmat1_sheen_roughness, SR_testmat1_coat, SR_testmat1_coat_color, SR_testmat1_coat_roughness, SR_testmat1_coat_anisotropy, SR_testmat1_coat_rotation, SR_testmat1_coat_IOR, geomprop_Nworld_out1, SR_testmat1_coat_affect_color, SR_testmat1_coat_affect_roughness, SR_testmat1_thin_film_thickness, SR_testmat1_thin_film_IOR, SR_testmat1_emission, SR_testmat1_emission_color, SR_testmat1_opacity, SR_testmat1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1, SR_testmat1_out); + material M_legacy_marble_out = SR_testmat1_out; + out1 = vec4(M_legacy_marble_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_vs.glsl new file mode 100644 index 0000000000..e9c23bbd30 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/marble/M_legacy_marble_vs.glsl @@ -0,0 +1,79 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionObject = i_position; + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/masonry/M_legacy_masonry_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/masonry/M_legacy_masonry_ps.glsl index ecc7c8b053..706214a941 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/masonry/M_legacy_masonry_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/masonry/M_legacy_masonry_ps.glsl @@ -1016,19 +1016,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1730,6 +1717,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1747,7 +1751,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1757,7 +1761,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_ps.glsl new file mode 100644 index 0000000000..80a6215fac --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_ps.glsl @@ -0,0 +1,3600 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform float u_alphaThreshold = 0.001000; +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform int geomprop_UV0_index = 0; +uniform float SR_legacy_metal_cutout_size_unit_in1 = 0.010000; +uniform float SR_legacy_metal_cutout_size_unit_in2 = 2.540000; +uniform float SR_legacy_metal_cutout_spacing_unit_in1 = 0.015000; +uniform float SR_legacy_metal_cutout_spacing_unit_in2 = 2.540000; +uniform sampler2D normal_map_knurl_file; +uniform vec2 normal_map_knurl_realworld_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_knurl_realworld_scale = vec2(1.000000, 1.000000); +uniform vec2 normal_map_knurl_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_knurl_uv_scale = vec2(1.000000, 1.000000); +uniform float normal_map_knurl_rotation_angle = 0.000000; +uniform float normal_map_knurl_normal_scale = 1.000000; +uniform int normal_map_knurl_uaddressmode = 2; +uniform int normal_map_knurl_vaddressmode = 2; +uniform sampler2D normal_map_diamondplate_file; +uniform vec2 normal_map_diamondplate_realworld_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_diamondplate_realworld_scale = vec2(1.000000, 1.000000); +uniform vec2 normal_map_diamondplate_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_diamondplate_uv_scale = vec2(1.000000, 1.000000); +uniform float normal_map_diamondplate_rotation_angle = 0.000000; +uniform float normal_map_diamondplate_normal_scale = 1.000000; +uniform int normal_map_diamondplate_uaddressmode = 2; +uniform int normal_map_diamondplate_vaddressmode = 2; +uniform sampler2D normal_map_checkerplate_file; +uniform vec2 normal_map_checkerplate_realworld_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_checkerplate_realworld_scale = vec2(1.000000, 1.000000); +uniform vec2 normal_map_checkerplate_uv_offset = vec2(0.000000, 0.000000); +uniform vec2 normal_map_checkerplate_uv_scale = vec2(1.000000, 1.000000); +uniform float normal_map_checkerplate_rotation_angle = 0.000000; +uniform float normal_map_checkerplate_normal_scale = 1.000000; +uniform int normal_map_checkerplate_uaddressmode = 2; +uniform int normal_map_checkerplate_vaddressmode = 2; +uniform int SR_legacy_metal_type = 3; +uniform vec3 SR_legacy_metal_custom_color = vec3(0.500000, 0.500000, 0.950000); +uniform int SR_legacy_metal_finish = 1; +uniform float SR_legacy_metal_custom_finish = 0.500000; +uniform float SR_legacy_metal_patina = 0.500000; +uniform bool SR_legacy_metal_relief_enable = false; +uniform int SR_legacy_metal_relief = 0; +uniform bool SR_legacy_metal_cutout_enable = true; +uniform int SR_legacy_metal_cutout = 4; +uniform vec3 SR_legacy_metal_custom_cutout = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_legacy_metal_tint_enable = false; +uniform vec3 SR_legacy_metal_tint_color = vec3(0.950000, 0.500000, 0.500000); + +in VertexData +{ + vec2 texcoord_0; + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void mx_rotate_vector2(vec2 _in, float amount, out vec2 result) +{ + float rotationRadians = mx_radians(amount); + float sa = mx_sin(rotationRadians); + float ca = mx_cos(rotationRadians); + result = vec2(ca*_in.x + sa*_in.y, -sa*_in.x + ca*_in.y); +} + +void NG_switch_vector2I(vec2 in1, vec2 in2, vec2 in3, vec2 in4, vec2 in5, vec2 in6, vec2 in7, vec2 in8, vec2 in9, vec2 in10, int which, out vec2 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec2 ifgreater_10_in2_tmp = vec2(0.000000, 0.000000); + vec2 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec2 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec2 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec2 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec2 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec2 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec2 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec2 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec2 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec2 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_place2d_vector2(vec2 texcoord, vec2 pivot, vec2 scale, float rotate, vec2 offset, int operationorder, out vec2 out1) +{ + vec2 N_subpivot_out = texcoord - pivot; + vec2 N_applyscale_out = N_subpivot_out / scale; + vec2 N_applyoffset2_out = N_subpivot_out - offset; + vec2 N_applyrot_out = vec2(0.0); + mx_rotate_vector2(N_applyscale_out, rotate, N_applyrot_out); + vec2 N_applyrot2_out = vec2(0.0); + mx_rotate_vector2(N_applyoffset2_out, rotate, N_applyrot2_out); + vec2 N_applyoffset_out = N_applyrot_out - offset; + vec2 N_applyscale2_out = N_applyrot2_out / scale; + vec2 N_addpivot_out = N_applyoffset_out + pivot; + vec2 N_addpivot2_out = N_applyscale2_out + pivot; + vec2 N_switch_operationorder_out = vec2(0.0); + NG_switch_vector2I(N_addpivot_out, N_addpivot2_out, vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), vec2(0.000000, 0.000000), operationorder, N_switch_operationorder_out); + out1 = N_switch_operationorder_out; +} + +vec2 mx_transform_uv(vec2 uv, vec2 uv_scale, vec2 uv_offset) +{ + uv = uv * uv_scale + uv_offset; + return vec2(uv.x, 1.0 - uv.y); +} + +void mx_image_vector3(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_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, result); +} + +void adsk_NG_adsk_normal_map(sampler2D file, vec2 realworld_offset, vec2 realworld_scale, vec2 uv_offset, vec2 uv_scale, float rotation_angle, float normal_scale, int uaddressmode, int vaddressmode, vec2 texcoord, vec3 normal, vec3 tangent, out vec3 out1) +{ + vec2 total_offset_out = realworld_offset + uv_offset; + vec2 total_scale_out = realworld_scale / uv_scale; + const float rotation_angle_param_in2_tmp = -1.000000; + float rotation_angle_param_out = rotation_angle * rotation_angle_param_in2_tmp; + vec3 normalmap_cross_out = cross(normal, tangent); + vec2 a_place2d_out = vec2(0.0); + NG_place2d_vector2(texcoord, vec2(0.000000, 0.000000), total_scale_out, rotation_angle_param_out, total_offset_out, 1, a_place2d_out); + vec3 normalmap_cross_norm_out = normalize(normalmap_cross_out); + vec3 b_image_out = vec3(0.0); + mx_image_vector3(file, 0, vec3(0.000000, 0.000000, 0.000000), a_place2d_out, uaddressmode, vaddressmode, 1, 0, 0, 0, vec2(1.000000, 1.000000), vec2(0.000000, 0.000000), b_image_out); + const vec3 b_image_negate_y_in2_tmp = vec3(1.000000, -1.000000, 1.000000); + vec3 b_image_negate_y_out = b_image_out * b_image_negate_y_in2_tmp; + const vec3 b_image_invert_y_in1_tmp = vec3(0.000000, 1.000000, 0.000000); + vec3 b_image_invert_y_out = b_image_invert_y_in1_tmp + b_image_negate_y_out; + vec3 impl_normalmap_out = vec3(0.0); + mx_normalmap_float(b_image_invert_y_out, normal_scale, normal, tangent, normalmap_cross_norm_out, impl_normalmap_out); + out1 = impl_normalmap_out; +} + +void NG_switch_floatI(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, int which, out float out1) +{ + const int ifgreater_10_value1_tmp = 10; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_switch_vector3I(vec3 in1, vec3 in2, vec3 in3, vec3 in4, vec3 in5, vec3 in6, vec3 in7, vec3 in8, vec3 in9, vec3 in10, int which, out vec3 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec3 ifgreater_10_in2_tmp = vec3(0.000000, 0.000000, 0.000000); + vec3 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec3 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec3 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec3 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec3 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec3 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec3 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec3 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec3 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec3 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_range_float(float in1, float inlow, float inhigh, float gamma, float outlow, float outhigh, bool doclamp, out float out1) +{ + const float N_remap1_float_outlow_tmp = 0.000000; + const float N_remap1_float_outhigh_tmp = 1.000000; + float N_remap1_float_out = N_remap1_float_outlow_tmp + (in1 - inlow) * (N_remap1_float_outhigh_tmp - N_remap1_float_outlow_tmp) / (inhigh - inlow); + const float N_recip_float_in1_tmp = 1.000000; + float N_recip_float_out = N_recip_float_in1_tmp / gamma; + float N_abs_float_out = abs(N_remap1_float_out); + float N_sign_float_out = sign(N_remap1_float_out); + float N_pow_float_out = pow(N_abs_float_out, N_recip_float_out); + float N_gamma_float_out = N_pow_float_out * N_sign_float_out; + const float N_remap2_float_inlow_tmp = 0.000000; + const float N_remap2_float_inhigh_tmp = 1.000000; + float N_remap2_float_out = outlow + (N_gamma_float_out - N_remap2_float_inlow_tmp) * (outhigh - outlow) / (N_remap2_float_inhigh_tmp - N_remap2_float_inlow_tmp); + float N_clamp_float_out = clamp(N_remap2_float_out, outlow, outhigh); + const bool N_switch_float_value2_tmp = true; + float N_switch_float_out = (doclamp == N_switch_float_value2_tmp) ? N_clamp_float_out : N_remap2_float_out; + out1 = N_switch_float_out; +} + +void NG_separate2_vector2(vec2 in1, out float outx, out float outy) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; +} + +void NG_circle_float(vec2 texcoord, vec2 center, float radius, out float out1) +{ + vec2 delta_out = texcoord - center; + float rad_square_out = radius * radius; + float dist_square_out = dot(delta_out, delta_out); + const float dist_comp_in1_tmp = 0.000000; + const float dist_comp_in2_tmp = 1.000000; + float dist_comp_out = (dist_square_out > rad_square_out) ? dist_comp_in1_tmp : dist_comp_in2_tmp; + out1 = dist_comp_out; +} + +void NG_convert_float_color3(float in1, out vec3 out1) +{ + vec3 combine_out = vec3(in1,in1,in1); + out1 = combine_out; +} + +void NG_tiledcircles_color3(vec2 texcoord, vec2 uvtiling, vec2 uvoffset, float size, bool staggered, out vec3 out1) +{ + vec2 texcoord_scale_out = texcoord * uvtiling; + const float scale_half_in2_tmp = 2.000000; + float scale_half_out = size / scale_half_in2_tmp; + vec2 texcoord_bias_out = texcoord_scale_out - uvoffset; + float texcoord_bias_separate_outx = 0.0; + float texcoord_bias_separate_outy = 0.0; + NG_separate2_vector2(texcoord_bias_out, texcoord_bias_separate_outx, texcoord_bias_separate_outy); + const vec2 mod_texcoord_in2_tmp = vec2(1.000000, 1.000000); + vec2 mod_texcoord_out = mx_mod(texcoord_bias_out, mod_texcoord_in2_tmp); + const float stagg_Y_in2_tmp = 1.732050; + float stagg_Y_out = mx_mod(texcoord_bias_separate_outy, stagg_Y_in2_tmp); + const float mod_Y_1_in2_tmp = 0.866025; + float mod_Y_1_out = mx_mod(texcoord_bias_separate_outy, mod_Y_1_in2_tmp); + const float mod_texcoord_2_in2_tmp = 2.000000; + vec2 mod_texcoord_2_out = mod_texcoord_out * mod_texcoord_2_in2_tmp; + const float delta_X_value2_tmp = 0.866025; + const float delta_X_in1_tmp = 0.500000; + const float delta_X_in2_tmp = 0.000000; + float delta_X_out = (stagg_Y_out > delta_X_value2_tmp) ? delta_X_in1_tmp : delta_X_in2_tmp; + const float coord_adj_3_in1_tmp = 0.866025; + float coord_adj_3_out = coord_adj_3_in1_tmp - mod_Y_1_out; + const float recenter_in2_tmp = 1.000000; + vec2 recenter_out = mod_texcoord_2_out - recenter_in2_tmp; + float shift_X_out = texcoord_bias_separate_outx + delta_X_out; + float circle_regular_out = 0.0; + NG_circle_float(recenter_out, vec2(0.000000, 0.000000), size, circle_regular_out); + const float mod_X_1_in2_tmp = 1.000000; + float mod_X_1_out = mx_mod(shift_X_out, mod_X_1_in2_tmp); + vec2 coord_circ1_out = vec2(mod_X_1_out,mod_Y_1_out); + const float coord_adj_1_in1_tmp = 1.000000; + float coord_adj_1_out = coord_adj_1_in1_tmp - mod_X_1_out; + const float coord_adj_2_in2_tmp = 0.500000; + float coord_adj_2_out = mod_X_1_out - coord_adj_2_in2_tmp; + float circle_stagg1_out = 0.0; + NG_circle_float(coord_circ1_out, vec2(0.000000, 0.000000), scale_half_out, circle_stagg1_out); + vec2 coord_circ2_out = vec2(coord_adj_1_out,mod_Y_1_out); + vec2 coord_circ3_out = vec2(coord_adj_2_out,coord_adj_3_out); + float circle_stagg2_out = 0.0; + NG_circle_float(coord_circ2_out, vec2(0.000000, 0.000000), scale_half_out, circle_stagg2_out); + float circle_stagg3_out = 0.0; + NG_circle_float(coord_circ3_out, vec2(0.000000, 0.000000), scale_half_out, circle_stagg3_out); + float max1_out = max(circle_stagg1_out, circle_stagg2_out); + float max_out = max(max1_out, circle_stagg3_out); + const bool pattern_selection_value2_tmp = true; + float pattern_selection_out = (staggered == pattern_selection_value2_tmp) ? max_out : circle_regular_out; + vec3 to_rgb_out = vec3(0.0); + NG_convert_float_color3(pattern_selection_out, to_rgb_out); + out1 = to_rgb_out; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void NG_hexagon_float(vec2 texcoord, vec2 center, float radius, out float out1) +{ + vec2 delta_out = texcoord - center; + float k_separate_outx = 0.0; + float k_separate_outy = 0.0; + float k_separate_outz = 0.0; + NG_separate3_vector3(vec3(-0.866025, 0.500000, 0.577350), k_separate_outx, k_separate_outy, k_separate_outz); + const vec3 minus_k_in1_tmp = vec3(-0.866025, 0.500000, 0.577350); + const float minus_k_in2_tmp = -1.000000; + vec3 minus_k_out = minus_k_in1_tmp * minus_k_in2_tmp; + vec2 delta_abs_out = abs(delta_out); + vec2 kxy_out = vec2(k_separate_outx,k_separate_outy); + float kz_r1_out = k_separate_outz * radius; + float minus_k_separate_outx = 0.0; + float minus_k_separate_outy = 0.0; + float minus_k_separate_outz = 0.0; + NG_separate3_vector3(minus_k_out, minus_k_separate_outx, minus_k_separate_outy, minus_k_separate_outz); + float delta_abs_separate_outx = 0.0; + float delta_abs_separate_outy = 0.0; + NG_separate2_vector2(delta_abs_out, delta_abs_separate_outx, delta_abs_separate_outy); + vec2 combine_mkx_ky_out = vec2(minus_k_separate_outx,k_separate_outy); + float minus_kz_r_out = minus_k_separate_outz * radius; + vec2 p_out = vec2(delta_abs_separate_outy,delta_abs_separate_outx); + float dot_kxy_p_out = dot(kxy_out, p_out); + const float min_dotkxyp_p_in2_tmp = 0.000000; + float min_dotkxyp_p_out = min(dot_kxy_p_out, min_dotkxyp_p_in2_tmp); + vec2 multiply_kxy_min_out = kxy_out * min_dotkxyp_p_out; + const float multiply2_1_in2_tmp = 2.000000; + vec2 multiply2_1_out = multiply_kxy_min_out * multiply2_1_in2_tmp; + vec2 new_p1_out = p_out - multiply2_1_out; + float dot_kxy_p1_out = dot(combine_mkx_ky_out, new_p1_out); + const float min_0_in2_tmp = 0.000000; + float min_0_out = min(dot_kxy_p1_out, min_0_in2_tmp); + vec2 multiply_min_comb_out = combine_mkx_ky_out * min_0_out; + const float multiply2_2_in2_tmp = 2.000000; + vec2 multiply2_2_out = multiply_min_comb_out * multiply2_2_in2_tmp; + vec2 new_p2_out = new_p1_out - multiply2_2_out; + const int new_p2_x_index_tmp = 0; + float new_p2_x_out = new_p2_out[new_p2_x_index_tmp]; + float clamp_out = clamp(new_p2_x_out, minus_kz_r_out, kz_r1_out); + vec2 combine_clamp_rad_out = vec2(clamp_out,radius); + vec2 new_p3_out = new_p2_out - combine_clamp_rad_out; + const vec2 p3_sum_in2_tmp = vec2(1.000000, 1.000000); + float p3_sum_out = dot(new_p3_out, p3_sum_in2_tmp); + float p3_sqrt_out = sqrt(p3_sum_out); + const float ifgreater_p3_value2_tmp = 0.000000; + const float ifgreater_p3_in1_tmp = 0.000000; + const float ifgreater_p3_in2_tmp = 1.000000; + float ifgreater_p3_out = (p3_sqrt_out > ifgreater_p3_value2_tmp) ? ifgreater_p3_in1_tmp : ifgreater_p3_in2_tmp; + out1 = ifgreater_p3_out; +} + +void NG_tiledhexagons_color3(vec2 texcoord, vec2 uvtiling, vec2 uvoffset, float size, bool staggered, out vec3 out1) +{ + vec2 texcoord_scale_out = texcoord * uvtiling; + const float scale_half_in2_tmp = 2.000000; + float scale_half_out = size / scale_half_in2_tmp; + vec2 texcoord_bias_out = texcoord_scale_out - uvoffset; + float texcoord_bias_separate_outx = 0.0; + float texcoord_bias_separate_outy = 0.0; + NG_separate2_vector2(texcoord_bias_out, texcoord_bias_separate_outx, texcoord_bias_separate_outy); + const vec2 mod_texcoord_in2_tmp = vec2(1.000000, 1.000000); + vec2 mod_texcoord_out = mx_mod(texcoord_bias_out, mod_texcoord_in2_tmp); + const float stagg_Y_in2_tmp = 1.732050; + float stagg_Y_out = mx_mod(texcoord_bias_separate_outy, stagg_Y_in2_tmp); + const float mod_Y_1_in2_tmp = 0.866025; + float mod_Y_1_out = mx_mod(texcoord_bias_separate_outy, mod_Y_1_in2_tmp); + const float mod_texcoord_2_in2_tmp = 2.000000; + vec2 mod_texcoord_2_out = mod_texcoord_out * mod_texcoord_2_in2_tmp; + const float delta_X_value2_tmp = 0.866025; + const float delta_X_in1_tmp = 0.500000; + const float delta_X_in2_tmp = 0.000000; + float delta_X_out = (stagg_Y_out > delta_X_value2_tmp) ? delta_X_in1_tmp : delta_X_in2_tmp; + const float coord_adj_3_in1_tmp = 0.866025; + float coord_adj_3_out = coord_adj_3_in1_tmp - mod_Y_1_out; + const float recenter_in2_tmp = 1.000000; + vec2 recenter_out = mod_texcoord_2_out - recenter_in2_tmp; + float shift_X_out = texcoord_bias_separate_outx + delta_X_out; + float hexagon_regular_out = 0.0; + NG_hexagon_float(recenter_out, vec2(0.000000, 0.000000), size, hexagon_regular_out); + const float mod_X_1_in2_tmp = 1.000000; + float mod_X_1_out = mx_mod(shift_X_out, mod_X_1_in2_tmp); + vec2 coord_circ1_out = vec2(mod_X_1_out,mod_Y_1_out); + const float coord_adj_1_in1_tmp = 1.000000; + float coord_adj_1_out = coord_adj_1_in1_tmp - mod_X_1_out; + const float coord_adj_2_in2_tmp = 0.500000; + float coord_adj_2_out = mod_X_1_out - coord_adj_2_in2_tmp; + float hexagon_stagg1_out = 0.0; + NG_hexagon_float(coord_circ1_out, vec2(0.000000, 0.000000), scale_half_out, hexagon_stagg1_out); + vec2 coord_circ2_out = vec2(coord_adj_1_out,mod_Y_1_out); + vec2 coord_circ3_out = vec2(coord_adj_2_out,coord_adj_3_out); + float hexagon_stagg2_out = 0.0; + NG_hexagon_float(coord_circ2_out, vec2(0.000000, 0.000000), scale_half_out, hexagon_stagg2_out); + float hexagon_stagg3_out = 0.0; + NG_hexagon_float(coord_circ3_out, vec2(0.000000, 0.000000), scale_half_out, hexagon_stagg3_out); + float max1_out = max(hexagon_stagg1_out, hexagon_stagg2_out); + float max_out = max(max1_out, hexagon_stagg3_out); + const bool pattern_selection_value2_tmp = true; + float pattern_selection_out = (staggered == pattern_selection_value2_tmp) ? max_out : hexagon_regular_out; + vec3 to_rgb_out = vec3(0.0); + NG_convert_float_color3(pattern_selection_out, to_rgb_out); + out1 = to_rgb_out; +} + +void NG_grid_color3(vec2 texcoord, vec2 uvtiling, vec2 uvoffset, float thickness, bool staggered, out vec3 out1) +{ + vec2 texcoord_scale_out = texcoord * uvtiling; + const float thick_to_size_in1_tmp = 1.000000; + float thick_to_size_out = thick_to_size_in1_tmp - thickness; + vec2 texcoord_bias_out = texcoord_scale_out - uvoffset; + float texcoord_bias_separate_outx = 0.0; + float texcoord_bias_separate_outy = 0.0; + NG_separate2_vector2(texcoord_bias_out, texcoord_bias_separate_outx, texcoord_bias_separate_outy); + const float mod_Y_row_in2_tmp = 2.000000; + float mod_Y_row_out = mx_mod(texcoord_bias_separate_outy, mod_Y_row_in2_tmp); + const float mod_Y_in2_tmp = 1.000000; + float mod_Y_out = mx_mod(texcoord_bias_separate_outy, mod_Y_in2_tmp); + const float alt_rows_shift_value2_tmp = 1.000000; + const float alt_rows_shift_in1_tmp = 0.500000; + const float alt_rows_shift_in2_tmp = 0.000000; + float alt_rows_shift_out = (mod_Y_row_out > alt_rows_shift_value2_tmp) ? alt_rows_shift_in1_tmp : alt_rows_shift_in2_tmp; + const float mody_2_in2_tmp = 2.000000; + float mody_2_out = mod_Y_out * mody_2_in2_tmp; + float shift_X_out = texcoord_bias_separate_outx + alt_rows_shift_out; + const float subY_1_in2_tmp = 1.000000; + float subY_1_out = mody_2_out - subY_1_in2_tmp; + const bool stagger_selection_value2_tmp = true; + float stagger_selection_out = (staggered == stagger_selection_value2_tmp) ? shift_X_out : texcoord_bias_separate_outx; + float abs_Y_out = abs(subY_1_out); + const float mod_X_in2_tmp = 1.000000; + float mod_X_out = mx_mod(stagger_selection_out, mod_X_in2_tmp); + const float Y_detect_in1_tmp = 0.000000; + const float Y_detect_in2_tmp = 1.000000; + float Y_detect_out = (abs_Y_out > thick_to_size_out) ? Y_detect_in1_tmp : Y_detect_in2_tmp; + const float modx_2_in2_tmp = 2.000000; + float modx_2_out = mod_X_out * modx_2_in2_tmp; + const float subX_1_in2_tmp = 1.000000; + float subX_1_out = modx_2_out - subX_1_in2_tmp; + float abs_X_out = abs(subX_1_out); + const float X_detect_in1_tmp = 0.000000; + const float X_detect_in2_tmp = 1.000000; + float X_detect_out = (abs_X_out > thick_to_size_out) ? X_detect_in1_tmp : X_detect_in2_tmp; + float min_out = min(X_detect_out, Y_detect_out); + const float inv_result_in1_tmp = 1.000000; + float inv_result_out = inv_result_in1_tmp - min_out; + vec3 to_rgb_out = vec3(0.0); + NG_convert_float_color3(inv_result_out, to_rgb_out); + out1 = to_rgb_out; +} + +void NG_distance_vector2(vec2 in1, vec2 in2, out float out1) +{ + vec2 N_mtlxsubtract_out = in1 - in2; + float N_mtlxmagnitude_out = length(N_mtlxsubtract_out); + out1 = N_mtlxmagnitude_out; +} + +void NG_line_float(vec2 texcoord, vec2 center, float radius, vec2 point1, vec2 point2, out float out1) +{ + vec2 delta_out = texcoord - center; + vec2 b_a_out = point2 - point1; + vec2 p_a_out = delta_out - point1; + float dot_ba_ba_out = dot(b_a_out, b_a_out); + float dot_pa_ba_out = dot(p_a_out, b_a_out); + float divide_dots_out = dot_pa_ba_out / dot_ba_ba_out; + const float clamp_low_tmp = 0.000000; + const float clamp_high_tmp = 1.000000; + float clamp_out = clamp(divide_dots_out, clamp_low_tmp, clamp_high_tmp); + vec2 multiply_clamp_ba_out = b_a_out * clamp_out; + float distance_out = 0.0; + NG_distance_vector2(p_a_out, multiply_clamp_ba_out, distance_out); + const float dist_comp_in1_tmp = 0.000000; + const float dist_comp_in2_tmp = 1.000000; + float dist_comp_out = (distance_out > radius) ? dist_comp_in1_tmp : dist_comp_in2_tmp; + out1 = dist_comp_out; +} + +void NG_crosshatch_color3(vec2 texcoord, vec2 uvtiling, vec2 uvoffset, float thickness, bool staggered, out vec3 out1) +{ + vec2 texcoord_scale_out = texcoord * uvtiling; + vec2 texcoord_bias_out = texcoord_scale_out - uvoffset; + float texcoord_bias_separate_outx = 0.0; + float texcoord_bias_separate_outy = 0.0; + NG_separate2_vector2(texcoord_bias_out, texcoord_bias_separate_outx, texcoord_bias_separate_outy); + const float mod_Y_row_in2_tmp = 2.000000; + float mod_Y_row_out = mx_mod(texcoord_bias_separate_outy, mod_Y_row_in2_tmp); + const float mod_Y_in2_tmp = 1.000000; + float mod_Y_out = mx_mod(texcoord_bias_separate_outy, mod_Y_in2_tmp); + const float alt_rows_shift_value2_tmp = 1.000000; + const float alt_rows_shift_in1_tmp = 0.500000; + const float alt_rows_shift_in2_tmp = 0.000000; + float alt_rows_shift_out = (mod_Y_row_out > alt_rows_shift_value2_tmp) ? alt_rows_shift_in1_tmp : alt_rows_shift_in2_tmp; + const float mody_2_in2_tmp = 2.000000; + float mody_2_out = mod_Y_out * mody_2_in2_tmp; + float shift_X_out = texcoord_bias_separate_outx + alt_rows_shift_out; + const float subY_1_in2_tmp = 1.000000; + float subY_1_out = mody_2_out - subY_1_in2_tmp; + const bool stagger_selection_value2_tmp = true; + float stagger_selection_out = (staggered == stagger_selection_value2_tmp) ? shift_X_out : texcoord_bias_separate_outx; + const float mod_X_in2_tmp = 1.000000; + float mod_X_out = mx_mod(stagger_selection_out, mod_X_in2_tmp); + const float modx_2_in2_tmp = 2.000000; + float modx_2_out = mod_X_out * modx_2_in2_tmp; + const float subX_1_in2_tmp = 1.000000; + float subX_1_out = modx_2_out - subX_1_in2_tmp; + vec2 sample_vec_out = vec2(subX_1_out,subY_1_out); + float line_diag1_out = 0.0; + NG_line_float(sample_vec_out, vec2(0.000000, 0.000000), thickness, vec2(1.000000, 1.000000), vec2(-1.000000, -1.000000), line_diag1_out); + float line_diag2_out = 0.0; + NG_line_float(sample_vec_out, vec2(0.000000, 0.000000), thickness, vec2(-1.000000, 1.000000), vec2(1.000000, -1.000000), line_diag2_out); + float composite_diags_out = max(line_diag1_out, line_diag2_out); + float max_out = max(composite_diags_out, composite_diags_out); + vec3 to_rgb_out = vec3(0.0); + NG_convert_float_color3(max_out, to_rgb_out); + out1 = to_rgb_out; +} + +void NG_cloverleaf_float(vec2 texcoord, vec2 center, float radius, out float out1) +{ + vec2 sample_double_out = texcoord + texcoord; + vec2 center_double_out = center + center; + vec2 sample_add_out = sample_double_out + radius; + float sample_double_separate_outx = 0.0; + float sample_double_separate_outy = 0.0; + NG_separate2_vector2(sample_double_out, sample_double_separate_outx, sample_double_separate_outy); + vec2 sample_subtract_out = sample_double_out - radius; + float sample_add_separate_outx = 0.0; + float sample_add_separate_outy = 0.0; + NG_separate2_vector2(sample_add_out, sample_add_separate_outx, sample_add_separate_outy); + float sample_subtract_separate_outx = 0.0; + float sample_subtract_separate_outy = 0.0; + NG_separate2_vector2(sample_subtract_out, sample_subtract_separate_outx, sample_subtract_separate_outy); + vec2 coord1_out = vec2(sample_add_separate_outx,sample_double_separate_outy); + vec2 coord4_out = vec2(sample_double_separate_outx,sample_add_separate_outy); + vec2 coord2_out = vec2(sample_subtract_separate_outx,sample_double_separate_outy); + vec2 coord3_out = vec2(sample_double_separate_outx,sample_subtract_separate_outy); + float circle1_out = 0.0; + NG_circle_float(coord1_out, center_double_out, radius, circle1_out); + float circle4_out = 0.0; + NG_circle_float(coord4_out, center_double_out, radius, circle4_out); + float circle2_out = 0.0; + NG_circle_float(coord2_out, center_double_out, radius, circle2_out); + float circle3_out = 0.0; + NG_circle_float(coord3_out, center_double_out, radius, circle3_out); + float max1_out = max(circle1_out, circle2_out); + float max2_out = max(circle3_out, circle4_out); + float max_out = max(max1_out, max2_out); + out1 = max_out; +} + +void NG_tiledcloverleafs_color3(vec2 texcoord, vec2 uvtiling, vec2 uvoffset, float size, bool staggered, out vec3 out1) +{ + vec2 texcoord_scale_out = texcoord * uvtiling; + const float scale_half_in2_tmp = 2.000000; + float scale_half_out = size / scale_half_in2_tmp; + vec2 texcoord_bias_out = texcoord_scale_out - uvoffset; + float texcoord_bias_separate_outx = 0.0; + float texcoord_bias_separate_outy = 0.0; + NG_separate2_vector2(texcoord_bias_out, texcoord_bias_separate_outx, texcoord_bias_separate_outy); + const vec2 mod_texcoord_in2_tmp = vec2(1.000000, 1.000000); + vec2 mod_texcoord_out = mx_mod(texcoord_bias_out, mod_texcoord_in2_tmp); + const float stagg_Y_in2_tmp = 1.000000; + float stagg_Y_out = mx_mod(texcoord_bias_separate_outy, stagg_Y_in2_tmp); + const float mod_Y_1_in2_tmp = 0.500000; + float mod_Y_1_out = mx_mod(texcoord_bias_separate_outy, mod_Y_1_in2_tmp); + const float mod_texcoord_2_in2_tmp = 2.000000; + vec2 mod_texcoord_2_out = mod_texcoord_out * mod_texcoord_2_in2_tmp; + const float delta_X_value2_tmp = 0.500000; + const float delta_X_in1_tmp = 0.500000; + const float delta_X_in2_tmp = 0.000000; + float delta_X_out = (stagg_Y_out > delta_X_value2_tmp) ? delta_X_in1_tmp : delta_X_in2_tmp; + const float coord_adj_3_in1_tmp = 0.500000; + float coord_adj_3_out = coord_adj_3_in1_tmp - mod_Y_1_out; + const float recenter_in2_tmp = 1.000000; + vec2 recenter_out = mod_texcoord_2_out - recenter_in2_tmp; + float shift_X_out = texcoord_bias_separate_outx + delta_X_out; + float cloverleaf_regular_out = 0.0; + NG_cloverleaf_float(recenter_out, vec2(0.000000, 0.000000), size, cloverleaf_regular_out); + const float mod_X_1_in2_tmp = 1.000000; + float mod_X_1_out = mx_mod(shift_X_out, mod_X_1_in2_tmp); + vec2 coord_circ1_out = vec2(mod_X_1_out,mod_Y_1_out); + const float coord_adj_1_in1_tmp = 1.000000; + float coord_adj_1_out = coord_adj_1_in1_tmp - mod_X_1_out; + const float coord_adj_2_in2_tmp = 0.500000; + float coord_adj_2_out = mod_X_1_out - coord_adj_2_in2_tmp; + float cloverleaf_stagg1_out = 0.0; + NG_cloverleaf_float(coord_circ1_out, vec2(0.000000, 0.000000), scale_half_out, cloverleaf_stagg1_out); + vec2 coord_circ2_out = vec2(coord_adj_1_out,mod_Y_1_out); + vec2 coord_circ3_out = vec2(coord_adj_2_out,coord_adj_3_out); + float cloverleaf_stagg2_out = 0.0; + NG_cloverleaf_float(coord_circ2_out, vec2(0.000000, 0.000000), scale_half_out, cloverleaf_stagg2_out); + float cloverleaf_stagg3_out = 0.0; + NG_cloverleaf_float(coord_circ3_out, vec2(0.000000, 0.000000), scale_half_out, cloverleaf_stagg3_out); + float max1_out = max(cloverleaf_stagg1_out, cloverleaf_stagg2_out); + float max_out = max(max1_out, cloverleaf_stagg3_out); + const bool pattern_selection_value2_tmp = true; + float pattern_selection_out = (staggered == pattern_selection_value2_tmp) ? max_out : cloverleaf_regular_out; + vec3 to_rgb_out = vec3(0.0); + NG_convert_float_color3(pattern_selection_out, to_rgb_out); + out1 = to_rgb_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_noise3d_float(float amplitude, float pivot, vec3 position, out float result) +{ + float value = mx_perlin_noise_float(position); + result = value * amplitude + pivot; +} + +void NG_switch_float(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, float which, out float out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_fractal3d_max8_float(vec3 position, float octaves, float amplitude, out float out1) +{ + float noise3d1_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d1_out); + const float multiply2_in2_tmp = 2.000000; + vec3 multiply2_out = position * multiply2_in2_tmp; + const float multiply3_in2_tmp = 4.000000; + vec3 multiply3_out = position * multiply3_in2_tmp; + const float multiply4_in2_tmp = 8.000000; + vec3 multiply4_out = position * multiply4_in2_tmp; + const float multiply5_in2_tmp = 16.000000; + vec3 multiply5_out = position * multiply5_in2_tmp; + const float multiply6_in2_tmp = 32.000000; + vec3 multiply6_out = position * multiply6_in2_tmp; + const float multiply7_in2_tmp = 64.000000; + vec3 multiply7_out = position * multiply7_in2_tmp; + const float multiply8_in2_tmp = 128.000000; + vec3 multiply8_out = position * multiply8_in2_tmp; + const float modulo_octave_in2_tmp = 5.000000; + float modulo_octave_out = mx_mod(octaves, modulo_octave_in2_tmp); + const float divide_octave_in2_tmp = 5.000000; + float divide_octave_out = octaves / divide_octave_in2_tmp; + const float modulo_fractional_in2_tmp = 1.000000; + float modulo_fractional_out = mx_mod(octaves, modulo_fractional_in2_tmp); + float noise3d2_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply2_out, noise3d2_out); + float noise3d3_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply3_out, noise3d3_out); + float noise3d4_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply4_out, noise3d4_out); + float noise3d5_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply5_out, noise3d5_out); + float noise3d6_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply6_out, noise3d6_out); + float noise3d7_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply7_out, noise3d7_out); + float noise3d8_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply8_out, noise3d8_out); + float multiply9_out = noise3d1_out * modulo_fractional_out; + const float divide2_in2_tmp = 2.000000; + float divide2_out = noise3d2_out / divide2_in2_tmp; + const float divide3_in2_tmp = 4.000000; + float divide3_out = noise3d3_out / divide3_in2_tmp; + const float divide4_in2_tmp = 8.000000; + float divide4_out = noise3d4_out / divide4_in2_tmp; + const float divide5_in2_tmp = 16.000000; + float divide5_out = noise3d5_out / divide5_in2_tmp; + const float divide6_in2_tmp = 32.000000; + float divide6_out = noise3d6_out / divide6_in2_tmp; + const float divide7_in2_tmp = 64.000000; + float divide7_out = noise3d7_out / divide7_in2_tmp; + const float divide8_in2_tmp = 128.000000; + float divide8_out = noise3d8_out / divide8_in2_tmp; + float add2_out = noise3d1_out + divide2_out; + float multiply10_out = divide2_out * modulo_fractional_out; + float multiply11_out = divide3_out * modulo_fractional_out; + float multiply12_out = divide4_out * modulo_fractional_out; + float multiply13_out = divide5_out * modulo_fractional_out; + float multiply14_out = divide6_out * modulo_fractional_out; + float multiply15_out = divide7_out * modulo_fractional_out; + float multiply16_out = divide8_out * modulo_fractional_out; + float add3_out = add2_out + divide3_out; + float switch3_out = 0.0; + NG_switch_float(multiply9_out, multiply10_out, multiply11_out, multiply12_out, multiply13_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch3_out); + float switch4_out = 0.0; + NG_switch_float(multiply14_out, multiply15_out, multiply16_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch4_out); + float add4_out = add3_out + divide4_out; + float switch_fractional_out = 0.0; + NG_switch_float(switch3_out, switch4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_fractional_out); + float add5_out = add4_out + divide5_out; + float switch1_out = 0.0; + NG_switch_float(0.000000, noise3d1_out, add2_out, add3_out, add4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch1_out); + float add6_out = add5_out + divide6_out; + float add7_out = add6_out + divide7_out; + float add8_out = add7_out + divide8_out; + float switch2_out = 0.0; + NG_switch_float(add5_out, add6_out, add7_out, add8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch2_out); + float switch_octave_out = 0.0; + NG_switch_float(switch1_out, switch2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_out); + float add_fractional_out = switch_octave_out + switch_fractional_out; + const float ifgreatereq_octave_value2_tmp = 8.000000; + float ifgreatereq_octave_out = (octaves >= ifgreatereq_octave_value2_tmp) ? add8_out : add_fractional_out; + float multiply_amplitude_out = ifgreatereq_octave_out * amplitude; + out1 = multiply_amplitude_out; +} + +void NG_turbulence3d_max8_float(vec3 position, float octaves, float amplitude, out float out1) +{ + const float multiply8_in2_tmp = 128.000000; + vec3 multiply8_out = position * multiply8_in2_tmp; + const float multiply7_in2_tmp = 64.000000; + vec3 multiply7_out = position * multiply7_in2_tmp; + const float multiply6_in2_tmp = 32.000000; + vec3 multiply6_out = position * multiply6_in2_tmp; + const float multiply5_in2_tmp = 16.000000; + vec3 multiply5_out = position * multiply5_in2_tmp; + const float multiply4_in2_tmp = 8.000000; + vec3 multiply4_out = position * multiply4_in2_tmp; + const float multiply3_in2_tmp = 4.000000; + vec3 multiply3_out = position * multiply3_in2_tmp; + const float multiply2_in2_tmp = 2.000000; + vec3 multiply2_out = position * multiply2_in2_tmp; + float noise3d1_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d1_out); + const float modulo_octave_in2_tmp = 5.000000; + float modulo_octave_out = mx_mod(octaves, modulo_octave_in2_tmp); + const float divide_octave_in2_tmp = 5.000000; + float divide_octave_out = octaves / divide_octave_in2_tmp; + const float modulo_fractional_in2_tmp = 1.000000; + float modulo_fractional_out = mx_mod(octaves, modulo_fractional_in2_tmp); + float noise3d8_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply8_out, noise3d8_out); + float noise3d7_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply7_out, noise3d7_out); + float noise3d6_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply6_out, noise3d6_out); + float noise3d5_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply5_out, noise3d5_out); + float noise3d4_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply4_out, noise3d4_out); + float noise3d3_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply3_out, noise3d3_out); + float noise3d2_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply2_out, noise3d2_out); + float absval1_out = abs(noise3d1_out); + const float divide8_in2_tmp = 128.000000; + float divide8_out = noise3d8_out / divide8_in2_tmp; + const float divide7_in2_tmp = 64.000000; + float divide7_out = noise3d7_out / divide7_in2_tmp; + const float divide6_in2_tmp = 32.000000; + float divide6_out = noise3d6_out / divide6_in2_tmp; + const float divide5_in2_tmp = 16.000000; + float divide5_out = noise3d5_out / divide5_in2_tmp; + const float divide4_in2_tmp = 8.000000; + float divide4_out = noise3d4_out / divide4_in2_tmp; + const float divide3_in2_tmp = 4.000000; + float divide3_out = noise3d3_out / divide3_in2_tmp; + const float divide2_in2_tmp = 2.000000; + float divide2_out = noise3d2_out / divide2_in2_tmp; + float multiply9_out = absval1_out * modulo_fractional_out; + float absval8_out = abs(divide8_out); + float absval7_out = abs(divide7_out); + float absval6_out = abs(divide6_out); + float absval5_out = abs(divide5_out); + float absval4_out = abs(divide4_out); + float absval3_out = abs(divide3_out); + float absval2_out = abs(divide2_out); + float multiply16_out = absval8_out * modulo_fractional_out; + float multiply15_out = absval7_out * modulo_fractional_out; + float multiply14_out = absval6_out * modulo_fractional_out; + float multiply13_out = absval5_out * modulo_fractional_out; + float multiply12_out = absval4_out * modulo_fractional_out; + float multiply11_out = absval3_out * modulo_fractional_out; + float add2_out = absval1_out + absval2_out; + float multiply10_out = absval2_out * modulo_fractional_out; + float switch4_out = 0.0; + NG_switch_float(multiply14_out, multiply15_out, multiply16_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch4_out); + float add3_out = add2_out + absval3_out; + float switch3_out = 0.0; + NG_switch_float(multiply9_out, multiply10_out, multiply11_out, multiply12_out, multiply13_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch3_out); + float add4_out = add3_out + absval4_out; + float switch_fractional_out = 0.0; + NG_switch_float(switch3_out, switch4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_fractional_out); + float add5_out = add4_out + absval5_out; + float switch1_out = 0.0; + NG_switch_float(0.000000, absval1_out, add2_out, add3_out, add4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch1_out); + float add6_out = add5_out + absval6_out; + float add7_out = add6_out + absval7_out; + float add8_out = add7_out + absval8_out; + float switch2_out = 0.0; + NG_switch_float(add5_out, add6_out, add7_out, add8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch2_out); + float switch_octave_out = 0.0; + NG_switch_float(switch1_out, switch2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_out); + float add_fractional_out = switch_octave_out + switch_fractional_out; + const float ifgreatereq_octave_value2_tmp = 8.000000; + float ifgreatereq_octave_out = (octaves >= ifgreatereq_octave_value2_tmp) ? add8_out : add_fractional_out; + float multiply_amplitude_out = ifgreatereq_octave_out * amplitude; + out1 = multiply_amplitude_out; +} + +void mx_smoothstep_float(float val, float low, float high, out float result) +{ + if (val >= high) + result = 1.0; + else if (val <= low) + result = 0.0; + else + result = smoothstep(low, high, val); +} + +void NG_legacy_noise_color3(vec3 position, vec3 color1, vec3 color2, int noise_type, float size, float threshold_low, float threshold_high, bool threshold_smooth, float phase, float levels, vec3 realworld_offset, vec3 rotation_angle, out vec3 output_color3) +{ + const float modulo_phase_in2_tmp = 1.000000; + float modulo_phase_out = mx_mod(phase, modulo_phase_in2_tmp); + const float inv_size_in1_tmp = 1.000000; + float inv_size_out = inv_size_in1_tmp / size; + float rotation_xyz_outx = 0.0; + float rotation_xyz_outy = 0.0; + float rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, rotation_xyz_outx, rotation_xyz_outy, rotation_xyz_outz); + const float mult_phase_in2_tmp = 2.000000; + float mult_phase_out = modulo_phase_out * mult_phase_in2_tmp; + vec3 adj_coord_out = position * inv_size_out; + vec3 add_offset_out = adj_coord_out - realworld_offset; + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(add_offset_out, rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + float noise3d_float_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, rotate3d_z_out, noise3d_float_out); + float fractal3d_max8_out = 0.0; + NG_fractal3d_max8_float(rotate3d_z_out, levels, 1.000000, fractal3d_max8_out); + float turbulence3d_max8_out = 0.0; + NG_turbulence3d_max8_float(rotate3d_z_out, levels, 1.000000, turbulence3d_max8_out); + float range_noise3d_out = 0.0; + NG_range_float(noise3d_float_out, -1.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_noise3d_out); + float range_fractal3d_out = 0.0; + NG_range_float(fractal3d_max8_out, -1.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_fractal3d_out); + float range_turbulence3d_out = 0.0; + NG_range_float(turbulence3d_max8_out, 0.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_turbulence3d_out); + float switch_type_out = 0.0; + NG_switch_floatI(range_noise3d_out, range_fractal3d_out, range_turbulence3d_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, noise_type, switch_type_out); + float add_noise1_out = switch_type_out + mult_phase_out; + const float modulo_ramp_in2_tmp = 1.000000; + float modulo_ramp_out = mx_mod(add_noise1_out, modulo_ramp_in2_tmp); + const float modulo_odd_even_in2_tmp = 2.000000; + float modulo_odd_even_out = mx_mod(add_noise1_out, modulo_odd_even_in2_tmp); + const float invert_ramp_amount_tmp = 1.000000; + float invert_ramp_out = invert_ramp_amount_tmp - modulo_ramp_out; + const float if_odd_even_value2_tmp = 1.000000; + float if_odd_even_out = (modulo_odd_even_out >= if_odd_even_value2_tmp) ? invert_ramp_out : modulo_ramp_out; + float lowhi_range_smooth_out = 0.0; + mx_smoothstep_float(if_odd_even_out, threshold_low, threshold_high, lowhi_range_smooth_out); + float lowhi_range_out = 0.0; + NG_range_float(if_odd_even_out, threshold_low, threshold_high, 1.000000, 0.000000, 1.000000, true, lowhi_range_out); + const bool smooth_select_value2_tmp = true; + float smooth_select_out = (threshold_smooth == smooth_select_value2_tmp) ? lowhi_range_smooth_out : lowhi_range_out; + vec3 mix_colors_out = mix(color2, color1, smooth_select_out); + output_color3 = mix_colors_out; +} + +void NG_switch_color3(vec3 in1, vec3 in2, vec3 in3, vec3 in4, vec3 in5, vec3 in6, vec3 in7, vec3 in8, vec3 in9, vec3 in10, float which, out vec3 out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const vec3 ifgreater_10_in2_tmp = vec3(0.000000, 0.000000, 0.000000); + vec3 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + vec3 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + vec3 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + vec3 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + vec3 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + vec3 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + vec3 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + vec3 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + vec3 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + vec3 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void NG_legacy_metal(vec2 texcoord, int type, vec3 custom_color, int finish, float custom_finish, float patina, bool relief_enable, int relief, vec3 normal_custom_relief, bool cutout_enable, int cutout, float cutout_size, float cutout_spacing, vec3 custom_cutout, bool tint_enable, vec3 tint_color, vec3 normal_knurl, vec3 normal_diamondplate, vec3 normal_checkerplate, out surfaceshader out1) +{ + vec3 geomprop_Pobject_out1 = vd.positionObject; + const float patina_min_in1_tmp = 1.000000; + const float patina_min_in2_tmp = 0.050000; + float patina_min_out = patina_min_in1_tmp - patina_min_in2_tmp; + float convert_type_float_out = float(type); + const int ifcopper1_value2_tmp = 3; + const float ifcopper1_in1_tmp = 1.000000; + const float ifcopper1_in2_tmp = 0.000000; + float ifcopper1_out = (type == ifcopper1_value2_tmp) ? ifcopper1_in1_tmp : ifcopper1_in2_tmp; + const int ifbronze1_value2_tmp = 5; + const float ifbronze1_in1_tmp = 1.000000; + const float ifbronze1_in2_tmp = 0.000000; + float ifbronze1_out = (type == ifbronze1_value2_tmp) ? ifbronze1_in1_tmp : ifbronze1_in2_tmp; + float switch_finish_out = 0.0; + NG_switch_floatI(0.000000, 0.100000, 0.300000, custom_finish, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, finish, switch_finish_out); + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec2 patterns_texcoord1_out = texcoord; + const float reciprocal_spacing_in1_tmp = 1.000000; + float reciprocal_spacing_out = reciprocal_spacing_in1_tmp / cutout_spacing; + float convert_cutout_float_out = float(cutout); + vec3 switch_relief_out = vec3(0.0); + NG_switch_vector3I(normal_knurl, normal_diamondplate, normal_checkerplate, normal_custom_relief, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), relief, switch_relief_out); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Bworld_out1 = normalize(vd.bitangentWorld); + float patina_range_out = 0.0; + NG_range_float(patina, 0.000000, 1.000000, 1.000000, 0.000000, patina_min_out, false, patina_range_out); + const float modulo_type_in2_tmp = 5.000000; + float modulo_type_out = mx_mod(convert_type_float_out, modulo_type_in2_tmp); + const float divide_type_in2_tmp = 5.000000; + float divide_type_out = convert_type_float_out / divide_type_in2_tmp; + float copper_or_bronze_out = ifcopper1_out + ifbronze1_out; + vec2 combine_spacing_out = vec2(reciprocal_spacing_out,reciprocal_spacing_out); + float adjust_size_out = cutout_size * reciprocal_spacing_out; + const float modulo_cutout_in2_tmp = 5.000000; + float modulo_cutout_out = mx_mod(convert_cutout_float_out, modulo_cutout_in2_tmp); + const float divide_cutout_in2_tmp = 5.000000; + float divide_cutout_out = convert_cutout_float_out / divide_cutout_in2_tmp; + vec3 relief_normalmap_out = vec3(0.0); + mx_normalmap_float(vec3(0.500000, 0.500000, 1.000000), 1.000000, geomprop_Nworld_out1, geomprop_Tworld_out1, geomprop_Bworld_out1, relief_normalmap_out); + const float patina_max_in2_tmp = 0.050000; + float patina_max_out = patina_range_out + patina_max_in2_tmp; + vec3 circles_pattern_regular1_out = vec3(0.0); + NG_tiledcircles_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, false, circles_pattern_regular1_out); + vec3 circles_pattern_staggered1_out = vec3(0.0); + NG_tiledcircles_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, true, circles_pattern_staggered1_out); + const float adj_thickness_amount_tmp = 1.000000; + float adj_thickness_out = adj_thickness_amount_tmp - adjust_size_out; + vec3 tiledhexagons_out = vec3(0.0); + NG_tiledhexagons_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, true, tiledhexagons_out); + vec3 squares_pattern_regular2_out = vec3(0.0); + NG_grid_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, false, squares_pattern_regular2_out); + vec3 crosshatch_out = vec3(0.0); + NG_crosshatch_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, false, crosshatch_out); + vec3 tiledcloverleafs_out = vec3(0.0); + NG_tiledcloverleafs_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adjust_size_out, true, tiledcloverleafs_out); + const bool relief_selection_value2_tmp = true; + vec3 relief_selection_out = (relief_enable == relief_selection_value2_tmp) ? switch_relief_out : relief_normalmap_out; + vec3 patina_noise_output_color3 = vec3(0.0); + NG_legacy_noise_color3(geomprop_Pobject_out1, vec3(0.000000, 0.000000, 0.000000), vec3(1.000000, 1.000000, 1.000000), 1, 1.000000, patina_range_out, patina_max_out, false, 0.000000, 4.000000, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), patina_noise_output_color3); + const vec3 invert_col_circ1_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 invert_col_circ1_out = invert_col_circ1_amount_tmp - circles_pattern_regular1_out; + const vec3 invert_col_circ2_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 invert_col_circ2_out = invert_col_circ2_amount_tmp - circles_pattern_staggered1_out; + vec3 squares_pattern_regular1_out = vec3(0.0); + NG_grid_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adj_thickness_out, false, squares_pattern_regular1_out); + vec3 squares_pattern_staggered1_out = vec3(0.0); + NG_grid_color3(patterns_texcoord1_out, combine_spacing_out, vec2(0.000000, 0.000000), adj_thickness_out, true, squares_pattern_staggered1_out); + const vec3 invert_col_hexag_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 invert_col_hexag_out = invert_col_hexag_amount_tmp - tiledhexagons_out; + vec3 combine_grecian_out = max(squares_pattern_regular2_out, crosshatch_out); + const vec3 invert_col_clover_amount_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 invert_col_clover_out = invert_col_clover_amount_tmp - tiledcloverleafs_out; + const int patina_mask_from_r_index_tmp = 0; + float patina_mask_from_r_out = patina_noise_output_color3[patina_mask_from_r_index_tmp]; + vec3 switch_cutout1_out = vec3(0.0); + NG_switch_color3(invert_col_circ1_out, invert_col_circ2_out, squares_pattern_regular1_out, squares_pattern_staggered1_out, invert_col_hexag_out, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), modulo_cutout_out, switch_cutout1_out); + vec3 switch_cutout2_out = vec3(0.0); + NG_switch_color3(combine_grecian_out, invert_col_clover_out, custom_cutout, vec3(0.000000, 0.000000, 0.000000), vec3(1.000000, 1.000000, 1.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), modulo_cutout_out, switch_cutout2_out); + float fade_mask_out = patina * patina_mask_from_r_out; + vec3 switch_cutout_out = vec3(0.0); + NG_switch_color3(switch_cutout1_out, switch_cutout2_out, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(1.000000, 1.000000, 1.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), divide_cutout_out, switch_cutout_out); + const vec3 mix_copper_patina_bg_tmp = vec3(0.737000, 0.314000, 0.184000); + const vec3 mix_copper_patina_fg_tmp = vec3(0.136229, 0.231811, 0.083546); + vec3 mix_copper_patina_out = mix(mix_copper_patina_bg_tmp, mix_copper_patina_fg_tmp, fade_mask_out); + const vec3 mix_bronze_patina_bg_tmp = vec3(0.412000, 0.302000, 0.231000); + const vec3 mix_bronze_patina_fg_tmp = vec3(0.053004, 0.099831, 0.024831); + vec3 mix_bronze_patina_out = mix(mix_bronze_patina_bg_tmp, mix_bronze_patina_fg_tmp, fade_mask_out); + float range_patina_out = 0.0; + NG_range_float(fade_mask_out, 0.000000, 1.000000, 1.000000, switch_finish_out, 0.750000, true, range_patina_out); + const bool cutout_selection_value2_tmp = true; + const vec3 cutout_selection_in2_tmp = vec3(1.000000, 1.000000, 1.000000); + vec3 cutout_selection_out = (cutout_enable == cutout_selection_value2_tmp) ? switch_cutout_out : cutout_selection_in2_tmp; + vec3 switch_type1_out = vec3(0.0); + NG_switch_color3(vec3(0.957000, 0.957000, 0.957000), custom_color, vec3(0.957000, 0.957000, 0.957000), mix_copper_patina_out, vec3(0.796000, 0.604000, 0.231000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), modulo_type_out, switch_type1_out); + vec3 switch_type2_out = vec3(0.0); + NG_switch_color3(mix_bronze_patina_out, vec3(0.745000, 0.737000, 0.729000), vec3(0.647000, 0.678000, 0.649000), custom_color, vec3(0.220900, 0.449300, 0.793800), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), modulo_type_out, switch_type2_out); + const float patina_on_value2_tmp = 0.500000; + float patina_on_out = (copper_or_bronze_out > patina_on_value2_tmp) ? range_patina_out : switch_finish_out; + vec3 switch_type_out = vec3(0.0); + NG_switch_color3(switch_type1_out, switch_type2_out, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), divide_type_out, switch_type_out); + vec3 multiply_tint_out = switch_type_out * tint_color; + const bool tint_selection_value2_tmp = true; + vec3 tint_selection_out = (tint_enable == tint_selection_value2_tmp) ? multiply_tint_out : switch_type_out; + surfaceshader stdsurf_legacy_metal_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, tint_selection_out, 0.000000, 1.000000, 1.000000, tint_selection_out, patina_on_out, 1.500000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.100000, 0.000000, 0.000000, 1.500000, geomprop_Nworld_out1, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), cutout_selection_out, false, relief_selection_out, geomprop_Tworld_out1, stdsurf_legacy_metal_out); + out1 = stdsurf_legacy_metal_out; +} + +void main() +{ + vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + float SR_legacy_metal_cutout_size_unit_out = SR_legacy_metal_cutout_size_unit_in1 * SR_legacy_metal_cutout_size_unit_in2; + float SR_legacy_metal_cutout_spacing_unit_out = SR_legacy_metal_cutout_spacing_unit_in1 * SR_legacy_metal_cutout_spacing_unit_in2; + vec3 normal_map_knurl_out = vec3(0.0); + adsk_NG_adsk_normal_map(normal_map_knurl_file, normal_map_knurl_realworld_offset, normal_map_knurl_realworld_scale, normal_map_knurl_uv_offset, normal_map_knurl_uv_scale, normal_map_knurl_rotation_angle, normal_map_knurl_normal_scale, normal_map_knurl_uaddressmode, normal_map_knurl_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, normal_map_knurl_out); + vec3 normal_map_diamondplate_out = vec3(0.0); + adsk_NG_adsk_normal_map(normal_map_diamondplate_file, normal_map_diamondplate_realworld_offset, normal_map_diamondplate_realworld_scale, normal_map_diamondplate_uv_offset, normal_map_diamondplate_uv_scale, normal_map_diamondplate_rotation_angle, normal_map_diamondplate_normal_scale, normal_map_diamondplate_uaddressmode, normal_map_diamondplate_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, normal_map_diamondplate_out); + vec3 normal_map_checkerplate_out = vec3(0.0); + adsk_NG_adsk_normal_map(normal_map_checkerplate_file, normal_map_checkerplate_realworld_offset, normal_map_checkerplate_realworld_scale, normal_map_checkerplate_uv_offset, normal_map_checkerplate_uv_scale, normal_map_checkerplate_rotation_angle, normal_map_checkerplate_normal_scale, normal_map_checkerplate_uaddressmode, normal_map_checkerplate_vaddressmode, geomprop_UV0_out1, geomprop_Nworld_out1, geomprop_Tworld_out1, normal_map_checkerplate_out); + surfaceshader SR_legacy_metal_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_legacy_metal(geomprop_UV0_out1, SR_legacy_metal_type, SR_legacy_metal_custom_color, SR_legacy_metal_finish, SR_legacy_metal_custom_finish, SR_legacy_metal_patina, SR_legacy_metal_relief_enable, SR_legacy_metal_relief, geomprop_Nworld_out1, SR_legacy_metal_cutout_enable, SR_legacy_metal_cutout, SR_legacy_metal_cutout_size_unit_out, SR_legacy_metal_cutout_spacing_unit_out, SR_legacy_metal_custom_cutout, SR_legacy_metal_tint_enable, SR_legacy_metal_tint_color, normal_map_knurl_out, normal_map_diamondplate_out, normal_map_checkerplate_out, SR_legacy_metal_out); + material M_legacy_metal_out = SR_legacy_metal_out; + float outAlpha = clamp(1.0 - dot(M_legacy_metal_out.transparency, vec3(0.3333)), 0.0, 1.0); + out1 = vec4(M_legacy_metal_out.color, outAlpha); + if (outAlpha < u_alphaThreshold) + { + discard; + } +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_vs.glsl new file mode 100644 index 0000000000..312c4a0b78 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metal/M_legacy_metal_vs.glsl @@ -0,0 +1,84 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec2 i_texcoord_0; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec2 texcoord_0; + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.texcoord_0 = i_texcoord_0; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionObject = i_position; + vd.bitangentWorld = cross(vd.normalWorld, vd.tangentWorld); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_ps.glsl new file mode 100644 index 0000000000..b32fe148fc --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_ps.glsl @@ -0,0 +1,2803 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 SR_legacy_metallicpaint_color = vec3(0.950000, 0.100000, 0.500000); +uniform vec3 SR_legacy_metallicpaint_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_legacy_metallicpaint_highlight_spread = 0.550000; +uniform int SR_legacy_metallicpaint_coat_type = 0; +uniform float SR_legacy_metallicpaint_coat_custom_glossiness = 0.800000; +uniform float SR_legacy_metallicpaint_coat_custom_ior = 1.500000; +uniform int SR_legacy_metallicpaint_coat_finish = 1; +uniform bool SR_legacy_metallicpaint_flakes_enable = false; +uniform float SR_legacy_metallicpaint_flakes_size = 1.000000; +uniform bool SR_legacy_metallicpaint_tint_enable = false; +uniform vec3 SR_legacy_metallicpaint_tint_color = vec3(0.200000, 0.200000, 0.800000); + +in VertexData +{ + vec3 positionObject; + vec3 normalWorld; + vec3 tangentWorld; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_switch_floatI(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, int which, out float out1) +{ + const int ifgreater_10_value1_tmp = 10; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void mx_normalmap_vector2(vec3 value, vec2 normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + value = (dot(value, value) == 0.0) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0; + value = T * value.x * normal_scale.x + + B * value.y * normal_scale.y + + N * value.z; + result = normalize(value); +} + +void mx_normalmap_float(vec3 value, float normal_scale, vec3 N, vec3 T, vec3 B, out vec3 result) +{ + mx_normalmap_vector2(value, vec2(normal_scale), N, T, B, 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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_fractal3d_vector3(vec3 amplitude, int octaves, float lacunarity, float diminish, vec3 position, out vec3 result) +{ + vec3 value = mx_fractal3d_noise_vec3(position, octaves, lacunarity, diminish); + result = value * amplitude; +} + + +void mx_cellnoise3d_float(vec3 position, out float result) +{ + result = mx_cell_noise_float(position); +} + + +void mx_cellnoise2d_float(vec2 texcoord, out float result) +{ + result = mx_cell_noise_float(texcoord); +} + +void NG_range_float(float in1, float inlow, float inhigh, float gamma, float outlow, float outhigh, bool doclamp, out float out1) +{ + const float N_remap1_float_outlow_tmp = 0.000000; + const float N_remap1_float_outhigh_tmp = 1.000000; + float N_remap1_float_out = N_remap1_float_outlow_tmp + (in1 - inlow) * (N_remap1_float_outhigh_tmp - N_remap1_float_outlow_tmp) / (inhigh - inlow); + const float N_recip_float_in1_tmp = 1.000000; + float N_recip_float_out = N_recip_float_in1_tmp / gamma; + float N_abs_float_out = abs(N_remap1_float_out); + float N_sign_float_out = sign(N_remap1_float_out); + float N_pow_float_out = pow(N_abs_float_out, N_recip_float_out); + float N_gamma_float_out = N_pow_float_out * N_sign_float_out; + const float N_remap2_float_inlow_tmp = 0.000000; + const float N_remap2_float_inhigh_tmp = 1.000000; + float N_remap2_float_out = outlow + (N_gamma_float_out - N_remap2_float_inlow_tmp) * (outhigh - outlow) / (N_remap2_float_inhigh_tmp - N_remap2_float_inlow_tmp); + float N_clamp_float_out = clamp(N_remap2_float_out, outlow, outhigh); + const bool N_switch_float_value2_tmp = true; + float N_switch_float_out = (doclamp == N_switch_float_value2_tmp) ? N_clamp_float_out : N_remap2_float_out; + out1 = N_switch_float_out; +} + +void NG_randomfloat_float(float in1, float min, float max, int seed, out float out1) +{ + const float N_scaleInput_in2_tmp = 4096.000000; + float N_scaleInput_out = in1 * N_scaleInput_in2_tmp; + float N_convertSeed1_out = float(seed); + vec2 N_combine2_out = vec2(N_scaleInput_out,N_convertSeed1_out); + float N_cellnoise1_out = 0.0; + mx_cellnoise2d_float(N_combine2_out, N_cellnoise1_out); + float N_remapRange_out = 0.0; + NG_range_float(N_cellnoise1_out, 0.000000, 1.000000, 1.000000, min, max, true, N_remapRange_out); + out1 = N_remapRange_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void NG_convert_vector3_color3(vec3 in1, out vec3 out1) +{ + float separate_outx = 0.0; + float separate_outy = 0.0; + float separate_outz = 0.0; + NG_separate3_vector3(in1, separate_outx, separate_outy, separate_outz); + vec3 combine_out = vec3(separate_outx,separate_outy,separate_outz); + out1 = combine_out; +} + +void NG_legacy_flakes(vec3 position, vec3 base_color, vec3 specular_color, float size, float divergence, float strenght, float cutoff, out vec3 base_color_out, out vec3 specular_color_out, out vec3 out_rgb, out vec3 out_vec3, out float out_blend) +{ + vec3 dot_base_color_out = base_color; + const float default_density_in2_tmp = 20.000000; + vec3 default_density_out = position * default_density_in2_tmp; + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Bworld_out1 = normalize(vd.bitangentWorld); + vec3 adjust_size_out = default_density_out / size; + vec3 position_noise_out = vec3(0.0); + mx_fractal3d_vector3(vec3(0.500000, 0.500000, 0.500000), 2, 2.000000, 0.500000, adjust_size_out, position_noise_out); + vec3 position_shift_out = position_noise_out + adjust_size_out; + float flake_noise_out = 0.0; + mx_cellnoise3d_float(position_shift_out, flake_noise_out); + float flake_depth_out = 0.0; + NG_randomfloat_float(flake_noise_out, 0.000000, 1.000000, 2, flake_depth_out); + float flake_divergence_out = 0.0; + NG_randomfloat_float(flake_noise_out, 0.000000, 1.000000, 0, flake_divergence_out); + float flake_orientation_out = 0.0; + NG_randomfloat_float(flake_noise_out, 0.000000, 1.000000, 1, flake_orientation_out); + float depth_cutoff_adjustement_out = 0.0; + NG_range_float(flake_depth_out, cutoff, 1.000000, 1.250000, 0.000000, 1.000000, true, depth_cutoff_adjustement_out); + float divergence_scale_out = flake_divergence_out * divergence; + const float orientation_degrees_in2_tmp = 360.000000; + float orientation_degrees_out = flake_orientation_out * orientation_degrees_in2_tmp; + const float strenght_adjustment_outlow_tmp = 0.000000; + const float strenght_adjustment_inlow_tmp = 0.000000; + const float strenght_adjustment_inhigh_tmp = 1.000000; + float strenght_adjustment_out = strenght_adjustment_outlow_tmp + (depth_cutoff_adjustement_out - strenght_adjustment_inlow_tmp) * (strenght - strenght_adjustment_outlow_tmp) / (strenght_adjustment_inhigh_tmp - strenght_adjustment_inlow_tmp); + float divergence_cutoff_adj_out = divergence_scale_out * depth_cutoff_adjustement_out; + vec3 mix_specular_out = mix(base_color, specular_color, strenght_adjustment_out); + vec3 rotate_divergence_out = vec3(0.0); + mx_rotate_vector3(vec3(0.000000, 0.000000, 1.000000), divergence_cutoff_adj_out, vec3(0.000000, 1.000000, 0.000000), rotate_divergence_out); + vec3 rotate_orientation_out = vec3(0.0); + mx_rotate_vector3(rotate_divergence_out, orientation_degrees_out, vec3(0.000000, 0.000000, 1.000000), rotate_orientation_out); + const float divide_half_in2_tmp = 2.000000; + vec3 divide_half_out = rotate_orientation_out / divide_half_in2_tmp; + const float offset_half_in2_tmp = 0.500000; + vec3 offset_half_out = divide_half_out + offset_half_in2_tmp; + vec3 convert_vec_col_out = vec3(0.0); + NG_convert_vector3_color3(offset_half_out, convert_vec_col_out); + vec3 normalmap1_out = vec3(0.0); + mx_normalmap_float(offset_half_out, 1.000000, geomprop_Nworld_out1, geomprop_Tworld_out1, geomprop_Bworld_out1, normalmap1_out); + base_color_out = dot_base_color_out; + specular_color_out = mix_specular_out; + out_rgb = convert_vec_col_out; + out_vec3 = normalmap1_out; + out_blend = strenght_adjustment_out; +} + +void NG_switch_vector3I(vec3 in1, vec3 in2, vec3 in3, vec3 in4, vec3 in5, vec3 in6, vec3 in7, vec3 in8, vec3 in9, vec3 in10, int which, out vec3 out1) +{ + const int ifgreater_10_value1_tmp = 10; + const vec3 ifgreater_10_in2_tmp = vec3(0.000000, 0.000000, 0.000000); + vec3 ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + vec3 ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + vec3 ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + vec3 ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + vec3 ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + vec3 ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + vec3 ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + vec3 ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + vec3 ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + vec3 ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void NG_legacy_metallicpaint(vec3 position, vec3 color, vec3 specular_color, float highlight_spread, int coat_type, float coat_custom_glossiness, float coat_custom_ior, int coat_finish, vec3 normal_orangepeel, bool flakes_enable, float flakes_size, bool tint_enable, vec3 tint_color, out surfaceshader out1) +{ + vec3 tint_mult_out = color * tint_color; + const float invert_coat_gloss_amount_tmp = 1.000000; + float invert_coat_gloss_out = invert_coat_gloss_amount_tmp - coat_custom_glossiness; + float switch_coat_ior_out = 0.0; + NG_switch_floatI(1.500000, 1.800000, 1.500000, coat_custom_ior, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, coat_type, switch_coat_ior_out); + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Bworld_out1 = normalize(vd.bitangentWorld); + const bool tint_selection_value2_tmp = true; + vec3 tint_selection_out = (tint_enable == tint_selection_value2_tmp) ? tint_mult_out : color; + float switch_coat_rough_out = 0.0; + NG_switch_floatI(0.000000, 0.000000, 0.500000, invert_coat_gloss_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, coat_type, switch_coat_rough_out); + vec3 smooth_norm_out = vec3(0.0); + mx_normalmap_float(vec3(0.500000, 0.500000, 1.000000), 1.000000, geomprop_Nworld_out1, geomprop_Tworld_out1, geomprop_Bworld_out1, smooth_norm_out); + vec3 legacy_flakes1_base_color_out = vec3(0.0); + vec3 legacy_flakes1_specular_color_out = vec3(0.0); + vec3 legacy_flakes1_out_rgb = vec3(0.0); + vec3 legacy_flakes1_out_vec3 = vec3(0.0); + float legacy_flakes1_out_blend = 0.0; + NG_legacy_flakes(position, tint_selection_out, specular_color, flakes_size, 40.000000, 1.000000, 0.000000, legacy_flakes1_base_color_out, legacy_flakes1_specular_color_out, legacy_flakes1_out_rgb, legacy_flakes1_out_vec3, legacy_flakes1_out_blend); + vec3 switch_finish_out = vec3(0.0); + NG_switch_vector3I(smooth_norm_out, normal_orangepeel, vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), vec3(0.000000, 0.000000, 0.000000), coat_finish, switch_finish_out); + const bool flakes_enable_base_color_value2_tmp = true; + vec3 flakes_enable_base_color_out = (flakes_enable == flakes_enable_base_color_value2_tmp) ? legacy_flakes1_base_color_out : tint_selection_out; + const bool flakes_enable_specular_color_value2_tmp = true; + vec3 flakes_enable_specular_color_out = (flakes_enable == flakes_enable_specular_color_value2_tmp) ? legacy_flakes1_specular_color_out : specular_color; + const bool flakes_enable_normal_value2_tmp = true; + vec3 flakes_enable_normal_out = (flakes_enable == flakes_enable_normal_value2_tmp) ? legacy_flakes1_out_vec3 : smooth_norm_out; + const bool flakes_enable_metallic_value2_tmp = true; + const float flakes_enable_metallic_in2_tmp = 0.750000; + float flakes_enable_metallic_out = (flakes_enable == flakes_enable_metallic_value2_tmp) ? legacy_flakes1_out_blend : flakes_enable_metallic_in2_tmp; + surfaceshader standard_surface_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(1.000000, flakes_enable_base_color_out, 0.000000, flakes_enable_metallic_out, 1.000000, flakes_enable_specular_color_out, highlight_spread, 1.500000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.000000, vec3(0.000000, 0.000000, 0.000000), 0.000000, 0.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), 1.000000, 0.000000, 0.000000, vec3(1.000000, 1.000000, 1.000000), 0.300000, 1.000000, vec3(1.000000, 1.000000, 1.000000), switch_coat_rough_out, 0.000000, 0.000000, switch_coat_ior_out, switch_finish_out, 0.000000, 0.000000, 0.000000, 1.500000, 0.000000, vec3(1.000000, 1.000000, 1.000000), vec3(1.000000, 1.000000, 1.000000), false, flakes_enable_normal_out, geomprop_Tworld_out1, standard_surface_out); + out1 = standard_surface_out; +} + +void main() +{ + vec3 geomprop_Pobject_out1 = vd.positionObject; + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + surfaceshader SR_legacy_metallicpaint_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_legacy_metallicpaint(geomprop_Pobject_out1, SR_legacy_metallicpaint_color, SR_legacy_metallicpaint_specular_color, SR_legacy_metallicpaint_highlight_spread, SR_legacy_metallicpaint_coat_type, SR_legacy_metallicpaint_coat_custom_glossiness, SR_legacy_metallicpaint_coat_custom_ior, SR_legacy_metallicpaint_coat_finish, geomprop_Nworld_out1, SR_legacy_metallicpaint_flakes_enable, SR_legacy_metallicpaint_flakes_size, SR_legacy_metallicpaint_tint_enable, SR_legacy_metallicpaint_tint_color, SR_legacy_metallicpaint_out); + material M_legacy_metallicpaint_out = SR_legacy_metallicpaint_out; + out1 = vec4(M_legacy_metallicpaint_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_vs.glsl new file mode 100644 index 0000000000..ba245c9267 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/metallicpaint/M_legacy_metallicpaint_vs.glsl @@ -0,0 +1,81 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 positionObject; + vec3 normalWorld; + vec3 tangentWorld; + vec3 bitangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.positionObject = i_position; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.bitangentWorld = cross(vd.normalWorld, vd.tangentWorld); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/mirror/M_legacy_mirror_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/mirror/M_legacy_mirror_ps.glsl index c9a8242f82..d632fb95ca 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/mirror/M_legacy_mirror_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/mirror/M_legacy_mirror_ps.glsl @@ -849,19 +849,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1563,6 +1550,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1580,7 +1584,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1590,7 +1594,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_ps.glsl new file mode 100644 index 0000000000..499bb086b6 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_ps.glsl @@ -0,0 +1,2949 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 legacy_noise_color3_color1 = vec3(0.915398, 0.000000, 0.000000); +uniform vec3 legacy_noise_color3_color2 = vec3(1.000000, 1.000000, 1.000000); +uniform int legacy_noise_color3_noise_type = 2; +uniform float legacy_noise_color3_size = 3.000000; +uniform float legacy_noise_color3_threshold_low = 0.015000; +uniform float legacy_noise_color3_threshold_high = 0.820000; +uniform bool legacy_noise_color3_threshold_smooth = true; +uniform float legacy_noise_color3_phase = 0.000000; +uniform float legacy_noise_color3_levels = 6.500000; +uniform vec3 legacy_noise_color3_realworld_offset = vec3(0.000000, 0.000000, 0.000000); +uniform vec3 legacy_noise_color3_rotation_angle = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_base = 1.000000; +uniform float SR_testmat1_diffuse_roughness = 0.000000; +uniform float SR_testmat1_metalness = 0.000000; +uniform float SR_testmat1_specular = 1.000000; +uniform vec3 SR_testmat1_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_specular_roughness = 0.100000; +uniform float SR_testmat1_specular_IOR = 1.500000; +uniform float SR_testmat1_specular_anisotropy = 0.000000; +uniform float SR_testmat1_specular_rotation = 0.000000; +uniform float SR_testmat1_transmission = 0.000000; +uniform vec3 SR_testmat1_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_transmission_depth = 0.000000; +uniform vec3 SR_testmat1_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_transmission_scatter_anisotropy = 0.000000; +uniform float SR_testmat1_transmission_dispersion = 0.000000; +uniform float SR_testmat1_transmission_extra_roughness = 0.000000; +uniform float SR_testmat1_subsurface = 0.400000; +uniform vec3 SR_testmat1_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_subsurface_scale = 1.000000; +uniform float SR_testmat1_subsurface_anisotropy = 0.000000; +uniform float SR_testmat1_sheen = 0.000000; +uniform vec3 SR_testmat1_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_sheen_roughness = 0.300000; +uniform float SR_testmat1_coat = 0.000000; +uniform vec3 SR_testmat1_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_coat_roughness = 0.100000; +uniform float SR_testmat1_coat_anisotropy = 0.000000; +uniform float SR_testmat1_coat_rotation = 0.000000; +uniform float SR_testmat1_coat_IOR = 1.500000; +uniform float SR_testmat1_coat_affect_color = 0.000000; +uniform float SR_testmat1_coat_affect_roughness = 0.000000; +uniform float SR_testmat1_thin_film_thickness = 0.000000; +uniform float SR_testmat1_thin_film_IOR = 1.500000; +uniform float SR_testmat1_emission = 0.000000; +uniform vec3 SR_testmat1_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_testmat1_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_noise3d_float(float amplitude, float pivot, vec3 position, out float result) +{ + float value = mx_perlin_noise_float(position); + result = value * amplitude + pivot; +} + +void NG_switch_float(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, float which, out float out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_fractal3d_max8_float(vec3 position, float octaves, float amplitude, out float out1) +{ + float noise3d1_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d1_out); + const float multiply2_in2_tmp = 2.000000; + vec3 multiply2_out = position * multiply2_in2_tmp; + const float multiply3_in2_tmp = 4.000000; + vec3 multiply3_out = position * multiply3_in2_tmp; + const float multiply4_in2_tmp = 8.000000; + vec3 multiply4_out = position * multiply4_in2_tmp; + const float multiply5_in2_tmp = 16.000000; + vec3 multiply5_out = position * multiply5_in2_tmp; + const float multiply6_in2_tmp = 32.000000; + vec3 multiply6_out = position * multiply6_in2_tmp; + const float multiply7_in2_tmp = 64.000000; + vec3 multiply7_out = position * multiply7_in2_tmp; + const float multiply8_in2_tmp = 128.000000; + vec3 multiply8_out = position * multiply8_in2_tmp; + const float modulo_octave_in2_tmp = 5.000000; + float modulo_octave_out = mx_mod(octaves, modulo_octave_in2_tmp); + const float divide_octave_in2_tmp = 5.000000; + float divide_octave_out = octaves / divide_octave_in2_tmp; + const float modulo_fractional_in2_tmp = 1.000000; + float modulo_fractional_out = mx_mod(octaves, modulo_fractional_in2_tmp); + float noise3d2_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply2_out, noise3d2_out); + float noise3d3_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply3_out, noise3d3_out); + float noise3d4_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply4_out, noise3d4_out); + float noise3d5_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply5_out, noise3d5_out); + float noise3d6_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply6_out, noise3d6_out); + float noise3d7_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply7_out, noise3d7_out); + float noise3d8_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply8_out, noise3d8_out); + float multiply9_out = noise3d1_out * modulo_fractional_out; + const float divide2_in2_tmp = 2.000000; + float divide2_out = noise3d2_out / divide2_in2_tmp; + const float divide3_in2_tmp = 4.000000; + float divide3_out = noise3d3_out / divide3_in2_tmp; + const float divide4_in2_tmp = 8.000000; + float divide4_out = noise3d4_out / divide4_in2_tmp; + const float divide5_in2_tmp = 16.000000; + float divide5_out = noise3d5_out / divide5_in2_tmp; + const float divide6_in2_tmp = 32.000000; + float divide6_out = noise3d6_out / divide6_in2_tmp; + const float divide7_in2_tmp = 64.000000; + float divide7_out = noise3d7_out / divide7_in2_tmp; + const float divide8_in2_tmp = 128.000000; + float divide8_out = noise3d8_out / divide8_in2_tmp; + float add2_out = noise3d1_out + divide2_out; + float multiply10_out = divide2_out * modulo_fractional_out; + float multiply11_out = divide3_out * modulo_fractional_out; + float multiply12_out = divide4_out * modulo_fractional_out; + float multiply13_out = divide5_out * modulo_fractional_out; + float multiply14_out = divide6_out * modulo_fractional_out; + float multiply15_out = divide7_out * modulo_fractional_out; + float multiply16_out = divide8_out * modulo_fractional_out; + float add3_out = add2_out + divide3_out; + float switch3_out = 0.0; + NG_switch_float(multiply9_out, multiply10_out, multiply11_out, multiply12_out, multiply13_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch3_out); + float switch4_out = 0.0; + NG_switch_float(multiply14_out, multiply15_out, multiply16_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch4_out); + float add4_out = add3_out + divide4_out; + float switch_fractional_out = 0.0; + NG_switch_float(switch3_out, switch4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_fractional_out); + float add5_out = add4_out + divide5_out; + float switch1_out = 0.0; + NG_switch_float(0.000000, noise3d1_out, add2_out, add3_out, add4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch1_out); + float add6_out = add5_out + divide6_out; + float add7_out = add6_out + divide7_out; + float add8_out = add7_out + divide8_out; + float switch2_out = 0.0; + NG_switch_float(add5_out, add6_out, add7_out, add8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch2_out); + float switch_octave_out = 0.0; + NG_switch_float(switch1_out, switch2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_out); + float add_fractional_out = switch_octave_out + switch_fractional_out; + const float ifgreatereq_octave_value2_tmp = 8.000000; + float ifgreatereq_octave_out = (octaves >= ifgreatereq_octave_value2_tmp) ? add8_out : add_fractional_out; + float multiply_amplitude_out = ifgreatereq_octave_out * amplitude; + out1 = multiply_amplitude_out; +} + +void NG_turbulence3d_max8_float(vec3 position, float octaves, float amplitude, out float out1) +{ + const float multiply8_in2_tmp = 128.000000; + vec3 multiply8_out = position * multiply8_in2_tmp; + const float multiply7_in2_tmp = 64.000000; + vec3 multiply7_out = position * multiply7_in2_tmp; + const float multiply6_in2_tmp = 32.000000; + vec3 multiply6_out = position * multiply6_in2_tmp; + const float multiply5_in2_tmp = 16.000000; + vec3 multiply5_out = position * multiply5_in2_tmp; + const float multiply4_in2_tmp = 8.000000; + vec3 multiply4_out = position * multiply4_in2_tmp; + const float multiply3_in2_tmp = 4.000000; + vec3 multiply3_out = position * multiply3_in2_tmp; + const float multiply2_in2_tmp = 2.000000; + vec3 multiply2_out = position * multiply2_in2_tmp; + float noise3d1_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d1_out); + const float modulo_octave_in2_tmp = 5.000000; + float modulo_octave_out = mx_mod(octaves, modulo_octave_in2_tmp); + const float divide_octave_in2_tmp = 5.000000; + float divide_octave_out = octaves / divide_octave_in2_tmp; + const float modulo_fractional_in2_tmp = 1.000000; + float modulo_fractional_out = mx_mod(octaves, modulo_fractional_in2_tmp); + float noise3d8_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply8_out, noise3d8_out); + float noise3d7_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply7_out, noise3d7_out); + float noise3d6_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply6_out, noise3d6_out); + float noise3d5_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply5_out, noise3d5_out); + float noise3d4_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply4_out, noise3d4_out); + float noise3d3_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply3_out, noise3d3_out); + float noise3d2_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, multiply2_out, noise3d2_out); + float absval1_out = abs(noise3d1_out); + const float divide8_in2_tmp = 128.000000; + float divide8_out = noise3d8_out / divide8_in2_tmp; + const float divide7_in2_tmp = 64.000000; + float divide7_out = noise3d7_out / divide7_in2_tmp; + const float divide6_in2_tmp = 32.000000; + float divide6_out = noise3d6_out / divide6_in2_tmp; + const float divide5_in2_tmp = 16.000000; + float divide5_out = noise3d5_out / divide5_in2_tmp; + const float divide4_in2_tmp = 8.000000; + float divide4_out = noise3d4_out / divide4_in2_tmp; + const float divide3_in2_tmp = 4.000000; + float divide3_out = noise3d3_out / divide3_in2_tmp; + const float divide2_in2_tmp = 2.000000; + float divide2_out = noise3d2_out / divide2_in2_tmp; + float multiply9_out = absval1_out * modulo_fractional_out; + float absval8_out = abs(divide8_out); + float absval7_out = abs(divide7_out); + float absval6_out = abs(divide6_out); + float absval5_out = abs(divide5_out); + float absval4_out = abs(divide4_out); + float absval3_out = abs(divide3_out); + float absval2_out = abs(divide2_out); + float multiply16_out = absval8_out * modulo_fractional_out; + float multiply15_out = absval7_out * modulo_fractional_out; + float multiply14_out = absval6_out * modulo_fractional_out; + float multiply13_out = absval5_out * modulo_fractional_out; + float multiply12_out = absval4_out * modulo_fractional_out; + float multiply11_out = absval3_out * modulo_fractional_out; + float add2_out = absval1_out + absval2_out; + float multiply10_out = absval2_out * modulo_fractional_out; + float switch4_out = 0.0; + NG_switch_float(multiply14_out, multiply15_out, multiply16_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch4_out); + float add3_out = add2_out + absval3_out; + float switch3_out = 0.0; + NG_switch_float(multiply9_out, multiply10_out, multiply11_out, multiply12_out, multiply13_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch3_out); + float add4_out = add3_out + absval4_out; + float switch_fractional_out = 0.0; + NG_switch_float(switch3_out, switch4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_fractional_out); + float add5_out = add4_out + absval5_out; + float switch1_out = 0.0; + NG_switch_float(0.000000, absval1_out, add2_out, add3_out, add4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch1_out); + float add6_out = add5_out + absval6_out; + float add7_out = add6_out + absval7_out; + float add8_out = add7_out + absval8_out; + float switch2_out = 0.0; + NG_switch_float(add5_out, add6_out, add7_out, add8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch2_out); + float switch_octave_out = 0.0; + NG_switch_float(switch1_out, switch2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_out); + float add_fractional_out = switch_octave_out + switch_fractional_out; + const float ifgreatereq_octave_value2_tmp = 8.000000; + float ifgreatereq_octave_out = (octaves >= ifgreatereq_octave_value2_tmp) ? add8_out : add_fractional_out; + float multiply_amplitude_out = ifgreatereq_octave_out * amplitude; + out1 = multiply_amplitude_out; +} + +void NG_range_float(float in1, float inlow, float inhigh, float gamma, float outlow, float outhigh, bool doclamp, out float out1) +{ + const float N_remap1_float_outlow_tmp = 0.000000; + const float N_remap1_float_outhigh_tmp = 1.000000; + float N_remap1_float_out = N_remap1_float_outlow_tmp + (in1 - inlow) * (N_remap1_float_outhigh_tmp - N_remap1_float_outlow_tmp) / (inhigh - inlow); + const float N_recip_float_in1_tmp = 1.000000; + float N_recip_float_out = N_recip_float_in1_tmp / gamma; + float N_abs_float_out = abs(N_remap1_float_out); + float N_sign_float_out = sign(N_remap1_float_out); + float N_pow_float_out = pow(N_abs_float_out, N_recip_float_out); + float N_gamma_float_out = N_pow_float_out * N_sign_float_out; + const float N_remap2_float_inlow_tmp = 0.000000; + const float N_remap2_float_inhigh_tmp = 1.000000; + float N_remap2_float_out = outlow + (N_gamma_float_out - N_remap2_float_inlow_tmp) * (outhigh - outlow) / (N_remap2_float_inhigh_tmp - N_remap2_float_inlow_tmp); + float N_clamp_float_out = clamp(N_remap2_float_out, outlow, outhigh); + const bool N_switch_float_value2_tmp = true; + float N_switch_float_out = (doclamp == N_switch_float_value2_tmp) ? N_clamp_float_out : N_remap2_float_out; + out1 = N_switch_float_out; +} + +void NG_switch_floatI(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, int which, out float out1) +{ + const int ifgreater_10_value1_tmp = 10; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const int ifgreater_9_value1_tmp = 9; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const int ifgreater_8_value1_tmp = 8; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const int ifgreater_7_value1_tmp = 7; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const int ifgreater_6_value1_tmp = 6; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const int ifgreater_5_value1_tmp = 5; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const int ifgreater_4_value1_tmp = 4; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const int ifgreater_3_value1_tmp = 3; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const int ifgreater_2_value1_tmp = 2; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const int ifgreater_1_value1_tmp = 1; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void mx_smoothstep_float(float val, float low, float high, out float result) +{ + if (val >= high) + result = 1.0; + else if (val <= low) + result = 0.0; + else + result = smoothstep(low, high, val); +} + +void NG_legacy_noise_color3(vec3 position, vec3 color1, vec3 color2, int noise_type, float size, float threshold_low, float threshold_high, bool threshold_smooth, float phase, float levels, vec3 realworld_offset, vec3 rotation_angle, out vec3 output_color3) +{ + const float modulo_phase_in2_tmp = 1.000000; + float modulo_phase_out = mx_mod(phase, modulo_phase_in2_tmp); + const float inv_size_in1_tmp = 1.000000; + float inv_size_out = inv_size_in1_tmp / size; + float rotation_xyz_outx = 0.0; + float rotation_xyz_outy = 0.0; + float rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, rotation_xyz_outx, rotation_xyz_outy, rotation_xyz_outz); + const float mult_phase_in2_tmp = 2.000000; + float mult_phase_out = modulo_phase_out * mult_phase_in2_tmp; + vec3 adj_coord_out = position * inv_size_out; + vec3 add_offset_out = adj_coord_out - realworld_offset; + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(add_offset_out, rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + float noise3d_float_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, rotate3d_z_out, noise3d_float_out); + float fractal3d_max8_out = 0.0; + NG_fractal3d_max8_float(rotate3d_z_out, levels, 1.000000, fractal3d_max8_out); + float turbulence3d_max8_out = 0.0; + NG_turbulence3d_max8_float(rotate3d_z_out, levels, 1.000000, turbulence3d_max8_out); + float range_noise3d_out = 0.0; + NG_range_float(noise3d_float_out, -1.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_noise3d_out); + float range_fractal3d_out = 0.0; + NG_range_float(fractal3d_max8_out, -1.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_fractal3d_out); + float range_turbulence3d_out = 0.0; + NG_range_float(turbulence3d_max8_out, 0.000000, 1.000000, 1.000000, 0.000000, 1.000000, true, range_turbulence3d_out); + float switch_type_out = 0.0; + NG_switch_floatI(range_noise3d_out, range_fractal3d_out, range_turbulence3d_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, noise_type, switch_type_out); + float add_noise1_out = switch_type_out + mult_phase_out; + const float modulo_ramp_in2_tmp = 1.000000; + float modulo_ramp_out = mx_mod(add_noise1_out, modulo_ramp_in2_tmp); + const float modulo_odd_even_in2_tmp = 2.000000; + float modulo_odd_even_out = mx_mod(add_noise1_out, modulo_odd_even_in2_tmp); + const float invert_ramp_amount_tmp = 1.000000; + float invert_ramp_out = invert_ramp_amount_tmp - modulo_ramp_out; + const float if_odd_even_value2_tmp = 1.000000; + float if_odd_even_out = (modulo_odd_even_out >= if_odd_even_value2_tmp) ? invert_ramp_out : modulo_ramp_out; + float lowhi_range_smooth_out = 0.0; + mx_smoothstep_float(if_odd_even_out, threshold_low, threshold_high, lowhi_range_smooth_out); + float lowhi_range_out = 0.0; + NG_range_float(if_odd_even_out, threshold_low, threshold_high, 1.000000, 0.000000, 1.000000, true, lowhi_range_out); + const bool smooth_select_value2_tmp = true; + float smooth_select_out = (threshold_smooth == smooth_select_value2_tmp) ? lowhi_range_smooth_out : lowhi_range_out; + vec3 mix_colors_out = mix(color2, color1, smooth_select_out); + output_color3 = mix_colors_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Pobject_out1 = vd.positionObject; + vec3 legacy_noise_color3_output_color3 = vec3(0.0); + NG_legacy_noise_color3(geomprop_Pobject_out1, legacy_noise_color3_color1, legacy_noise_color3_color2, legacy_noise_color3_noise_type, legacy_noise_color3_size, legacy_noise_color3_threshold_low, legacy_noise_color3_threshold_high, legacy_noise_color3_threshold_smooth, legacy_noise_color3_phase, legacy_noise_color3_levels, legacy_noise_color3_realworld_offset, legacy_noise_color3_rotation_angle, legacy_noise_color3_output_color3); + surfaceshader SR_testmat1_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_testmat1_base, legacy_noise_color3_output_color3, SR_testmat1_diffuse_roughness, SR_testmat1_metalness, SR_testmat1_specular, SR_testmat1_specular_color, SR_testmat1_specular_roughness, SR_testmat1_specular_IOR, SR_testmat1_specular_anisotropy, SR_testmat1_specular_rotation, SR_testmat1_transmission, SR_testmat1_transmission_color, SR_testmat1_transmission_depth, SR_testmat1_transmission_scatter, SR_testmat1_transmission_scatter_anisotropy, SR_testmat1_transmission_dispersion, SR_testmat1_transmission_extra_roughness, SR_testmat1_subsurface, SR_testmat1_subsurface_color, SR_testmat1_subsurface_radius, SR_testmat1_subsurface_scale, SR_testmat1_subsurface_anisotropy, SR_testmat1_sheen, SR_testmat1_sheen_color, SR_testmat1_sheen_roughness, SR_testmat1_coat, SR_testmat1_coat_color, SR_testmat1_coat_roughness, SR_testmat1_coat_anisotropy, SR_testmat1_coat_rotation, SR_testmat1_coat_IOR, geomprop_Nworld_out1, SR_testmat1_coat_affect_color, SR_testmat1_coat_affect_roughness, SR_testmat1_thin_film_thickness, SR_testmat1_thin_film_IOR, SR_testmat1_emission, SR_testmat1_emission_color, SR_testmat1_opacity, SR_testmat1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1, SR_testmat1_out); + material M_legacy_noise_out = SR_testmat1_out; + out1 = vec4(M_legacy_noise_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_vs.glsl new file mode 100644 index 0000000000..e9c23bbd30 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/noise/M_legacy_noise_vs.glsl @@ -0,0 +1,79 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionObject = i_position; + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/plastic/M_legacy_plastic_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/plastic/M_legacy_plastic_ps.glsl index 2b477139e7..4fb619c7e9 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/plastic/M_legacy_plastic_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/plastic/M_legacy_plastic_ps.glsl @@ -1053,19 +1053,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1767,6 +1754,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1784,7 +1788,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1794,7 +1798,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_ps.glsl new file mode 100644 index 0000000000..41d3431abe --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_ps.glsl @@ -0,0 +1,2776 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform float legacy_speckle_color3_size_unit_in1 = 7.600000; +uniform float legacy_speckle_color3_size_unit_in2 = 2.540000; +uniform vec3 legacy_speckle_color3_color1 = vec3(0.996616, 0.000000, 0.000000); +uniform vec3 legacy_speckle_color3_color2 = vec3(1.000000, 1.000000, 1.000000); +uniform float legacy_speckle_color3_octaves = 5.500000; +uniform vec3 legacy_speckle_color3_realworld_offset = vec3(0.000000, 0.000000, 0.000000); +uniform vec3 legacy_speckle_color3_rotation_angle = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_base = 1.000000; +uniform float SR_testmat1_diffuse_roughness = 0.000000; +uniform float SR_testmat1_metalness = 0.000000; +uniform float SR_testmat1_specular = 1.000000; +uniform vec3 SR_testmat1_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_specular_roughness = 0.100000; +uniform float SR_testmat1_specular_IOR = 1.500000; +uniform float SR_testmat1_specular_anisotropy = 0.000000; +uniform float SR_testmat1_specular_rotation = 0.000000; +uniform float SR_testmat1_transmission = 0.000000; +uniform vec3 SR_testmat1_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_transmission_depth = 0.000000; +uniform vec3 SR_testmat1_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_transmission_scatter_anisotropy = 0.000000; +uniform float SR_testmat1_transmission_dispersion = 0.000000; +uniform float SR_testmat1_transmission_extra_roughness = 0.000000; +uniform float SR_testmat1_subsurface = 0.400000; +uniform vec3 SR_testmat1_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_subsurface_scale = 1.000000; +uniform float SR_testmat1_subsurface_anisotropy = 0.000000; +uniform float SR_testmat1_sheen = 0.000000; +uniform vec3 SR_testmat1_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_sheen_roughness = 0.300000; +uniform float SR_testmat1_coat = 0.000000; +uniform vec3 SR_testmat1_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_coat_roughness = 0.100000; +uniform float SR_testmat1_coat_anisotropy = 0.000000; +uniform float SR_testmat1_coat_rotation = 0.000000; +uniform float SR_testmat1_coat_IOR = 1.500000; +uniform float SR_testmat1_coat_affect_color = 0.000000; +uniform float SR_testmat1_coat_affect_roughness = 0.000000; +uniform float SR_testmat1_thin_film_thickness = 0.000000; +uniform float SR_testmat1_thin_film_IOR = 1.500000; +uniform float SR_testmat1_emission = 0.000000; +uniform vec3 SR_testmat1_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_testmat1_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_noise3d_float(float amplitude, float pivot, vec3 position, out float result) +{ + float value = mx_perlin_noise_float(position); + result = value * amplitude + pivot; +} + +void NG_util_specklenoise_float(vec3 position, out float out1) +{ + float noise3d_out = 0.0; + mx_noise3d_float(1.000000, 0.000000, position, noise3d_out); + const float multiply1_in2_tmp = 1.650000; + float multiply1_out = noise3d_out * multiply1_in2_tmp; + const float clamp1_low_tmp = -1.000000; + const float clamp1_high_tmp = 1.000000; + float clamp1_out = clamp(multiply1_out, clamp1_low_tmp, clamp1_high_tmp); + const float add1_in2_tmp = 1.000000; + float add1_out = clamp1_out + add1_in2_tmp; + const float multiply2_in2_tmp = 0.500000; + float multiply2_out = add1_out * multiply2_in2_tmp; + out1 = multiply2_out; +} + +void NG_switch_float(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, float which, out float out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_legacy_speckle_color3(vec3 position, vec3 color1, vec3 color2, float size, float octaves, vec3 realworld_offset, vec3 rotation_angle, out vec3 out1) +{ + const float check_zero_value2_tmp = 0.000100; + const float check_zero_in2_tmp = 0.000100; + float check_zero_out = (size >= check_zero_value2_tmp) ? size : check_zero_in2_tmp; + float separate_rotation_xyz_outx = 0.0; + float separate_rotation_xyz_outy = 0.0; + float separate_rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, separate_rotation_xyz_outx, separate_rotation_xyz_outy, separate_rotation_xyz_outz); + const float modulo_octave_in2_tmp = 5.000000; + float modulo_octave_out = mx_mod(octaves, modulo_octave_in2_tmp); + const float divide_octave_in2_tmp = 5.000000; + float divide_octave_out = octaves / divide_octave_in2_tmp; + const float octave_frac_in2_tmp = 1.000000; + float octave_frac_out = mx_mod(octaves, octave_frac_in2_tmp); + const float divide_10inv_in1_tmp = 10.000000; + float divide_10inv_out = divide_10inv_in1_tmp / check_zero_out; + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(position, separate_rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, separate_rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, separate_rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + vec3 offset_xyz_out = rotate3d_z_out - realworld_offset; + vec3 multiply_coord_out = offset_xyz_out * divide_10inv_out; + float util_specklenoise1_out = 0.0; + NG_util_specklenoise_float(multiply_coord_out, util_specklenoise1_out); + const float multiply2_in2_tmp = 2.000000; + vec3 multiply2_out = multiply_coord_out * multiply2_in2_tmp; + const float multiply3_in2_tmp = 4.000000; + vec3 multiply3_out = multiply_coord_out * multiply3_in2_tmp; + const float multiply4_in2_tmp = 8.000000; + vec3 multiply4_out = multiply_coord_out * multiply4_in2_tmp; + const float multiply5_in2_tmp = 16.000000; + vec3 multiply5_out = multiply_coord_out * multiply5_in2_tmp; + const float multiply6_in2_tmp = 32.000000; + vec3 multiply6_out = multiply_coord_out * multiply6_in2_tmp; + const float multiply7_in2_tmp = 64.000000; + vec3 multiply7_out = multiply_coord_out * multiply7_in2_tmp; + const float multiply8_in2_tmp = 128.000000; + vec3 multiply8_out = multiply_coord_out * multiply8_in2_tmp; + float multiply9_out = util_specklenoise1_out * octave_frac_out; + float util_specklenoise2_out = 0.0; + NG_util_specklenoise_float(multiply2_out, util_specklenoise2_out); + float util_specklenoise3_out = 0.0; + NG_util_specklenoise_float(multiply3_out, util_specklenoise3_out); + float util_specklenoise4_out = 0.0; + NG_util_specklenoise_float(multiply4_out, util_specklenoise4_out); + float util_specklenoise5_out = 0.0; + NG_util_specklenoise_float(multiply5_out, util_specklenoise5_out); + float util_specklenoise6_out = 0.0; + NG_util_specklenoise_float(multiply6_out, util_specklenoise6_out); + float util_specklenoise7_out = 0.0; + NG_util_specklenoise_float(multiply7_out, util_specklenoise7_out); + float util_specklenoise8_out = 0.0; + NG_util_specklenoise_float(multiply8_out, util_specklenoise8_out); + const float divide2_in2_tmp = 2.000000; + float divide2_out = util_specklenoise2_out / divide2_in2_tmp; + const float divide3_in2_tmp = 4.000000; + float divide3_out = util_specklenoise3_out / divide3_in2_tmp; + const float divide4_in2_tmp = 8.000000; + float divide4_out = util_specklenoise4_out / divide4_in2_tmp; + const float divide5_in2_tmp = 16.000000; + float divide5_out = util_specklenoise5_out / divide5_in2_tmp; + const float divide6_in2_tmp = 32.000000; + float divide6_out = util_specklenoise6_out / divide6_in2_tmp; + const float divide7_in2_tmp = 64.000000; + float divide7_out = util_specklenoise7_out / divide7_in2_tmp; + const float divide8_in2_tmp = 128.000000; + float divide8_out = util_specklenoise8_out / divide8_in2_tmp; + float add2_out = util_specklenoise1_out + divide2_out; + float multiply10_out = divide2_out * octave_frac_out; + float multiply11_out = divide3_out * octave_frac_out; + float multiply12_out = divide4_out * octave_frac_out; + float multiply13_out = divide5_out * octave_frac_out; + float multiply14_out = divide6_out * octave_frac_out; + float multiply15_out = divide7_out * octave_frac_out; + float multiply16_out = divide8_out * octave_frac_out; + float add3_out = add2_out + divide3_out; + float switch_oct_frac1_out = 0.0; + NG_switch_float(multiply9_out, multiply10_out, multiply11_out, multiply12_out, multiply13_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch_oct_frac1_out); + float switch_oct_frac2_out = 0.0; + NG_switch_float(multiply14_out, multiply15_out, multiply16_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch_oct_frac2_out); + float add4_out = add3_out + divide4_out; + float switch_octave_frac_out = 0.0; + NG_switch_float(switch_oct_frac1_out, switch_oct_frac2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_frac_out); + float switch_oct1_out = 0.0; + NG_switch_float(0.000000, util_specklenoise1_out, add2_out, add3_out, add4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch_oct1_out); + float add5_out = add4_out + divide5_out; + float add6_out = add5_out + divide6_out; + float add7_out = add6_out + divide7_out; + float add8_out = add7_out + divide8_out; + float switch_oct2_out = 0.0; + NG_switch_float(add5_out, add6_out, add7_out, add8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_octave_out, switch_oct2_out); + float switch_octave_out = 0.0; + NG_switch_float(switch_oct1_out, switch_oct2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_octave_out, switch_octave_out); + float add_frac_octave_out = switch_octave_out + switch_octave_frac_out; + const float octave_max_value2_tmp = 8.000000; + float octave_max_out = (octaves >= octave_max_value2_tmp) ? add8_out : add_frac_octave_out; + vec3 color_mix_out = mix(color1, color2, octave_max_out); + out1 = color_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Pobject_out1 = vd.positionObject; + float legacy_speckle_color3_size_unit_out = legacy_speckle_color3_size_unit_in1 * legacy_speckle_color3_size_unit_in2; + vec3 legacy_speckle_color3_out = vec3(0.0); + NG_legacy_speckle_color3(geomprop_Pobject_out1, legacy_speckle_color3_color1, legacy_speckle_color3_color2, legacy_speckle_color3_size_unit_out, legacy_speckle_color3_octaves, legacy_speckle_color3_realworld_offset, legacy_speckle_color3_rotation_angle, legacy_speckle_color3_out); + surfaceshader SR_testmat1_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_testmat1_base, legacy_speckle_color3_out, SR_testmat1_diffuse_roughness, SR_testmat1_metalness, SR_testmat1_specular, SR_testmat1_specular_color, SR_testmat1_specular_roughness, SR_testmat1_specular_IOR, SR_testmat1_specular_anisotropy, SR_testmat1_specular_rotation, SR_testmat1_transmission, SR_testmat1_transmission_color, SR_testmat1_transmission_depth, SR_testmat1_transmission_scatter, SR_testmat1_transmission_scatter_anisotropy, SR_testmat1_transmission_dispersion, SR_testmat1_transmission_extra_roughness, SR_testmat1_subsurface, SR_testmat1_subsurface_color, SR_testmat1_subsurface_radius, SR_testmat1_subsurface_scale, SR_testmat1_subsurface_anisotropy, SR_testmat1_sheen, SR_testmat1_sheen_color, SR_testmat1_sheen_roughness, SR_testmat1_coat, SR_testmat1_coat_color, SR_testmat1_coat_roughness, SR_testmat1_coat_anisotropy, SR_testmat1_coat_rotation, SR_testmat1_coat_IOR, geomprop_Nworld_out1, SR_testmat1_coat_affect_color, SR_testmat1_coat_affect_roughness, SR_testmat1_thin_film_thickness, SR_testmat1_thin_film_IOR, SR_testmat1_emission, SR_testmat1_emission_color, SR_testmat1_opacity, SR_testmat1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1, SR_testmat1_out); + material M_legacy_speckle_out = SR_testmat1_out; + out1 = vec4(M_legacy_speckle_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_vs.glsl new file mode 100644 index 0000000000..e9c23bbd30 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/speckle/M_legacy_speckle_vs.glsl @@ -0,0 +1,79 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionObject = i_position; + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/stone/M_legacy_stone_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/stone/M_legacy_stone_ps.glsl index 8cbfda9279..214364f150 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/stone/M_legacy_stone_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/stone/M_legacy_stone_ps.glsl @@ -1101,19 +1101,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1815,6 +1802,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1832,7 +1836,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1842,7 +1846,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/tiles/M_legacy_tiles_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/tiles/M_legacy_tiles_ps.glsl index 9c9bb69053..68d53e5ad8 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/tiles/M_legacy_tiles_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/tiles/M_legacy_tiles_ps.glsl @@ -2122,19 +2122,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -2836,6 +2823,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -2853,7 +2857,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -2863,7 +2867,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wallpaint/M_legacy_wallpaint_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wallpaint/M_legacy_wallpaint_ps.glsl index f3258ac459..7f79a3cb90 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wallpaint/M_legacy_wallpaint_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wallpaint/M_legacy_wallpaint_ps.glsl @@ -1057,19 +1057,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1771,6 +1758,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1788,7 +1792,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1798,7 +1802,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/water/M_legacy_water_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/water/M_legacy_water_ps.glsl index e7e99220ec..20cc25f64c 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/water/M_legacy_water_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/water/M_legacy_water_ps.glsl @@ -1158,19 +1158,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1872,6 +1859,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1889,7 +1893,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1899,7 +1903,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_ps.glsl new file mode 100644 index 0000000000..e2bb3677e4 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_ps.glsl @@ -0,0 +1,2851 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform vec3 legacy_waves_color3_color1 = vec3(0.000000, 0.000000, 0.742809); +uniform vec3 legacy_waves_color3_color2 = vec3(1.000000, 1.000000, 1.000000); +uniform float legacy_waves_color3_waves = 2.500000; +uniform float legacy_waves_color3_radius = 2.000000; +uniform float legacy_waves_color3_seed = 0.000000; +uniform float legacy_waves_color3_wave_min = 0.100000; +uniform float legacy_waves_color3_wave_max = 0.300000; +uniform float legacy_waves_color3_phase = 0.500000; +uniform bool legacy_waves_color3_distribution_3d = false; +uniform bool legacy_waves_color3_fade = false; +uniform vec3 legacy_waves_color3_realworld_offset = vec3(0.000000, 1.500000, 0.000000); +uniform vec3 legacy_waves_color3_rotation_angle = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_base = 1.000000; +uniform float SR_testmat1_diffuse_roughness = 0.000000; +uniform float SR_testmat1_metalness = 0.000000; +uniform float SR_testmat1_specular = 1.000000; +uniform vec3 SR_testmat1_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_specular_roughness = 0.100000; +uniform float SR_testmat1_specular_IOR = 1.500000; +uniform float SR_testmat1_specular_anisotropy = 0.000000; +uniform float SR_testmat1_specular_rotation = 0.000000; +uniform float SR_testmat1_transmission = 0.000000; +uniform vec3 SR_testmat1_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_transmission_depth = 0.000000; +uniform vec3 SR_testmat1_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_transmission_scatter_anisotropy = 0.000000; +uniform float SR_testmat1_transmission_dispersion = 0.000000; +uniform float SR_testmat1_transmission_extra_roughness = 0.000000; +uniform float SR_testmat1_subsurface = 0.400000; +uniform vec3 SR_testmat1_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_subsurface_scale = 1.000000; +uniform float SR_testmat1_subsurface_anisotropy = 0.000000; +uniform float SR_testmat1_sheen = 0.000000; +uniform vec3 SR_testmat1_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_sheen_roughness = 0.300000; +uniform float SR_testmat1_coat = 0.000000; +uniform vec3 SR_testmat1_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_coat_roughness = 0.100000; +uniform float SR_testmat1_coat_anisotropy = 0.000000; +uniform float SR_testmat1_coat_rotation = 0.000000; +uniform float SR_testmat1_coat_IOR = 1.500000; +uniform float SR_testmat1_coat_affect_color = 0.000000; +uniform float SR_testmat1_coat_affect_roughness = 0.000000; +uniform float SR_testmat1_thin_film_thickness = 0.000000; +uniform float SR_testmat1_thin_film_IOR = 1.500000; +uniform float SR_testmat1_emission = 0.000000; +uniform vec3 SR_testmat1_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_testmat1_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_cellnoise2d_float(vec2 texcoord, out float result) +{ + result = mx_cell_noise_float(texcoord); +} + +void NG_range_float(float in1, float inlow, float inhigh, float gamma, float outlow, float outhigh, bool doclamp, out float out1) +{ + const float N_remap1_float_outlow_tmp = 0.000000; + const float N_remap1_float_outhigh_tmp = 1.000000; + float N_remap1_float_out = N_remap1_float_outlow_tmp + (in1 - inlow) * (N_remap1_float_outhigh_tmp - N_remap1_float_outlow_tmp) / (inhigh - inlow); + const float N_recip_float_in1_tmp = 1.000000; + float N_recip_float_out = N_recip_float_in1_tmp / gamma; + float N_abs_float_out = abs(N_remap1_float_out); + float N_sign_float_out = sign(N_remap1_float_out); + float N_pow_float_out = pow(N_abs_float_out, N_recip_float_out); + float N_gamma_float_out = N_pow_float_out * N_sign_float_out; + const float N_remap2_float_inlow_tmp = 0.000000; + const float N_remap2_float_inhigh_tmp = 1.000000; + float N_remap2_float_out = outlow + (N_gamma_float_out - N_remap2_float_inlow_tmp) * (outhigh - outlow) / (N_remap2_float_inhigh_tmp - N_remap2_float_inlow_tmp); + float N_clamp_float_out = clamp(N_remap2_float_out, outlow, outhigh); + const bool N_switch_float_value2_tmp = true; + float N_switch_float_out = (doclamp == N_switch_float_value2_tmp) ? N_clamp_float_out : N_remap2_float_out; + out1 = N_switch_float_out; +} + +void NG_randomfloat_float(float in1, float min, float max, int seed, out float out1) +{ + const float N_scaleInput_in2_tmp = 4096.000000; + float N_scaleInput_out = in1 * N_scaleInput_in2_tmp; + float N_convertSeed1_out = float(seed); + vec2 N_combine2_out = vec2(N_scaleInput_out,N_convertSeed1_out); + float N_cellnoise1_out = 0.0; + mx_cellnoise2d_float(N_combine2_out, N_cellnoise1_out); + float N_remapRange_out = 0.0; + NG_range_float(N_cellnoise1_out, 0.000000, 1.000000, 1.000000, min, max, true, N_remapRange_out); + out1 = N_remapRange_out; +} + +void NG_util_sinewave_float(vec3 position, float phase, float rate, out float out1) +{ + float distance_out = length(position); + float multiply_shift_out = phase * rate; + float distance_adj_out = distance_out - multiply_shift_out; + const float multiply_2PI_in2_tmp = 6.283180; + float multiply_2PI_out = distance_adj_out * multiply_2PI_in2_tmp; + float sin1_out = mx_sin(multiply_2PI_out); + const float add1_in2_tmp = 1.000000; + float add1_out = sin1_out + add1_in2_tmp; + const float multiply2_in2_tmp = 0.500000; + float multiply2_out = add1_out * multiply2_in2_tmp; + out1 = multiply2_out; +} + +void NG_util_wave_float(vec3 position, float radius, float seed, float wavemin, float wavemax, float phase, bool distribution3d, bool fade, out float out1) +{ + float dot_seed_out = seed; + const float add_seed2_in2_tmp = 1.000000; + float add_seed2_out = dot_seed_out + add_seed2_in2_tmp; + int floor1_out = int(floor(dot_seed_out)); + const float add_seed3_in2_tmp = 1.000000; + float add_seed3_out = add_seed2_out + add_seed3_in2_tmp; + int floor2_out = int(floor(add_seed2_out)); + float randomfloat_X_out = 0.0; + NG_randomfloat_float(0.000000, -1.000000, 1.000000, floor1_out, randomfloat_X_out); + const float add_seed4_in2_tmp = 1.000000; + float add_seed4_out = add_seed3_out + add_seed4_in2_tmp; + int floor3_out = int(floor(add_seed3_out)); + float randomfloat_Y_out = 0.0; + NG_randomfloat_float(0.000000, -1.000000, 1.000000, floor2_out, randomfloat_Y_out); + int floor4_out = int(floor(add_seed4_out)); + const float add_seed5_in2_tmp = 1.000000; + float add_seed5_out = add_seed4_out + add_seed5_in2_tmp; + float randomfloat_Z_out = 0.0; + NG_randomfloat_float(0.000000, -1.000000, 1.000000, floor3_out, randomfloat_Z_out); + float randomfloat_size_out = 0.0; + NG_randomfloat_float(0.000000, 0.000000, 1.000000, floor4_out, randomfloat_size_out); + int floor5_out = int(floor(add_seed5_out)); + const bool distrib_2D_3D_select_value2_tmp = true; + const float distrib_2D_3D_select_in2_tmp = 0.000000; + float distrib_2D_3D_select_out = (distribution3d == distrib_2D_3D_select_value2_tmp) ? randomfloat_Z_out : distrib_2D_3D_select_in2_tmp; + const float remap_min_max_inlow_tmp = 0.000000; + const float remap_min_max_inhigh_tmp = 1.000000; + float remap_min_max_out = wavemin + (randomfloat_size_out - remap_min_max_inlow_tmp) * (wavemax - wavemin) / (remap_min_max_inhigh_tmp - remap_min_max_inlow_tmp); + float randomfloat_phase_out = 0.0; + NG_randomfloat_float(0.000000, 0.000000, 1.000000, floor5_out, randomfloat_phase_out); + vec3 combine_origin_out = vec3(randomfloat_X_out,randomfloat_Y_out,distrib_2D_3D_select_out); + const float divide3_in1_tmp = 1.000000; + float divide3_out = divide3_in1_tmp / remap_min_max_out; + float add_phase_out = randomfloat_phase_out + phase; + vec3 multiply_radius_out = combine_origin_out * radius; + vec3 subtract_position_out = position - multiply_radius_out; + vec3 divide2_out = subtract_position_out / remap_min_max_out; + float center_magnitude_out = length(subtract_position_out); + float util_sinewave_out = 0.0; + NG_util_sinewave_float(divide2_out, add_phase_out, divide3_out, util_sinewave_out); + float mag_square_out = center_magnitude_out * center_magnitude_out; + const float add_one_in1_tmp = 1.000000; + float add_one_out = add_one_in1_tmp + mag_square_out; + const float divide1_in1_tmp = 1.000000; + float divide1_out = divide1_in1_tmp / add_one_out; + float fade_decay_out = util_sinewave_out * divide1_out; + const bool fade_enable_value2_tmp = true; + float fade_enable_out = (fade == fade_enable_value2_tmp) ? fade_decay_out : util_sinewave_out; + out1 = fade_enable_out; +} + +void NG_switch_float(float in1, float in2, float in3, float in4, float in5, float in6, float in7, float in8, float in9, float in10, float which, out float out1) +{ + const float ifgreater_10_value1_tmp = 10.000000; + const float ifgreater_10_in2_tmp = 0.000000; + float ifgreater_10_out = (ifgreater_10_value1_tmp > which) ? in10 : ifgreater_10_in2_tmp; + const float ifgreater_9_value1_tmp = 9.000000; + float ifgreater_9_out = (ifgreater_9_value1_tmp > which) ? in9 : ifgreater_10_out; + const float ifgreater_8_value1_tmp = 8.000000; + float ifgreater_8_out = (ifgreater_8_value1_tmp > which) ? in8 : ifgreater_9_out; + const float ifgreater_7_value1_tmp = 7.000000; + float ifgreater_7_out = (ifgreater_7_value1_tmp > which) ? in7 : ifgreater_8_out; + const float ifgreater_6_value1_tmp = 6.000000; + float ifgreater_6_out = (ifgreater_6_value1_tmp > which) ? in6 : ifgreater_7_out; + const float ifgreater_5_value1_tmp = 5.000000; + float ifgreater_5_out = (ifgreater_5_value1_tmp > which) ? in5 : ifgreater_6_out; + const float ifgreater_4_value1_tmp = 4.000000; + float ifgreater_4_out = (ifgreater_4_value1_tmp > which) ? in4 : ifgreater_5_out; + const float ifgreater_3_value1_tmp = 3.000000; + float ifgreater_3_out = (ifgreater_3_value1_tmp > which) ? in3 : ifgreater_4_out; + const float ifgreater_2_value1_tmp = 2.000000; + float ifgreater_2_out = (ifgreater_2_value1_tmp > which) ? in2 : ifgreater_3_out; + const float ifgreater_1_value1_tmp = 1.000000; + float ifgreater_1_out = (ifgreater_1_value1_tmp > which) ? in1 : ifgreater_2_out; + out1 = ifgreater_1_out; +} + +void NG_legacy_waves_color3(vec3 position, vec3 color1, vec3 color2, float waves, float radius, float seed, float wave_min, float wave_max, float phase, bool distribution_3d, bool fade, vec3 realworld_offset, vec3 rotation_angle, out vec3 out1) +{ + const float divide_waves_in2_tmp = 5.000000; + float divide_waves_out = waves / divide_waves_in2_tmp; + const float modulo_waves_in2_tmp = 5.000000; + float modulo_waves_out = mx_mod(waves, modulo_waves_in2_tmp); + float separate_rotation_xyz_outx = 0.0; + float separate_rotation_xyz_outy = 0.0; + float separate_rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, separate_rotation_xyz_outx, separate_rotation_xyz_outy, separate_rotation_xyz_outz); + const float add_seed2_in2_tmp = 10.000000; + float add_seed2_out = seed + add_seed2_in2_tmp; + const float add_seed5_in1_tmp = 0.000000; + const float add_seed5_in2_tmp = 10.000000; + float add_seed5_out = add_seed5_in1_tmp + add_seed5_in2_tmp; + float floor_wavenum_out = floor(waves); + const float modulo_frac_in2_tmp = 1.000000; + float modulo_frac_out = mx_mod(waves, modulo_frac_in2_tmp); + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(position, separate_rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + const float add_seed3_in2_tmp = 10.000000; + float add_seed3_out = add_seed2_out + add_seed3_in2_tmp; + const float add_seed6_in2_tmp = 10.000000; + float add_seed6_out = add_seed5_out + add_seed6_in2_tmp; + const float add_wave_one_in2_tmp = 1.000000; + float add_wave_one_out = floor_wavenum_out + add_wave_one_in2_tmp; + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, separate_rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + const float add_seed4_in2_tmp = 10.000000; + float add_seed4_out = add_seed3_out + add_seed4_in2_tmp; + const float add_seed7_in2_tmp = 10.000000; + float add_seed7_out = add_seed6_out + add_seed7_in2_tmp; + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, separate_rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + const float add_seed8_in2_tmp = 10.000000; + float add_seed8_out = add_seed7_out + add_seed8_in2_tmp; + vec3 add_offset_out = rotate3d_z_out - realworld_offset; + float util_wave1_out = 0.0; + NG_util_wave_float(add_offset_out, radius, seed, wave_min, wave_max, phase, distribution_3d, fade, util_wave1_out); + float util_wave2_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed2_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave2_out); + float util_wave3_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed3_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave3_out); + float util_wave4_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed4_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave4_out); + float util_wave5_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed5_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave5_out); + float util_wave6_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed6_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave6_out); + float util_wave7_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed7_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave7_out); + float util_wave8_out = 0.0; + NG_util_wave_float(add_offset_out, radius, add_seed8_out, wave_min, wave_max, phase, distribution_3d, fade, util_wave8_out); + float multiply_frac1_out = util_wave1_out * modulo_frac_out; + float add_wave2_out = util_wave1_out + util_wave2_out; + float multiply_frac2_out = util_wave2_out * modulo_frac_out; + float multiply_frac3_out = util_wave3_out * modulo_frac_out; + float multiply_frac4_out = util_wave4_out * modulo_frac_out; + float multiply_frac5_out = util_wave5_out * modulo_frac_out; + float multiply_frac6_out = util_wave6_out * modulo_frac_out; + float multiply_frac7_out = util_wave7_out * modulo_frac_out; + float multiply_frac8_out = util_wave8_out * modulo_frac_out; + float add_wave3_out = add_wave2_out + util_wave3_out; + float switch_fract1_out = 0.0; + NG_switch_float(multiply_frac1_out, multiply_frac2_out, multiply_frac3_out, multiply_frac4_out, multiply_frac5_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_waves_out, switch_fract1_out); + float switch_frac2_out = 0.0; + NG_switch_float(multiply_frac6_out, multiply_frac7_out, multiply_frac8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_waves_out, switch_frac2_out); + float add_wave4_out = add_wave3_out + util_wave4_out; + float switch_frac_out = 0.0; + NG_switch_float(switch_fract1_out, switch_frac2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_waves_out, switch_frac_out); + float switch_waves1_out = 0.0; + NG_switch_float(0.000000, util_wave1_out, add_wave2_out, add_wave3_out, add_wave4_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_waves_out, switch_waves1_out); + float add_wave5_out = add_wave4_out + util_wave5_out; + float divide_fract_out = switch_frac_out / add_wave_one_out; + float add_wave6_out = add_wave5_out + util_wave6_out; + float add_wave7_out = add_wave6_out + util_wave7_out; + float add_wave8_out = add_wave7_out + util_wave8_out; + float switch_waves2_out = 0.0; + NG_switch_float(add_wave5_out, add_wave6_out, add_wave7_out, add_wave8_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, modulo_waves_out, switch_waves2_out); + const float divide_last_wave_in2_tmp = 8.000000; + float divide_last_wave_out = add_wave8_out / divide_last_wave_in2_tmp; + float switch_waves_out = 0.0; + NG_switch_float(switch_waves1_out, switch_waves2_out, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, divide_waves_out, switch_waves_out); + float divide_wavenum_out = switch_waves_out / waves; + float add_fractional_out = divide_wavenum_out + divide_fract_out; + const float wave_limit_value2_tmp = 8.000000; + float wave_limit_out = (waves >= wave_limit_value2_tmp) ? divide_last_wave_out : add_fractional_out; + vec3 mix_colors_out = mix(color2, color1, wave_limit_out); + out1 = mix_colors_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 geomprop_Pworld_out1 = vd.positionWorld; + vec3 legacy_waves_color3_out = vec3(0.0); + NG_legacy_waves_color3(geomprop_Pworld_out1, legacy_waves_color3_color1, legacy_waves_color3_color2, legacy_waves_color3_waves, legacy_waves_color3_radius, legacy_waves_color3_seed, legacy_waves_color3_wave_min, legacy_waves_color3_wave_max, legacy_waves_color3_phase, legacy_waves_color3_distribution_3d, legacy_waves_color3_fade, legacy_waves_color3_realworld_offset, legacy_waves_color3_rotation_angle, legacy_waves_color3_out); + surfaceshader SR_testmat1_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_testmat1_base, legacy_waves_color3_out, SR_testmat1_diffuse_roughness, SR_testmat1_metalness, SR_testmat1_specular, SR_testmat1_specular_color, SR_testmat1_specular_roughness, SR_testmat1_specular_IOR, SR_testmat1_specular_anisotropy, SR_testmat1_specular_rotation, SR_testmat1_transmission, SR_testmat1_transmission_color, SR_testmat1_transmission_depth, SR_testmat1_transmission_scatter, SR_testmat1_transmission_scatter_anisotropy, SR_testmat1_transmission_dispersion, SR_testmat1_transmission_extra_roughness, SR_testmat1_subsurface, SR_testmat1_subsurface_color, SR_testmat1_subsurface_radius, SR_testmat1_subsurface_scale, SR_testmat1_subsurface_anisotropy, SR_testmat1_sheen, SR_testmat1_sheen_color, SR_testmat1_sheen_roughness, SR_testmat1_coat, SR_testmat1_coat_color, SR_testmat1_coat_roughness, SR_testmat1_coat_anisotropy, SR_testmat1_coat_rotation, SR_testmat1_coat_IOR, geomprop_Nworld_out1, SR_testmat1_coat_affect_color, SR_testmat1_coat_affect_roughness, SR_testmat1_thin_film_thickness, SR_testmat1_thin_film_IOR, SR_testmat1_emission, SR_testmat1_emission_color, SR_testmat1_opacity, SR_testmat1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1, SR_testmat1_out); + material M_legacy_waves_out = SR_testmat1_out; + out1 = vec4(M_legacy_waves_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_vs.glsl new file mode 100644 index 0000000000..6a98f2a08b --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/waves/M_legacy_waves_vs.glsl @@ -0,0 +1,77 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_ps.glsl new file mode 100644 index 0000000000..0478c8756e --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_ps.glsl @@ -0,0 +1,2733 @@ +#version 400 + + +struct BSDF { vec3 response; vec3 throughput; }; +#define EDF vec3 +struct VDF { vec3 response; vec3 throughput; }; +struct surfaceshader { vec3 color; vec3 transparency; }; +struct volumeshader { vec3 color; vec3 transparency; }; +struct displacementshader { vec3 offset; float scale; }; +struct lightshader { vec3 intensity; vec3 direction; }; +#define material surfaceshader + +// Uniform block: PrivateUniforms +uniform mat4 u_envMatrix = mat4(-1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, -1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D u_envRadiance; +uniform float u_envLightIntensity = 1.000000; +uniform int u_envRadianceMips = 1; +uniform int u_envRadianceSamples = 16; +uniform sampler2D u_envIrradiance; +uniform bool u_refractionTwoSided = false; +uniform vec3 u_viewPosition = vec3(0.0); + +// Uniform block: PublicUniforms +uniform surfaceshader backsurfaceshader; +uniform displacementshader displacementshader1; +uniform float NG_legacy_wood_thickness_unit_in1 = 1.000000; +uniform float NG_legacy_wood_thickness_unit_in2 = 2.540000; +uniform float multiply_vector3FA_in2 = 1.000000; +uniform vec3 NG_legacy_wood_color1 = vec3(0.603000, 0.445000, 0.072000); +uniform vec3 NG_legacy_wood_color2 = vec3(0.212000, 0.072000, 0.000000); +uniform float NG_legacy_wood_radial_noise = 0.700000; +uniform float NG_legacy_wood_axial_noise = 0.600000; +uniform bool NG_legacy_wood_loop = false; +uniform float NG_legacy_wood_noise_repetitions = 20.000000; +uniform vec3 NG_legacy_wood_realworld_offset = vec3(0.000000, 2.900000, 0.000000); +uniform vec3 NG_legacy_wood_rotation_angle = vec3(0.000000, 90.000000, 0.000000); +uniform float SR_testmat1_base = 1.000000; +uniform float SR_testmat1_diffuse_roughness = 0.000000; +uniform float SR_testmat1_metalness = 0.000000; +uniform float SR_testmat1_specular = 1.000000; +uniform vec3 SR_testmat1_specular_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_specular_roughness = 0.100000; +uniform float SR_testmat1_specular_IOR = 1.500000; +uniform float SR_testmat1_specular_anisotropy = 0.000000; +uniform float SR_testmat1_specular_rotation = 0.000000; +uniform float SR_testmat1_transmission = 0.000000; +uniform vec3 SR_testmat1_transmission_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_transmission_depth = 0.000000; +uniform vec3 SR_testmat1_transmission_scatter = vec3(0.000000, 0.000000, 0.000000); +uniform float SR_testmat1_transmission_scatter_anisotropy = 0.000000; +uniform float SR_testmat1_transmission_dispersion = 0.000000; +uniform float SR_testmat1_transmission_extra_roughness = 0.000000; +uniform float SR_testmat1_subsurface = 0.400000; +uniform vec3 SR_testmat1_subsurface_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_subsurface_radius = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_subsurface_scale = 1.000000; +uniform float SR_testmat1_subsurface_anisotropy = 0.000000; +uniform float SR_testmat1_sheen = 0.000000; +uniform vec3 SR_testmat1_sheen_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_sheen_roughness = 0.300000; +uniform float SR_testmat1_coat = 0.000000; +uniform vec3 SR_testmat1_coat_color = vec3(1.000000, 1.000000, 1.000000); +uniform float SR_testmat1_coat_roughness = 0.100000; +uniform float SR_testmat1_coat_anisotropy = 0.000000; +uniform float SR_testmat1_coat_rotation = 0.000000; +uniform float SR_testmat1_coat_IOR = 1.500000; +uniform float SR_testmat1_coat_affect_color = 0.000000; +uniform float SR_testmat1_coat_affect_roughness = 0.000000; +uniform float SR_testmat1_thin_film_thickness = 0.000000; +uniform float SR_testmat1_thin_film_IOR = 1.500000; +uniform float SR_testmat1_emission = 0.000000; +uniform vec3 SR_testmat1_emission_color = vec3(1.000000, 1.000000, 1.000000); +uniform vec3 SR_testmat1_opacity = vec3(1.000000, 1.000000, 1.000000); +uniform bool SR_testmat1_thin_walled = false; + +in VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +// Pixel shader outputs +out vec4 out1; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +#define DIRECTIONAL_ALBEDO_METHOD 0 + +#define AIRY_FRESNEL_ITERATIONS 2 + +#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; +} + +float mx_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// 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 on 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(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(mx_cos(phi) * sinTheta, + mx_sin(phi) * sinTheta, + cosTheta); +} + +// PDF of a cosine-weighted hemisphere sample. +float mx_cosine_hemisphere_PDF(float cosTheta) +{ + return max(cosTheta, 0.0) * M_PI_INV; +} + +// PDF of a uniform hemisphere sample. +float mx_uniform_hemisphere_PDF() +{ + return 0.5 * M_PI_INV; +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} + +const int FRESNEL_MODEL_DIELECTRIC = 0; +const int FRESNEL_MODEL_CONDUCTOR = 1; +const int FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// 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 * mx_cos(phi); + float y = sinTheta * mx_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; +} + +// PDF of a reflection direction sampled from the GGX VNDF. +float mx_ggx_VNDF_reflection_PDF(vec3 H, vec2 alpha, float G1V, float NdotV) +{ + return mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); +} + +// 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 * NdotL * NdotV / (lambdaL * NdotV + lambdaV * NdotL); +} + +// 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(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 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); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = mx_atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = mx_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)); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +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) * mx_cos(pos * phase + shift) * exp(- var * phase*phase); + xyz.x += 9.7470e-14 * sqrt(2.0*M_PI * 4.5282e+09) * mx_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, FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = mx_cos(mx_atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m = 1; m <= AIRY_FRESNEL_ITERATIONS; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(mx_matrix_mul(XYZ_TO_RGB, I), 0.0, 1.0); + + return I; +} + +FresnelData mx_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + FresnelData fd; + fd.model = FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx_compute_fresnel(float cosTheta, FresnelData fd) +{ + if (fd.airy) + { + return mx_fresnel_airy(cosTheta, fd); + } + else 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 // FRESNEL_MODEL_SCHLICK + { + return mx_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +// Directional albedo accounting for different Fresnel functions. +vec3 mx_ggx_dir_albedo(float NdotV, float alpha, FresnelData fd) +{ + if (fd.airy) + { + // Approximation using a blend between mirror (alpha = 0) + // and rougher cases. This helps to maintain angular + // color variation at lower roughness values. + vec3 mirrorDirAlbedo = mx_compute_fresnel(NdotV, fd); + vec3 F0 = mx_fresnel_airy(1.0, fd); + vec3 roughDirAlbedo = mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + return mix(mirrorDirAlbedo, roughDirAlbedo, sqrt(alpha)); + } + else if (fd.model == FRESNEL_MODEL_DIELECTRIC) + { + float F0 = mx_ior_to_f0(fd.ior.x); + return mx_ggx_dir_albedo(NdotV, alpha, vec3(F0), vec3(1.0)); + } + else if (fd.model == FRESNEL_MODEL_CONDUCTOR) + { + vec3 F0 = mx_fresnel_conductor(1.0, fd.ior, fd.extinction); + return mx_ggx_dir_albedo(NdotV, alpha, F0, vec3(1.0)); + } + else // FRESNEL_MODEL_SCHLICK + { + return mx_ggx_dir_albedo(NdotV, alpha, fd.F0, fd.F90); + } +} + +// 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 = -mx_asin(dir.y) * M_PI_INV + 0.5; + float longitude = mx_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 tex_sampler) +{ + vec3 envDir = normalize(mx_matrix_mul(transform, vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(tex_sampler, 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); +} + +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = u_envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = mx_matrix_mul(tangentToWorld, L); + float pdf = mx_ggx_VNDF_reflection_PDF(H, alpha, G1V, NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float(u_envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, u_envMatrix, lod, u_envRadiance); + + // Compute the Fresnel term. + vec3 F = mx_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which simplifies to inverted Fresnel for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - F : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return (u_envRadianceSamples == 0 ? vec3(0.0) : radiance) * 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) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if (u_refractionTwoSided) + { + tint = mx_square(tint); + } + return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} + +void NG_separate3_vector3(vec3 in1, out float outx, out float outy, out float outz) +{ + const int N_extract_0_index_tmp = 0; + float N_extract_0_out = in1[N_extract_0_index_tmp]; + const int N_extract_1_index_tmp = 1; + float N_extract_1_out = in1[N_extract_1_index_tmp]; + const int N_extract_2_index_tmp = 2; + float N_extract_2_out = in1[N_extract_2_index_tmp]; + outx = N_extract_0_out; + outy = N_extract_1_out; + outz = N_extract_2_out; +} + +void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) +{ + // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the + // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the + // order of parameters to cross are reversed. + + axis = normalize(axis); + float rotationRadians = mx_radians(amount); + float s = mx_sin(rotationRadians); + float c = mx_cos(rotationRadians); + float oc = 1.0 - c; + result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; +} + +/* +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 overall "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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a + 1u, b, c)), + mx_bits_to_01(mx_bjfinal(a + 2u, b, c)) + ); +} + +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); + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u; + a += uint(ix); + b += uint(iy); + c += uint(iz); + mx_bjmix(a, b, c); + a += uint(iw); + return vec3( + mx_bits_to_01(mx_bjfinal(a, b, c)), + mx_bits_to_01(mx_bjfinal(a, b + 1u, c)), + mx_bits_to_01(mx_bjfinal(a, b + 2u, c)) + ); +} + +float mx_fractal2d_noise_float(vec2 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_fractal2d_noise_vec3(vec2 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_fractal2d_noise_vec2(vec2 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal2d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal2d_noise_vec4(vec2 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal2d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal2d_noise_float(p+vec2(19, 193), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_fractal3d_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_fractal3d_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_fractal3d_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal3d_noise_float(p, octaves, lacunarity, diminish), + mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal3d_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal3d_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal3d_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +vec2 mx_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + 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; + + return vec2(float(x), float(y)) + off; +} + +vec3 mx_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + return vec3(float(x), float(y), float(z)) + off; +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec2 cellpos = mx_worley_cell_position(x, y, xoff, yoff, jitter); + 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 Euclidean 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 cellpos = mx_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter); + 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 Euclidean or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + float dist; + 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) + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int style, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + vec2 minpos = vec2(0,0); + 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); + vec2 cellpos = mx_worley_cell_position(x, y, X, Y, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +float mx_worley_noise_float(vec3 p, float jitter, int style, 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; + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if(dist < sqdist) + { + sqdist = dist; + minpos = cellpos; + } + } + } + } + if (style == 1) + return mx_cell_noise_float(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (style == 1) + { + vec3 tmp = mx_cell_noise_vec3(minpos + p); + return vec2(tmp.x,tmp.y); + } + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int style, 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); + vec3 minpos = vec3(0,0,0); + 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); + vec3 cellpos = mx_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (style == 1) + return mx_cell_noise_vec3(minpos + p); + else + { + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; + } +} + +void mx_noise2d_float(float amplitude, float pivot, vec2 texcoord, out float result) +{ + float value = mx_perlin_noise_float(texcoord); + result = value * amplitude + pivot; +} + +void NG_util_woodnoise_float(float distance, float noiserep, bool loop, out float out1) +{ + const float combine2_1_in2_tmp = 0.000000; + vec2 combine2_1_out = vec2(distance,combine2_1_in2_tmp); + const float add_loop_one_in2_tmp = 1.000000; + float add_loop_one_out = noiserep + add_loop_one_in2_tmp; + float noise2d1_out = 0.0; + mx_noise2d_float(0.500000, 0.500000, combine2_1_out, noise2d1_out); + float modulo_loop_out = mx_mod(distance, add_loop_one_out); + float fract_mix_out = modulo_loop_out - noiserep; + const float combine2_2_in2_tmp = 0.000000; + vec2 combine2_2_out = vec2(modulo_loop_out,combine2_2_in2_tmp); + const float combine2_3_in2_tmp = 0.000000; + vec2 combine2_3_out = vec2(fract_mix_out,combine2_3_in2_tmp); + float noise2d2_out = 0.0; + mx_noise2d_float(0.500000, 0.500000, combine2_2_out, noise2d2_out); + float noise2d3_out = 0.0; + mx_noise2d_float(0.500000, 0.500000, combine2_3_out, noise2d3_out); + float mix_end_start_out = mix(noise2d2_out, noise2d3_out, fract_mix_out); + const float ifgreater_overlap_value2_tmp = 0.000000; + float ifgreater_overlap_out = (fract_mix_out > ifgreater_overlap_value2_tmp) ? mix_end_start_out : noise2d2_out; + const bool ifequal_loop_value2_tmp = true; + float ifequal_loop_out = (loop == ifequal_loop_value2_tmp) ? ifgreater_overlap_out : noise2d1_out; + out1 = ifequal_loop_out; +} + +void mx_smoothstep_float(float val, float low, float high, out float result) +{ + if (val >= high) + result = 1.0; + else if (val <= low) + result = 0.0; + else + result = smoothstep(low, high, val); +} + +void NG_util_woodfactor_float(vec3 position, float size, float radialnoise, float axialnoise, bool loop, float noiserep, out float out1) +{ + vec3 scale_out = position / size; + float separate3_1_outx = 0.0; + float separate3_1_outy = 0.0; + float separate3_1_outz = 0.0; + NG_separate3_vector3(scale_out, separate3_1_outx, separate3_1_outy, separate3_1_outz); + float util_woodnoise1_out = 0.0; + NG_util_woodnoise_float(separate3_1_outx, noiserep, loop, util_woodnoise1_out); + float util_woodnoise2_out = 0.0; + NG_util_woodnoise_float(separate3_1_outy, noiserep, loop, util_woodnoise2_out); + float util_woodnoise3_out = 0.0; + NG_util_woodnoise_float(separate3_1_outz, noiserep, loop, util_woodnoise3_out); + float multiply_radialnoise1_out = util_woodnoise1_out * radialnoise; + float multiply_radialnoise2_out = util_woodnoise2_out * radialnoise; + float multiply_radialnoise3_out = util_woodnoise3_out * radialnoise; + float add_radialnoise1_out = multiply_radialnoise1_out + separate3_1_outx; + float add_radialnoise2_out = multiply_radialnoise2_out + separate3_1_outy; + float add_radialnoise3_out = multiply_radialnoise3_out + separate3_1_outz; + const float scale_quarter_in2_tmp = 4.000000; + float scale_quarter_out = add_radialnoise1_out / scale_quarter_in2_tmp; + float y_square_out = add_radialnoise2_out * add_radialnoise2_out; + float z_square_out = add_radialnoise3_out * add_radialnoise3_out; + float util_woodnoise4_out = 0.0; + NG_util_woodnoise_float(scale_quarter_out, noiserep, loop, util_woodnoise4_out); + float add_yz_out = y_square_out + z_square_out; + float multiply_axialnoise_out = util_woodnoise4_out * axialnoise; + float distance_out = sqrt(add_yz_out); + float util_woodnoise5_out = 0.0; + NG_util_woodnoise_float(distance_out, noiserep, loop, util_woodnoise5_out); + float distance_axialnoise_out = multiply_axialnoise_out + util_woodnoise5_out; + float add_dist_axialnoise_out = distance_axialnoise_out + distance_out; + const float modulo_ring_in2_tmp = 1.000000; + float modulo_ring_out = mx_mod(add_dist_axialnoise_out, modulo_ring_in2_tmp); + float smoothstep1_out = 0.0; + mx_smoothstep_float(modulo_ring_out, 0.000000, 0.800000, smoothstep1_out); + float smoothstep2_out = 0.0; + mx_smoothstep_float(modulo_ring_out, 0.830000, 1.000000, smoothstep2_out); + float ring_ramp_out = smoothstep1_out - smoothstep2_out; + out1 = ring_ramp_out; +} + +void NG_legacy_wood_color3(vec3 position, vec3 color1, vec3 color2, float radial_noise, float axial_noise, float thickness, bool loop, float noise_repetitions, vec3 realworld_offset, vec3 rotation_angle, out vec3 out1) +{ + float separate_rotation_xyz_outx = 0.0; + float separate_rotation_xyz_outy = 0.0; + float separate_rotation_xyz_outz = 0.0; + NG_separate3_vector3(rotation_angle, separate_rotation_xyz_outx, separate_rotation_xyz_outy, separate_rotation_xyz_outz); + vec3 rotate3d_x_out = vec3(0.0); + mx_rotate_vector3(position, separate_rotation_xyz_outx, vec3(1.000000, 0.000000, 0.000000), rotate3d_x_out); + vec3 rotate3d_y_out = vec3(0.0); + mx_rotate_vector3(rotate3d_x_out, separate_rotation_xyz_outy, vec3(0.000000, 1.000000, 0.000000), rotate3d_y_out); + vec3 rotate3d_z_out = vec3(0.0); + mx_rotate_vector3(rotate3d_y_out, separate_rotation_xyz_outz, vec3(0.000000, 0.000000, 1.000000), rotate3d_z_out); + vec3 add_offset_out = rotate3d_z_out - realworld_offset; + float util_woodfactor1_out = 0.0; + NG_util_woodfactor_float(add_offset_out, thickness, radial_noise, axial_noise, loop, noise_repetitions, util_woodfactor1_out); + vec3 mix_colors_out = mix(color1, color2, util_woodfactor1_out); + out1 = mix_colors_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; + } +} +// These are defined based on the HwShaderGenerator::ClosureContextType enum +// if that changes - these need to be updated accordingly. + +#define CLOSURE_TYPE_DEFAULT 0 +#define CLOSURE_TYPE_REFLECTION 1 +#define CLOSURE_TYPE_TRANSMISSION 2 +#define CLOSURE_TYPE_INDIRECT 3 +#define CLOSURE_TYPE_EMISSION 4 + +struct ClosureData { + int closureType; + vec3 L; + vec3 V; + vec3 N; + vec3 P; + float occlusion; +}; + +ClosureData makeClosureData(int closureType, vec3 L, vec3 V, vec3 N, vec3 P, float occlusion) +{ + return ClosureData(closureType, L, V, N, P, occlusion); +} + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx_oren_nayar_diffuse_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_oren_nayar_diffuse_dir_albedo_monte_carlo(float NdotV, float roughness) +{ + NdotV = clamp(NdotV, M_FLOAT_EPS, 1.0); + vec3 V = vec3(sqrt(1.0 - 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. + vec3 L = mx_uniform_sample_hemisphere(Xi); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + // Compute diffuse reflectance. + float reflectance = mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness); + + // Add the radiance contribution of this sample. + // uniform_pdf = 1 / (2 * PI) + // radiance = (reflectance * NdotL) / (uniform_pdf * PI); + radiance += reflectance * NdotL; + } + + // Apply global components and normalize. + radiance *= 2.0 / float(SAMPLE_COUNT); + + // Return the final directional albedo. + return radiance; +} + +float mx_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ +#if DIRECTIONAL_ALBEDO_METHOD == 2 + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_monte_carlo(NdotV, roughness); +#else + float dirAlbedo = mx_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); +#endif + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (mx_acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf +// Section 5.3 +float mx_burley_diffuse(float NdotV, float NdotL, float LdotH, float roughness) +{ + 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 = mx_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 * mx_sin(x * 0.5)); + vec3 R = mx_burley_diffusion_profile(dist, shape); + sumD += R * max(mx_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(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * closureData.occlusion * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 diffuse = energy_compensation ? + mx_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * diffuse * weight; + } +} + +void mx_translucent_bsdf(ClosureData closureData, float weight, vec3 color, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Invert normal since we're transmitting light from the other side + N = -N; + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float NdotL = clamp(dot(N, L), 0.0, 1.0); + bsdf.response = color * weight * NdotL * M_PI_INV; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +void mx_subsurface_bsdf(ClosureData closureData, float weight, vec3 color, vec3 radius, float anisotropy, vec3 N, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + vec3 P = closureData.P; + float occlusion = closureData.occlusion; + + N = mx_forward_facing_normal(N, V); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + vec3 sss = mx_subsurface_scattering_approx(N, L, P, color, radius); + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float visibleOcclusion = 1.0 - NdotL * (1.0 - occlusion); + bsdf.response = sss * visibleOcclusion * weight; + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + // For now, we render indirect subsurface as simple indirect diffuse. + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * weight; + } +} + +// https://fpsunflower.github.io/ckulla/data/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. + // radiance = reflectance * NdotL / uniform_pdf; + radiance += reflectance * NdotL / mx_uniform_hemisphere_PDF(); + } + + // 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); +} + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= mx_inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = mx_matrix_mul(toLTC, L); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return mx_cosine_hemisphere_PDF(wo.z) * mx_square(aInv / lenSqr); +} + +vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx_cosine_sample_hemisphere(Xi); + + float aInv = mx_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= mx_inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = mx_cosine_hemisphere_PDF(w.z) * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx_orthonormal_basis_ltc(V, N, NdotV); + w = mx_matrix_mul(fromLTC, w); + + return w; +} + +void mx_sheen_bsdf(ClosureData closureData, float weight, vec3 color, float roughness, vec3 N, int mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + float dirAlbedo; + if (mode == 0) + { + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), 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); + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + + // 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 * closureData.occlusion * weight; + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.response = dirAlbedo * fr * closureData.occlusion * weight; + } + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + float dirAlbedo; + if (mode == 0) + { + dirAlbedo = mx_imageworks_sheen_dir_albedo(NdotV, roughness); + } + else + { + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx_zeltner_sheen_dir_albedo(NdotV, roughness); + } + + vec3 Li = mx_environment_irradiance(N); + bsdf.response = Li * color * dirAlbedo * weight; + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + } +} + +void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + if (closureData.closureType != CLOSURE_TYPE_TRANSMISSION && scatter_mode == 1) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + // Retroreflective mode is only supported for reflection and indirect + if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION)) + V = reflect(-V, N); + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + float F0 = mx_ior_to_f0(ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 safeTint = max(tint, 0.0); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + bsdf.response = D * F * G * comp * safeTint * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_TRANSMISSION) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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; + } + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + + 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(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + vec3 V = closureData.V; + vec3 L = closureData.L; + + V = retroreflective ? reflect(-V, N) : V; + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_conductor(ior_n, ior_k, thinfilm_thickness, thinfilm_ior); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + if (closureData.closureType == CLOSURE_TYPE_REFLECTION) + { + 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 VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + 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 * closureData.occlusion * weight / (4.0 * NdotV); + } + else if (closureData.closureType == CLOSURE_TYPE_INDIRECT) + { + vec3 F = mx_compute_fresnel(NdotV, fd); + 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_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); +} +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + +void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) +{ + // + // Coat affect roughness: blend specular roughness toward 1.0 + float coat_roughness_factor = (coat_affect_roughness * coat) * coat_roughness; + float coat_affected_specular_roughness = (specular_roughness * (1 - coat_roughness_factor)) + coat_roughness_factor; + // + // Roughness + vec2 main_roughness; + mx_roughness_anisotropy(coat_affected_specular_roughness, specular_anisotropy, main_roughness); + // + // Tangent rotation + vec3 main_tangent = tangent; + if (specular_anisotropy > 0.0) + { + float tangent_rotate_degree = specular_rotation * 360.0; + vec3 tangent_rotated; + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + main_tangent = normalize(tangent_rotated); + } + // + // Coat tangent rotation + vec3 coat_tangent = tangent; + if (coat_anisotropy > 0.0) + { + float coat_tangent_rotate_degree = coat_rotation * 360.0; + vec3 coat_tangent_rotated; + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + coat_tangent = normalize(coat_tangent_rotated); + } + // + // Coat affect color: darken diffuse under the coat + vec3 coat_gamma = vec3((clamp(coat, 0.0, 1.0) * coat_affect_color) + 1.0); + vec3 coat_affected_diffuse_color = pow(clamp(base_color, 0.0, 1.0), coat_gamma); + // + // Coat affect subsurface color + vec3 coat_affected_subsurface_color = pow(clamp(subsurface_color, 0.0, 1.0), coat_gamma); + // + // Diffuse BSDF (Oren-Nayar) + // `energy_compensation=false` to match the Standard Surface spec, + // instead of the more physically-correct `true` in OpenPBR + BSDF diffuse_bsdf; + diffuse_bsdf.response = vec3(0.0, 0.0, 0.0); + diffuse_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_oren_nayar_diffuse_bsdf(closureData, base, coat_affected_diffuse_color, diffuse_roughness, normal, false, diffuse_bsdf); + // + // Subsurface scattering + vec3 subsurface_radius_scaled = subsurface_radius * subsurface_scale; + BSDF sss_bsdf; + sss_bsdf.response = vec3(0.0, 0.0, 0.0); + sss_bsdf.throughput = vec3(1.0, 1.0, 1.0); + if (thin_walled) + { + mx_translucent_bsdf(closureData, 1.0, coat_affected_subsurface_color, normal, sss_bsdf); + } + else + { + mx_subsurface_bsdf(closureData, 1.0, coat_affected_subsurface_color, subsurface_radius_scaled, subsurface_anisotropy, normal, sss_bsdf); + } + // + // Subsurface mix: blend SSS with diffuse + BSDF subsurface_mix; + subsurface_mix.response = mix(diffuse_bsdf.response, sss_bsdf.response, subsurface); + subsurface_mix.throughput = mix(diffuse_bsdf.throughput, sss_bsdf.throughput, subsurface); + // + // Sheen BSDF + BSDF sheen_bsdf_out; + sheen_bsdf_out.response = vec3(0.0, 0.0, 0.0); + sheen_bsdf_out.throughput = vec3(1.0, 1.0, 1.0); + mx_sheen_bsdf(closureData, sheen, sheen_color, sheen_roughness, normal, 0, sheen_bsdf_out); + // + // Sheen layer: sheen over subsurface mix + bsdf.response = sheen_bsdf_out.response + (subsurface_mix.response * sheen_bsdf_out.throughput); + bsdf.throughput = sheen_bsdf_out.throughput * subsurface_mix.throughput; + // + // Transmission roughness (coat-affected) + float transmission_roughness_clamped = clamp(specular_roughness + transmission_extra_roughness, 0.0, 1.0); + float transmission_roughness_scalar = (transmission_roughness_clamped * (1 - coat_roughness_factor)) + coat_roughness_factor; + vec2 transmission_roughness; + mx_roughness_anisotropy(transmission_roughness_scalar, specular_anisotropy, transmission_roughness); + // + // Transmission BSDF (dielectric transmission) + BSDF transmission_bsdf; + transmission_bsdf.response = vec3(0.0, 0.0, 0.0); + transmission_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, 1.0, transmission_color, specular_IOR, transmission_roughness, false, 0.0, 1.5, normal, main_tangent, 0, 1, transmission_bsdf); + // + // Transmission mix: blend transmission with sheen layer + bsdf.response = mix(bsdf.response, transmission_bsdf.response, transmission); + bsdf.throughput = mix(bsdf.throughput, transmission_bsdf.throughput, transmission); + // + // Specular BSDF (dielectric reflection) + BSDF specular_bsdf; + specular_bsdf.response = vec3(0.0, 0.0, 0.0); + specular_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, specular, specular_color, specular_IOR, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, 0, specular_bsdf); + // + // Layer: specular over transmission mix + bsdf.response = specular_bsdf.response + (bsdf.response * specular_bsdf.throughput); + bsdf.throughput = specular_bsdf.throughput * bsdf.throughput; + // + // Artistic IOR (reflectivity/edge-color -> physical IOR/extinction) + vec3 metal_reflectivity = base_color * base; + vec3 metal_edgecolor = specular_color * specular; + vec3 ior_n; + vec3 ior_k; + mx_artistic_ior(metal_reflectivity, metal_edgecolor, ior_n, ior_k); + // + // Conductor BSDF (metal reflection) + BSDF metal_bsdf; + metal_bsdf.response = vec3(0.0, 0.0, 0.0); + metal_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_conductor_bsdf(closureData, metalness, ior_n, ior_k, main_roughness, false, thin_film_thickness, thin_film_IOR, normal, main_tangent, 0, metal_bsdf); + // + // Metalness mix: conductor (fg) vs specular layer (bg) + // Conductor response is already scaled by metalness (the weight), + // so we just add it to the attenuated specular layer. + float one_minus_metalness = 1 - metalness; + bsdf.response = metal_bsdf.response + (bsdf.response * one_minus_metalness); + bsdf.throughput = metal_bsdf.throughput + (bsdf.throughput * one_minus_metalness); + // + // Coat attenuation: tint underlying layers by coat color + // Float3 coercion needed: RgbF lerp result -> Float3 for BSDF multiply + vec3 coat_attenuation = mix(vec3(1.0), coat_color, coat); + bsdf.response = bsdf.response * coat_attenuation; + bsdf.throughput = bsdf.throughput * coat_attenuation; + // + // Coat roughness + vec2 coat_roughness_vec; + mx_roughness_anisotropy(coat_roughness, coat_anisotropy, coat_roughness_vec); + // + // Coat BSDF (dielectric reflection) + BSDF coat_bsdf; + coat_bsdf.response = vec3(0.0, 0.0, 0.0); + coat_bsdf.throughput = vec3(1.0, 1.0, 1.0); + mx_dielectric_bsdf(closureData, coat, vec3(1.0, 1.0, 1.0), coat_IOR, coat_roughness_vec, false, 0.0, 1.5, coat_normal, coat_tangent, 0, 0, coat_bsdf); + // + // Coat layer: coat over attenuated base + bsdf.response = coat_bsdf.response + (bsdf.response * coat_bsdf.throughput); + bsdf.throughput = coat_bsdf.throughput * bsdf.throughput; +} + + +void mx_luminance_color3(vec3 _in, vec3 lumacoeffs, out vec3 result) +{ + result = vec3(dot(_in, lumacoeffs)); +} + + +void mx_uniform_edf(ClosureData closureData, vec3 color, out EDF result) +{ + if (closureData.closureType == CLOSURE_TYPE_EMISSION) + { + result = color; + } +} + +void NG_metashade_standard_surface(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) +{ + vec3 emission_weight_out = emission_color * emission; + vec3 opacity_luminance_out = vec3(0.0); + mx_luminance_color3(opacity, vec3(0.272229, 0.674082, 0.053689), opacity_luminance_out); + const int opacity_luminance_float_index_tmp = 0; + float opacity_luminance_float_out = opacity_luminance_out[opacity_luminance_float_index_tmp]; + surfaceshader surface_ctor_out = surfaceshader(vec3(0.0),vec3(0.0)); + { + vec3 N = normalize(vd.normalWorld); + vec3 V = normalize(u_viewPosition - vd.positionWorld); + vec3 P = vd.positionWorld; + vec3 L = vec3(0.000000, 0.000000, 0.000000); + float occlusion = 1.0; + + float surfaceOpacity = opacity_luminance_float_out; + + // Shadow occlusion + + // Ambient occlusion + occlusion = 1.0; + + // Add environment contribution + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_INDIRECT, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + + surface_ctor_out.color += occlusion * ss_bsdf_bsdf.response; + } + + // Add surface emission + { + ClosureData closureData = makeClosureData(CLOSURE_TYPE_EMISSION, L, V, N, P, occlusion); + EDF emission_edf_out = EDF(0.0); + mx_uniform_edf(closureData, emission_weight_out, emission_edf_out); + surface_ctor_out.color += emission_edf_out; + } + + // Calculate the BSDF transmission for viewing direction + ClosureData closureData = makeClosureData(CLOSURE_TYPE_TRANSMISSION, L, V, N, P, occlusion); + BSDF ss_bsdf_bsdf = BSDF(vec3(0.0),vec3(1.0)); + mx_metashade_standard_surface_bsdf(closureData, base, base_color, diffuse_roughness, metalness, specular, specular_color, specular_roughness, specular_IOR, specular_anisotropy, specular_rotation, transmission, transmission_color, transmission_extra_roughness, subsurface, subsurface_color, subsurface_radius, subsurface_scale, subsurface_anisotropy, sheen, sheen_color, sheen_roughness, coat, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_IOR, coat_normal, coat_affect_color, coat_affect_roughness, thin_film_thickness, thin_film_IOR, thin_walled, normal, tangent, ss_bsdf_bsdf); + surface_ctor_out.color += ss_bsdf_bsdf.response; + + // Compute and apply surface opacity + { + surface_ctor_out.color *= surfaceOpacity; + surface_ctor_out.transparency = mix(vec3(1.000000, 1.000000, 1.000000), surface_ctor_out.transparency, surfaceOpacity); + } + } + + out1 = surface_ctor_out; +} + +void main() +{ + vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); + vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); + vec3 position_vector3_out = vd.positionObject; + float NG_legacy_wood_thickness_unit_out = NG_legacy_wood_thickness_unit_in1 * NG_legacy_wood_thickness_unit_in2; + vec3 multiply_vector3FA_out = position_vector3_out * multiply_vector3FA_in2; + vec3 NG_legacy_wood_out = vec3(0.0); + NG_legacy_wood_color3(multiply_vector3FA_out, NG_legacy_wood_color1, NG_legacy_wood_color2, NG_legacy_wood_radial_noise, NG_legacy_wood_axial_noise, NG_legacy_wood_thickness_unit_out, NG_legacy_wood_loop, NG_legacy_wood_noise_repetitions, NG_legacy_wood_realworld_offset, NG_legacy_wood_rotation_angle, NG_legacy_wood_out); + surfaceshader SR_testmat1_out = surfaceshader(vec3(0.0),vec3(0.0)); + NG_metashade_standard_surface(SR_testmat1_base, NG_legacy_wood_out, SR_testmat1_diffuse_roughness, SR_testmat1_metalness, SR_testmat1_specular, SR_testmat1_specular_color, SR_testmat1_specular_roughness, SR_testmat1_specular_IOR, SR_testmat1_specular_anisotropy, SR_testmat1_specular_rotation, SR_testmat1_transmission, SR_testmat1_transmission_color, SR_testmat1_transmission_depth, SR_testmat1_transmission_scatter, SR_testmat1_transmission_scatter_anisotropy, SR_testmat1_transmission_dispersion, SR_testmat1_transmission_extra_roughness, SR_testmat1_subsurface, SR_testmat1_subsurface_color, SR_testmat1_subsurface_radius, SR_testmat1_subsurface_scale, SR_testmat1_subsurface_anisotropy, SR_testmat1_sheen, SR_testmat1_sheen_color, SR_testmat1_sheen_roughness, SR_testmat1_coat, SR_testmat1_coat_color, SR_testmat1_coat_roughness, SR_testmat1_coat_anisotropy, SR_testmat1_coat_rotation, SR_testmat1_coat_IOR, geomprop_Nworld_out1, SR_testmat1_coat_affect_color, SR_testmat1_coat_affect_roughness, SR_testmat1_thin_film_thickness, SR_testmat1_thin_film_IOR, SR_testmat1_emission, SR_testmat1_emission_color, SR_testmat1_opacity, SR_testmat1_thin_walled, geomprop_Nworld_out1, geomprop_Tworld_out1, SR_testmat1_out); + material M_legacy_wood_out = SR_testmat1_out; + out1 = vec4(M_legacy_wood_out.color, 1.0); +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_vs.glsl b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_vs.glsl new file mode 100644 index 0000000000..e9c23bbd30 --- /dev/null +++ b/contrib/tests/metashade_ref/renders/standard_surface/adsk/Examples/Revit/wood/M_legacy_wood_vs.glsl @@ -0,0 +1,79 @@ +#version 400 + + +// Uniform block: PrivateUniforms +uniform mat4 u_worldMatrix = mat4(1.0); +uniform mat4 u_viewProjectionMatrix = mat4(1.0); +uniform mat4 u_worldInverseTransposeMatrix = mat4(1.0); + +// Inputs block: VertexInputs +in vec3 i_position; +in vec3 i_normal; +in vec3 i_tangent; + +out VertexData +{ + vec3 normalWorld; + vec3 tangentWorld; + vec3 positionObject; + vec3 positionWorld; +} vd; + +#define M_FLOAT_EPS 1e-8 +#define M_PI 3.1415926535897932 + +#define mx_mod mod +#define mx_inverse inverse +#define mx_inversesqrt inversesqrt +#define mx_sin sin +#define mx_cos cos +#define mx_tan tan +#define mx_asin asin +#define mx_acos acos +#define mx_atan atan +#define mx_radians radians +#define mx_float_bits_to_int floatBitsToInt + +vec2 mx_matrix_mul(vec2 v, mat2 m) { return v * m; } +vec3 mx_matrix_mul(vec3 v, mat3 m) { return v * m; } +vec4 mx_matrix_mul(vec4 v, mat4 m) { return v * m; } +vec2 mx_matrix_mul(mat2 m, vec2 v) { return m * v; } +vec3 mx_matrix_mul(mat3 m, vec3 v) { return m * v; } +vec4 mx_matrix_mul(mat4 m, vec4 v) { return m * v; } +mat2 mx_matrix_mul(mat2 m1, mat2 m2) { return m1 * m2; } +mat3 mx_matrix_mul(mat3 m1, mat3 m2) { return m1 * m2; } +mat4 mx_matrix_mul(mat4 m1, mat4 m2) { return m1 * m2; } + +float mx_square(float x) +{ + return x*x; +} + +vec2 mx_square(vec2 x) +{ + return x*x; +} + +vec3 mx_square(vec3 x) +{ + return x*x; +} + +vec3 mx_srgb_encode(vec3 color) +{ + bvec3 isAbove = greaterThan(color, vec3(0.0031308)); + vec3 linSeg = color * 12.92; + vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; + return mix(linSeg, powSeg, isAbove); +} + +void main() +{ + vec4 hPositionWorld = u_worldMatrix * vec4(i_position, 1.0); + gl_Position = u_viewProjectionMatrix * hPositionWorld; + vd.normalWorld = normalize(mx_matrix_mul(u_worldInverseTransposeMatrix, vec4(i_normal, 0.0)).xyz); + vd.tangentWorld = normalize(mx_matrix_mul(u_worldMatrix, vec4(i_tangent, 0.0)).xyz); + vd.positionObject = i_position; + vd.positionWorld = hPositionWorld.xyz; +} + diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brass_tiled/Tiled_Brass_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brass_tiled/Tiled_Brass_ps.glsl index 4dfddf21ed..dae0390811 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brass_tiled/Tiled_Brass_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brass_tiled/Tiled_Brass_ps.glsl @@ -991,19 +991,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1705,6 +1692,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1722,7 +1726,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1732,7 +1736,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brick_procedural/M_BrickPattern_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brick_procedural/M_BrickPattern_ps.glsl index 984e093227..d0aa9fb4b4 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brick_procedural/M_BrickPattern_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_brick_procedural/M_BrickPattern_ps.glsl @@ -1119,19 +1119,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1833,6 +1820,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1850,7 +1854,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1860,7 +1864,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_carpaint/Car_Paint_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_carpaint/Car_Paint_ps.glsl index 6c29d114d9..65dd615607 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_carpaint/Car_Paint_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_carpaint/Car_Paint_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_B_ps.glsl index f290a15c8f..830bae2728 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_B_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_W_ps.glsl index 8dca64843c..1ecfd59451 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Bishop_W_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_B_ps.glsl index 9a1a76be79..1e189f87b6 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_B_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_W_ps.glsl index 7984b843d7..336ba3f6ba 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Castle_W_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Chessboard_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Chessboard_ps.glsl index 1e14265257..3ad6742780 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Chessboard_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Chessboard_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_B_ps.glsl index dcb7dab625..bd78f349b3 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_B_ps.glsl @@ -1024,19 +1024,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1738,6 +1725,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1765,7 +1769,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_W_ps.glsl index 8b138d1dee..c6878a2871 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_King_W_ps.glsl @@ -1024,19 +1024,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1738,6 +1725,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1765,7 +1769,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_B_ps.glsl index 514baeafe3..674c583fc9 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_B_ps.glsl @@ -1004,19 +1004,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1718,6 +1705,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1735,7 +1739,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_W_ps.glsl index bba335d918..5e2aceb003 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Knight_W_ps.glsl @@ -1004,19 +1004,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1718,6 +1705,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1735,7 +1739,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_B_ps.glsl index ac41c74642..e839004a5b 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_B_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_W_ps.glsl index 9666dc7af2..d9c5a72113 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Body_W_ps.glsl @@ -1014,19 +1014,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1728,6 +1715,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1745,7 +1749,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_B_ps.glsl index 03a9594787..44e914df32 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_B_ps.glsl @@ -944,19 +944,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1658,6 +1645,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1675,7 +1679,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1685,7 +1689,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_W_ps.glsl index 09ec08006d..66a359de7b 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Pawn_Top_W_ps.glsl @@ -944,19 +944,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1658,6 +1645,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1675,7 +1679,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1685,7 +1689,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_B_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_B_ps.glsl index 10f861125a..b1eb3e2f67 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_B_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_B_ps.glsl @@ -1024,19 +1024,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1738,6 +1725,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1765,7 +1769,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_W_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_W_ps.glsl index 89ce77e223..976f546d89 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_W_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chess_set/M_Queen_W_ps.glsl @@ -1024,19 +1024,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1738,6 +1725,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1755,7 +1759,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1765,7 +1769,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chrome/Chrome_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chrome/Chrome_ps.glsl index c9a28867bc..041207fd5a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chrome/Chrome_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_chrome/Chrome_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_copper/Copper_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_copper/Copper_ps.glsl index 3285e28955..89c5b96621 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_copper/Copper_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_copper/Copper_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_default/Default_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_default/Default_ps.glsl index 1fb628f80a..118a966f7f 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_default/Default_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_default/Default_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass/Glass_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass/Glass_ps.glsl index 0067143706..135716e84c 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass/Glass_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass/Glass_ps.glsl @@ -886,19 +886,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1600,6 +1587,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1617,7 +1621,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1627,7 +1631,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass_tinted/GlassTinted_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass_tinted/GlassTinted_ps.glsl index 7076fd4d58..5d74062e26 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass_tinted/GlassTinted_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_glass_tinted/GlassTinted_ps.glsl @@ -886,19 +886,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1600,6 +1587,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1617,7 +1621,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1627,7 +1631,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_gold/Gold_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_gold/Gold_ps.glsl index 84213c3c52..d2c509e800 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_gold/Gold_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_gold/Gold_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere/Greysphere_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere/Greysphere_ps.glsl index b78fba63ea..04d96cadf1 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere/Greysphere_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere/Greysphere_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere_calibration/Greysphere_Calibration_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere_calibration/Greysphere_Calibration_ps.glsl index f227f0403d..e58082445a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere_calibration/Greysphere_Calibration_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_greysphere_calibration/Greysphere_Calibration_ps.glsl @@ -1012,19 +1012,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1726,6 +1713,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1743,7 +1747,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1753,7 +1757,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_jade/Jade_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_jade/Jade_ps.glsl index 243c41fe47..ee737b8307 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_jade/Jade_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_jade/Jade_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Greysphere_Calibration_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Greysphere_Calibration_ps.glsl index f227f0403d..e58082445a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Greysphere_Calibration_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Greysphere_Calibration_ps.glsl @@ -1012,19 +1012,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1726,6 +1713,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1743,7 +1747,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1753,7 +1757,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Tiled_Brass_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Tiled_Brass_ps.glsl index 4dfddf21ed..dae0390811 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Tiled_Brass_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_brass_tiled/Tiled_Brass_ps.glsl @@ -991,19 +991,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1705,6 +1692,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1722,7 +1726,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1732,7 +1736,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Greysphere_Calibration_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Greysphere_Calibration_ps.glsl index f227f0403d..e58082445a 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Greysphere_Calibration_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Greysphere_Calibration_ps.glsl @@ -1012,19 +1012,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1726,6 +1713,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1743,7 +1747,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1753,7 +1757,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Tiled_Wood_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Tiled_Wood_ps.glsl index 85b51f6a21..5533be8553 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Tiled_Wood_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_look_wood_tiled/Tiled_Wood_ps.glsl @@ -992,19 +992,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1706,6 +1693,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1723,7 +1727,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1733,7 +1737,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_marble_solid/Marble_3D_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_marble_solid/Marble_3D_ps.glsl index 98b0abf425..2108fe9465 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_marble_solid/Marble_3D_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_marble_solid/Marble_3D_ps.glsl @@ -1663,19 +1663,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -2377,6 +2364,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -2394,7 +2398,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -2404,7 +2408,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_metal_brushed/Metal_Brushed_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_metal_brushed/Metal_Brushed_ps.glsl index e88d46bf59..8104fcf12f 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_metal_brushed/Metal_Brushed_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_metal_brushed/Metal_Brushed_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_onyx_hextiled/M_OnyxHextiled_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_onyx_hextiled/M_OnyxHextiled_ps.glsl index 3d0d1a5d40..de3913be13 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_onyx_hextiled/M_OnyxHextiled_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_onyx_hextiled/M_OnyxHextiled_ps.glsl @@ -1181,19 +1181,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1895,6 +1882,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1912,7 +1916,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1922,7 +1926,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_plastic/Plastic_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_plastic/Plastic_ps.glsl index 265faae321..0c992861a0 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_plastic/Plastic_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_plastic/Plastic_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_thin_film/ThinFilm_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_thin_film/ThinFilm_ps.glsl index af9b1ab421..f3718e01f6 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_thin_film/ThinFilm_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_thin_film/ThinFilm_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_velvet/Velvet_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_velvet/Velvet_ps.glsl index f5fa219307..41f6ef478f 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_velvet/Velvet_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_velvet/Velvet_ps.glsl @@ -885,19 +885,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1599,6 +1586,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1616,7 +1620,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1626,7 +1630,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_wood_tiled/Tiled_Wood_ps.glsl b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_wood_tiled/Tiled_Wood_ps.glsl index 85b51f6a21..5533be8553 100644 --- a/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_wood_tiled/Tiled_Wood_ps.glsl +++ b/contrib/tests/metashade_ref/renders/standard_surface/aswf/standard_surface_wood_tiled/Tiled_Wood_ps.glsl @@ -992,19 +992,6 @@ void mx_roughness_anisotropy(float roughness, float anisotropy, out vec2 result) result.y = roughness_sqr; } } -void mx_rotate_vector3(vec3 _in, float amount, vec3 axis, out vec3 result) -{ - // Based on https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula, where the - // Wikipedia formula follows v' = M * v and MaterialX follows v' = v * M, thus the - // order of parameters to cross are reversed. - - axis = normalize(axis); - float rotationRadians = mx_radians(amount); - float s = mx_sin(rotationRadians); - float c = mx_cos(rotationRadians); - float oc = 1.0 - c; - result = _in * c + cross(_in, axis) * s + axis * dot(axis, _in) * oc; -} // These are defined based on the HwShaderGenerator::ClosureContextType enum // if that changes - these need to be updated accordingly. @@ -1706,6 +1693,23 @@ void mx_artistic_ior(vec3 reflectivity, vec3 edge_color, out vec3 ior, out vec3 k2 = max(k2, 0.0); extinction = sqrt(k2); } +// Rodrigues' rotation formula. +// +// Private copy of the stdlib rotate3d helper. Avoids +// duplicate-definition errors when the material's own nodegraph +// also uses rotate3d nodes, which would cause the generator to +// emit mx_rotate_vector3 a second time +// (see https://github.com/metashade/metashade/issues/230). +// +void _mx_metashade_rotate_vector3(vec3 in_, float amount, vec3 axis, out vec3 result) +{ + vec3 axis_n = normalize(axis); + float rad = radians(amount); + float s = sin(rad); + float c = cos(rad); + result = ((in_ * c) + (cross(in_, axis_n) * s)) + ((axis_n * dot(axis_n, in_)) * (1 - c)); +} + void mx_metashade_standard_surface_bsdf(ClosureData closureData, 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_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, bool thin_walled, vec3 normal, vec3 tangent, inout BSDF bsdf) { // @@ -1723,7 +1727,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float tangent_rotate_degree = specular_rotation * 360.0; vec3 tangent_rotated; - mx_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); + _mx_metashade_rotate_vector3(tangent, tangent_rotate_degree, normal, tangent_rotated); main_tangent = normalize(tangent_rotated); } // @@ -1733,7 +1737,7 @@ void mx_metashade_standard_surface_bsdf(ClosureData closureData, float base, vec { float coat_tangent_rotate_degree = coat_rotation * 360.0; vec3 coat_tangent_rotated; - mx_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); + _mx_metashade_rotate_vector3(tangent, coat_tangent_rotate_degree, coat_normal, coat_tangent_rotated); coat_tangent = normalize(coat_tangent_rotated); } // diff --git a/contrib/tests/test_render_metashade.py b/contrib/tests/test_render_metashade.py index 6b9c3796fd..9295bd2279 100644 --- a/contrib/tests/test_render_metashade.py +++ b/contrib/tests/test_render_metashade.py @@ -290,29 +290,9 @@ def test_render(self, case: RenderTestCase, subtests, override_env): override_env.run_test(case, subtests) -# Adsk materials excluded from the Metashade SS override test. -# mx_rotate_vector3 duplicate: override search path adds genglsl dirs for -# #include resolution, causing the shader generator to inline -# mx_rotate_vector3 twice when these materials use specular_rotation. -# FLIP threshold: visual difference from the Metashade SS implementation. -_ADSK_METASHADE_EXCLUDE = frozenset({ - "knurl.mtlx", # mx_rotate_vector3 duplicate - "marble.mtlx", # mx_rotate_vector3 duplicate - "metal.mtlx", # mx_rotate_vector3 duplicate - "metallicpaint.mtlx", # mx_rotate_vector3 duplicate - "noise.mtlx", # mx_rotate_vector3 duplicate - "speckle.mtlx", # mx_rotate_vector3 duplicate - "waves.mtlx", # mx_rotate_vector3 duplicate - "wood.mtlx", # mx_rotate_vector3 duplicate -}) - - def _get_adsk_metashade_test_files(): - """Collect adsk materials, excluding known Metashade override failures.""" - return [ - p for p in collect_adsk_test_files() - if p.values[0].input_path.name not in _ADSK_METASHADE_EXCLUDE - ] + """Collect adsk materials for Metashade override testing.""" + return collect_adsk_test_files() class TestRenderMetashadeAdskMaterials(MetashadeOverrideTestBase):