tutoriales.com

Creando un Sistema de Portales Dimensiones con Render Textures en Unity 🌌

Descubre cómo implementar portales dimensionales realistas en Unity, utilizando Render Textures para mostrar el contenido de otra ubicación. Este tutorial te guiará paso a paso, desde la configuración de las cámaras hasta la creación de shaders personalizados para efectos visuales inmersivos.

Intermedio18 min de lectura10 views
Reportar error

¡Hola, aventureros de Unity! 👋 ¿Alguna vez has soñado con crear esos impresionantes portales que transportan al jugador a otras dimensiones, como en juegos clásicos como Portal o Antichamber? ¡Hoy haremos realidad ese sueño! En este tutorial, aprenderemos a construir un sistema de portales dimensionales utilizando una técnica muy potente de Unity: las Render Textures.

Este sistema no solo te permitirá ver a través del portal como si fuera una ventana a otra escena o área, sino que también sentará las bases para futuras interacciones de teletransporte. ¡Prepárate para expandir tu conocimiento de Unity!


🚀 ¿Qué aprenderemos hoy?

En este tutorial, cubriremos los siguientes puntos clave:

  • Entender qué son las Render Textures y cómo funcionan.
  • Configurar cámaras auxiliares para capturar la vista de la 'otra dimensión'.
  • Crear y aplicar materiales con Render Textures a nuestros objetos de portal.
  • Optimizar el rendimiento y resolver problemas comunes.
  • Mejorar la inmersión con efectos visuales básicos para los portales.
💡 Consejo: Asegúrate de tener una versión reciente de Unity (2020.3 LTS o superior es recomendable) y conocimientos básicos del editor y programación en C# para seguir este tutorial sin problemas.

🛠️ Requisitos Previos

Antes de empezar, asegúrate de tener lo siguiente:

  • Unity Hub y una versión de Unity instalada.
  • Un nuevo proyecto de Unity (preferiblemente URP si quieres los mejores resultados gráficos, pero el estándar también funciona).
  • Un entorno 3D básico con algunos objetos para poder ver los efectos del portal.
🔥 Importante: Para este tutorial, asumiremos que ya tienes un proyecto de Unity con una escena configurada. Si no, crea un nuevo proyecto 3D y añade algunos cubos o esferas para visualizar los efectos.

🖼️ Paso 1: Entendiendo las Render Textures

Las Render Textures son activos especiales en Unity que actúan como texturas a las que una cámara puede renderizar directamente. En lugar de dibujar la vista de la cámara en la pantalla principal del juego, la dibuja en esta textura. Luego, puedes usar esta textura en un material para mostrarla en cualquier superficie 3D.

Imagina que tienes una cámara grabando un programa de televisión y, en lugar de transmitirlo, lo muestra en una pantalla dentro del propio programa. ¡Eso es una Render Texture en esencia!

Propiedades Clave de una Render Texture:

PropiedadDescripciónValores Típicos
SizeResolución de la textura (ancho x alto). Mayor resolución, mejor calidad, más rendimiento.1024x1024, 2048x2048
Color FormatFormato de color de los píxeles. RGB24, ARGB32 son comunes.ARGB32
Depth BufferProfundidad de bits para el búfer de profundidad. Necesario para 3D.16 bit, 24 bit (recomendado para 3D)
Filter ModeCómo se interpola la textura al escalarse.Bilinear, Trilinear
Wrap ModeCómo se comporta la textura si las coordenadas UV van más allá de [0,1].Repeat, Clamp

🌐 Paso 2: Configurando el Escenario y los Portales

Para empezar, necesitamos un par de objetos que actúen como nuestros portales y dos ubicaciones distintas para enlazar.

  1. Crea la Estructura Base:

    • Crea una escena sencilla con dos habitaciones o áreas separadas. Puedes usar cubos para paredes y suelos.
    • En cada habitación, coloca algunos objetos distintivos (cubos de colores, esferas) para que sea fácil distinguir una de otra.
  2. Crea los Objetos del Portal:

    • En cada una de las dos áreas, crea un Quad (GameObject -> 3D Object -> Quad). Estos serán nuestros portales. Renómbralos a PortalA y PortalB.
    • Asegúrate de que los quads estén mirando hacia el interior de la habitación, de modo que cuando el jugador se acerque, vea la 'ventana' al otro lado.
    • Ajusta la escala y posición para que parezcan entradas razonables.
