So, I managed to figure out a way to make really nice caustics underwater with just a height lerp, light vector, and mip level function built into the ground shader. At the moment, the caustics are planar mapped vertically using the X and Y channels of the world position, but I was wondering if it’s possible to tilt this planar map, say, at a 30 degree angle. This would allow the caustics texture to wrap around objects according to the angle of the sun.
If so, I’d say I found a cheap solution for caustics rendering that looks gorgeous underwater. Otherwise, I’d be forced to use a light function, which means A: no chromatic aberration (except in post), B: no depth-based softening, and C: increased light complexity/harder performance hit of the entire scene. And I’d hate to do all that just for the caustics to match the angle of the light source.
sure, just take the Z component times 0.2 or 0.3 or so and add it to either the X or Y depending on the direction you want. and also apply the same tiling factor as well before doing the 0.2 or 0.3 so its in the same scale.
A simple method like this is referred to sometimes as a z shear. oftentimes the same method is used to make a tiling 2d texture into a psuedo 3d texture by applying a psuedorandom Z offset instead of a lienar Z gradient like you will be doing.
Wow! Thank you so much, it worked! I just multiplied the R and G channels of the world position node by the R and G channels of the inverse of the light vector, and it shears the texture accordingly!
I now have an almost fully functional caustics rendering function: it uses a custom mip function to blur the caustics and lower the intensity of the light as caustics scatter deeper into the water. It also cuts off completely at sea level. It works with a light vector blueprint, whatever normals you have, and can be applied to any object for a very nice effect. 30 instructions, plus whatever lighting model you want to use, and you can have underwater caustics almost as good as Cry Engine!
The only thing it’s missing is the shadow environment: because all this stuff gets plugged into the emissive channel, it lights up in areas that should be blocked by shadow. And since there’s no way to completely remove GGX specular rendering by height, there are some extra instructions there for specularity that doesn’t exist. I wish there was a way to access the shadow environment in the material, or build custom lighting within the material editor. Until then, this is the best that can be done to render caustics.
Another tip for 100% correct directional light projection so you dont have any fudge factors… if you take WorldPosition and then use the material function “TransformToZVector”, then provide your light vector as the Z vector, it will returned a perfectly transformed worldspace facing into the lightvector, so then you can simply component mask R and G to get 2d coordinates facing the sun.
You could also do this inside of a light function material. You could conceivably mask out using the water Z height of the world to avoid projecting caustics above water level.
Unfortunately, using a height lerp with a directional light doesn’t seem to work. You need a spotlight for height to work, but spotlights cast shadows on objects according to the center of the spotlight, not from a direction covering the whole world. The spotlight needs to literally be placed where the sun should be in order to get correct shadows. And the light function method would basically hack down your framerate by a third… and there’s no chromatic aberration because light functions don’t use color.
I wish there was a way to calculate shadows in the material editor. Regardless, this thing looks awesome in motion! And it’s only an extra 47-50 instructions for whatever materials are underwater… that’s WAY cheaper than a light function!
I’ll take a look at the Transform to Z function. Thanks for all your help!
I skipped the Z shear and transform to keep the image simple. I should have just used LightVector.xy * Tiling which is actually correct for a lightfunction since you don’t need to do your own transform anymore. it automatically aligns with worldcoords transformed into the light vector for a directional light. of course I think of it just after posting the image :S
but yes, you are right that you lose the chromatic abberation and pay some perf. Personally I’d just use post process volumes to increase it underwater. Yea you wont get it when looking from above the water, but if you need to see the effect take a screenshot from below with higher abberation.
Sadly the only other way to get shadowing on it without a light function would be to render out your own math in the code lighting path and its hard to say how much you could make it faster without trying. Either that or you make the caustics themselves post process based and use a threshold using scenetexture so that only pixels that were already at X brightness get the effect, targeting the lit vs shadow color to exclude shadow areas. in practice it could work but I could also see certain content being a pain to adjust for it… ie it woudl equally detect bright textures and bright lighting so it would work best with somewhat low contrast content underwater. Not necessarily that limiting.
OK, so I was able to get the light function to work as intended. Instead of pixel depth, I had to use the distance between the absolute world position and camera position because light functions can’t read the pixel depth. It works beautifully! Though I do wish there was a colored option, it’s fine.
This is my caustic uv math. It’s basically clip ray from world position towards light with water plane. This correctly works with all light types you just need to calculate normalized direction towards light. This is uniform for directional light but for spot or point you need to calculate that yourself.(I use dynamic material instance and send light position as vector param). This is optimized version of general ray plane intersection and this assumes water plane to point towards up.
Edit: Output distance is distance that ray travels water volume.
Edit: Only works for light sources above water level.
I never thought people would actually like this enough to pay money for it, wow!
The ocean floor and objects should be lit by a light function. UNFORTUNATELY this will incur a lighting cost over the whole map. The function should skew the texture in world space in accordance to the angle of the sun. From there, just use a variety of distance calculations to figure out the water height, blur the caustics as they travel through the water (mip blurring works very well), and remove the caustics completely above the water. I’m also coloring any objects that might come under the water by depth so they become more blue the further you go down. If there is a way to do this with translucent surfaces that affect lightmass, that would be great.
The material function should be added to particle effects because they do not respect the shading or position of light functions. You can leave them unlit and color them purely through the function, though this does remove the particle’s respect to shadowing.
As for the algea, I assume you mean the moss growing on the rocks, just vertex paint a moss color/texture/normal blend between the rock and algea, and if you want to go all out, give it some subsurface to really simulate translucency in the light. For the grass, just use a simple two-sided shader with some movement. I personally use the simple grass movement and change the speed and sizes to get something that looks appropriate underwater. Slower speed, larger sizes. If there’s a way to convert the simple grass shader to normals, I would include that as well for accurate lighting, but it’s not totally necessary. If you can set all your grass vertex normals to face upwards, the lighting will be nice and smooth.
I can release this on the marketplace. It may take a while, though. For now, you can take a look at the material function and get started with building the caustics. I suggest looking at vertex painting for moss/algea, and the grass is just the same as any other grass.
OK, I figured out a way to get some very nice caustics over an entire map with a single light function. There are settings for the brightness above the water, and below. The planar skewing method is good for lighting particles, and providing a separate method if for any reason you want to opt out of the light function method and hardcode the caustics into the individual materials yourself.
I’m nearing the point where I can say the light function is ready for a Marketplace release. But the final package will feature more than just a light function, I’ll also have to make sure the material function method is just as viable. There will need to be multiple functions, not just for the caustics, but also for the coloring effect because unfortunately light functions do not support coloring in and of themselves: if you want your underwater environment to be blue, it needs to be shaded that way per-object. On top of that, I want to include a Blueprint to make the setup of caustics much easier. Right now there’s a little tricky and unusual math involved because of the way light functions operate. For whatever reason, the light function material does not scale the brightness beyond what is defined in the light itself: the values get clamped. And since the caustics need to be brighter than the surface, the light brightness is actually the caustics brightness while the actual environment light needs to be scaled in the function. You have to work backwards with the math. Again, I’ll do the best I can to simplify before release.