top of page
Foto del escritorBraulio Madrid

Peces, como hacer shaders animados.


Algo que le da vida a nuestro juego es que haya movimiento, pero el movimiento en juegos es penalizado, por eso recurrimos a técnicas gráficas que nos permita conservar la ilusión, hacer que nuestros juegos se vean más detallados y nos den ganas de vivir dentro de ese mundo virtual.


Crearemos un shader que anima los vértices, que ya en otras ocasiones he hecho varios ejemplos en árboles o en el agua, pero esta vez me basaré en el shader de otro blog para traerlo al español y darle mi toque personal.


En el próximo blog mejoraremos el resultado de este agregándole luces y sombras para tratar de alcanzar la calidad de un shader de superficie.


Preparación de la escena.


  • Modela o descarga un pez en programa de modelado 3D, puedes descargar este modelo gratuito.

  • Arrastra el modelo dentro del apartado Project en unity.

  • Retira, todas las animaciones, esqueletos y materiales que contenga el modelo.

  • Retira todas las luces y cámaras que pueda contener el modelo.

  • Crea una nueva escena.

  • Arrastra el modelo del pez a la escena.

  • Crea un nuevo material y arrástrelo al modelo del pez en la escena.

  • Crea un nuevo shader de vértices y fragmentos y arrástrelo al material que ha creado y edite el shader.






Propiedades.


Realmente no hay nada especial que decir acerca de las propiedades, salvo que vamos a lograr que este pez pueda ondularse, para eso están las propiedades que comienzan con la palabra Wave, _WaveSpeed actúa como velocidad, _WaveHeight actúa como Amplitud, _Wave density actúa como frecuencia.


_Threshold es el umbral donde comienza el efecto dentro del modelo.


Las propiedades Stride, hacen que el modelo se mueva por completo, oscilando de izquierda a derecha.



