Introduction
This article is about a simple approach to rendering reflections and smooth shadows of 3D objects using just Windows GDI. There are no true 3D objects on the screen. We will just play with bitmaps and a few calls to the BitBlt()
and PlgBlt()
Win32 API functions.
Background
I have always liked how people use CPU power to simulate real-world behavior of objects. In the 3D games available today, I have seen whole virtual worlds created just for one purpose - to have fun and to play. Since I was not a 3D modeling expert, but I wanted to create something that looked real in a scene, I used the only weapon I have at the moment: Windows GDI. I have built a demo scene to show people who wonder how to do things like this. It was more than easy, but took some time to create. Here is how it was done...
The Scene Background
The background of the scene is a flat polygon with a texture. It is easy to do with a simple call to the PlgBlt()
method. It takes as arguments the HDC
of the destination, polygon vertices, the HDC
of the source bitmap loaded with a simple LoadBitmap()
method call, offsets and dimensions of the loaded bitmap, and some masking arguments, which are set to NULL
here. See the code below:
CBitmap bgBitmap;
bgBitmap.LoadBitmap(IDB_BITMAP_BACKGROUND);
BITMAP bmp;
bgBitmap.GetBitmap(&bmp);
POINT pBgBitmapPoints[3] = {{50, 250}, {380, 250}, {200, 400}};
PlqBlt(hDestDC, pBgBitmapPoints, hSrcDC, 0, 0, bmp.bmWidth, bmp.bmHeight, NULL, 0, 0);
The exact source code can be found in the DrawBackground()
method in the demo project.
The Object Reflection
Now comes the most interesting part: how can we render the reflection of the object so that the surface the object stands on can be seen as shiny and smooth (like a glass surface)? Solution: We will render the same object upside-down on that surface, but using a small trick. First, we render just the sides of the object that can have a reflection. Second, we will do a "decreasing alpha blend" technique while rendering those sides, so that we get a realistic effect. Here is the algorithm:
For each side that has a reflection:
- Blit a part of the screen which is covered with this side using
BitBlt()
method on the first bitmap - Blit inversed side using
PlgBlt()
method on the second bitmap - Merge these two bitmaps using a decreasing alpha blending technique as you move along each row
- The result is in the second bitmap now
- Blit this second bitmap on the screen again using
BitBlt()
method
The exact source code can be found in the DrawReflectedBox()
method in the demo project.
The Object Shadows
Rendering of the shadow can be done in a simple way or a complex one - it's all up to you. I have decided to use a simple technique called "rendering a shadow using ground transformation on the z=0 plane", and I made it even more simple. I didn't use exact math in this example because it is not so important, but even then the shadow still looks real. The light source is at infinity, so the light rays are parallel. This means that the projection of the object on the ground (z=0 plane) keeps the dimensions of the object itself. Knowing that, I did the following:
- Blit the part of the screen that is covered with this shadow using the
BitBlt()
method on the first bitmap - Blit the part of the screen that is covered with this shadow using the
BitBlt()
method on the second bitmap - Filter the second bitmap using a simple blur filtering technique (the filter size is up to you)
- Merge these two bitmaps using a constant alpha blending technique as you move along each row
- The result is in the second bitmap now
- Blit this second bitmap onto the screen again using the
BitBlt()
method
The exact source code can be found in the DrawShadow()
method in the demo project.
The Main Object
The main object was rendered similarly to the scene background. It is a box with 3 sides (polygons) with different textures each. It is rendered at the end, as the last object on the scene.
The exact source code can be found in the DrawOriginalBox()
method in the demo project.
Question About the Speed of Rendering
Please understand that this demo is just a simple example that does not try to break any speed record with GDI. It just shows that some things can be done, not that they should be done in this way at all. Please, refer to DirectX/OpenGL documentation for high quality 3D graphics rendering on the Windows platforms.
Points of Interest
I have learned while doing this example that it is possible to create a high quality real-world scene just by using simple Win32 API calls for GDI rendering. So, there is something that can be done (and look nice) with GDI, which has no default support for any of the DirectX/OpenGL advanced rendering modes: antialiasing, shadowing, reflections, etc.
He has a master degree in Computer Science at Faculty of Electronics in Nis (Serbia), and works as a C++/C# application developer for Windows platforms since 2001. He likes traveling, reading and meeting new people and cultures.