// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include "MaterialXRenderGlsl/GLTextureHandler.h" #include #include #include #include #include #include #include #include #include #include #include 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 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(); if (node && node->getCategory() == "texcoord") { mx::InputPtr index = node->getInput("index"); mx::ValuePtr value = index ? index->getValue() : nullptr; if (value && value->isA() && value->asA() != 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 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 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() : nullptr; if (node && node->getCategory() == mx::SURFACE_MATERIAL_NODE_STRING && mx::getShaderNodes(node).empty()) { typedElem = nullptr; } // Create new materials. if (!typedElem) { std::vector 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()) { for (const std::string& udim : udimSetValue->asA()) { 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 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 dir = mx::Matrix44::createRotationY(_lightRotation / 180.0f * PI).transformVector(value->asA()); _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(_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; }