top of page

Shader para los árboles 2da parte, Programa de vértices y superficie

Programa de vértices:


A continuación sigue el programa de vértices, que tiene como parámetro el struct appdata_full cedido por parte de "UnityCG.cginc"


Normalmente los cálculos para el viento son hechos con funciones que calculan en tiempo real un #perlinNoise separado por cada eje y usando al tiempo una transformada de #Fourier, que es la combinación de ondas sinusoidales a distintas velocidades.


Mi enfoque es muy distinto, en mi caso uso una textura que viene siendo el reemplazo del ruido y uso la posición del vértice en el ámbito mundial como coordenadas UV y los multiplico por la escala(Amplitud) y le sumo la velocidad(Frecuencia). Este enfoque es más simplista, pero es más eficiente en términos de cálculos, a los dispositivos móviles les aterra el uso de funciones trigonométricas, aquí solo hago uso de uno solo.




Falta anotar qué para lograr esto, se debe tener en cuenta que la geometría del objeto debe tener los vértices pintados con colores, los componentes de rojo, verde y azul debe coincidir con los ejes en el mundo donde rojo es (eje X), verde es (eje Y) y azul es (eje Z), para pintar los vértices debes recurrir a algún programa de modelado 3D que te permita hacerlo.


void vert(inout appdata_full v)
{
//Calculos para el viento
float4 wPos = mul(unity_ObjectToWorld, v.vertex);				float4 tex = tex2Dlod(_NoiseTex, float4((wPos.xz * _Scale) + (_SinTime.x * _Speed), 0, 0));

Los cálculos de la colisión, no es realmente una colisión, sino una ilusión donde usando un vector se logra que las hojas se doblen en algún sentido evitando el vector, este enfoque solo sirve para un solo personaje, pero existen otros enfoques donde usan una textura que se crea en tiempo real sobre una porción del mundo y genera el mismo efecto pero con múltiples personajes.


Para calcular la colisión debemos hallar dos puntos y hallar su distancia, el primero es hallar el centro del objeto del mundo, esto se logra multiplicando la posición del objeto(unity_ObjectToWorld) con el vector cero(float4(0,0,0,1));


Luego hayamos el punto que le asignamos a nuestro personaje (_GLOBALposition) que será el que afectará la geometría de las hojas restando el centro del objeto.


Hallar la distancia del personaje respecto al objeto es necesario para poder aplicar el efecto, que tanto se logran afectar las hojas.


//Calculos para la colision 
float4 centerObjectWorld = mul(unity_ObjectToWorld, float4(0, 0.0, 0, 1));
half2 direction = normalize(centerObjectWorld.xz - _GLOBALposition.xz);				float dist = distance(wPos, _GLOBALposition);				half effect = 1 - saturate(dist / _RangeEffect);

Ya teniendo todo calculado, vamos a la aplicación, que es sumarle a la posición del vértice, el resultado de multiplicar el efecto de flexión, por la dirección y el color del vértice.

//Aplicacion 
wPos.xz += effect * direction * _FlexMultiplier * v.color;			v.vertex = mul(unity_WorldToObject, wPos);
//La posición del vértice depende mucho de la rotación

Por último es volver a regresar la posición del vértice a posición del objeto, y enviar los datos de la posición junto con el cálculo que hicimos del viento multiplicados por el color del vértice, como mencioné arriba el componente R para el eje X y el componente B para el eje Z.

//Como Blender rota el eje X en -90°, 
//hay que poner a vertex en el eje xy 
//Normalmente seria vertex.xz para la hierba				v.vertex.xy += tex.xz * v.color.rb;				//v.color = tex; 
}

Programa de superficie:


Continuando con el programa surf, tiene de parámetros de entrada la estructura Input mencionada arriba, y la salida SurfaceOutput, las primeras lineas no tienen nada del otro mundo, solo una variable _HealthColor que usaremos en un blog más adelante donde crearemos variaciones de este shader sin generar más drawcalls.


void surf(Input IN, inout SurfaceOutput o)
{
float4 c = tex2D(_MainTex, IN.uv_MainTex) * _HealthColor;
float4 n = tex2D(_NormalMap, IN.uv_MainTex);
o.Normal = UnpackNormal(n);

Por último vamos a agregarle algo de nieve, similar al Blog anterior, usamos la posición mundial del objeto y los multiplicamos por un vector que siempre apunta hacia arriba, pero debemos cuadrar bien la distancia del suelo al cielo y por eso dividimos 1/_SnowLevel,


Ahora dependiendo de la altura a la que esté el objeto, esta se teñirá de blanco si supera la altura deseada, la altura entre valores de 0 a 1, con _SnowLevel, lo que hacemos es estirar ese valor, para que alcanzar el 1, siempre esté más alto.

#if IF_SNOW
fixed height = dot(IN.worldPos, fixed3(0, 1 / _SnowLevel, 0));

if (height > 0.9)
{
	o.Albedo = c * height;
}
else
{
	o.Albedo = c.rgb;
}
#else
	o.Albedo = c.rgb;
#endif
	o.Alpha = c.a;
}

