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.
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.
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:
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.
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.
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:
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);
* 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:
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







Entries (RSS)