Self-Occluding 2D Textures

Or, "How does something like Sprite Lamp work?"

Part 1: Normal Mapping

Very common technique for preserving detail while reducing triangle count

  • Input: 3D high-detail model and/or 2D Heightmap
  • Algorithm samples model or heightmap at various points, generates curves
  • Output: Normal map, which stores direction texel faces in RGB image
  • Assuming we're already in tangent space:
    • Subtract light position from texel position to get light direction (make sure it's normalized)
    • Apply dot product to light direction and normal texture sample
    • Multiply result of dot product to final texture color
  • In short, if the surface faces the light, it gets more light; if tilted away, it receives less light

Part 2:

Horizon Mapping

The 'self-occluding' part

Bake in more information:

 

How much light does a texel get from a given direction?

Up

(0, 1, 0)

Right

(1, 0, 0)

Down

(0, -1, 0)

Left

(-1, 0, 0)

Could be 4 images -- linear data, so it could be 4 channels in a single image instead (rgba maps to urdl, for example)

This covers a case that normal mapping doesn't handle:

 

Normal mapping just indicates what direction texel faces

 

When generating or authoring a horizon map, we can bake in information about other geometry into this texel

 

For example, if a texel would face light and be 'lit' under normal mapping, but would logically be darkened by a taller texel that blocks light, we darken texel in that direction's horizon map

 

Useful for Both 2D Sprites or 3D Assets

In addition to normal lighting:

  • Assume we have 4 horizon maps
  • Assume they each have vectors in up/right/down/left direction assigned to them
  • When light aligns with a direction assigned to a horizon map, that horizon map contributes more heavily, scaling the texel's light down by its own occlusion information
  • When no horizon maps contribute to the texel, assume full light -- facing head-on
  • Assign weights/coeffecients:
    • Up = clamp(dot(light direction, (0, 1, 0)), 0, 1)
    • Right = clamp(dot(light direction, (1, 0, 0)), 0, 1)
    • etc. for Down and Left
    • Original contribution fills in what's left -- e.g. if light direction is perfectly lined up with surface normal, horizon maps are useless
      • 'Original' weight = 1 - (up weight + right weight + etc.)
      • Then, multiply weights by texture samples and lighting calculations done already
      • Final light = (original light (from normal lighting algorigthm) * original weight) + (up weight * up texture sample) + etc. for right/down/left

Live demo!

(I also took the time to start using WebGL and migrate from native applications and C++)

Self-Occluding 2D Textures

By tdhoward

Self-Occluding 2D Textures

  • 596