Stylized Water Shader

Table of contents

  • Introduction
  • Goal
  • Week one
    • Waves
    • Transparency
  • Week two
    • Debugging depth
    • Debugging 2.0
  • Week three
    • Scope
    • Water’s glow-up
    • Lighting
  • Week four + overtime
    • Bumps
    • Reflection
      • Screen space reflection
      • Reflection probe
      • Planar reflection
      • Skybox reflection
    • Look and feel
  • Conclusion
  • References
  • Assest/Helpers

Introduction

When the task came to think about what R&D project I wanted to make within 4-5 weeks, I had no idea what I wanted to make. The boot camp workshops provided a good start at which topic interested me the most, and shaders was the one. I thoroughly enjoyed messing around and trying different things to create something presentable. I looked in the previous semesters for ideas and to see what had already been done before with shaders. I noticed that some people had done something with water shaders; most had the same look and feel, and it didn’t attract my attention to want to know more about how to create something like that. So, I went to look at the Unity asset store to see different kinds of water shaders with look and feel in mind. I found one asset that got my attention, and I was like, “Wow, you’re beautiful”. At that moment, I wanted to create a stylized water shader.     

Goal

With the little knowledge I developed during the shader assignment, my goal for this project is to create a stylized water shader, “waar je u tegen zegt”. The main focus will be on the look and feel since I want to create a different water shader than what others have made. Most water shaders you see are likely made within Shader Graphs, and I have no idea how difficult it will be to create a beautiful water shader with the look and feel in mind. Still, I’m going to research and work out how one can create a stylized water shader in Unity’s unlit shader.

Week one

In the first week, the minimum that I wanted to deliver was an actual water shader. It doesn’t matter how barebones or bad it looks; having the base done in the first week will help add future components to make it look great. And what does a water shader need to make it look like it’s from a beach? Waves.

Waves

Before I could actually start working on the shader, I needed to know how to create waves. During the shader assignment, I found that CatLikeCoding has a tutorial about how to make waves. I thought that was a good place to start, as the water shader I want to make involves having waves, so I followed it to the tea. The end result that you get is a Gerstner wave. A quite complex water wave that’s leaning more toward a realistic wave. It looked great; however, I’m trying to achieve low and peaceful waves. I could create that by adjusting the steepness and wavelength values.

Gerstner Wave
Adjusting values to get low waves

Even though it looked more low and peaceful, it still wasn’t what I was looking for. To better understand the shader basics, I decided to follow a video from Freya Holmér. It might give me ideas of achieving the waves I’m looking for that are similar to the asset. After watching the video, I returned to look at the videos on the asset for a better look. The whole flow of the waves goes up and down, like an ebb and flow motion, and the waves that you notice when looking up close move quite fast in different directions, as in left and right.

A classmate of mine sent me a link about the Lattice Boltzmann method. The idea is that by applying this method, I could create an ebb and flow motion. I was quickly overwhelmed by the math signs and the equations, as I’d never seen most of them before. The more I read the article, the more I got lost. I wanted to ask chatGPT if they could translate the formula for me, but since I couldn’t copy-paste the formula as text, it couldn’t help me. I went to search online for more information or at least an explanation so that I could understand the LBM. There were videos explaining the LBM, but they didn’t go in-depth about how you could apply the formula. From what I’ve seen, they only explained the theory behind the LBM; all I needed was an explanation of the formula and how to apply it. After researching, searching, and listening for a long time, I stopped with the LBM and decided to go for a simpler alternative. This decision is also for the best since I’m not looking to create something realistic, but it would’ve put the look and feel to a whole nother level.    

After failing to understand the LBM, I went to think about what a simpler alternative would be. A way to achieve the motion of the object to move up and down is by using a sin function. It gives you that motion that makes it look like the waves go to the shore and dissipate. With the Gerstner wave, it might be redundant to add it. The wave already has that motion, but not as a flat motion. Comparing my shader with the asset one made me think I might not need an actual wave. If the plane goes up and down that resemblance an ebb and flow, then I could create an illusion that there are waves on the plane without actually having waves.

float tideOffset = _TideHeight * sin(_Time.y * _TideSpeed);
gPoint.y += tideOffset;

