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