Files
UnrealEngine/Engine/Source/ThirdParty/MaterialX/MaterialX-1.39.3/source/MaterialXView/RenderPipelineGL.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

548 lines
20 KiB
C++

//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//
#include <MaterialXView/RenderPipelineGL.h>
#include <MaterialXView/Viewer.h>
#include <MaterialXRenderGlsl/GLTextureHandler.h>
#include <MaterialXGenGlsl/GlslShaderGenerator.h>
#include <MaterialXRenderGlsl/TextureBaker.h>
#include <MaterialXRenderGlsl/GLFramebuffer.h>
#include <MaterialXRenderGlsl/GlslMaterial.h>
#include <nanogui/messagedialog.h>
#include <nanogui/opengl.h>
namespace
{
const float PI = std::acos(-1.0f);
}
GLRenderPipeline::GLRenderPipeline(Viewer* viewerPtr) :
RenderPipeline(viewerPtr)
{
}
void GLRenderPipeline::initialize(void*, void*)
{
}
mx::ImageHandlerPtr GLRenderPipeline::createImageHandler()
{
return mx::GLTextureHandler::create(mx::StbImageLoader::create());
}
mx::MaterialPtr GLRenderPipeline::createMaterial()
{
return mx::GlslMaterial::create();
}
std::shared_ptr<void> GLRenderPipeline::createTextureBaker(unsigned int width,
unsigned int height,
mx::Image::BaseType baseType)
{
return std::static_pointer_cast<void>(mx::TextureBakerGlsl::create(width, height, baseType));
}
void GLRenderPipeline::initFramebuffer(int, int, void*)
{
}
void GLRenderPipeline::resizeFramebuffer(int, int, void*)
{
}
void GLRenderPipeline::updateAlbedoTable(int tableSize)
{
auto& genContext = _viewer->_genContext;
auto& stdLib = _viewer->_stdLib;
auto& lightHandler = _viewer->_lightHandler;
auto& imageHandler = _viewer->_imageHandler;
if (lightHandler->getAlbedoTable())
{
return;
}
// Create framebuffer.
mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(tableSize, tableSize, 3, mx::Image::BaseType::FLOAT);
framebuffer->bind();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Create shader.
mx::ShaderPtr hwShader = mx::createAlbedoTableShader(genContext, stdLib, "__ALBEDO_TABLE_SHADER__");
mx::GlslMaterialPtr material = mx::GlslMaterial::create();
try
{
material->generateShader(hwShader);
}
catch (std::exception& e)
{
new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to generate albedo table shader", e.what());
return;
}
// Render albedo table.
material->bindShader();
if (material->getProgram()->hasUniform(mx::HW::ALBEDO_TABLE_SIZE))
{
std::static_pointer_cast<mx::GlslMaterial>(material)->getProgram()
->bindUniform(mx::HW::ALBEDO_TABLE_SIZE, mx::Value::createValue(tableSize));
}
_viewer->renderScreenSpaceQuad(material);
// Store albedo table image.
imageHandler->releaseRenderResources(lightHandler->getAlbedoTable());
lightHandler->setAlbedoTable(framebuffer->getColorImage());
if (_viewer->_saveGeneratedLights)
{
imageHandler->saveImage("AlbedoTable.exr", lightHandler->getAlbedoTable());
}
// Restore state for scene rendering.
glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
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<mx::GLTextureHandler>(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<int>(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;
auto& imageHandler = _viewer->_imageHandler;
auto& shadowCamera = _viewer->_shadowCamera;
auto& stdLib = _viewer->_stdLib;
auto& geometryHandler = _viewer->_geometryHandler;
if (!_viewer->_shadowMap)
{
// Generate shaders for shadow rendering.
if (!_viewer->_shadowMaterial)
{
try
{
mx::ShaderPtr hwShader = mx::createDepthShader(genContext, stdLib, "__SHADOW_SHADER__");
_viewer->_shadowMaterial = mx::GlslMaterial::create();
_viewer->_shadowMaterial->generateShader(hwShader);
}
catch (std::exception& e)
{
std::cerr << "Failed to generate shadow shader: " << e.what() << std::endl;
_viewer->_shadowMaterial = nullptr;
}
}
if (!_viewer->_shadowBlurMaterial)
{
try
{
mx::ShaderPtr hwShader = mx::createBlurShader(genContext, stdLib, "__SHADOW_BLUR_SHADER__", "gaussian", 1.0f);
_viewer->_shadowBlurMaterial = mx::GlslMaterial::create();
_viewer->_shadowBlurMaterial->generateShader(hwShader);
}
catch (std::exception& e)
{
std::cerr << "Failed to generate shadow blur shader: " << e.what() << std::endl;
_viewer->_shadowBlurMaterial = nullptr;
}
}
if (_viewer->_shadowMaterial && _viewer->_shadowBlurMaterial)
{
// Create framebuffer.
mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(shadowMapSize, shadowMapSize, 2, mx::Image::BaseType::FLOAT);
framebuffer->bind();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Render shadow geometry.
_viewer->_shadowMaterial->bindShader();
for (auto mesh : geometryHandler->getMeshes())
{
_viewer->_shadowMaterial->bindMesh(mesh);
_viewer->_shadowMaterial->bindViewInformation(shadowCamera);
for (size_t i = 0; i < mesh->getPartitionCount(); i++)
{
mx::MeshPartitionPtr geom = mesh->getPartition(i);
_viewer->_shadowMaterial->drawPartition(geom);
}
}
_viewer->_shadowMap = framebuffer->getColorImage();
// Apply Gaussian blurring.
mx::ImageSamplingProperties blurSamplingProperties;
blurSamplingProperties.uaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP;
blurSamplingProperties.vaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP;
blurSamplingProperties.filterType = mx::ImageSamplingProperties::FilterType::CLOSEST;
for (unsigned int i = 0; i < _viewer->_shadowSoftness; i++)
{
framebuffer->bind();
_viewer->_shadowBlurMaterial->bindShader();
if (imageHandler->bindImage(_viewer->_shadowMap, blurSamplingProperties))
{
mx::GLTextureHandlerPtr textureHandler = std::static_pointer_cast<mx::GLTextureHandler>(imageHandler);
int textureLocation = textureHandler->getBoundTextureLocation(_viewer->_shadowMap->getResourceId());
if (textureLocation >= 0)
{
std::static_pointer_cast<mx::GlslMaterial>(_viewer->_shadowBlurMaterial)
->getProgram()->bindUniform("image_file", mx::Value::createValue(textureLocation));
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
_viewer->renderScreenSpaceQuad(_viewer->_shadowBlurMaterial);
imageHandler->releaseRenderResources(_viewer->_shadowMap);
_viewer->_shadowMap = framebuffer->getColorImage();
}
// Restore state for scene rendering.
glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
}
// Reset frame timing after shadow generation.
_viewer->resetFrameTiming();
}
return _viewer->_shadowMap;
}
void GLRenderPipeline::renderFrame(void*, int shadowMapSize, const char* dirLightNodeCat)
{
auto& genContext = _viewer->_genContext;
auto& lightHandler = _viewer->_lightHandler;
auto& imageHandler = _viewer->_imageHandler;
auto& viewCamera = _viewer->_viewCamera;
auto& envCamera = _viewer->_envCamera;
auto& shadowCamera = _viewer->_shadowCamera;
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);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LEQUAL);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_CULL_FACE);
glDisable(GL_FRAMEBUFFER_SRGB);
// Update lighting state.
lightHandler->setLightTransform(mx::Matrix44::createRotationY(lightRotation / 180.0f * PI));
// Update shadow state.
mx::ShadowState shadowState;
shadowState.ambientOcclusionGain = _viewer->_ambientOcclusionGain;
mx::NodePtr dirLight = lightHandler->getFirstLightOfCategory(dirLightNodeCat);
if (genContext.getOptions().hwShadowMap && dirLight)
{
mx::ImagePtr shadowMap = getShadowMap(shadowMapSize);
if (shadowMap)
{
shadowState.shadowMap = shadowMap;
shadowState.shadowMatrix = viewCamera->getWorldMatrix().getInverse() *
shadowCamera->getWorldViewProjMatrix();
}
else
{
genContext.getOptions().hwShadowMap = false;
}
}
glEnable(GL_FRAMEBUFFER_SRGB);
// Environment background
if (_viewer->_drawEnvironment)
{
mx::MaterialPtr envMaterial = _viewer->getEnvironmentMaterial();
if (envMaterial)
{
const mx::MeshList& meshes = _viewer->_envGeometryHandler->getMeshes();
mx::MeshPartitionPtr envPart = !meshes.empty() ? meshes[0]->getPartition(0) : nullptr;
if (envPart)
{
// Apply rotation to the environment shader.
float longitudeOffset = (lightRotation / 360.0f) + 0.5f;
envMaterial->modifyUniform("longitude/in2", mx::Value::createValue(longitudeOffset));
// Apply light intensity to the environment shader.
envMaterial->modifyUniform("envImageAdjusted/in2", mx::Value::createValue(lightHandler->getEnvLightIntensity()));
// Render the environment mesh.
glDepthMask(GL_FALSE);
envMaterial->bindShader();
envMaterial->bindMesh(meshes[0]);
envMaterial->bindViewInformation(envCamera);
envMaterial->bindImages(imageHandler, searchPath, false);
envMaterial->drawPartition(envPart);
glDepthMask(GL_TRUE);
}
}
else
{
_viewer->_drawEnvironment = false;
}
}
// Enable backface culling if requested.
if (!_viewer->_renderDoubleSided)
{
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
// Opaque pass
for (const auto& assignment : _viewer->_materialAssignments)
{
mx::MeshPartitionPtr geom = assignment.first;
mx::GlslMaterialPtr material = std::dynamic_pointer_cast<mx::GlslMaterial>(assignment.second);
shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material);
if (!material)
{
continue;
}
material->bindShader();
material->bindMesh(geometryHandler->findParentMesh(geom));
if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD))
{
material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.99f));
}
material->bindViewInformation(viewCamera);
material->bindLighting(lightHandler, imageHandler, shadowState);
material->bindImages(imageHandler, searchPath);
material->drawPartition(geom);
material->unbindImages(imageHandler);
}
// Transparent pass
if (_viewer->_renderTransparency)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (const auto& assignment : _viewer->_materialAssignments)
{
mx::MeshPartitionPtr geom = assignment.first;
mx::GlslMaterialPtr material = std::dynamic_pointer_cast<mx::GlslMaterial>(assignment.second);
shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material);
if (!material || !material->hasTransparency())
{
continue;
}
material->bindShader();
material->bindMesh(geometryHandler->findParentMesh(geom));
if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD))
{
material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.001f));
}
material->bindViewInformation(viewCamera);
material->bindLighting(lightHandler, imageHandler, shadowState);
material->bindImages(imageHandler, searchPath);
material->drawPartition(geom);
material->unbindImages(imageHandler);
}
glDisable(GL_BLEND);
}
if (!_viewer->_renderDoubleSided)
{
glDisable(GL_CULL_FACE);
}
glDisable(GL_FRAMEBUFFER_SRGB);
// Wireframe pass
if (_viewer->_outlineSelection)
{
mx::MaterialPtr wireMaterial = _viewer->getWireframeMaterial();
if (wireMaterial)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
wireMaterial->bindShader();
wireMaterial->bindMesh(geometryHandler->findParentMesh(_viewer->getSelectedGeometry()));
wireMaterial->bindViewInformation(viewCamera);
wireMaterial->drawPartition(_viewer->getSelectedGeometry());
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else
{
_viewer->_outlineSelection = false;
}
}
}
void GLRenderPipeline::bakeTextures()
{
auto& imageHandler = _viewer->_imageHandler;
mx::MaterialPtr material = _viewer->getSelectedMaterial();
mx::DocumentPtr doc = material ? material->getDocument() : nullptr;
if (!doc)
{
return;
}
{
// Construct a texture baker.
mx::Image::BaseType baseType = _viewer->_bakeHdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8;
mx::UnsignedIntPair bakingRes = _viewer->computeBakingResolution(doc);
mx::TextureBakerPtr baker = std::static_pointer_cast<mx::TextureBakerPtr::element_type>(createTextureBaker(bakingRes.first, bakingRes.second, baseType));
baker->setupUnitSystem(_viewer->_stdLib);
baker->setDistanceUnit(_viewer->_genContext.getOptions().targetDistanceUnit);
baker->setAverageImages(_viewer->_bakeAverage);
baker->setOptimizeConstants(_viewer->_bakeOptimize);
baker->writeDocumentPerMaterial(_viewer->_bakeDocumentPerMaterial);
// Assign our existing image handler, releasing any existing render resources for cached images.
imageHandler->releaseRenderResources();
baker->setImageHandler(imageHandler);
// Extend the image search path to include material source folders.
mx::FileSearchPath extendedSearchPath = _viewer->_searchPath;
extendedSearchPath.append(_viewer->_materialSearchPath);
// Bake all materials in the active document.
try
{
baker->bakeAllMaterials(doc, extendedSearchPath, _viewer->_bakeFilename);
}
catch (std::exception& e)
{
std::cerr << "Error in texture baking: " << e.what() << std::endl;
}
// Release any render resources generated by the baking process.
imageHandler->releaseRenderResources();
}
// After the baker has been destructed, restore state for scene rendering.
glfwMakeContextCurrent(_viewer->m_glfw_window);
glfwGetFramebufferSize(_viewer->m_glfw_window, &_viewer->m_fbsize[0], &_viewer->m_fbsize[1]);
glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
}
mx::ImagePtr GLRenderPipeline::getFrameImage()
{
glFlush();
const auto& size = _viewer->m_size;
const auto& pixel_ratio = _viewer->m_pixel_ratio;
// Create an image with dimensions adjusted for device DPI.
mx::ImagePtr image = mx::Image::create((unsigned int) (size.x() * pixel_ratio),
(unsigned int) (size.y() * pixel_ratio), 3);
image->createResourceBuffer();
// Read pixels into the image buffer.
glReadPixels(0, 0, image->getWidth(), image->getHeight(), GL_RGB, GL_UNSIGNED_BYTE, image->getResourceBuffer());
return image;
}