I am trying to find the best way to correctly do normal mapping in a 2D batched sprite renderer.

For each sprite sheet (aka texture atlas) I render all sprites as a textured quads with a single glDrawElements() call. Therefore, vertex coordinates in VBO are already transformed (on CPU) according to sprite position, rotation and scale.

Unfortunately, on rotated sprites normal map texture is also rotated, leading to incorrect normals:


I did a bit of research on this subject and so far I found few possible solutions:

  1. Pass tangents to shader. Pretty straightforward, the same way it's usually done for 3D models. While this approach would work, doing it for just simple 2D sprites seems quite wasteful.

  2. Pass just sprite rotation in additional vertex attribute. Then I could use this attribute in the fragment shader to rotate normal vector accordingly.

  3. Use dFdx(), dFdy() functions. It looks like I could use these functions to deduce transformation from screen to texture coordinates and then use it to rotate normals. To be honest, this approach is not entirely clear to me, and I don't fully understand its possible performance implications, especially on mobile platforms.

  4. Use geometry instancing. I think if I use glDrawElementsInstanced() and just dump all the sprite coordinates + transformations in one giant texture buffer, I then could use this information in the fragment shader to rotate normals. But as with approach #3, I don't fully understand if it could lead to possible performance or compatibility issues on mobile platforms.

So far I think approach #2 is probably the better solution. Not only it is simple and requires minimal changes to the code, but it would also allow me to offload sprite rotation calculations from CPU to GPU. I'd still have to pass sprite rotation per-vertex (and not per-sprite), but I don't think this is to big overhead.

But maybe there is a better, more elegant or more performant solution to this problem? How this is typically done in 2D games?

  • \$\begingroup\$ Do some profiling and see which option suits you better. \$\endgroup\$ – Kromster Apr 3 '18 at 5:10

Assuming you have access to the code of the shader and not using a "magic" library.

Instead of rotating the normal texture, you could modify the direction of the normal vector. Since on the normal map, the colors are actually "vectors", you can easily swap the direction of a vector adding a minus before the coordinate/direction at fault. In the following video, the direction of the lightning is facing the wrong direction on the x axis. By adding a minus sign in front of it in the fragment shader code, it fixes the problem: https://youtu.be/BCGKsh51TNA . In your case, you may have to swap 2 of the directions. Hope that helps.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.