Habitación A Cubo Rojo Cubo Oro Quad A Habitación B Cubo Verde Cubo Lila Quad B Vista superior: Los portales (Quads) conectan ambos espacios

📸 Paso 3: Las Cámaras Auxiliares y Render Textures

Ahora viene la magia: una cámara por cada portal que renderizará la vista del otro lado.

3.1. Creando las Render Textures

  1. En la ventana Project, crea una nueva carpeta llamada RenderTextures.
  2. Haz clic derecho en RenderTextures -> Create -> Render Texture.
  3. Crea dos: PortalARenderTexture y PortalBRenderTexture.
  4. Selecciona cada Render Texture y en el Inspector, ajusta:
    • Size: 1024 x 1024 (o 2048 x 2048 para mayor calidad, pero prueba con 1024 primero).
    • Depth Buffer: 24 bit.

3.2. Configurando las Cámaras de Portal

Vamos a necesitar una cámara para cada portal.

  1. Crea dos nuevas cámaras: PortalACamera y PortalBCamera (GameObject -> Camera).

  2. Configura PortalACamera:

    • Selecciónala. En el Inspector, para Target Texture, arrastra PortalBRenderTexture.
    • Esto significa que PortalACamera capturará lo que ve y lo dibujará en la textura para PortalB. Sí, lo lees bien: la cámara del portal A mira la vista que se mostrará en el portal B. ¡Es un reflejo!
    • Ajusta Clear Flags a Solid Color y el Background a negro o a un color que encaje con tu ambiente.
    • Desmarca HDR si no lo necesitas, y marca Opaque Texture si usas URP.
    • Ajusta el Field of View (FOV) para que coincida con el FOV de tu cámara principal (jugador), generalmente 60.
    • Asegúrate de que la cámara no renderice la misma capa que tu portal para evitar recursión infinita (por ahora, esto lo manejaremos más adelante).
  3. Configura PortalBCamera:

    • Selecciónala. En el Inspector, para Target Texture, arrastra PortalARenderTexture.
    • Aplica las mismas configuraciones generales (Clear Flags, Background, FOV) que para PortalACamera.
📌 Nota: Es crucial que la cámara que renderiza *para* un portal esté posicionada *donde* estaría la cámara principal si pasara por el *otro* portal.

🎨 Paso 4: Creando Materiales y Asignándolos a los Portales

Ahora necesitamos materiales para nuestros quads que usen estas Render Textures.

  1. Crea una nueva carpeta llamada Materials.

  2. Crea dos materiales: PortalAMaterial y PortalBMaterial (Create -> Material).

  3. Configura PortalAMaterial:

    • Selecciónalo. En el Inspector, para la propiedad Albedo (o Base Map en URP), arrastra PortalARenderTexture.
    • Cambia el Rendering Mode a Fade o Transparent si quieres un efecto translúcido.
    • Arrastra PortalAMaterial al Quad llamado PortalA en tu escena.
  4. Configura PortalBMaterial:

    • Selecciónalo. En el Inspector, para la propiedad Albedo (o Base Map en URP), arrastra PortalBRenderTexture.
    • Aplica las mismas configuraciones de Rendering Mode.
    • Arrastra PortalBMaterial al Quad llamado PortalB en tu escena.

¡En este punto, deberías ver que tus quads ahora muestran una imagen! Puede que no sea la correcta todavía, ¡pero es un progreso!


📐 Paso 5: Posicionamiento Dinámico de las Cámaras de Portal

