• Braulio Madrid

Cómo hacer cáusticas usando un proyector


¿Qué son las causticas?, es el efecto que se produce cuando la luz atraviesa las crestas de la olas del agua, provocando que se refleje en la superficies duras, generando un patrón como telarañas de luz entrelazados y en movimiento en el caso del agua. Pero el nombre no lo recibe el efecto, sino el patron de red luz que se produce, otro ejemplo seria la luz al rojo vivo que produce la lava de un volcán, cubierto por costras de ceniza, esto genera ese patron de red.


En esta oportunidad te enseñaré como hacer las causticas del agua de una manera relativamente simple, la idea es poder completar en el futuro un shader que pueda simular el agua con relativo realismo.


Por ahora comenzaremos con lo mas sencillo, que es hacer un proyector que nos anime una textura y que esa textura permanezca en el mismo sitio aunque movamos el proyector.


Puedes leer este articulo de programación en CG para ver al detalle como hacer un proyector, también puedes leer este otro articulo de la misma pagina para texturizar en el mundo, en lugar de hacerlo por objeto. Los artículos están en ingles así que acá encontrarás un resumen en español de ambos artículos.


Preparando la escena.


Lo primero que vamos a hacer es crear una escena nueva, le agregamos un plano con cualquier material y algunos objetos distintos como esferas o cubos, estos solo son las superficies donde se reflejará el proyector.


Unity tiene un componente que precisamente se llama projector, que lo que hace es tomar un material y reflejarlo en la superficie de otro, el punto es que normalmente los proyectores agregan luz a las superficies y si se hace con un material común, este solo reemplazará la textura de la superficie con la que contenga el material del proyector.


  • Vamos a crear un objeto vacío y le añadimos el componente projector.

  • En la ventana de assets vamos a crear un material con el nombre que quieran, en mi caso lo llamaré projector.

  • Creamos un shader con el mismo nombre si quieren.


Preparando el shader


Lo primero es descargar una textura de causticas tileada, pueden usar esta imagen para el propósito.


Estas son las propiedades que necesitaremos.

