UNITY Cámara en perspectiva

De MediaWiki
Saltar a: navegación, buscar

Introducción

  • Preparación:
  • Crea una nueva escena de nombre Escena_UD2_Camara_Perspectiva


  • La cámara va a ser nuestros ojos. Van a mostrar la escena como si estuviéramos nosotros viendo con nuestros ojos los objetos 3D dibujados a nuestro alrededor.
  • En el desarrollo de videojuegos podemos hacer uso dos tipos de cámara diferentes:
  • Cámara ortográfica: Utilizada en los juegos 2D.
  • Cámara en perspectiva. Utilizada en los juegos 3D.
  • En este punto vamos a tratar solo la cámara en perspectiva.




Cámara en perspectiva

  • Este tipo de cámara va a 'dibujar' los objetos en perspectiva, en función de la distancia, de tal forma que los objetos más cercanos se verán más grandes y los objetos más lejanos se verán más pequeños.


LIBGDX UD4 Camara 10.jpg
En este gráfico, la cámara estaría situada en en cuadrado más pequeño, mirando cara a los triángulos. Después explicaremos más en detalle.
Como vemos existen dos triángulo, el triángulo rojo está delante del verde, por lo tanto debe de verse mayor (los dos tienen el mismo tamaño).
Veamos el resultado:
LIBGDX UD4 Camara 12.jpg


  • Veamos un poco más en detalle los parámetros que conforman una cámara en perspectiva.

Parámetros de una cámara en perspectiva

LIBGDX UD4 Camara 11.jpg


En la imagen anterior el ojo representa a una cámara.
Como vemos, de él 'surgen' rayos formando un pirámide que se va abriendo hasta el infinito.
  • Cuando configuramos una cámara en perspectiva podemos determinar desde que punto y hasta que punto se visualizan los objetos.
Fuera de estas dos 'distancias' los objetos no se dibujarán.
Esos dos puntos en la figura que representa el área de visualización (como una pirámide aumentando de tamaño), forman dos planos que es lo que se conoce como el plano near (el situado en el punto más cercano) y el plano far (el situado en el punto más alejado)
El área de visualización (lo que va a dibujar la cámara) se conoce como view frustum.
Normalmente cuando definimos una cámara 3D indicamos un punto muy cercano (por ejemplo, 0.3) y un punto muy lejano (1000) aunque este último va a depender del tipo de juego. Recordar que estamos hablando de 'unidades' en el mundo 3D que son los cuadrados que se ven en el suelo.
¿ Por qué limitar el área de visualización ? Para ahorrar recursos. Ten en cuenta que un objeto 3D muy alejado no será apreciado por la vista, pero el ordenador seguirá dibujándolo aunque no lo veas.


  • Existe otro parámetro que es el Field of View => FOV
LIBGDX UD4 Camara 14.jpg
Este parámetro es un ángulo y representa el ángulo de visión de la cámara sobre la proyección de los objetos.
Viene a ser como la apertura del objetivo de una cámara. Cuanto más grande, más abarcamos.
El valor por defecto es de 60 grados, pero dependiendo del juego puede ser que nos interese aumentarlo, como en los juegos de primera persona.
Cabe señalar que al disminuir el ángulo conseguimos un 'efecto zoom' como se ve en la imagen anterior.
IMPORTANTE: En la cámara en perspectiva tiene que cumplirse que 0 < near < far.


  • Por último tenemos el viewport.
En una cámara 3D, el viewport va a establecer la relación de aspecto (la relación que existe entre el ancho y el alto de la pantalla). Hablaremos de esto en el siguiente punto.
Indicar que en una cámara en perspectiva, el viewport va 'creciendo en tamaño' a medida que se aleja, a diferencia del viewport en una cámara ortográfica (2D) que siempre tiene el mismo tamaño.


  • En Unity3D podemos seleccionar la cámara en le ventana de jerarquía y podremos observar los valores de todos estos parámetros en la ventana inspector:
