From 3580bb795ac6bf8bbe577da7cda4cd32a0257c05 Mon Sep 17 00:00:00 2001 From: Apoorva Joshi Date: Fri, 29 Dec 2023 07:02:58 +0100 Subject: [PATCH] Improvements to prefiltered environment maps (#1420) As discussed with @jstone-lucasfilm, MaterialXView's pre-integrated IBL path (i.e. when FIS is turned off) is broken. This PR attempts to fix this issue. --- .../genglsl/lib/mx_environment_fis.glsl | 10 -- .../genglsl/lib/mx_environment_prefilter.glsl | 9 +- .../genglsl/lib/mx_microfacet_specular.glsl | 24 +++++ .../genglsl/lib/mx_prefilter_environment.glsl | 76 +++++++++++++ .../JsMaterialXGenShader/JsGenOptions.cpp | 1 + .../MaterialXGenGlsl/GlslShaderGenerator.cpp | 13 ++- source/MaterialXGenMsl/MslShaderGenerator.cpp | 11 ++ source/MaterialXGenShader/GenOptions.h | 5 + .../MaterialXGenShader/HwShaderGenerator.cpp | 13 +++ source/MaterialXGenShader/HwShaderGenerator.h | 2 + source/MaterialXRender/ImageHandler.cpp | 2 +- source/MaterialXRender/ImageHandler.h | 2 +- source/MaterialXRender/LightHandler.h | 27 +++++ source/MaterialXRender/Util.cpp | 20 ++++ source/MaterialXRender/Util.h | 5 + .../MaterialXRenderGlsl/GLTextureHandler.cpp | 2 +- source/MaterialXRenderGlsl/GLTextureHandler.h | 2 +- source/MaterialXRenderGlsl/GlslProgram.cpp | 13 ++- .../MaterialXRenderMsl/MetalTextureHandler.h | 2 +- .../MaterialXRenderMsl/MetalTextureHandler.mm | 6 +- .../MslPipelineStateObject.mm | 2 +- source/MaterialXView/RenderPipeline.h | 1 + source/MaterialXView/RenderPipelineGL.cpp | 102 +++++++++++++++++- source/MaterialXView/RenderPipelineGL.h | 1 + source/MaterialXView/RenderPipelineMetal.h | 2 + source/MaterialXView/RenderPipelineMetal.mm | 98 ++++++++++++++++- source/MaterialXView/Viewer.cpp | 5 + .../PyMaterialXGenShader/PyGenOptions.cpp | 1 + 28 files changed, 425 insertions(+), 32 deletions(-) create mode 100644 libraries/pbrlib/genglsl/lib/mx_prefilter_environment.glsl diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl index 575991e28d..85c88c3280 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl @@ -1,15 +1,5 @@ #include "mx_microfacet_specular.glsl" -// 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. diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl index 6def6fb439..e448ae247e 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl @@ -1,12 +1,5 @@ #include "mx_microfacet_specular.glsl" -float mx_latlong_compute_lod(float alpha) -{ - // Select a mip level based on input alpha. - float lodBias = alpha < 0.25 ? sqrt(alpha) : 0.5*alpha + 0.375; - return lodBias * float($envRadianceMips); -} - vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, FresnelData fd) { N = mx_forward_facing_normal(N, V); @@ -19,7 +12,7 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio float G = mx_ggx_smith_G2(NdotV, NdotV, avgAlpha); vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G; - vec3 Li = mx_latlong_map_lookup(L, $envMatrix, mx_latlong_compute_lod(avgAlpha), $envRadiance); + vec3 Li = mx_latlong_map_lookup(L, $envMatrix, mx_latlong_alpha_to_lod(avgAlpha), $envRadiance); return Li * FG; } diff --git a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl index 63aba17869..85430c00ca 100644 --- a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl @@ -593,3 +593,27 @@ vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D envSam vec2 uv = mx_latlong_projection(envDir); return textureLod(envSampler, uv, lod).rgb; } + +// 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); +} + +float mx_latlong_alpha_to_lod(float alpha) +{ + // Return the mip level associated with the given alpha in a prefiltered environment. + float lodBias = (alpha < 0.25) ? sqrt(alpha) : 0.5 * alpha + 0.375; + return lodBias * float($envRadianceMips - 1); +} + +float mx_latlong_lod_to_alpha(float lod) +{ + // Return the alpha associated with the given mip level in a prefiltered environment. + float lodBias = lod / float($envRadianceMips - 1); + return (lodBias < 0.5) ? mx_square(lodBias) : 2.0 * (lodBias - 0.375); +} diff --git a/libraries/pbrlib/genglsl/lib/mx_prefilter_environment.glsl b/libraries/pbrlib/genglsl/lib/mx_prefilter_environment.glsl new file mode 100644 index 0000000000..fd608871ed --- /dev/null +++ b/libraries/pbrlib/genglsl/lib/mx_prefilter_environment.glsl @@ -0,0 +1,76 @@ +#include "mx_microfacet_specular.glsl" + +// 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); +} + +// The inverse of mx_latlong_projection. +vec3 mx_latlong_map_projection_inverse(vec2 uv) +{ + float latitude = (uv.y - 0.5) * M_PI; + float longitude = (uv.x - 0.5) * M_PI * 2.0; + + float x = -cos(latitude) * sin(longitude); + float y = -sin(latitude); + float z = cos(latitude) * cos(longitude); + + return vec3(x, y, z); +} + +vec3 mx_prefilter_environment() +{ + vec2 uv = gl_FragCoord.xy * pow(2.0, $envPrefilterMip) / vec2(2048.0, 1024.0); + float alpha = mx_latlong_lod_to_alpha(float($envPrefilterMip)); + if ($envPrefilterMip == 0) + { + return textureLod($envRadiance, uv, 0).rgb; + } + + // Compute world normal and transform. + vec3 worldN = mx_latlong_map_projection_inverse(uv); + mat3 tangentToWorld = mx_orthonormal_basis(worldN); + + // Local normal and view vectors are constant and aligned. + vec3 V = vec3(0.0, 0.0, 1.0); + float NdotV = 1.0; + float G1V = mx_ggx_smith_G1(NdotV, alpha); + + // Integrate the LD term for the given environment and alpha. + vec3 radiance = vec3(0.0, 0.0, 0.0); + float weight = 0.0; + int envRadianceSamples = 1024; + 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, vec2(alpha)); + vec3 L = -V + 2.0 * H.z * H; + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, alpha); + + // Sample the environment light from the given direction. + vec3 Lw = tangentToWorld * L; + float pdf = mx_ggx_NDF(H, vec2(alpha)) * G1V / (4.0 * NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float($envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, $envMatrix, lod, $envRadiance); + + // Add the radiance contribution of this sample. + radiance += G * sampleColor; + weight += G; + } + + return radiance / weight; +} diff --git a/source/JsMaterialX/JsMaterialXGenShader/JsGenOptions.cpp b/source/JsMaterialX/JsMaterialXGenShader/JsGenOptions.cpp index 7e3313cdf7..9b28a6f534 100644 --- a/source/JsMaterialX/JsMaterialXGenShader/JsGenOptions.cpp +++ b/source/JsMaterialX/JsMaterialXGenShader/JsGenOptions.cpp @@ -42,5 +42,6 @@ EMSCRIPTEN_BINDINGS(GenOptions) .property("hwMaxActiveLightSources", &mx::GenOptions::hwMaxActiveLightSources) .property("hwNormalizeUdimTexCoords", &mx::GenOptions::hwNormalizeUdimTexCoords) .property("hwWriteAlbedoTable", &mx::GenOptions::hwWriteAlbedoTable) + .property("hwWriteEnvPrefilter", &mx::GenOptions::hwWriteEnvPrefilter) ; } diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp index ea78e1bee3..b9351ddc08 100644 --- a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp +++ b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp @@ -553,7 +553,7 @@ void GlslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& c bool lighting = requiresLighting(graph); // Define directional albedo approach - if (lighting || context.getOptions().hwWriteAlbedoTable) + if (lighting || context.getOptions().hwWriteAlbedoTable || context.getOptions().hwWriteEnvPrefilter) { emitLine("#define DIRECTIONAL_ALBEDO_METHOD " + std::to_string(int(context.getOptions().hwDirectionalAlbedoMethod)), stage, false); emitLineBreak(stage); @@ -591,6 +591,13 @@ void GlslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& c emitLineBreak(stage); } + // Emit environment prefiltering code + if (context.getOptions().hwWriteEnvPrefilter) + { + emitLibraryInclude("pbrlib/genglsl/lib/mx_prefilter_environment.glsl", context, stage); + emitLineBreak(stage); + } + // Set the include file to use for uv transformations, // depending on the vertical flip flag. if (context.getOptions().fileTextureVerticalFlip) @@ -636,6 +643,10 @@ void GlslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& c { emitLine(outputSocket->getVariable() + " = vec4(mx_generate_dir_albedo_table(), 1.0)", stage); } + else if (context.getOptions().hwWriteEnvPrefilter) + { + emitLine(outputSocket->getVariable() + " = vec4(mx_prefilter_environment(), 1.0)", stage); + } else { // Add all function calls. diff --git a/source/MaterialXGenMsl/MslShaderGenerator.cpp b/source/MaterialXGenMsl/MslShaderGenerator.cpp index 7fe50fef4a..5749f0d211 100644 --- a/source/MaterialXGenMsl/MslShaderGenerator.cpp +++ b/source/MaterialXGenMsl/MslShaderGenerator.cpp @@ -1059,6 +1059,13 @@ void MslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& co emitLineBreak(stage); } + // Emit environment prefiltering code + if (context.getOptions().hwWriteEnvPrefilter) + { + emitLibraryInclude("pbrlib/genglsl/lib/mx_prefilter_environment.glsl", context, stage); + emitLineBreak(stage); + } + // Set the include file to use for uv transformations, // depending on the vertical flip flag. if (context.getOptions().fileTextureVerticalFlip) @@ -1104,6 +1111,10 @@ void MslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& co { emitLine(outputSocket->getVariable() + " = float4(mx_generate_dir_albedo_table(), 1.0)", stage); } + else if (context.getOptions().hwWriteEnvPrefilter) + { + emitLine(outputSocket->getVariable() + " = float4(mx_prefilter_environment(), 1.0)", stage); + } else { // Add all function calls. diff --git a/source/MaterialXGenShader/GenOptions.h b/source/MaterialXGenShader/GenOptions.h index 34a1efde0b..6af73d62a8 100644 --- a/source/MaterialXGenShader/GenOptions.h +++ b/source/MaterialXGenShader/GenOptions.h @@ -90,6 +90,7 @@ class MX_GENSHADER_API GenOptions hwMaxActiveLightSources(3), hwNormalizeUdimTexCoords(false), hwWriteAlbedoTable(false), + hwWriteEnvPrefilter(false), hwImplicitBitangents(true), emitColorTransforms(true) { @@ -174,6 +175,10 @@ class MX_GENSHADER_API GenOptions /// Defaults to false. bool hwWriteAlbedoTable; + /// Enables the generation of a prefiltered environment map. + /// Defaults to false. + bool hwWriteEnvPrefilter; + /// Calculate fallback bitangents from existing normals and tangents /// inside the bitangent node. bool hwImplicitBitangents; diff --git a/source/MaterialXGenShader/HwShaderGenerator.cpp b/source/MaterialXGenShader/HwShaderGenerator.cpp index 0db78eda27..6ba94d1280 100644 --- a/source/MaterialXGenShader/HwShaderGenerator.cpp +++ b/source/MaterialXGenShader/HwShaderGenerator.cpp @@ -59,6 +59,7 @@ const string T_ENV_RADIANCE = "$envRadiance"; const string T_ENV_RADIANCE_MIPS = "$envRadianceMips"; const string T_ENV_RADIANCE_SAMPLES = "$envRadianceSamples"; const string T_ENV_IRRADIANCE = "$envIrradiance"; +const string T_ENV_PREFILTER_MIP = "$envPrefilterMip"; const string T_REFRACTION_TWO_SIDED = "$refractionTwoSided"; const string T_ALBEDO_TABLE = "$albedoTable"; const string T_ALBEDO_TABLE_SIZE = "$albedoTableSize"; @@ -113,6 +114,7 @@ const string ENV_RADIANCE = "u_envRadiance"; const string ENV_RADIANCE_MIPS = "u_envRadianceMips"; const string ENV_RADIANCE_SAMPLES = "u_envRadianceSamples"; const string ENV_IRRADIANCE = "u_envIrradiance"; +const string ENV_PREFILTER_MIP = "u_envPrefilterMip"; const string REFRACTION_TWO_SIDED = "u_refractionTwoSided"; const string ALBEDO_TABLE = "u_albedoTable"; const string ALBEDO_TABLE_SIZE = "u_albedoTableSize"; @@ -222,6 +224,7 @@ HwShaderGenerator::HwShaderGenerator(SyntaxPtr syntax) : _tokenSubstitutions[HW::T_AMB_OCC_GAIN] = HW::AMB_OCC_GAIN; _tokenSubstitutions[HW::T_VERTEX_DATA_INSTANCE] = HW::VERTEX_DATA_INSTANCE; _tokenSubstitutions[HW::T_LIGHT_DATA_INSTANCE] = HW::LIGHT_DATA_INSTANCE; + _tokenSubstitutions[HW::T_ENV_PREFILTER_MIP] = HW::ENV_PREFILTER_MIP; // Setup closure contexts for defining closure functions // @@ -359,6 +362,16 @@ ShaderPtr HwShaderGenerator::createShader(const string& name, ElementPtr element psPrivateUniforms->add(Type::INTEGER, HW::T_ALBEDO_TABLE_SIZE, Value::createValue(64)); } + // Add uniforms for environment prefiltering. + if (context.getOptions().hwWriteEnvPrefilter) + { + psPrivateUniforms->add(Type::FILENAME, HW::T_ENV_RADIANCE); + psPrivateUniforms->add(Type::INTEGER, HW::T_ENV_PREFILTER_MIP, Value::createValue(1)); + const Matrix44 yRotationPI = Matrix44::createScale(Vector3(-1, 1, -1)); + psPrivateUniforms->add(Type::MATRIX44, HW::T_ENV_MATRIX, Value::createValue(yRotationPI)); + psPrivateUniforms->add(Type::INTEGER, HW::T_ENV_RADIANCE_MIPS, Value::createValue(1)); + } + // Create uniforms for the published graph interface for (ShaderGraphInputSocket* inputSocket : graph->getInputSockets()) { diff --git a/source/MaterialXGenShader/HwShaderGenerator.h b/source/MaterialXGenShader/HwShaderGenerator.h index bba572c631..f839ac56bd 100644 --- a/source/MaterialXGenShader/HwShaderGenerator.h +++ b/source/MaterialXGenShader/HwShaderGenerator.h @@ -126,6 +126,7 @@ extern MX_GENSHADER_API const string T_ENV_RADIANCE; extern MX_GENSHADER_API const string T_ENV_RADIANCE_MIPS; extern MX_GENSHADER_API const string T_ENV_RADIANCE_SAMPLES; extern MX_GENSHADER_API const string T_ENV_IRRADIANCE; +extern MX_GENSHADER_API const string T_ENV_PREFILTER_MIP; extern MX_GENSHADER_API const string T_REFRACTION_TWO_SIDED; extern MX_GENSHADER_API const string T_ALBEDO_TABLE; extern MX_GENSHADER_API const string T_ALBEDO_TABLE_SIZE; @@ -182,6 +183,7 @@ extern MX_GENSHADER_API const string ENV_RADIANCE; extern MX_GENSHADER_API const string ENV_RADIANCE_MIPS; extern MX_GENSHADER_API const string ENV_RADIANCE_SAMPLES; extern MX_GENSHADER_API const string ENV_IRRADIANCE; +extern MX_GENSHADER_API const string ENV_PREFILTER_MIP; extern MX_GENSHADER_API const string REFRACTION_TWO_SIDED; extern MX_GENSHADER_API const string ALBEDO_TABLE; extern MX_GENSHADER_API const string ALBEDO_TABLE_SIZE; diff --git a/source/MaterialXRender/ImageHandler.cpp b/source/MaterialXRender/ImageHandler.cpp index 2d56207f32..b148bf63d9 100644 --- a/source/MaterialXRender/ImageHandler.cpp +++ b/source/MaterialXRender/ImageHandler.cpp @@ -165,7 +165,7 @@ void ImageHandler::unbindImages() } } -bool ImageHandler::createRenderResources(ImagePtr, bool) +bool ImageHandler::createRenderResources(ImagePtr, bool, bool) { return false; } diff --git a/source/MaterialXRender/ImageHandler.h b/source/MaterialXRender/ImageHandler.h index e8eaf99b2f..e5d2082f8d 100644 --- a/source/MaterialXRender/ImageHandler.h +++ b/source/MaterialXRender/ImageHandler.h @@ -230,7 +230,7 @@ class MX_RENDER_API ImageHandler } /// Create rendering resources for the given image. - virtual bool createRenderResources(ImagePtr image, bool generateMipMaps); + virtual bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false); /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRender/LightHandler.h b/source/MaterialXRender/LightHandler.h index 852ccfa41d..a080c72081 100644 --- a/source/MaterialXRender/LightHandler.h +++ b/source/MaterialXRender/LightHandler.h @@ -37,6 +37,7 @@ class MX_RENDER_API LightHandler _lightTransform(Matrix44::IDENTITY), _directLighting(true), _indirectLighting(true), + _usePrefilteredMap(false), _envSampleCount(DEFAULT_ENV_SAMPLE_COUNT), _refractionTwoSided(false) { @@ -101,6 +102,30 @@ class MX_RENDER_API LightHandler return _envRadianceMap; } + /// Set the environment radiance map for the prefiltered environment lighting model. + void setEnvPrefilteredMap(ImagePtr map) + { + _envPrefilteredMap = map; + } + + /// Return the environment radiance map for the prefiltered environment lighting model. + ImagePtr getEnvPrefilteredMap() const + { + return _envPrefilteredMap; + } + + /// Set whether to use the prefiltered environment lighting model. + void setUsePrefilteredMap(bool val) + { + _usePrefilteredMap = val; + } + + /// Return whether to use the prefiltered environment lighting model. + bool getUsePrefilteredMap() + { + return _usePrefilteredMap; + } + /// Set the environment irradiance map void setEnvIrradianceMap(ImagePtr map) { @@ -216,8 +241,10 @@ class MX_RENDER_API LightHandler Matrix44 _lightTransform; bool _directLighting; bool _indirectLighting; + bool _usePrefilteredMap; ImagePtr _envRadianceMap; + ImagePtr _envPrefilteredMap; ImagePtr _envIrradianceMap; int _envSampleCount; diff --git a/source/MaterialXRender/Util.cpp b/source/MaterialXRender/Util.cpp index e8ec0f7c48..f33c67173e 100644 --- a/source/MaterialXRender/Util.cpp +++ b/source/MaterialXRender/Util.cpp @@ -76,6 +76,26 @@ ShaderPtr createAlbedoTableShader(GenContext& context, return shader; } +ShaderPtr createEnvPrefilterShader(GenContext& context, + DocumentPtr stdLib, + const string& shaderName) +{ + // Construct a dummy nodegraph. + DocumentPtr doc = createDocument(); + doc->importLibrary(stdLib); + NodeGraphPtr nodeGraph = doc->addNodeGraph(); + NodePtr constant = nodeGraph->addNode("constant"); + OutputPtr output = nodeGraph->addOutput(); + output->setConnectedNode(constant); + + // Generate the shader + GenContext tableContext = context; + tableContext.getOptions().hwWriteEnvPrefilter = true; + ShaderPtr shader = createShader(shaderName, tableContext, output); + + return shader; +} + ShaderPtr createBlurShader(GenContext& context, DocumentPtr stdLib, const string& shaderName, diff --git a/source/MaterialXRender/Util.h b/source/MaterialXRender/Util.h index 922c06e529..bf1a3d543c 100644 --- a/source/MaterialXRender/Util.h +++ b/source/MaterialXRender/Util.h @@ -47,6 +47,11 @@ MX_RENDER_API ShaderPtr createAlbedoTableShader(GenContext& context, DocumentPtr stdLib, const string& shaderName); +/// Create a shader that generates a prefiltered environment map. +MX_RENDER_API ShaderPtr createEnvPrefilterShader(GenContext& context, + DocumentPtr stdLib, + const string& shaderName); + /// Create a blur shader, using the given standard libraries for code generation. MX_RENDER_API ShaderPtr createBlurShader(GenContext& context, DocumentPtr stdLib, diff --git a/source/MaterialXRenderGlsl/GLTextureHandler.cpp b/source/MaterialXRenderGlsl/GLTextureHandler.cpp index 314a78c257..02a0a53927 100644 --- a/source/MaterialXRenderGlsl/GLTextureHandler.cpp +++ b/source/MaterialXRenderGlsl/GLTextureHandler.cpp @@ -91,7 +91,7 @@ bool GLTextureHandler::unbindImage(ImagePtr image) return false; } -bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps) +bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool) { if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { diff --git a/source/MaterialXRenderGlsl/GLTextureHandler.h b/source/MaterialXRenderGlsl/GLTextureHandler.h index 942d6dc446..13eaab098f 100644 --- a/source/MaterialXRenderGlsl/GLTextureHandler.h +++ b/source/MaterialXRenderGlsl/GLTextureHandler.h @@ -37,7 +37,7 @@ class MX_RENDERGLSL_API GLTextureHandler : public ImageHandler bool unbindImage(ImagePtr image) override; /// Create rendering resources for the given image. - bool createRenderResources(ImagePtr image, bool generateMipMaps) override; + bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false) override; /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRenderGlsl/GlslProgram.cpp b/source/MaterialXRenderGlsl/GlslProgram.cpp index 4497019e91..eecab8036e 100644 --- a/source/MaterialXRenderGlsl/GlslProgram.cpp +++ b/source/MaterialXRenderGlsl/GlslProgram.cpp @@ -578,9 +578,20 @@ void GlslProgram::bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr ima Matrix44 envRotation = Matrix44::createRotationY(PI) * lightHandler->getLightTransform().getTranspose(); bindUniform(HW::ENV_MATRIX, Value::createValue(envRotation), false); bindUniform(HW::ENV_RADIANCE_SAMPLES, Value::createValue(lightHandler->getEnvSampleCount()), false); + ImagePtr envRadiance = nullptr; + if (lightHandler->getIndirectLighting()) + { + envRadiance = lightHandler->getUsePrefilteredMap() ? + lightHandler->getEnvPrefilteredMap() : + lightHandler->getEnvRadianceMap(); + } + else + { + envRadiance = imageHandler->getZeroImage(); + } ImageMap envImages = { - { HW::ENV_RADIANCE, lightHandler->getIndirectLighting() ? lightHandler->getEnvRadianceMap() : imageHandler->getZeroImage() }, + { HW::ENV_RADIANCE, envRadiance }, { HW::ENV_IRRADIANCE, lightHandler->getIndirectLighting() ? lightHandler->getEnvIrradianceMap() : imageHandler->getZeroImage() } }; for (const auto& env : envImages) diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.h b/source/MaterialXRenderMsl/MetalTextureHandler.h index 4c329f3f6d..c78469a4ce 100644 --- a/source/MaterialXRenderMsl/MetalTextureHandler.h +++ b/source/MaterialXRenderMsl/MetalTextureHandler.h @@ -57,7 +57,7 @@ class MX_RENDERMSL_API MetalTextureHandler : public ImageHandler id getMTLSamplerStateForImage(unsigned int index); /// Create rendering resources for the given image. - bool createRenderResources(ImagePtr image, bool generateMipMaps) override; + bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false) override; /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.mm b/source/MaterialXRenderMsl/MetalTextureHandler.mm index 745019ac92..ab8da46f1f 100644 --- a/source/MaterialXRenderMsl/MetalTextureHandler.mm +++ b/source/MaterialXRenderMsl/MetalTextureHandler.mm @@ -128,7 +128,7 @@ return false; } -bool MetalTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps) +bool MetalTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget) { id texture = nil; @@ -149,9 +149,7 @@ texDesc.height = image->getHeight(); texDesc.mipmapLevelCount = generateMipMaps ? image->getMaxMipCount() : 1; texDesc.usage = MTLTextureUsageShaderRead | - // For now, we set generate mip maps flag off, - // when we want to use the texture as render target - (!generateMipMaps ? MTLTextureUsageRenderTarget : 0); + (useAsRenderTarget ? MTLTextureUsageRenderTarget : 0); texDesc.resourceOptions = MTLResourceStorageModePrivate; texDesc.pixelFormat = pixelFormat; if(generateMipMaps) diff --git a/source/MaterialXRenderMsl/MslPipelineStateObject.mm b/source/MaterialXRenderMsl/MslPipelineStateObject.mm index 4f8378ae46..f0cc8e47bb 100644 --- a/source/MaterialXRenderMsl/MslPipelineStateObject.mm +++ b/source/MaterialXRenderMsl/MslPipelineStateObject.mm @@ -594,7 +594,7 @@ int GetStrideOfMetalType(MTLDataType type) // Bind environment lights. ImageMap envLights = { - { HW::ENV_RADIANCE, lightHandler->getEnvRadianceMap() }, + { HW::ENV_RADIANCE, lightHandler->getUsePrefilteredMap() ? lightHandler->getEnvPrefilteredMap() : lightHandler->getEnvRadianceMap() }, { HW::ENV_IRRADIANCE, lightHandler->getEnvIrradianceMap() } }; for (const auto& env : envLights) diff --git a/source/MaterialXView/RenderPipeline.h b/source/MaterialXView/RenderPipeline.h index 87baac3fa4..3b7e7a48b7 100644 --- a/source/MaterialXView/RenderPipeline.h +++ b/source/MaterialXView/RenderPipeline.h @@ -50,6 +50,7 @@ class RenderPipeline virtual void bakeTextures() = 0; virtual void updateAlbedoTable(int tableSize) = 0; + virtual void updatePrefilteredMap() = 0; virtual std::shared_ptr createTextureBaker(unsigned int width, unsigned int height, mx::Image::BaseType baseType) = 0; diff --git a/source/MaterialXView/RenderPipelineGL.cpp b/source/MaterialXView/RenderPipelineGL.cpp index f08704d0a3..68ac0be040 100644 --- a/source/MaterialXView/RenderPipelineGL.cpp +++ b/source/MaterialXView/RenderPipelineGL.cpp @@ -110,6 +110,100 @@ void GLRenderPipeline::updateAlbedoTable(int tableSize) glDrawBuffer(GL_BACK); } +void GLRenderPipeline::updatePrefilteredMap() +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + + if (lightHandler->getEnvPrefilteredMap()) + { + return; + } + + // Create the prefilter shader. + mx::GlslMaterialPtr material = nullptr; + try + { + mx::ShaderPtr hwShader = mx::createEnvPrefilterShader(genContext, _viewer->_stdLib, "__ENV_PREFILTER__"); + material = mx::GlslMaterial::create(); + material->generateShader(hwShader); + } + catch (std::exception& e) + { + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to generate prefilter shader", e.what()); + } + + mx::ImagePtr srcTex = lightHandler->getEnvRadianceMap(); + + int w = srcTex->getWidth(); + int h = srcTex->getHeight(); + int numMips = srcTex->getMaxMipCount(); + + // Create texture to hold the prefiltered environment. + mx::GLTextureHandlerPtr glImageHandler = std::dynamic_pointer_cast(imageHandler); + mx::ImagePtr outTex = mx::Image::create(w, h, 3, mx::Image::BaseType::HALF); + glImageHandler->createRenderResources(outTex, true, true); + + mx::GlslProgramPtr program = material->getProgram(); + + try + { + int i = 0; + while (w > 0 && h > 0) + { + // Create framebuffer + unsigned int framebuffer; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outTex->getResourceId(), i); + glViewport(0, 0, w, h); + material->bindShader(); + + // Bind the source texture + mx::ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = mx::ImageSamplingProperties::AddressMode::PERIODIC; + samplingProperties.vaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.filterType = mx::ImageSamplingProperties::FilterType::LINEAR; + imageHandler->bindImage(srcTex, samplingProperties); + int textureLocation = glImageHandler->getBoundTextureLocation(srcTex->getResourceId()); + assert(textureLocation >= 0); + material->getProgram()->bindUniform(mx::HW::ENV_RADIANCE, mx::Value::createValue(textureLocation)); + // Bind other uniforms + program->bindUniform(mx::HW::ENV_PREFILTER_MIP, mx::Value::createValue(i)); + const mx::Matrix44 yRotationPI = mx::Matrix44::createScale(mx::Vector3(-1, 1, -1)); + program->bindUniform(mx::HW::ENV_MATRIX, mx::Value::createValue(yRotationPI)); + program->bindUniform(mx::HW::ENV_RADIANCE_MIPS, mx::Value::createValue(numMips)); + + _viewer->renderScreenSpaceQuad(material); + + glDeleteFramebuffers(1, &framebuffer); + + w /= 2; + h /= 2; + i++; + } + } + catch (mx::ExceptionRenderError& e) + { + for (const std::string& error : e.errorLog()) + { + std::cerr << error << std::endl; + } + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to render prefiltered environment", e.what()); + } + catch (std::exception& e) + { + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to render prefiltered environment", e.what()); + } + + // Clean up. + glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + lightHandler->setEnvPrefilteredMap(outTex); +} + mx::ImagePtr GLRenderPipeline::getShadowMap(int shadowMapSize) { auto& genContext = _viewer->_genContext; @@ -219,7 +313,13 @@ void GLRenderPipeline::renderFrame(void*, int shadowMapSize, const char* dirLigh float lightRotation = _viewer->_lightRotation; auto& searchPath = _viewer->_searchPath; auto& geometryHandler = _viewer->_geometryHandler; - + + // Update prefiltered environment. + if (lightHandler->getUsePrefilteredMap() && !_viewer->_materialAssignments.empty()) + { + updatePrefilteredMap(); + } + // Initialize OpenGL state glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); diff --git a/source/MaterialXView/RenderPipelineGL.h b/source/MaterialXView/RenderPipelineGL.h index fcb64a6436..00ebcbc6eb 100644 --- a/source/MaterialXView/RenderPipelineGL.h +++ b/source/MaterialXView/RenderPipelineGL.h @@ -31,6 +31,7 @@ class GLRenderPipeline : public RenderPipeline mx::ImageHandlerPtr createImageHandler() override; mx::MaterialPtr createMaterial() override; void updateAlbedoTable(int tableSize) override; + void updatePrefilteredMap() override; std::shared_ptr createTextureBaker(unsigned int width, unsigned int height, mx::Image::BaseType baseType) override; diff --git a/source/MaterialXView/RenderPipelineMetal.h b/source/MaterialXView/RenderPipelineMetal.h index 1ed8c063bd..9cd2b2f6b3 100644 --- a/source/MaterialXView/RenderPipelineMetal.h +++ b/source/MaterialXView/RenderPipelineMetal.h @@ -40,6 +40,7 @@ class MetalRenderPipeline : public RenderPipeline mx::ImageHandlerPtr createImageHandler() override; mx::MaterialPtr createMaterial() override; void updateAlbedoTable(int tableSize) override; + void updatePrefilteredMap() override; void renderFrame(void* color_texture, int shadowMapSize, const char* dirLightNodeCat) override; void bakeTextures() override; mx::ImagePtr getFrameImage() override; @@ -50,6 +51,7 @@ class MetalRenderPipeline : public RenderPipeline protected: mx::ImagePtr getShadowMap(int shadowMapSize) override; mx::MetalFramebufferPtr _shadowMapFramebuffer; + mx::MetalFramebufferPtr _prefilterFramebuffer; mx::ImagePtr _shadowMap[SHADOWMAP_TEX_COUNT]; }; diff --git a/source/MaterialXView/RenderPipelineMetal.mm b/source/MaterialXView/RenderPipelineMetal.mm index 026e35720a..0dc85b8aa4 100644 --- a/source/MaterialXView/RenderPipelineMetal.mm +++ b/source/MaterialXView/RenderPipelineMetal.mm @@ -155,6 +155,96 @@ } } +void MetalRenderPipeline::updatePrefilteredMap() +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + + if (lightHandler->getEnvPrefilteredMap()) + { + return; + } + + mx::ImagePtr srcTex = lightHandler->getEnvRadianceMap(); + int w = srcTex->getWidth(); + int h = srcTex->getHeight(); + + mx::MetalTextureHandlerPtr mtlImageHandler = std::dynamic_pointer_cast(imageHandler); + mx::ImagePtr outTex = mx::Image::create(w, h, 3, mx::Image::BaseType::HALF); + mtlImageHandler->createRenderResources(outTex, true, true); + id metalTex = mtlImageHandler->getAssociatedMetalTexture(outTex); + + // Create framebuffer. + _prefilterFramebuffer = mx::MetalFramebuffer::create( + MTL(device), + w, h, + 4, + mx::Image::BaseType::HALF, + metalTex + ); + + MTL_PUSH_FRAMEBUFFER(_prefilterFramebuffer); + + // Create shader. + mx::ShaderPtr hwShader = mx::createEnvPrefilterShader(genContext, _viewer->_stdLib, "__ENV_PREFILTER__"); + mx::MslMaterialPtr material = mx::MslMaterial::create(); + try + { + material->generateShader(hwShader); + } + catch (std::exception& e) + { + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to generate convolution shader", e.what()); + } + + int i = 0; + + while (w > 0 && h > 0) + { + MTL(beginCommandBuffer()); + MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor new]; + [desc.colorAttachments[0] setTexture:metalTex]; + [desc.colorAttachments[0] setLevel:i]; + [desc.colorAttachments[0] setLoadAction:MTLLoadActionDontCare]; + [desc.colorAttachments[0] setStoreAction:MTLStoreActionStore]; + [desc.depthAttachment setTexture:_prefilterFramebuffer->getDepthTexture()]; + [desc.depthAttachment setLoadAction:MTLLoadActionDontCare]; + [desc.depthAttachment setStoreAction:MTLStoreActionDontCare]; + [desc setStencilAttachment:nil]; + + MTL(beginEncoder(desc)); + [MTL(renderCmdEncoder) setDepthStencilState:MTL_DEPTHSTENCIL_STATE(opaque)]; + + _prefilterFramebuffer->bind(desc); + material->bindShader(); + material->getProgram()->bindUniform(mx::HW::ENV_PREFILTER_MIP, mx::Value::createValue(i)); + + bool prevValue = lightHandler->getUsePrefilteredMap(); + lightHandler->setUsePrefilteredMap(false); + material->getProgram()->prepareUsedResources( + MTL(renderCmdEncoder), + _viewer->_identityCamera, + nullptr, + imageHandler, + lightHandler); + lightHandler->setUsePrefilteredMap(prevValue); + + _viewer->renderScreenSpaceQuad(material); + + MTL(endCommandBuffer()); + [desc release]; + + w /= 2; + h /= 2; + i++; + } + + MTL_POP_FRAMEBUFFER(); + + lightHandler->setEnvPrefilteredMap(outTex); +} + mx::ImagePtr MetalRenderPipeline::getShadowMap(int shadowMapSize) { auto& genContext = _viewer->_genContext; @@ -175,7 +265,7 @@ !mtlImageHandler->getAssociatedMetalTexture(_shadowMap[i])) { _shadowMap[i] = mx::Image::create(shadowMapSize, shadowMapSize, 2, mx::Image::BaseType::FLOAT); - _viewer->_imageHandler->createRenderResources(_shadowMap[i], false); + _viewer->_imageHandler->createRenderResources(_shadowMap[i], false, true); } shadowMapTex[i] = @@ -315,6 +405,12 @@ auto& searchPath = _viewer->_searchPath; auto& geometryHandler = _viewer->_geometryHandler; + // Update prefiltered environment. + if (lightHandler->getUsePrefilteredMap() && !_viewer->_materialAssignments.empty()) + { + updatePrefilteredMap(); + } + // Update lighting state. lightHandler->setLightTransform(mx::Matrix44::createRotationY(lightRotation / 180.0f * M_PI)); diff --git a/source/MaterialXView/Viewer.cpp b/source/MaterialXView/Viewer.cpp index c06846f2ce..eeaf7a82b0 100644 --- a/source/MaterialXView/Viewer.cpp +++ b/source/MaterialXView/Viewer.cpp @@ -485,9 +485,12 @@ void Viewer::loadEnvironmentLight() // Release any existing environment maps and store the new ones. _imageHandler->releaseRenderResources(_lightHandler->getEnvRadianceMap()); + _imageHandler->releaseRenderResources(_lightHandler->getEnvPrefilteredMap()); _imageHandler->releaseRenderResources(_lightHandler->getEnvIrradianceMap()); + _lightHandler->setEnvRadianceMap(envRadianceMap); _lightHandler->setEnvIrradianceMap(envIrradianceMap); + _lightHandler->setEnvPrefilteredMap(nullptr); // Look for a light rig using an expected filename convention. if (!_splitDirectLight) @@ -746,12 +749,14 @@ void Viewer::createAdvancedSettings(Widget* parent) ng::CheckBox* importanceSampleBox = new ng::CheckBox(advancedPopup, "Environment FIS"); importanceSampleBox->set_checked(_genContext.getOptions().hwSpecularEnvironmentMethod == mx::SPECULAR_ENVIRONMENT_FIS); + _lightHandler->setUsePrefilteredMap(_genContext.getOptions().hwSpecularEnvironmentMethod != mx::SPECULAR_ENVIRONMENT_FIS); importanceSampleBox->set_callback([this](bool enable) { _genContext.getOptions().hwSpecularEnvironmentMethod = enable ? mx::SPECULAR_ENVIRONMENT_FIS : mx::SPECULAR_ENVIRONMENT_PREFILTER; #ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.getOptions().hwSpecularEnvironmentMethod = _genContext.getOptions().hwSpecularEnvironmentMethod; #endif + _lightHandler->setUsePrefilteredMap(!enable); reloadShaders(); }); diff --git a/source/PyMaterialX/PyMaterialXGenShader/PyGenOptions.cpp b/source/PyMaterialX/PyMaterialXGenShader/PyGenOptions.cpp index 75b5fb11bb..c40728ad26 100644 --- a/source/PyMaterialX/PyMaterialXGenShader/PyGenOptions.cpp +++ b/source/PyMaterialX/PyMaterialXGenShader/PyGenOptions.cpp @@ -38,6 +38,7 @@ void bindPyGenOptions(py::module& mod) .def_readwrite("hwNormalizeUdimTexCoords", &mx::GenOptions::hwNormalizeUdimTexCoords) .def_readwrite("hwAmbientOcclusion", &mx::GenOptions::hwAmbientOcclusion) .def_readwrite("hwWriteAlbedoTable", &mx::GenOptions::hwWriteAlbedoTable) + .def_readwrite("hwWriteEnvPrefilter", &mx::GenOptions::hwWriteEnvPrefilter) .def_readwrite("hwImplicitBitangents", &mx::GenOptions::hwImplicitBitangents) .def_readwrite("emitColorTransforms", &mx::GenOptions::emitColorTransforms) .def(py::init<>());