Teh Engine
And there was light
Finally, proper dynamic lighting. In the picture you can see 5 per-pixel Phong spot-lights. There are some differences between the lights as they appear in Max and the ones from the engine - the engine ones have a wider spot.
Because of some stupid mistakes I've made I thought I was never going to make this work. FIrst I wrongly transformed the light position and as a result the light was in a different position for each object. So some objects were lit properly, others on the other side. Then, I forgot to initialize a variable in one shader code path and this generated weird colors on the unlit side of objects.

2 December 2006
First optimizations
Moved the vector/matrix code to C++ and removed glGetError calls in the rendering path. The result? The frame rate increased from 90 fps to 1050 fps for a minimal window size with no polygons in view.
28 November 2006
Speed of light
You know your programming language is slow when duplicating these two lines cause your frame rate to drop from 90 fps to 50 fps:
self.__worldViewMatrix = self.__viewMatrix * self.__worldMatrix
self.__worldViewProjectionMatrix = self.__projectionMatrix * self.__worldViewMatrix
Let's see what the profiler tells:
4758093 function calls in 24.559 CPU seconds
Ordered by: cumulative time, call count
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 24.559 24.559 C:\Projects\SP\Python\SP\Main.py:72(_AppRun)
1 0.000 0.000 24.559 24.559 C:\Projects\SP\Python\px\WindowProc.py:36(MessagePump)
804 0.025 0.000 24.509 0.030 C:\Projects\SP\Python\SP\Engine\__init__.py:57(Process)
804 0.068 0.000 24.180 0.030 C:\Projects\SP\Python\SP\Engine\Scene.py:126(Render)
3216 0.042 0.000 23.030 0.007 C:\Projects\SP\Python\SP\Engine\MeshNode.py:27(Render)
4824 0.118 0.000 22.846 0.005 C:\Projects\SP\Python\SP\Engine\MeshNodePart.py:32(Render)
4824 0.163 0.000 20.422 0.004 C:\Projects\SP\Python\SP\Engine\UniformManager.py:35(UpdateProgram)
9857 0.069 0.000 16.522 0.002 C:\Projects\SP\Python\px\Geom\Matrix4.py:166(__mul__)
9857 5.511 0.001 15.624 0.002 C:\Projects\SP\Python\px\Geom\Matrix4.py:181(__imul__)
1368536 6.646 0.000 9.309 0.000 C:\Projects\SP\Python\px\Pointer.py:26(__getitem__)
296755 1.446 0.000 2.025 0.000 C:\Projects\SP\Python\px\Pointer.py:48(__setitem__)
14472 0.416 0.000 1.300 0.000 C:\Projects\SP\Python\px\Geom\Matrix3.py:75(__init__)
4824 0.151 0.000 1.235 0.000 C:\Projects\SP\Python\px\Geom\Matrix4Util.py:9(ExtractTopMatrix3)
4824 0.163 0.000 1.220 0.000 C:\Projects\SP\Python\px\Geom\Matrix3Util.py:3(Inverse)
14472 0.157 0.000 1.205 0.000 C:\Projects\SP\Python\SP\Engine\VertexAttributeBuffer.py:17(Bind)
...
OMG! 66% of the time is spent multiplicating matrices. The Pointer.py calls are related. And we are talking about 6 objects. I think it's time to move the math part to C++.
26 November 2006
Independence Day
A week since the last update! Loading everything automaticaly from the COLLADA export was more tough than I thought. But now the engine is finally free. No more manually loading textures and meshes. All the information is now contained in a text file generated from the COLLADA source. It's an older configuration file format I've created for Audio Pencil. It's based on YAML, only much simpler.
The overlay and font systems are also done. The big red number is the current FPS. It's kind of low, however there are plenty of optimizations possible so I'm not worried yet.
In case you didn't noticed, the room has a lightmap on. The shadows are really nice and smooth, given that they were computed in 3ds Max using Render to Texture. Gone are the Quake days when you had to run light.exe. Who knows, if I figure it out, maybe I'll even render the lightmap using global illumination.