Unity3d camara 1.jpg
Podemos observar:
  • FOV tiene un valor de 60 grados.
  • El plano near está situado a 0.3 unidades de la cámara y el plano far a 1.000 unidades de la cámara. Por lo tanto la cámara va a 'dibujar' los objetos situados entre 0.3 y 1000 (es el view frustum)
  • La relación de aspecto va a ser 1 a 1 (valor del viewport). Vamos a explicar un poco más los valores del ViewPort:
  • Los valores (X,Y) del viewport se mueven entre 0-1 con decimales, separados por punto. Indican la posición del Viewport (lo que se va a visualizar) en la pantalla, siendo la posición (0,0) abajo-izquierda y (1,1) arriba-derecha. Así el valor (0.5,0) desplazaría el viewport hasta la mitad de la pantalla, apareciendo una banda negra por la izquierda.
  • Los valores W,H pueden tener cualquier valor ya que indican la relación de aspecto, pero Unity va a ajustar la imagen a cualquier pantalla al generar el ejecutable del juego. Si ponemos unos valores inferiores a 1 (entre 0-1 con decimales) tendremos el efecto de 'ver' el viewport en pequeño, siendo el valor 0 en el que no se ve nada y el valor 1 en el que el viewport ocupa toda la pantalla. Así, un valor de (0.5,1) haría que el viewport visualizara la mitad de la pantalla de ancho.



  • Si seleccionamos la cámara en la ventana de jerarquía podemos ver en la ventana de escena el área de visualización de la cámara que viene representado por unas líneas blancas que surgen de ella formando una pirámide:
Unity3d camara 2.jpg




Relación de aspecto

  • Todos estamos 'cansados' de escuchar algo como: Me voy a comprar una televisión 16:9, o voy a comprarme un monitor 16:10
Estos números representan la relación que existe entre el ancho (primer número) y el alto (segundo número)
Unity3d cam rel aspecto 1.JPG


  • Cuando alguien filmar una película lo hacemos utilizando la cámara con una relación de aspecto determinada.
¿ Qué pasa cuando intentamos visualizar lo filmado en una televisión con una relación de aspecto diferente a la original ?


Unity3d cam rel aspecto 2.JPG
Imagen obtenido de esta web
Que aparecen las temidas bandas negras :)
Esto es así para que la película no se deforme:
Unity3d cam rel aspecto 3.JPG
En esta imagen, el ancho de ha duplicado, pero la altura no, por lo que la imagen aparece 'deformada'.


  • En juegos pasa algo parecido, con la ventaja que los objetos son dibujados en el momento que se juega (no está filmado previamente, bueno, aunque puede ser que haya escenas animadas previamente hechas).


  • Lo que tenemos que tener claro es que la cámara tiene una relación de aspecto que se lo da el ViewPort en su valores W y H:
Esa relación de aspecto no tiene que ver con el dispositivo físico donde se va a ejecutar el juego.
Así, si ejecuto el juego en una pantalla con 16:9, en principio se vería deformado.
Para evitarlo, Unity (si no tocamos los valores por defecto) ajustará, al iniciar el juego, el ViewPort al tamaño de la pantalla para mantener la relación de aspecto.
En el caso de los juegos 3D esto llevará consigo que se verán diferentes zonas.


Unity3d cam rel aspecto 4.jpg


  • Podemos comprobar como quedaría el juego con diferentes relaciones de aspecto en Unity.
