Skip to content

Commit 5dc6415

Browse files
committed
3D model rendering added
1 parent 56af3b2 commit 5dc6415

18 files changed

+158455
-1843263
lines changed

Output.png

-1.47 MB
Binary file not shown.

Output.ppm

Lines changed: 0 additions & 921603 deletions
This file was deleted.

final.ppm

Lines changed: 0 additions & 921603 deletions
This file was deleted.

final_500.ppm

-1.56 MB
Binary file not shown.

final_test.png

-1.51 MB
Binary file not shown.

src/RayTracing/Hittable/Model3D.java

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package RayTracing.Hittable;
2+
3+
import RayTracing.HitInfo.HitRecord;
4+
import RayTracing.HitInfo.Interval;
5+
import RayTracing.Material.Material;
6+
import RayTracing.Point;
7+
import RayTracing.Ray;
8+
import RayTracing.Vec2;
9+
import RayTracing.Vec3;
10+
11+
import java.io.BufferedReader;
12+
import java.io.FileInputStream;
13+
import java.io.InputStream;
14+
import java.io.InputStreamReader;
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
18+
public class Model3D implements Hittable {
19+
public List<Hittable> triangles;
20+
public AABB box;
21+
22+
public Model3D() {
23+
box = new AABB();
24+
triangles = new ArrayList<>();
25+
}
26+
27+
public void add(Triangle object) {
28+
triangles.add(object);
29+
box = new AABB(box, object.boundingBox());
30+
}
31+
32+
public void translate(double x, double y, double z){
33+
box = new AABB();
34+
Vec3 move_vec = new Vec3(x,y,z);
35+
for (Hittable triangle : triangles) {
36+
if (triangle instanceof Triangle tri) {
37+
((Triangle) triangle).vertices[0] = tri.vertices[0].add(move_vec);
38+
((Triangle) triangle).vertices[1] = tri.vertices[1].add(move_vec);
39+
((Triangle) triangle).vertices[2] = tri.vertices[2].add(move_vec);
40+
box = new AABB(box, triangle.boundingBox());
41+
}
42+
}
43+
}
44+
45+
public void scale(double s){
46+
box = new AABB();
47+
for (int i = 0; i < triangles.size(); ++i) {
48+
if (triangles.get(i) instanceof Triangle tri) {
49+
Vec3[] new_vertexes = new Vec3[]{
50+
tri.vertices[0].multiply(s),
51+
tri.vertices[1].multiply(s),
52+
tri.vertices[2].multiply(s)
53+
};
54+
triangles.set(i, new Triangle(new_vertexes, tri.uvs, tri.mat));
55+
box = new AABB(box, triangles.get(i).boundingBox());
56+
}
57+
}
58+
}
59+
60+
public void rotate(double angle, int axis) {
61+
box = new AABB();
62+
for (int i = 0; i < triangles.size(); ++i) {
63+
if (triangles.get(i) instanceof Triangle tri) {
64+
Vec3[] new_vertexes = new Vec3[]{
65+
tri.vertices[0].rotate(angle, axis),
66+
tri.vertices[1].rotate(angle, axis),
67+
tri.vertices[2].rotate(angle, axis)
68+
};
69+
triangles.set(i, new Triangle(new_vertexes, tri.uvs, tri.mat));
70+
box = new AABB(box, triangles.get(i).boundingBox());
71+
}
72+
}
73+
}
74+
75+
76+
@Override
77+
public boolean hit(Ray r, Interval t, HitRecord rec) {
78+
boolean hitAnything = false;
79+
for (Hittable object : triangles) {
80+
if (object.hit(r, t, rec)) {
81+
t = new Interval(t.min(), rec.t);
82+
hitAnything = true;
83+
}
84+
}
85+
return hitAnything;
86+
}
87+
88+
@Override
89+
public AABB boundingBox() {
90+
return box;
91+
}
92+
93+
public static Model3D loadModel(String path, Material mat) {
94+
Model3D model = new Model3D();
95+
96+
try (InputStream fileData = new FileInputStream(path);
97+
InputStreamReader InputStreamReader = new InputStreamReader(fileData);
98+
BufferedReader buffer = new BufferedReader(InputStreamReader))
99+
{
100+
String line;
101+
String[] text;
102+
int[] index = new int[3];
103+
104+
Point[] vertex;
105+
Vec2[] uv_data;
106+
Triangle triangle;
107+
108+
ArrayList<Double> vertexData = new ArrayList<>();
109+
ArrayList<Double> uData = new ArrayList<>();
110+
ArrayList<Double> vData = new ArrayList<>();
111+
while((line=buffer.readLine())!=null)
112+
{
113+
text = line.split("\\s+");
114+
switch (text[0].trim()) {
115+
case "v" -> {
116+
// Vertex
117+
vertexData.add(Double.parseDouble(text[1]));
118+
//System.out.println(text[1]);
119+
vertexData.add(Double.parseDouble(text[2]));
120+
vertexData.add(Double.parseDouble(text[3]));
121+
}
122+
case "vt" -> {
123+
// UV
124+
uData.add(Double.parseDouble(text[1]));
125+
vData.add(Double.parseDouble(text[2]));
126+
}
127+
case "f" -> {
128+
// Face
129+
String[] info1, info2, info3;
130+
if (text[1].contains("/")) {
131+
info1 = text[1].split("/");
132+
info2 = text[2].split("/");
133+
info3 = text[3].split("/");
134+
135+
vertex = new Point[3];
136+
index[0] = Integer.parseInt(info1[0]) - 1;
137+
vertex[0] = new Point(vertexData.get(index[0] * 3), vertexData.get(index[0] * 3 + 1), vertexData.get(index[0] * 3 + 2));
138+
index[1] = Integer.parseInt(info2[0]) - 1;
139+
vertex[1] = new Point(vertexData.get(index[1] * 3), vertexData.get(index[1] * 3 + 1), vertexData.get(index[1] * 3 + 2));
140+
index[2] = Integer.parseInt(info3[0]) - 1;
141+
vertex[2] = new Point(vertexData.get(index[2] * 3), vertexData.get(index[2] * 3 + 1), vertexData.get(index[2] * 3 + 2));
142+
143+
uv_data = new Vec2[3];
144+
index[0] = Integer.parseInt(info1[1]) - 1;
145+
uv_data[0] = new Vec2(uData.get(index[0]), vData.get(index[0]));
146+
index[1] = Integer.parseInt(info2[1]) - 1;
147+
uv_data[1] = new Vec2(uData.get(index[1]), vData.get(index[1]));
148+
index[2] = Integer.parseInt(info3[1]) - 1;
149+
uv_data[2] = new Vec2(uData.get(index[2]), vData.get(index[2]));
150+
151+
triangle = new Triangle(vertex, uv_data, mat);
152+
model.add(triangle);
153+
}else {
154+
vertex = new Point[3];
155+
index[0] = Integer.parseInt(text[1]) - 1;
156+
vertex[0] = new Point(vertexData.get(index[0] * 3), vertexData.get(index[0] * 3 + 1), vertexData.get(index[0] * 3 + 2));
157+
index[1] = Integer.parseInt(text[2]) - 1;
158+
vertex[1] = new Point(vertexData.get(index[1] * 3), vertexData.get(index[1] * 3 + 1), vertexData.get(index[1] * 3 + 2));
159+
index[2] = Integer.parseInt(text[3]) - 1;
160+
vertex[2] = new Point(vertexData.get(index[2] * 3), vertexData.get(index[2] * 3 + 1), vertexData.get(index[2] * 3 + 2));
161+
162+
triangle = new Triangle(vertex, mat);
163+
model.add(triangle);
164+
}
165+
}
166+
}
167+
}
168+
} catch(Exception e) {
169+
throw new RuntimeException(e);
170+
}
171+
172+
return model;
173+
}
174+
}
175+
176+