Part of the scene description file which generated this:
activeCamera: 'Camera-camera'
camera:
'Camera-camera':
fovX: 45.0
fovY: None
type: 'Perspective'
zFar: 10000.0
zNear: 1.0
cameraNode:
+
target: 'Camera-camera'
transform: c'Matrix(0.959113001823, 0.0640140026808, 0.275691002607, 241.112335205, 0.277859002352, -0.0277169998735, -0.960222005844, -929.326843262, -0.0538260005414, 0.997564017773, -0.0443710014224, 317.346252441, 0.0, 0.0, 0.0, 1.0)'
light:
'Fspot01-light':
color: c'Color3(1.0, 1.0, 1.0)'
constantAttenuation: 1.0
falloffAngle: 72.800003000000004
falloffExponent: 0.0
linearAttenuation: 0.0
quadraticAttenuation: 0.0
type: 'SpotLight'
lightNode:
+
target: 'Fspot01-light'
transform: c'Matrix(-0.760151982307, -0.400429993868, 0.511689007282, 548.637451172, 0.64860200882, -0.420938998461, 0.634133994579, 689.709716797, -0.0385360009968, 0.813920021057, 0.579697012901, 767.769165039, 0.0, 0.0, 0.0, 1.0)'
material:
Ceiling:
program: 'MeshDiffuseLightMap'
sampler:
u_DiffuseTexture:
borderColor: c'Color4(0.0, 0.0, 0.0, 0.0)'
magFilter: 'GL_LINEAR'
minFilter: 'GL_LINEAR_MIPMAP_LINEAR'
path: u'Texture/Ceiling.tex'
wrapS: 'GL_REPEAT'
wrapT: 'GL_REPEAT'
u_LightMapTexture:
borderColor: c'Color4(0.0, 0.0, 0.0, 0.0)'
magFilter: 'GL_LINEAR'
minFilter: 'GL_LINEAR_MIPMAP_LINEAR'
path: u'Texture/RoomLightMap.tex'
wrapS: 'GL_REPEAT'
wrapT: 'GL_REPEAT'
mesh:
'Budha-mesh':
material:
+ 'Marble'
path: u'Room/Budha.mesh'
'Room-mesh':
material:
+ 'Floor'
+ 'Walls'
+ 'Ceiling'
path: u'Room/Room.mesh'
'StatueSupport-mesh':
material:
+ 'StatueSupport'
path: u'Room/StatueSupport.mesh'
'Text-mesh':
material:
+ 'Text'
path: u'Room/Text.mesh'
meshNode:
+
materialBind:
Marble: 'Marble'
target: 'Budha-mesh'
transform: c'Matrix(1.0, 0.0, 0.0, 4.3321480751, 0.0, 0.0, -1.0, 9.00000031834e-006, 0.0, 1.0, 0.0, 150.0, 0.0, 0.0, 0.0, 1.0)'
+
materialBind:
StatueSupport: 'StatueSupport'
target: 'StatueSupport-mesh'
transform: c'Matrix(1.0, 0.0, 0.0, 0.0125500001013, 0.0, 1.0, 0.0, 9.99999997475e-007, 0.0, 0.0, 1.0, -12.1479272842, 0.0, 0.0, 0.0, 1.0)'
+
materialBind:
Text: 'Text'
target: 'Text-mesh'
transform: c'Matrix(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 1000.0, 0.0, 1.0, 0.0, 400.0, 0.0, 0.0, 0.0, 1.0)'
+
materialBind:
Ceiling: 'Ceiling'
Floor: 'Floor'
Walls: 'Walls'
target: 'Room-mesh'
transform: c'Matrix(2.0, 0.0, 0.0, -50.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0)'
program:
MeshDiffuseLightMap:
attribute:
+
name: 'a_Position'
semantic: 'VERTEX'
set: 0
size: 3
+
name: 'a_DiffuseTexCoord'
semantic: 'TEXCOORD'
set: 0
size: 2
+
name: 'a_LightMapTexCoord'
semantic: 'TEXCOORD'
set: 1
size: 2
fragmentShaderPath: u'Shader/MeshLightMap.frag'
uniform:
+
name: 'u_WorldViewProjectionMatrix'
+
name: 'u_DiffuseTexture'
+
name: 'u_LightMapTexture'
vertexShaderPath: u'Shader/MeshLightMap.vert'
25 November 2006
Level up
Finally! The first scene with multiple objects and multiple textures. Still not 100% automatic through ;) Meaning that the object locations and textures are manually specified instead of being loaded from the exported scene description.
3ds Max scene picture:

