using System.Collections.Generic; using EscapeRoomEngine.VR.Runtime; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace EscapeRoomEngine.Portal.Runtime { public class RenderPortalCameras : ScriptableRendererFeature { private enum PassType { Opaque, Transparent } private class PortalCameraRenderPass : ScriptableRenderPass { private static readonly List ShaderTagIds = new() { new ShaderTagId("SRPDefaultUnlit"), new ShaderTagId("UniversalForward"), new ShaderTagId("UniversalForwardOnly") }; private RenderStateBlock _renderStateBlock; private CommandBuffer _commandBuffer; private readonly LayerMask _layers; private readonly PassType _passType; public PortalCameraRenderPass(LayerMask layers, PassType passType) { _layers = layers; _passType = passType; renderPassEvent = passType == PassType.Opaque ? RenderPassEvent.AfterRenderingOpaques : RenderPassEvent.AfterRenderingSkybox; _renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing); // set stencil var stencilState = StencilState.defaultValue; stencilState.enabled = true; stencilState.SetCompareFunction(CompareFunction.Equal); stencilState.SetPassOperation(StencilOp.Keep); stencilState.SetFailOperation(StencilOp.Keep); stencilState.SetZFailOperation(StencilOp.Keep); _renderStateBlock.mask |= RenderStateMask.Stencil; _renderStateBlock.stencilState = stencilState; } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // we need to store a reference to the command buffer before executing, because the reference in RenderingData is internal _commandBuffer = cmd; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (Player.Instance) { var drawingSettings = CreateDrawingSettings(ShaderTagIds, ref renderingData, _passType == PassType.Opaque ? renderingData.cameraData.defaultOpaqueSortFlags : SortingCriteria.CommonTransparent); var filteringSettings = new FilteringSettings(_passType == PassType.Opaque ? RenderQueueRange.opaque : RenderQueueRange.transparent, _layers); foreach (var portal in FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { var linkedPortal = portal.linkedPortal; if (linkedPortal) { // set which portal to render _renderStateBlock.stencilReference = portal.PortalNumber; // prepare the camera ref var cameraData = ref renderingData.cameraData; var camera = cameraData.camera; var cameraTransform = camera.transform; var originalPosition = cameraTransform.position; var originalRotation = cameraTransform.rotation; var originalProjection = camera.projectionMatrix; camera = linkedPortal.SetUpCamera(camera, camera.stereoEnabled ? cameraData.xr.multipassId == 0 ? Camera.MonoOrStereoscopicEye.Left : Camera.MonoOrStereoscopicEye.Right : Camera.MonoOrStereoscopicEye.Mono); RenderingUtils.SetViewAndProjectionMatrices(_commandBuffer, camera.worldToCameraMatrix, GL.GetGPUProjectionMatrix(camera.projectionMatrix, true), false); // execute command buffer context.ExecuteCommandBuffer(_commandBuffer); _commandBuffer.Clear(); // frustum culling camera.TryGetCullingParameters(true, out var cullingParameters); var cullingResults = context.Cull(ref cullingParameters); // render the portal context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings, ref _renderStateBlock); // reset RenderingUtils.SetViewAndProjectionMatrices(_commandBuffer, cameraData.GetViewMatrix(), cameraData.GetGPUProjectionMatrix(), false); camera.transform.SetPositionAndRotation(originalPosition, originalRotation); camera.projectionMatrix = originalProjection; } } } } } private PortalCameraRenderPass _opaquePass, _transparentPass; public LayerMask layers; public override void Create() { _opaquePass = new PortalCameraRenderPass(layers, PassType.Opaque); _transparentPass = new PortalCameraRenderPass(layers, PassType.Transparent); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(_opaquePass); renderer.EnqueuePass(_transparentPass); } } }