Shader "VertexFragment/AnimatedFish"
{
Properties
{
    _MainTex("Texture", 2D) = "white" {}
    _EffectRadius("Wave Effect Radius",Range(0.0,1.0)) = 0.5
    _WaveSpeed("Wave Speed", Range(0.0,100.0)) = 3.0
    _WaveHeight("Wave Height", Range(0.0,30.0)) = 5.0
    _WaveDensity("Wave Density", Range(0.0001,1.0)) = 0.007
    _Threshold("Threshold",Range(0,30)) = 3
    _StrideSpeed("Stride Speed",Range(0.0,10.0)) = 2.0
    _StrideStrength("Stride Strength", Range(0.0,20.0)) = 3.0
}

Tags, propiedades y preprocesadores.


Las aletas de nuestro pez en realidad es un trozo de textura transparente, por lo tanto es necesario que nuestro shader se renderice de forma transparente.


Como existe el conflicto con los proyectores, porque nuestro modelo anima vértices, dejando un modelo fantasma que es el que recibe las cáusticas del proyector, por ese motivo es mejor ignorar todos los proyectores.


Nuestro modelo muestra la parte posterior de las aletas, pero no la parte interior, por eso desactivamos el sacrificio, esto puede penalizar un poco el rendimiento, pero si comparte material con otros peces, no se notará.


El único tag importante es seria multi_compile_fog, que lo usaremos para activar la posibilidad que nuestro pez sea afectado por la niebla.


De no ser que estamos incluyendo la posibilidad de que nuestro modelo sea afectado por la niebla, podríamos prescindir de UnityCG.cginc.


En la estructura de entrada appdata es necesario obtener los datos de la posición de los vértices y las coordenadas uv, normalmente cuando se hace un shader de vértices y fragmentos básico, usamos la estructura que nos proporciona unity como appdata_base o la versión completa appdata_full.


En la estructura de salida v2f, pasaremos los datos de posición de los vértices y las uv, pero además incluimos las coordenadas uv para la niebla.

SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" }

Cull Off
Blend SrcAlpha OneMinusSrcAlpha
Pass

{
CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #pragma multi_compile_fog
    #include "UnityCG.cginc"
    
    struct appdata
    {
    	float4 vertex : POSITION;
    	float2 uv : TEXCOORD0;
    };
    
    struct v2f
    {
    	float2 uv : TEXCOORD0;
    	UNITY_FOG_COORDS(1)
    	float4 vertex : SV_POSITION;
    };
    
    sampler2D _MainTex;
    float4 _MainTex_ST;
    half _EffectRadius;
    half _WaveSpeed;
    half _WaveHeight;
    half _WaveDensity;
    int _Threshold;
    half _StrideSpeed;
    half _StrideStrength;


Programa de vértices.


En shaders anteriores, normalmente me he apoyado en texturas para aportar los datos de movimiento y manipulando la escala de la textura, obtengo su frecuencia y simplemente le sumo el tiempo para obtener el efecto de velocidad, este enfoque me evita el tener que usar funciones trigonométricas y ahorrarme algunos cálculos a costo de ocupar un poco la parte de la memoria de la gráfica.


En este shader usaremos el enfoque matemático para ondear este pez, en este caso aplicaremos 2 movimientos locales, un movimiento por vértice del modelo y un movimiento a todo el modelo


half sinUse = sin(-_Time.y * _WaveSpeed + v.vertex.z * _WaveDensity); multiplicamos la variable del tiempo que nos proporciona unity por la velocidad, luego multiplicamos la posición del vértice Z por la frecuencia.


Tenga en cuenta que el eje según en hacia que dirección haya modelado su pez, puede usarse el eje X en lugar de Z, por lo que deba de modificar el shader según el eje del modelo.


Solo con esta función Seno básicamente nos bastaría, pero resulta que los peces al nadar mueven muy poco la cabeza y mueven más la cola, por lo que es necesario hacer un gradiente como la que se muestra en la imagen.


half yDirScaling = clamp(pow(yValue * _EffectRadius,_Threshold),0.0,1.0); El gradiente del que hablo es esta línea, donde _Threshold controla el centro del gradiente que va desde 0 a 1, _EffectRadius controla la cantidad de gradiente que afectará la función Seno que habíamos calculado. Otra alternativa que hubiera usado, seria pintar los vértices dentro del modelo, permitiéndome mayor control sobre los detalles.


v.vertex.x += sinUse * _WaveHeight* yDirScaling; Con esta línea hacemos efectivo el movimiento de los vértices del pez, donde multiplicamos la función Seno con la Amplitud para obtener mayor fuerza en el movimiento y lo atenuamos multiplicándolo con el gradiente calculado.


Pero los peces no solo serpentean, se mueven dentro de las corrientes de agua, lo que hace que oscilen de izquierda a derecha, por eso al resultado le sumamos la siguiente linea.


v.vertex.x += sin(-_Time.y * _StrideSpeed) * _StrideStrength; Multiplicamos la variable del tiempo con la velocidad dentro de una función seno, para que oscile, luego a la resultante lo multiplicamos con la amplitud o fuerza en este caso para que el movimiento sea más notable.


Por último transferimos los vértices a la matriz local con UnityObjectToClipPos, transferimos las coordenadas UV y las coordenadas de la niebla.

v2f vert(appdata v)
{
    v2f o;
    half sinUse = sin(-_Time.y * _WaveSpeed + v.vertex.z * _WaveDensity);
    half yValue = v.vertex.z - _Yoffset;
    half yDirScaling = clamp(pow(yValue * _EffectRadius,_Threshold),0.0,1.0);
    v.vertex.x += sinUse * _WaveHeight* yDirScaling;
    v.vertex.x += sin(-_Time.y * _StrideSpeed) * _StrideStrength;
    
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

Programa de fragmentos.


Aquí no hay nada en especial, simplemente aplicamos la textura y la niebla. Si ha seguido atentamente las lineas hasta ahora, ha podido notar varias macro referentes a la niebla como #pragma multi_compile_fog que habilita la aplicación de la niebla, UNITY_FOG_COORDS(TEXCOORD int) que asigna coordenadas uv para la aplicación de la niebla, UNITY_TRANSFER_FOG(output struct, output vertex) que transfiere la posición de los vértices y UNITY_APPLY_FOG(input fog uv coords, final color) que aplica finalmente el color de la niebla al resultado del color.


fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex,i.uv);
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

        ENDCG
        }
    }
}

Conclusiones.


Este shader solo aplica el movimiento de vértices, solo adornándolo un poco con el color de la niebla, la intención principal era la de aplicar movimiento a los vértices, aunque en otras ocasiones he aplicado movimientos de vértices en shaders de superficie, la forma correcta es poder aplicarlo a shaders de vértices y fragmentos.


La segunda intención era poder mostrar como aplicar niebla totalmente compatible y de la forma que unity recomienda que lo hagamos. La niebla no es necesaria en shaders de superficie, porque ya lo aplica por defecto, pero en shader de vértices y fragmentos, hay que hacerlo de forma manual. Si desea saber más de como aplicar niebla, puede ver este link.


Aunque la niebla que nos proporciona unity nos da problemas de rendimiento en dispositivos móviles, debido a que la coloración de la niebla lo hace en el programa de fragmentos. Quizás un día dedique a hacer un tutorial de como aplicar niebla eficiente para móviles.


No siendo más, nos vemos en el próximo Blog.

120 visualizaciones0 comentarios

Comments


bottom of page