The Journey So Far: Update 1
After returning to game engine development after some time off, it feels like a great time to reflect on what I’ve built and what’s next. I’m quite happy with the way things are shaping up, and I’m getting closer to the point where I think it makes sense to start exploring physics.
My engine’s core graphics library is called “orc” as an homage to OGRE, which has been an incredible reference and inspiration. At the time of this writing, orc features:
- An API for building scene graphs, which establish parent-child relationships between nodes in a scene
- A few useful node types that can be attached to a scene graph, including cameras, omni-directional lights, spot lights, and drawable objects
- Translation, rotation, and scaling, applied recursively across the scene graph, so that each node’s local coordinate space is relative to its parent’s
- Support for loading 3D models from disk and attaching them to objects within a scene
- Standard shaders that implement basic lighting and texture mapping
Demo
To showcase some of these features, I built this scene using orc and some 3D models made by some talented artists kind enough to publish their work under a free license.
This work is based on “Bulky Knight” by Arthur Krut and “Sponza Scene” by kevin.kayu, both licensed under CC-BY-4.0.
Here’s the relevant code used to render this scene:
// Create scene and position camera
orc::Scene scene;
scene.GetCamera().SetAspectRatio(windowWidth/windowHeight);
scene.GetCamera().Translate(-5, 10, 25);
scene.GetCamera().Rotate(-M_PI_4/6, -M_PI_4/3, 0);
// Load and position the castle environment model
std::shared_ptr<orc::Object> sponza = orc::LoadModel("data/models/sponza_scene/scene.gltf");
sponza->Scale(4, 4, 4);
sponza->Translate(0, 0, 15);
sponza->Rotate(M_PI_2, 0, 0);
scene.GetRoot().AttachChild(sponza);
// Load and position knight character. The scale used in the model file is
// is quite small, so scale up by a factor of 100
std::shared_ptr<orc::Object> character = orc::LoadModel("data/models/bulky_knight/scene.gltf");
character->Scale(100, 100, 100);
scene.GetRoot().AttachChild(character);
// Create new coordinate space to offset transformations baked into the
// character model itself
std::shared_ptr<orc::Node> reset = orc::Node::Create();
reset->Scale(0.01, 0.01, 0.01);
reset->Rotate(0, M_PI_2, 0);
character->AttachChild(reset);
// Within the reset coordinate space, create another coordinate space that
// is slightly tilted - the light travels on the XZ plane of this node
std::shared_ptr<orc::Node> tilted = orc::Node::Create();
tilted->Rotate(0, 0, M_PI_2/3);
tilted->Translate(0, 5, 0);
reset->AttachChild(tilted);
// Create another coordinate space that will be rotated around the Y axis of
// the tilted node
std::shared_ptr<orc::Node> rotating = orc::Node::Create();
tilted->AttachChild(rotating);
// Create a light and move it away from the origin of the rotating node
std::shared_ptr<orc::OmniLight> light = orc::OmniLight::Create();
light->Translate(3, 0, 0);
light->Scale(0.5, 0.5, 0.5);
light->AddMesh(orc::BuildCubeMesh("data"));
light->SetColor(1, 0, 0);
light->SetBrightness(4);
rotating->AttachChild(light);
// Each frame, nodes should be manipulated as needed, then Update should be
// called once. At the end of the frame, call Draw. In this case, we rotate
// the light's parent node to move the light around the knight.
window.StartRenderLoop([&scene, &rotating]{
float t = glfwGetTime();
rotating->SetRotation(t*1.5, 0, 0);
scene.Update();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
scene.Draw();
});
Up Next
Looking back, my journey up to this point has been about learning the tools and exploring the problem space. And, as a byproduct, the bones of a graphics engine are starting to take shape. The next phase will be about starting the transition from a code-only concept into a useable piece of software. To me, this means:
- A material system featuring dynamic mappings between shaders, properties, and graphical objects
- Some kind of 3D scene editing capability and the ability to save and load projects
- UI surface area for configuration, debugging, and experimentation
Of course, no plan survives contact with the enemy (I’ve found this to be especially true for this project). If I happen to get stuck or bored, there are plenty of interesting detours I could take. Off the top of my head:
- Animation support
- 2D
- Performance-related features (e.g. batch rendering, instancing, etc.)
- Support for graphics APIs other than OpenGL
- Particle effects and fog