To create the illusion of having waves on my shader, I searched online for any help. There are plenty of water shader tutorials online, but those are either with shader graphs or blender. I picked a tutorial from Paro that uses shader graphs. Following a tutorial that uses shader graphs isn’t bad since I can always translate the nodes into code; it just takes more time to figure out how the code will look. Either way, he uses a Normal map to make it look like there are waves. I had to sample from the Normal map to manipulate the x and y offset for the code. The offset is needed to move the Normal map on the x and y axes. Using the right Normal map is important since it could lead to interesting results; see the video below. I tried different Normal maps, but they never gave me the desired look. Instead of using one Normal map, I decided to try and use two Normal maps to see if that could give me the desired look. I searched for two water Normal maps and ended up on a blog post that did what I wanted to do. His water shader looked quite nice, so I took his Normal maps and used them for mine.

Water shader with one Normal map
Water shader with two Normal maps
float2 uvNormal = IN.uv_NormalMap;
float2 uvNormal2 = IN.uv_NormalMap2;

float2 offsetX = float2(_Time.x * _OffsetSpeedX, 0);
float2 offsetY = float2(0, _Time.y * _OffsetSpeedY);

float2 uvX = frac(uvNormal + offsetX);
float2 uvY = frac(uvNormal2 - offsetY);

float4 sampleX = tex2D(_NormalMap, uvX);
float4 sampleY = tex2D(_NormalMap2, uvY);

fixed4 blendedNormal = lerp(sampleX, sampleY, _Lerp);

I personally think it looks quite nice, but when comparing it to the asset, it looks too “busy” and doesn’t give you that calm vibe. I went to look around for different Normal maps and ended up with a new pair combination. However, there were two problems I encountered. First, you can see lines moving vertically and horizontally. This is likely due to the Normal maps not being seamless. I don’t know how to fix this, so I asked a classmate for help and will come back to this in the next iteration. The second is that you can see holes in the water at certain angles. I believe this has something to do with not correctly calculating the normals. What I first had, and also mentioned in the blog post, was an Unpacknormal method where you could unpack your textures. I changed that to o.Normal = blendedNormal.rgb;, which seemed to resolve the issue. However, that’s only when you look at it from a certain angle. From the sideline, you can see that the Normal maps aren’t blending very well. I’ve tried different coding methods to try and fix it, but it might be a Normal map issue. Since they don’t seem to be seamless, it could’ve affected the results. For now, I’ll put these issues on the back burner until I hear more from my classmate and pretend everything is fine.

Holes in my water shader
Normal map issues?

This has nothing to do with the wave, but I wanted to add a different skybox to the project. After I saved the project and imported new skyboxes, the second problem mentioned before was resolved. I changed the normal value back to the Unity Unpacknormal function since that would be better suited for future work with reflection and lighting, and everything looked great. I wish I could explain how it got magically fixed, but I have no idea.

// Calulcating a new blended normal map and alpha
half4 blendNormalsHeight(half4 sample1, half4 sample2) {
    sample1.rgb = sample1.rgb * 2 - 1;
    sample2.rgb = sample2.rgb * 2 - 1;
    half blendFactor = (sample1.a + sample2.a) * 0.5;
    half3 blendedNormal = normalize(lerp(sample1.rgb, sample2.rgb, blendFactor));
    half blendedAlpha = lerp(sample1.a, sample2.a, _AlphaLerp);
    return half4(blendedNormal * 0.5 + 0.5, blendedAlpha);
}

void surf (Input IN, inout SurfaceOutputStandard o) {
    ...previous code...

    half4 blendedNormal = blendNormalsHeight(sampleX, sampleY);
    o.Albedo = _Color;
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
    o.Alpha = blendedNormal.a;
    //o.Normal = blendedNormal;
    o.Normal = UnpackNormal(blendedNormal);
}
End result

Transparency

In the previous videos, you could already see that I had a bit of transparency, as you could see a bit of the terrain under the water shader. In the same video I watched from Freya Holmér, she mentioned that the tags, specifically RenderType, could be different types where transparent was an example. I wanted my water to be transparent since I later wanted to add depth to showcase how deep the water is from far and up close. In another video from Harry, he mentioned how you could add transparency to your water shader. By applying both their knowledge, I got transparency on my water shader. If I later on decide that I want to be able to go underwater and I have enough time, it’ll be possible.