src/RayTracing/Hittable/Triangle.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package RayTracing.Hittable;
2+
3+
import RayTracing.*;
4+
import RayTracing.HitInfo.HitRecord;
5+
import RayTracing.HitInfo.Interval;
6+
import RayTracing.Material.Material;
7+
8+
import static RayTracing.Utility.infinity;
9+
import static java.lang.Math.*;
10+
11+
public class Triangle implements Hittable{
12+
13+
public Vec3[] vertices;
14+
public Vec2[] uvs;
15+
Vec3 normal, e1, e2;
16+
Material mat;
17+
18+
public Triangle(Vec3[] vertices, Material material){
19+
this.vertices = vertices;
20+
this.mat = material;
21+
this.e1 = vertices[1].minus(vertices[0]);
22+
this.e2 = vertices[2].minus(vertices[0]);
23+
this.normal = Vec3.unitVector(e1.cross(e2));
24+
}
25+
26+
public Triangle(Vec3[] vertices, Vec2[] uvs, Material material){
27+
this.vertices = vertices;
28+
this.uvs = uvs;
29+
this.mat = material;
30+
this.e1 = vertices[1].minus(vertices[0]);
31+
this.e2 = vertices[2].minus(vertices[0]);
32+
this.normal = Vec3.unitVector(e1.cross(e2));
33+
}
34+
35+
private Vec2 calculateObjTextureUV(double u, double v){
36+
return (uvs[2].multiply(u))
37+
.add(uvs[1].multiply(v))
38+
.add(uvs[0].multiply((1-u-v)));
39+
}
40+
41+
private void convertToObjUV(HitRecord rec){
42+
if (uvs != null){
43+
Vec2 uv = calculateObjTextureUV(rec.u, rec.v);
44+
rec.u = uv.x;
45+
rec.v = uv.y;
46+
}
47+
}
48+
49+
@Override
50+
public boolean hit(Ray r, Interval it, HitRecord rec) {
51+
if(abs(normal.dot(r.dir())) < 1e-8)
52+
return false;
53+
54+
Vec3 s0 = r.orig().minus(vertices[0]);
55+
Vec3 s1 = r.dir().cross(e2);
56+
Vec3 s2 = s0.cross(e1);
57+
58+
double s1e1 = s1.dot(e1);
59+
double t = s2.dot(e2) / s1e1;
60+
61+
if (t < it.min() || t > it.max())
62+
return false;
63+
64+
double b1 = s1.dot(s0) / s1e1;
65+
if (b1 > 0) {
66+
double b2 = s2.dot(r.dir()) / s1e1;
67+
if (b2 > 0) {
68+
if ((1-b1-b2) > 0) {
69+
rec.t = t;
70+
rec.u = b2;
71+
rec.v = b1;
72+
this.convertToObjUV(rec);
73+
rec.p = r.at(t);
74+
rec.setFaceNormal(r, normal);
75+
rec.mat = mat;
76+
77+
return true;
78+
} else return false;
79+
} else return false;
80+
}
81+
return false;
82+
}
83+
84+
@Override
85+
public AABB boundingBox() {
86+
Point min = new Point(infinity, infinity, infinity);
87+
Point max = new Point(-infinity, -infinity, -infinity);
88+
89+
for (int i = 0; i < 3; ++i) {
90+
min = new Point(min(min.x, vertices[i].x), min(min.y, vertices[i].y), min(min.z, vertices[i].z));
91+
max = new Point(max(max.x, vertices[i].x), max(max.y, vertices[i].y), max(max.z, vertices[i].z));
92+
}
93+
return new AABB(min.minus(1e-8), max.add(1e-8));
94+
}
95+
}

