Watershader | HLSL

I developed a watershader in the contex of the computer graphics II course at the Beuth Hochschule Berlin.

The impact of shaderprogramming raised in the last few years. Now a days the visual power of games would be unimaginable without shaderprogramming. All techniques like, normal mapping, post processing effects, multiple light passes, shadow mapping etc. are computed bye the graphics card.

Special topics need an special introduction, so I will start with basics about the graphics pipeline.

By outsourcing the hardware intensive calculations for example: cloud simulations, watershading, HDR rendering, realtime ambient occlusion and hardware instancing  we will gain 200% more calculation power.

This happens by relocating the intensive calculations from the CPU to the GPU.

The topic of creating efficient shaders reminds of a doctoral thesis, thus I don`t wanna give you a step bye step tutorial. This article presents a theoretical approach to give you some background information about the graphics pipeline and in this coherence the Watershader.

shader_header

HLSL represents the High Level Shading Language which was developed as a cooperation of Nvidia & Microsoft.
HLSL was developed to bring the cryptically assembler code one layer higher, to make it easier to understand and  implement for artist and programmers.

HLSL compiled code is primarily faster as code compiled by any High Level Language, like C++ and C#.

Shaders can also be created purely with assemblercode but I don’t want to talk about efficiency and resulting benefits.graphics_pipeline

The graphics pipeline is a model which describes the different stages, which are needed to be passed through to get the final render.
These steps depend on your soft- and hardware as well as the targeted display quality. The connection to the graphics pipeline is possible via a Graphic -
API Frameworks like DirectX or OpenGL. This model has to be understood to start with shaderprogamming, it is a really essential step to go ahead.

The individual Steps:

  • The player starts a game
  • This game is shown on your display with the help of a graphics API
  • CPU sends data to the graphics card Front End; The Front End uses the communication from CPU to GPU
  • Data are provided in form of vertices, those are now in the object space
  • Data will be sent to the programmable vertexprocessor

Programmable Vertex Processor == Vertexshader !

  • Data will be prepared for the Pixelshader, e.g.:
  • Transformation of vertices
  • Adjusting vertex normals
  • Colorizing by vertex
  • Assigning texture coordinates per vertex
  • Illumination per vertex

Output:  Transformed vertices with attributes!


Primitive Assembly

  • Connect the vertices and construct a triangle

Output: Triangles


Rasterizing & Interpolation

  • Triangles will provided with screenspace coordinates
  • Interpolation of vertexdata to pixel coordinates; all pixels between two vertices will be interpolated (color, normals, uv coords etc)
  • All data from the vertexattributes will be interpolated

Output:  Interpolated pixeldata

Programmable Pixel/Fragment Processor == Pixelshader !

  • Interpolated vertices were arranged and changed by the pixel processor
  • The pixel shader is qualified to make changes on the pixellayer

Output: Final color per pixel


Final Rasterization

* Final rasterization were performed

Frame Buffer

* Provided Data are now ready and persist on the stack

The whole process looks like this:

grafics_pipeline_example

Software Setup

Basically FX data can be created with any rich text editor , but I suggest to not use an editor without  features like intellisense or any form of code completion.

Intellishade  is a plugin for VS 2008 Pro which delivers intellisence and code completion.

I like to develop with MS Visual Studio. It does not matter in which high level  language you want to interact with you shader. I prefer C# and XNA for my purposes.

So until now we got enough background information to start with an example from my watershader.

watershader1

As mentioned before, this is not an step by step tutorial. I want to give you a short introduction about this powerful technology.

There are several ways to create a watershader. To keep it simple I grab the easiest one.

* Procedural generation of a polygon plane with 128×128 vertices
* This grid would be created during runtime and is now in the GPU Front
* Vertexdata will  pass through the programmable vertex processor
* Our water has to move to get the believability, this means we must animate our vertices from the plane in dependence of a function

Every single vertice is animated bye four sum additive sinusoidal oscillations. We calculate the sum of this four sinus waves with a disturbance to simulate the behavior of the waves. By adding new parameters we get a wavelike movement. It is important in this stage to bring in correct mathematical formulas.
I prefer to do this step on paper to get a clear vision about the complexity of the shader.

Attributes of the animated wave

// Control over the Waves
float4 paramWaveSpeeds;
float4 paramWaveHeights;
float4 paramWaveLengths;

// Sinusfunktionen
float2 paramWaveDirection0;
float2 paramWaveDirection1;
float2 paramWaveDirection2;
float2 paramWaveDirection3;
float2 paramWaveDirection;

Skybox | Cubemap

You could present the scene with a infinity view. I decided to use a teqnique which is called “Cubemap”. This cube is generated procedurally inside the sourcecode. Every cube got six faces, every single face will be mapped with the help of the DirectX texture tool. I took some skybox textures from cgtextures.com these textures are a good starting  point.

skybox_360

Quick guide:

* Open the skybox textures in Photoshop, or any image editing application which you prefer. Use the ruler tool to select the faces.
* Save your faces with a selfdescribing name convention like: SkyboxMap1_Xup
* Open the DirectX Texture Tool and load one face
* Go to Format –> make into Cubemap
* You can select the empty faces under category View*
* Go to File once you selected another empty face. Select “open onto this Surface” and the tool will load the  previously selected face directly on the cube face.
* Repeat this process until all faces are mapped correctly
* If you maped all faces, save this image as *.dds data, you have now created your own cubemap texture!

The result should look like this:

skybox_cubemapped1

Specular highlight / Reflection


To get some nice specular highlights on the watersurface we must sample the complete DDS file:

Mathematic:

* Position the camera in 3D space with the help of vectors
* Point of view is directly on the water surface.
* The eyevector should be reflected on every vertex along their normal

float3 eyeVector = normalize(In.Pos3D - paramCameraPosition);
float3 reflection = reflect(eyeVector, normalW);

* The reflected vector hits the cubemap(Skybox.dds)
* We have to check where the vector hits the cubemap;
* Our cubemap has colorinformation which is discribed with a value in the range from 0-1

// Get the Color that is reflected by the current pixel, get the reflectionvalue by the color of the cubemap
float4 reflectiveColor = texCUBE(CubeMapSampler, reflection);

reflectionvector1

* Sum the three colorcomponents (RGB)
* The brightest point is at the value of 3, on the other hand the darkest point  has the value 0.
* Divided by the amount of colorchannels(RGB) –> We get a floatdegree between 0 and 1
* Reflectionvector will be reflected backwards from the cubemap
* The vector obtained a colorvalue from the cubemap with a power of 30.
* Only the brightest point goes in the final specular highlight calculation
* We have to filter all color values which are smaler then 0.95

// Specular reflections depends on the incoming light.
float sunlight = reflectiveColor.r;
sunlight += reflectiveColor.g;
sunlight += reflectiveColor.b;

sunlight /= 3.0f;
// Only >0.95 = sunlight, only light
float specular = pow(sunlight,30);

// Conclusion: the final colorized pixel
// fresnelterm between 0.5( right angle,  full reflection - 1 ( watercolor 100%)
// --> reflectivcolor = 0 if the camera is looking straight to the ocean + specterm
Out.Color = paramWaterColor*fresnelTerm + reflectiveColor * (1 - fresnelTerm) + specular;

Normalmapping

In the actual state our water looks very flat, we need to get more depth in it. On a classical water surface there is always a bit of noise in it. I suggest to use a tileable normal texture.
A tileable texture allows you to repeat the texture over the whole ocean and no seams should be visible. You get dirty seams if you do not use a tileable texture.

To solve this issue with a huge amount of vertices would be overkill for your CPU/GPU.

Normalmap from the watershader:

watershader_normal

We created the Tangent to World Matrix(TTWM) inside the vertexshader, the collumns are the normal, the binormal and a proper tangent per vertex.

// normal , binormale, and tangent --> define a new coordsystem
// Y is final --> depends on X and Z
float3 Normal = float3(0, 1, 0);
float3 Binormal = float3(1, 0, 0);
float3 Tangent = float3(0, 0, 1);
//float3 Tangent = cross(Normal, Binormal);

float3x3 tangentToObject;
tangentToObject[0] = normalize(Binormal);
tangentToObject[1] = normalize(Tangent);
tangentToObject[2] = normalize(Normal);

// Tangent to world matrix are necessary for the pixel shader,
// to sample the right texture coords from the bumpmap. for every vertice
float3x3 tangentToWorld = mul(tangentToObject, paramWorld);
Out.TangentToWorld = tangentToWorld;

The Pixelshader must sample the colorvalues from the normalmap.

// normal in positiv and negativ direction,
// Basic Color from 0-1
float3 bumpColor1 = tex2D(BumpMapSampler, In.TextureCoord)-0.5f;
float3 bumpColor2 = tex2D(BumpMapSampler, In.TextureCoord.yx)-0.5f;
float3 bumpColor3 = tex2D(BumpMapSampler, In.TextureCoord)-0.5f;

// Interpolate between the 3 vertices by this randomized bumpvalue
float3 normalT = bumpColor1 + bumpColor2 + bumpColor3;

This concludes my quick overview on the topic of watershader creation and graphics pipelines.

The watershader is still missing some functions to make it more realistic

* There are no refractions
* No caustics implemented

If you have any questions feel free to contact me: mail@iamthomasvogel.de

Happy shading

Resources:

There are a plenty of resources. Habibs blog was the best resource to get started with the theoretical background of this topic.

Habibs WordPressblog -> Best resource I fond on the net.

Riemers –> Good starting tutorial with XNA and  HLSL plus another  Methode for a watershader demonstration

XNA-UK –> a exelent blog which containts  HLSL tutorials plus  an oceanshader example

Valve Watershader description –> Watershading  in Source Engine