Tags { 
    "RenderType"="Transparent"
    "Queue"="Transparent"
}

Blend SrcAlpha OneMinusSrcAlpha
Zwrite Off
Cull Off

#pragma surface surf Standard fullforwardshadows vertex:vert addshadow alpha:premul 

Week two

After the first week’s assessment, or what they call data points, I received some feedback, and they are as follows:

  • Put focus more on the surface. For example, don’t make anything underwater-related if it’s not within my scope.
  • Don’t use the asset as a final product reference, but more as an inspiration to make my own original water shader. 
  • Don’t use too many YouTube videos in the sense that if they don’t provide me the information that I need, then don’t use it. 
  • The scope could be more defined.

At the start of the project, I was already stuck with my scope. I knew I wanted to make a beautiful water shader, but I wasn’t sure what would be needed. I asked my teacher how to tackle this, and in the end, I would define my scope weekly. That way, I can work towards a product that will hopefully result in a beautiful water shader.

On this week’s agenda, I will be working on adding depth and foam to my water shader. Depth in the sense of transparency depth and not wave depth. You can see transparency when you’re close to any surface, but it shouldn’t be transparent once you’re further away. The foam will add some aesthetics to the water shader and the “ebb and flow” motion. When the water goes to the surface, foam will be there, and when the water goes away, the foam should disappear.

Debugging depth

Oddly enough, I had many problems adding depth to my water shader. Since I was following a tutorial to add Gerstner wave, I actually switched from unlit to surface shader. This meant it automatically takes care of lighting, shadows, and reflection. That’s the whole reason why my water shader actually looked really good in the first week. It was because I was using a surface shader.

I followed a tutorial to add depth to my shader, followed their advice, and got started. The depth worked in their project setup, so I went ahead and applied it to my project. However, since they use an unlit shader, I had to make a few adjustments to ensure it would work in my surface shader. I was trying to implement the code above to give me the depth I wanted for my water. It didn’t work. Even when I moved the _DepthMaxDistance around, I could only see the deep watercolor. When the value of that variable became less than zero, it would turn into the shallow watercolor that I assigned. I didn’t understand why it didn’t work. I asked my guild members for help, but they didn’t know either. Maybe it was my project that was bugged? Maybe my scrolling maps were intervening with the code? Maybe the depth wasn’t calculated correctly? I was stumped when I checked previous students’ work to see how they added depth. It was basically the same code, but our difference was a bigger surface area and different shader (he used unlit).

float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPosition)).r;
float existingDepthLinear = LinearEyeDepth(existingDepth01);
float depthDifference = existingDepthLinear - IN.screenPosition.w;

float depthDifference01 = saturate(depthDifference / _DepthMaxDistance);

float4 waterColor = lerp(_DepthGradientShallow, _DepthGradientDeep, depthDifference01);

After lots of trial and error, I decided to make a new project to see if it was project-related and added a new asset pack to get a bigger surface area since mine clearly didn’t want to show any depth. Everything worked. Why? Because it was written in an unlit shader. I returned to my old project, made a new scene, and did everything I did in the other project setup. After adjusting the _DepthMaxDistance variable, I could see depth. As a backup plan, I converted the code in the surface shader to an unlit shader. This was in case I couldn’t figure out why it wouldn’t work in a surface shader and if I had to make a decision to continue with an unlit shader.

Converted everything to unlit shader with depth added

It doesn’t look great, and I’m stumped that it’s not working in my surface shader. I’ve checked several things and variables to see where the issue could be, but I couldn’t find it. A classmate offered to help me, and we went to see where the issue could be. Tried different things and different codes from people, and we couldn’t make it work. They all work in unlit shaders but not in surface shaders. It might be because lighting, shadows, and reflections were already calculated, which might have impacted how depth was calculated. There’s no easy way to Debug shaders’ values; otherwise, we might’ve found a solution or at least found where the problem was.

I’ve also noted some warning signs on my surface shader when you look at it in the inspector. I’ve checked other shaders, and they didn’t have any warnings on them granted, they are unlit, and everything worked fine in there. A possibility is that those warning signs caused the depth not to work in my surface shader. I compiled the code and checked what those numbers referred to, making me even more clueless. Not all have comments stating what those variables and values are for, so it’s hard to know what the issues could be. I quickly disregard them as I have no way or idea of how to fix those warnings, and since it’s quite late in the evening and time is running out.

