Lines
Slide
Slide
Slide
Slide
Slide
Slide
Slide
Slide

INVENTED
WORLDS

Навигация

Unity Portal HDRP

Визуал

В камерах на порталах убрать пост процессинг. Убрать моушен блюр.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;

public class Portal : MonoBehaviour
{

    [Header ("Main Settings")]
    public Transform linkedPortal;
    public MeshRenderer screen;
    public int recursion = 3;
    // Небольшой отступ чтобы избежать артефактов
    public float nearClipOffset = 0.0f;
    public float nearClipLimit = 0.0f;

    RenderTexture viewTexture;
    Camera portalCamera;
    Camera otherPortalCam;
    Camera mainCamera;
    HDAdditionalCameraData hdCamData;
    // Кэш для трансформаций порталов (оптимизация)
    private Matrix4x4[] cachedTransforms;
    private bool transformsCacheValid = false;

    void Awake () 
    {
        //не забыть поставить тег на основную камеру
        mainCamera = Camera.main;
        portalCamera = GetComponentInChildren<Camera> (true);
        if (linkedPortal != null)
            otherPortalCam = linkedPortal.GetComponentInChildren<Camera> (true);
        if (portalCamera != null)
        {
            portalCamera.enabled = false;
            hdCamData = portalCamera.GetComponent<HDAdditionalCameraData>();
        }
    }

    void Start()
    {
        // Создаем и назначаем текстуру ОДИН раз при старте
        CreateViewTexture();
        // Инициализируем кэш трансформаций
        InitializeTransformCache();
    }

    void Update () 
    {
        // Render ();
        // Проверяем, изменились ли трансформации порталов
        if (transformsCacheValid && (transform.hasChanged || (linkedPortal != null && linkedPortal.hasChanged)))
        {
            InvalidateTransformCache();
            transform.hasChanged = false;
            if (linkedPortal != null) linkedPortal.hasChanged = false;
        }
    }

    public void Render () 
    {
        if (linkedPortal == null || portalCamera == null || viewTexture == null) return;
        // Рендерим рекурсивно от последней итерации к первой
        for (int i = recursion - 1; i >= 0; i--)
        {
            RenderPortalIteration(i);
        }
    }
    public void PrePortalRender() {
        // Подготовка перед рендерингом (если нужно)
    }
    public void PostPortalRender() {
        // Очистка после рендеринга (если нужно)
    }

    void CreateViewTexture()
    {
        if (screen == null || screen.material == null)
        {
            Debug.LogError($"[{name}] Portal screen or material is null!");
            return;
        }
        int w = Mathf.Max(1, Screen.width);
        int h = Mathf.Max(1, Screen.height);
        bool needCreate = (viewTexture == null) || (viewTexture.width != w) || (viewTexture.height != h);
        if (needCreate)
        {
            if (viewTexture != null)
            {
                viewTexture.Release();
                DestroyImmediate(viewTexture);
                viewTexture = null;
            }
            // --- HDR-compatible descriptor ---
            var desc = new RenderTextureDescriptor(w, h)
            {
                msaaSamples = 1, // ОТКЛЮЧЕНО MSAA
                volumeDepth = 1,
                enableRandomWrite = false,
                useMipMap = false,
                autoGenerateMips = false,
                dimension = UnityEngine.Rendering.TextureDimension.Tex2D,
                depthBufferBits = 24,
                // GraphicsFormat R16G16B16A16_SFloat — HDR-safe и совместим с HDRP
                graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat,
                // depthStencilFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.D32,
                sRGB = (QualitySettings.activeColorSpace == ColorSpace.Linear)
            };
            viewTexture = new RenderTexture(desc);
            viewTexture.filterMode = FilterMode.Bilinear;
            viewTexture.wrapMode = TextureWrapMode.Clamp;
            viewTexture.useMipMap = false;
            viewTexture.autoGenerateMips = false;
            viewTexture.name = gameObject.name + "_PortalTexture";
            if (!viewTexture.Create())
            {
                Debug.LogError($"[{name}] Failed to create RenderTexture!");
                viewTexture = null;
                return;
            }
            screen.material.SetTexture("_MainTex", viewTexture);
        }
        // Всегда устанавливаем target texture для камеры и отключаем MSAA/allowMSAA
        if (portalCamera != null)
        {
            portalCamera.targetTexture = viewTexture;
            portalCamera.allowMSAA = false; // отключаем на камере
            portalCamera.allowHDR = true;   // включаем HDR (по желанию)
        }
    }

