Sixth 3D - Realtime 3D engine
Table of Contents
1. Introduction
Sixth 3D is a realtime 3D rendering engine written in pure Java. It runs entirely on the CPU — no GPU required, no OpenGL, no Vulkan, no native libraries. Just Java.
The motivation is simple: GPU-based 3D is a minefield of accidental complexity. Drivers are buggy or missing entirely. Features you need aren't supported on your target hardware. You run out of GPU RAM. You wrestle with platform-specific interop layers, shader compilation quirks, and dependency hell. Every GPU API comes with its own ecosystem of pain — version mismatches, incomplete implementations, vendor-specific workarounds. I want a library that "just works".
Sixth 3D takes a different path. By rendering everything in software on the CPU, the entire GPU problem space simply disappears. You add a Maven dependency, write some Java, and you have a 3D scene. It runs wherever Java runs.
This approach is quite practical for many use-cases. Modern systems ship with many CPU cores, and those with unified memory architectures offer high bandwidth between CPU and RAM. Software rendering that once seemed wasteful is now a reasonable choice where you need good-enough performance without the overhead of a full GPU pipeline. Java's JIT compiler helps too, optimizing hot rendering paths at runtime.
Beyond convenience, CPU rendering gives you complete control. You own every pixel. You can freely experiment with custom rendering algorithms, optimization strategies, and visual effects without being constrained by what a GPU API exposes. Instead of brute-forcing everything through a fixed GPU pipeline, you can implement clever, application-specific optimizations.
Sixth 3D is part of the larger Sixth project, with the long-term goal of providing a platform for 3D user interfaces and interactive data visualization. It can also be used as a standalone 3D engine in any Java project. See the demos for examples of what it can do today.
2. Understanding 3D engine
- To understand main render loop, see dedicated page: Rendering loop
- To understand perspective-correct texture mapping, see dedicated page: Perspective-correct textures
- Study demo applications for practical examples. Start with minimal example.
2.1. Coordinate System (X, Y, Z)
Sixth 3D uses a left-handed coordinate system with X pointing right and Y pointing down, matching standard 2D screen coordinates. This coordinate system should feel intuitive for people with preexisting 2D graphics background.
| Axis | Direction | Meaning |
|---|---|---|
| X | Horizontal, positive = RIGHT | Objects with larger X appear to the right |
| Y | Vertical, positive = DOWN | Lower Y = higher visually (up) |
| Z | Depth, positive = away from viewer | Negative Z = closer to camera |
Practical Examples
- A point at
(0, 0, 0)is at the origin. - A point at
(100, 50, 200)is: 100 units right, 50 units down visually, 200 units away from the camera. - To place object A "above" object B, give A a smaller Y value than B.
The sixth-3d-demos project includes an interactive coordinate system reference showing X, Y, Z axes as colored arrows with a grid plane for spatial context.
2.2. Vertex
A vertex is a single point in 3D space, defined by three coordinates: x, y, and z. Every 3D object is ultimately built from vertices. A vertex can also carry additional data beyond position.
- Position:
(x, y, z) - Can also store: color, texture UV, normal vector
- A triangle = 3 vertices, a cube = 8 vertices
- Vertex maps to Point3D class in Sixth 3D engine.
2.3. Edge
An edge is a straight line segment connecting two vertices. Edges define the wireframe skeleton of a 3D model. In rendering, edges themselves are rarely drawn — they exist implicitly as boundaries of faces.
- Edge = line from V₁ to V₂
- A triangle has 3 edges
- A cube has 12 edges
- Wireframe mode renders edges visibly
- Edge is related to and can be represented by the Line class in Sixth 3D engine.
2.4. Face (Triangle)
A face is a flat surface enclosed by edges. In most 3D engines, the fundamental face is a triangle — defined by exactly 3 vertices. Triangles are preferred because they are always planar (flat) and trivially simple to rasterize.
- Triangle = 3 vertices + 3 edges
- Always guaranteed to be coplanar
- Quads (4 vertices) = 2 triangles
- Complex shapes = many triangles (a "mesh")
- Face maps to SolidTriangle, SolidPolygon, or TexturedTriangle in Sixth 3D.
2.5. Normal Vector
A normal is a vector perpendicular to a surface. It tells the renderer which direction a face is pointing. Normals are critical for lighting — the angle between the light direction and the normal determines how bright a surface appears.
- Face normal: one normal per triangle
- Vertex normal: one normal per vertex (averaged from adjacent faces for smooth shading)
dot(L, N)→ surface brightness- Flat shading → face normals
- Gouraud/Phong → vertex normals + interpolation
2.6. Mesh
A mesh is a collection of vertices, edges, and faces that together define the shape of a 3D object. Even curved surfaces like spheres are approximated by many small triangles — more triangles means a smoother appearance.
- Mesh data = vertex array + index array
- Index array avoids duplicating shared vertices
- Cube: 8 vertices, 12 triangles
- Smooth sphere: hundreds–thousands of triangles
vertices[] + indices[]→ efficient storage- In Sixth 3D engine:
- AbstractCoordinateShape: base class for single shapes with vertices (triangles, lines). Use when creating one primitive.
- AbstractCompositeShape: groups multiple shapes into one object. Use for complex models that move/rotate together.
See the Shape Gallery demo for a visual showcase of all primitive shapes available in Sixth 3D, rendered in both wireframe and solid polygon styles with dynamic lighting.
2.7. Winding Order & Backface Culling
The order in which a triangle's vertices are listed determines its winding order. In Sixth 3D, screen coordinates have Y-axis pointing down, which inverts the apparent winding direction compared to standard mathematical convention (Y-up). Counter-clockwise (CCW) in screen space means front-facing. Backface culling skips rendering triangles that face away from the camera — a major performance optimization.
- CCW winding (in screen space) → front face (visible)
- CW winding (in screen space) → back face (culled)
- When viewing a polygon from outside: define vertices in counter-clockwise order as seen from the camera
- Saves ~50% of triangle rendering
- Implementation uses signed area:
signedArea < 0means front-facing (in Y-down screen coordinates, negative signed area corresponds to visually CCW winding)
In Sixth 3D, backface culling is optional and disabled by default. Enable it per-shape:
- SolidPolygon.setBackfaceCulling(true)
- TexturedTriangle.setBackfaceCulling(true)
- AbstractCompositeShape.setBackfaceCulling(true) (applies to all sub-shapes)
See the Winding Order demo for an interactive visualization.
2.8. Frustum & View Frustum Culling
The view frustum is a truncated pyramid-shaped volume that represents everything the camera can see. Objects completely outside this volume are skipped during rendering — a powerful optimization called frustum culling.
2.8.1. The Six Frustum Planes
The frustum is defined by six clipping planes:
| Plane | Purpose |
|---|---|
| Left | Left edge of viewport |
| Right | Right edge of viewport |
| Top | Top edge of viewport (smaller Y in Y-down) |
| Bottom | Bottom edge of viewport (larger Y) |
| Near | Closest visible distance from camera |
| Far | Farthest visible distance from camera |
Each plane divides 3D space into "inside" (visible) and "outside" (culled). An object must pass all six plane tests to be considered potentially visible.
2.8.2. Frustum Culling vs Backface Culling
These are complementary optimizations at different levels:
| Optimization | Level | What it skips |
|---|---|---|
| Frustum culling | Object level | Entire composite shapes + children |
| Backface culling | Polygon level | Individual triangles facing away |
Frustum culling happens first during the transform phase — entire object trees are skipped with a single bounding box test. Backface culling happens later during rasterization — individual triangles are checked before being drawn.
For best performance, use both: organize your scene with composite shapes for effective frustum culling, and enable backface culling on closed meshes.
2.8.3. How Frustum Culling Works in Sixth 3D
Frustum culling is applied automatically to all composite shapes during Phase 2 of the rendering loop:
- Update frustum: Compute 6 planes from camera FOV and viewport size
- For each composite shape:
- Get its Axis-Aligned Bounding Box (AABB)
- Transform all 8 corners to view space
- Test against frustum using intersectsAABB()
- If outside: skip the entire composite and all children
- If inside: continue transforming children
2.8.4. The AABB Intersection Algorithm
The intersection test uses an optimized "P-vertex" approach:
For each plane, instead of testing all 8 corners of the bounding box, we test only the P-vertex — the corner most aligned with the plane normal. If this "best" corner is behind the plane, the entire box must be outside the frustum.
- Plane normal points into the frustum (toward visible region)
- P-vertex: select corner based on normal direction
- If normal.x > 0 → use maxX (rightmost corner)
- If normal.x < 0 → use minX (leftmost corner)
- Same logic for Y and Z
- Test:
dot(normal, P-vertex) < distance→ outside
This reduces from 48 tests (8 corners × 6 planes) to just 6 tests per object.
2.8.5. Performance Benefits
Frustum culling can dramatically improve performance for large scenes:
- High cull % (60-90%): Excellent — most objects skipped entirely
- Medium cull % (20-60%): Moderate benefit
- Low cull % (0-20%): Limited benefit — most objects visible
A composite shape that is culled skips:
- Transforming all its children
- Computing bounding boxes for children
- All polygon-level operations (backface culling, rasterization)
Open Developer Tools (F12) to see real-time frustum culling statistics.
2.8.6. Scene Design for Effective Culling
Frustum culling works best when you organize your scene into well-defined composite shapes:
// Good: Each building is a separate composite AbstractCompositeShape cityBlock = new AbstractCompositeShape(); for (Building building : buildings) { AbstractCompositeShape buildingComposite = new AbstractCompositeShape(); buildingComposite.add(buildingWalls); buildingComposite.add(buildingRoof); buildingComposite.add(buildingInterior); cityBlock.add(buildingComposite); } // Less effective: Everything in one giant composite AbstractCompositeShape allObjects = new AbstractCompositeShape(); allObjects.add(building1Walls); allObjects.add(building1Roof); allObjects.add(building2Walls); // ... hundreds of shapes directly in root
Best practices:
- Use composites to group objects that occupy a bounded region of space
- Keep bounding boxes tight (don't add distant objects to the same composite)
- Nest composites hierarchically for multi-level culling (city → block → building)
- Call updateBoundingBox() after moving shapes
2.8.7. Technical Details
The Frustum class:
- Computes planes in view space (camera at origin, looking along +Z)
- FOV derived from
projectionScale = width / 3(≈112° horizontal FOV) - Default clip distances: Near = 1.0, Far = 10000.0
- Planes stored in Hesse normal form: (normal vector, distance)
The frustum is updated once per frame in ShapeCollection.transformAllShapes() before processing composite shapes.
2.9. Working with Colors
Sixth 3D uses its own Color class (not java.awt.Color):
import eu.svjatoslav.sixth.e3d.renderer.raster.Color; // Using predefined colors Color red = Color.RED; Color green = Color.GREEN; Color blue = Color.BLUE; // Create custom color (R, G, B, A) Color custom = new Color(255, 128, 64, 200); // semi-transparent orange // Or use hex string Color hex = new Color("FF8040CC"); // same orange with alpha
3. Developer tools
Press F12 anywhere in the application to open the Developer Tools panel. This debugging interface helps you understand what the engine is doing internally and diagnose rendering issues.
The Developer Tools panel provides real-time insight into the rendering pipeline with three diagnostic toggles, camera position display, frustum culling statistics, and a live log viewer that's always recording.
3.1. Render frame logging (always on)
Render frame diagnostics are always logged to a circular buffer. When you open the Developer Tools panel, you can see the complete rendering history.
Log entries include:
- Abort conditions (bufferStrategy or renderingContext not available)
- Blit exceptions
- Buffer contents lost (triggers reinitialization)
- Render frame exceptions
Use this for:
- Diagnosing buffer strategy issues (screen tearing, blank frames)
- Debugging rendering failures
3.2. Show polygon borders
Draws yellow outlines around all textured polygons to visualize:
- Triangle tessellation patterns
- Perspective-correct texture slicing
- Polygon coverage and overlap
This is particularly useful when debugging:
- Texture mapping issues
- Perspective distortion problems
- Mesh density and triangulation quality
- Z-fighting between overlapping polygons
The yellow borders are rendered on top of the final image, making it easy to see the underlying geometric structure of textured surfaces.
3.3. Render alternate segments (overdraw debug)
Renders only even-numbered horizontal segments (0, 2, 4, 6) while leaving odd segments (1, 3, 5, 7) black.
The engine divides the screen into 8 horizontal segments for parallel multi-threaded rendering. This toggle helps detect overdraw (threads writing outside their allocated segment).
If you see rendering artifacts in the black segments, it indicates that threads are writing pixels outside their assigned area — a clear sign of a bug.
3.4. Show segment boundaries
Draws visible lines between horizontal rendering segments to show where the screen is divided for parallel multi-threaded rendering.
The engine divides the screen into 8 horizontal segments for parallel rendering. This toggle draws boundary lines between segments, making it easy to see exactly where each thread's rendered area begins and ends.
Useful for:
- Verifying correct segment division
- Debugging segment-related rendering issues
- Understanding the parallel rendering architecture visually
3.5. Camera position
Displays the current camera coordinates and orientation in real-time:
| Parameter | Description |
|---|---|
| x, y, z | Camera position in 3D world space |
| yaw | Rotation around the Y axis (left/right) |
| pitch | Rotation around the X axis (up/down) |
| roll | Rotation around the Z axis (tilt) |
The Copy button copies the full camera position string to the clipboard in a format ready to paste into bug reports or configuration files.
Use this for:
- Reporting exact camera positions when filing bugs
- Saving interesting viewpoints for later reference
- Understanding camera movement during navigation
- Sharing specific views with other developers
Example copied format:
500.00, -300.00, -800.00, 0.60, -0.50, -0.00
3.6. Frustum culling statistics
Shows real-time statistics about composite shape frustum culling efficiency:
| Statistic | Description |
|---|---|
| Total | Number of composite shapes tested against the frustum |
| Culled | Number of composites rejected (outside view frustum) |
| Culled % | Percentage of composites that were culled (0-100%) |
What is frustum culling?
Frustum culling is an optimization that skips rendering objects outside the camera's view. Before rendering each composite shape, the engine tests its bounding box against the view frustum. If the bounding box is completely outside the visible area, the entire composite (and all its children) are skipped.
How to interpret the numbers:
- High cull % (60-90%): Excellent — most objects are being correctly culled
- Medium cull % (20-60%): Moderate — some optimization benefit
- Low cull % (0-20%): Limited benefit — either all objects are visible, or scene needs restructuring
Example:
Total: 473 Culled: 425 Culled %: 89.9%
This means 473 composite shapes were tested, 425 were outside the view and skipped entirely, and only 48 composites (with all their children) actually needed to be rendered. This is excellent culling efficiency.
The statistics update every 200ms while the panel is open. Note that the root composite is never frustum-tested (it's always rendered), so the "Total" count excludes it.
3.7. Live log viewer
The scrollable text area shows captured debug output in real-time:
- Green text on black background for readability
- Auto-scrolls to show latest entries
- Updates every 500ms while panel is open
- Captures logs even when panel is closed (replays when reopened)
Use the Clear Logs button to reset the log buffer for fresh diagnostic captures.
3.8. API access
You can access and control developer tools programmatically:
import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.DeveloperTools; ViewPanel viewPanel = ...; // get your view panel DeveloperTools tools = viewPanel.getDeveloperTools(); // Enable diagnostics programmatically tools.showPolygonBorders = true; tools.renderAlternateSegments = false; tools.showSegmentBoundaries = true;
This allows you to:
- Enable debugging based on command-line flags
- Toggle features during automated testing
- Create custom debug overlays or controls
- Integrate with external logging frameworks
3.9. Technical details
The Developer Tools panel is implemented as a JFrame that:
- Centers on the parent
ViewPanelwindow - Runs on the Event Dispatch Thread (EDT)
- Does not block the render loop
- Automatically closes when parent window closes
- Updates statistics every 200ms while open
Log entries are stored in a circular buffer (DebugLogBuffer) with
configurable capacity (default: 10,000 entries). When full, oldest
entries are discarded.
Each ViewPanel has its own independent DeveloperTools instance,
so multiple views can have different debug configurations simultaneously.
4. Source code
This program is free software: released under Creative Commons Zero (CC0) license
Program author:
- Svjatoslav Agejenko
- Homepage: https://svjatoslav.eu
- Email: mailto://svjatoslav@svjatoslav.eu
- See also: Other software projects hosted at svjatoslav.eu
Getting the source code:
- Download latest source code snapshot in TAR GZ format
- Browse Git repository online
Clone Git repository using command:
git clone https://www3.svjatoslav.eu/git/sixth-3d.git
4.1. Understanding the Sixth 3D source code
- Study how scene definition works.
- Understand main rendering loop.
- Read online JavaDoc.
- See Sixth 3D class diagrams. (Diagrams were generated by using JavaInspect utility)
- Study demo applications.