Depth not working in surface shader

In the video above, you can see that the whole plane/water changes color. This is not supposed to happen. The video before showcases how it’s supposed to look like. On Wednesday, the day of the upcoming iteration process, I’ll ask people for their input and see if we can come to a solution. If not, I’ll proceed with the project with the unlit shader.

Debugging 2.0

When I arrived in class, I looked for information from the warning signs in my shader when looking in the inspector. I asked chatGPT and Unity Muse for more information to see where the issue could be. One by one, classmates arrived, and I asked if they had any idea of how I could fix my issue. They had no idea. Eventually, when one of my teachers arrived, I asked him for help. He gave me some tips, and I tried to see if I could find the issue with his tips in mind. I quickly saw that one line of code gave me those warnings, but specifically, the _CameraDepthTexture seemed to be the underlying issue. _CameraDepthTexture is a built-in texture from Unity, so there shouldn’t be any reason why it’s not working, especially since it also works in the unlit shader.


_CameraDepthTexture is a built-in texture in Unity that stores the depth information of a scene from the camera’s perspective. Each pixel in this texture represents the distance from the camera to the corresponding point in the scene.

Unity Muse

To see if the _CameraDepthTexture is working accordingly, I went to debug in my shader based on visualization. The code below showcases what I did to debug it. The result that I received was a black plane, which is not supposed to be the case. This means that the depth values aren’t being properly read or the depth texture isn’t set up correctly.

float existingDepth01 = tex2Dproj(_CameraDepthTexture, IN.screenPosition).r;

// Visualize the depth values as grayscale colors
float depthVisualized = existingDepth01 * 10; // Adjust the multiplier to change the visualization range
float3 depthColor = float3(depthVisualized, depthVisualized, depthVisualized);

o.Albedo = depthColor;
o.Alpha = 1.0;
[SerializeField] DepthTextureMode depthTextureMode;

private void Awake()
{
    SetCameraDepthTextureMode();
}

private void SetCameraDepthTextureMode()
{
    GetComponent<Camera>().depthTextureMode = depthTextureMode;
}

I already have a script that I can use to enable depth mode textures, and it’s attached to the camera object. So, I highly doubt that it’s not set up correctly. Based on my own assumption, I believe that the _CameraDepthTexture isn’t getting the correct value as showcased by the debug process. I’ve tried several options given to me by Unity Muse and chatGPT but without any success. Before I spent any more time fixing the issue, I asked myself what the pros and cons were for proceeding with surface shader and unlit shader.

Based on what I know and have found out, I have set up some pros and cons for unlit and surface shaders. The pros of the unlit shader outweigh the cons of the surface shader. I already spent two and a half days trying to fix the issue. If my peers, the internet, and AI can’t help me solve this issue, it’s best to move on to something that works and where I can get help from others.

Unlit shaderSurface shader
Pros
– More resources available online
– One of my guildmates could help me out
– Already converted my surface shader code to an unlit shader
– It’s more accessible with other pipelines

Cons
– I have to write lighting, shadows, and reflections myself
– Redefine my scope
Pros
– Don’t have to deal with lighting, shadows, and reflection

Cons
– The issue kept persisting, and no idea how long it would take to fix it
– Not a lot of available resources for surface shader
– Guildmates aren’t able to help out
Pros and cons for both unlit and surface shader

Week three

Week two wasn’t the best week based on how much I managed to do within a few days due to a busy week and the fact that I couldn’t fix my surface shader issue and had to move forward with an unlit shader. The decision to proceed with an unlit shader might be a blessing in disguise, as now I have a clearer idea and vision of what I need to make a good-looking water shader. So, let’s talk about scope.

Scope

However, first, we must define what “beautiful” is before discussing the scope. Not knowing exactly what beautiful is will hinder the scope as it basically means there are no requirements. What makes something beautiful? What do I need to add to make my water shader look good? Let’s think about what components make a water shader beautiful.

