Files
UnrealEngine/Engine/Source/ThirdParty/MaterialX/MaterialX-1.38.10/source/MaterialXGraphEditor/RenderView.cpp
2025-05-18 13:04:45 +08:00

1015 lines
34 KiB
C++

//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//
#include <MaterialXGraphEditor/RenderView.h>
#include "MaterialXRenderGlsl/GLTextureHandler.h"
#include <MaterialXRenderGlsl/External/Glad/glad.h>
#include <MaterialXRender/CgltfLoader.h>
#include <MaterialXRender/Harmonics.h>
#include <MaterialXRender/OiioImageLoader.h>
#include <MaterialXRender/ShaderRenderer.h>
#include <MaterialXRender/StbImageLoader.h>
#include <MaterialXRender/TinyObjLoader.h>
#include <MaterialXGenShader/DefaultColorManagementSystem.h>
#include <MaterialXFormat/Util.h>
#include <imgui_impl_glfw.h>
#include <iostream>
const mx::Vector3 DEFAULT_CAMERA_POSITION(0.0f, 0.0f, 5.0f);
const float DEFAULT_CAMERA_VIEW_ANGLE = 45.0f;
const float DEFAULT_CAMERA_ZOOM = 1.f;
const int SHADOW_MAP_SIZE = 2048;
const int IRRADIANCE_MAP_WIDTH = 256;
const int IRRADIANCE_MAP_HEIGHT = 128;
const float ORTHO_VIEW_DISTANCE = 1000.0f;
const float ORTHO_PROJECTION_HEIGHT = 1.8f;
const std::string DIR_LIGHT_NODE_CATEGORY = "directional_light";
const std::string IRRADIANCE_MAP_FOLDER = "irradiance";
const float IDEAL_MESH_SPHERE_RADIUS = 2.0f;
const float PI = std::acos(-1.0f);
// this is mostly taken from MaterialXView Viewer.cpp but only a subset of the functions with some changes and additions
void applyModifiers(mx::DocumentPtr doc, const DocumentModifiers& modifiers)
{
for (mx::ElementPtr elem : doc->traverseTree())
{
if (modifiers.remapElements.count(elem->getCategory()))
{
elem->setCategory(modifiers.remapElements.at(elem->getCategory()));
}
if (modifiers.remapElements.count(elem->getName()))
{
elem->setName(modifiers.remapElements.at(elem->getName()));
}
mx::StringVec attrNames = elem->getAttributeNames();
for (const std::string& attrName : attrNames)
{
if (modifiers.remapElements.count(elem->getAttribute(attrName)))
{
elem->setAttribute(attrName, modifiers.remapElements.at(elem->getAttribute(attrName)));
}
}
if (elem->hasFilePrefix() && !modifiers.filePrefixTerminator.empty())
{
std::string filePrefix = elem->getFilePrefix();
if (!mx::stringEndsWith(filePrefix, modifiers.filePrefixTerminator))
{
elem->setFilePrefix(filePrefix + modifiers.filePrefixTerminator);
}
}
std::vector<mx::ElementPtr> children = elem->getChildren();
for (mx::ElementPtr child : children)
{
if (modifiers.skipElements.count(child->getCategory()) ||
modifiers.skipElements.count(child->getName()))
{
elem->removeChild(child->getName());
}
}
}
// Remap unsupported texture coordinate indices.
for (mx::ElementPtr elem : doc->traverseTree())
{
mx::NodePtr node = elem->asA<mx::Node>();
if (node && node->getCategory() == "texcoord")
{
mx::InputPtr index = node->getInput("index");
mx::ValuePtr value = index ? index->getValue() : nullptr;
if (value && value->isA<int>() && value->asA<int>() != 0)
{
index->setValue(0);
}
}
}
}
void RenderView::setDocument(mx::DocumentPtr document)
{
// Set new current document
_document = document;
// Initialize rendering context.
initContext(_genContext);
}
RenderView::RenderView(mx::DocumentPtr doc,
const std::string& meshFilename,
const std::string& envRadianceFilename,
const mx::FileSearchPath& searchPath,
int viewWidth,
int viewHeight) :
_textureID(0),
_meshFilename(meshFilename),
_envRadianceFilename(envRadianceFilename),
_searchPath(searchPath),
_meshScale(1.0f),
_cameraPosition(DEFAULT_CAMERA_POSITION),
_cameraUp(0.0f, 1.0f, 0.0f),
_cameraViewAngle(DEFAULT_CAMERA_VIEW_ANGLE),
_cameraNearDist(0.05f),
_cameraFarDist(5000.0f),
_cameraZoom(DEFAULT_CAMERA_ZOOM),
_pixelRatio(1.0f),
_viewWidth(viewWidth),
_viewHeight(viewHeight),
_userTranslationActive(false),
_lightRotation(0.0f),
_shadowSoftness(1),
_selectedGeom(0),
_selectedMaterial(0),
_viewCamera(mx::Camera::create()),
_envCamera(mx::Camera::create()),
_shadowCamera(mx::Camera::create()),
_lightHandler(mx::LightHandler::create()),
_genContext(mx::GlslShaderGenerator::create()),
_unitRegistry(mx::UnitConverterRegistry::create()),
_splitByUdims(true),
_materialCompilation(false),
_renderTransparency(true),
_renderDoubleSided(true),
_captureRequested(false),
_exitRequested(false)
{
// Resolve input filenames, taking both the provided search path and
// current working directory into account.
mx::FileSearchPath localSearchPath = searchPath;
localSearchPath.append(mx::FilePath::getCurrentPath());
//_materialFilename = localSearchPath.find(_materialFilename);
_meshFilename = localSearchPath.find(_meshFilename);
_envRadianceFilename = localSearchPath.find(_envRadianceFilename);
// Set default Glsl generator options.
_genContext.getOptions().targetColorSpaceOverride = "lin_rec709";
_genContext.getOptions().fileTextureVerticalFlip = true;
_genContext.getOptions().hwShadowMap = true;
// Make sure all uniforms are added so value updates can
// find the the corresponding uniform.
_genContext.getOptions().shaderInterfaceType = mx::SHADER_INTERFACE_COMPLETE;
setDocument(doc);
}
void RenderView::initialize()
{
// Initialize image handler.
_imageHandler = mx::GLTextureHandler::create(mx::StbImageLoader::create());
#if MATERIALX_BUILD_OIIO
_imageHandler->addLoader(mx::OiioImageLoader::create());
#endif
_imageHandler->setSearchPath(_searchPath);
// Create geometry handler.
mx::TinyObjLoaderPtr objLoader = mx::TinyObjLoader::create();
mx::CgltfLoaderPtr gltfLoader = mx::CgltfLoader::create();
_geometryHandler = mx::GeometryHandler::create();
_geometryHandler->addLoader(objLoader);
_geometryHandler->addLoader(gltfLoader);
loadMesh(_searchPath.find(_meshFilename));
// Initialize environment light.
loadEnvironmentLight();
// Initialize camera.
initCamera();
// Update geometry selections.
updateGeometrySelections();
_pixelRatio = 1.f;
}
void RenderView::assignMaterial(mx::MeshPartitionPtr geometry, mx::GlslMaterialPtr material)
{
if (!geometry || _geometryHandler->getMeshes().empty())
{
return;
}
if (geometry == getSelectedGeometry())
{
setSelectedMaterial(material);
}
if (material)
{
_materialAssignments[geometry] = material;
material->unbindGeometry();
}
else
{
_materialAssignments.erase(geometry);
}
}
mx::ElementPredicate RenderView::getElementPredicate()
{
return [this](mx::ConstElementPtr elem)
{
if (elem->hasSourceUri())
{
return (_xincludeFiles.count(elem->getSourceUri()) == 0);
}
return true;
};
}
void RenderView::updateGeometrySelections()
{
_geometryList.clear();
if (_geometryHandler->getMeshes().empty())
{
return;
}
for (auto mesh : _geometryHandler->getMeshes())
{
for (size_t partIndex = 0; partIndex < mesh->getPartitionCount(); partIndex++)
{
mx::MeshPartitionPtr part = mesh->getPartition(partIndex);
_geometryList.push_back(part);
}
}
std::vector<std::string> items;
for (const mx::MeshPartitionPtr& part : _geometryList)
{
std::string geomName = part->getName();
mx::StringVec geomSplit = mx::splitString(geomName, ":");
if (!geomSplit.empty() && !geomSplit[geomSplit.size() - 1].empty())
{
geomName = geomSplit[geomSplit.size() - 1];
}
items.push_back(geomName);
}
_selectedGeom = 0;
}
void RenderView::loadMesh(const mx::FilePath& filename)
{
_geometryHandler->clearGeometry();
if (_geometryHandler->loadGeometry(filename))
{
_meshFilename = filename;
if (_splitByUdims)
{
for (auto mesh : _geometryHandler->getMeshes())
{
mesh->splitByUdims();
}
}
updateGeometrySelections();
// Assign the selected material to all geometries.
_materialAssignments.clear();
mx::GlslMaterialPtr material = getSelectedMaterial();
if (material)
{
for (mx::MeshPartitionPtr geom : _geometryList)
{
assignMaterial(geom, material);
}
}
// Unbind utility materials from the previous geometry.
if (_wireMaterial)
{
_wireMaterial->unbindGeometry();
}
if (_shadowMaterial)
{
_shadowMaterial->unbindGeometry();
}
_meshRotation = mx::Vector3();
_meshScale = 1.0f;
_cameraTarget = mx::Vector3();
initCamera();
if (_shadowMap)
{
_imageHandler->releaseRenderResources(_shadowMap);
_shadowMap = nullptr;
}
}
}
void RenderView::setScrollEvent(float scrollY)
{
_cameraZoom = std::max(0.1f, _cameraZoom * ((scrollY > 0) ? 1.1f : 0.9f));
}
void RenderView::setKeyEvent(int key)
{
if (key == ImGuiKey_KeypadAdd)
{
_cameraZoom *= 1.1f;
}
if (key == ImGuiKey_KeypadSubtract)
{
_cameraZoom = std::max(0.1f, _cameraZoom * 0.9f);
}
}
void RenderView::setMouseMotionEvent(mx::Vector2 pos)
{
if (_viewCamera->applyArcballMotion(pos))
{
return;
}
if (_userTranslationActive)
{
updateCameras();
mx::Vector3 boxMin = _geometryHandler->getMinimumBounds();
mx::Vector3 boxMax = _geometryHandler->getMaximumBounds();
mx::Vector3 sphereCenter = (boxMax + boxMin) / 2.0f;
float viewZ = _viewCamera->projectToViewport(sphereCenter)[2];
mx::Vector3 pos1 = _viewCamera->unprojectFromViewport(
mx::Vector3(pos[0], (float) _viewWidth - pos[1], viewZ));
mx::Vector3 pos0 = _viewCamera->unprojectFromViewport(
mx::Vector3(_userTranslationPixel[0], (float) _viewWidth - _userTranslationPixel[1], viewZ));
_userTranslation = _userTranslationStart + (pos1 - pos0);
}
}
void RenderView::setMouseButtonEvent(int button, bool down, mx::Vector2 pos)
{
if ((button == 0) && !ImGui::IsKeyPressed(ImGuiKey_RightShift) && !ImGui::IsKeyPressed(ImGuiKey_LeftShift))
{
_viewCamera->arcballButtonEvent(pos, down);
}
else if ((button == 1) || ((button == 0) && ImGui::IsKeyDown(ImGuiKey_RightShift)) || ((button == 0) && ImGui::IsKeyDown(ImGuiKey_LeftShift)))
{
_userTranslationStart = _userTranslation;
_userTranslationActive = true;
_userTranslationPixel = pos;
}
if ((button == 0) && !down)
{
_viewCamera->arcballButtonEvent(pos, false);
}
if (!down)
{
_userTranslationActive = false;
}
}
void RenderView::setMaterial(mx::TypedElementPtr elem)
{
// compare graph element to material in order to assign correct one
for (mx::GlslMaterialPtr mat : _materials)
{
mx::TypedElementPtr telem = mat->getElement();
if (telem->getNamePath() == elem->getNamePath())
{
assignMaterial(_geometryList[0], mat);
}
}
}
void RenderView::updateMaterials(mx::TypedElementPtr typedElem)
{
// Clear user data on the generator.
_genContext.clearUserData();
// Clear materials.
for (mx::MeshPartitionPtr geom : _geometryList)
{
if (_materialAssignments.count(geom))
{
assignMaterial(geom, nullptr);
}
}
_materials.clear();
std::vector<mx::GlslMaterialPtr> newMaterials;
try
{
_materialSearchPath = mx::getSourceSearchPath(_document);
// Apply modifiers to the content document.
applyModifiers(_document, _modifiers);
// Apply direct lights.
applyDirectLights(_document);
// Check for any udim set.
mx::ValuePtr udimSetValue = _document->getGeomPropValue(mx::UDIM_SET_PROPERTY);
// Skip material nodes without upstream shaders.
mx::NodePtr node = typedElem ? typedElem->asA<mx::Node>() : nullptr;
if (node &&
node->getCategory() == mx::SURFACE_MATERIAL_NODE_STRING &&
mx::getShaderNodes(node).empty())
{
typedElem = nullptr;
}
// Create new materials.
if (!typedElem)
{
std::vector<mx::TypedElementPtr> elems = mx::findRenderableElements(_document);
if (!elems.empty())
{
typedElem = elems[0];
}
}
mx::TypedElementPtr udimElement = nullptr;
mx::NodePtr materialNode = nullptr;
if (typedElem)
{
materialNode = node;
if (udimSetValue && udimSetValue->isA<mx::StringVec>())
{
for (const std::string& udim : udimSetValue->asA<mx::StringVec>())
{
mx::GlslMaterialPtr mat = mx::GlslMaterial::create();
mat->setDocument(_document);
mat->setElement(typedElem);
mat->setMaterialNode(materialNode);
mat->setUdim(udim);
newMaterials.push_back(mat);
udimElement = typedElem;
}
}
else
{
mx::GlslMaterialPtr mat = mx::GlslMaterial::create();
mat->setDocument(_document);
mat->setElement(typedElem);
mat->setMaterialNode(materialNode);
newMaterials.push_back(mat);
}
}
if (!newMaterials.empty())
{
// Extend the image search path to include material source folders.
mx::FileSearchPath extendedSearchPath = _searchPath;
extendedSearchPath.append(_materialSearchPath);
_imageHandler->setSearchPath(extendedSearchPath);
// Add new materials to the global vector.
_materials.insert(_materials.end(), newMaterials.begin(), newMaterials.end());
mx::GlslMaterialPtr udimMaterial = nullptr;
for (mx::GlslMaterialPtr mat : newMaterials)
{
// Clear cached implementations, in case libraries on the file system have changed.
_genContext.clearNodeImplementations();
mx::TypedElementPtr elem = mat->getElement();
std::string udim = mat->getUdim();
if (!udim.empty())
{
if ((udimElement == elem) && udimMaterial)
{
// Reuse existing material for all udims
mat->copyShader(udimMaterial);
}
else
{
// Generate a shader for the new material.
mat->generateShader(_genContext);
if (udimElement == elem)
{
udimMaterial = mat;
}
}
}
else
{
// Generate a shader for the new material.
mat->generateShader(_genContext);
}
if (materialNode)
{
// Apply geometric assignments specified in the document, if any.
for (mx::MeshPartitionPtr part : _geometryList)
{
std::string geom = part->getName();
for (const std::string& id : part->getSourceNames())
{
geom += mx::ARRAY_PREFERRED_SEPARATOR + id;
}
if (!getGeometryBindings(materialNode, geom).empty())
{
assignMaterial(part, mat);
}
}
// Apply implicit udim assignments, if any.
if (!udim.empty())
{
for (mx::MeshPartitionPtr geom : _geometryList)
{
if (geom->getName() == udim)
{
assignMaterial(geom, mat);
}
}
}
}
}
// Apply fallback assignments.
mx::GlslMaterialPtr fallbackMaterial = newMaterials[0];
for (mx::MeshPartitionPtr geom : _geometryList)
{
if (!_materialAssignments[geom])
{
assignMaterial(geom, fallbackMaterial);
}
}
// Validate assigned materials.
for (const auto& pair : _materialAssignments)
{
if (pair.first == getSelectedGeometry())
{
mx::GlslMaterialPtr material = pair.second;
if (material)
{
material->bindShader();
}
}
}
}
}
catch (mx::ExceptionRenderError& e)
{
for (const std::string& error : e.errorLog())
{
std::cerr << error << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void RenderView::reloadShaders()
{
try
{
for (mx::GlslMaterialPtr material : _materials)
{
material->generateShader(_genContext);
for (GLenum error = glGetError(); error; error = glGetError())
{
std::cerr << "OpenGL error "
<< "reload"
<< ": " << std::to_string(error) << std::endl;
}
}
return;
}
catch (mx::ExceptionRenderError& e)
{
for (const std::string& error : e.errorLog())
{
std::cerr << error << std::endl;
}
}
_materials.clear();
}
void RenderView::initContext(mx::GenContext& context)
{
// Initialize search path
context.registerSourceCodeSearchPath(_searchPath);
// Initialize unit management.
mx::UnitTypeDefPtr distanceTypeDef = _document->getUnitTypeDef("distance");
_distanceUnitConverter = mx::LinearUnitConverter::create(distanceTypeDef);
_unitRegistry->addUnitConverter(distanceTypeDef, _distanceUnitConverter);
mx::UnitTypeDefPtr angleTypeDef = _document->getUnitTypeDef("angle");
mx::LinearUnitConverterPtr angleConverter = mx::LinearUnitConverter::create(angleTypeDef);
_unitRegistry->addUnitConverter(angleTypeDef, angleConverter);
// Create the list of supported distance units.
auto unitScales = _distanceUnitConverter->getUnitScale();
_distanceUnitOptions.resize(unitScales.size());
for (auto unitScale : unitScales)
{
int location = _distanceUnitConverter->getUnitAsInteger(unitScale.first);
_distanceUnitOptions[location] = unitScale.first;
}
// Initialize color management.
mx::DefaultColorManagementSystemPtr cms = mx::DefaultColorManagementSystem::create(context.getShaderGenerator().getTarget());
cms->loadLibrary(_document);
context.getShaderGenerator().setColorManagementSystem(cms);
// Initialize unit management.
mx::UnitSystemPtr unitSystem = mx::UnitSystem::create(context.getShaderGenerator().getTarget());
unitSystem->loadLibrary(_document);
unitSystem->setUnitConverterRegistry(_unitRegistry);
context.getShaderGenerator().setUnitSystem(unitSystem);
context.getOptions().targetDistanceUnit = "meter";
}
void RenderView::drawContents()
{
updateCameras();
glClearColor(1.0, 1.0, 1.0, 1.0);
// Render the current frame.
try
{
renderFrame();
}
catch (std::exception&)
{
_materialAssignments.clear();
glDisable(GL_FRAMEBUFFER_SRGB);
}
// Capture the current frame.
if (_captureRequested)
{
_captureRequested = false;
mx::ImagePtr frameImage = _renderFrame->getColorImage();
if (frameImage && _imageHandler->saveImage(_captureFilename, frameImage, true))
{
std::cout << "Wrote frame to disk: " << _captureFilename.asString() << std::endl;
}
// Restore state for scene rendering.
glViewport(0, 0, (int32_t) _viewWidth, (int32_t) _viewHeight);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
}
}
void RenderView::applyDirectLights(mx::DocumentPtr doc)
{
if (_lightRigDoc)
{
doc->importLibrary(_lightRigDoc);
_xincludeFiles.insert(_lightRigFilename);
}
try
{
std::vector<mx::NodePtr> lights;
_lightHandler->findLights(doc, lights);
_lightHandler->registerLights(doc, lights, _genContext);
_lightHandler->setLightSources(lights);
}
catch (std::exception& e)
{
std::cerr << "Failed to set up lighting" << e.what();
}
}
void RenderView::loadEnvironmentLight()
{
// Load the requested radiance map.
mx::ImagePtr envRadianceMap = _imageHandler->acquireImage(_envRadianceFilename);
if (!envRadianceMap)
{
return;
}
// Look for an irradiance map using an expected filename convention.
mx::ImagePtr envIrradianceMap;
mx::FilePath envIrradiancePath = _envRadianceFilename.getParentPath() / IRRADIANCE_MAP_FOLDER / _envRadianceFilename.getBaseName();
envIrradianceMap = _imageHandler->acquireImage(envIrradiancePath);
// If not found, then generate an irradiance map via spherical harmonics.
if (envIrradianceMap == _imageHandler->getZeroImage())
{
mx::Sh3ColorCoeffs shIrradiance = mx::projectEnvironment(envRadianceMap, true);
envIrradianceMap = mx::renderEnvironment(shIrradiance, IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT);
}
// Release any existing environment maps and store the new ones.
_imageHandler->releaseRenderResources(_lightHandler->getEnvRadianceMap());
_imageHandler->releaseRenderResources(_lightHandler->getEnvIrradianceMap());
_lightHandler->setEnvRadianceMap(envRadianceMap);
_lightHandler->setEnvIrradianceMap(envIrradianceMap);
// Look for a light rig using an expected filename convention.
_lightRigFilename = _envRadianceFilename;
_lightRigFilename.removeExtension();
_lightRigFilename.addExtension(mx::MTLX_EXTENSION);
_lightRigFilename = _searchPath.find(_lightRigFilename);
if (_lightRigFilename.exists())
{
_lightRigDoc = mx::createDocument();
mx::readFromXmlFile(_lightRigDoc, _lightRigFilename, _searchPath);
}
else
{
_lightRigDoc = nullptr;
}
}
void RenderView::renderFrame()
{
// 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;
mx::NodePtr dirLight = _lightHandler->getFirstLightOfCategory(DIR_LIGHT_NODE_CATEGORY);
if (_genContext.getOptions().hwShadowMap && dirLight)
{
mx::ImagePtr shadowMap = getShadowMap();
if (shadowMap)
{
shadowState.shadowMap = shadowMap;
shadowState.shadowMatrix = _viewCamera->getWorldMatrix().getInverse() *
_shadowCamera->getWorldViewProjMatrix();
}
else
{
_genContext.getOptions().hwShadowMap = false;
}
}
// Initialize viewport render.
if (!_renderFrame ||
_renderFrame->getWidth() != (unsigned int) _viewWidth ||
_renderFrame->getHeight() != (unsigned int) _viewHeight)
{
_renderFrame = mx::GLFramebuffer::create((unsigned int) _viewWidth, (unsigned int) _viewHeight,
4, mx::Image::BaseType::UINT8);
}
_renderFrame->bind();
glClearColor(.70f, .70f, .75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEnable(GL_FRAMEBUFFER_SRGB);
// Enable backface culling if requested.
if (!_renderDoubleSided)
{
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
// Opaque pass
for (const auto& assignment : _materialAssignments)
{
mx::MeshPartitionPtr geom = assignment.first;
mx::GlslMaterialPtr material = assignment.second;
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 (_renderTransparency)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (const auto& assignment : _materialAssignments)
{
mx::MeshPartitionPtr geom = assignment.first;
mx::GlslMaterialPtr material = assignment.second;
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 (!_renderDoubleSided)
{
glDisable(GL_CULL_FACE);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Store viewport texture for render.
_textureID = _renderFrame->getColorTexture();
}
void RenderView::initCamera()
{
_viewCamera->setViewportSize(mx::Vector2((float) _viewWidth, (float) _viewHeight));
if (_geometryHandler->getMeshes().empty())
{
return;
}
const mx::Vector3& boxMax = _geometryHandler->getMaximumBounds();
const mx::Vector3& boxMin = _geometryHandler->getMinimumBounds();
mx::Vector3 sphereCenter = (boxMax + boxMin) * 0.5;
mx::Matrix44 meshRotation = mx::Matrix44::createRotationZ(_meshRotation[2] / 180.0f * PI) *
mx::Matrix44::createRotationY(_meshRotation[1] / 180.0f * PI) *
mx::Matrix44::createRotationX(_meshRotation[0] / 180.0f * PI);
_meshTranslation = -meshRotation.transformPoint(sphereCenter);
_meshScale = IDEAL_MESH_SPHERE_RADIUS / (sphereCenter - boxMin).getMagnitude();
}
void RenderView::updateCameras()
{
mx::Matrix44 viewMatrix, projectionMatrix;
float aspectRatio = (float) _viewHeight / _viewHeight;
if (_cameraViewAngle != 0.0f)
{
viewMatrix = mx::Camera::createViewMatrix(_cameraPosition, _cameraTarget, _cameraUp);
float fH = std::tan(_cameraViewAngle / 360.0f * PI) * _cameraNearDist;
float fW = fH * aspectRatio;
projectionMatrix = mx::Camera::createPerspectiveMatrix(-fW, fW, -fH, fH, _cameraNearDist, _cameraFarDist);
}
else
{
viewMatrix = mx::Matrix44::createTranslation(mx::Vector3(0.0f, 0.0f, -ORTHO_VIEW_DISTANCE));
float fH = ORTHO_PROJECTION_HEIGHT;
float fW = fH * aspectRatio;
projectionMatrix = mx::Camera::createOrthographicMatrix(-fW, fW, -fH, fH, 0.0f, ORTHO_VIEW_DISTANCE + _cameraFarDist);
}
mx::Matrix44 meshRotation = mx::Matrix44::createRotationZ(_meshRotation[2] / 180.0f * PI) *
mx::Matrix44::createRotationY(_meshRotation[1] / 180.0f * PI) *
mx::Matrix44::createRotationX(_meshRotation[0] / 180.0f * PI);
mx::Matrix44 arcball = _viewCamera->arcballMatrix();
_viewCamera->setWorldMatrix(meshRotation *
mx::Matrix44::createTranslation(_meshTranslation + _userTranslation) *
mx::Matrix44::createScale(mx::Vector3(_meshScale * _cameraZoom)));
_viewCamera->setViewMatrix(arcball * viewMatrix);
_viewCamera->setProjectionMatrix(projectionMatrix);
_envCamera->setWorldMatrix(mx::Matrix44::createScale(mx::Vector3(300.0f)));
_envCamera->setViewMatrix(_viewCamera->getViewMatrix());
_envCamera->setProjectionMatrix(_viewCamera->getProjectionMatrix());
mx::NodePtr dirLight = _lightHandler->getFirstLightOfCategory(DIR_LIGHT_NODE_CATEGORY);
if (dirLight)
{
mx::Vector3 sphereCenter = (_geometryHandler->getMaximumBounds() + _geometryHandler->getMinimumBounds()) * 0.5;
float r = (sphereCenter - _geometryHandler->getMinimumBounds()).getMagnitude();
_shadowCamera->setWorldMatrix(meshRotation * mx::Matrix44::createTranslation(-sphereCenter));
_shadowCamera->setProjectionMatrix(mx::Camera::createOrthographicMatrix(-r, r, -r, r, 0.0f, r * 2.0f));
mx::ValuePtr value = dirLight->getInputValue("direction");
if (value->isA<mx::Vector3>())
{
mx::Vector3 dir = mx::Matrix44::createRotationY(_lightRotation / 180.0f * PI).transformVector(value->asA<mx::Vector3>());
_shadowCamera->setViewMatrix(mx::Camera::createViewMatrix(dir * -r, mx::Vector3(0.0f), _cameraUp));
}
}
}
void RenderView::renderScreenSpaceQuad(mx::GlslMaterialPtr material)
{
if (!_quadMesh)
_quadMesh = mx::GeometryHandler::createQuadMesh();
material->bindMesh(_quadMesh);
material->drawPartition(_quadMesh->getPartition(0));
}
mx::ImagePtr RenderView::getShadowMap()
{
if (!_shadowMap)
{
// Generate shaders for shadow rendering.
if (!_shadowMaterial)
{
try
{
mx::ShaderPtr hwShader = mx::createDepthShader(_genContext, _document, "__SHADOW_SHADER__");
_shadowMaterial = mx::GlslMaterial::create();
_shadowMaterial->generateShader(hwShader);
}
catch (std::exception& e)
{
std::cerr << "Failed to generate shadow shader: " << e.what() << std::endl;
_shadowMaterial = nullptr;
}
}
if (!_shadowBlurMaterial)
{
try
{
mx::ShaderPtr hwShader = mx::createBlurShader(_genContext, _document, "__SHADOW_BLUR_SHADER__", "gaussian", 1.0f);
_shadowBlurMaterial = mx::GlslMaterial::create();
_shadowBlurMaterial->generateShader(hwShader);
}
catch (std::exception& e)
{
std::cerr << "Failed to generate shadow blur shader: " << e.what() << std::endl;
_shadowBlurMaterial = nullptr;
}
}
if (_shadowMaterial && _shadowBlurMaterial)
{
// Create framebuffer.
mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 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.
_shadowMaterial->bindShader();
for (auto mesh : _geometryHandler->getMeshes())
{
_shadowMaterial->bindMesh(mesh);
_shadowMaterial->bindViewInformation(_shadowCamera);
for (size_t i = 0; i < mesh->getPartitionCount(); i++)
{
mx::MeshPartitionPtr geom = mesh->getPartition(i);
_shadowMaterial->drawPartition(geom);
}
}
_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 < _shadowSoftness; i++)
{
framebuffer->bind();
_shadowBlurMaterial->bindShader();
if (_imageHandler->bindImage(_shadowMap, blurSamplingProperties))
{
mx::GLTextureHandlerPtr textureHandler = std::static_pointer_cast<mx::GLTextureHandler>(_imageHandler);
int textureLocation = textureHandler->getBoundTextureLocation(_shadowMap->getResourceId());
if (textureLocation >= 0)
{
_shadowBlurMaterial->getProgram()->bindUniform("image_file", mx::Value::createValue(textureLocation));
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
renderScreenSpaceQuad(_shadowBlurMaterial);
_imageHandler->releaseRenderResources(_shadowMap);
_shadowMap = framebuffer->getColorImage();
}
// Restore state for scene rendering.
glViewport(0, 0, (int32_t) _viewWidth, (int32_t) _viewHeight);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
}
}
return _shadowMap;
}