El truco para los portales realistas es que la cámara que renderiza para un portal siempre debe estar en la posición relativa correcta al otro portal. Esto lo haremos con un script C#.

  1. Crea una nueva carpeta Scripts y crea un script C# llamado PortalCamera.

  2. Abre PortalCamera.cs y pega el siguiente código:

using UnityEngine;

public class PortalCamera : MonoBehaviour
{
    public Transform playerCamera;
    public Transform portalA;
    public Transform portalB;

    void LateUpdate()
    {
        if (playerCamera == null || portalA == null || portalB == null)
        {
            Debug.LogWarning("Asigna playerCamera, portalA y portalB en el inspector del script PortalCamera.");
            return;
        }

        // Calcular la posición y rotación del jugador en el espacio del Portal A
        Vector3 playerOffsetFromPortalA = playerCamera.position - portalA.position;
        Quaternion playerRotationFromPortalA = Quaternion.Inverse(portalA.rotation) * playerCamera.rotation;

        // Reflejar esa posición y rotación en el espacio del Portal B
        // (Ajuste de la rotación para que mire en la dirección correcta)
        Vector3 newCameraPosition = portalB.position + Quaternion.Euler(0, 180, 0) * (portalB.rotation * playerOffsetFromPortalA);
        Quaternion newCameraRotation = portalB.rotation * Quaternion.Euler(0, 180, 0) * playerRotationFromPortalA;

        // Asignar la nueva posición y rotación a la cámara del portal (esta cámara)
        transform.position = newCameraPosition;
        transform.rotation = newCameraRotation;
    }
}
  1. Asigna el script:

    • Arrastra el script PortalCamera a PortalACamera y a PortalBCamera.
    • Para PortalACamera (que renderiza para PortalB): en el Inspector, arrastra tu Main Camera (la cámara del jugador) al campo Player Camera, PortalA al campo PortalA, y PortalB al campo PortalB.
    • Para PortalBCamera (que renderiza para PortalA): en el Inspector, arrastra tu Main Camera al campo Player Camera, PortalB al campo PortalA, y PortalA al campo PortalB. ¡Cuidado con el orden aquí! La cámara del portal debe saber qué portal es el suyo (el portalA en el script) y cuál es el otro (portalB en el script) para el cálculo.
    ⚠️ Advertencia: La lógica en `PortalCamera` asume que el `portalA` es el portal que la cámara *actual* está emulando, y `portalB` es el portal *destino*. Asegúrate de asignar correctamente las referencias en el Inspector para cada cámara auxiliar.
¿Por qué usamos `LateUpdate()`?Cuando actualizamos la cámara del portal en `LateUpdate()`, nos aseguramos de que la cámara del jugador ya haya terminado de moverse en `Update()`. Esto garantiza que los cálculos de posición y rotación sean lo más precisos posible con respecto a la posición final del jugador en el frame actual.

✨ Paso 6: Añadiendo un Shader Básico para un Efecto de Portal

Un simple material Standard puede funcionar, pero para un efecto más convincente, un shader personalizado puede hacer maravillas. Vamos a crear un shader URP (o Standard si no usas URP) que solo muestre la textura y permita cierta transparencia.

6.1. Creando un Shader Gráfico (Shader Graph - URP)

Si usas URP, Shader Graph es la forma más sencilla.

  1. En la carpeta Materials, haz clic derecho -> Create -> Shader Graph -> URP -> Unlit Graph (o PBR Graph si quieres reflejos).

  2. Renombra a PortalShaderGraph.

  3. Abre el Shader Graph.

    • Crea un nodo Texture 2D Asset y arrastra tu Render Texture (por ejemplo, PortalARenderTexture) a él.
    • Conecta la salida RGBA del nodo Texture 2D Asset al nodo Base Color (o Color en Unlit).
    • Si quieres transparencia, cambia Surface a Transparent en las propiedades del Graph Inspector (Pestaña Graph Settings). Conecta la salida Alpha a la entrada Alpha del nodo Fragment.
    • Guarda el Asset. ¡Ya tienes tu shader!
  4. Crea nuevos materiales (por ejemplo, PortalA_ShaderMaterial) y asigna este PortalShaderGraph.

  5. Asigna la Render Texture correcta (e.g. PortalARenderTexture) a la propiedad de textura que el shader exponga (si la expusiste como propiedad).

