Isometric render shader

from Red Blob Games
12 Oct 2019


Testing out some rendering ideas for WebGL. I had created something along these lines a long time ago http://www-cs-students.stanford.edu/~amitp/_test/isometric-visibility.html[1] for Flash but WebGL exposes more GPU features than Flash did, so I want to try:

 1  Notes#

 1.1 Sprite rendering

I initially copied the sprites into a separate texture to add padding and outlines. I also ran into a difference between Safari and Chrome/Firefox. In Safari canvas, drawing a canvas onto another canvas with shadow will apply the shadow, then clip to the rect I want to draw. On Chrome/Firefox it clips first, then applies the shadow. I ended up having to make an extra copy to clip first, then apply the shadow in a separate draw.

But I threw all that away. I’m now using the unaltered spritesheet.

1.1.1 Clipping

First problem I ran into was that when I drew a sprite, it would leak pixels from adjacent sprites. I was using GL_NEAREST so I thought it would be fine, but noooo, it looks like I get texture coordinates outside the 0,0 – 1,1 range! Maybe for antialiasing? I don’t know.

In any case the fix was to clamp the coordinates to the sprite range and then do the texture lookup.

1.1.2 Outlines

I ended up implementing outlines for my cubes, and extended it to sprites. I assign each surface a surface type, 0.0 for ground, 0.5 for billboard sprites, 1.0 for cube faces. Then whenever the surface type changes from one output pixel to the next, I can draw an outline.

TODO: THIS DOESN’T HANDLE OVERLAPPING BILLBOARDS so maybe I need to apply the outline in the sprite renderer, and then suppress it later? Ugh.

 1.2 Cube rendering

I draw each face of the cube in a separate draw call. That lets me put the normal into a uniform, and then I can put four corners into instanced rendering so that I can just put sprite positions in once.

1.2.1 Outlines

I draw the normals into a texture, and then whenever the normal changes, I can draw a border. I think this is overkill, as the surface type should be enough.

TODO: try surface type without normals. Maybe each sprite can be its own surface? That would fix the overlapping billboard problem. And it would mean I don’t need to save the normals. I need the normals only for lighting. → this works!

 1.3 Lighting

  1. Brightness based on distance to player
  2. Do I also want to make something based on the normal?

What about actual lights?

1.3.1 Billboards

The way I draw billboards doesn’t work with the light/shadow system, so I had to apply one matrix to orient the billboards for lighting purposes, and apply a different matrix to orient the billboards for rendering purposes. I think there must be a simpler way but I haven’t investigated.

1.3.2 Cube tops

The top of a wall block will always be in shadow.

Possible fix: sample from the closest edge of the square. This doesn’t work well because there’s not continuity between adjacent wall blocks.

Possible fix: sample from both the horizontal and vertical edges of the square, and pick the max. This works better but interior corners never get lit up.

Possible fix: sample from both the horizontal and vertical edges and also the diagonal corner. This helps with interior corners but now non-corners get lit up.

Possible fix: sample from all edges and light up the block if any wall is lit. This is more expensive and looks wrong, as it’s not continuous with the floors.

Possible fix: distinguish interior corners by writing the wall vs floor data to the shadow map in another channel.

Email me at , or tweet to @redblobgames, or post a public comment: