Skip to content

Commit

Permalink
Improvements to prefiltered environment maps (#1420)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ApoorvaJ authored Dec 29, 2023
1 parent dddf104 commit 3580bb7
Show file tree
Hide file tree
Showing 28 changed files with 425 additions and 32 deletions.
10 changes: 0 additions & 10 deletions libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
9 changes: 1 addition & 8 deletions libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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;
}

Expand Down
24 changes: 24 additions & 0 deletions libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
76 changes: 76 additions & 0 deletions libraries/pbrlib/genglsl/lib/mx_prefilter_environment.glsl
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions source/JsMaterialX/JsMaterialXGenShader/JsGenOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
;
}
13 changes: 12 additions & 1 deletion source/MaterialXGenGlsl/GlslShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions source/MaterialXGenMsl/MslShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions source/MaterialXGenShader/GenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class MX_GENSHADER_API GenOptions
hwMaxActiveLightSources(3),
hwNormalizeUdimTexCoords(false),
hwWriteAlbedoTable(false),
hwWriteEnvPrefilter(false),
hwImplicitBitangents(true),
emitColorTransforms(true)
{
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions source/MaterialXGenShader/HwShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -359,6 +362,16 @@ ShaderPtr HwShaderGenerator::createShader(const string& name, ElementPtr element
psPrivateUniforms->add(Type::INTEGER, HW::T_ALBEDO_TABLE_SIZE, Value::createValue<int>(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<int>(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<int>(1));
}

// Create uniforms for the published graph interface
for (ShaderGraphInputSocket* inputSocket : graph->getInputSockets())
{
Expand Down
2 changes: 2 additions & 0 deletions source/MaterialXGenShader/HwShaderGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRender/ImageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ void ImageHandler::unbindImages()
}
}

bool ImageHandler::createRenderResources(ImagePtr, bool)
bool ImageHandler::createRenderResources(ImagePtr, bool, bool)
{
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRender/ImageHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions source/MaterialXRender/LightHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

Expand Down
20 changes: 20 additions & 0 deletions source/MaterialXRender/Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions source/MaterialXRender/Util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 3580bb7

Please sign in to comment.