This tutorial goes over the process of how I created my water shader for the River Shack scene in my portfolio. The shader is not meant to be a complete solution, and will require a lot of tweaking on your part in order to achieve the exact look you want – what is important is that the properties can be tweaked with much modification. In the future I will add a more advanced water shader tutorial that covers some Dx11 features such as tessellation, but its important to have a strong base of knowledge. Before I start, I have to give credit where credit is due. Thanks to Chris Albeluhn and Alyssa Reuter for their water tutorials. While I found these tutorials to be helpful, I was unhappy with the end results. There simply wasn’t enough control over things like water color by depth, distortion, and reflection strength. My goal was simple – create a better looking, more efficient water shader that gives the artist more control. I decided on a limit of 100 vertex shader instructions to keep it simple, and was able to get it down to 109. By comparison, UDK’s default water shader M_Water_03 is 186 instructions, while Alyssa Reuter’s is 127.
While the network may look deceptively complex, its actually quite simple once you break it down into chunks. It’s absolutely necessary that you create comments in larger shader networks so that other artists – or yourself – can look at the network later and make sense of it.
In order to follow this tutorial, you’ll need a few things already set up. You should have a simple scene – I would suggest using the Landscape tool and making a simple scene with a few assets like trees, plants, large rocks, etc. You want to have enough props in your scene to determine what adjustments to make. You can download my sample scene here to get straight into building the material. (Requires UDK March 2012 beta or later)
Next, you need to create a package and your water material. I prefer to create a group within my package named Water so I can find all my materials and textures easily, although you can use whatever naming and grouping convention you find works best for you.
Elements of a Water Shader
There are a few elements that define water, and so I’ve broken the tutorial up into categories based around those elements and how we can recreate them in a logical order, as well as how we establish those properties in UDK.
- Reflectivity Controlled by SceneCaptureReflectActor and RenderToTexture
- Specularity & Opacity Controlled by PixelDepth and DepthBiasedAlpha
- Distortion/Waves Controlled by the artist’s normal maps
- “Natural Flow” Controlled by a series of Texture Cooridinate, Panner, and Rotator nodes
- Color Controlled by interpolation of two colors based on DepthBiasedAlpha
Each of these elements does not make realistic water on its own, and they depend on one another for the effect to work. I’ll describe each one below, and how they relate to the actual shader network in order for you to see how the principles are applied.
Water has a specular reflection. As the law of reflection states, incoming light and outgoing light will have the same angle based on the object’s surface normal. In the case of a mirror, you will see a perfectly flat reflection. UDK provides us with SceneCaptureReflectActor, which replicates these properties. You can find this actor in the Content Browser under Actor Classes > SceneCaptureActor > SceneCaptureReflectActor. This actor should be placed in your level at just below the height of the plane that you will put your water material on. On its own, this actor will not do anything, and if you press “G” to enter Game mode in the editor viewport, you’ll notice it disappears. To work properly, it needs to have a texture to render to…I wonder what it might be called?
In the content browser, navigate to the package where you created your water material. Create a new RenderToTexture. Name it something like “WaterReflection_t.” At this point, a window will pop up with the texture’s properties, including the resolution of the texture. The resolution you should pick is largely dependent on the size of the body of water. Pixelation will occur at lower resolutions, but the distortion effects we create later will cover it up. For calmer water, or for a mirror, you may want to use a higher resolution, but this comes at a significant increase in processing power, so allocate your resources wisely.
Save your package and then right click on newly created RenderToTexture and copy the name to the clipboard. In the editor viewport, select the SceneCaptureReflectActor and bring up its actor properties (shortcut key is “F4″). Under SceneCaptureActor, paste the path to the RenderToTexture file and change the frame rate to something more reasonable, like 40. At this point, your RenderToTexture will be ready.
In the material editor, create a new Texture Sample by pressing “T” and clicking in the editor window. Plug your RenderToTexture into the texture sample and plug that into the Emissive slot. You’ve now created a basic reflective material, but it won’t work without a few more elements.
Specularity is basically shininess, and is the easiest part of this tutorial. You simply need to set up two Constants (press 1 on your keyboard). One will control the specular color, while one will control the specularity power. Specular power determines how sharp your specularity will be – will it curve over the object or be limited to the hardest edges?
In my example, the specular color is set to 4. This is a simple black-to-white node. If you want to have color in your specularity, you need to make a Constant 3 Vector (press 3 on the keyboard). Use that to define your RGB values. This is helpful for metal, which often has a blueish tint for specularity. For water, you want a white specular highlight, so what we have is fine.
Specular power is set to 325. If undefined, it defaults to approximately 25. The higher the power, the sharper the specularity will be. Unfortunately, specularity only works if you have a normal map that will define the differentiating height values for the edges to reflect. So, let’s set up the normal maps!
To be continued in Part 2.