Complex algorithms, clean architecture, photorealistic results
View Project Journey β’ Technical Architecture β’ Anti-Aliasing
The Question: How do computers generate realistic 3D images? How does a sphere "know" it should look round? How does light "know" how to bounce?
This project started as an academic assignment in Introduction to Software Engineering. But somewhere between calculating ray-sphere intersections and debugging reflection vectors, it became something more: a deep dive into algorithmic thinking and software architecture.
"Build a system where math, physics, and code come together to create something beautiful."
When you're rendering photorealistic images, precision is everything:
- Mathematical precision β one misplaced sign and your shadows point the wrong way
- Architectural clarity β 10+ geometry types, 4 light sources, recursive reflections... all working together
- Rigorous testing β every feature needs tests, because visual bugs are expensive to debug
A full-featured 3D rendering engine that traces light rays through a virtual scene to generate photorealistic images:
Scene scene = new Scene("My First Render");
// Build the world
scene.geometries.add(
new Sphere(50, new Point(0, 0, -200))
.setEmission(new Color(100, 50, 50))
.setMaterial(new Material()
.setKD(0.5).setKS(0.5).setShininess(100)
.setKR(0.3)) // 30% reflective
);
// Add lights
scene.lights.add(
new SpotLight(new Color(400, 240, 0), new Point(60, 50, 0),
new Vector(-1, -1, -2))
.setKL(0.00001).setKQ(0.000005)
);
// Render with anti-aliasing
Camera camera = Camera.getBuilder()
.setLocation(new Point(0, 0, 1000))
.setDirection(new Vector(0, 0, -1), new Vector(0, 1, 0))
.setVPDistance(1000).setVPSize(200, 200)
.setImageWriter(new ImageWriter("myRender", 800, 800))
.setRayTracer(new SimpleRayTracer(scene))
.setSamplingConfig(new SamplingConfig()
.enableAntiAliasing(81, 1.0, SamplingPattern.JITTERED))
.build();
camera.generateRenderedImage().writeToImage();Result: A photorealistic image with smooth edges, realistic lighting, and reflections.
A complete scene showcasing the engine's capabilities: spheres with reflections, transparent triangles, realistic shadows, and multiple light sources working together.
Notice how the edges become progressively smoother as we increase the number of sample rays per pixel. This is the power of super-sampling anti-aliasing.
File: src/renderer/superSampling/
The anti-aliasing system uses Jittered Sampling (a bonus feature):
// Generate 81 rays around the intersection point
List<Ray> rays = antiAliasingSampler.generateSampleRays(
intersection.point, primaryRay);
// Trace each ray and average the colors
List<Color> colors = new ArrayList<>();
for (Ray ray : rays) {
GeoPoint intersection = findClosestIntersection(ray);
colors.add(intersection == null ? scene.background :
traceSimpleRay(ray, intersection));
}
return calculateAverageColor(colors);Why Jittered? Combines the benefits of grid sampling (even coverage) with random sampling (avoids repetitive artifacts).
| Configuration | Samples/Pixel | Render Time | Quality | Use Case |
|---|---|---|---|---|
| No AA | 1 | Baseline | Standard | Debugging |
| Standard AA | 81 (9Γ9) | ~40Γ slower | Excellent | Demo images |
| High Quality | 324 (18Γ18) | ~160Γ slower | Outstanding | Final renders |
The codebase showcases 7 design patterns, demonstrating architectural maturity:
| Pattern | Location | Purpose |
|---|---|---|
| Builder | Camera.Builder |
Fluent API for developer empathy and robust validation |
| Strategy | SamplingPattern enum |
Pluggable sampling algorithms (JITTERED, RANDOM) |
| Composite | Geometries class |
Treat single/multiple geometries uniformly |
| Template Method | RayTracerBase |
Define ray tracing skeleton |
| Null Object | AmbientLight.NONE |
Eliminate null checks |
| Factory Method | SamplingPattern.generate() |
Create sample points by pattern |
| Flyweight | Point.ZERO, Color.BLACK |
Share immutable objects |
src/
βββ primitives/ # Math foundation
β βββ Point.java
β βββ Vector.java β createPerpendicular() for sampling
β βββ Ray.java
β βββ Color.java
β βββ Material.java
βββ geometries/ # 3D shapes
β βββ Sphere.java
β βββ Triangle.java
β βββ Plane.java
β βββ Cylinder.java
β βββ Geometries.java (Composite)
βββ lighting/ # Light sources
β βββ AmbientLight.java
β βββ DirectionalLight.java
β βββ PointLight.java
β βββ SpotLight.java
βββ renderer/ # Core rendering
β βββ Camera.java β Builder Pattern
β βββ SimpleRayTracer.java
β βββ superSampling/ β β
Anti-Aliasing System β
β βββ SuperSampling.java
β βββ SamplingPattern.java (Strategy)
β βββ SamplingConfig.java
β βββ TargetArea.java
βββ scene/
βββ Scene.java (Plain Data Structure)
100+ unit tests written using JUnit 5:
unitTests/
βββ primitives/ # Point, Vector, Ray tests
βββ geometries/ # Intersection accuracy tests
βββ renderer/
β βββ CameraTests.java
β βββ ShadowTests.java
β βββ ReflectionRefractionTests.java
β βββ superSampling/
β βββ AntiAliasingTest.java β 3-way comparison
β βββ SuperSamplingTests.java
β βββ SamplingPatternTests.java
βββ bigScenes/
βββ RealisticPianoFinal.java β 30 samples
βββ CoffeeCupScene.java
βββ HouseScene.java
βββ SunflowerScene.java
Example: Anti-Aliasing Test (AntiAliasingTest.java)
@Test
public void runCompleteAntiAliasingTest() {
// Render 3 versions of the same scene
long noAATime = renderWithoutAntiAliasing();
long standardAATime = renderWithStandardAntiAliasing(); // 81 samples
long highAATime = renderWithHighQualityAntiAliasing(); // 324 samples
// Output performance comparison
printPerformanceSummary(noAATime, standardAATime, highAATime);
}Output:
====================================
PERFORMANCE SUMMARY
====================================
No Anti-Aliasing: 1,203 ms
Standard AA (81): 48,560 ms (40.4x slower)
High Quality AA (324): 194,832 ms (162.0x slower)
MP1 Requirements fulfilled:
β 10+ geometric bodies
β 3 different light sources
β Anti-aliasing ON/OFF capability
β 50+ sample rays
β Performance timing measurements
====================================
- Ray-Geometry Intersections β Sphere, Plane, Triangle, Polygon, Cylinder, Tube
- Phong Lighting Model β Ambient + Diffuse + Specular components
- Multiple Light Sources β Ambient, Directional, Point, Spot lights with attenuation
- Shadows β Shadow ray casting with transparency support
- Recursive Ray Tracing β Reflections (kR) and refractions (kT) up to 10 levels
- Material Properties β kD (diffuse), kS (specular), nShininess, kR (reflection), kT (transparency)
- Super-Sampling Anti-Aliasing β JITTERED and RANDOM patterns
- Camera Builder β Fluent API with validation
- Scene Composition β Multiple geometries and lights with Composite pattern
- Image Export β PNG output with customizable resolution
- 7 Design Patterns β Builder, Strategy, Composite, Template Method, Null Object, Factory, Flyweight
- SOLID Principles β Single Responsibility, Open/Closed, Liskov Substitution, Dependency Inversion
- Defensive Programming β Input validation on every public method
- Comprehensive JavaDoc β All classes and methods documented
- 100+ Unit Tests β JUnit 5 with EP/BVA testing strategies
- Immutable Primitives β Thread-safe Point, Vector, Ray, Color. No side effects, easier testing
Multiple colored lights create a rainbow effect on a reflective sphere |
Three light sources (blue, yellow, white) illuminate geometric shapes |
Base configuration |
Light position adjusted |
Dynamic shadow casting |
Demonstrating how shadows change realistically as light sources move through the scene.
- Java 17 or higher
- JUnit 5 (included in project)
-
Clone the repository
git clone https://github.com/Noammandelbaum/ISE5784_1674.git cd ISE5784_1674 -
Compile the project
javac -d out src/**/*.java
-
Run a demo scene
java -cp out bigScenes.RealisticPianoFinal
-
Run tests
# From your IDE (IntelliJ IDEA / Eclipse): # Right-click on unitTests folder β Run All Tests
-
View generated images
# Images are saved to the images/ directory open images/antiAliasing/with_antialiasing_81_samples.png
- Linear Algebra in Practice β Dot products, cross products, vector normalization aren't just theory
- Floating-Point Precision β Why
alignZero()is critical for numerical stability - Recursive Algorithms β Balancing depth vs. performance (MAX_LEVEL = 10, MIN_K = 0.001)
- Performance Optimization β Understanding the cost of each ray (81Γ slowdown = need for optimization)
- TDD Discipline β Writing tests first prevents expensive visual debugging
- Builder Pattern Value β Compare
new Camera(8 params)vs. fluent API - Strategy Pattern Flexibility β Added JITTERED pattern without changing existing code
- Defensive Programming β Input validation prevents silent failures
This principle guided every decision:
- Named variables clearly (
pixelColumnIndexnotj) - Validated inputs explicitly (throw
IllegalArgumentExceptionwith clear messages) - Tested edge cases obsessively (colinear points, zero vectors, parallel rays)
- Documented intent with JavaDoc (not just "what" but "why")
Build quality in from the start β don't patch it later.
First successful render: Basic sphere, plane, and triangle with simple color emission. This proved the core ray-intersection math was correct.
Directional Light |
Point Light with Attenuation |
Spot Light with Direction |
Implemented the Phong reflection model with three types of light sources. Each light type required different attenuation calculations.
Hard shadows with opaque objects |
Soft shadows through transparent objects |
Shadow rays revealed which objects block light. Transparent materials (kT > 0) create soft, realistic shadows.
Mirror-like reflections (kR = 0.9) |
Glass-like transparency (kT = 0.8) |
Recursive ray tracing up to 10 levels. Each reflection/refraction spawns a new ray, creating stunning realism but requiring careful performance management.
See the Anti-Aliasing section above for the full before/after comparison.
Realistic Upright Piano |
Architectural Scene |
From a single sphere to photorealistic scenes - 8 exercises, countless tests, one powerful engine.
Builder Pattern for Complex Objects:
// Bad: Constructor with 8 parameters - easy to mix up
Camera camera = new Camera(p0, vTo, vUp, width, height, distance, imageWriter, rayTracer);
// Good: Builder with validation - clear and safe
Camera camera = Camera.getBuilder()
.setLocation(new Point(0, 0, 0))
.setDirection(new Vector(0, 0, -1), new Vector(0, 1, 0))
.setVPSize(200, 200)
.build(); // Validates all required fieldsClear Error Messages:
if (numSamples < 1) {
throw new IllegalArgumentException("Number of samples must be at least 1");
}Transparent Benchmarking:
System.out.println("Standard AA (81): 48,560 ms (40.4x slower)");Real performance data helps make informed tradeoffs between quality and speed.
- 100+ unit tests β Comprehensive coverage
- Immutable primitives β No side effects, thread-safe
- Defensive validation β Fail fast with clear messages
- Continuous optimization β First render: 2 min β After optimization: 45 sec
- JavaDoc: All classes and public methods fully documented
- Code Comments: Explain "why" not "what" (algorithms, edge cases, optimizations)
- Test Documentation: Each test describes expected behavior
- README: You're reading it! π
- Course: Introduction to Software Engineering, Jerusalem College of Technology
- Concepts Inspired By: Peter Shirley's "Ray Tracing in One Weekend"
- Pattern Reference: Gang of Four Design Patterns
I'm always excited to discuss this project, ray tracing techniques, or software development opportunities.
Built with precision, tested with discipline, designed for clarity.
A journey through algorithms, architecture, and the art of turning math into visuals
