When I look at the reference, what pops out the most is the reflection on the water. It’s jaw-dropping. Adding refraction to make the reflected environment/object seen on the water look distorted also looks great. Water that’s supposed to be beach water isn’t water without foam, and it looks great since it has caustics added to it, the small foam effects, and depth. You can also see that it has sparkles. It gives it that je ne sais quoi feel. It’s like a glitter effect that makes it stand out more. It also mentioned that it contains a custom (Amplify) toon shader. That’s probably why it looks a bit more anime/toon-ish. Aside from the visual effects, it also contains waves, but that’s what you’d expect from a water shader. However, the waves are very calm-looking. It gives you that peace of mind when you stare at it for too long. Queue that relaxing vacation music and seagull noises, grab a cocktail, and enjoy the scenery.  

Based on the “analysis,” what could my requirement be? What should my water shader contain to make it look as good or at least somewhat close as the reference? These are components that I believe, based on the reference, will make the water shader look good.

Must havesNice to haves
– Calm waves 
– Depth/foam 
– Transparency
– Lighting 
– (Planar) reflection
– Distortion/refraction
– A beach/island like environment
– Caustics
– Sparkles
Scope

Water’s glow-up

In one of the previous videos, you could see that my unlit shader looked pretty bad in comparison to the surface shader one. I first wanted and needed to fix how it looked overall. The water was too dark, and the waves didn’t look great. I looked for different normal maps to see what results they would bring. I also added foam, which I couldn’t add the previous week. It might be hard to see in the video, though. Also, during this time, I received access to Unity’s AI textures and sprites beta. I decided to see if the textures could provide anything good since I could always convert the textures into a normal map. The texture you see in the video is the one I generated with Unity’s textures AI.

Fixed water color and waves

I found the waves too busy even when changing the intensity of the normal maps. It doesn’t look too bad at a lower intensity, but I’m unsure about the look. I converted the texture into a normal map, making me question whether I should stick with it or look further. It made me question it, so I searched for more available options. Ultimately, I went with this look since I think it looks pretty interesting with the foam. It kind of gives me that northern light vibes.

Testing waves with the foam look
Her glow-up

Lighting

For lighting, guild members have mentioned specular and diffuse lighting. When I looked up which lighting model was used for the surface shader, it used the standard lighting model, which uses Lambert for diffuse lighting and Blinn-Phong for specular lighting. If I wanted to make my shader look the way it looked with the surface shader, I would have to add those two lighting models to my project.

During our workshops, we got a lecture about lighting. I have the memory and attention span of a goldfish, so I couldn’t remember much of what was said. I went through the PowerPoint presentation and didn’t get much wiser. Yes, the formula of diffuse and specular lighting was shown, but I needed extra information to understand how it all works. I searched online for explanations about diffuse and specular lighting. I came across another video from Freya Holmér that explained diffuse (Lambertian) and specular (Phong and Blinn-Phong) lighting. Perfect, as I needed Lambertian and Blinn-Phong to see how my unlit water shader would look with the lighting models that the surface shader uses. The only difference is my variable naming, and I applied it directly to my water shader.

Diffuse (Lambertian)
Specular (Blinn-Phong)
Diffuse (Lambertian) and specular (Blinn-Phong) combined

The lighting makes a difference, although it now removes the water’s transparency. This is easily fixed by adding a property that can change the transparency value since, in the video, it was always set to 1. The difference between my unlit and surface shader is that my waves have no bumps and reflection. They’re still pretty flat. I mean, I personally think it looks great, but it does need that bumpiness to highlight the waves.

Week four + overtime

Before I went to do anything else, I checked online what other stylized water looked like. From what I’ve seen, most, if not all, don’t have foam on their water. They all provide a top-down view, and it’s usually sparkles that you see and or reflection, and not foam. However, it really depends on what style of stylized water you’re making since it depends on what kind of texture/normal map is being used. The foam that is used is usually those that are close to objects/landscapes. So, based on my findings, are the random foam sprinkles on top of my water necessary? Absolutely not. Does it add something? Sure, but it’ll depend on what I’m aiming for. For now, they’re unnecessary for what I’m trying to achieve.

Bumps

The main issue that my shader currently has is a lack of wave height; it’s flat. I’m already using normal maps, but apparently, I’m not using them correctly. To tackle the issue and get a better understanding of normal maps, I watched another Freya Holmér video explaining normal maps and tangent space. By applying that knowledge and adjusting the lighting code, the waves actually have some height to them now, and it doesn’t look entirely flat anymore.

