MonoGame on Windows Phone 8 Lighting and Normal Mapping Your Game
In this article, I will try to explain how to use HLSL and MonoGame for deferred lighting and normal mapping in 2D for Windows Phone 8 games.
In good old days of Microsoft XNA Game Studio, I was browsing the web in search for good HLSL articles for study purposes. I found one article which was written in Russian here. After some time spent with the code described in the article, I felt comfortable with techniques presented and implemented similar stuff in Windows Desktop version Undead Carnage: Redemption. When I got my hands dirty with MonoGame, a cross platform and open source implementation of XNA 4.0, I felt that I had to do the same for WP8 with MonoGame framework. I knew that in this case the code for both C# and HLSL would not be 100% compatible due to some differences between MonoGame and XNA. And I thought it would be great to share my experience about porting this awesome stuff to MonoGame. So let's start.
If you are unfamiliar with MonoGame, I strongly recommend on reading XNA Games On Windows Phone 8 with Monogame and MonoGame on Windows Phone 8 Post-Processing Your Game articles first. These will guide you through getting started with MonoGame and then shader compilation and usage within MonoGame game.
Now let's talk about the techniques we will be using here. These are Deferred Lighting and Normal Mapping.
Deferred Lighting - What Is This?
Deferred Lighting is a modification of Deferred Shading technique. Deferred Shading is a shading technique that is performed in screen space. The main difference between this and other shading and lighting techniques is the way geometry is shaded and lit. It's called deferred because of no shading and lighting take place in the first shader pass of vertex and pixel shaders. Instead all of shading and lighting is done in the second shader pass. This way the whole scene (all of the geometry) is drawn to geometry buffer (G-Buffer) as a series of textures. Each of these textures contains a chunk of information about amount of light and depth for each pixel drawn. This information is gathered in the first shader pass. In the second pass, pixel shader computes direct and indirect light for each pixel in screen space. The deferred lighting is a so called third pass of pixel shader. In our case, as we do all this stuff in 2D, we will supply the already gathered information as a normal map texture to a pixel shader. For more information on Deferred Shading and Lighting please refer to this article on Wikipedia.
OK. Let's assume I Understood That. Then What is Normal Mapping?
Normal mapping is a technique of faking geometry complexity. This technique is used to add details without adding polygons to 3D geometry. A common usage of this is to have tons of details in a low-polygonal geometry (3D model). In other words, this is a 3D graphics trickery that dramatically enhances the overall look of 3D geometry. Normal maps are commonly stored as simple images where R, G and B channels correspond to X, Y and Z coordinates of surface normal. For more information on Normal Mapping technique, please have a look at this article on Wikipedia.
So what do we need to have both these techniques in our game?
- First we need MonoGame binaries for Windows Phone 8 which can be obtained from here.
- We need at least two textures preferably of the same size. These will be the color map (or diffuse texture) and the normal map.
- We need at least one source of light. This can be the simplest point light.
We don't need the following at least in our case:
- The actual G-Buffer information. We will not draw any complex 3D geometry or complex scenes to G-Buffer. All we need we will have just after we load our two textures (these will be the diffuse and normal maps)
ImplementationAt the time of porting this code to MonoGame, I had compiled the shader using the 2MGFX tool with /dx11 flag at the first place. But the shader did not compile. The problem was in custom defined parameters inside the shader. There was a
You may ask why am I mentioning number three for parameters count. Well, that's because of Shader Model 2.0 limitation. We can have only to 64 arithmetic instructions in pixel shader per pass. And we are actually targeting SM 2.0 for WP8. So just three lights for now. However we can render more than three using multi-pass rendering with just shifting start index in our array of lights. That is not complex. For limitations and comparisons of Shader Models in HLSL, please refer here.
Let's Take a Look at Results
Let's talk about controls. On the last screenshot you can see some buttons.
Let's start from last one with three dots.
0) This button switches the states between controlling lights and controlling their properties. Then we go left to right:
1) R+ / R- increases / decreases the radius of both lights
2) Z+ / Z- increases / decreases the distance between light sources and the our surface (brick wall)
3) C+ / C- increases / decreases the light correction attribute. This serves as a Color multiplier for the actual color of light. Say it changes the power of light source
When the controls panel is turned off you can manipulate with the lights. At this point only red light is controlled by the user. The white light changes it's position by inverting screen space coordinates of the red one. All calculations are done in screen space of size 800x480.
For downloadable example please refer to the "The Code" section of this article.
In this article we've seen how we can dramatically enhance the overall look of our rendered scene with very small effort thanks to simple shading techniques and ingenious algorithms for indirect lighting (and the creators of these algorithms of course). Other thing that deserves mentioning is inverting of touch locations reported by the TouchPanel. As it's earlier been told we do not get the landscape mode in MonoGame just out of the box. This applies to touch points as well.
Say you're developing a side scrolling 2D action game. In this case you may this technique almost unchanged. The only changes will be in the rendering itself. If you have a tile map for your level and the same tile map for normal texture (like me :) ) then you should use render targets for the actual diffuse and normal. So on the first pass you draw your scene using normal texture to one render target, then on the second pass you draw the same scene with diffuse texture to another render target. And after that you update your lights, supply the normal target to the shader, apply shader passes and draw your diffuse render target.
There you go! Have fun HLSLing :) File:DeferredNormals.zip
- Many thanks to ForhaxeD from http://habrahabr.ru for the original tutorial
- Even more thanks to MonoGameTeam for creating such a good implementation of XNA and letting it all "Just Work" like in old good times so we can just keep working with our favorite framework.