Posts In: HLSL

Procedural Sunset

After implementing several new features in DirectX, including a bloom effect and a skydome primitive, I experimented with making skies that could pass as acceptable. Though not by any means blown away by the appearance of my procedural clouds (let's face it, when it comes right down to it, perlin noise doesn't look anything like real volumetric clouds), I was quite happy with the overall effect created by the skydome, the bloom shader, and a sunset-style fog.

The result:

Note that the cloud shader actually consists of a turbulent perlin noise, which gives the clouds a slightly nicer appearance than simple perlin noise would allow. Interestingly, the seed value of the noise can be animated, along with scrolling of the texture coordinate offset, to produce the illusion of moving, ever-changing clouds. Unfortunately, the technique is quite expensive, especially for high-resolution cloud maps. However, with the rapidly-expanding power of graphics cards, it may be viable to animate a full, dynamic, 1024 res cloudmap in at smooth framerates in the very near future.

SlimShady: Procedural Shader Generator

In my silence over the past week, I've been experimenting a great deal with pixel shaders, hoping to get to a point where I feel that I can use procedural shaders to generate any type of texture - grass, rock, metal, etc. A procedural texture system will be critical in the development of a realistic virtual world.

To aid me in my search, I've created an innovative system (with the rather cheesy name "SlimShady") that writes, compiles, and executes procedural shaders. Not procedural in the sense that the shader generates procedural textures, rather, procedural in the sense that the shader is randomly generated by the program itself. Yes, SlimShady is a program that writes programs.

The real idea behind SlimShady is as follows: I can be working on something on my desktop and have SlimShady running on a laptop connected to my TV, such that the program will write a random shader, execute it and animate the seed value for a minute or so, then rinse and repeat. As I'm working, I can occassionaly glance at the TV to see if the shader catches my eye. If so, I hit return on a wireless keyboard, which instructs SlimShady to save a preview image and the full source code for whatever shader is currently executing. In essence, I can sift through an infinite number of shader permutations without having to touch the code! Not to mention, it makes a rediculously cool display...I could spend hours watching the program go to work on the TV.

Here are a few simple shaders written by an earlier version of SlimShady. The newer ones look even better! Also, note that the shaders are generally animated with a seed value - meaning they dynamically morph in real-time, making them far more interesting than can be conveyed in a static image.

Fractals in HLSL

Here's an interesting little shader I wrote today, initially as an attempt to render the Mandelbrot Set in real-time. Unfortunately, my attempt failed somewhere along the line, because the output set only vaguely resembled the famous Mandelbrot image. Instead of quitting, I played around with the equation and came up with some interesting, real-time morphing fractals. Couple that with a little bloom filter and color transformation, and you've got a pretty nice little image.

Pixel shaders amaze me.

Selective Erosion of Heightmaps Using Density Maps

In an improvement on the original heightmap erosion algorithm, which (roughly) approximates fluvial erosion in terrains, I have incorporated a density map as an input to the erosion shader.  This is almost identical to the algorithm explained several months ago, though it seems to work far better and produces very interesting results.

Given a simple Perlin noise density maps, interesting effects can be achieved in the final, eroded terrain:

The left map represents the heightmap, whereas the right image represents the terrain density map.  It is easy to see that parts of the height map have been severely eroded; while other parts have stayed intact (white areas on the density map correspond to areas of high terrain density where erosion will have less of an effect).  The effect in this case seems rather unrealistic, since real terrains would not erode in such a non-uniform way.  The threshold values in the shader, however, can be used to control the degree to which the density map influences the erosion factor of the shader.

With more tweaking, I believe that this algorithm will be able to produce some very interesting features in terrains, as well as add a heterogeneous feel to them.

Procedural Trees, Part 1

Finally, after having thought about the automatic creation of tree models for many months, I implemented my ideas with the help of XNA and HLSL.  The results were absolutely fantastic, far better than I could have imagined:

Yes, admittedly, it's just a trunk.  But it sure is a good-looking trunk!

The trunk is actually just a basic primitive that's being displaced in the vertex shader in real time, which means that the tree model can be changed almost instantly by making changes to the handful of maps that are controlling the vertex shader.  As with terrains, I can generate trees - well, tree trunks, at least - nearly instantly.

The next step is to hammer out branches and leaves.  Considering how well the trunk turned out, I'm confident that the rest of the procedural tree system will turn out nicely!