Engine picture:

As you can see, lighting is very wrong.
Now I want to add an overlay system (images on top of the display) in which to display various statistics, like FPS. This will also require some sort of text system, probably using bitmap fonts.
16 November 2006
Little stuff
These last days I've been working mostly on infrastructure things: a simple camera system which allows navigation using cursor keys, a new pack file format (the file which contains all resources, like textures and meshes) with optional compression and encryption, cleaned-up OpenGL extension bindings, support for multi-sampling, anisotropic texture filtering and vertical retrace waiting. The last three improve visual quality.
Now I'm creating a texture file format, something very similar to DDS, only easier to parse (not that DDS files are very complicated).
Oh, and just for fun I've added a Mandelbrot shader from a book, which took only 10 minutes to integrate:

13 November 2006
Camera, Action!
More matrix fun. Today I've worked on the view transform matrix. Basically I need to create a matrix which will transform the vertices as if viewed from a certain point in space (the camera location) looking in a certain direction (the camera target). To do this, you need to know the camera "up" vector. The problem is that OpenGL considers the y axis to be up, while 3ds Max and the game engine thinks z is up. The camera transform matrix can be computed with a utility function in both OpenGL and DirectX. I used the same method as they use to also compute this matrix. But I thought that since in my engine z is up, I need to take this into account. And it didn't work.
After failing to fix this, I've started to play with a camera in Max to try do duplicate the issue. I first tweaked the camera settings in the engine until the view was the same as the one set in Max for a simple camera (directly above the scene). To my surprise, the exact same camera location, camera target and camera up vector were needed. I thought that the y and z axis would need to be exchanged. Then I moved the camera in Max to an arbitrary location and copied it's position and rotation into the engine. From the rotation I've computed the camera up vector and using this I was able to duplicate the view.
Perfect match!

Now I have everything required to support navigating through the scene.
8 November 2006
Enter the Matrix
Matrices. An essential part of any 3D engine. And also a very confusing part at first. I've used two sources to get the rotation, translation and perspective matrices: the DirectX 10 documentation and the OpenGL specification. Also, my matrix class stored it's data in row-major order. So I just couldn't figure out why it worked, when the GLSL specification clearly states that matrices are loaded in column order. And then everything started falling apart when I tried adding a second rotation to the mesh.
I was left no choice and I really had to figure this whole transformation affair out. After looking through the books I have and through online forums I've got to the bottom of this. As it turns out, there are really 4 types of matrices: you have two ways to store them in memory (column-major or row-major) and two ways to write them on paper (also column-major and column-major). Also you can interpret a vertex position (x, y, z) as a single column or as a single row matrix. You can only mix them up only in certain ways, and order in which you must apply transformations also changes. So I've decided to use column-major matrices and column vectors, just like in OpenGL. I've fixed all classes to respect this design choice, and sure enough, everything started to work again, this time with as many rotations as I want.
Hopefully I won't forget this in 2 weeks and start having the same issues again.
7 November 2006
Got mesh?
Mesh importing is now almost working. The almost part is because of different coordinate systems used by my game engine and by 3ds Max (the content creation software I use). In max z is up, while in my game engine y is up, like in OpenGL and DirectX. I will convert the code so that z is up after I study the math behind this a little more ;) Another problem is that the textures look mirrored. This could be related to the above problem.
I've also added some crude lighting. It's almost correct. Right now it's hard to implement a proper solution since the object moves, but the light does not. Instead of just hacking something till it works, I think it would be better to spend the time implementing full scene import.
At least the mesh data is loaded properly. And the whole "semantic" stuff starts to get clearer. In old school OpenGL, you only had a bunch of vertex attributes: position, color, texture coordinate, and that's about it. In the new generation you can add whatever attribute you want. The attributes are now called semantics since they describe how the raw data should be interpreted. The SP mesh format can store arbitrary semantics and the scene manager scans them on loading in search for supported ones. Right now only POSITION, NORMAL and TEXCOORD are recognized and forwarded to the vertex shader (note that these are just convention names, you can name them however you please).

