Una de las cuestiones que me he complicado durante mucho tiempo, pensando que eran un misterio o que eran difíciles era el hecho de usar passes, pero nada mas alejado de la realidad, los passes es de lo mas sencillo de usar.
Lo primero que hay que entender es que los passes son muy parecido a usar capas en photoshop, que entre mas abajo esté el pase, mas se superpone a la anterior, por ejemplo: en el primer pase puedes poner un objeto de color rojo y luego en el segundo pase puedes poner el mismo objeto de color verde, el resultado final es que el objeto quedará de color verde.
Otra cosa que hay que entender es que hay dos tipos de passes, el primero es el llamado Pass{} a secas, este puede tomar un objeto aparte, tratarlo y luego ser usado mas adelante, el Pass tiene un atributo name "nombre del pase", este atributo le da una identidad, para luego ser invocado, por ejemplo: imagina que tienes 3 shaders distintos, el primero es un shader capaz de hacer blur, otro capaz de hacer un efecto vignette y otro capas de teñir la pantalla con sangre, pero resulta que quieres combinar los tres shaders pero no quieres volver a escribir todo el código de nuevo. entonces a cada shader le pones un name
Pass{name "blur"}
Pass{name "vignette"}
Pass{name "blood"}
Luego haces un shader nuevo que sea capaz de llamar a cada shader por su parámetro name
UsePass "Image FX/Blur/blur"
UsePass "Image FX/Vignette/vignette"
UsePass "Image FX/Blood/bood"
Como puede suponer en las comillas va el nombre del parámetro Shader que es el que usamos para generar las ruta del shader concatenado con "/" nombre del pase.
Un ejemplo de uso de este tipo de pase el que mostré para combinar efectos previamente hechos para no reescribir código. Pero existe otro uso que se le da y es el de crear varias versiones de un tipo de shader, para buscar la mejor compatibilidad con un dispositivo, entonces en este caso el shader que los usa iría con pre-procesadores para seleccionar la opción mas compatible.
El otro tipo de pase, es un tipo de pase muy particular, se llama GrabPass{} este tipo de pase toma un screenshot de lo que tiene la cámara, para luego ser usado como textura, este tipo de pase es muy común para hacer representaciones de vidrio o hielo que puede llegar a deformar las coordenadas uv del GrabPass{}
Es recomendable asignarle un nombre al GrabPass{"Name"} para que el screenshot sea de usa porción pequeña de solo la fracción del objeto que estemos usando.
Por ultimo anotar que el usar passes penaliza el rendimiento y en dispositivos móviles se nota, así que procura usar la menor cantidad de passes o buscar en lo posible resolverlo usando solo un pase.
Un Ejemplo practico:
Recuerdan el shader de desenfoque que creamos hace un tiempo, si recuerdan ese shader se veía al cheapy, lo volveremos a usar para mejorarlo y que se vea un poco mas profesional. Si no lo recuerdan les dejo el link para como se hace el desenfoque gaussiano.
Shader "Image FX/Terrible Simple Blur" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Distance("Distance", Float) = 0.5
}
SubShader {
ZWrite Off
Pass{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
#include "IFX.cginc"
uniform sampler2D _MainTex;
fixed _Distance;
fixed4 frag(v2f_img i) : COLOR {
return Blur(_MainTex,i.uv,_Distance);
}
ENDCG
}
Quizás este shader ha cambiado un poco porque desde la ultima vez la función de blur la separamos aparte dentro de el archivo IFX.cginc, si quieres saber el porque o como separa la funcionalidad en archivo aparte, te dejo el link para que revises como se hace un archivo CGINCLUDE
Como puede ver aquí solo hay una capa de desenfoque y si aplico mucha distancia se ve mal, ahora vamos a agregarle 2 pases mas.
GrabPass{"_BLUR"}
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
#include "IFX.cginc"
uniform sampler2D _BLUR: register(s0);
fixed _Distance;
fixed4 frag(v2f_img i) : COLOR {
#if UNITY_UV_STARTS_AT_TOP
fixed2 uv = fixed2(i.uv.x,1 - i.uv.y);
#else
fixed2 uv = i.uv;
#endif
return Blur(_BLUR, uv, _Distance/2);
}
ENDCG
}
}
FallBack "Diffuse"
}
Hemos agregado el GrabPass{"_Blur"} para identificarlo, ademas de hacer que rinda un poco mejor, el nombre _Blur, será usado en un Sampler2D del mismo nombre.
uniform sampler2D _BLUR: register(s0);
Algo que hay que anotar es el uso de la palabra reservada register(s0); los registros son como hacer que una variable de uso temporal quede regitrada, en este caso _Blur es una textura tomada directamente desde el mismo shader en tiempo real y esta no es enviada desde la CPU, por eso se hace necesario registrarla, también hay que anotar es que hay un limite de registros según el shader level que soporta el dispositivo, si mal no recuerdo shader model 2, soporta 4 registers, shader model 3 soporta 8 o 16, y de ahí en adelante puede subir una barbaridad, pero no veo necesario el uso de mas de 2 registers.
Luego vienen unas lineas muy comunes que quizás alguna vez hayan visto
Resulta que no podemos copiar tal cual el shader porque lo voltea de cabeza, este error es común sobretodo cuando activas suavizadores de bordes como el #MSAA o el #antialiasing, entonces unity tiene que calcular según que api donde comienza las #coordenadasUV.
Por ultimo la linea que hace todo el trabajo.
return Blur(_BLUR, uv, _Distance/2);
Como puede apreciar, el método desenfoca no a partir de la textura _MainTex que manda la cámara por primera vez, sino desde el screenshot obtenido del GrabPass{"_Blur"} y si observa la distancia se ha dividido en dos, esto es porque si desenfoco obtengo casi el mismo resultado cheapy de la vez pasada.
Ahora se preguntará porque no simplemente desenfocar mas el _MainTex del primer pase. Créame que lo intenté pero no logro el mismo efecto de desenfoque, el resultado puede verse suavizado, pero se ve muy nítido. Así que como conclusión llegue a que el desenfoque cheapy debe ser usado para dispositivos móviles si o si, aunque por ahí circulan métodos muy raros en los que previamente se usa una #renderTexture de mala calidad para simular desenfoque.
Este es el final de este blog, este es un ejemplo de como implementar #Passes, pero quiero que ustedes lo hagan usando el UsePass "x/blur", también es el final de los tutoriales de efectos de imagen por ahora, quien haya seguido los tutoriales ya tiene herramientas suficientes para hacer sus propios shaders bastante decentes, los shaders de efectos de imagen es solo la superficie de los shader vertex fragment, los shaders de superficie es solo un atajo nuevamente de los shaders vertex fragment, esto indica que de aquí en adelante si quieres lograr algo mas especifico o tener mas control, toca si o si aprender a escribir shaders vertex fragment.
De ahora en adelante si escribo un tutorial de efectos de imagen, ya será porque encontré alguna aplicación curiosa, lo mismo va para los de superficie, aunque escribo estos blogs pensando en los demás, a quien debo rendirle cuentas es a mi mismo, mientras escribo estos blogs también refuerzo conocimiento.
No siendo mas nos vemos en otro blog, hasta otra.
Comentarios