Tuesday, October 26, 2010

texture lookups in mental mill

One thing that confused me in the early beginning when I started writing shaders was this: Sometimes texture lookups that caused artifacts that looked somewhat like aliasing when previewing them in the realtime preview. Later I learned why and how that happens and how to avoid it. So I will present the problem and its solution:

Cutting texture coordinates

Whenever you 'cut' texture coordinates, this creates discontinuities. In this example I take a fraction of the texture and loop it over across my preview geometry. This is accomplished by using an fmod() operation. After performing the texture lookup and previewing the shader, you can see some jaggy artifacts that are especially noticeable and disturbing when moving the geometry. In the magnified region you see that between
the tiles some dark pixels show up.

Wrong Mip Map lookups

So what is going on here? You might have guessed already that the fmod() operation did something bad to our coordinates. Well, one could say so, but the operation itself is innocent. The reason for this behavior is that during rasterization the GPU tries to determine the MipMap level automatically by comparing the texture current coordinates with the values of the adjacent fragment or pixel. As long as the coordinates are continuous it can correctly determine the right MipMap level.
But when the coordinates were 'sliced', this created discontinuities. When such a discontinuity is reached, the difference between two neighboring pixels or fragments is so big, that the GPU chooses a very small MapMap which causes the artifacts in the realtime preview.

Fixing incorrect lookups by using custom derivatives

How can we remedy this situation? We need the texture lookup to choose the appropriate MipMap level, even though there is a discontinuity. That is why there is an overloaded version of the tex2D texture lookup in mental mill (which also exists in the Cg language, by the way).

tex2D(texture2D texture, float2 coordinates, float2 coord_dx, float2 coord_dy)

The second two arguments are the derivatives to use. First we need to know: What are derivatives? The derivatives indicate the changes from one pixel or fragment to the next. If the user performs a texture lookup without providing custom derivatives, the derivatives of the texture coordinates are automatically calculated and used.

We just saw that this operation failed. We also know why, so let's calculate our own derivatives and pass them to the texture lookup function above.

How to determine the correct derivatives

First we need to learn about the functions that calculate derivatives for us. These are:

ddx() and ddy()

These are overloaded functions which take a float, float2, float3, float4 or Color. The functions return a variable of the same type which contains the derivative. The derivative returns the difference of a variable between two fragments. ddx() calculates the difference in the x-direction while ddy() calculates the difference in the y-direction.

The derivatives that we really want to use are the derivatives of the original texture coordinates before we sliced them up. Why is that?
We didn't change the scaling of the sliced texture coordinates. We just set a different tiling. This means that we can use the derivatives of the original texture coordinates for the texture lookup.

So we can write:

float2 x_deriv = ddx(texture_coordinate[0].xy);
float2 y_deriv = ddy(texture_coordinate[0].xy);
result = tex2D(tex, coord, x_deriv, y_deriv);

Now, lets see how that affects the shader:
As you can see now, the aliasing artifacts are gone.

So whenever you see such artifacts, check if you are causing discontinuities in your texture coordinates. These are created by using the fmod operation for example, but if-else clauses that assemble texture coordinates based on a condition can cause this.
If that is the case, try to resolve these issues by using the overloaded texture lookup that uses derivatives. This is especially relevant if you intend to use your mental mill shader in a realtime application.

I hope next time you run into this problem you will remember this post and quickly resolve these issues.


Wednesday, October 20, 2010

Mandelbrot revisitted

Recently I was updating one of the first shaders that I created with mental mill called fractal. What I love about fractals are, that a simple algorithm yields such impressive shapes with sheer endless details. Obviously I was intrigued and was inspired to write a shader that made use of them.

One thing that I like about working with mental mill is, that you can make any parameter tweakable. So if you have a formula that uses three variables, they don't necessarily have to be constants. Instead you can edit them through the mental mill GUI. Sometimes you can yield surprising results by making variables tweakable that are 'not supposed' to be tweaked.

That happened to me when I recently revisited the MetaSL code for my fractal shader. I was looking at the abort conditions in the fractal loop. There are two conditions:
  1. If the squared distance of the original point exceeds 4.0, then the point is not part of the set and is discarded.
  2. If the iteration exceeds the maximum number of iterations the process stops. At this point it is not certain whether the point will diverge or not, however it is regarded to be part of the set.
There is not much one can change about the second condition (and the number of iterations is already a shader parameter anyway). Making this a parameter made the abort condition tweakable with some astounding results. If you lower the value, you can see how the rendering morphs into a cartoony look.

This is quite surprising and beautiful to see. If you adjust the values carefully you can get some really great pictures. If you want to use the shader in mental mill, you can find it here. TThe shader parameters are straightforward. You can switch between the Mandelbrot and the Julia fractal. However, if you want to view the Mandelbrot fractal in its original shape, you must set the shader input parameters start_real and start_imaginary to 0.
Here are some other presets that I created using that shader:

That's it for now. By the way, there is a lesser known variation of the Mandelbrot fractal: The Buddhabrot which unveils even more fascinating shapes. Check out Melinda Green's page!


Introduction to the mental mill blog

Welcome to the mental mill blog!

This blog is all about mental mill®, the shader workbench for programmers and artists. So what kind of information can you expect here? On this blog you can read a lot of random information about mental mill, MetaSL and 3D computer graphics. The information will be quite versatile. I plan to post shader snippets and complete shaders for mental mill. I will also include Tips and Tricks and best practices when using the mental mill application and the typical do's and dont's.
In some cases I will show workarounds and other non-official tricks, that may be interesting for those of you who like to get your hands dirty.

Since I have an artist background and I like shader writing, this will be the place where both worlds meet for new ideas to inspire your own projects. Also if I come across extraordinary work from other users, which has been published, I will link you to it.

That's all for now, I am looking forward to writing some interesting stuff here, so check out what's coming up! Shade safe and have a nice day :D