Unity3d cam rel aspecto 5.jpg
En este ejemplo, la pantalla donde se probaría el juego tiene una relación 16:9 mientras que el juego se estaría ejecutando con una resolución 4:3 (Unity permite establecer la resolución con la que se juega el juego que no tiene por qué coincidir con la pantalla completa.
Si cambiamos la relación de aspecto a 16:9 vemos que el juego ocupa toda la pantalla (tenéis que cambiar el tamaño de la ventana Game para que el juego ocupe todo):
Unity3d cam rel aspecto 6.jpg



Trabajando con varias cámaras a la vez

  • Lo visto anteriormente nos puede servir para crear juegos en los que tengamos que hacer uso de varias cámaras.
Crea una nueva escena de nombre Escena_UD2_CamaraPerspectiva_DosCamaras y cárgala presionando dos veces sobre ella.


  • Sigue los siguientes pasos:


  • Indicar que podemos mejorar el efecto y tener una imagen 'dentro de la cual' tendríamos la imagen de la cámara.
Imaginar un espejo o un retrovisor de un coche...
Para ello tendríamos que hacer uso de un Render Texture.




Layers


  • Las Capas o Layers son un recurso que nos proporciona Unity gracias al cual vamos a poder asociar a cada GameObject de la escena una capa.
Cada capa está referenciada por un nombre.


  • De esta forma el uso de capas nos va a permitir:
  • Que una determinada cámara solo visualice (dibuje) los gameobjects que estén asociados a la capa indicada.
  • Cuando veamos los 'rayos' a continuación, veremos que el uso de capas nos va a permitir restringir la detección de colisiones solo a los gameobjects que se encuentren en una capa determinada.
  • Que las luces solo tengan efecto (iluminen) aquellos objetos que se encuentren en una determinada capa.


  • Por defecto ya existen una serie de capas creadas y nosotros podemos añadir las que queramos hasta un máximo de 32.




Avanzado: Conceptos para programadores

  • Nota: Vuelve a cargar la escena Escena_UD2_CamaraPerspectiva.


  • Cuando trabajamos sobre el mundo 3D, hablamos de coordenadas (x,y,z) sin embargo nuestra cámara está visualizando 'pixeles' sobre una pantalla 2D (ancho-alto).
Es necesario tener algún mecanismo que nos permita 'pasar' de coordenadas de nuestro mundo (world cooordinates) a coordenadas de nuestra cámara (screen space).
  • Veamos las dos operaciones.


Paso de coordenadas desde ScreenSpace a WorldSpace

  • Imaginemos que queremos obtener la posición de pulsar el botón izquierdo del ratón en nuestro mundo 3D.
Para ello es necesario 'trasladar' nuestro punto (x,y) de la pantalla nuestro mundo 3D, pero necesitaremos indicar el plano sobre el que se va a proyectar.
Recordemos que nuestra cámara tiene dos planos definidos (near - far) pero podríamos proyector el punto sobre cualquier plano entre esos dos puntos (imaginar un cubo al que lo cortáis para para hacer sándwiches, cada corte es un plano al que podéis llevar el punto).


Veamos un ejemplo:
Crear un script de nombre 'UD_CamaraPerspectiva_1' y asociarlo a un cubo en la posición (0,0,0). Situar a la cámara en la posición (0,0,-8) y que se vea el cubo.
Unity3d camPers 1.JPG


Escribir este código en el script:
  1. public class UD_CamaraPerspectiva_1 : MonoBehaviour {
  2.  
  3.     private Camera cam;
  4.     private Vector3 posWorld;
  5.  
  6.   // Use this for initialization
  7.   void Start () {
  8.         cam = Camera.main;
  9.         posWorld = transform.position;
  10.   }
  11.  
  12.   // Update is called once per frame
  13.   void Update () {
  14.  
  15.         if (Input.GetMouseButtonDown(0)) {  // Pulsamos el botón izquierdo del ratón
  16.             posWorld = cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 8));
  17.             Debug.Log("Posición en el mundo:" + posWorld);
  18.         }
  19.         transform.position = posWorld;
  20.   }
  21. }
Si ahora pulsáis en la pantalla podéis ver como el cubo se mueve al punto pulsado.
Nota: transform.position es la posición del cubo. Lo veremos en el siguiente punto.
Fijarse que en el código tenemos puesto 8 como valor z al llamar al método ScreenToWorldPoint. Esto es así ya que la distancia desde la cámara al cubo es de 8 unidades hacia adelante de la cámara (la cámara está situada en la posición -8):
Unity3d camPers 2.JPG


Podemos observar en la pestaña de consola cuales son las coordenadas (x,y,z) de nuestro punto en el mundo en el plano z=8 unidades:
Unity3d camPers 3.JPG


  • Veremos más adelante que Unity (y todos los motores/frameworks) tienen mecanismos para implementar rayos de colisión. Un rayo es un punto con una dirección que atraviesa nuestro mundo 3D. Es como si dispararas una flecha en línea recta desde donde tocas la pantalla hacia el interior de nuestro mundo 3D. Parte del punto de la pantalla donde pulsamos y 'atraviesa' todos los planos de nuestro mundo 3D hasta un plano determinado (normalmente empieza en el plano near de la cámara y acabaría en el plano far de la misma, ya que es el área de visualización) siguiendo una dirección. Más información en este punto.




Paso de coordenadas desde WorldSpace a ScreenSpace

  • En el caso contrario a lo visto anteriormente. Pasamos de coordenados en nuestro mundo 3D a coordenadas del monitor en pixeles.