Normal maps are a type of Bump Map. They are a special kind of texture that allow you to add surface detail such as bumps, grooves, and scratches to a model which catch the light as if they are represented by real geometry.

Unity
float3 tangentSpaceNormal = BlendScrollingMaps(i);
tangentSpaceNormal = normalize(lerp(float3(0, 0, 1), tangentSpaceNormal, _MapsIntensity));

float3x3 mtxTangToWorld = 
{
     i.tangent.x, i.bitangent.x, i.normal.x,
     i.tangent.y, i.bitangent.y, i.normal.y,
     i.tangent.z, i.bitangent.z, i.normal.z,
};

float3 worldSpaceNormal = mul(mtxTangToWorld, tangentSpaceNormal);
Waves not looking flat anymore

Reflection

Unity already provides two different reflection options for us: screen space reflection and reflection probe. Other viable options to create reflections ourselves are planar reflection and skybox reflection. Note: I’m working in the standard render pipeline, and it does not support planar reflection probes; otherwise, I would’ve tried it out as well.

Screen space reflection

Screen space reflection does have a bad reputation for being “ugly” or “non-realistic.” According to Unity, Glossy floorings and wet planar surfaces are good candidates for receiving Screen Space Reflections (Unity, n.d.-a). I’m using a shader, which already doesn’t seem like a great combination as it’s written in an unlit shader, meaning I don’t have the standard metallic and smoothness included and must fix it myself. But to test out how it would look in my project, I added post-processing and, in it, added the SSRs. Added a plane with a standard material and set the metallic and smoothness values to 1 to get reflections. As you can see from the image, it doesn’t look great at all. It’s nice to see my skybox reflected on top of the plane, but the reflection of the cube doesn’t look great. Another issue with SSRs is that the rendering path needs to be set to “Deferred”; otherwise, it won’t work. As deferred is not set as a standard, it could impact post-processing components in the future. Based on my findings, I won’t use SSRs as one of the reflection options and move forward with the next possible option.

Reflection probe

The reflection probe seems like an ideal reflection source for my water. I don’t need any material, and I can get the information based on a cube map. I tested the reflection probe based on the plane and cube I used for the SSRs test. With the reflection probe, I can clearly see the reflections of the skybox and cube very well. However, the reflection of the cube seems very off. It’s very off in the distance when it’s supposed to be at least near the cube, and it’s also a lot taller. Regardless, I applied the reflection probe cube map to my water shader to see how it would look with distortion. For that, I needed a new cube map property to sample a cube map texture and apply it to the returned value. As you can see in the video, the distortion due to the normal maps is looking awful. It’s like tree roots are floating in the water. I mean, it could be an interesting effect, maybe, but definitely not the look and feel I’m going for. I tried adjusting values, but it ended up more as static noise than anything else. Based on my findings, reflection probes are not the answer to getting good reflections.

Reflection probe cube map

Planar reflection

The reference asset used planar reflection. That’s why you can see the environment being reflected on top of the water. It looks really good. However, I have no idea how to make a planar reflection, nor do I know how it works. To get a better understanding of the concept, I went to search around for information on how I could implement it. I found a video about implementing planar reflection based on stencil buffering. I went to follow the tutorial and tried to understand what he did. The render texture generated via script is accurate, but it’s very weird. I disabled my terrain and continued to look at it with just my water plane and some objects to see if the reflections were correct. They’re not correct whatsoever. It doesn’t capture the skybox very well and somehow blended with something else since part of the clouds got meshed into a long oblivion. They’re also split in halves, which makes sense since it’s supposed to be a mirror, but it doesn’t look right. In the game view, it’s mirrored. The rotation needs to be flipped for the objects to have their correct reflection. I’ve tried to fix it alongside help from chatGPT but without any success. I’ve tried working from scratch, so it’s just a normal flat plane, but that gave it even more weird problems. I’ve spent quite some time on it, so I’ll call it quits and try a different method to at least have some reflections on top of my water.

Weird reflections

Skybox reflection