src/RayTracing/Main.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ public static void main(String[] args) {
88
// imageRender(50spp, 1280x720) for final image = 206.791s
99
// imageRenderParallel(50spp, 1280x720) for final image = 46.492s
1010
// imageRenderParallel(500spp, 1280x720) for final image = 546.934s - With AABB is 228s!
11+
// AABB improves performance by approximately 2x
1112
// Only takes around 100MB of memory to render! - With AABB is 600MB!
1213

1314
System.out.println("\nSelect a render mode:");
1415
System.out.println("1. Single-threaded with 50 samples per pixel (deprecated)");
1516
System.out.println("2. Multi-threaded with 50 samples per pixel");
1617
System.out.println("3. Multi-threaded with 500 samples per pixel");
17-
System.out.println("4. Windowed with 15 samples per pixel");
18-
System.out.println("5. Windowed with 50 samples per pixel");
18+
System.out.println("4. Multi-threaded with 1500 samples per pixel - Warning: Takes a long time!");
19+
System.out.println("5. Windowed with 15 samples per pixel");
20+
System.out.println("6. Windowed with 50 samples per pixel");
1921

2022

2123
int choice = 0;
22-
while (choice < 1 || choice > 5) {
24+
while (choice < 1 || choice > 6) {
2325
choice = Utility.readInt();
2426
}
2527

@@ -34,9 +36,12 @@ public static void main(String[] args) {
3436
System.out.println("8. Cornell Box Scene");
3537
System.out.println("9. Cornell Smoke Scene");
3638
System.out.println("10. Final Scene");
39+
System.out.println("11. Reimu Scene");
40+
System.out.println("12. Dragon Scene");
41+
System.out.println("13. Ultimate Scene");
3742

3843
int sceneChoice = 0;
39-
while (sceneChoice < 1 || sceneChoice > 10) {
44+
while (sceneChoice < 1 || sceneChoice > 13) {
4045
sceneChoice = Utility.readInt();
4146
}
4247

@@ -71,14 +76,23 @@ public static void main(String[] args) {
7176
case 10:
7277
chosen = Scene.finalScene();
7378
break;
79+
case 11:
80+
chosen = Scene.reimu3D();
81+
break;
82+
case 12:
83+
chosen = Scene.dragon();
84+
break;
85+
case 13:
86+
chosen = Scene.ultimateScene();
87+
break;
7488
default:
7589
break;
7690
}
7791

7892
Renderer render;
7993

8094

81-
if (sceneChoice == 6 || sceneChoice == 8 || sceneChoice == 9 || sceneChoice == 10) {
95+
if (sceneChoice == 6 || sceneChoice == 8 || sceneChoice == 9 || sceneChoice == 10 || sceneChoice == 13) {
8296
render = new Renderer(chosen.camera, 1080, 1080, chosen.backgroundColor);
8397
} else {
8498
render = new Renderer(chosen.camera, chosen.backgroundColor);
@@ -95,13 +109,16 @@ public static void main(String[] args) {
95109
render.imageRenderParallel(chosen.world, 500, "Output(500spp).png");
96110
break;
97111
case 4:
98-
render.windowRender(chosen.world, 15);
112+
render.imageRenderParallel(chosen.world, 1500, "Output(1500spp).png");
99113
break;
100114
case 5:
115+
render.windowRender(chosen.world, 15);
116+
break;
117+
case 6:
101118
render.windowRender(chosen.world, 50);
102119
break;
103120
default:
104121
break;
105122
}
106123
}
107-
}
124+
}

src/RayTracing/Renderer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@ public void paint(Graphics g) {
236236
}
237237
};
238238

239-
240239
window.setSize(image_width, image_height);
241240
return window;
242241
}

0 commit comments

Comments
 (0)