Skip to content

Commit

Permalink
Improvements to prefiltered environment maps (AcademySoftwareFoundati…
Browse files Browse the repository at this point in the history
…on#1420)

This changelist adds support for prefiltering environment maps on the GPU, using filtered VNDF sampling for efficiency.

The MaterialX Viewer can be used to test the new functionality by unchecking the Environment FIS option in Advanced Settings.
  • Loading branch information
ApoorvaJ authored and jstone-lucasfilm committed Dec 29, 2023
1 parent dddf104 commit eae062a
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 eae062a

Please sign in to comment.