Advanced Game Tech All

Terrain Raymarching


Arround 2002 I made my first attempts to visualize fractal mountains and test procedural texturing. At that time I was not able to raytrace polygons and my rasterization engine was really bad. So I chose to do a “raymarcher” to create some pictures cause it’s a really simple technique. What I implemented was a quite brute force thing and was very slow. But it was very simple to code, something I could afford, and allowed me to do some nice images, with reflections and some participating media too.【作者做地形生成的时候发现性能是个大问题,所以有了如下的内容】


That’s in fact the beauty of the technique – an incredibly simple code that produces interesting images. It’s perfect for small size demos, that’s why it has been used so much (think in Hymalaya/TBC or Ixaleno/rgba – the image on the right of this text, which is created from a 4 kilobytes executable). In fact you can code a basic prototype in few minutes (Ixaleno was pretty much done in one day, coded from scratch during 2007 Christmas; then of course I had to spend some extra days tunning the colors etc, but that’s another story.【这是一个非常简单的方法,你几分钟就可以搞定代码】


The basic idea is to have a height function y = f(x,z) that defines for each 2d point in the plane (x,z) the height of the terain at that point.【这个方法作用于高度图地形,这是高度图的定义函数】



The purpose of this article is not to generate such a function so that one gets an interesting landscape (see articles like the one on advanced value noise), but how to render those with the simple raymarching algorithm.【这里不讨论生成有趣的地形(见这里 ),讨论怎么去渲染地形】


Once you have such a function f(x,z), the goal is to use a raytracing setup to render the image and do other effects as shadows or reflections. That means that for a given ray with some starting point in space (like the camera position) and a direction (like the view direction) we want to compute the intersection of the ray and the terrain f. The simplest approach is to slowly advance in the ray direction in small steps, and at each stepping point determine if we are above of below the terrain level. The image below shows the process. 【对于高度图地形,如果我们需要做光照效果,我们要做的就是计算光源位置点和地形的关系,如下图。】


We start at some point in the ray close to the camera (the leftmost blue point in the image). We evaluate the terrain function f at the current x and z coordinates to get the altitude of the terrain h:h = f(x,z). Now we compare the altitude of the blue sampling point y with h, and we realize that y > h, or in other words, that the blue point is above the mountains. So, we step to the next blue point in the ray, and we repeat the process. At some point, perhapse, one of the sampling point will fall below the terrain, like the yellow point in the image. When that happens, y < h, and we know our ray crossed the terrain surface. We can just stop here and mark the current yellow point as the intersection point (even if we know it’s slightly further than the real intersection point), or perhaps take the last blue point as interesection point (slightly closer than the real intersection) or the average of the last blue and yellow points.【我们找光线上的一个点(x, y, z),同时去高度图f(x, y)获得其h,比较z,h。如果z>h则表示在地上,否则地下,地上地下的切换点就是相交位置。】





The mint, maxt and delt constants should be adapted for every scene. The first one is the distance to the near clipping plane, you can set it to zero. The second one is the maximun distance the ray is allowed to travel, ie, the visibility distance. The third one is the step size, and it directly influences the rendering speed and the quality of the image. The bigger it is, the faster of course, but the lower the terrain sampling quality will be.dolt,mint.maxt每帧都要调整,mint表示近裁剪面(ray开始),maxt表示的是远裁剪面(ray结束),dolt表示步长】


As you can see the code is terribly simple. There are many optimizations and improvements possible of course. For example, the accuracy of the intersection can be done more accurately by doing a linear approximation of the terrain altitudes between the blue and yellow points and compute the analytical intersection between the ray and the idealized terrain.【这个过程可以做很多优化,比如蓝色点和黄色点之间可以继续做差值计算来提升精度】


The other optimization is to note that as the marching moves the potential intersection point further and further (the bigger t becomes), the less important the error becomes, as geometric details get smaller in screen space as they get further from the camera. In fact, details decay inverse-linearly with the distance, so we can make our error or accuracy delt linear to the distance. This saves lot of rendering time and gives more uniform artifactas than the naive approach. This trick is described in several places, for example in the “Texturing And Modeling – A Procedural Approach” book.【另一个优化是依据越来越远地潜在交点,误差越不重要。因为随着距离相机越远,屏幕空间占比越小,所以我们可以使我们的误差与距离成线性关系。】



So the complete algorithm to build an image is simple. For every pixel of the screen construct a ray that starts at the camera position that passes through the pixel location as if the screen was right in front of the viewer, and cast the ray. Once the intersection point is found, the color of the terrain must be gathered, the shaded, and a color must be returned. That’s what the terrainColor() functions must do. If there is no intersection with the terrain, then the right color must be computed for the sky. So, the main code looks like this:【因此完整的image build算法就是,对于每一个pixel发出一条射线去找地形的交点,取地形交点颜色填充pixel




Usually terrainColor() will need to compute first the intersection point p, then compute the normal to the surface n, do some lighting/shading s based on that normal (even cast some shadow ray by using the castRay() function for doing some shadows), decide the color of the terrain surface m at the intersection point, combine the shading information with it, and probably do some fog calculations. Something like this:【terrain要做的工作见下面的代码】