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 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.