top of page
Foto del escritorBraulio Madrid

Como crear partículas de niebla realista.

Actualizado: 29 oct 2020




Hay ocasiones donde deseamos crear #partículas como cúmulos de vapor, pero resulta que este vapor se ve demasiado solido y no reacciona como vapor real o niebla real.

En algún blog anterior les enseñé como funciona el efecto de imagen de la #niebla, que es simplemente un color que se aplica al búfer z que nos entrega la cámara.

Esta vez aplicaremos un enfoque un tanto distinto.



Preparación de escena.



  • Crea una escena nueva.

  • Agrega algunos objetos para ser afectados por la niebla.

  • Agrega un plano que haga las veces de suelo.

  • Agrega algunas luces que afecten la escena.

  • Agrega un objeto de partículas a la escena, con una emisión de 200 partículas.

  • Crea un nuevo material y asígnelo al componente de partículas.

  • Crea un nuevo shader de vértices y fragmentos y asígnelo al material.

  • Modifica la niebla en Window/Rendering/Lighting Settings.

  • Ajusta el rendering path de la cámara a Forward rendering.


Propiedades del editor.


Este shader tiene la propiedad de ser afectado por la luz y la niebla, por lo tanto tiene multiplicadores para la luz puntual y la luz direccional, además tiene una característica opcional, el hacer que las partículas funcionen como niebla.


Otra característica es que puedes elegir opcionalmente la #mezclas, lo que permite tener mayor flexibilidad con este shader, si quieres saber más al respecto, tengo un blog dedicado al manejo de características en el inspector.