The idea to use skybox reflection actually came from a video I watched a while ago. He applied skybox reflection to his water shader, so I decided to do the same and have the skybox reflect on top of my water. I already had everything I needed when testing the reflection probe, so I could jump right into testing. Looking at the scene view, off the bat, the skybox reflections look better than the reflection probe cube map. Primarily, on the right side of the water is where the difference can be seen. It looks more clear and detailed. Now it’s time to see how it would look like when distorted. It looks way better than the reflection probe cube map, but now the waves have begun to lose their bumps; you can barely see any waves. I tried to add a height map to increase the bumps on my normal map, but with a lot of trial and error, I couldn’t seem to get extra height on it. I checked the Unity Documentation to get more information about normal maps. In Unity, there’s a button called “Create from Grayscale,” where you can adjust the bump level of your normal map. I set it to the max it could do, and now you can see the bumps on the waves again. Skybox reflection is a good alternative since I didn’t manage to make planar reflection work. It still gives some form of reflection, which adds to the look and feel.

float3 reflectionVector = reflect(-viewVector, worldSpaceNormal);
float4 cubeMap = texCUBE(_CubeMap, reflectionVector);

return float4 (cubeMap + diffuseLight + specularLightBlinnPhong, _AlphaLerp);
Without extra bumpiness
With exra bumpiness

Look and feel

In the previous videos, you can see that I already did something to my terrain. It’s not looking realistic anymore. I removed everything from that terrain, so only the model was left and applied a shader to it. The shader is attached to a material that I put in the terrain settings. The shader that I’m using is from a paid asset, so I haven’t done anything to it. I tried different shaders out, but they didn’t look nearly as good as this one.It’s now looking more like a sand environment instead of a mountain. The next thing that needs to be added is post-processing.

For post-processing, I’ve added Ambient Occlusion to get a bit more highlight on the terrain to gain a bit of depth so that it isn’t too flat-looking. Color grading to tone the overall color down since it was extremely bright. I also have applied a vignette to get it a bit darker around the edges of the screen. It also slightly darkens the overall look, which I don’t mind. Any of them can be disabled or adjusted based on preferences.

For post-processing, I’ve added Ambient Occlusion to get a bit more highlight on the terrain to gain a bit of depth so that it isn’t too flat-looking. Color grading to tone the overall color down since it was extremely bright. I also have applied a vignette to get it a bit darker around the edges of the screen. It also slightly darkens the overall look, which I don’t mind. Any of them can be disabled or adjusted based on preferences.

After adding the post-processing effect, it looked pretty good! However, what I find extremely annoying is at the far away bit, you can see the reflection jittering when the plane moves up and down. I tried to combat that by applying fresnel (based on his explanation of fresnel, I got the formula working) and adjusting the watercolor intensity. To me, it looks more pleasing to look at now. Adding fog would’ve probably made it look better, but I couldn’t find a fog setting, and otherwise, I would’ve had to make a shader for it, which I didn’t have the time for, or change to HDRP, that already supports fog.

I also got feedback to add music to the scene to really sell the water shader. However, I couldn’t find a fitting audio clip that would suit the scene’s ambiance. The waves’ audio was too loud, deep, or high. I was also afraid that the mismatch of the audio sound, together with the waves, would ruin the immersion. Regardless, I think I have found one that’s decently okay. If the audio ruins anything, then it can always be muted.

float fresnel = dot(viewVector, worldSpaceNormal);
fresnel = pow(1 - fresnel, 3) * _FresnelStrength;

return float4 (cubeMap + diffuseLight + specularLightBlinnPhong + (_LightColor0.xyz * fresnel), _AlphaLerp);
With fresnel and watercolor intensity applied
End product with audio

Conclusion

Looking back at how it first looked to what the water currently looks like is quite fascinating. How it came from a moving plane to a shader that actually looks like water with reflection is very pleasing. It’s safe to say that I’m pleased with how it looks, though, obviously, there is a lot to improve upon. I’ve kind of managed to reduce the jittering effect, but by no means is that perfect. Having the sky reflection as an alternative for planar reflection is pretty good, but having an actual planar reflection would’ve been amazing. These are but a couple of things to mention, but since I’m comparing my shader to the reference asset, I can’t help but feel inferior. Regardless of the outcome, I’ve learned much during these past weeks about (water) shaders, and I’m glad I don’t have to tip my toes into foreign territory anymore.

References