6.2. Shader Básico Códificado (Fallback para Standard RP o si prefieres código)

Si no usas URP o prefieres código, puedes usar un shader básico.

  1. En la carpeta Materials, haz clic derecho -> Create -> Shader -> Standard Surface Shader.
  2. Renombra a PortalSurfaceShader.
  3. Abre el archivo .shader y modifícalo para que se parezca a esto (ejemplo simplificado de unlit transparente):
Shader "Custom/PortalSurfaceShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                return col;
            }
            ENDCG
        }
    }
}
  1. Crea nuevos materiales que usen este shader y asigna la Render Texture apropiada a la propiedad _MainTex.

🔄 Paso 7: Teletransporte del Jugador (Opcional pero recomendado)

Para que los portales sean realmente funcionales, el jugador debe poder atravesarlos. Esto implica teletransportar al jugador cuando cruza la superficie del portal.

  1. Crea un nuevo script C# llamado PortalTeleporter.

  2. Abre PortalTeleporter.cs y pega el siguiente código:

using UnityEngine;

public class PortalTeleporter : MonoBehaviour
{
    public Transform player;
    public Transform receiver;

    private bool playerIsOverlapping = false;

    void Start()
    {
        // Asegurarse de que el objeto del portal tiene un Collider marcado como Trigger
        Collider portalCollider = GetComponent<Collider>();
        if (portalCollider == null || !portalCollider.isTrigger)
        {
            Debug.LogWarning("El objeto Portal debe tener un Collider con 'Is Trigger' activado.");
            // Añadir un BoxCollider si no existe y configurarlo como trigger
            if (portalCollider == null)
            {
                portalCollider = gameObject.AddComponent<BoxCollider>();
            }
            portalCollider.isTrigger = true;
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            playerIsOverlapping = true;
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            playerIsOverlapping = false;
        }
    }

    void Update()
    {
        if (playerIsOverlapping)
        {
            if (player == null || receiver == null)
            {
                Debug.LogWarning("Asigna player y receiver en el inspector del script PortalTeleporter.");
                return;
            }

            // Obtener la dirección en la que el portal está mirando (adelante)
            Vector3 portalForward = transform.forward;

            // Comprobar si el jugador ha cruzado el umbral del portal
            // Si el producto punto es negativo, significa que el jugador cruzó el plano del portal
            float dotProduct = Vector3.Dot(player.position - transform.position, portalForward);

            if (dotProduct < 0f)
            {
                // Calcular la rotación para alinear el jugador con el portal de destino
                Quaternion portalToPortalRotation = Quaternion.FromToRotation(transform.forward, -receiver.forward);
                float angularDifference = Quaternion.Angle(transform.rotation, receiver.rotation);
                Quaternion rotationDifference = Quaternion.Euler(0, angularDifference, 0);

                Vector3 positionOffsetFromPortal = player.position - transform.position;
                positionOffsetFromPortal = portalToPortalRotation * positionOffsetFromPortal;
                player.position = receiver.position + positionOffsetFromPortal;

                // Aplicar la rotación del portal al jugador
                // Esto asegura que el jugador mire en la dirección correcta después del teletransporte
                player.rotation = portalToPortalRotation * player.rotation;

                playerIsOverlapping = false; // Reset para evitar teletransporte múltiple
            }
        }
    }
}
  1. Configura el teletransporte:
    • Asegúrate de que tu Player (o el objeto que representa al jugador, por ejemplo, el padre de la Main Camera) tenga un Tag llamado Player.
    • Añade el script PortalTeleporter a los objetos PortalA y PortalB.
    • Para PortalA:
      • Arrastra tu Player al campo Player.
      • Arrastra PortalB al campo Receiver.
    • Para PortalB:
      • Arrastra tu Player al campo Player.
      • Arrastra PortalA al campo Receiver.
    • Asegúrate de que ambos Quad de los portales tengan un Box Collider marcado como Is Trigger.