Debemos de tener en cuenta que para la cámara, la coordenada (0,0) se encuentra abajo-izquierda mientras que las coordenadas en la pantalla, la coordenada (0,0) está arriba a la izquierda. Por tanto, cuando obtengamos la coordenada de pantalla a partir de la coordenada 3D debemos de hacer la operación: Screen_Height-Y.


  • Modificar el código anterior:
  1. public class UD_CamaraPerspectiva_1 : MonoBehaviour {
  2.  
  3.     private Camera cam;
  4.     private Vector3 posWorld;
  5.  
  6.     // Use this for initialization
  7.     void Start () {
  8.         cam = Camera.main;
  9.         posWorld = transform.position;
  10.  
  11.     }
  12.  
  13.     private void OnGUI()
  14.     {
  15.         Vector2 posPantalla = cam.WorldToScreenPoint(transform.position);
  16.         posPantalla.y = Screen.height - posPantalla.y;
  17.         Rect areaVisual = new Rect(posPantalla, new Vector2(50, 20));
  18.  
  19.         Debug.Log("Posición en pantalla:" + posPantalla);
  20.  
  21.         GUILayout.BeginArea(areaVisual);
  22.         GUI.color = Color.yellow;
  23.         GUILayout.Label("CUBO");
  24.         GUILayout.EndArea();
  25.  
  26.     }
  27.  
  28.     // Update is called once per frame
  29.     void Update () {
  30.  
  31.         if (Input.GetMouseButtonDown(0)) {  // Pulsamos el botón izquierdo del ratón
  32.             posWorld = cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 8));
  33.  
  34.         }
  35.         transform.position = posWorld;
  36.     }
  37. }
  • Línea 15: Pasamos de coordenadas 3D a coordenadas de pantalla (pixeles)
  • Línea 16: Debido a que el punto (0,0) de la cámara es diferente al de la pantalla debemos de restar el alto de la misma.
  • El resto del código ya lo iremos viendo, pero básicamente crea un rectángulo donde dibuja un Label de color amarillo con la palabra 'Cubo'.
Si ejecutáis el código anterior, al pulsar sobre la pantalla podéis ver como el cubo se traslada al punto y aparece la palabra 'Cubo' siempre sobre él:
Unity3d camPers 4.JPG




Rayos de detección de choques

  • Crea una nueva escena de nombre Escena_UD2_CamaraPerspectiva_Rayos.
Carga dicha escena pulsando dos veces sobre la misma.
Recuerda moverla (o crearla directamente) sobre la carpeta 'Assets/Scenes' de la ventana 'Project Window'.
Agrega a dicha escena un objeto 3D Plane y cambia la escala a 10x10x10.
Sitúa la cámara en una posición en la que se vea todo el plano.


  • Veremos en otras secciones la forma que tiene Unity (y otros motores) de detectar 'colisiones' entre diferentes GameObjects.
En esta sección veremos como hacer uso de los rayos.
Un rayo es una línea recta que se desplaza desde una posición inicial siguiendo una dirección.
Al desplazarse por el espacio 3D va atravesando diferentes GameObjects.


  • Un ejemplo de uso es cuando queremos disparar hacia un enemigo y 'presionamos' con el ratón sobre el mismo.
Para comprobar si le hemos dado, el programador debe de crear un rayo que vaya desde la posición inicial (en este caso sería desde la cámara) hacia el punto indicado por el ratón.
Nota: Indicar que podríamos hacer uso del método anterior para 'pasar' del punto x-y del ratón al espacio 3D, pero necesitaríamos saber la distancia Z para llevar el punto al plano exacto donde se encuentra la posición del ratón, por lo que es mucho más complicado.


Unity3d camara ray 1.jpg
En la imagen anterior podemos ver como al presionar sobre el plano, queremos 'lanzar' un rayo desde la perspectiva de la cámara y ver en que posición choca con el suelo.


  • Nota Importante:
