Fake caustics using visual shader in Godot 4!

Hello everyone and welcome back. In today’s post, I will going to show how we can create fake caustics in Godot.

For this, I am using Godot 4.2 and I have also installed an addon called Shader-Lib.



What are caustics?

Caustics are basically curved light pattern which is created due to the refraction of light rays when passing through one medium to another.

In most modern renderers, we can simulate realistic looking caustics but that can require much more computation power.

So we will cheat a bit and create a fake caustics effect using Visual shader.

Creating the Shader

Let’s create a visual shader, set the mode Spatial. Call it cautics_shader.

Let’s also create a material for our shader. I will call it caustics. Then assign our shader in our material.

Now, instead of directly assigning the material to our geometry, we will go to the its material and in the next pass slot, we will assign our caustics.material.



Here we are telling Godot to execute whatever instruction we have in this material, and after doing that, apply the caustics material on top the previous material.

In our shader, let’s first create a Voronoi node. It will give us this nice cellular noise.

Take its output and feed it into Power node. Power node will darken the values which are less than 1 as we increase the power.

We will control the power from the inspector so let’s create a Float ParameterLet’s call it Blend and give default value of 5. Then feed it into the Power node.

Now take our Power node’s output and feed it into Multiply node. This way we can control the intensity of our caustics, by increasing the value. We will do that from inspector so let’s create another Float ParameterLet’s call it Intensity, Give default value of 5 and feed it into Multiply node.

Then take our Multiply node’s output and feed it into the Albedo.

Now we can see the caustics but the rest is all black. To fix it, take another output from Power node and feed it into the Alpha.

Now in the Voronoi node, the cell density controls the amount of cells. We will control it from the inspector so let’s create another Float ParameterLet’s call it Density, give default value of 5 and feed it into the cell density.



Chromatic aberration

Now we have this pure white caustics, but when light ray get’s refracted, it may disperse into multiple colors.

To mimic that behaviour, we will sample this same Voronoi node 3 times, so let’s just duplicate it.

Now let’s offset all 3 of them in different direction. For that we will use UVFunc node with mode PanningNow we can use this offset to pan the UVs. Take its output and feed it into the Voronoi nodes' UVs.

And we want to control the chromatic strength from the inspector so let’s just create Float ParameterI will call it ChromaticStrength, Set the Hint to Range. And you can experiment with the values here but I find value of 0.02 works nice so set the range from 0 to 0.02.

Now Let’s offset the first Voronoi node upwards. For that we will change Y component. So let’s create VectorCompose node, select Vector2 from the dropdown.

Take our ChromaticStrength and feed it into the YThen take our VectorCompose node’s output and feed it into the offset.

Now let’s offset second Voronoi node to the right side. So instead of Y we will change X component.

For the final Voronoi node, we will offset it to the left side so we will just invert the X value. And to do that we will use Negate node.

Now let’s combine all 3 Voronoi node’s together using VectorCompose node.

Now take our combined Voronoi noise (VectorCompose) and feed it into the Power node.



Scrolling the texture

In our Voronoi node, this angle offset, offsets the cells of our noise, we will update them over time. So let’s create a Time variable. Then to control the speed, let’s create a Float ParameterCall it AngularSpeedGive default value of 0.2.

Let’s multiply it with Time.

Take our Multiply node’s output, and feed it into Angular offset of all 3 Voronoi nodes.

Lastly we want to scroll our noise over time so Let’s create Vector2Parameter to control the speed and direction. Let’s call it ScrollSpeedGive default value of 0 and 0.2. Multiply it with our Time.

Now we also want to preserve our Chromatic offset so we will simply add the Multiply node’s output to VectorCompose nodes.

Then take Add nodes outputs and feed them into offsets of UVFunc nodes.



Applying the texture from XZ plane

Now we can adjust our material to see how our effect looks in the scene view and will look nice until we add our effect to other objects. There will be disconnect, like our caustics don’t match with other objects at all.

The reason is we are using default UVs of the objects to apply the texture. We want to apply our texture parallel to the XZ plane. Just like we lay our bedsheet onto the bed.

For that we need Vertex position in local space. To access that, we will use variable called varyingsIf you don’t know what that is, don’t worry.

Varyings are basically variables to send data from Vertex processor to Fragment or Light processors.

So let’s go to Vertex processorClick the Manage VaryingWe want to add the varying variable so add varying.


Vertex position is vector3 so select Vector3 in type dropdown. Then Let’s call it VertexLocalThen we want to pass it from Vertex processor to Fragment processor so select the first option and hit create.



Then to set the value, we will use VaryinSetter node. Select our variable from the dropdown.

Let’s get our Vertex variable and feed it to our VertexLocal.



Let’s head back to our Fragment processor.

Now to access the VertexLocal here we will use, you guessed it, VaryingGetter.

Select VertexLocal from the dropdown. We now have Vertex position in Local space, let’s convert it from Local to world using VectorTransform node.

And we will use XZ components for our UVs.



Now our caustics will match for all objects.

Trouble following along? Check out this step by step tutorial!



Thank you so much for reading!

Comments