Long live procedural content.

Real-Time Morphing of Dynamic Procedural Terrain

This is just plain cool.  Instead of approaching terrains by creating a set of vertices based on heightmap data and shoving them through a draw call, why not let the GPU do the work?

When I asked Victor Porof how he achieved such fantastic speeds in his procedural terrain video, he explained that, instead of actual translating heightmaps to vertex data on the CPU, he was using a vertex shader to displace the vertices dynamically based on the loaded heightmap texture.  The implications of this?  Any changes to the heightmap could be immediately visualized with no hiccups and no load on the CPU.

Now, at first I thought "WHAT!? You can't do texture lookups in vertex shaders!!"  Thankfully, I was wrong!  Vertex Shader Model 3.0 and above supports VTF (Vertex Texture Fetch), which allows texture lookups within the vertex shader.  I yelped with joy when I learned this.

At first, I implemented the method using just a static heightmap to observe the performance difference.  Indeed, the method is fast.  More importantly, however, it's possible to generate new terrains very, very quickly thanks to the immediate response from the GPU and the lack of CPU workload.  How quickly, you ask?  I decided to find out.

After modifying the Perlin multifractal shader code to add some continuity (i.e. make the noise function less sensitive to seed such that adding .0001f would only make a small change to the overall terrain), I threw in some code to increment the seed and update the heightmap every frame.  The results?  Unreal.  I had before me a dynamic, morphing terrain based off of a complex Perlin multifractal shader running at roughly 70 fps.  It looked really, really cool.

Let's take a moment to reflect on the implications of such speed.  The ability to generate new heightmaps at 70fps means that, really, we can generate 70 completely unique terrains in one second (by simply setting the noise function back to normal, where a tiny change in seed value gives you a completely new terrain).  Now, I'll be the first to admit, Perlin multifractal isn't enough for a beautiful terrain by itself.  But, theoretically, I could generate a terrain of 70x the complexity, as far as shader instructions are concerned, and still only require roughly one second per terrain.

If those numbers don't really sink in, I'll put it this way: beautiful, complex, procedural planets are looking really feasible.  In fact, at those speeds, I don't think it would even be necessary to save assets to the HD when exiting from the virtual could just recreate all of the procedural assets (or at least the terrain) at runtime.  Incredible!

Unfortunately, I don't have any screenshots of this epic triumph, since they really wouldn't show the action.  If I can dig up Camtasia sometime this week, I'll try to upload a video so I will always be able to remember the day that I learned how to create 70 unique terrains per second!

Terraced Multifractals

Yes, it's official, I'm now severely addicted to HLSL. The raw power that it opens up for Project: A New Reality is simply mind-blowing. Experimentation with all sorts of random ideas has led to some pretty cool-looking terrains, though I'm going to spare the blog from any further screenshot explosions until I have something really draw-dropping.

Here's an interesting example of a neat heightmap shader. The algorithm is as follows:

  1. Generate a Perlin Multifractal [1]
  2. Generate a Perlin Multifractal [2]
  3. Reduce the bit depth of (1) by using the function y = a * floor(y/a), where a is the number of discrete levels to allow [3]
  4. Generate a final heightmap by blending [1] and [3] with [2] as the blending function

Basically, that's a complicated way of saying take a heightmap, force it into discrete levels, then blend it back on itself with another fractal function.  This way, natural-looking terraces appear but blend smoothly back into the terrain.

This example heightmap is a little too aggressive with the terraces, but that can, of course, be easily modified by fooling with the blending function.

Perlin Noise Shader

I did it!!

After hours and hours of reading tutorials on HLSL coding, I finally managed to write a perlin noise shader. The result? Unbelievable speed. At least 100x faster than the CPU-based implementation I coded a few months ago. It can easily display a new 512 by 512 heightmap at 60 FPS. Yes, that's 60 unique heightmaps in one second.

With this kind of raw power now available to me for procedural content generation, I know that great things lie ahead in terms of my work with virtual worlds.

For reference (since I lost the old one and had to rewrite from scratch), here's the noise function I used:

float Noise2D(float x,float y)
float a = acos(cos(((x+SEED*9939.0134)*(x+546.1976)+1)/(y*(y+48.9995)+149.7913)) + sin(x+y/71.0013))/PI;
float b = sin(a*10000+SEED) + 1;
return b*.5;