Thursday, November 25, 2010

Displaying functions in a MetaSL shader

In the previous blog entries I talked about the procedural generation of lines, squares and spheres. Next I want to talk about the possibility to visualize 1 dimensional functions in your shader as a plotted graph. Depending on your expertise you might never ever want to do that. I remember that I always wondered at school what function graphs are useful for. It wasn't until I started 3D rendering, animation and shader writing that I saw a need for creating my own functions or try to understand what a certain function looks like. Now that has changed and I check regularly the look and shape of my functions.
I will not discuss WHY you would want to do this. You will find out at the moment when you need it. Just make sure to remember this blog entry. 

Displaying a function graph on a plane

Sometimes it can be really useful to try to understand a function and what it does in a graphical way. I will present a simple framework for displaying your graph inside a shader.

Using texture coordinates as our coordinate system
First we will start by displaying a typical function graph on a plane. As in the previous blogs I will be using texture_space[0] as my coordinate system. In mental mill, if you switch to the plane preview geometry object texture space 0 aligns perfectly with the quad. This means, it covers the UV coordinates which range from 0 to 1 linearly.


Changing the range of the coordinate system
Now that we have a suitable coordinate system at hand, we take a closer look: The UV coordinates range from 0 to 1 which might be fine in some cases, however, before we start plotting a function, we should add a function that allows us to change the range of our coordinate system. I will call the function change_range. It maps the interval that lies between old_min and old_max so that the interval will be remapped to lie between new_min and new_max.

Example: If old_min and old_max are 0 and 1 and new_min and new_max is 10 and 20 a value that was 0.5 in the 'old' range will be mapped to 15 in the new range.

Here is the MetaSL code for the function:

// changes the range of a given coordinate system.
// Values that varied between 'old_min' and 'old_max' 
// will be mapped so that they vary between 
// 'new_min' and 'new_max'
float change_range(float old_min, 
          float old_max,
          float new_min,
          float new_max,
          float x)
{
    return ((x-old_min)/(old_max - old_min))*(new_max - new_min) + new_min;
}


If we pass the UV coordinates and choose 0 and 1 for OldMin and Oldmax we can choose any arbitrary values for NewMin and NewMax. See the following image where the coordinate space is remapped using the function mentioned above: 

You can see that the shader debugger clearly shows the new
range of the texture coordinates.
Outputting a function graph
Now that we have met all our prerequisites, we can start thinking about plotting our function graph. For each value on the x-axis we compute a value on the y-axis that we will output.
So this task is now straight-forward: We write whatever function we want to calculate and feed the x-coordinate (of our remapped texture space) as our function argument. For example if we just take a square function it would look like this:

float x_square(float x)
{
  return x*x;
}

We will pass the x-coordinate to our function so that it will be processed by the function to obtain the new value. Now that we have computed the new value, how can we output a graph? In older blog posts I mentioned that inside a shader we can not ask to draw lines or polygons, because a shader is executed for a given pixel when lines and polygons are already being rasterized. So instead of asking "draw here" or "draw there" we ask: "Where are we and what information we have currently available?" and based in this information we can decide in which color we want to output that pixel/fragment that is being rasterized.
So we know the current coordinates and we have also calculated the new value. How can we put that into a graph? As you know, we want to show the resulting value in the y-axis of the coordinate system. As we can not explicitely ask to draw a point at a certain position, instead we test if the value that our function returned is greater or smaller than the value of the y-coordinate of our coordinate system.
If it is smaller, we assign the 'lower' color, otherwise the 'upper' color.
Displaying the function y = x*x using the remapped texture coordinates
as our coordinate system
Of course you can add more functionality to your shader by adding input parameters that allow you to change other parameters of your function: In the image below, the power function is used and the user may provide different values for the pow-function.

Check out the code sample here, feel free to extend and reuse it for your own projects!


Related links:
RGBA's past and future intros This video is a seminar that was held during Breakpoint '07. In this video clip, IQ talks about how he and his demo group are creating their demos. It has a strong realtime aspect and I recommend watching this video especially to those users who are interested in realtime shaders and those who like the demo scene in general. 
Check out the section at minute 14. Here you can see how function can be (ab)used to generate procedural textures. Very inspiring! (Note: Don't be confused: The file info says that the file  1kb only, but this will be the full video file!)

No comments:

Post a Comment