Shader "Vertex Fragment/Tutorial/Particle Configurable Affected by lights and Fog behaviour"
{
Properties
	{
	_MainTex("Texture", 2D) = "white" {}
	_PointSpotLightMultiplier("Point/Spot Light Multiplier", Range(0, 10)) = 2
	_DirectionalLightMultiplier("Directional Light Multiplier", Range(0, 10)) = 1
	_AmbientLightMultiplier("Ambient light multiplier", Range(0, 1)) = 0.25
	_InvFade("Soft Particles Factor", Range(0.01, 100.0)) = 1.0
	
	[Toggle(IS_MIST)]_IsMist("Is Mist?", Float) = 1
	[Header(Mist Settings)]_MistAmount("Mist Amount", Range(-1,1)) = 1
[Header(Blend State)]
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend("SrcBlend", Float) = 1 //"One"
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend("DestBlend", Float) = 0 //"Zero"
}


Tags y propiedades.


Renderizado transparente y orden de dibujo transparente, como se puede observar en la mezcla, se hace efectivo el Rendering.Blendmode


Importante a destacar es que la linea #pragma multi_compile_fog, esto permite que el shader sea afectado por la niebla que produce unity, esta característica de este #preprocesador acompañado de algunas #macros, solo sirve en shaders de vértices y fragmentos, pues los shaders de superficie ya viene incluida esta característica.

SubShader
	{
	Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
	LOD 100
Pass
	{
	Blend[_SrcBlend][_DstBlend]
	ZWrite Off
	Cull Back
	Lighting On
	AlphaTest Greater 0.01
	ColorMask RGB
	
	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	
	// make fog work
	#pragma multi_compile_fog
	#pragma shader_feature IS_MIST
	
	#include "UnityCG.cginc"
	
	struct appdata
	{
		float4 vertex : POSITION;
		float2 uv : TEXCOORD0;
		float4 color : COLOR;
	};
	
	struct v2f
	{
	float2 uv : TEXCOORD0;
	UNITY_FOG_COORDS(1)
	float4 vertex : SV_POSITION;
	float4 color : COLOR;
#if defined(SOFTPARTICLES_ON)
	float4 projPos : TEXCOORD1;
#endif
	};
	
	sampler2D _MainTex;
	float4 _MainTex_ST;
	
	float _DirectionalLightMultiplier;
	float _PointSpotLightMultiplier;
	float _AmbientLightMultiplier;
	
#ifdef IS_MIST
	float _MistAmount;
#endif
#if defined(SOFTPARTICLES_ON)
	float _InvFade;
	sampler2D _CameraDepthTexture;
#endif

Funciones.


Light for vertex.


Esta función sirve para enviar parámetros a otra función encargada de aplicar la coloración de la luz, toma la posición de los vértices y lo multiplica por la matriz de la vista.


También toma la variable de la luz ambiental que proporciona unity y lo multiplica por la propiedad _AmbientLightMultiplier, para establecer el grado de afectación de la luz ambiental en la partícula.


fixed4 LightForVertex(float4 vertex)
	{
	float3 viewPos = UnityObjectToViewPos(vertex).xyz;
	fixed3 lightColor = UNITY_LIGHTMODEL_AMBIENT.rgb * _AmbientLightMultiplier;
	
	lightColor = ApplyLight(0, lightColor, viewPos);
	lightColor = ApplyLight(1, lightColor, viewPos);
	lightColor = ApplyLight(2, lightColor, viewPos);
	lightColor = ApplyLight(3, lightColor, viewPos);
	
	return fixed4(lightColor, 1);
	}

Apply Light.


Esta función toma las variables entregadas por unity acerca de la posición y el color de las luces, según la posición de la luz aplica una atenuación que luego se multiplica por la propiedad _DirectionalLightMultiplier o _PointSpotLightMultiplier, según la posición de la luz en el eje W.


float3 ApplyLight(int index, float3 lightColor, float3 viewPos)
	{
	fixed3 currentLightColor = unity_LightColor[index].rgb;
	float4 lightPos = unity_LightPosition[index];
	if (lightPos.w == 0)
		{
// directional light, the lightPos is actually the direction of the light
// for some weird reason, Unity seems to change the directional light position based on the vertex,
// this hack seems to compensate for that
		lightPos = mul(lightPos, UNITY_MATRIX_V);
// depending on how the directional light is pointing, reduce the intensity (which goes to 0 as it goes below the horizon)

		fixed multiplier = clamp((lightPos.y * 2) + 1, 0, 1);
		return lightColor + (currentLightColor * multiplier * _DirectionalLightMultiplier);
		}
	else
		{
		float3 toLight = lightPos.xyz - viewPos;
		fixed lengthSq = dot(toLight, toLight);
fixed atten = 1.0 / (1.0 + (lengthSq * unity_LightAtten[index].z));
return lightColor + (currentLightColor * atten * _PointSpotLightMultiplier);
		}
	}

Vertex program.


Importante de esta función de vértices es que hacemos aplicación de luces por vértice, en lugar de hacerlo en el programa de fragmentos, esto permite dar unas partículas que si bien no son realistas en la iluminación, si son al menos convincentes y a bajo coste computacional.


La otra característica especial es que puede aplicar transparencia en los vértices en el canal alpha, según su posición respecto a la vista en el eje Z, produciendo un efecto, que cuando mas alejado está la partícula de la cámara, más nítida se ve y entre más cerca, más difuso se ve.


v2f vert(appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    
#ifdef IS_MIST
    float alpha = saturate(-UnityObjectToViewPos(v.vertex).z* _MistAmount);
    o.color = saturate(LightForVertex(v.vertex) * v.color * alpha);
#else
    o.color = saturate(LightForVertex(v.vertex) * v.color);
#endif

#if defined(SOFTPARTICLES_ON)
    o.projPos = ComputeScreenPos(o.pos);
    COMPUTE_EYEDEPTH(o.projPos.z);
#endif
    return o;
    }

Fragment Program.


Se ha tratado de cubrir todos los aspectos posibles dentro de este shader de partículas, lo único destacable es que aplica las coordenadas de proyección, para aplicar la afectación por neblina, también aprovecha el que haya una textura de profundidad de la cámara para aplicar suavidad en las partículas al chocar con el suelo.

fixed4 frag(v2f i) : SV_Target
	{
#if defined(SOFTPARTICLES_ON)
	float sceneZ = 
LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));

	float partZ = i.projPos.z;
	i.color.a *= saturate(_InvFade * (sceneZ - partZ));
#endif

	// sample the texture
	fixed4 col = tex2D(_MainTex, i.uv) * i.color;
	// apply fog
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
	}
	ENDCG
}
}
Fallback "Particles/Alpha Blended"
}

Conclusiones.


Puede parecer un shader hostigoso y poco claro, pero es un shader bastante útil y versátil y tiene un comportamiento bastante realista respecto a la luz y a la profundidad, este shader fácilmente puede funcionar como nubes en el cielo o complemento de neblina en juegos.


Al tratarse de un shader bastante largo, es posible mejorarlo y optimizarlo un poco más.


No he profundizado mucho al respecto en este shader, porque llegados hasta aquí los que han seguido blogs anteriores, deben saber ya a estas alturas lo que hace cada cosa.


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

72 visualizaciones1 comentario

1 Comment


Braulio Madrid
Braulio Madrid
Nov 27, 2020

Excelente muy bien explicado

Like
bottom of page