home

Tutorial Index

DirectX tutorial 7: Let there be light

Consider video games, and think about their settings. You usually choose about shadows, dynamic lighting and so on, and perhaps you’ve thought how much better it looks when all these are on high settings. Now consider the games not just without shadows, dynamic lighting and effects, but without lighting at all. I’m not talking about darkness, I’m talking about all parts of a model being equally lit. You probably can’t bring that image to your mind at all, it’s just too unnatural. So we have to light up our models.

In 3Dheader.h we shall create a new function void setLight(PDIRECT3DDEVICE9 pd3dDevice). Add a call to this function at the end of setTransforms(). First off, we need to enable lighting. If you did disable it in the previous tutorial, remove that line of code. In the function we set the renderstate if D3DRS_LIGHTING to TRUE (pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);). But we have no light. We will use ambient light for the first part of this tutorial. Ambient light is light that is dispersed all over the scene. It is normally fairly dim, and only serves the purpose of making sure the player can also see something in the darker corners.

Set the render state of D3DRS_AMBIENT to D3DCOLOR_ARGB(22, 0, 255, 0). Keep note of this line of code. This will set it to a green light.

Finally we have to enable the vertex colors for Direct3D, so they are not ignored (remember how I said you may get a black mesh if you don’t turn off the lights in the previous tutorial? It usually is because Direct3D doesn’t calculate vertex colors by default). To do this we set the render state of D3DRS_AMBIENTMATERIALSOURCE to D3DMCS_COLOR1. MCS is Material Color Source.

Make sure you called the function and run the program! You should get one corner painted green and the rest of the mesh black. Let’s see why this is.


Colors both in nature and in DirectX:

The color of an item depends on what photons, what light, which have/has bounced off it we receive. If the item is blue, we receive only the blue light that hits it, because that’s the only one that bounces off. All the rest are absorbed.
That means however, that if something reflects only blue, but there is no blue light, it will absorb everything and appear black. This is what happened here. Try changing the ambient light’s color and see what happens.

Now, you’re probably going to argue that you’re bound to see something. I agree. All around us nothing is perfectly constructed on a microscopic scale, so you’re bound to have some elements in all items that reflect a bit of everything. Not much, so it’s still got the color it claims to have, but enough that when you only shine that color of light, you will get some of it back. Also it is in the nature of light, along with properties like refraction, that a small amount will bounce off most surfaces whatsoever. This is why we get a long trail under the sun in the sea; at some areas the light hits the water at angles that cause much more reflection than refraction.

Now, there are techniques that fix that, and that also work with blending colors from different sources, but they are well outside the scope of these tutorials. Consider that this year (2015) we have seen showcases of such technologies on the social media pages of NVIDIA, which probably means it’s not something too easy to do, at least not efficiently.


Back to business. Copy your project, we’re changing the vertex format again. Now, that probably sounds annoying, but we’re getting closer to the final stage by the minute. Typically you don’t work with vertex colors. You saw how they interpolate the color (they give a gradient across the surface, they smooth it between them if you’re not familiar with the terms), so you can probably imagine that it would not be that easy or, to be more precise, effective, to represent sharp edges with vertex coloring. There are two alternatives, which, actually, can coexist. In fact all can coexist but that’s rarely if ever practical.

The alternative we are looking at in this tutorial is materials. The same material is going to be applied for everything rendered during the draw call following it, and until it is replaced. Materials allow us to store more data than a simple color, including ambient, diffuse, emissive and specular color, and a couple more stuff.
The other alternative is texture mapping, which is no doubt the one that is never missing from any game. Texture mapping refers to stretching an image over the model, how this is performed we will see in more detail in the very near future.
As far as changing the format is concerned, we will change it again for the texture mapping and then we will meet get to use meshes, which, right after we see how they work, we will learn how to load and save. By then you will be able to work on an external 3d modeling package, as long as it supports the .x file format.


Part 2: other lights, normal maps and materials.

