Thursday, September 26, 2013

Unity Dust Particle Shader

Update: You can now buy our particle shader on the Unity asset store for $5!

We're using Lou's painting as inspiration for our first test shot. I started developing a shader for Unity's particle system to create the close up floaties. The tricky part here was that I had no way of accessing particle ids in the shader, which meant that I couldn't randomize the particles in the shader. I wanted to randomize opacity, color and flashes. I originally tried creating a perlin noise texture look up in screen space based on the particle's screen space position to get a random value for that particle. That works for our purposes since our camera is static. I didn't get very good results with this and instead varied the opacity in the particle system by manually setting Alpha to be 0 or 255 in Color over Lifetime. The varying opacity was used to drive a super bright emission and the color naturally looked random because of the different opacities.  

o.Emission = lerp(0,_Emission, smoothstep(_FlashSpeed,1,o.Alpha));

 My dust motes are made up of two particle systems: Large blurry dust and micro dust.

Large Blurry Dust
For the coloration, I wanted to create a "photographic" look. I looked a lot at images like these I found on google:

Even though the overall mote looks circular, it is actually a cutout of an imperfect circle texture I painted. This just adds a subtle randomness to all of them as they slowly spawn and rotate (I think anyway).

if(_DoShape)
    o.Alpha = o.Alpha*tex2D(_Shape,IN.uv_Noise.xy).r;
//otherwise make a circle
else{
    if(dist>=.5)
o.Alpha = 0;
}

I wrote a function to create a bright yellow ring near the edge of the particle that fades toward the center. As it fades, the color turns bluish.

float dist = distance(IN.uv_Noise.xy, float2(.5,.5));
if(dist<(.5-_Blur)){
    o.Alpha = o.Alpha*max(1.0*pow(dist/(.5-_Blur),2),(1-_Transparency));
    //add a shadow color as the particle fades
    o.Albedo = lerp(_ShadowColor.rgb, o.Albedo,o.Alpha);

}  

Finally, I have a noise on top that creates some pink speckles for a low resolution pixelated look.

o.Albedo = lerp(o.Albedo,_Speckles.rgb,tex2D(_Noise,(IN.uv_Noise.xy*.1+_Time*.01)).r);

As as bonus, I created a custom lighting model for these dust motes where based on the light direction and view direction, the dust motes will disappear.

half4 LightingParticle(SurfaceOutputCustom s, half3 lightDir, half3 viewDir, half atten) { 
    half4 c;  
    c.rgb = s.Albedo;
    c.a =lerp(0,s.Alpha, 1-saturate(dot(normalize(viewDir), lightDir)));
    return c;
}

NOTE the lighting model does not work with multiple lights. If you want to use multiple lights, I would recommend removing this from the shader. An easy way to do this is to just add "alpha" to the pragma surface line. This basically overrides the lighting model:

#pragma surface surf Particle alpha


Micro Dust
This one was much simpler. Basically white and the cutout is an irregular polygon texture.

And here is my result:
If nothing loads, watch with Vimeo or try installing Unity Player .

3 comments:

Anonymous said...

Beautiful! Would you mind sharing the unity file? Or making a tutorial?

Nancy said...

Thank you so much! I'd be happy to answer any specific questions if you shoot me an email or comment here. Unfortunately I don't have much time right now to write up a tutorial. It's not very complex, but if you are new to shader writing I can see how it might be.

Nancy said...

I had some time today to clean up and post some code. Hope that helps!