Aquí les dejo el código completo.


Shader "Surface/Lighting/Texture Wind Leaves" {
Properties{
[Header(Color)]
_MainTex("Base (RGB)", 2D) = "white" {}
_HealthColor("Health Color", Color) = (1,1,1,1)
_NormalMap("Normal", 2D) = "normal" {}
_Cutoff("Alpha cutoff", Range(0,1)) = 0.0

[Header(Wind)]
_NoiseTex("Noise", 2D) = "whiate" {}
_Scale("Noise Scale", Range(0,2)) = 1.0
_Speed("Effect Speed", float) = 1.0

[Header(Collision)]
_FlexMultiplier("Flex", Range(0,1)) = 0.5
_RangeEffect("RangeEffect", Range(0,2)) = 1.0

[Header(Snow)]
[MaterialToggle(IF_SNOW)] _IfSnow("if Snow", Float) = 1
[ShowIf(IF_SNOW)]_SnowLevel("Snow Level", Float) = 1.0
}

SubShader
{
Blend One OneMinusSrcAlpha

Tags 
{
    "Queue" = "AlphaTest"
    "RenderType" = "TreeTransparentCutout"
}
Cull Off

CGPROGRAM
#include "CustomLighting.cginc"
#pragma surface surf Translucency vertex:vert addshadow Alphatest:_Cutoff 

#pragma target 3.0
#pragma shader_feature IF_SNOW
#include "UnityCG.cginc"

sampler2D _MainTex, _NormalMap, _NoiseTex;
float4 _GLOBALposition;
float _FlexMultiplier, _RangeEffect;
float _Speed;

UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
    UNITY_DEFINE_INSTANCED_PROP(fixed4, _HealthColor)
UNITY_INSTANCING_BUFFER_END(Props)

#if IF_SNOW
    float _SnowLevel;
#endif

struct Input
{
    float2 uv_MainTex;
    float4 color : COLOR;
    float3 worldPos;
};

void vert(inout appdata_full v)
{
//Calculos para el viento
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
fixed4 tex = tex2Dlod(_NoiseTex, float4((worldPos.xz * _Scale) + (_SinTime.x * _Speed), 0, 0));

//Calculos para la colision
fixed4 centerObjectWorld = mul(unity_ObjectToWorld, fixed4(0, 0, 0, 1));
half2 direction = normalize(centerObjectWorld.xz - _GLOBALposition.xz);
float dist = distance(worldPos, _GLOBALposition);
half effect = 1 - saturate(dist / _RangeEffect);

//Aplicacion
worldPos.xz += effect * direction * _FlexMultiplier * v.color;
v.vertex = mul(unity_WorldToObject, worldPos);

//La posicion del vertice depende mucho de la rotacion
//Como Blender rota el eje X en -90°, 
//hay que poner a vertex en el eje xy
//Normalmente seria vertex.xz para la hierba
v.vertex.xy += tex.xz * v.color.rb;
}

void surf(Input IN, inout SurfaceOutput o)
{
float4 c = tex2D(_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _HealthColor);

float4 n = tex2D(_NormalMap, IN.uv_MainTex);
o.Normal = UnpackNormal(n);

#if IF_SNOW
fixed height = dot(IN.worldPos, fixed3(0, 1 / _SnowLevel, 0));
if (height > 0.9){
    o.Albedo = c * height;
    }
else{
    o.Albedo = c.rgb;
    }
#else
    o.Albedo = c.rgb;
#endif
    o.Alpha = c.a;
}
ENDCG
}
FallBack "Nature/Tree Creator Leaves"
Dependency "OptimizedShader" = "Hidden/Nature/Tree Creator Leaves Optimized"
}

Por ahora el shader está terminado y completamente funcional, desearía en un futuro agregarle soporte para la instanciación, pero aún sigo algo crudo en el tema.


Si ha logrado terminar este tutorial, ya conoce las bases de como usar transparencia, moverse entre matrices de objeto y mundiales, sabe mover vértices y usar texturas para moverlas, crear nieve y usar modelos de iluminación propios para darle un mejor acabado.


No siendo más nos vemos en el próximo blog. Hasta otra.

17 visualizaciones0 comentarios

Entradas Recientes

Ver todo
bottom of page