Sunday, 10 November 2019

Stylized Waterfall FX with flow interruption

I have been experimenting a lot with UE4's material system recently, seeing if I could push my knowledge. I decided on creating a stylized waterfall effect (because any sort of water effect is always really fun to work on) but I wanted to do something unique with it:

For Sky Adventure (My current game project - you can read more about it here) I really wanted to include waterfalls that the player's balloon could fly through, interrupting the flow of the water as it falls.

This effect needed the following features:

  • Intersection of the waterfall with overlapping objects, masking out parts of the waterfall.
  • Waterfall should have thickness and not just be a flat plane, so ideally intersection should work from all sides of the mesh, not just one side.
  • Splashes and ripples at bottom
  • Controllable parameters for things like colour and water flow speed.
  • Could be adjustable later on for blueprints (effect will be extended at later date so it can be used in different configurations within the game) 
  • Nice, clean and cartoony style that fits with existing game art style

So I went to work figuring out how to get this effect to work inside Unreal Engine. Below is the final result I ended up with!

Final Waterfall effect complete with flow breakup, masked particles, top highlight, ripples and a healthy amount of UV distortion.

How it works

The waterfall uses a scene capture component to record depth information from an attached camera, this picks up objects that are overlapping with the waterfall and uses their Zdepth information relative to that camera to generate a texture. This depth texture is then used as a starting point to create a mask on our waterfall. You can see how the depth is captured below:

The render target capturing the depth of the intersecting objects.

Initially I had this effect setup with the scene capture placed behind the waterfall. This captured objects insecting with a black background plane (invisible to the player) at the same location of the waterfall plane. This worked for a basic intersection effect but it a couple of limitations:

  • The main issue: If the waterfall has thickness then the mask will be applied to both sides of the waterfall or result in a gap in the mask. Ideally I need it so that the waterfall masks the position of intersecting meshes at any point. 
  • I was just using a simple diffuse colour capture of the objects intersecting a black plane, this meant that as soon as I changed the colour of the intersecting objects from white they stopped working! So I need to capture the Zdepth of the camera instead
  • I could try some workaround by having the scene capture rotate according to the point where the object enters the waterfall but it seemed very overcomplicated for this effect. I didn't want to perform all these different checks during gameplay. 
After a bit of digging I was suggested to try using a projection from either the top or bottom of the waterfall instead. With a little bit of shader magic, the render target could then be used as a sort of volume texture; this can be used to figure out where the object is intersecting the waterfall based on it's associated depth value in the render target and then mask out everything under the intersecting pixel's world position!

The benefit of doing it this way is now the waterfall can be any shape and it will mask perfectly from any side, perfect for what I want!

How I setup the Projection

So to start creating the waterfall I created a blueprint with a scene capture component added. This capture component has been put on an end of a spring so it's position can be adjusted easier if needed.


The waterfall mesh is 800 unreal units tall so I positioned my capture component at the top of the waterfall so it is 800 units form the base and pointed it downwards.

I also set the rotation of the capture component to be absolute, this got rid of some weird projection issues I was having when I turned the waterfall around, this means the projection would always stay the same rotation but unless the waterfall was really wide it didn't really matter.