Acerola. (2023, July 24). Turning sine waves into water [Video]. YouTube. https://www.youtube.com/watch?v=PH9q0HNBjT4
CG Standard Library Documentation. (n.d.). https://developer.download.nvidia.com/cg/index_stdlib.html
Flick J. Waves. Published July 25, 2018. https://catlikecoding.com/unity/tutorials/flow/waves/
Freya Holmér. Shader Basics, Blending & Textures • Shaders for Game Devs [Part 1]. YouTube. Published online February 26, 2021. https://youtu.be/kfM-yu0iQBk?si=mrR6Ydcz01tFLFE7&t=2262
Freya Holmér. (2021, February 26). Healthbars, SDFs & Lighting • Shaders for Game Devs [Part 2] [Video]. YouTube. https://youtu.be/mL8U8tIiRRg?si=hNZ1cIWD-WSTBNcv&t=7709
Freya Holmér. (2021b, February 26). Normal Maps, Tangent Space & IBL • Shaders for Game Devs [Part 3] [Video]. YouTube. https://www.youtube.com/watch?v=E4PHFnvMzFc
Guidev. (2019, June 2). Planar reflection with Unity – Part 1/2 [Video]. YouTube. https://www.youtube.com/watch?v=tdIv9lJghVg
帕羅 Paro. Unity Water Shader Part 1 – Reflection, refraction and depth. YouTube. Published online January 2, 2023. https://www.youtube.com/watch?v=91XXV6WtEbQ
Pinterest. (n.d.). Pinterest. https://www.pinterest.com/pin/348184614948305582/visual-search/?x=16&y=16&w=532&h=343
Technically Harry. (2022, January 29). Another stylized water shader (Part 1) | Unity shader stream [Video]. YouTube. https://www.youtube.com/watch?v=VtFdTwPhb-w
Technologies, U. (n.d.). Unity – Manual: Writing surface shaders. https://docs.unity3d.com/Manual/SL-SurfaceShaders.html
Technologies, U. (n.d.-a). Unity – Manual:  Custom lighting models in Surface Shaders. https://docs.unity3d.com/2022.3/Documentation/Manual/SL-SurfaceShaderLighting.html
Technologies, U. (n.d.-b). Unity – Manual: Normal map (Bump mapping). https://docs.unity3d.com/Manual/StandardShaderMaterialParameterNormalMap.html
Technologies, U. (n.d.-a). Create reflections and real-time effects for PC and console games | Unity. Unity. https://unity.com/how-to/reflections-real-time-lighting-effects#:~:text=To%20enable%20Screen%20Space%20Reflections,to%20show%20Screen%20Space%20Reflections.
Thömmes G, Seaı̈D M, Banda MK. Lattice Boltzmann methods for shallow water flow applications. International Journal for Numerical Methods in Fluids. 2007;55(7):673-692. doi:10.1002/fld.1489 https://www.researchgate.net/publication/227709142_Lattice_Boltzmann_methods_for_shallow_water_flow_applications
Unity Toon Water Shader Tutorial at Roystan. (n.d.). https://roystan.net/articles/toon-water/

Assets/Helpers

ChatGPT. (n.d.). https://chat.openai.com
Engström L. Scrolling Normal maps. Tumblr. Published April 8, 2015. https://watersimulation.tumblr.com/post/115928250077/scrolling-normal-maps
Free Island Collection | 3D Landscapes | Unity Asset Store. (2018, January 26). Unity Asset Store. https://assetstore.unity.com/packages/3d/environments/landscapes/free-island-collection-104753
Free Stylized Skybox | 2D Sky | Unity Asset Store. (2022, February 10). Unity Asset Store. https://assetstore.unity.com/packages/2d/textures-materials/sky/free-stylized-skybox-212257
H., J. (n.d.). Sandy beach. Pixabay. https://pixabay.com/sound-effects/sandy-beach-calm-waves-water-nature-sounds-8052/
Katsukagi. (2019, March 14). Water 002. 3D TEXTURES. https://3dtextures.me/2018/11/29/water-002/
Petry, C. (2014, June 28). NormalMap-Online. https://cpetry.github.io/NormalMap-Online/
Stylized anime water | VFX Shaders | Unity Asset Store. Unity Asset Store. Published May 23, 2023. https://assetstore.unity.com/packages/vfx/shaders/stylized-anime-water-196896
Unity Muse | Ask anything. (n.d.). https://muse.unity.com/en/chat

Leave a Reply

Your email address will not be published. Required fields are marked *