Ya hablaremos en otra sección de los 'collider'. Para adelantar digamos que son figuras 3D que rodean a los GameObjects y que nos van a permitir gestionar los choques entre diferentes gameobjects.
Para que funcione el registro de la 'colisión' entre un rayo y un objeto 3D, es necesario que este tenga algún tipo de collider asociado (existen varios tipos, ya los veremos).
Por defecto, cando agregamos un plano, ya crea un collider asociado:
Unity3d camara ray 1B.jpg



  • Previamente a ver el código que nos permite detectar el choque, vamos a realizar una optimización que va a consistir en lo siguiente: Cuando lanzamos un rayo este puede atravesar múltiples 'gameobjects' de nuestro juego, pero seguramente a nosotros solo nos interesa detectar los choques 'contra ciertos gameobjects' en concreto. Por ejemplo, en nuestro ejemplo, solo nos interesa detectar el punto de choque con el suelo.
Para ello podemos hacer uso de Layers.
Los Layers son capas que están identificadas con un nombre y que asociamos a diferentes GameObjects.
Vamos a crear una nueva capa (layer) y la asociaremos con el suelo (es un Plane, escalado a 10x10x10) del ejemplo.


  • Ahora vamos a crear un nuevo script de nombre UD2_CamaraPerspectiva_Rayos
Unity3d camara ray 5.jpg
  • El código del script sería el siguiente:
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. public class UD2_CamaraPerspectiva_Rayos : MonoBehaviour {
  6.  
  7.     // Identificador de la capa (Layer) creada previamente sobre la que queremos detectar el choque
  8.     private int idCapa;
  9.     // Objecto que nos va a servir para obtener información sobre el choque del rayo con el suelo
  10.     private RaycastHit raycastHit;
  11.     // Distancia del rayo
  12.     private float distanciaRayo;
  13.  
  14.         // Use this for initialization
  15.         void Start () {
  16.  
  17.         idCapa = LayerMask.GetMask("Suelo");   // Podríamos poner varias separadas por coma...
  18.         distanciaRayo = 100;
  19.         }
  20.        
  21.         // Update is called once per frame
  22.         void Update () {
  23.         if (Input.GetMouseButtonDown(0))
  24.         {
  25.             Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition); // Crea un rayo desde la posición del ratón en la dirección de la cámara
  26.             if (Physics.Raycast(camRay, out raycastHit, distanciaRayo,idCapa))
  27.             {
  28.                 Debug.Log("Posición choque:" + raycastHit.point);
  29.                 Debug.DrawRay(Camera.main.transform.position, camRay.direction*50, Color.red,10,false);
  30.             }
  31.         }
  32.  
  33.     }
  34.  
  35. }
  • Líneas 8,10,12: Definimos una serie de variable que vamos a necesitar.
  • El id del layer 'Suelo' creado previamente y asignado al plano.
  • Un objeto de la clase RaycastHit que nos va a servir para obtener el punto de 'choque' del rayo con el suelo.
  • Especificamos la distancia del rayo, ya que si no, este se desplazaría a lo largo del view frustrum y no es necesario.
  • Línea 17: Obtenemos el identificador de la capa (Layer) Suelo.
  • Línea 18: Establecemos una distancia máxima para el rayo de 100 unidades.
  • Línea 23: Comprobamos si pulsamos el botón izquierdo del ratón.
  • Línea 25: Creamos un rayo que vaya desde la cámara hasta la posición del ratón.
  • Línea 26: Hacemos uso de la clase Phisics para llamar al método de clase Raycast el cual recibe como parámetros: el rayo, un objeto de la clase RayCastHit de tipo output, esto quiere decir que se van a guardar datos en dicho objeto, la distancia del rayo y el id del Layer que queremos que se tenga en cuenta. Este método va a devolver un boolean indicando si hubo o no choque.
  • Línea 28: En caso de choque, podemos saber el punto 3D del mismo con la propiedad 'point' del objeto raycastHit.
  • Línea 29: Dibuja el rayo saliendo de la cámara. Esto solo se debe de hacer mientras estamos en la fase de desarrollo. Dibuja una línea solo visible en la ventana Scene. El rayo tendrá un tamaño de 50 unidades (camRay.direction*50), tardará 10 segundos en desaparecer, será de color Rojo y saldrá de la cámara. El último parámetro indica que el rayo no se vea afectado por otros GameObjects que haya cerca.


  • Si asociamos el script creado al GameObject 'Suelo' (solo hay que arrastrarlo gráficamente), al pulsar sobre el plano en ejecución podemos ver el punto de choque en la ventana Console:
Unity3d camara ray 6.jpg



-- Ángel D. Fernández González -- (2018).