3D Engine Powered by SFML
Overview
This custom 3D engine, built using SFML (Simple and Fast Multimedia Library), creates a 3D rendering system while leveraging SFML’s 2D capabilities. Instead of relying on OpenGL directly, the engine uses SFML’s vertex system with mathematical transformations and shaders to simulate a 3D perspective. With features like a custom Z-buffer, a hybrid 2D-3D collision system, dynamic lighting, and an external UI editor, this engine provides an efficient and flexible environment for game development.




Core Features
The engine uses SFML’s 2D rendering system to display 3D objects. This is achieved through:
- Perspective Projection: By applying transformations and custom shaders, the engine maps 3D vertices into a 2D space, giving the illusion of depth.
- Custom Z-Buffer: The Z-buffer determines object rendering order by calculating depth from front to back. It processes objects as units and, when necessary, cuts intersecting triangles to refine depth sorting.
- AABB & OBB Collision: Supports Axis-Aligned Bounding Box (AABB) and Oriented Bounding Box (OBB) collisions for global physics interactions.
- Raycasting & Object Detection: The Raycast helper class allows easy ray, sphere, and cube casting to detect object intersections. It provides detailed hit information, including position, object type, and impact point.
- Dual Collision System:
- Physics-Based Collision: Applies mass-based interactions with basic physics calculations.
- Simple Interaction System: Detects collisions without applying physical forces.
- 2D-3D Interaction Handling: The engine offers two methods:
- Bounding Box Projection: Converts 3D object bounds into 2D screen space.
- Mesh Projection: Renders mesh bounds into a complex shape and applies an algorithm to check ship-hitbox collisions with convex objects.
In SFML, rendering operates on a flat 2D plane where objects are positioned using (x, y) coordinates. However, to achieve advanced lighting and shading effects, the shader must interpret this 2D space as if it were part of a 3D environment.
2D to 3D Projection
Even though the engine only has access to 2D vertex positions, a projection transformation is applied within the shader to simulate depth. This involves treating the X and Y coordinates as they are while deriving a pseudo-Z value, often based on object depth, layer order, or distance from the light source. By doing so, the shader can compute lighting and shading interactions as if the objects existed in a true 3D space.
Texture Coordinates (UV Mapping)
To ensure proper texture rendering, each vertex is assigned UV coordinates, which map its position to the corresponding location on a texture. These UV coordinates are passed to the fragment shader, enabling accurate texture sampling. This mapping allows techniques like normal mapping, roughness mapping, and emissive effects to be applied dynamically, adding depth and realism to otherwise flat 2D surfaces.

Once the basic projection and UV mapping are done, the shader can handle different types of maps. Here’s how each type of map is applied:
- Normal Mapping: This technique simulates fine surface detail by altering how light interacts with the surface, even if the surface itself is flat. The shader uses the normal map’s RGB values (representing the normal vectors) to modify the lighting calculation. The 2D vertex positions do not contain normal information, so the shader uses the normal map’s data and the calculated surface normal to simulate lighting changes based on the "geometry" of the surface.
In the fragment shader, the normal from the normal map is transformed by the light source's direction. The final effect is that lighting appears more dynamic, with simulated depth and detail, despite the 2D nature of the geometry.
float value = max(0.0, dot(vec3(normalVector.rgb), lightPos[i]));
- Roughness Mapping: This map determines how shiny or matte a surface appears. The roughness value from the texture affects the way light is reflected from the surface. In the shader, the roughness map is used to modify the light reflection models. In simpler terms, the roughness texture controls how diffuse or specular light behaves when it interacts with the surface.
shininess = 1 + roughness.r/10 ;



- Albedo Mapping: Albedo maps store the base color of an object. These colors are combined with lighting calculations in the fragment shader to produce the final color of a pixel on the object. The shader multiplies the albedo map's color by the final lighting value, giving the surface its final appearance, taking into account dynamic lighting effects.
- Emissive Mapping: Emissive maps simulate materials that glow or emit light, like neon signs or hot surfaces. The emissive texture is applied in the fragment shader, and its values are used to add an additional light source contribution to the final color of a pixel. This can create effects like glowing objects or areas with a higher perceived brightness.
Since SFML doesn’t natively support 3D shadow casting, the engine uses a unique technique for shadows, particularly self-shadowing, where objects cast shadows onto themselves. This method still works with 2D data but requires computing the depth of the object in relation to light sources and its surface inclination.
- Self-Shadowing Calculation: The 2D positions of the vertices are used to compute how each point is affected by the nearest light source. In the shader, this could involve a combination of the object’s surface normal (if available), the light direction, and the object's proximity to the light source.
In the case of self-shadowing, the shader calculates which parts of the object are in shadow based on the direction of the light. This can be approximated in a way where areas facing away from the light source appear darker, creating the illusion of shadows on a flat surfac