To be able to better integrate all this stuff I will now create a small and simple level which contains everything - a camera, some lights, and geometry with textures applied. Then I'll try to get everything working together. I think this is easier then just implementing the lighting first, then the scene graph and so on. On the downside, this will take a while since I don't yet have a scene graph and I changed my plan a little bit.
Initially I thought of exporting the COLLADA scene data to some binary structures serialized to a file. After a little more thought I came to the conclusion that while somewhat easier to implement, this is very hard to debug. So I'm now thinking of having all the scene data in Python modules which are loaded dynamically from the pack file. Only the meshes and the images will be stored in binary files. This will make it very easy to edit the scene with just a text editor. I haven't yet decided if the files should contain full object declarations or just attribute dictionaries (think JSON). And I hope XML hasn't crossed your mind while reading this :P
4 November 2006
1000 words
The code required to load JPEG and PNG images is already present in the GX library. GigiMap needs it. I've modified it a bit to avoid creating a DC for holding the image and I've made it return just the raw uncompressed pixel data. From here it was easy to load into an OpenGL texture. Ironically, I now support JPEG and PNG textures, but not BMP ones. They will be easy to add however when the need will come.

Now I really need to make the COLLADA parser dump some kind of binary mesh file to disk. I have created a simple format which will support anything required at this stage. Time to implement it.
1 November 2006
Cube me not
Well, after displaying a triangle it was easy to go and do a full spinning cube. No lighting yet. Remember that since this is 100% shader based, I have to do the lighting equations myself. And this is a lot harder than it may seem. The cube is hardcoded in source and it doesn't have any normals yet. These are needed for any kind of lighting.
So instead of just adding them I looked a bit at the COLLADA parser I made two weeks ago to see how to make it export meshes for the engine use. It looks like I will need a post-processing step to reorganize the vertex data. The information in the COLLADA format is not organized for easy display. For example a cube can have 8 vertex positions, 24 vertex normals and 24 vertex texture coordinates. We need the same number of values for each attribute. So I will need to duplicate some information, the vertex position in this case.

30 October 2006
First light
I finally managed to create all the code required to display the Hello World of 3D programming - the red triangle. I basically took the C++/Python infrastructure from the Audio Pencil project and added the OpenGL bindings on top of it after removing the audio code. All the OpenGL bindings are done except the texture functions.
So here it is:

Yes, this is not very impressive. But this is how it all starts.
29 October 2006
The new language
So I've investigated into a little more detail the structures required to hold a bunch of objects for screen display. This is called the scene graph and the more I study it, the harder it looks to implement. Or more precisely, it looks like it will take a lot of time. Also, a good design is not very obvious. So I'm starting to have this crazy idea: what if I would implement the whole engine in Python? Could it be fast enough?
The power of Python will make implementing and changing the design a breeze. Yes. This is what I'll do. I'll start with a Python engine and refine it until the design starts to crystalize and only then I'll move the critical parts to C++ for raw speed.
I'll first have to create the bindings which will make possible the use of OpenGL from Python. This is easy, but boring since it involves a lot of boilerplate code.
25 October 2006
Out with the old, in with the new
After playing for nearly 6 months with OpenGL, I feel that I'm ready to go to the next level. I'll create a mini 3D game engine. Given that this is just a personal test project, I can choose my target. So good night fixed pipeline, good morning programmable one. Given that affordable DirectX 10 hardware is still months away, I will chose OpenGL as the base API. However, since I won't use any part of the fixed function, it should be easy to port to the other side.
Of course, there is a small price to pay. I will have to implement a lot of stuff before I can render the first triangle. I will also have to compute my own transform matrices and pass them to the shaders, since I won't be using glRotate or glTranslate (just like in DirectX 10).
So what should the scope of this little project be? Here's the starting feature list:
- per pixel lightning
- dynamic lightning
- shadows
- light maps
- normal maps
- HDR
- COLLADA import
- effect system (maybe in Python?)
- scene graph with node animation
- simple portal visibility
Hmm, now it looks like it will take a while...
But before we get to all this, I need to cleanup my other 3 projects a little bit - Audio Pencil, BuddyCheck and GigiMap. They all share code. Some is already stored into a library called GX. Another big part is just duplicated around, sometimes with small changes. Since I will also need this code for the game engine, I'll first revamp the GX library and put all common code there.
Hopefully next time I'll have a picture to show.
22 October 2006
First post
22 October 2006