quinta-feira, 28 de março de 2019

Mapgen4: Oblique Projection

One of my goals for mapgen4 was to explore non-realistic approaches, both to map generation and to map rendering. I wanted to explain the unusual projection I'm using.

For procedurally generated wilderness maps, it's very common to see a top-down view, often using colors to represent height or biomes:

Top-down view

It's simple to implement, especially in 2D, and it shows the areas of the map, including coastlines and rivers and lakes. You can see where the mountains are, but only because of the color and lighting.

It's also common to show a side view, often with perspective:

Side view

It's usually implemented in a 3D engine, and it's good for seeing things like mountains, towns, and trees. It's what you'd see in a first person view game. Features like rivers and lakes are distorted and often hidden behind other objects.

Sometimes you want to see both the top-down features (rivers, coastlines, lakes) and the side view features (mountains, towns, trees), and many games use an isometric or orthographic view:

Orthographic view

It gives you a bit of both. However, the top-down features are skewed compared to a pure top-down view, and the side view features are also skewed compared to pure side view.

If you look at a hand drawn fantasy map, such as the Lord of the Rings map, or the ones you find on Cartographers' Guild or Reddit r/mapmaking, you'll see artists don't feel constrained to one of the above three views. Instead, very often, they use both top-down view to show rivers and coastlines and side view to show mountains and towns.

Lord of the Rings map
Lord of the Rings map (from Wikipedia)

The rivers, roads, and coastlines are a top-down view, but the mountains and hills and trees and grasses are a side view. One way to think about this is that the mountains, hills, trees, and grasses are icons like these and therefore don't have to use the same view as the rest of the map. However, it's possible to implement this directly in matrix math. The trick is to use an oblique projection. It's not the standard rotate, translate, scale methods that you get from the matrix libraries in Unity or Unreal, but it can be represented by setting the matrix values directly.

Let's look at the 2D version:

Rotate operation Shear operation
Rotate vs shear operation
  • In a 2D rotation operation, some of x gets transferred to y and some of y gets transferred to x. It preserves the shape of the objects but distorts y and x.
  • In a 2D shear operation, none of x gets transferred to y and some of y gets transferred to x. It distorts the shapes and x but preserves y.

To make a hybrid of top-down view's x and y with side view's x and z, we can use a shear operation: preserve x and y, and transfer z to y.

The result is that the top-down view is preserved. When there's no z (mountains or trees), x and y stay exactly the same. But when there's z, it gets mapped to y, so that you can see the side of the mountain. This is entirely implemented in a 4x4 matrix, without using billboard sprites. Here's the result:

Top-down oblique view

I also have rotation and scaling and translating. I apply them in this order:

  1. Rotate
  2. Shear
  3. Scale
  4. Translate

I apply shear after rotate so that if the map is rotated, the z axis gets mapped to "up" in the output view. Otherwise, z would get mapped to "up" (y) and then y would get rotated, so the mountains would point sideways instead of up.

I also wanted to support both orthographic and oblique, so I blended the rotate and shear operations together (with one line of code!), so that I can smoothly interpolate between the two projections. I can also smoothly interpolate between top-down view and oblique, or oblique and side view.

I'm pretty happy with the effect!

More reading:

Sem comentários: