diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a9e4bedbbd..931c41703b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2479,6 +2479,7 @@ Application::Application( copyViewFrustum(viewFrustum); return viewFrustum.getPosition(); }); + MirrorModeHelpers::setComputeMirrorViewOperator(EntityRenderer::computeMirrorViewOperator); DependencyManager::get()->setKickConfirmationOperator([this] (const QUuid& nodeID, unsigned int banFlags) { userKickConfirmation(nodeID, banFlags); }); diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 704d7963e7..130b8c77ea 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -27,7 +27,7 @@ public: using Config = SecondaryCameraJobConfig; using JobModel = render::Job::ModelO; SecondaryCameraJob() { - _cachedArgsPointer = std::make_shared(_cachedArgs); + _cachedArgsPointer = std::make_shared(); _attachedEntityPropertyFlags += PROP_POSITION; _attachedEntityPropertyFlags += PROP_ROTATION; } @@ -203,7 +203,6 @@ public: } protected: - RenderArgs _cachedArgs; RenderArgsPointer _cachedArgsPointer; private: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 827388aa1c..bb6fbcd899 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3530,8 +3530,9 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON_LOOK_AT || qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON; bool overrideAnim = _skeletonModel ? _skeletonModel->getRig().isPlayingOverrideAnimation() : false; + bool isInMirror = renderArgs->_mirrorDepth > 0; bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition()); - return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead); + return !defaultMode || isInMirror || (!firstPerson && !insideHead) || (overrideAnim && !insideHead); } void MyAvatar::setRotationRecenterFilterLength(float length) { diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 12814aa6b6..47f772b4bc 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -9,6 +9,7 @@ // #include "RenderScriptingInterface.h" +#include #include #include "LightingModel.h" @@ -79,14 +80,35 @@ void RenderScriptingInterface::setRenderMethod(RenderMethod renderMethod) { emit settingsChanged(); } } + +void recursivelyUpdateMirrorRenderMethods(const QString& parentTaskName, int renderMethod, int depth) { + if (depth == RenderMirrorTask::MAX_MIRROR_DEPTH) { + return; + } + + for (size_t mirrorIndex = 0; mirrorIndex < RenderMirrorTask::MAX_MIRRORS_PER_LEVEL; mirrorIndex++) { + std::string mirrorTaskString = parentTaskName.toStdString() + ".RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth) + ".DeferredForwardSwitch"; + auto mirrorConfig = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig(QString::fromStdString(mirrorTaskString))); + if (mirrorConfig) { + mirrorConfig->setBranch((int)renderMethod); + recursivelyUpdateMirrorRenderMethods(QString::fromStdString(mirrorTaskString) + (renderMethod == 1 ? ".RenderForwardTask" : ".RenderShadowsAndDeferredTask.RenderDeferredTask"), + renderMethod, depth + 1); + } + } +} + void RenderScriptingInterface::forceRenderMethod(RenderMethod renderMethod) { _renderSettingLock.withWriteLock([&] { _renderMethod = (int)renderMethod; _renderMethodSetting.set((int)renderMethod); - auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DeferredForwardSwitch")); + QString configName = "RenderMainView.DeferredForwardSwitch"; + auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig(configName)); if (config) { config->setBranch((int)renderMethod); + + recursivelyUpdateMirrorRenderMethods(configName + (renderMethod == RenderMethod::FORWARD ? ".RenderForwardTask" : ".RenderShadowsAndDeferredTask.RenderDeferredTask"), + (int)renderMethod, 0); } }); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b2d6a6260b..790b45843c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1120,7 +1120,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const batch.setModelTransform(textTransform); { PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText"); - displayNameRenderer->draw(batch, text_x, -text_y, glm::vec2(-1.0f), nameUTF8.data(), textColor, true, forward); + displayNameRenderer->draw(batch, { nameUTF8.data(), textColor, { text_x, -text_y }, glm::vec2(-1.0f), forward }); } } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 03a463c82a..47f22dfaee 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -357,7 +357,7 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - getGLBackend()->setCameraCorrection(mat4(), mat4(), true); + getGLBackend()->setCameraCorrection(mat4(), mat4(), true, true); for (auto& cursorValue : _cursorsData) { auto& cursorData = cursorValue.second; @@ -701,7 +701,7 @@ void OpenGLDisplayPlugin::present(const std::shared_ptr& if (_currentFrame) { auto correction = getViewCorrection(); - getGLBackend()->setCameraCorrection(correction, _prevRenderView); + getGLBackend()->setCameraCorrection(correction, _prevRenderView, true); _prevRenderView = correction * _currentFrame->view; { withPresentThreadLock([&] { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 212baa6634..09bbfb9284 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -12,6 +12,7 @@ #include "RenderableEntityItem.h" +#include #include #include "RenderableShapeEntityItem.h" @@ -192,6 +193,10 @@ ItemKey EntityRenderer::getKey() { builder.withSubMetaCulled(); } + if (_mirrorMode == MirrorMode::MIRROR || (_mirrorMode == MirrorMode::PORTAL && !_portalExitID.isNull())) { + builder.withMirror(); + } + if (!_visible) { builder.withInvisible(); } @@ -221,12 +226,113 @@ bool EntityRenderer::passesZoneOcclusionTest(const std::unordered_set& co return true; } +ItemID EntityRenderer::computeMirrorView(ViewFrustum& viewFrustum) const { + glm::vec3 inPropertiesPosition; + glm::quat inPropertiesRotation; + MirrorMode mirrorMode; + QUuid portalExitID; + withReadLock([&]{ + inPropertiesPosition = _entity->getWorldPosition(); + inPropertiesRotation = _entity->getWorldOrientation(); + mirrorMode = _mirrorMode; + portalExitID = _portalExitID; + }); + return computeMirrorViewOperator(viewFrustum, inPropertiesPosition, inPropertiesRotation, mirrorMode, portalExitID); +} + +ItemID EntityRenderer::computeMirrorViewOperator(ViewFrustum& viewFrustum, const glm::vec3& inPropertiesPosition, const glm::quat& inPropertiesRotation, + MirrorMode mirrorMode, const QUuid& portalExitID) { + glm::mat4 inToWorld = glm::translate(inPropertiesPosition) * glm::mat4_cast(inPropertiesRotation); + glm::mat4 worldToIn = glm::inverse(inToWorld); + + glm::vec3 outPropertiesPosition = inPropertiesPosition; + glm::quat outPropertiesRotation = inPropertiesRotation; + glm::mat4 outToWorld = inToWorld; + bool foundPortalExit = false; + if (mirrorMode == MirrorMode::PORTAL && !portalExitID.isNull()) { + auto renderer = DependencyManager::get(); + if (renderer) { + if (auto renderable = renderer->renderableForEntityId(portalExitID)) { + renderable->withReadLock([&] { + outPropertiesPosition = renderable->_entity->getWorldPosition(); + outPropertiesRotation = renderable->_entity->getWorldOrientation(); + }); + + outToWorld = glm::translate(outPropertiesPosition) * glm::mat4_cast(outPropertiesRotation); + foundPortalExit = true; + } + } + } + + // get mirror camera position by reflecting main camera position's z coordinate in mirror space + glm::vec3 cameraPositionWorld = viewFrustum.getPosition(); + glm::vec3 cameraPositionIn = vec3(worldToIn * vec4(cameraPositionWorld, 1.0f)); + glm::vec3 mirrorCameraPositionIn = vec3(cameraPositionIn.x, cameraPositionIn.y, -cameraPositionIn.z); + if (foundPortalExit) { + // portals also flip over x + mirrorCameraPositionIn.x *= -1.0f; + } + glm::vec3 mirrorCameraPositionWorld = vec3(outToWorld * vec4(mirrorCameraPositionIn, 1.0f)); + + // get mirror camera rotation by reflecting main camera rotation in mirror space + // TODO: we are assuming here that UP is world y-axis + glm::quat mainCameraRotationWorld = viewFrustum.getOrientation(); + glm::quat mainCameraRotationMirror = worldToIn * glm::mat4_cast(mainCameraRotationWorld); + glm::quat mirrorCameraRotationMirror = glm::quat(mainCameraRotationMirror.w, -mainCameraRotationMirror.x, -mainCameraRotationMirror.y, mainCameraRotationMirror.z) * + glm::angleAxis((float)M_PI, glm::vec3(0, 1, 0)); + if (foundPortalExit) { + // portals also flip over x + mirrorCameraRotationMirror = glm::quat(mirrorCameraRotationMirror.w, mirrorCameraRotationMirror.x, -mirrorCameraRotationMirror.y, -mirrorCameraRotationMirror.z); + } + glm::quat mirrorCameraRotationWorld = outToWorld * glm::mat4_cast(mirrorCameraRotationMirror); + + viewFrustum.setPosition(mirrorCameraPositionWorld); + viewFrustum.setOrientation(mirrorCameraRotationWorld); + + // modify the near clip plane to be the XY plane of the mirror + // from: https://terathon.com/lengyel/Lengyel-Oblique.pdf + glm::mat4 view = viewFrustum.getView(); + glm::mat4 projection = viewFrustum.getProjection(); + + //Find the camera-space 4D reflection plane vector + glm::vec3 cameraSpacePosition = glm::inverse(view) * glm::vec4(outPropertiesPosition, 1.0f); + glm::vec3 cameraSpaceNormal = glm::transpose(view) * (outPropertiesRotation * glm::vec4(0, 0, -1, 0)); + glm::vec4 clipPlane = glm::vec4(cameraSpaceNormal, -glm::dot(cameraSpaceNormal, cameraSpacePosition)); + // Make sure we pick the direction facing away from us + if (clipPlane.w > 0.0f) { + clipPlane *= -1.0f; + } + + // Calculate the clip-space corner point opposite the clipping plane + // as (sign(clipPlane.x), sign(clipPlane.y), 1, 1) and + // transform it into camera space by multiplying it + // by the inverse of the projection matrix + glm::vec4 q; + q.x = (glm::sign(clipPlane.x) + projection[0][2]) / projection[0][0]; + q.y = (glm::sign(clipPlane.y) + projection[1][2]) / projection[1][1]; + q.z = -1.0f; + q.w = (1.0f + projection[2][2]) / projection[2][3]; + + // Calculate the scaled plane vector + glm::vec4 c = (2.0f / glm::dot(clipPlane, q)) * clipPlane; + + // Replace the third row of the projection matrix + projection[0][2] = c.x; + projection[1][2] = c.y; + projection[2][2] = c.z + 1.0f; + projection[3][2] = c.w; + + viewFrustum.setProjection(projection, true); + + return foundPortalExit ? DependencyManager::get()->renderableIdForEntityId(portalExitID) : Item::INVALID_ITEM_ID; +} + void EntityRenderer::render(RenderArgs* args) { if (!isValidRenderItem()) { return; } - if (_visible && (args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || !_cauterized)) { + if (_visible && (!_cauterized || args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || args->_mirrorDepth > 0)) { doRender(args); } } @@ -454,6 +560,8 @@ void EntityRenderer::doRenderUpdateAsynchronous(const EntityItemPointer& entity) _canCastShadow = entity->getCanCastShadow(); setCullWithParent(entity->getCullWithParent()); _cauterized = entity->getCauterized(); + setMirrorMode(entity->getMirrorMode()); + setPortalExitID(entity->getPortalExitID()); if (entity->needsZoneOcclusionUpdate()) { entity->resetNeedsZoneOcclusionUpdate(); _renderWithZones = entity->getRenderWithZones(); @@ -505,6 +613,10 @@ graphics::MaterialPointer EntityRenderer::getTopMaterial() { } EntityRenderer::Pipeline EntityRenderer::getPipelineType(const graphics::MultiMaterial& materials) { + if (_mirrorMode == MirrorMode::MIRROR || (_mirrorMode == MirrorMode::PORTAL && !_portalExitID.isNull())) { + return Pipeline::MIRROR; + } + if (materials.top().material && materials.top().material->isProcedural() && materials.top().material->isReady()) { return Pipeline::PROCEDURAL; } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 3caeef0713..99dbffbc72 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -58,12 +58,13 @@ public: enum class Pipeline { SIMPLE, MATERIAL, - PROCEDURAL + PROCEDURAL, + MIRROR }; virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); virtual graphics::MaterialPointer getTopMaterial(); - static Pipeline getPipelineType(const graphics::MultiMaterial& materials); + Pipeline getPipelineType(const graphics::MultiMaterial& materials); virtual gpu::TexturePointer getTexture() { return nullptr; } virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } @@ -74,6 +75,9 @@ public: virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override; virtual Item::Bound getBound(RenderArgs* args) override; bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const override; + ItemID computeMirrorView(ViewFrustum& viewFrustum) const override; + static ItemID computeMirrorViewOperator(ViewFrustum& viewFrustum, const glm::vec3& inPropertiesPosition, const glm::quat& inPropertiesRotation, + MirrorMode mirrorMode, const QUuid& portalExitID); protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } @@ -116,6 +120,8 @@ protected: virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; } virtual void setCullWithParent(bool value) { _cullWithParent = value; } + virtual void setMirrorMode(MirrorMode value) { _mirrorMode = value; } + virtual void setPortalExitID(const QUuid& value) { _portalExitID = value; } template std::shared_ptr asTypedEntity() { return std::static_pointer_cast(_entity); } @@ -151,6 +157,8 @@ protected: BillboardMode _billboardMode { BillboardMode::NONE }; bool _cauterized { false }; bool _moving { false }; + MirrorMode _mirrorMode { MirrorMode::NONE }; + QUuid _portalExitID; Transform _renderTransform; MaterialMap _materials; diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 42df1e2888..10ae144334 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -263,8 +263,9 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) { bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true)); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true)); batch.setModelTransform(transform); Pipeline pipelineType = getPipelineType(materials); diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index e374fe29c0..3f40218d46 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -103,8 +103,9 @@ void GridEntityRenderer::doRender(RenderArgs* args) { } else { transform.setTranslation(renderTransform.getTranslation()); } + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch->setModelTransform(transform); auto minCorner = glm::vec2(-0.5f, -0.5f); diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index 9592a3e57f..66a5d0d609 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -147,8 +147,9 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { gpu::Batch* batch = args->_batch; + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); float imageWidth = _texture->getWidth(); float imageHeight = _texture->getHeight(); @@ -198,8 +199,10 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { procedural->prepare(*batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created, ProceduralProgramKey(transparent)); } else if (pipelineType == Pipeline::SIMPLE) { batch->setResourceTexture(0, _texture->getGPUTexture()); - } else if (RenderPipelines::bindMaterials(materials, *batch, args->_renderMode, args->_enableTexturing)) { - args->_details._materialSwitches++; + } else if (pipelineType == Pipeline::MATERIAL) { + if (RenderPipelines::bindMaterials(materials, *batch, args->_renderMode, args->_enableTexturing)) { + args->_details._materialSwitches++; + } } DependencyManager::get()->renderQuad( diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index a36cdde212..1117c97c75 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -47,8 +47,9 @@ void LineEntityRenderer::doRender(RenderArgs* args) { const auto& modelTransform = getModelTransform(); Transform transform = Transform(); transform.setTranslation(modelTransform.getTranslation()); + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); if (_linePoints.size() > 1) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, false, true, diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index b8f829f4ba..fe44c41094 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -303,8 +303,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { proceduralRender = true; } + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); if (!proceduralRender) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8ed3f84609..6b83d87732 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1274,6 +1274,8 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _model->setBillboardMode(_billboardMode, scene); _model->setCullWithParent(_cullWithParent, scene); _model->setRenderWithZones(_renderWithZones, scene); + _model->setMirrorMode(_mirrorMode, scene); + _model->setPortalExitID(_portalExitID, scene); }); if (didVisualGeometryRequestSucceed) { emit DependencyManager::get()-> @@ -1353,6 +1355,8 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint model->setBillboardMode(_billboardMode, scene); model->setCullWithParent(_cullWithParent, scene); model->setRenderWithZones(_renderWithZones, scene); + model->setMirrorMode(_mirrorMode, scene); + model->setPortalExitID(_portalExitID, scene); }); if (entity->blendshapesChanged()) { @@ -1466,6 +1470,18 @@ void ModelEntityRenderer::setCullWithParent(bool value) { setKey(_didLastVisualGeometryRequestSucceed, _model); } +void ModelEntityRenderer::setMirrorMode(MirrorMode value) { + Parent::setMirrorMode(value); + // called within a lock so no need to lock for _model + setKey(_didLastVisualGeometryRequestSucceed, _model); +} + +void ModelEntityRenderer::setPortalExitID(const QUuid& value) { + Parent::setPortalExitID(value); + // called within a lock so no need to lock for _model + setKey(_didLastVisualGeometryRequestSucceed, _model); +} + // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items void ModelEntityRenderer::doRender(RenderArgs* args) { DETAILED_PROFILE_RANGE(render_detail, "MetaModelRender"); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index f394d389f5..425d082f01 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -170,6 +170,8 @@ protected: void setIsVisibleInSecondaryCamera(bool value) override; void setRenderLayer(RenderLayer value) override; void setCullWithParent(bool value) override; + void setMirrorMode(MirrorMode value) override; + void setPortalExitID(const QUuid& value) override; private: void animate(const TypedEntityPointer& entity, const ModelPointer& model); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index aca501985a..81f4c5fcb4 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -325,8 +325,9 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { buildPipelines(); } + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 8331e016fd..26091a1ed4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1860,8 +1860,9 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); gpu::Batch& batch = *args->_batch; + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(_position, _orientation, _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); Transform transform(glm::translate(_position) * rotation * _lastVoxelToLocalMatrix); batch.setModelTransform(transform); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index a6fee03311..82350f54bf 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -118,8 +118,9 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), _shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron)); batch.setModelTransform(transform); @@ -157,7 +158,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } } } else { - if (RenderPipelines::bindMaterials(materials, batch, args->_renderMode, args->_enableTexturing)) { + if (pipelineType == Pipeline::MATERIAL && RenderPipelines::bindMaterials(materials, batch, args->_renderMode, args->_enableTexturing)) { args->_details._materialSwitches++; } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 2858e12afd..a15e2839a4 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -164,8 +164,9 @@ void TextEntityRenderer::doRender(RenderArgs* args) { transform = _renderTransform; }); + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); Pipeline pipelineType = getPipelineType(materials); @@ -180,7 +181,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { } auto geometryCache = DependencyManager::get(); - if (pipelineType == Pipeline::SIMPLE) { + if (pipelineType == Pipeline::SIMPLE || pipelineType == Pipeline::MIRROR) { geometryCache->renderQuad(batch, glm::vec2(-0.5f), glm::vec2(0.5f), backgroundColor, _geometryID); } else { geometryCache->renderQuad(batch, glm::vec2(-0.5f), glm::vec2(0.5f), glm::vec2(0.0f), glm::vec2(1.0f), backgroundColor, _geometryID); @@ -260,6 +261,10 @@ ItemKey entities::TextPayload::getKey() const { builder.withInvisible(); } + if (textRenderable->_mirrorMode == MirrorMode::MIRROR || (textRenderable->_mirrorMode == MirrorMode::PORTAL && !textRenderable->_portalExitID.isNull())) { + builder.withMirror(); + } + return builder; } } @@ -311,6 +316,17 @@ bool entities::TextPayload::passesZoneOcclusionTest(const std::unordered_set(); + if (entityTreeRenderer) { + auto renderable = entityTreeRenderer->renderableForEntityId(_entityID); + if (renderable) { + return renderable->computeMirrorView(viewFrustum); + } + } + return Item::INVALID_ITEM_ID; +} + void entities::TextPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("TextPayload::render"); Q_ASSERT(args->_batch); @@ -335,12 +351,15 @@ void entities::TextPayload::render(RenderArgs* args) { glm::vec3 dimensions; glm::vec4 textColor; + bool mirror; textRenderable->withReadLock([&] { transform = textRenderable->_renderTransform; dimensions = textRenderable->_dimensions; float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f; textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha); + + mirror = textRenderable->_mirrorMode == MirrorMode::MIRROR || (textRenderable->_mirrorMode == MirrorMode::PORTAL && !textRenderable->_portalExitID.isNull()); }); bool forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; @@ -352,8 +371,9 @@ void entities::TextPayload::render(RenderArgs* args) { return; } + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), textRenderable->_billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); float scale = textRenderable->_lineHeight / textRenderer->getFontSize(); transform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z)); @@ -361,9 +381,8 @@ void entities::TextPayload::render(RenderArgs* args) { batch.setModelTransform(transform); glm::vec2 bounds = glm::vec2(dimensions.x - (textRenderable->_leftMargin + textRenderable->_rightMargin), dimensions.y - (textRenderable->_topMargin + textRenderable->_bottomMargin)); - textRenderer->draw(batch, textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale, bounds / scale, scale, - textRenderable->_text, textRenderable->_font, textColor, effectColor, textRenderable->_effectThickness, textRenderable->_effect, - textRenderable->_alignment, textRenderable->_unlit, forward); + textRenderer->draw(batch, textRenderable->_font, { textRenderable->_text, textColor, effectColor, { textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale }, + bounds / scale, scale, textRenderable->_effectThickness, textRenderable->_effect, textRenderable->_alignment, textRenderable->_unlit, forward, mirror }); } namespace render { @@ -399,4 +418,11 @@ template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Poi return false; } +template <> ItemID payloadComputeMirrorView(const entities::TextPayload::Pointer& payload, ViewFrustum& viewFrustum) { + if (payload) { + return payload->computeMirrorView(viewFrustum); + } + return Item::INVALID_ITEM_ID; +} + } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 8a18554dea..f48bb8085f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -101,6 +101,7 @@ public: ShapeKey getShapeKey() const; void render(RenderArgs* args); bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const; + ItemID computeMirrorView(ViewFrustum& viewFrustum) const; protected: QUuid _entityID; @@ -117,6 +118,7 @@ namespace render { template <> const ShapeKey shapeGetShapeKey(const entities::TextPayload::Pointer& payload); template <> void payloadRender(const entities::TextPayload::Pointer& payload, RenderArgs* args); template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Pointer& payload, const std::unordered_set& containingZones); + template <> ItemID payloadComputeMirrorView(const entities::TextPayload::Pointer& payload, ViewFrustum& viewFrustum); } #endif // hifi_RenderableTextEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index c98bfe7f63..77f6fe99f6 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -320,8 +320,9 @@ void WebEntityRenderer::doRender(RenderArgs* args) { batch.setResourceTexture(0, _texture); + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); // Turn off jitter for these entities diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ad4345ba9f..cc0d93604a 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -106,6 +106,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_TAGS; requestedProperties += _grabProperties.getEntityProperties(params); + requestedProperties += PROP_MIRROR_MODE; + requestedProperties += PROP_PORTAL_EXIT_ID; // Physics requestedProperties += PROP_DENSITY; @@ -307,6 +309,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); + APPEND_ENTITY_PROPERTY(PROP_MIRROR_MODE, (uint32_t)getMirrorMode()); + APPEND_ENTITY_PROPERTY(PROP_PORTAL_EXIT_ID, getPortalExitID()); // Physics APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); @@ -884,6 +888,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef bytesRead += bytesFromGrab; dataAt += bytesFromGrab; }); + READ_ENTITY_PROPERTY(PROP_MIRROR_MODE, MirrorMode, setMirrorMode); + READ_ENTITY_PROPERTY(PROP_PORTAL_EXIT_ID, QUuid, setPortalExitID); READ_ENTITY_PROPERTY(PROP_DENSITY, float, setDensity); { @@ -1365,6 +1371,8 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire withReadLock([&] { _grabProperties.getProperties(properties); }); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(mirrorMode, getMirrorMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(portalExitID, getPortalExitID); // Physics COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); @@ -1504,6 +1512,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { bool grabPropertiesChanged = _grabProperties.setProperties(properties); somethingChanged |= grabPropertiesChanged; }); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(mirrorMode, setMirrorMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(portalExitID, setPortalExitID); // Physics SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, setDensity); @@ -3559,6 +3569,32 @@ void EntityItem::setBillboardMode(BillboardMode value) { }); } +MirrorMode EntityItem::getMirrorMode() const { + return resultWithReadLock([&] { + return _mirrorMode; + }); +} + +void EntityItem::setMirrorMode(MirrorMode value) { + withWriteLock([&] { + _needsRenderUpdate |= _mirrorMode != value; + _mirrorMode = value; + }); +} + +QUuid EntityItem::getPortalExitID() const { + return resultWithReadLock([&] { + return _portalExitID; + }); +} + +void EntityItem::setPortalExitID(const QUuid& value) { + withWriteLock([&] { + _needsRenderUpdate |= _portalExitID != value; + _portalExitID = value; + }); +} + void EntityItem::setTags(const QSet& tags) { withWriteLock([&] { _tags = tags; @@ -3566,5 +3602,7 @@ void EntityItem::setTags(const QSet& tags) { } QSet EntityItem::getTags() const { - return resultWithReadLock>([&] { return _tags; }); -} \ No newline at end of file + return resultWithReadLock>([&] { + return _tags; + }); +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 01a2a7c5bf..84ec70e9a1 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -559,6 +559,12 @@ public: BillboardMode getBillboardMode() const; virtual bool getRotateForPicking() const { return false; } + MirrorMode getMirrorMode() const; + void setMirrorMode(MirrorMode value); + + QUuid getPortalExitID() const; + void setPortalExitID(const QUuid& value); + void setTags(const QSet& tags); QSet getTags() const; @@ -743,6 +749,9 @@ protected: bool _cullWithParent { false }; + MirrorMode _mirrorMode { MirrorMode::NONE }; + QUuid _portalExitID; + QSet _tags; mutable bool _needsRenderUpdate { false }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 205f0ab5f2..590d93a451 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -424,6 +424,23 @@ void EntityItemProperties::setEntityHostTypeFromString(const QString& entityHost } } +inline void addMirrorMode(QHash& lookup, MirrorMode mirrorMode) { lookup[MirrorModeHelpers::getNameForMirrorMode(mirrorMode)] = mirrorMode; } +const QHash stringToMirrorModeLookup = [] { + QHash toReturn; + addMirrorMode(toReturn, MirrorMode::NONE); + addMirrorMode(toReturn, MirrorMode::MIRROR); + addMirrorMode(toReturn, MirrorMode::PORTAL); + return toReturn; +}(); +QString EntityItemProperties::getMirrorModeAsString() const { return MirrorModeHelpers::getNameForMirrorMode(_mirrorMode); } +void EntityItemProperties::setMirrorModeFromString(const QString& mirrorMode) { + auto mirrorModeItr = stringToMirrorModeLookup.find(mirrorMode.toLower()); + if (mirrorModeItr != stringToMirrorModeLookup.end()) { + _mirrorMode = mirrorModeItr.value(); + _mirrorModeChanged = true; + } +} + QVector EntityItemProperties::getTagsAsVector() const { QVector tags; for (const QString& tag : _tags) { @@ -471,6 +488,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); CHECK_PROPERTY_CHANGE(PROP_TAGS, tags); changedProperties += _grab.getChangedProperties(); + CHECK_PROPERTY_CHANGE(PROP_MIRROR_MODE, mirrorMode); + CHECK_PROPERTY_CHANGE(PROP_PORTAL_EXIT_ID, portalExitID); // Physics CHECK_PROPERTY_CHANGE(PROP_DENSITY, density); @@ -842,6 +861,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @property {Entities.Grab} grab - The entity's grab-related properties. * + * @property {MirrorMode} mirrorMode="none" - If this entity should render as a mirror (reflecting the view of the camera), + * a portal (reflecting the view through its portalExitID), or normally. + * @property {Uuid} portalExitID=Uuid.NULL - The ID of the entity that should act as the portal exit if the mirrorMode + * is set to portal. + * * @comment The different entity types have additional properties as follows: * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} * @see {@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo} @@ -1634,6 +1658,8 @@ ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool s COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TAGS, tags, getTagsAsVector()); _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_MIRROR_MODE, mirrorMode, getMirrorModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PORTAL_EXIT_ID, portalExitID); // Physics COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DENSITY, density); @@ -2051,6 +2077,8 @@ void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool h COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(tags, qVectorQString, setTagsFromVector, getTagsAsVector); _grab.copyFromScriptValue(object, namesSet, _defaultSettings); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(mirrorMode, MirrorMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(portalExitID, QUuid, setPortalExitID); // Physics COPY_PROPERTY_FROM_QSCRIPTVALUE(density, float, setDensity); @@ -2338,6 +2366,8 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(billboardMode); COPY_PROPERTY_IF_CHANGED(tags); _grab.merge(other._grab); + COPY_PROPERTY_IF_CHANGED(mirrorMode); + COPY_PROPERTY_IF_CHANGED(portalExitID); // Physics COPY_PROPERTY_IF_CHANGED(density); @@ -2648,6 +2678,8 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, Grab, grab, EquippableIndicatorOffset, equippableIndicatorOffset); } + ADD_PROPERTY_TO_MAP(PROP_MIRROR_MODE, MirrorMode, mirrorMode, MirrorMode); + ADD_PROPERTY_TO_MAP(PROP_PORTAL_EXIT_ID, PortalExitID, portalExitID, QUuid); // Physics ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_DENSITY, Density, density, float, @@ -3112,6 +3144,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticGrab.setProperties(properties); _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_MIRROR_MODE, (uint32_t)properties.getMirrorMode()); + APPEND_ENTITY_PROPERTY(PROP_PORTAL_EXIT_ID, properties.getPortalExitID()); // Physics APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); @@ -3591,6 +3625,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TAGS, QSet, setTags); properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MIRROR_MODE, MirrorMode, setMirrorMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PORTAL_EXIT_ID, QUuid, setPortalExitID); // Physics READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); @@ -3984,6 +4020,8 @@ void EntityItemProperties::markAllChanged() { _billboardModeChanged = true; _tagsChanged = true; _grab.markAllChanged(); + _mirrorModeChanged = true; + _portalExitIDChanged = true; // Physics _densityChanged = true; @@ -4386,6 +4424,12 @@ QList EntityItemProperties::listChangedProperties() { out += "tags"; } getGrab().listChangedProperties(out); + if (mirrorModeChanged()) { + out += "mirrorMode"; + } + if (portalExitIDChanged()) { + out += "portalExitID"; + } // Physics if (densityChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 74cd9c4f15..57d3234d90 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -70,6 +70,7 @@ #include "GizmoType.h" #include "TextEffect.h" #include "TextAlignment.h" +#include "MirrorMode.h" class ScriptEngine; @@ -205,6 +206,8 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); DEFINE_PROPERTY_REF(PROP_TAGS, Tags, tags, QSet, QSet()); DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); + DEFINE_PROPERTY_REF_ENUM(PROP_MIRROR_MODE, MirrorMode, mirrorMode, MirrorMode, MirrorMode::NONE); + DEFINE_PROPERTY_REF(PROP_PORTAL_EXIT_ID, PortalExitID, portalExitID, QUuid, UNKNOWN_ENTITY_ID); // Physics DEFINE_PROPERTY(PROP_DENSITY, Density, density, float, ENTITY_ITEM_DEFAULT_DENSITY); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 555ac0a2de..1117e0924f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -61,6 +61,8 @@ enum EntityPropertyList { PROP_GRAB_EQUIPPABLE_INDICATOR_URL, PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, + PROP_MIRROR_MODE, + PROP_PORTAL_EXIT_ID, // Physics PROP_DENSITY, diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index af39458f17..c0116274ee 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -81,6 +81,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_disableContextViewCorrection), (&::gpu::gl::GLBackend::do_restoreContextViewCorrection), + (&::gpu::gl::GLBackend::do_setContextMirrorViewCorrection), (&::gpu::gl::GLBackend::do_disableContextStereo), (&::gpu::gl::GLBackend::do_restoreContextStereo), @@ -348,6 +349,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: case Batch::COMMAND_setProjectionJitter: + case Batch::COMMAND_setContextMirrorViewCorrection: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); @@ -413,6 +415,7 @@ void GLBackend::renderPassDraw(const Batch& batch) { case Batch::COMMAND_setProjectionJitter: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setDepthRangeTransform: + case Batch::COMMAND_setContextMirrorViewCorrection: { PROFILE_RANGE(render_gpu_gl_detail, "transform"); CommandCall call = _commandCalls[(*command)]; @@ -619,6 +622,16 @@ void GLBackend::do_restoreContextViewCorrection(const Batch& batch, size_t param _transform._viewCorrectionEnabled = true; } +void GLBackend::do_setContextMirrorViewCorrection(const Batch& batch, size_t paramOffset) { + bool prevMirrorViewCorrection = _transform._mirrorViewCorrection; + _transform._mirrorViewCorrection = batch._params[paramOffset]._uint != 0; + + if (_transform._correction.correction != glm::mat4()) { + setCameraCorrection(_transform._mirrorViewCorrection ? _transform._flippedCorrection : _transform._unflippedCorrection, _transform._correction.prevView, false); + _transform._invalidView = true; + } +} + void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) { } @@ -997,15 +1010,29 @@ void GLBackend::recycle() const { _textureManagement._transferEngine->manageMemory(); } -void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { +void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool primary, bool reset) { auto invCorrection = glm::inverse(correction); auto invPrevView = glm::inverse(prevRenderView); _transform._correction.prevView = (reset ? Mat4() : prevRenderView); _transform._correction.prevViewInverse = (reset ? Mat4() : invPrevView); _transform._correction.correction = correction; _transform._correction.correctionInverse = invCorrection; - _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); - _pipeline._cameraCorrectionBuffer._buffer->flush(); + + if (!_inRenderTransferPass) { + _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); + _pipeline._cameraCorrectionBuffer._buffer->flush(); + } + + if (primary) { + _transform._unflippedCorrection = _transform._correction.correction; + quat flippedRotation = glm::quat_cast(_transform._unflippedCorrection); + flippedRotation.y *= -1.0f; + flippedRotation.z *= -1.0f; + vec3 flippedTranslation = _transform._unflippedCorrection[3]; + flippedTranslation.x *= -1.0f; + _transform._flippedCorrection = glm::translate(glm::mat4_cast(flippedRotation), flippedTranslation); + _transform._mirrorViewCorrection = false; + } } void GLBackend::syncProgram(const gpu::ShaderPointer& program) { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 2947649ce7..5545858877 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -121,7 +121,7 @@ public: // Shutdown rendering and persist any required resources void shutdown() override; - void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) override; + void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool primary, bool reset = false) override; void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState @@ -211,6 +211,7 @@ public: virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; + virtual void do_setContextMirrorViewCorrection(const Batch& batch, size_t paramOffset) final; virtual void do_disableContextStereo(const Batch& batch, size_t paramOffset) final; virtual void do_restoreContextStereo(const Batch& batch, size_t paramOffset) final; @@ -433,6 +434,9 @@ protected: Transform _view; CameraCorrection _correction; bool _viewCorrectionEnabled{ true }; + mat4 _unflippedCorrection; + mat4 _flippedCorrection; + bool _mirrorViewCorrection{ false }; Mat4 _projection; Vec4i _viewport{ 0, 0, 1, 1 }; diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index e6217cc600..a41f586d74 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -464,6 +464,12 @@ void Batch::restoreContextViewCorrection() { ADD_COMMAND(restoreContextViewCorrection); } +void Batch::setContextMirrorViewCorrection(bool shouldMirror) { + ADD_COMMAND(setContextMirrorViewCorrection); + uint mirrorFlag = shouldMirror ? 1 : 0; + _params.emplace_back(mirrorFlag); +} + void Batch::disableContextStereo() { ADD_COMMAND(disableContextStereo); } diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 0a438ea148..f89dd3ea90 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -239,6 +239,7 @@ public: void disableContextViewCorrection(); void restoreContextViewCorrection(); + void setContextMirrorViewCorrection(bool shouldMirror); void disableContextStereo(); void restoreContextStereo(); @@ -340,6 +341,7 @@ public: COMMAND_disableContextViewCorrection, COMMAND_restoreContextViewCorrection, + COMMAND_setContextMirrorViewCorrection, COMMAND_disableContextStereo, COMMAND_restoreContextStereo, diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 1946f447f8..ebc81f14e9 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -66,7 +66,7 @@ public: virtual void syncProgram(const gpu::ShaderPointer& program) = 0; virtual void recycle() const = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; - virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) {} + virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool primary, bool reset = false) {} virtual bool supportedTextureFormat(const gpu::Element& format) = 0; diff --git a/libraries/gpu/src/gpu/FrameIOKeys.h b/libraries/gpu/src/gpu/FrameIOKeys.h index 1a98d0decd..2d88158afb 100644 --- a/libraries/gpu/src/gpu/FrameIOKeys.h +++ b/libraries/gpu/src/gpu/FrameIOKeys.h @@ -181,6 +181,7 @@ constexpr const char* COMMAND_NAMES[] = { "disableContextViewCorrection", "restoreContextViewCorrection", + "setContextMirrorViewCorrection", "disableContextStereo", "restoreContextStereo", diff --git a/libraries/graphics/src/graphics/ShaderConstants.h b/libraries/graphics/src/graphics/ShaderConstants.h index 3a614d26cd..8fd0df31f0 100644 --- a/libraries/graphics/src/graphics/ShaderConstants.h +++ b/libraries/graphics/src/graphics/ShaderConstants.h @@ -27,6 +27,7 @@ #define GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS 4 #define GRAPHICS_TEXTURE_MATERIAL_OCCLUSION 5 #define GRAPHICS_TEXTURE_MATERIAL_SCATTERING 6 +#define GRAPHICS_TEXTURE_MATERIAL_MIRROR 1 // Mirrors use albedo textures, but nothing else // Make sure these match the ones in render-utils/ShaderConstants.h #define GRAPHICS_TEXTURE_SKYBOX 11 @@ -59,6 +60,7 @@ enum Texture { MaterialRoughness = GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS, MaterialOcclusion = GRAPHICS_TEXTURE_MATERIAL_OCCLUSION, MaterialScattering = GRAPHICS_TEXTURE_MATERIAL_SCATTERING, + MaterialMirror = GRAPHICS_TEXTURE_MATERIAL_MIRROR, Skybox = GRAPHICS_TEXTURE_SKYBOX }; } // namespace texture diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 55e183d00b..2ee8e0f609 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -290,6 +290,7 @@ enum class EntityVersion : PacketVersion { UserAgent, AllBillboardMode, TextAlignment, + Mirror, EntityTags, // Add new versions above here diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 745551eb22..e61bef45e7 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -42,6 +42,7 @@ #include "GizmoType.h" #include "TextEffect.h" #include "TextAlignment.h" +#include "MirrorMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -283,6 +284,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, GizmoType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, TextEffect& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, TextAlignment& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, MirrorMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index dc1ffb7b67..78652bfb09 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -71,14 +71,15 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform _cauterizedTransform = renderTransform; } -void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const { - bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization; +void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const { + bool useCauterizedMesh = _enableCauterization && (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && + mirrorDepth == 0; if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { batch.setUniformBuffer(graphics::slot::buffer::Skinning, _cauterizedClusterBuffer); } batch.setModelTransform(_cauterizedTransform); } else { - ModelMeshPartPayload::bindTransform(batch, transform, renderMode); + ModelMeshPartPayload::bindTransform(batch, transform, renderMode, mirrorDepth); } } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 430f41fc08..cef7b6d9b5 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -25,7 +25,7 @@ public: void updateTransformForCauterizedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning); - void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const override; + void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const override; void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8d7fc345ac..3eb5924d19 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -286,6 +286,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input outputs.edit0() = _deferredFramebuffer; outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); + outputs.edit2() = _deferredFramebuffer->getDeferredFramebuffer(); gpu::doInBatch("PrepareDeferred::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 4779376410..058e0a4cbf 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -78,8 +78,8 @@ class PrepareDeferred { public: // Inputs: primaryFramebuffer and lightingModel using Inputs = render::VaryingSet2 ; - // Output: DeferredFramebuffer, LightingFramebuffer - using Outputs = render::VaryingSet2; + // Output: DeferredFramebuffer, LightingFramebuffer, the framebuffer to be used for mirrors (same as DeferredFramebuffer) + using Outputs = render::VaryingSet3; using JobModel = render::Job::ModelIO; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 5a8b09b018..b4aced626d 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -506,8 +506,9 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren const auto jitter = inputs.getN(4); // Prepare the ShapePipeline - auto shapePlumber = std::make_shared(); - { + static ShapePlumberPointer shapePlumber = std::make_shared(); + static std::once_flag once; + std::call_once(once, [] { auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setColorWriteMask(false, false, false, false); @@ -515,7 +516,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren auto fadeEffect = DependencyManager::get(); initZPassPipelines(*shapePlumber, state, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); - } + }); auto sharedParameters = std::make_shared(); const auto highlightSelectionNames = task.addJob("SelectionToHighlight", sharedParameters); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 8ba5be54e6..9adeb39e7c 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -12,6 +12,7 @@ #include "MeshPartPayload.h" #include +#include #include #include #include @@ -188,7 +189,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } -void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const { +void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const { if (_clusterBuffer) { batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); } @@ -220,6 +221,10 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { builder.withSubMetaCulled(); } + if (_mirrorMode == MirrorMode::MIRROR || (_mirrorMode == MirrorMode::PORTAL && !_portalExitID.isNull())) { + builder.withMirror(); + } + _itemKey = builder.build(); } @@ -299,8 +304,9 @@ Item::Bound ModelMeshPartPayload::getBound(RenderArgs* args) const { auto worldBound = _adjustedLocalBound; auto parentTransform = _parentTransform; if (args) { + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; parentTransform.setRotation(BillboardModeHelpers::getBillboardRotation(parentTransform.getTranslation(), parentTransform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); } worldBound.transform(parentTransform); return worldBound; @@ -313,18 +319,19 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); - if (!args || (args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && _cauterized)) { + if (!args || (_cauterized && args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && args->_mirrorDepth == 0)) { return; } gpu::Batch& batch = *(args->_batch); Transform transform = _parentTransform; + bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); Transform modelTransform = transform.worldTransform(_localTransform); - bindTransform(batch, modelTransform, args->_renderMode); + bindTransform(batch, modelTransform, args->_renderMode, args->_mirrorDepth); //Bind the index buffer and vertex buffer and Blend shapes if needed bindMesh(batch); @@ -346,7 +353,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { procedural->prepare(batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f, _shapeKey.isDeformed(), _shapeKey.isDualQuatSkinned())); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); - } else { + } else if (!_itemKey.isMirror()) { // apply material properties if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) { args->_details._materialSwitches++; @@ -377,6 +384,12 @@ bool ModelMeshPartPayload::passesZoneOcclusionTest(const std::unordered_set& blendshapeBuffers, const QVector& blendedMeshSizes) { if (_meshIndex < blendedMeshSizes.length() && blendedMeshSizes.at(_meshIndex) == _meshNumVertices) { auto blendshapeBuffer = blendshapeBuffers.find(_meshIndex); @@ -426,4 +439,11 @@ template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Poin } return false; } + +template <> ItemID payloadComputeMirrorView(const ModelMeshPartPayload::Pointer& payload, ViewFrustum& viewFrustum) { + if (payload) { + return payload->computeMirrorView(viewFrustum); + } + return Item::INVALID_ITEM_ID; +} } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 1a3a898582..7e331a9497 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -37,7 +37,7 @@ public: // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch); - virtual void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const; + virtual void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const; void drawCall(gpu::Batch& batch) const; void updateKey(const render::ItemKey& key); @@ -58,7 +58,10 @@ public: void setCullWithParent(bool value) { _cullWithParent = value; } void setRenderWithZones(const QVector& renderWithZones) { _renderWithZones = renderWithZones; } void setBillboardMode(BillboardMode billboardMode) { _billboardMode = billboardMode; } + void setMirrorMode(MirrorMode mirrorMode) { _mirrorMode = mirrorMode; } + void setPortalExitID(const QUuid& portalExitID) { _portalExitID = portalExitID; } bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const; + render::ItemID computeMirrorView(ViewFrustum& viewFrustum) const; void addMaterial(graphics::MaterialLayer material) { _drawMaterials.push(material); } void removeMaterial(graphics::MaterialPointer material) { _drawMaterials.remove(material); } @@ -93,6 +96,8 @@ private: bool _cullWithParent { false }; QVector _renderWithZones; BillboardMode _billboardMode { BillboardMode::NONE }; + MirrorMode _mirrorMode { MirrorMode::NONE }; + QUuid _portalExitID; uint64_t _created; Transform _localTransform; @@ -107,6 +112,8 @@ namespace render { template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload); template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set& containingZones); + template <> ItemID payloadComputeMirrorView(const ModelMeshPartPayload::Pointer& payload, ViewFrustum& viewFrustum); + } #endif // hifi_MeshPartPayload_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index eabcabc7e5..7d622ab489 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -232,6 +232,8 @@ void Model::updateRenderItems() { auto renderWithZones = self->getRenderWithZones(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool cauterized = self->isCauterized(); + auto mirrorMode = self->getMirrorMode(); + const QUuid& portalExitID = self->getPortalExitID(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -246,7 +248,7 @@ void Model::updateRenderItems() { transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, invalidatePayloadShapeKey, primitiveMode, billboardMode, renderItemKeyGlobalFlags, - cauterized, renderWithZones](ModelMeshPartPayload& data) { + cauterized, renderWithZones, mirrorMode, portalExitID](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); @@ -260,6 +262,8 @@ void Model::updateRenderItems() { data.setCauterized(cauterized); data.setRenderWithZones(renderWithZones); data.setBillboardMode(billboardMode); + data.setMirrorMode(mirrorMode); + data.setPortalExitID(portalExitID); data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); @@ -1065,6 +1069,46 @@ void Model::setRenderWithZones(const QVector& renderWithZones, const rend } } +void Model::setMirrorMode(MirrorMode mirrorMode, const render::ScenePointer& scene) { + if (_mirrorMode != mirrorMode) { + _mirrorMode = mirrorMode; + if (!scene) { + _needsFixupInScene = true; + return; + } + + render::Transaction transaction; + auto renderItemsKey = _renderItemKeyGlobalFlags; + for (auto item : _modelMeshRenderItemIDs) { + transaction.updateItem(item, [mirrorMode, renderItemsKey](ModelMeshPartPayload& data) { + data.setMirrorMode(mirrorMode); + data.updateKey(renderItemsKey); + }); + } + scene->enqueueTransaction(transaction); + } +} + +void Model::setPortalExitID(const QUuid& portalExitID, const render::ScenePointer& scene) { + if (_portalExitID != portalExitID) { + _portalExitID = portalExitID; + if (!scene) { + _needsFixupInScene = true; + return; + } + + render::Transaction transaction; + auto renderItemsKey = _renderItemKeyGlobalFlags; + for (auto item : _modelMeshRenderItemIDs) { + transaction.updateItem(item, [portalExitID, renderItemsKey](ModelMeshPartPayload& data) { + data.setPortalExitID(portalExitID); + data.updateKey(renderItemsKey); + }); + } + scene->enqueueTransaction(transaction); + } +} + const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { return _renderItemKeyGlobalFlags; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 17b7df9b23..63a96f7253 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -41,6 +41,7 @@ #include "Rig.h" #include "PrimitiveMode.h" #include "BillboardMode.h" +#include "MirrorMode.h" // Use dual quaternion skinning! // Must match define in Skinning.slh @@ -131,6 +132,12 @@ public: void setRenderWithZones(const QVector& renderWithZones, const render::ScenePointer& scene = nullptr); const QVector& getRenderWithZones() const { return _renderWithZones; } + void setMirrorMode(MirrorMode mirrorMode, const render::ScenePointer& scene = nullptr); + MirrorMode getMirrorMode() const { return _mirrorMode; } + + void setPortalExitID(const QUuid& portalExitID, const render::ScenePointer& scene = nullptr); + const QUuid& getPortalExitID() const { return _portalExitID; } + // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. const render::ItemKey getRenderItemKeyGlobalFlags() const; @@ -503,6 +510,8 @@ protected: bool _cauterized { false }; bool _cullWithParent { false }; QVector _renderWithZones; + MirrorMode _mirrorMode { MirrorMode::NONE }; + QUuid _portalExitID; bool shouldInvalidatePayloadShapeKey(int meshIndex); diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 7cf7f1129f..a509ede437 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -13,7 +13,10 @@ #include "render-utils/ShaderConstants.h" #include "DeferredLightingEffect.h" +#include "FadeEffect.h" #include "RenderUtilsLogging.h" +#include "RenderViewTask.h" +#include "StencilMaskPass.h" namespace ru { using render_utils::slot::texture::Texture; @@ -25,9 +28,11 @@ namespace gr { using graphics::slot::buffer::Buffer; } +using RenderArgsPointer = std::shared_ptr; using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); +extern void initMirrorPipelines(ShapePlumber& plumber, gpu::StatePointer state, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter, bool forward); void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { timer = _gpuTimer; @@ -45,10 +50,14 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); } +render::ShapePlumberPointer DrawLayered3D::_shapePlumber = std::make_shared(); + DrawLayered3D::DrawLayered3D(bool opaque) : - _shapePlumber(std::make_shared()), _opaquePass(opaque) { - initForwardPipelines(*_shapePlumber); + static std::once_flag once; + std::call_once(once, [] { + initForwardPipelines(*_shapePlumber); + }); } void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { @@ -262,3 +271,149 @@ void ResolveFramebuffer::run(const render::RenderContextPointer& renderContext, } } +class SetupMirrorTask { +public: + using Input = RenderMirrorTask::Inputs; + using Outputs = render::VaryingSet4; + using JobModel = render::Job::ModelIO; + + SetupMirrorTask(size_t mirrorIndex, size_t depth) : _mirrorIndex(mirrorIndex), _depth(depth) {} + + void run(const render::RenderContextPointer& renderContext, const Input& inputs, Outputs& outputs) { + auto args = renderContext->args; + auto items = inputs.get0(); + + if (items.empty() || _mirrorIndex > items.size() - 1) { + renderContext->taskFlow.abortTask(); + return; + } + + auto inputFramebuffer = inputs.get1(); + if (!_mirrorFramebuffer || _mirrorFramebuffer->getWidth() != inputFramebuffer->getWidth() || _mirrorFramebuffer->getHeight() != inputFramebuffer->getHeight()) { + _mirrorFramebuffer.reset(gpu::Framebuffer::create("mirror" + _mirrorIndex, gpu::Element::COLOR_SRGBA_32, inputFramebuffer->getWidth(), inputFramebuffer->getHeight())); + } + + render::ItemBound mirror = items[_mirrorIndex]; + + _cachedArgsPointer->_renderMode = args->_renderMode; + _cachedArgsPointer->_blitFramebuffer = args->_blitFramebuffer; + _cachedArgsPointer->_ignoreItem = args->_ignoreItem; + _cachedArgsPointer->_mirrorDepth = args->_mirrorDepth; + _cachedArgsPointer->_numMirrorFlips = args->_numMirrorFlips; + + ViewFrustum srcViewFrustum = args->getViewFrustum(); + ItemID portalExitID = args->_scene->getItem(mirror.id).computeMirrorView(srcViewFrustum); + + args->_blitFramebuffer = _mirrorFramebuffer; + args->_ignoreItem = portalExitID != Item::INVALID_ITEM_ID ? portalExitID : mirror.id; + args->_mirrorDepth = _depth; + args->_numMirrorFlips += portalExitID != Item::INVALID_ITEM_ID ? 0 : 1; + + gpu::doInBatch("SetupMirrorTask::run", args->_context, [&](gpu::Batch& batch) { + bool shouldMirror = args->_numMirrorFlips % 2 == (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE); + batch.setContextMirrorViewCorrection(shouldMirror); + }); + + // Without calculating the bound planes, the mirror will use the same culling frustum as the main camera, + // which is not what we want here. + srcViewFrustum.calculate(); + args->pushViewFrustum(srcViewFrustum); + + outputs.edit0() = mirror; + outputs.edit1() = inputFramebuffer; + outputs.edit2() = _cachedArgsPointer; + outputs.edit3() = inputs.get2(); + } + +protected: + gpu::FramebufferPointer _mirrorFramebuffer { nullptr }; + RenderArgsPointer _cachedArgsPointer { std::make_shared() }; + size_t _mirrorIndex; + size_t _depth; + +}; + +class DrawMirrorTask { +public: + using Inputs = SetupMirrorTask::Outputs; + using JobModel = render::Job::ModelI; + + DrawMirrorTask() { + static std::once_flag once; + std::call_once(once, [this] { + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMaskDrawShape(*state); + + auto fadeEffect = DependencyManager::get(); + initMirrorPipelines(*_forwardPipelines, state, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter(), true); + initMirrorPipelines(*_deferredPipelines, state, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter(), false); + }); + } + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + auto args = renderContext->args; + auto mirror = inputs.get0(); + auto framebuffer = inputs.get1(); + auto cachedArgs = inputs.get2(); + auto jitter = inputs.get3(); + + if (cachedArgs) { + args->_renderMode = cachedArgs->_renderMode; + } + args->popViewFrustum(); + + gpu::doInBatch("DrawMirrorTask::run", args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + if (cachedArgs) { + bool shouldMirror = cachedArgs->_numMirrorFlips % 2 == (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE); + batch.setContextMirrorViewCorrection(shouldMirror); + } + + batch.setFramebuffer(framebuffer); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setProjectionJitter(jitter.x, jitter.y); + batch.setViewTransform(viewMat); + + batch.setResourceTexture(gr::Texture::MaterialMirror, args->_blitFramebuffer->getRenderBuffer(0)); + + renderShapes(renderContext, args->_renderMethod == render::Args::RenderMethod::FORWARD ? _forwardPipelines : _deferredPipelines, { mirror }); + + args->_batch = nullptr; + }); + + if (cachedArgs) { + // Restore the blit framebuffer after we've sampled from it + args->_blitFramebuffer = cachedArgs->_blitFramebuffer; + args->_ignoreItem = cachedArgs->_ignoreItem; + args->_mirrorDepth = cachedArgs->_mirrorDepth; + args->_numMirrorFlips = cachedArgs->_numMirrorFlips; + } + } + +private: + static ShapePlumberPointer _forwardPipelines; + static ShapePlumberPointer _deferredPipelines; +}; + +ShapePlumberPointer DrawMirrorTask::_forwardPipelines = std::make_shared(); +ShapePlumberPointer DrawMirrorTask::_deferredPipelines = std::make_shared(); + + void RenderMirrorTask::build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, size_t depth) { + size_t nextDepth = depth + 1; + const auto setupOutput = task.addJob("SetupMirror" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), inputs, mirrorIndex, nextDepth); + + task.addJob("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1, nextDepth); + + task.addJob("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput); + } diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 15d6ff9895..cada3cb6ea 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -10,6 +10,8 @@ #define hifi_RenderCommonTask_h #include +#include + #include "LightStage.h" #include "HazeStage.h" #include "LightingModel.h" @@ -71,7 +73,7 @@ public: void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: - render::ShapePlumberPointer _shapePlumber; + static render::ShapePlumberPointer _shapePlumber; int _maxDrawn; // initialized by Config bool _opaquePass { true }; }; @@ -152,4 +154,17 @@ protected: render::Args::RenderMethod _method; }; +class RenderMirrorTask { +public: + using Inputs = render::VaryingSet3; + using JobModel = render::Task::ModelI; + + RenderMirrorTask() {} + + void build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, size_t depth); + + static const size_t MAX_MIRROR_DEPTH { 3 }; + static const size_t MAX_MIRRORS_PER_LEVEL { 3 }; +}; + #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index c506f22bc7..fd10452dfa 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -100,11 +100,14 @@ void RenderDeferredTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { - auto fadeEffect = DependencyManager::get(); +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, size_t depth) { + static auto fadeEffect = DependencyManager::get(); // Prepare the ShapePipelines - ShapePlumberPointer shapePlumber = std::make_shared(); - initDeferredPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + static ShapePlumberPointer shapePlumber = std::make_shared(); + static std::once_flag once; + std::call_once(once, [] { + initDeferredPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + }); const auto& inputs = input.get(); @@ -116,6 +119,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Extract opaques / transparents / lights / metas / layered / background const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& mirrors = items[RenderFetchCullSortTask::MIRROR]; const auto& inFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; const auto& inFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; const auto& hudOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; @@ -139,7 +143,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Shadow Stage Frame const auto shadowFrame = shadowTaskOutputs[1]; - fadeEffect->build(task, opaques); const auto jitter = task.addJob("JitterCam"); @@ -154,6 +157,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto prepareDeferredOutputs = task.addJob("PrepareDeferred", prepareDeferredInputs); const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); + const auto mirrorTargetFramebuffer = prepareDeferredOutputs.getN(2); // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", scaledPrimaryFramebuffer); @@ -162,6 +166,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); + if (depth < RenderMirrorTask::MAX_MIRROR_DEPTH) { + const auto mirrorInputs = RenderMirrorTask::Inputs(mirrors, mirrorTargetFramebuffer, jitter).asVarying(); + for (size_t i = 0; i < RenderMirrorTask::MAX_MIRRORS_PER_LEVEL; i++) { + task.addJob("RenderMirrorTask" + std::to_string(i) + "Depth" + std::to_string(depth), mirrorInputs, i, cullFunctor, depth); + } + } + // Opaque all rendered // Linear Depth Pass @@ -234,7 +245,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Lighting Buffer ready for tone mapping const auto toneMappingInputs = ToneMapAndResample::Input(lightingFramebuffer, destFramebuffer).asVarying(); - const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs); + const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs, depth); // Debugging task is happening in the "over" layer after tone mapping and just before HUD { // Debug the bounds of the rendered items, still look at the zbuffer @@ -398,8 +409,12 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input // Status icon rendering job { // Grab a texture map representing the different status icons and assign that to the drawStatusJob - auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; - auto statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); + static gpu::TexturePointer statusIconMap; + static std::once_flag once; + std::call_once(once, [] { + auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; + statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); + }); const auto drawStatusInputs = DrawStatus::Input(opaques, jitter).asVarying(); task.addJob("DrawStatus", drawStatusInputs, DrawStatus(statusIconMap)); } @@ -407,8 +422,6 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input const auto debugZoneInputs = DebugZoneLighting::Inputs(deferredFrameTransform, lightFrame, backgroundFrame).asVarying(); task.addJob("DrawZoneStack", debugZoneInputs); } - - } gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) { diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 969094488e..5fc9580981 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -144,7 +144,7 @@ public: RenderDeferredTask(); void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output); + void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, size_t depth); private: }; diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index e34db755b7..722ba2248c 100644 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -34,6 +34,8 @@ #include "RenderCommonTask.h" #include "RenderHUDLayerTask.h" +#include "RenderViewTask.h" + namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; @@ -66,13 +68,16 @@ void RenderForwardTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, size_t depth) { task.addJob("SetRenderMethodTask", render::Args::FORWARD); // Prepare the ShapePipelines auto fadeEffect = DependencyManager::get(); - ShapePlumberPointer shapePlumber = std::make_shared(); - initForwardPipelines(*shapePlumber); + static ShapePlumberPointer shapePlumber = std::make_shared(); + static std::once_flag once; + std::call_once(once, [] { + initForwardPipelines(*shapePlumber); + }); // Unpack inputs const auto& inputs = input.get(); @@ -86,6 +91,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; const auto& metas = items[RenderFetchCullSortTask::META]; + const auto& mirrors = items[RenderFetchCullSortTask::MIRROR]; const auto& inFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; const auto& inFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; const auto& hudOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; @@ -107,7 +113,6 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // First job, alter faded fadeEffect->build(task, opaques); - // GPU jobs: Start preparing the main framebuffer const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBufferForward"); @@ -125,6 +130,16 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel, hazeFrame).asVarying(); task.addJob("DrawOpaques", opaqueInputs, shapePlumber, true); + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); +#ifndef Q_OS_ANDROID + if (depth < RenderMirrorTask::MAX_MIRROR_DEPTH) { + const auto mirrorInputs = RenderMirrorTask::Inputs(mirrors, scaledPrimaryFramebuffer, nullJitter).asVarying(); + for (size_t i = 0; i < RenderMirrorTask::MAX_MIRRORS_PER_LEVEL; i++) { + task.addJob("RenderMirrorTask" + std::to_string(i) + "Depth" + std::to_string(depth), mirrorInputs, i, cullFunctor, depth); + } + } +#endif + // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job const auto backgroundInputs = DrawBackgroundStage::Inputs(lightingModel, backgroundFrame, hazeFrame).asVarying(); task.addJob("DrawBackgroundForward", backgroundInputs); @@ -134,7 +149,6 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend task.addJob("DrawTransparents", transparentInputs, shapePlumber, false); // Layered - const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, nullJitter).asVarying(); const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, hazeFrame, nullJitter).asVarying(); task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); @@ -159,7 +173,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto destFramebuffer = static_cast(nullptr); const auto toneMappingInputs = ToneMapAndResample::Input(resolvedFramebuffer, destFramebuffer).asVarying(); - const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs); + const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs, depth); // HUD Layer const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying(); task.addJob("RenderHUDLayer", renderHUDLayerInputs); diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 6833e42449..de3a6dd205 100644 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -36,7 +36,7 @@ public: RenderForwardTask() {} void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output); + void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, size_t depth); }; diff --git a/libraries/render-utils/src/RenderHUDLayerTask.cpp b/libraries/render-utils/src/RenderHUDLayerTask.cpp index 743e59eebc..8fee3d57bc 100644 --- a/libraries/render-utils/src/RenderHUDLayerTask.cpp +++ b/libraries/render-utils/src/RenderHUDLayerTask.cpp @@ -16,8 +16,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra assert(renderContext->args); assert(renderContext->args->_context); - // We do not want to render HUD elements in secondary camera - if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { + // We do not want to render HUD elements in secondary camera or mirrors + if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE || renderContext->args->_mirrorDepth > 0) { return; } diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ed9fb326c4..b5a88b0ba9 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -42,6 +42,7 @@ namespace gr { void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initForwardPipelines(ShapePlumber& plumber); void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +void initMirrorPipelines(ShapePlumber& plumber, gpu::StatePointer state, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter, bool forward); void addPlumberPipeline(ShapePlumber& plumber, const ShapeKey& key, int programId, @@ -368,6 +369,45 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con gpu::Shader::createProgram(model_shadow_fade_deformeddq), state, extraBatchSetter, itemSetter); } +void initMirrorPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, const render::ShapePipeline::BatchSetter& extraBatchSetter, const render::ShapePipeline::ItemSetter& itemSetter, bool forward) { + using namespace shader::render_utils::program; + + if (forward) { + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutDeformed().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror_forward), state); + + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withoutDualQuatSkinned().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror_forward_deformed), state); + + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withDualQuatSkinned().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror_forward_deformeddq), state); + } else { + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutDeformed().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror), state); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutDeformed().withFade(), + gpu::Shader::createProgram(model_shadow_mirror_fade), state, extraBatchSetter, itemSetter); + + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withoutDualQuatSkinned().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror_deformed), state); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withoutDualQuatSkinned().withFade(), + gpu::Shader::createProgram(model_shadow_mirror_fade_deformed), state, extraBatchSetter, itemSetter); + + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withDualQuatSkinned().withoutFade(), + gpu::Shader::createProgram(model_shadow_mirror_deformeddq), state); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withDeformed().withDualQuatSkinned().withFade(), + gpu::Shader::createProgram(model_shadow_mirror_fade_deformeddq), state, extraBatchSetter, itemSetter); + } +} + bool RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures) { graphics::MultiMaterial multiMaterial; multiMaterial.push(graphics::MaterialLayer(material, 0)); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index dbfa23a143..9fabad5fd0 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -46,15 +46,16 @@ void RenderShadowTask::configure(const Config& configuration) { void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) { // Prepare the ShapePipeline - ShapePlumberPointer shapePlumber = std::make_shared(); - { + static ShapePlumberPointer shapePlumber = std::make_shared(); + static std::once_flag once; + std::call_once(once, [] { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); auto fadeEffect = DependencyManager::get(); initZPassPipelines(*shapePlumber, state, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); - } + }); const auto setupOutput = task.addJob("ShadowSetup", input); const auto queryResolution = setupOutput.getN(1); const auto shadowFrame = setupOutput.getN(3); diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 93a3ff2d67..b9320b6ad3 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -15,7 +15,7 @@ #include "RenderDeferredTask.h" #include "RenderForwardTask.h" -void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { +void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, size_t depth) { task.addJob("SetRenderMethodTask", render::Args::DEFERRED); const auto items = input.getN(0); @@ -28,16 +28,16 @@ void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& const auto shadowTaskOut = task.addJob("RenderShadowTask", shadowTaskIn, cullFunctor, tagBits, tagMask); const auto renderDeferredInput = RenderDeferredTask::Input(items, lightingModel, lightingStageFramesAndZones, shadowTaskOut).asVarying(); - task.addJob("RenderDeferredTask", renderDeferredInput); + task.addJob("RenderDeferredTask", renderDeferredInput, cullFunctor, depth); } -void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { - task.addBranch("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask); +void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, size_t depth) { + task.addBranch("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask, depth); - task.addBranch("RenderForwardTask", 1, input); + task.addBranch("RenderForwardTask", 1, input, cullFunctor, depth); } -void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { +void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, size_t depth) { const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); // Issue the lighting model, aka the big global settings for the view @@ -48,7 +48,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: #ifndef Q_OS_ANDROID const auto deferredForwardIn = DeferredForwardSwitchJob::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); - task.addJob("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask); + task.addJob("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask, depth); #else const auto renderInput = RenderForwardTask::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); task.addJob("RenderForwardTask", renderInput); diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h index cdb56a2189..139d00125e 100644 --- a/libraries/render-utils/src/RenderViewTask.h +++ b/libraries/render-utils/src/RenderViewTask.h @@ -24,7 +24,7 @@ public: RenderShadowsAndDeferredTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, size_t depth); }; @@ -36,7 +36,7 @@ public: DeferredForwardSwitchJob() {} void configure(const render::SwitchConfig& config) {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, size_t depth); }; @@ -47,7 +47,7 @@ public: RenderViewTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00, size_t depth = 0); }; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 76d8374fb7..8ab1b8e0e9 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -40,21 +40,18 @@ float TextRenderer3D::getFontSize() const { return 0.0f; } -void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, - const QString& str, const glm::vec4& color, bool unlit, bool forward) { +void TextRenderer3D::draw(gpu::Batch& batch, const Font::DrawProps& props) { if (_font) { - _font->drawString(batch, _drawInfo, str, color, glm::vec3(0.0f), 0, TextEffect::NO_EFFECT, TextAlignment::LEFT, { x, y }, bounds, 1.0f, unlit, forward); + _font->drawString(batch, _drawInfo, props); } } -void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, - const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, - float effectThickness, TextEffect effect, TextAlignment alignment, bool unlit, bool forward) { +void TextRenderer3D::draw(gpu::Batch& batch, const QString& font, const Font::DrawProps& props) { if (font != _family) { _family = font; _font = Font::load(_family); } if (_font) { - _font->drawString(batch, _drawInfo, str, color, effectColor, effectThickness, effect, alignment, { x, y }, bounds, scale, unlit, forward); + _font->drawString(batch, _drawInfo, props); } } \ No newline at end of file diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index edccf1429c..9db93e9dcc 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -26,12 +26,9 @@ public: glm::vec2 computeExtent(const QString& str) const; float getFontSize() const; // Pixel size - - void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, - const QString& str, const glm::vec4& color, bool unlit, bool forward); - void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, - const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, - float effectThickness, TextEffect effect, TextAlignment alignment, bool unlit, bool forward); + + void draw(gpu::Batch& batch, const Font::DrawProps& props); + void draw(gpu::Batch& batch, const QString& font, const Font::DrawProps& props); private: TextRenderer3D(const char* family); diff --git a/libraries/render-utils/src/ToneMapAndResampleTask.cpp b/libraries/render-utils/src/ToneMapAndResampleTask.cpp index 10312f7f2e..1ea9deb1fa 100644 --- a/libraries/render-utils/src/ToneMapAndResampleTask.cpp +++ b/libraries/render-utils/src/ToneMapAndResampleTask.cpp @@ -25,9 +25,10 @@ using namespace shader::render_utils::program; gpu::PipelinePointer ToneMapAndResample::_pipeline; gpu::PipelinePointer ToneMapAndResample::_mirrorPipeline; -ToneMapAndResample::ToneMapAndResample() { +ToneMapAndResample::ToneMapAndResample(size_t depth) { Parameters parameters; _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); + _depth = depth; } void ToneMapAndResample::init() { @@ -95,7 +96,8 @@ void ToneMapAndResample::run(const RenderContextPointer& renderContext, const In batch.setViewportTransform(destViewport); batch.setProjectionTransform(glm::mat4()); batch.resetViewTransform(); - batch.setPipeline(args->_renderMode == RenderArgs::MIRROR_RENDER_MODE ? _mirrorPipeline : _pipeline); + bool shouldMirror = args->_numMirrorFlips >= (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE); + batch.setPipeline(shouldMirror ? _mirrorPipeline : _pipeline); batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(srcBufferSize, args->_viewport)); batch.setUniformBuffer(render_utils::slot::buffer::ToneMappingParams, _parametersBuffer); diff --git a/libraries/render-utils/src/ToneMapAndResampleTask.h b/libraries/render-utils/src/ToneMapAndResampleTask.h index 1c7ef2cf48..3a812cf445 100644 --- a/libraries/render-utils/src/ToneMapAndResampleTask.h +++ b/libraries/render-utils/src/ToneMapAndResampleTask.h @@ -49,11 +49,9 @@ signals: class ToneMapAndResample { public: - ToneMapAndResample(); + ToneMapAndResample(size_t depth); virtual ~ToneMapAndResample() {} - void render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, gpu::FramebufferPointer& destinationBuffer); - void setExposure(float exposure); float getExposure() const { return _parametersBuffer.get()._exposure; } @@ -75,7 +73,8 @@ protected: gpu::FramebufferPointer _destinationFrameBuffer; - float _factor{ 2.0f }; + float _factor { 2.0f }; + size_t _depth { 0 }; gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer); diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 98abc29d8c..2f80fbde99 100644 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -36,7 +36,15 @@ <@include DeferredBufferWrite.slh@> <@endif@> <@else@> - layout(location=0) out vec4 _fragColor0; + <@if HIFI_USE_MIRROR@> + <@if HIFI_USE_FORWARD@> + layout(location=0) out vec4 _fragColor0; + <@else@> + <@include DeferredBufferWrite.slh@> + <@endif@> + <@else@> + layout(location=0) out vec4 _fragColor0; + <@endif@> <@endif@> <@if HIFI_USE_UNLIT@> @@ -45,6 +53,9 @@ <@if HIFI_USE_SHADOW or HIFI_USE_UNLIT@> <$declareMaterialTextures(ALBEDO)$> + <@if HIFI_USE_MIRROR@> + LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_MIRROR) uniform sampler2D mirrorMap; + <@endif@> <@else@> <@if not HIFI_USE_LIGHTMAP@> <@if HIFI_USE_NORMALMAP and HIFI_USE_TRANSLUCENT@> @@ -124,7 +135,14 @@ void main(void) { <@endif@> <@endif@> - <@if HIFI_USE_SHADOW@> + <@if HIFI_USE_MIRROR@> + vec3 mirrorColor = texelFetch(mirrorMap, ivec2(gl_FragCoord.xy), 0).rgb; + <@if HIFI_USE_FORWARD@> + _fragColor0 = vec4(mirrorColor, 1.0); + <@else@> + packDeferredFragmentUnlit(vec3(1.0, 0.0, 0.0), 1.0, mirrorColor); + <@endif@> + <@elif HIFI_USE_SHADOW@> _fragColor0 = vec4(1.0); <@elif HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> _fragColor0 = vec4(albedo * isUnlitEnabled(), opacity); diff --git a/libraries/render-utils/src/render-utils/model.slp b/libraries/render-utils/src/render-utils/model.slp index b63ec898eb..a3c28631e9 100644 --- a/libraries/render-utils/src/render-utils/model.slp +++ b/libraries/render-utils/src/render-utils/model.slp @@ -1 +1 @@ -DEFINES (normalmap translucent:f unlit:f/lightmap:f)/shadow fade:f/forward:f deformed:v/deformeddq:v \ No newline at end of file +DEFINES (normalmap translucent:f unlit:f/lightmap:f)/(shadow mirror:f) fade:f/forward:f deformed:v/deformeddq:v \ No newline at end of file diff --git a/libraries/render-utils/src/render-utils/sdf_text3D.slp b/libraries/render-utils/src/render-utils/sdf_text3D.slp index 118135d099..f3f9af59aa 100644 --- a/libraries/render-utils/src/render-utils/sdf_text3D.slp +++ b/libraries/render-utils/src/render-utils/sdf_text3D.slp @@ -1 +1 @@ -DEFINES (translucent unlit:f)/forward \ No newline at end of file +DEFINES (translucent unlit:f)/forward mirror:f \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index c5bed1ecab..bf9bb0babd 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -41,6 +41,12 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord1 _texCoord01.zw layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reusing the fade texcoord locations here +<@if HIFI_USE_MIRROR@> + <@include graphics/ShaderConstants.h@> + + LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_MIRROR) uniform sampler2D mirrorMap; +<@endif@> + void main() { vec4 color = evalSDFSuperSampled(_texCoord0, _glyphBounds); @@ -51,6 +57,11 @@ void main() { } <@endif@> +<@if HIFI_USE_MIRROR@> + color.rgb = texelFetch(mirrorMap, ivec2(gl_FragCoord.xy), 0).rgb; + color.a = 1.0; +<@endif@> + <@if HIFI_USE_UNLIT@> <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> _fragColor0 = vec4(color.rgb * isUnlitEnabled(), color.a); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 81cdaa51c9..dd7074a071 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -29,7 +29,7 @@ static std::mutex fontMutex; -std::map, gpu::PipelinePointer> Font::_pipelines; +std::map, gpu::PipelinePointer> Font::_pipelines; gpu::Stream::FormatPointer Font::_format; struct TextureVertex { @@ -277,6 +277,7 @@ void Font::setupGPU() { if (_pipelines.empty()) { using namespace shader::render_utils::program; + // transparent, unlit, forward static const std::vector> keys = { std::make_tuple(false, false, false, sdf_text3D), std::make_tuple(true, false, false, sdf_text3D_translucent), std::make_tuple(false, true, false, sdf_text3D_unlit), std::make_tuple(true, true, false, sdf_text3D_translucent_unlit), @@ -284,18 +285,23 @@ void Font::setupGPU() { std::make_tuple(false, true, true, sdf_text3D_translucent_unlit/*sdf_text3D_unlit_forward*/), std::make_tuple(true, true, true, sdf_text3D_translucent_unlit/*sdf_text3D_translucent_unlit_forward*/) }; for (auto& key : keys) { + bool transparent = std::get<0>(key); + bool unlit = std::get<1>(key); + bool forward = std::get<2>(key); + auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL); - state->setBlendFunction(std::get<0>(key), + state->setDepthTest(true, !transparent, gpu::LESS_EQUAL); + state->setBlendFunction(transparent, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - if (std::get<0>(key)) { + if (transparent) { PrepareStencil::testMask(*state); } else { PrepareStencil::testMaskDrawShape(*state); } - _pipelines[std::make_tuple(std::get<0>(key), std::get<1>(key), std::get<2>(key))] = gpu::Pipeline::create(gpu::Shader::createProgram(std::get<3>(key)), state); + _pipelines[std::make_tuple(transparent, unlit, forward, false)] = gpu::Pipeline::create(gpu::Shader::createProgram(std::get<3>(key)), state); + _pipelines[std::make_tuple(transparent, unlit, forward, true)] = gpu::Pipeline::create(gpu::Shader::createProgram(forward ? sdf_text3D_forward_mirror : sdf_text3D_mirror), state); } // Sanity checks @@ -444,32 +450,30 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } } -void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - const glm::vec3& effectColor, float effectThickness, TextEffect effect, TextAlignment alignment, - const glm::vec2& origin, const glm::vec2& bounds, float scale, bool unlit, bool forward) { - if (!_loaded || str == "") { +void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const DrawProps& props) { + if (!_loaded || props.str == "") { return; } - int textEffect = (int)effect; + int textEffect = (int)props.effect; const int SHADOW_EFFECT = (int)TextEffect::SHADOW_EFFECT; // If we're switching to or from shadow effect mode, we need to rebuild the vertices - if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin || alignment != _alignment || + if (props.str != drawInfo.string || props.bounds != drawInfo.bounds || props.origin != drawInfo.origin || props.alignment != _alignment || (drawInfo.params.effect != textEffect && (textEffect == SHADOW_EFFECT || drawInfo.params.effect == SHADOW_EFFECT)) || - (textEffect == SHADOW_EFFECT && scale != _scale)) { - _scale = scale; - _alignment = alignment; - buildVertices(drawInfo, str, origin, bounds, scale, textEffect == SHADOW_EFFECT, alignment); + (textEffect == SHADOW_EFFECT && props.scale != _scale)) { + _scale = props.scale; + _alignment = props.alignment; + buildVertices(drawInfo, props.str, props.origin, props.bounds, props.scale, textEffect == SHADOW_EFFECT, props.alignment); } setupGPU(); - if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effectColor != effectColor || - drawInfo.params.effectThickness != effectThickness || drawInfo.params.effect != textEffect) { - drawInfo.params.color = color; - drawInfo.params.effectColor = effectColor; - drawInfo.params.effectThickness = effectThickness; + if (!drawInfo.paramsBuffer || drawInfo.params.color != props.color || drawInfo.params.effectColor != props.effectColor || + drawInfo.params.effectThickness != props.effectThickness || drawInfo.params.effect != textEffect) { + drawInfo.params.color = props.color; + drawInfo.params.effectColor = props.effectColor; + drawInfo.params.effectThickness = props.effectThickness; drawInfo.params.effect = textEffect; // need the gamma corrected color here @@ -484,7 +488,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString drawInfo.paramsBuffer->setSubData(0, sizeof(DrawParams), (const gpu::Byte*)&gpuDrawParams); } - batch.setPipeline(_pipelines[std::make_tuple(color.a < 1.0f, unlit, forward)]); + batch.setPipeline(_pipelines[std::make_tuple(props.color.a < 1.0f, props.unlit, props.forward, props.mirror)]); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 322e96439e..e8a353a686 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -57,10 +57,30 @@ public: glm::vec2 computeExtent(const QString& str) const; float getFontSize() const { return _fontSize; } + struct DrawProps { + DrawProps(const QString& str, const glm::vec4& color, const glm::vec3& effectColor, const glm::vec2& origin, const glm::vec2& bounds, + float scale, float effectThickness, TextEffect effect, TextAlignment alignment, bool unlit, bool forward, bool mirror) : + str(str), color(color), effectColor(effectColor), origin(origin), bounds(bounds), scale(scale), effectThickness(effectThickness), + effect(effect), alignment(alignment), unlit(unlit), forward(forward), mirror(mirror) {} + DrawProps(const QString& str, const glm::vec4& color, const glm::vec2& origin, const glm::vec2& bounds, bool forward) : + str(str), color(color), origin(origin), bounds(bounds), forward(forward) {} + + const QString& str; + const glm::vec4& color; + const glm::vec3& effectColor { glm::vec3(0.0f) }; + const glm::vec2& origin; + const glm::vec2& bounds; + float scale { 1.0f }; + float effectThickness { 0.0f }; + TextEffect effect { TextEffect::NO_EFFECT }; + TextAlignment alignment { TextAlignment::LEFT }; + bool unlit = true; + bool forward; + bool mirror = false; + }; + // Render string to batch - void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - const glm::vec3& effectColor, float effectThickness, TextEffect effect, TextAlignment alignment, - const glm::vec2& origin, const glm::vec2& bound, float scale, bool unlit, bool forward); + void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const DrawProps& props); static Pointer load(const QString& family); @@ -105,7 +125,7 @@ private: gpu::TexturePointer _texture; gpu::BufferStreamPointer _stream; - static std::map, gpu::PipelinePointer> _pipelines; + static std::map, gpu::PipelinePointer> _pipelines; static gpu::Stream::FormatPointer _format; }; diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index d09b0d2f2f..fb7d698672 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -159,6 +159,10 @@ namespace render { bool _takingSnapshot { false }; StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE }; std::function _stencilMaskOperator; + + ItemID _ignoreItem { 0 }; + size_t _mirrorDepth { 0 }; + size_t _numMirrorFlips { 0 }; }; } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 039cf01c86..cd69114bbf 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -82,7 +82,7 @@ void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const outItems.reserve(items.size()); for (auto& id : items) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) { outItems.emplace_back(ItemBound(id, item.getBound(renderContext->args))); } } @@ -190,7 +190,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("insideFitItems"); for (auto id : inSelection.insideItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { @@ -205,7 +205,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("insideSmallItems"); for (auto id : inSelection.insideSubcellItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { @@ -220,7 +220,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("partialFitItems"); for (auto id : inSelection.partialItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { @@ -235,7 +235,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("partialSmallItems"); for (auto id : inSelection.partialSubcellItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { @@ -252,7 +252,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("insideFitItems"); for (auto id : inSelection.insideItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { @@ -267,7 +267,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("insideSmallItems"); for (auto id : inSelection.insideSubcellItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); @@ -284,7 +284,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("partialFitItems"); for (auto id : inSelection.partialItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); if (test.frustumTest(itemBound.bound)) { outItems.emplace_back(itemBound); @@ -301,7 +301,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, PerformanceTimer perfTimer("partialSmallItems"); for (auto id : inSelection.partialSubcellItems) { auto& item = scene->getItem(id); - if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { + if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { ItemBound itemBound(id, item.getBound(args)); if (test.frustumTest(itemBound.bound) && test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); @@ -325,73 +325,6 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, std::static_pointer_cast(renderContext->jobConfig)->numItems = (int)outItems.size(); } -void CullShapeBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - RenderArgs* args = renderContext->args; - - const auto& inShapes = inputs.get0(); - const auto& cullFilter = inputs.get1(); - const auto& boundsFilter = inputs.get2(); - ViewFrustumPointer antiFrustum; - auto& outShapes = outputs.edit0(); - auto& outBounds = outputs.edit1(); - - if (!inputs[3].isNull()) { - antiFrustum = inputs.get3(); - } - outShapes.clear(); - outBounds = AABox(); - - if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) { - auto& details = args->_details.edit(_detailType); - CullTest test(_cullFunctor, args, details, antiFrustum); - auto scene = args->_scene; - - for (auto& inItems : inShapes) { - auto key = inItems.first; - auto outItems = outShapes.find(key); - if (outItems == outShapes.end()) { - outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first; - outItems->second.reserve(inItems.second.size()); - } - - details._considered += (int)inItems.second.size(); - - if (antiFrustum == nullptr) { - for (auto& item : inItems.second) { - if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { - const auto shapeKey = scene->getItem(item.id).getKey(); - if (cullFilter.test(shapeKey)) { - outItems->second.emplace_back(item); - } - if (boundsFilter.test(shapeKey)) { - outBounds += item.bound; - } - } - } - } else { - for (auto& item : inItems.second) { - if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { - const auto shapeKey = scene->getItem(item.id).getKey(); - if (cullFilter.test(shapeKey)) { - outItems->second.emplace_back(item); - } - if (boundsFilter.test(shapeKey)) { - outBounds += item.bound; - } - } - } - } - details._rendered += (int)outItems->second.size(); - } - - for (auto& items : outShapes) { - items.second.shrink_to_fit(); - } - } -} - void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 9a7466223d..9e214fd988 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -121,29 +121,6 @@ namespace render { void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; - class CullShapeBounds { - public: - using Inputs = render::VaryingSet4; - using Outputs = render::VaryingSet2; - using JobModel = Job::ModelIO; - - CullShapeBounds(CullFunctor cullFunctor, RenderDetails::Type type) : - _cullFunctor{ cullFunctor }, - _detailType(type) {} - - CullShapeBounds(CullFunctor cullFunctor) : - _cullFunctor{ cullFunctor } { - } - - void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); - - private: - - CullFunctor _cullFunctor; - RenderDetails::Type _detailType{ RenderDetails::OTHER }; - - }; - class ApplyCullFunctorOnItemBounds { public: using Inputs = render::VaryingSet2; diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index 369f227566..1633523267 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -160,4 +160,11 @@ namespace render { } return payload->passesZoneOcclusionTest(containingZones); } + + template <> ItemID payloadComputeMirrorView(const PayloadProxyInterface::Pointer& payload, ViewFrustum& viewFrustum) { + if (!payload) { + return Item::INVALID_ITEM_ID; + } + return payload->computeMirrorView(viewFrustum); + } } diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 5952be8a84..f91b887fcb 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -110,6 +110,8 @@ public: FIRST_LAYER_BIT, // 8 Exclusive Layers (encoded in 3 bits) available to organize the items in layers, an item can only belong to ONE layer LAST_LAYER_BIT = FIRST_LAYER_BIT + NUM_LAYER_BITS, + MIRROR, + __SMALLER, // Reserved bit for spatialized item to indicate that it is smaller than expected in the cell in which it belongs (probably because it overlaps over several smaller cells) NUM_FLAGS, // Not a valid flag @@ -162,6 +164,7 @@ public: Builder& withoutMetaCullGroup() { _flags.reset(META_CULL_GROUP); return (*this); } Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } Builder& withoutSubMetaCulled() { _flags.reset(SUB_META_CULLED); return (*this); } + Builder& withMirror() { _flags.set(MIRROR); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } // Set ALL the tags in one call using the Tag bits @@ -205,6 +208,9 @@ public: bool isSubMetaCulled() const { return _flags[SUB_META_CULLED]; } void setSubMetaCulled(bool metaCulled) { (metaCulled ? _flags.set(SUB_META_CULLED) : _flags.reset(SUB_META_CULLED)); } + bool isNotMirror() const { return !_flags[MIRROR]; } + bool isMirror() const { return _flags[MIRROR]; } + bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; } uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); } @@ -274,7 +280,10 @@ public: Builder& withMetaCullGroup() { _value.set(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); } Builder& withoutSubMetaCulled() { _value.reset(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); } - Builder& withSubMetaCulled() { _value.set(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); } + Builder& withSubMetaCulled() { _value.set(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); } + + Builder& withoutMirror() { _value.reset(ItemKey::MIRROR); _mask.set(ItemKey::MIRROR); return (*this); } + Builder& withMirror() { _value.set(ItemKey::MIRROR); _mask.set(ItemKey::MIRROR); return (*this); } Builder& withoutTag(ItemKey::Tag tagIndex) { _value.reset(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } Builder& withTag(ItemKey::Tag tagIndex) { _value.set(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } @@ -292,6 +301,7 @@ public: static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); } static Builder light() { return Builder().withTypeLight(); } static Builder meta() { return Builder().withTypeMeta(); } + static Builder mirror() { return Builder().withMirror(); } static Builder background() { return Builder().withViewSpace().withLayer(ItemKey::LAYER_BACKGROUND); } static Builder nothing() { return Builder().withNothing(); } }; @@ -440,6 +450,8 @@ public: virtual bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const = 0; + virtual ItemID computeMirrorView(ViewFrustum& viewFrustum) const = 0; + ~PayloadInterface() {} // Status interface is local to the base class @@ -493,6 +505,8 @@ public: bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const { return _payload->passesZoneOcclusionTest(containingZones); } + ItemID computeMirrorView(ViewFrustum& viewFrustum) const { return _payload->computeMirrorView(viewFrustum); } + // Access the status const StatusPointer& getStatus() const { return _payload->getStatus(); } @@ -547,6 +561,9 @@ template uint32_t metaFetchMetaSubItems(const std::shared_ptr& payl // Allows payloads to determine if they should render or not, based on the zones that contain the current camera template bool payloadPassesZoneOcclusionTest(const std::shared_ptr& payloadData, const std::unordered_set& containingZones) { return true; } +// Mirror Interface +template ItemID payloadComputeMirrorView(const std::shared_ptr& payloadData, ViewFrustum& viewFrustum) { return Item::INVALID_ITEM_ID; } + // THe Payload class is the real Payload to be used // THis allow anything to be turned into a Payload as long as the required interface functions are available // When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff" @@ -573,6 +590,8 @@ public: virtual bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const override { return payloadPassesZoneOcclusionTest(_data, containingZones); } + virtual ItemID computeMirrorView(ViewFrustum& viewFrustum) const override { return payloadComputeMirrorView(_data, viewFrustum); } + protected: DataPointer _data; @@ -628,6 +647,7 @@ public: virtual void render(RenderArgs* args) = 0; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const = 0; virtual bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const = 0; + virtual ItemID computeMirrorView(ViewFrustum& viewFrustum) const = 0; // FIXME: this isn't the best place for this since it's only used for ModelEntities, but currently all Entities use PayloadProxyInterface virtual void handleBlendedVertices(int blendshapeNumber, const QVector& blendshapeOffsets, @@ -640,6 +660,7 @@ template <> void payloadRender(const PayloadProxyInterface::Pointer& payload, Re template <> uint32_t metaFetchMetaSubItems(const PayloadProxyInterface::Pointer& payload, ItemIDs& subItems); template <> const ShapeKey shapeGetShapeKey(const PayloadProxyInterface::Pointer& payload); template <> bool payloadPassesZoneOcclusionTest(const PayloadProxyInterface::Pointer& payload, const std::unordered_set& containingZones); +template <> ItemID payloadComputeMirrorView(const PayloadProxyInterface::Pointer& payload, ViewFrustum& viewFrustum); typedef Item::PayloadPointer PayloadPointer; typedef std::vector Payloads; diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index b2656a597f..3bdaee25c6 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -35,18 +35,20 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto nonspatialSelection = task.addJob("FetchLayeredSelection", nonspatialFilter); // Multi filter visible items into different buckets - const int NUM_SPATIAL_FILTERS = 4; + const int NUM_SPATIAL_FILTERS = 5; const int NUM_NON_SPATIAL_FILTERS = 3; const int OPAQUE_SHAPE_BUCKET = 0; const int TRANSPARENT_SHAPE_BUCKET = 1; const int LIGHT_BUCKET = 2; const int META_BUCKET = 3; + const int MIRROR_BUCKET = 4; const int BACKGROUND_BUCKET = 2; MultiFilterItems::ItemFilterArray spatialFilters = { { - ItemFilter::Builder::opaqueShape(), + ItemFilter::Builder::opaqueShape().withoutMirror(), ItemFilter::Builder::transparentShape(), ItemFilter::Builder::light(), - ItemFilter::Builder::meta() + ItemFilter::Builder::meta().withoutMirror(), + ItemFilter::Builder::mirror() } }; MultiFilterItems::ItemFilterArray nonspatialFilters = { { ItemFilter::Builder::opaqueShape(), @@ -65,6 +67,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto transparents = task.addJob("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto lights = filteredSpatialBuckets[LIGHT_BUCKET]; const auto metas = filteredSpatialBuckets[META_BUCKET]; + const auto mirrors = task.addJob("DepthSortMirrors", filteredSpatialBuckets[MIRROR_BUCKET]); const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET]; @@ -76,7 +79,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin task.addJob("ClearContainingZones"); - output = Output(BucketList{ opaques, transparents, lights, metas, + output = Output(BucketList{ opaques, transparents, lights, metas, mirrors, filteredLayeredOpaque.getN(0), filteredLayeredTransparent.getN(0), filteredLayeredOpaque.getN(1), filteredLayeredTransparent.getN(1), background }, spatialSelection); diff --git a/libraries/render/src/render/RenderFetchCullSortTask.h b/libraries/render/src/render/RenderFetchCullSortTask.h index 0b475614a1..9823c2acdf 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.h +++ b/libraries/render/src/render/RenderFetchCullSortTask.h @@ -23,6 +23,7 @@ public: TRANSPARENT_SHAPE, LIGHT, META, + MIRROR, LAYER_FRONT_OPAQUE_SHAPE, LAYER_FRONT_TRANSPARENT_SHAPE, LAYER_HUD_OPAQUE_SHAPE, diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index b868c53542..e77dda600c 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -167,7 +167,8 @@ void UpsampleToBlitFramebuffer::run(const RenderContextPointer& renderContext, c batch.setViewportTransform(viewport); batch.setProjectionTransform(glm::mat4()); batch.resetViewTransform(); - batch.setPipeline(args->_renderMode == RenderArgs::MIRROR_RENDER_MODE ? _mirrorPipeline : _pipeline); + bool shouldMirror = args->_numMirrorFlips >= (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE); + batch.setPipeline(shouldMirror ? _mirrorPipeline : _pipeline); batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport)); batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0)); diff --git a/libraries/render/src/render/ResampleTask.h b/libraries/render/src/render/ResampleTask.h index 92f720c843..bf1e535949 100644 --- a/libraries/render/src/render/ResampleTask.h +++ b/libraries/render/src/render/ResampleTask.h @@ -73,7 +73,7 @@ namespace render { using Input = gpu::FramebufferPointer; using JobModel = Job::ModelIO; - UpsampleToBlitFramebuffer() {} + UpsampleToBlitFramebuffer(size_t depth) : _depth(depth) {} void run(const RenderContextPointer& renderContext, const Input& input, gpu::FramebufferPointer& resampledFrameBuffer); @@ -81,6 +81,8 @@ namespace render { static gpu::PipelinePointer _pipeline; static gpu::PipelinePointer _mirrorPipeline; + + size_t _depth; }; } diff --git a/libraries/shared/src/MirrorMode.cpp b/libraries/shared/src/MirrorMode.cpp new file mode 100644 index 0000000000..272eb5d7c0 --- /dev/null +++ b/libraries/shared/src/MirrorMode.cpp @@ -0,0 +1,36 @@ +// +// Created by HifiExperiments on 3/14/22. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MirrorMode.h" + +const char* MirrorModeNames[] = { + "none", + "mirror", + "portal" +}; + +static const size_t MIRROR_MODE_NAMES = (sizeof(MirrorModeNames) / sizeof(MirrorModeNames[0])); +std::function MirrorModeHelpers::_computeMirrorViewOperator = + [](ViewFrustum&, const glm::vec3&, const glm::quat&, MirrorMode, const QUuid&) { return 0; }; + +QString MirrorModeHelpers::getNameForMirrorMode(MirrorMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)MIRROR_MODE_NAMES)) { + mode = (MirrorMode)0; + } + + return MirrorModeNames[(int)mode]; +} + +void MirrorModeHelpers::setComputeMirrorViewOperator(std::function computeMirrorViewOperator) { + _computeMirrorViewOperator = computeMirrorViewOperator; +} + +uint32_t MirrorModeHelpers::computeMirrorView(ViewFrustum& viewFrustum, const glm::vec3& inPropertiesPosition, const glm::quat& inPropertiesRotation, + MirrorMode mirrorMode, const QUuid& portalExitID) { + return _computeMirrorViewOperator(viewFrustum, inPropertiesPosition, inPropertiesRotation, mirrorMode, portalExitID); +} \ No newline at end of file diff --git a/libraries/shared/src/MirrorMode.h b/libraries/shared/src/MirrorMode.h new file mode 100644 index 0000000000..e48e564df0 --- /dev/null +++ b/libraries/shared/src/MirrorMode.h @@ -0,0 +1,51 @@ +// +// Created by HifiExperiments on 3/14/22. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MirrorMode_h +#define hifi_MirrorMode_h + +#include + +#include "QString" + +#include "ViewFrustum.h" + +/*@jsdoc + *

If an entity is rendered as a mirror, a portal, or normally.

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
"none"The entity will render normally.
"mirror"The entity will render as a mirror.
"portal"The entity will render as a portal.
+ * @typedef {string} MirrorMode + */ + +enum class MirrorMode { + NONE = 0, + MIRROR, + PORTAL +}; + +class MirrorModeHelpers { +public: + static QString getNameForMirrorMode(MirrorMode mode); + + static void setComputeMirrorViewOperator(std::function computeMirrorViewOperator); + static uint32_t computeMirrorView(ViewFrustum& viewFrustum, const glm::vec3& inPropertiesPosition, const glm::quat& inPropertiesRotation, + MirrorMode mirrorMode, const QUuid& portalExitID); + +private: + static std::function _computeMirrorViewOperator; +}; + +#endif // hifi_MirrorMode_h diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index e925ef960d..daa08b5dbc 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -53,7 +53,7 @@ static const glm::vec4 NDC_VALUES[NUM_FRUSTUM_CORNERS] = { glm::vec4(-1.0f, 1.0f, 1.0f, 1.0f), }; -void ViewFrustum::setProjection(const glm::mat4& projection) { +void ViewFrustum::setProjection(const glm::mat4& projection, bool isOblique) { _projection = projection; glm::mat4 inverseProjection = glm::inverse(projection); @@ -63,16 +63,21 @@ void ViewFrustum::setProjection(const glm::mat4& projection) { _corners[i] /= _corners[i].w; } - // compute frustum properties - _nearClip = -_corners[BOTTOM_LEFT_NEAR].z; - _farClip = -_corners[BOTTOM_LEFT_FAR].z; - _aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) / - (_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y); - glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f); - top /= top.w; - _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top)))))); - _height = _corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_RIGHT_NEAR].y; - _width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x; + // HACK: these calculations aren't correct for our oblique mirror frustums, but we can just reuse the values from the original + // frustum since these values are only used on the CPU. + if (!isOblique) { + // compute frustum properties + _nearClip = -_corners[BOTTOM_LEFT_NEAR].z; + _farClip = -_corners[BOTTOM_LEFT_FAR].z; + _aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) / + (_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y); + glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f); + top /= top.w; + _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top)))))); + _height = _corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_RIGHT_NEAR].y; + _width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x; + } + _isOblique = isOblique; } void ViewFrustum::setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip) { @@ -109,12 +114,24 @@ void ViewFrustum::calculate() { // the function set3Points assumes that the points are given in counter clockwise order, assume you // are inside the frustum, facing the plane. Start with any point, and go counter clockwise for // three consecutive points - _planes[TOP_PLANE].set3Points(_cornersWorld[TOP_RIGHT_NEAR], _cornersWorld[TOP_LEFT_NEAR], _cornersWorld[TOP_LEFT_FAR]); - _planes[BOTTOM_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_RIGHT_FAR]); - _planes[LEFT_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[TOP_LEFT_FAR]); - _planes[RIGHT_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[TOP_RIGHT_FAR]); - _planes[NEAR_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[TOP_LEFT_NEAR]); - _planes[FAR_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[TOP_RIGHT_FAR]); + if (!_isOblique) { + _planes[TOP_PLANE].set3Points(_cornersWorld[TOP_RIGHT_NEAR], _cornersWorld[TOP_LEFT_NEAR], _cornersWorld[TOP_LEFT_FAR]); + _planes[BOTTOM_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_RIGHT_FAR]); + _planes[LEFT_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[TOP_LEFT_FAR]); + _planes[RIGHT_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[TOP_RIGHT_FAR]); + _planes[NEAR_PLANE].set3Points(_cornersWorld[BOTTOM_RIGHT_NEAR], _cornersWorld[BOTTOM_LEFT_NEAR], _cornersWorld[TOP_LEFT_NEAR]); + _planes[FAR_PLANE].set3Points(_cornersWorld[BOTTOM_LEFT_FAR], _cornersWorld[BOTTOM_RIGHT_FAR], _cornersWorld[TOP_RIGHT_FAR]); + } else { + Corners near = getCorners(_nearClip); + Corners far = getCorners(_farClip); + + _planes[TOP_PLANE].set3Points(near.topRight, near.topLeft, far.topLeft); + _planes[BOTTOM_PLANE].set3Points(near.bottomLeft, near.bottomRight, far.bottomRight); + _planes[LEFT_PLANE].set3Points(near.bottomLeft, far.bottomLeft, far.topLeft); + _planes[RIGHT_PLANE].set3Points(far.bottomRight, near.bottomRight, far.topRight); + _planes[NEAR_PLANE].set3Points(near.bottomRight, near.bottomLeft, near.topLeft); + _planes[FAR_PLANE].set3Points(far.bottomLeft, far.bottomRight, far.topRight); + } // Also calculate our projection matrix in case people want to project points... // Projection matrix : Field of View, ratio, display range : near to far diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 9c80538e60..fa66a0a87e 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -47,7 +47,7 @@ public: const glm::vec3& getRight() const { return _right; } // setters for lens attributes - void setProjection(const glm::mat4 & projection); + void setProjection(const glm::mat4& projection, bool isOblique = false); void setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip); void setFocalLength(float focalLength) { _focalLength = focalLength; } bool isPerspective() const; @@ -103,7 +103,6 @@ public: bool pointIntersectsFrustum(const glm::vec3& point) const; bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const; - bool cubeIntersectsFrustum(const AACube& box) const; bool boxIntersectsFrustum(const AABox& box) const; bool boxInsideFrustum(const AABox& box) const; @@ -175,6 +174,8 @@ private: float _nearClip { DEFAULT_NEAR_CLIP }; float _farClip { DEFAULT_FAR_CLIP }; + bool _isOblique { false }; + const char* debugPlaneName (int plane) const; // Used to project points diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 9f6c3ef278..57cb3d1bc8 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -215,7 +215,7 @@ }, "imageAlpha": { "tooltip": "The opacity of the image between 0.0 fully transparent and 1.0 completely opaque." - }, + }, "emissive": { "tooltip": "If enabled, the image will display at full brightness." }, @@ -236,7 +236,7 @@ }, "showKeyboardFocusHighlight": { "tooltip": "If enabled, highlights when it has keyboard focus." - }, + }, "isEmitting": { "tooltip": "If enabled, then particles are emitted." }, @@ -503,6 +503,12 @@ "grab.triggerable": { "tooltip": "If enabled, the collider on this entity is used for triggering events." }, + "mirrorMode": { + "tooltip": "If this entity should render normally, or as a \"Mirror\" or \"Portal\"" + }, + "portalExitID": { + "tooltip": "If this entity is a portal, what entity it should use as its exit." + }, "cloneable": { "tooltip": "If enabled, this entity can be duplicated." }, @@ -606,7 +612,7 @@ }, "useBackground": { "tooltip": "If disabled, this web entity will support a transparent background for the webpage and its elements if the CSS property of 'background-color' on the 'body' is set with transparency." - }, + }, "maxFPS": { "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." }, diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index e16b6ca653..b509168262 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -127,6 +127,22 @@ const GROUPS = [ }, propertyID: "billboardMode", }, + { + label: "Mirror Mode", + type: "dropdown", + options: { + none: "None", + mirror: "Mirror", + portal: "Portal" + }, + propertyID: "mirrorMode", + }, + { + label: "Portal Exit", + type: "string", + propertyID: "portalExitID", + showPropertyRule: { "mirrorMode": "portal" }, + }, { label: "Render With Zones", type: "multipleZonesSelection", diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index 0089c1577b..de39dacdea 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -122,7 +122,7 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { if (_correction != glm::mat4()) { std::unique_lock lock(_frameLock); if (_correction != glm::mat4()) { - _backend->setCameraCorrection(_correction, _activeFrame->view); + _backend->setCameraCorrection(_correction, _activeFrame->view, true); //_prevRenderView = _correction * _activeFrame->view; } }