    void RenderPortalIteration(int iterationID)
    {
        if (linkedPortal == null || portalCamera == null || mainCamera == null) return;
        if (iterationID == recursion -1)
            {
                screen.material.SetFloat("_displayMask", 0);
            }
        // Копируем позицию и поворот основной камеры
        portalCamera.transform.position = mainCamera.transform.position;
        portalCamera.transform.rotation = mainCamera.transform.rotation;
        // Вычисляем финальную трансформацию камеры через порталы математически
        TransformCameraThroughPortals(iterationID + 1);
        // Устанавливаем параметры камеры и рендерим
        UpdatePortalCameraSettings();
        // Базовая проекция перед расчётом oblique
        portalCamera.projectionMatrix = mainCamera.projectionMatrix;
        // Отсечение за плоскостью портала (oblique near plane)
        SetNearClipPlane();

        if (iterationID != 0)
        {
            hdCamData.flipYMode = HDAdditionalCameraData.FlipYMode.ForceFlipY;
        }
        portalCamera.Render();
        hdCamData.flipYMode = HDAdditionalCameraData.FlipYMode.Automatic;
        screen.material.SetFloat("_displayMask", 1);
    }

    void TransformCameraThroughPortals(int portalCount)
    {
        if (portalCount <= 0 || linkedPortal == null) return;
        // Вычисляем комбинированную трансформацию для прохода через N порталов
        Matrix4x4 combinedTransform = CalculatePortalTransform(portalCount);
        // Применяем трансформацию к позиции камеры
        portalCamera.transform.position = combinedTransform.MultiplyPoint(portalCamera.transform.position);
        // Применяем трансформацию к повороту камеры
        portalCamera.transform.rotation = combinedTransform.rotation * portalCamera.transform.rotation;
    }

    void InitializeTransformCache()
    {
        if (cachedTransforms == null || cachedTransforms.Length != recursion)
        {
            cachedTransforms = new Matrix4x4[recursion];
            transformsCacheValid = false;
        }
    }
    void InvalidateTransformCache()
    {
        transformsCacheValid = false;
    }

    Matrix4x4 CalculatePortalTransform(int portalCount)
    {
        if (portalCount <= 0 || linkedPortal == null) 
            return Matrix4x4.identity;
        // Проверяем кэш
        if (transformsCacheValid && portalCount <= cachedTransforms.Length)
        {
            return cachedTransforms[portalCount - 1];
        }
        // Вычисляем и кэшируем трансформации
        if (!transformsCacheValid)
        {
            Matrix4x4 singlePortalTransform = GetSinglePortalTransform();
            for (int i = 0; i < recursion; i++)
            {
                if (i == 0)
                {
                    cachedTransforms[i] = singlePortalTransform;
                }
                else
                {
                    cachedTransforms[i] = singlePortalTransform * cachedTransforms[i - 1];
                }
            }
            transformsCacheValid = true;
        }
        return cachedTransforms[portalCount - 1];
    }

    Matrix4x4 GetSinglePortalTransform()
    {
        // Создаем трансформацию для прохода через один портал
        // 1. Переводим в локальные координаты входного портала
        Matrix4x4 toLocal = transform.worldToLocalMatrix;
        // 2. Поворот на 180 градусов вокруг Y оси
        Matrix4x4 rotation180 = Matrix4x4.Rotate(Quaternion.Euler(0.0f, 180.0f, 0.0f));
        // 3. Переводим в мировые координаты выходного портала
        Matrix4x4 toWorld = linkedPortal.localToWorldMatrix;
        // Комбинируем все трансформации
        return toWorld * rotation180 * toLocal;
    }

