Small VFX tips

Hi everyone!

I want to show my small tips. Hope it will helpful for you.

For mobile development, it is the most important to have as few DIPs/batches as you can. And some popular blending modes often are Additive and Alpha blend. If you use both of this for particles it will break batching because you need different shaders for this. But you can use just one shader for both types of blending.

Here is a shader code for Unity3D.
Edited version. Thanks bgolus for advice!

/* Alpha and Additive blending in one shader.
*  If all color components are under 128, it will be like alpha blending. 
*  if the brightest color component goes from 128 to 255, it will decrease alphablend contribution and will look more like additive blending. 
*  if the brightest color component is 255, it will use 100% additive blending.
*  You must use premultiplied RGB channel.
*  Created by Alex Fedotovskikh
*/

Shader "FX/_Common/Add_AlphaBlend" {
    Properties {
        _Color ("Multiplier", Color) = (1,1,1,1)
        _MainTex ("MainTex", 2D) = "white" {}
        _ColorMul ("Color Multiplier", float) = 1
    }
    SubShader {
        Tags {
            "IgnoreProjector"="True"
            "Queue"="Transparent"
            "RenderType"="Transparent"
        }
        Pass {
            Name "FORWARD"
            Tags {
                "Queue"="Transparent"
            }
            ZWrite Off
            Cull Off
            Blend One OneMinusSrcAlpha
            
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            
            #pragma target 2.0
            
            #include "UnityCG.cginc"
            
            uniform float4 _Color;
            uniform sampler2D _MainTex; 
            uniform float4 _MainTex_ST;
            uniform float _ColorMul;

            struct appdata {
                half4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
                half4 color : COLOR;
            };

            struct v2f {
                half4 pos : POSITION;
                half2 texcoord : TEXCOORD0;
                half4 color : COLOR;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                v.color *= _Color;
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                float alphaBlendFactor = 1.0f - saturate(max(max(v.color.r, v.color.g), v.color.b) * 2.0f - 1.0f); // 1 = alphablend, 0 = additive blend
                
                o.color.rgb = v.color.rgb * v.color.a * lerp(_ColorMul, 2.0f, alphaBlendFactor);
                o.color.a = v.color.a * alphaBlendFactor;
                return o;
            }


            fixed4 frag(v2f i) : COLOR
            {
                fixed4 color;
                fixed4 tex = tex2D(_MainTex, i.texcoord);
                color = tex * i.color;
                return color;

            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

And comparision with standart Unity shaders.

Would it better to write all future tips in that place or separately?

13 Likes

thank you for sharing ! :slight_smile:
I would only suggest you to move this thread into resources, and defiantly keep this thread updated.

cheers.

1 Like

What’s the benefit of using this vs a straight pre-multiplied shader if you’re already using a pre-multiple texture? If you want alpha blend just animate the color and alpha together. If you want purely additive just set the alpha to zero.

Also there’s a lot of cruft in this shader. The “LightMode” should not be “ForwardBase” for an unlit shader, nor should it have #pragma multi_compile_fwdbase or #define UNITY_PASS_FORWARDBASE, you’re forcing Unity to generate additional shader variants for a shader that does not need them. It also shouldn’t have a fallback defined unless you want this shader to cast square shadows.

You’re also passing the texcoord as a float3 value but the alpha blend factor never gets used in the fragment shader. This doesn’t really do anything bad since internally passed values are always 4 components regardless of how you specify them and the shader compiler will optimize it away, it just causes confusion.

It should be noted the premultipled alpha particle shader that ships with Unity is broken and isn’t actually a proper premultiplied shader. It incorrectly multiplies the color and alpha by the alpha. This means the alpha falloff is squared instead of linear! It also removes the ability to control the alpha separately from the color so you can do additive and alpha blend in the same shader. The default particle material uses that shader too. :frowning:

Here’s my version of a premultiplied alpha shader. It requires a little more care with picking colors in the particle system, but should be faster, especially for mobile.

Shader "Particles/Premultipled Alpha" {
    Properties {
        _Color ("Tint Color", Color) = (1,1,1,1)
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
    }
    SubShader {
        Tags {
            "IgnoreProjector"="True"
            "Queue"="Transparent"
            "RenderType"="Transparent"
        }
        Pass {
            ZWrite Off
            Cull Off
            Blend One OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            
            #include "UnityCG.cginc"
            
            uniform fixed4 _Color;
            uniform sampler2D _MainTex;
            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                fixed4 color : COLOR;
            };
            struct v2f {
                float4 pos : POSITION;
                float2 texcoord : TEXCOORD0;
                fixed4 color : COLOR;
            };
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.color = v.color * _Color;
                o.texcoord = v.texcoord;
                return o;
            }
            fixed4 frag(v2f i) : COLOR
            {
                return tex2D(_MainTex, i.texcoord) * i.color;
            }
            ENDCG
        }
    }
}

edit: Removed texture offset and scale

6 Likes

Do you need the TRANSFORM_TEX macro if you aren’t scaling or translating? I’ve removed it from most of my shaders unless they are used with tiling textures.

1 Like

Agreed, I do usually remove that as well since I so rarely use it. Edited the shader.

Cool, yeah I have shaders that are almost identical. I went on an optimization blitz for a few weeks recently so I’ve been staring at stuff like this. Unity 5 has also disabled the fragmentoption apparently:

Compilation directives that don’t do anything starting with Unity 5.0 and can be safely removed: #pragma glsl, #pragma glsl_no_auto_normalization, #pragma profileoption, #pragma fragmentoption.
https://docs.unity3d.com/Manual/SL-ShaderPrograms.html

1 Like

Thanks! I will. )

It gives me an easy and predictable way to set right color into gradients. If you use usual alpha pre-multiplied shader it forces you to change color when you want to change just an alpha. For example, you have some gradient and a middle alpha key is 43. You decided to set it a little few, like 27. With usual alpha pre-multiplied shader you must calculate a percentage of alpha changing for saving blend operation because you don’t need spontaneously changing alpha blend to additive or something middle. Your calculation will be like that (43-27)/255 * currentColorInProperPositionOfGradient = that portion of reducing of color for each component. I don’t want to care about color if I change just alpha component. That’s my point.

Thank you, man! Very useful information! It seems just something like old inheritance generated ShaderForge. I trust it, unfortunately, and didn’t even care about that. Have to remove already. =)