Shader "Unlit/Projector"
{
Properties{
    _MainTex ("Texture", 2D) = "white" {}
    _Scale ("Scale", Float) = 1.0	}
    ...
    uniform sampler2D _MainTex;
    fixed _Scale;

Estas son algunos de las propiedades que necesitaremos para indicarle como debe comportarse nuestro subshader.


En primer lugar la mezcla, debe ser One One o SrcAlpha One, esto le dará un pase aditivo a nuestro shader, un proyector siempre se inicia un paso después de que los objetos sólidos ya han sido renderizados.


Como no necesitamos escribir nada en profundidad, desactivamos ZWrite

Para evitar el Z Fighting, o la pelea que se produce cuando 2 caras de un objeto se encuentran en la misma posición, para evitar esto usamos la propiedad Offset

SubShader	{
	Blend One One
	ZWrite Off
	Offset -1, -1  // evite las peleas en profundidad (debe ser "Offset -1, -1")
	Tags { "RenderType" = "Opaque" }
	LOD 100

Aquí me voy a detener a explicar algo sobre los espacios en que se mueven los vertices o las texturas. Usted puede hacer una función de vértice donde no posicione nada en el, este automáticamente asume que el objeto en cuestión estará frente a la cámara todo el tiempo, luego ya hay otros espacios como el espacio del mundo que es una matriz inamovible, pero que en realidad si lo hace, la que es inamovible es la cámara, la que se mueve es el mundo ante nuestros ojos, o la matriz de ese mundo, luego hay otra matriz que es la matriz del objeto, que por lo general resta su posición y su rotación de esta matriz mundial, por eso unity tiene algunas variables que ya contienen estas matrices calculadas, posiblemente no quedó muy claro, la idea que quiero que entiendas es que los motores de videojuegos les es mas fácil calcularlo todo con matrices, en lugar de usar vectores, las tarjetas gráficas se especializan en multiplicar matrices.


Unity_ObjectToWorld; //translada la matriz del objeto al mundo,
Unity_WorldToObject; //translada la matriz del mundo al objeto,

Si quiere indagar mas de este tema vaya al manual de shaders de unity Lo que ahora nosotros necesitamos es usar usar la matriz mundial y la matriz del objeto proyector.


Necesitamos la estructura de entrada que solo contiene la posición de los vertices en verInput, como salida necesitamos mandarle la posición del objeto, y las coordenadas uv del mundo, con esto ya es suficiente para hacer el calculo de la posición de nuestro objeto y la posición del mundo, por alguna razón aunque no necesitemos la posición del objeto, este se hace necesario o el proyector se perderá, no termino por entender porque, a veces los shaders tiene algunos detalles inexplicables, pero eso hace parte de la magia.

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
...
uniform float4x4 unity_Projector; // transformation matrix 
...
struct vertInput {
	fixed4 pos : POSITION;};

struct vert2frag {
	fixed4 pos : SV_POSITION;
	fixed4 worldPos : TEXCOORD1;
	};
	
vert2frag vert(vertInput v) {
	vert2frag o;
	// posicion local del objeto
	o.pos = UnityObjectToClipPos(v.pos);
	// posicion mundial del objeto
	o.worldPos = mul(unity_ObjectToWorld, v.pos);
	return o;	}

Por ultimo solo queda hacer la función de fragmento, donde usaremos nuestra textura 3 veces para generar esa sensación de movimiento, fíjese que uso las coordenadas globales en X y Z para ubicar la textura y proyectar hacia el suelo, también es posible proyectarla al techo, pero por eso está ese if que evita que se proyecte al techo, el componente w de la matriz solo puede valer 1 o -1, 1 es hacia el frente, -1 es detrás.


Uso el plano X y Z a la que le sumo la variable de unity _SinTime o _CosTime en sus distintos componente, lo importante es que sean distintos para que dé la ilusión de movimiento aleatorio, tomo 3 muestras distintas de la misma imagen y las promedio, pero usted puede optar por multiplicarlo por algún flotante.


half4 frag(vert2frag i) : COLOR{
if (i.worldPos.w > 0.0) // frente al proyector?
{
	fixed4 caustic = tex2D(_MainTex , (i.worldPos.xz + _SinTime.zw) * _Scale);
	fixed4 caustic2 = tex2D(_MainTex, (i.worldPos.xz + _CosTime.xy) * _Scale);
	fixed4 caustic3 = tex2D(_MainTex, (i.worldPos.xz + _CosTime.zw) * _Scale);
	return (caustic + caustic2 + caustic3)/3;			}
else // detrás del proyector
{
	return float4(0.0, 0.0, 0.0, 0.0);
}
}

Eso es todo por esta vez, como siempre les dejaré el código completo. No se preocupen si el resultado sale distinto, ya que yo modifiqué la textura arrastrando los canales 5 pixeles, bajo mi punto de vista considero que las crestas de las olas se vuelven como un prisma que divide la luz en los distintos espectros, pero ustedes pueden dejarlo como quieran.


Shader "Unlit/Projector"{
Properties	{
	_MainTex ("Texture", 2D) = "white" {}
	_Scale ("Scale", Float) = 1.0	}
	SubShader	{
	Blend One One
	ZWrite Off
	Offset -1, -1  // evite las peleas en profundidad (debe ser "Offset -1, -1")
	Tags { "RenderType" = "Opaque" }
	LOD 100
	
	Pass	{
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		
		uniform sampler2D _MainTex;
		fixed _Scale;
		
	struct vertInput {
		fixed4 pos : POSITION;
		};
	struct vert2frag {
		fixed4 pos : SV_POSITION;
		fixed4 worldPos : TEXCOORD1;
		};
	vert2frag vert(vertInput v) {
		vert2frag o;
		// posicion local del objeto
		o.pos = UnityObjectToClipPos(v.pos);
		// posicion mundial del objeto
		o.worldPos = mul(unity_ObjectToWorld, v.pos);
		return o;
		}
half4 frag(vert2frag i) : COLOR{
	if (i.worldPos.w > 0.0) // in front of projector?
	{
fixed4 caustic = tex2D(_MainTex , (i.worldPos.xz + _SinTime.zw) * _Scale);
fixed4 caustic2 = tex2D(_MainTex, (i.worldPos.xz + _CosTime.xy) * _Scale);
fixed4 caustic3 = tex2D(_MainTex, (i.worldPos.xz + _CosTime.zw) * _Scale);
	return (caustic + caustic2 + caustic3)/2;
	}
	else // behind projector
	{
		return float4(0.0, 0.0, 0.0, 0.0);
	}
}
ENDCG
}
}
}

Nos vemos en otro Blog.

© 2023 por Darkcom. Proudly created with Wix.com | Peñol, Antioquia, Colombia| +57 3113389725 | Politica de privacidad

  • GitHub
  • BMadrid
  • DarkcomDev
  • Darkcom.dev
  • Braulio-Madrid
  • YouTube Darkcom Tech
This site was designed with the
.com
website builder. Create your website today.
Start Now