Our new flexible vertex format is XYZ and the normal vector. Normal in many cases in mathematics means perpendicular. In some cases you have a normal vector on faces (triangles) so as to define which direction it is facing, and thus when it is to be drawn. Similarly in this case it shows a general direction vertical to the surface at the point where the vertex is. It is not that straightforward as a thought, but if it helps, if you put the normal on the vertex, you can have one more tilt between surfaces, which smoothes out lighting on a curve, and that’s what these are for. But don’t expect to use them manually much – we will barely bother with which goes where. Manually creating even a simple model is time consuming, so we will just look at the programming of handling one, and let your 3d modeling package do the rest.

The normal vector is a D3DVECTOR. It has 3 dimensions, X, Y and Z. in your vertex declarations remove the color property and replace it with 3 values. Now as this represents a direction, it is customary for it to have length 1, though it is not imposed on you. To be on the safe side, I would suggest you make all values zero and set only one of them to 1 or -1. Which value it is will affect which parts are being lit. I set on the three first the Z value to 1, and on the rest the X value, this gave me the upper triangle on the nearest surface lit, while the rest of the model remained unlit. If you want a better looking cube, you can make adjustments to accommodate this one from directxtutorial.com.

Now we go into the setLight() function. You may remove everything if you wish, it’s not much in common anyway. In there you should set the renderstate for D3DRS_LIGHTING to TRUE, and for D3DRS_AMBIENT to any color you want. I set all values to 25 (dark grey).

Declare a D3DMATERIAL9 variable, called material. You can set the values by hand, but I chose to create two D3DCOLORVALUE variables to go faster, I called them oneColor and zeroColor, where respectively colors are set to 1 and zero. This is on a 0 to 1 scale, so 1 corresponds to 255. Set Ambient to oneColor and the rest to zeroColor. The rest do not matter, as we have no other color types.

Finaly call pd3dDevice->SetMaterial(&material) to instruct Direct3D to use this material. Try it out, you should see the entire cube have any color common to the light and the material. This is because ambient light is uniformly dispersed around the scene, which means everything is equally colored, so no shadowy effects here.

Now, we have worked with ambient light, but we have not seen much in terms of shadows and so on. Though it won’t be very realistic due to our not bothering much with the normals, we will set a point light and see some shadow! To begin with, dim out the ambient light to 100 or below on each value, otherwise it will overpower our direct lights. Direct lights refers to all other light types, be that a point light, a spot light or a directional light, because the lighting they provide on each vertex depends on the direction of the light at that point (hat way travels from the source to the point) and the normal of the vector, out of these two you get the angle at which the light hits the surface there.

Let’s create a directional light. This is a light that is again all over the scene, but has a specific direction. This is most useful for representing sun light, or any other kind of light with a source big enough that position does not significantly change the angle. The code to do so is as follows:

D3DLIGHT9 light;    // create the light struct
ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
light.Diffuse = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);    // set the light's color
light.Direction = D3DXVECTOR3(-1.0f, -0.3f, -1.0f);
pd3dDevice->SetLight(0, &light);    // send the light struct properties to light #0
pd3dDevice->LightEnable(0, TRUE);

 

It’s not much, is it? Let’s get started. The first line is pretty clear, you declare the light. The second line we met when creating buffers, we clean any junk. I am satisfied accepting that zero values in the memory also translate to logical zero values. Alternatively you can skip it (garbage in the memory may have the values set to different stuff) or set the values manually, as we did with the material.

Next we set the light type to directional. The diffuse color is the general color it emits. It is the basis for most lights. You can skip it in some cases if you want to create fantasy settings, if you only use specular you only get the reflective light (those brighter parts on a car’s curves in the sunlight), which can be otherworldly with a little luck.

Finally we assign this light the number zero, so the device just handles it with that number, and we enable it through it’s index.

That’s just about it! Run it, you should have part of the cube well lit, and the rest dimly lit depending on the intensity of the ambient light. Try changing the color of the lights to see what effect it has.

In the next tutorial we take our final step before organizing our data into meshes, which is texture mapping. You can find the code for this tutorial here.