Oh, that a shame
 Sorry. Missed that during changing original shader. Thanks!

It’s so cheap. After compiling it will be just one Mad operation for vec2 in a vertex shader. But it will give you a comfortable way to use that shader for geometry too.

1 Like

I fairly frequently will intentionally fade out pre-multiplied particle effects with unmatched alpha and color so it goes to a subtle additive before fading out completely. However I don’t find adjusting the color gradient to match the alpha to be all that hard to do, especially in Unity. Just select the color you want, switch to HSV and type in “* (alpha/255)” on the value line. Basically you can do all that calculation inline with the value input.

TRANSFORM_TEX is cheap, but it too breaks batching just like any other value changing on the material. Unity can batch billboard particle systems and mesh particle systems, and meshes all together if they use the exact same material. Because of the way Unity does batching you’re better off using multiple meshes with manually adjusted UVs or scripts using additional vertex streams to adjust UVs. The later is what Unity already does for flipbook UVs on mesh particles and lightmap UVs internally.

1 Like

Cheap is relative. :slight_smile:

Sometimes you need to squeeeeeeeeeeeze that last drop of blood from the stone.

Hi, guys!
I translated my slides to English made for DevGamm conference. Hope you will like it and find here something useful.
I recommend to download it to watch videos and read the text below each slide.

8 Likes

Thanks for the powerpoint presentation Alexander, those scripts are insightful to read through! And for the bonus at the end lol xD

1 Like

Thanks for the presentation! loved the end of it hahaha

1 Like

Hi there!
Today I’m glad to add couple words about soft particles.
If you forget what it is, please read here

Many papers describe realization as presented in this figure:

mathematically it’s something like depthFade = (zBuffer_z - pixel_z) / fadeDistance * opaque;
but this approach makes fadeout border thinner when a camera looks parallel to a plane intersecting with a geometry of fx or particles.
As a VFX geometry, I used a sphere trying to make a magic shield rising from a ground.
And you can see here is a bug - height of a shield is getting thinner.

So we need to correct it. The Idea here is a compensation of difference between depths (zBuffer_z - pixel_z) by an angle between сamera vector and a normal map of a plane on the back. But we can’t read a normal map because it is a forward pass. So we need to reconstuct it from derivatives and zBuffer. See a figure below.

And here’s a result of a comparison.

Thanks!