Any environment that has even te smallest pool of non-shallow water should have caustics, it just doesn’t feel complete without it. Our Sewers are no exception to this. Aside from being beautiful, caustics are also extremely hard for a computer to generate on the fly, so creating something that looks good but at the same time is performant enough to not bog your game down is a challenge in en of itself.

First of there is creating the caustics, searching google for caustics gives a plethora of results. Though all of them are static single pictures. What we need is a set of animated looping caustics, because doing this by hand is too time consuming. There are 2 ways, the first is using Caustics Generators nifty little program (keep in mind the free version is only for non-commercial use).

Substance Player – Caustics Substance loaded

We however prefer to not throw money around like crazy and opted for the less accurate version using Substance Player (a free program from the creators of Substance Designer), along with the extremely well done Substance by Bruno Afonseca over at Substance share.

Substance Player gives us a great amount of control on how big the Depth should be or how large we want the texture to be. We are using 256×256 because of performance reasons.

Having created the needed caustics texture, comes the part where we actually make them work in the editor. The first try consisted of using the old method of creating a shader that supports vertex colors and manually painting the position where the caustics need to be visible. Don’t mind the shady looking baked lighting, it is purely for testing and clarity.

This leaves us with a crisp result and perfectly align-able caustics. However with one BIG downside, any object that needs to receive caustics needs to be painstakingly hand painted with the vertex colors to actually show the caustics. This of course works when you have a beach or are underwater where you have a single material for the entire ground mesh, it however doesn’t work when there are a lot of objects.

Solution 2 involves using the projector, this has the added benefit of creating caustics on any object in its view frustum.

Now this for starters looks good enough to be at least use able, it still however has some downsides. First it stretches across the entire object it hits from its current position, aside from this being extremely obvious, it also looks plain weird. Setting the texture to clamp instead of repeat gives even more problems.

On to the 3rd and final solution, simply using light cookies this in the end was the most elegant and simple solution. Light cookies are a way for Unity to tell a light (spot,point and directional) whether it should have an intensity of 0 or 1, based on a texture. In a way it works exactly the same as the projector.

This fixes the clamping without bringing any of the previous problems back to use, however there is one thing that needs fixing and that’s the strong edge coming from the texture itself. It’s an easy fix though. We simple apply a gradient on the edge of all of our textures using Photoshop batch processing, or using Substance Designer to adjust the substance file. Notice that this also gives the benefit of being able to easily adjust the intensity, color and range. But the biggest added bonus is shadows!

Now that it looks decent enough to be used in the game itself there is only one thing left to do and that is actually animating the texture. For this we create a simple script that replaces the texture at a certain interval, preferably at the same interval the textures were created: 30fps.

using UnityEngine;
public class AnimatedProjector : MonoBehaviour 
    public bool runEditor = true;
    public float fps = 30.0f;
    public bool reload;
    public string textureToReload;
    public Texture2D[] frames;    
    private int frameIndex;
    private Light projector;
//Used for in editor previews of the caustics, some performance impact
    void Update()
        if (!Application.isPlaying && runEditor)
    //Starts the actuall function
    void Awake()
            projector = GetComponent<Light>();
            InvokeRepeating("NextFrame", 1 / fps, 1 / fps);
	void NextFrame()
            projector.cookie = frames[frameIndex];
            frameIndex = (frameIndex + 1) % frames.Length;

Easy and simple, no need to use the update function as this would be too performance heavy for our particular case. What we ended up with is this: