Ondrej Paska Blog

Unity “Long exposure” VFX

Advertisements

I wanted to make an effect like what you get when you take a long exposure picture of a the night sky – the stars get stretched because of Earth’s rotation.

I first created about 500 star sprites in the scene in a rough half circle. I created a new layer called Stars.

I used a second camera to render the stars to a RenderTexture. That means the camera will not output to the screen but instead into a texture. In Unity you do this by assigning the Target Texture field of the camera.

The trick to retain everything from the previous render is to select the Don’t Clear option in the Clear Flags field of the camera. The Culling Mask field is used to tell the camera what layers it should render.

To actually display the texture, I created a fullscreen Quad in the scene and added the render texture as a main texture to a unlit shader. I added a tween to rotate the stars and voilà:

Well… almost! could we just clear the texture “a little” every frame to make the “tails” dissappear with time? On the CPU this could be done by the ReadPixels and SetPixels of the RenderTexture. But a faster way is to do it on the GPU with a shader!

Don’t worry – it’s very simple. You can create a shader with all the boilerplate code by right clicking the project window and selecting Create->Shader->UnlitShader.

Now we use the frag function, which is called for each individual pixel, to decrease the alpha value by a small amount each frame.

fixed4 frag (v2f i) : SV_Target
{
    fixed4 tx = tex2D(_MainTex, i.uv);
    tx.a *= 0.96;
    if( tx.a < 0.1)
    {
       tx *= 0;
    }
    return tx;
}

 

To actually use this shader, we first need to create a material that uses it. Then we use the Graphics.Blit function to run a texture through it. This function copies data from one texture to another and also applies a material on it. Because it is not allowed to Blit from a texture into itself, we need to create two RenderTextures and swap between them.

public void OnPreRender()
{
    Graphics.Blit(_renderTexture, _renderTextureTemp, _dimStarsMat);
    var temp = _renderTexture; // swap
    _renderTexture = _renderTextureTemp;
    _renderTextureTemp = temp;
}

A good place to call this code is in the OnPreRender callback which gets called on all scripts attached to a camera.

And that’s it! The finished effect now looks like this:

The technique has many other uses. I tweaked the shader and added a displacement texture to create this ghostly smiley: 

fixed4 frag (v2f i) : SV_Target
{
    fixed4 noise = tex2D(_NoiseTex, i.uv+fixed2(0,_Time.x*8));
    fixed2 displ = (noise.rg - 0.5)*_DisplAmount*0.01;
    fixed4 tx = tex2D(_MainTex, i.uv+displ);
    tx.a *= 0.96;
    if( tx.a < 0.1)
    {
        tx *= 0;
    }
    return tx;
}


This was just a brief overview of how this effect works, find the full source code on github.

What do you think? Is there a simpler way to achieve a similar effect? Let me know in the comments or on Twitter.

Advertisements

Advertisements