- Keyframe-Based Transformations: Objects follow interpolated transformations over time.
- Linear Interpolation: Ensures smooth movement between keyframes.
- Physics Integration: While animations do not directly interact with physics, modules can be added to synchronize movement with physical behavior.
The engine supports multiple camera modes:
- Free Camera: Fully controllable movement.
- Rail-Based Camera: Follows predefined paths.
- Fixed-Angle Camera: Stays at a specific viewpoint.
- Multi-Camera Support: Allows switching between different perspectives.
- Frustum & Backface Culling: Optimized rendering by removing non-visible objects.
- Background Rendering (3D) → Game Objects (3D) → 2D UI Overlay
- Performance Optimizations:
- Culling Techniques: Frustum and backface culling remove unnecessary vertices.
- Optimized Memory Layout: Uses structs instead of classes to keep data contiguous in memory.
- Multithreading: Parallelizes vertex recalculations and rendering.
- Vertex Prediction: Estimates future positions based on camera movement for smoother rendering.
- Multi-Frame Object Rendering: Allows objects to be rendered in multiple frames while maintaining consistent transformations.
- To manage large environments efficiently, the engine implements:
- Sub-Level Streaming: Each level is divided into smaller sections that load dynamically.
- Threaded Loading: While one sub-level is active, the next is loaded in a separate thread.
- Memory Management: Unused sub-levels are deallocated to free resources.
- External Editor (Dear ImGui): Provides a UI for managing game objects, but only supports OBJ models.
- OBJ Model Support: Loads 3D models in OBJ format for rendering.
- Mass-Based Objects Only: Objects have mass properties but no constraints (e.g., no joints or physics-based linkages).
- SFML Audio Engine: Handles sound effects and background music.
- Basic 3D Sound Simulation: Although SFML does not support full 3D audio, spatialization techniques can simulate positional audio.
- Modular Architecture: Allows adding physics, AI, and advanced rendering techniques.
- Shader Extendability: Developers can enhance graphics but require 2D and 3D rendering knowledge.
- Physics-Based Expansion: The mass-based physics system can be extended to support additional forces.
- This home-built engine proves that SFML can be pushed beyond its 2D limitations to create a pseudo-3D environment with real-time rendering, physics, and interactive elements. With optimized performance, custom shaders, dynamic lighting, and hybrid 2D-3D gameplay support, it serves as a powerful foundation for unique game experiences.
This home-built engine proves that SFML can be pushed beyond its 2D limitations to create a pseudo-3D environment with real-time rendering, physics, and interactive elements. With optimized performance, custom shaders, dynamic lighting, and hybrid 2D-3D gameplay support, it serves as a powerful foundation for unique game experiences.
Game Implementation: Hybrid 2D-3D Shoot 'Em Up
Using this engine, my team and I developed a shoot 'em up (shmup) that merges 3D-rendered environments with precise 2D gameplay mechanics, creating a visually striking and mechanically unique experience.
The game features a hybrid 2D-3D system where the player controls a spaceship constrained to a 2D plane while navigating through a fully realized 3D world. This approach allows for traditional shoot 'em up mechanics while leveraging the depth and immersion of 3D environments.
To enhance gameplay complexity, we implemented advanced 2D-3D interactions. For example, boss battles integrate 3D movement paths while maintaining 2D attack patterns, creating dynamic engagements that feel both familiar and innovative. In Level 2, towering 3D walls act as hazardous obstacles, forcing players to navigate carefully within the 2D plane to avoid collisions.
A cinematic camera transition system further elevates immersion by dynamically shifting perspectives during key moments, such as intense battles or environmental changes. This ensures smooth visual flow and enhances player engagement.
Finally, to support these mechanics, we designed a custom collision detection system that accurately processes interactions between the 2D-controlled player, enemy projectiles, and 3D environmental elements. This precise system allows for seamless integration of depth-based obstacles without compromising gameplay responsiveness.
By combining these elements, we created a shmup that bridges classic arcade gameplay with modern rendering techniques, delivering an experience that is both nostalgic and technically innovative.