⚠️ Advertencia: El script de teletransporte puede requerir ajustes finos dependiendo de la configuración de tu controlador de jugador (Rigidbodies, Character Controllers, etc.). El ejemplo asume un transform directo.

📈 Paso 8: Optimizaciones y Consideraciones Avanzadas

Crear portales con Render Textures puede ser exigente para el rendimiento. Aquí hay algunas formas de optimizar:

  1. Resolución de la Render Texture: Cuanto menor sea la resolución, menor será la carga. Experimenta con diferentes tamaños para encontrar un equilibrio entre calidad y rendimiento.
  2. Culling Masks de las Cámaras: En la PortalACamera y PortalBCamera, en la propiedad Culling Mask, desactiva las capas que no necesitas renderizar (por ejemplo, la capa del jugador, la propia geometría del portal). Esto reduce lo que la cámara tiene que dibujar.
  3. Renderizado Condicional: Desactiva las cámaras del portal cuando el jugador no las esté mirando, o cuando estén muy lejos. Puedes usar OnBecameVisible() y OnBecameInvisible() en un script adjunto a los portales, o cálculos de distancia y frustum.
  4. Desactivación de Componentes Innecesarios: Las cámaras de portal no necesitan componentes como Audio Listener o Flare Layer.
💡 Consejo: Si usas URP, puedes aprovechar los `Camera Stacking` y los `Renderer Features` para un control aún más fino sobre cómo y cuándo se renderizan las cámaras y sus efectos.

🐛 Resolución de Problemas Comunes

  • El portal está negro/vacío:
    • Asegúrate de que la Target Texture de la cámara del portal esté asignada correctamente a la Render Texture.
    • Verifica que el material del portal esté usando la Render Texture correcta en su Albedo (o Base Map).
    • Asegúrate de que los objetos a través del portal no estén en una capa que haya sido excluida por el Culling Mask de la cámara del portal.
    • Comprueba que el Near Clip Plane de la cámara del portal no sea demasiado grande, cortando lo que debería verse.
  • El portal muestra la misma escena (recursión):
    • Asegúrate de que el objeto Quad del portal no esté en una capa que esté siendo renderizada por la cámara del portal. Crea una nueva capa llamada PortalGeo para los quads y desactívala en el Culling Mask de las cámaras del portal.
  • El teletransporte no funciona:
    • Verifica que el Player tenga el tag Player.
    • Asegúrate de que los quads del portal tienen un Box Collider marcado como Is Trigger.
    • Comprueba que las referencias player y receiver estén asignadas correctamente en el script PortalTeleporter de cada portal.
    • Asegúrate de que el player tenga un Rigidbody si estás usando colisiones físicas para tu controlador de personaje, o un CharacterController con el cual OnTriggerEnter funcione.

🎉 ¡Conclusión y Próximos Pasos!

¡Felicidades! 🎉 Has construido un sistema de portales dimensionales funcional en Unity. Esto es solo el principio. Puedes expandir este sistema de muchas maneras:

  • Efectos visuales avanzados: Añade animaciones, partículas, distorsión de la imagen o shaders más complejos para hacer los portales más mágicos.
  • Audio espacial: Haz que el sonido del otro lado del portal sea audible y se adapte a la distancia.
  • Teletransporte con CharacterController: Adapta el script PortalTeleporter para trabajar con el CharacterController de Unity para movimientos de jugador más robustos.
  • Portal unidireccional: Modifica el script para que solo se pueda pasar en una dirección.
  • Más de dos portales: Implementa una lógica que permita conectar múltiples portales dinámicamente.

La técnica de Render Textures es increíblemente versátil y se utiliza en muchos otros contextos, como espejos, visores de cámaras de seguridad, o incluso mini-mapas dinámicos. ¡Sigue experimentando!

Tutorial Completo

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!