Lastly, I made sure to set the projection type to orthographic (don't want any perspective distortion in our texture!) and I set the ortho width to be 1024 (this would normally be the size of the texture as defined inside the render target itself.)

Depth Capture

I applied a simple post process material to the scene capture component, this allows me to capture the scene depth to the texture but only within a range I define, in this case the length of the waterfall which is 800 units.


What does this do?

Basically this means my texture will now capture depth values within a range of 0-800. This means the texture can be used to output the exact Z position of any intersecting objects.

My attempt at a diagram demonstrating how the height of the object is recorded as a value within the render target

Setting up the texture size of the render target

I created a material parameter collection so I could store the the size of my render target camera. A material parameter collection allows me to contain scalar and vector parameters that are global, meaning an easy way to store important data I would need for the material!

I added the following parameters to my collection, both will be used for placing the projection correctly in the world:

Parameters inside the material parameter collection
Next, back in my waterfall blueprint I added the following construction script. These nodes allow me to output data from the BP into the material parameter collection.

This construction script grabs the current ortho width and position of the render target camera and feeds it into the waterfall material.

Creating the waterfall material 

I begin to set up my UVs for the for waterfall masking. This graph below shows how I projected the render target inside my material using world position and using the capture size parameter to scale the texture correctly within the material.

Projecting Render Target UVs in World Space


This generates UVs that I can then feed into my 'Cutout Effect' material function. Here I am sampling the render texture based on my world position UVs, multipling it by my projection distance to get the depth to work between a 0-800 range rather than just between 0-1. I then offset the texture by the Waterfall's overall Z position so get the correct projection location.

Here is the fun bit: comparing the world position of each pixel of the waterfall mesh to the projected value of the render texture. If the depth value of a pixel on the projected render texture is lower than it's z position in the world then mask it out.

How to compare the depth value of the render target to the world z of the current pixel, this results in the space under intersecting objects being masked out.


Below is what the generated mask looks like:

Generated mask from comparing the render target values to world z position of each pixel on the object.

 

Defining the look of the waterfall

After establishing how the mask would work I turned my attention to developing the appearance of the waterfall. I looked at a bunch of ref and then created a simple concept painting to figure out what I wanted: I wanted clear, defined shapes that would be readable at a distance and very bright highlights for the top and the splashes. In terms of style I would say I was looking for a sort of blend between the Wind Waker and Rime styles (A bit more towards Wind Waker though as it has a more flat appearance), for reference I taken a look at a lot of Studio Ghibli water animations as well as water animations from the FMV game Dragon's Lair.

This current style goes very well with what I have in mind for the rest of the game: Simple, fluid and readable.


Adding Edge Highights

I added some edge highlights to the waterfall when intersected. This was done by grabbing the cutoff mask generated earlier and adding together multiple copies of it which have been offsetted in different directions. This was then added to the emissive colour channel to create thin highlight around any intersection.

Adding an outline for the masked out area, it is sharing the same output as the cutout effect, just offset up, left and right along the UVs by an outline thickness parameter!

Water flow using UV Distortion

Probably one of the most important contributing parts to the waterfall effect: the falling foam. This is needed to help establish a downwards flow of water. For this I used panning textures combined with UV Distortion with a noise texture. I use this effect a lot on this waterfall and on other projects and it's super useful. 
Uv distortion on the waterfall shown (slightly exaggerated for gif purposes.)

How noise is added to a UV channel to get the distortion effect.
To get a harsh defined edge to the mask that fits with the game style I use a SmoothStep node. This node allows me to input a threshold parameter; anything above max will be white and anything below min will be black, anything inbetween will be smoothed against a curve. If I keep the min and max values tight or even equal to each other I can adjust the sharpness of the edge.



How to set up a mask using smoothstep.
After this it's just a case of feeding the mask into a lerp so I can easily define the colours of the waterfall. :)

Ripples - Also with UV Distortion

I also used the same techniques on the ripples, the ripples use a circular mesh with textures panning outwards to create the look.

Ripples + Wireframe

Top Highlights

For the white highlight on the top of the waterfall I used the previous techniques to get the 'ripply' effect it has. Although to place the highlight I used a separate piece of geometry with a separate UV channel so I can control the placement of the highlight better. This piece of geometry was masked out with green vertex colours. The main benefit of doing it this way is that I can change the length of the waterfall later on without risk ofchanging the top highlight geometry/stretching it's UVs.

Vertex colours of the waterfall, the red channel is
used to mask out the ripples at the top.

Adding depth to the waterfall - Two Sided Texturing

The next step was to add thickness to the waterfall. This was relatively straight forward since I simply made the waterfall two sided, and made the inside of the waterfall an unlit white to make it look like foam. Normally materials set to two sided use the same texture/colour on the other side, but with a TwoSidedTexturing node you can give your backfaces a different input!



Splashes - Masking out Particles by position

At the moment though the fact that the waterfall is just a two sided material is still obvious because of the bottom looking like it's cutaway. I add in splashes at the bottom of the waterfall to cover up the intersection with the water plane!

The particles are simple circle shaped planes with a lot of geometry. This geometry is pushed up using world displacement and a noise mask. I can control the rise and fall of the splashes using a Dynamic Parameter in cascade, which can feed data directly into the material to drive the displacement.

The main part (that taken the most amount of headaches on this project) was figuring out how to mask out the particles when the water was cutaway. For this I grabbed my render target projection material script from the waterfall, the problem with using this straight up though was that the particles would only be partially masked out, which when combined with the displacment has an unsightly appearance:


Bad Masking - the render target is masking out the splashes at each pixel which results in this harsh outline.

To fix it I realized I could simply replace the world position node in my cutout UVs with a ParticlePosition node. Instead of sampling the UVs at the current pixel position it would do it for the center of each particle: if the center of the particle lays within the masked out section of the render target, the entire particle will mask itself out.

Replace World Position with Particle Position to sample render target UVs at position of particle center.



It's not perfect, currently it snaps the particle opacity completely. I would like something more gradual but I'm still experimenting with a better way to do it.

Summary

    It has been loads of fun putting this effect together and trying to get it to work, I have learnt loads about how to manipulate UVs in different ways and how to use render targets in more detail. For future work in Sky Adventure I will come back to this effect and add the ability to alter the size of the waterfall using construction script as well as adding a softer transition to the splashes. I also want to explore having the water redirect over intersecting objects, but that's for another time!

    If you have any further questions about this effect or you have any feedback please send me an email through my contact page or leave a comment to this post. :)

    In the meantime check out Sky Adventure at these places below:


    DEVBLOG: http://blog.idotri.games/
    TWITTER: https://twitter.com/IDOTRIGAMES



    No comments:

    Post a Comment

    Leave a comment, I'll try and get back to you soon as possible. ^.^