    void UpdatePortalCameraSettings()
    {
        if (portalCamera == null || mainCamera == null) return;

        // Копируем базовые настройки камеры
        portalCamera.fieldOfView = mainCamera.fieldOfView;
        portalCamera.nearClipPlane = mainCamera.nearClipPlane;
        portalCamera.farClipPlane = mainCamera.farClipPlane;
        portalCamera.aspect = mainCamera.aspect;
        portalCamera.orthographic = mainCamera.orthographic;
        portalCamera.orthographicSize = mainCamera.orthographicSize;
    }

    void OnTriggerEnter (Collider other) {
        Debug.Log($"[Portal:{name}] OnTriggerEnter: {other.name}");
    }

    void OnTriggerExit (Collider other) {
        Debug.Log($"[Portal:{name}] OnTriggerExit: {other.name}");
    }

    void SetNearClipPlane() {
        if (linkedPortal == null || portalCamera == null || mainCamera == null) return;
        // Определяем плоскость обрезки (плоскость портала)
        Transform clipPlane = linkedPortal;
        // Проверяем, находится ли камера портала за плоскостью портала
        Vector3 portalToCamera = portalCamera.transform.position - clipPlane.position;
        float distanceToPortal = Vector3.Dot(portalToCamera, clipPlane.forward);
        // Если камера находится за порталом, не используем oblique projection
        if (distanceToPortal < 0) {
            portalCamera.projectionMatrix = mainCamera.projectionMatrix;
            return;
        }
        // Простая и стабильная логика определения направления нормали
        int dot = System.Math.Sign(Vector3.Dot(clipPlane.forward, clipPlane.position - portalCamera.transform.position));
        Debug.Log($"dot {dot}");
        // Конвертируем в пространство камеры портала
        Vector3 camSpacePos = portalCamera.worldToCameraMatrix.MultiplyPoint(clipPlane.position);
        Vector3 camSpaceNormal = portalCamera.worldToCameraMatrix.MultiplyVector(clipPlane.forward) * dot;
        Debug.Log($"camSpaceNormal {camSpaceNormal}");
        float camSpaceDst = -Vector3.Dot(camSpacePos, camSpaceNormal);
        Debug.Log($"camSpaceDst {camSpaceDst}");
        // Используем oblique projection только если мы не слишком близко к порталу
        if (Mathf.Abs(camSpaceDst) > nearClipLimit) {
            Vector4 clipPlaneCameraSpace = new Vector4(camSpaceNormal.x, camSpaceNormal.y, camSpaceNormal.z, camSpaceDst + nearClipOffset);
            portalCamera.projectionMatrix = CalculateObliqueMatrix(portalCamera.projectionMatrix, clipPlaneCameraSpace);
            // portalCamera.projectionMatrix = portalCamera.CalculateObliqueMatrix(clipPlaneCameraSpace);
        } else {
            // Если слишком близко, используем обычную проекцию
            portalCamera.projectionMatrix = mainCamera.projectionMatrix;
        }
    }

    // Метод для создания oblique projection matrix
    Matrix4x4 CalculateObliqueMatrix(Matrix4x4 projection, Vector4 clipPlane) {
        Vector4 q = projection.inverse * new Vector4(
            System.Math.Sign(clipPlane.x),
            System.Math.Sign(clipPlane.y),
            1.0f,
            -1.0f
        );
        Vector4 c = clipPlane * (2.0f / (Vector4.Dot(clipPlane, q)));
        
        // Заменяем третью строку матрицы проекции
        Matrix4x4 obliqueMatrix = projection;
        obliqueMatrix[2] = c.x - obliqueMatrix[3];
        obliqueMatrix[6] = c.y - obliqueMatrix[7];
        obliqueMatrix[10] = c.z - obliqueMatrix[11];
        obliqueMatrix[14] = c.w - obliqueMatrix[15];
        
        return obliqueMatrix;
    }

    void OnDestroy()
    {
        CleanupTextures();
    }

    void OnDisable()
    {
        CleanupTextures();
    }

    void CleanupTextures()
    {
        if (viewTexture != null)
        {
            viewTexture.Release();
            viewTexture = null;
        }
    }
}

Комментарии

Комментариев пока нет.