Skip to content

Commit 5b62502

Browse files
committed
flat shading
1 parent 608a4a1 commit 5b62502

File tree

2 files changed

+78
-36
lines changed

2 files changed

+78
-36
lines changed

TinyRenderer.java

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.awt.image.BufferedImage;
55

66
import java.util.ArrayList;
7-
import java.util.Random;
87

98
import java.io.*;
109
import javax.imageio.ImageIO;
@@ -21,10 +20,9 @@ public class TinyRenderer extends JPanel {
2120
public static double[][] zbuffer = null; // z-buffer array
2221

2322
// compute the matrix product A*B
24-
public static double[][] matrix_multiply(double[][] A, double[][] B) {
23+
public static double[][] matrix_product(double[][] A, double[][] B) {
2524
if (A.length==0 || A[0].length != B.length)
2625
throw new IllegalStateException("invalid dimensions");
27-
2826
double[][] matrix = new double[A.length][B[0].length];
2927
for (int i=0; i<A.length; i++) {
3028
for (int j=0; j<B[0].length; j++) {
@@ -40,7 +38,6 @@ public static double[][] matrix_multiply(double[][] A, double[][] B) {
4038
// transpose the matrix
4139
public static double[][] matrix_transpose(double[][] matrix) {
4240
double[][] transpose = new double[matrix[0].length][matrix.length];
43-
4441
for (int i = 0; i < matrix.length; i++)
4542
for (int j = 0; j < matrix[i].length; j++)
4643
transpose[j][i] = matrix[i][j];
@@ -52,26 +49,63 @@ public static double[][] matrix_inverse(double[][] m) {
5249
if (m[0].length != m.length || m.length != 3)
5350
throw new IllegalStateException("invalid dimensions");
5451
double[][] inverse = new double[m.length][m.length];
55-
double invdet = 1. / (m[0][0]*(m[1][1]*m[2][2] - m[2][1]*m[1][2]) - m[0][1]*(m[1][0]*m[2][2] - m[1][2]*m[2][0]) + m[0][2]*(m[1][0]*m[2][1] - m[1][1]*m[2][0]));
56-
inverse[0][0] = (m[1][1]*m[2][2] - m[2][1]*m[1][2])*invdet;
57-
inverse[0][1] = (m[0][2]*m[2][1] - m[0][1]*m[2][2])*invdet;
58-
inverse[0][2] = (m[0][1]*m[1][2] - m[0][2]*m[1][1])*invdet;
59-
inverse[1][0] = (m[1][2]*m[2][0] - m[1][0]*m[2][2])*invdet;
60-
inverse[1][1] = (m[0][0]*m[2][2] - m[0][2]*m[2][0])*invdet;
61-
inverse[1][2] = (m[1][0]*m[0][2] - m[0][0]*m[1][2])*invdet;
62-
inverse[2][0] = (m[1][0]*m[2][1] - m[2][0]*m[1][1])*invdet;
63-
inverse[2][1] = (m[2][0]*m[0][1] - m[0][0]*m[2][1])*invdet;
64-
inverse[2][2] = (m[0][0]*m[1][1] - m[1][0]*m[0][1])*invdet;
52+
double det = m[0][0]*(m[1][1]*m[2][2] - m[2][1]*m[1][2]) - m[0][1]*(m[1][0]*m[2][2] - m[1][2]*m[2][0]) + m[0][2]*(m[1][0]*m[2][1] - m[1][1]*m[2][0]);
53+
if (Math.abs(det)<1e-6)
54+
throw new IllegalStateException("non-invertible matrix");
55+
inverse[0][0] = (m[1][1]*m[2][2] - m[2][1]*m[1][2])/det;
56+
inverse[0][1] = (m[0][2]*m[2][1] - m[0][1]*m[2][2])/det;
57+
inverse[0][2] = (m[0][1]*m[1][2] - m[0][2]*m[1][1])/det;
58+
inverse[1][0] = (m[1][2]*m[2][0] - m[1][0]*m[2][2])/det;
59+
inverse[1][1] = (m[0][0]*m[2][2] - m[0][2]*m[2][0])/det;
60+
inverse[1][2] = (m[1][0]*m[0][2] - m[0][0]*m[1][2])/det;
61+
inverse[2][0] = (m[1][0]*m[2][1] - m[2][0]*m[1][1])/det;
62+
inverse[2][1] = (m[2][0]*m[0][1] - m[0][0]*m[2][1])/det;
63+
inverse[2][2] = (m[0][0]*m[1][1] - m[1][0]*m[0][1])/det;
6564
return inverse;
6665
}
6766

67+
// dot product between two vectors; N.B. works for dimension 3 vectors only
68+
public static double dot_product(double[] v1, double[] v2) {
69+
if (v1.length != v2.length || v1.length != 3)
70+
throw new IllegalStateException("invalid dimensions");
71+
return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
72+
}
73+
74+
// cross product between two vectors
75+
public static double[] cross_product(double[] v1, double[] v2) {
76+
if (v1.length != v2.length || v1.length != 3)
77+
throw new IllegalStateException("invalid dimensions");
78+
double[] cross = new double[3];
79+
cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
80+
cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
81+
cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
82+
return cross;
83+
}
84+
85+
// given a triangle, return its normal
86+
public static double [] triangle_normal(double[] x, double[] y, double[] z) {
87+
if (x.length != y.length || x.length != z.length || x.length != 3)
88+
throw new IllegalStateException("invalid dimensions");
89+
double[] edge_a = {x[1] - x[0], y[1] - y[0], z[1] - z[0]};
90+
double[] edge_b = {x[2] - x[0], y[2] - y[0], z[2] - z[0]};
91+
double[] cross = cross_product(edge_a, edge_b);
92+
double norm = Math.sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]);
93+
if (norm<1e-6)
94+
throw new IllegalStateException("degenerate triangle");
95+
cross[0] /= norm;
96+
cross[1] /= norm;
97+
cross[2] /= norm;
98+
return cross;
99+
}
100+
68101
// given a triangle [(x0,y0), (x1,y1), (x2,y2)], compute barycentric coordinates of the point (x,y) w.r.t the triangle
69102
public static double[] barycentric_coords(int x0, int y0, int x1, int y1, int x2, int y2, int x, int y) {
70103
double[][] A = { { x0, x1, x2 }, { y0, y1, y2 }, { 1., 1., 1. } };
71104
double[][] b = { { x }, { y }, { 1. } };
72-
return matrix_transpose(matrix_multiply(matrix_inverse(A), b))[0];
105+
return matrix_transpose(matrix_product(matrix_inverse(A), b))[0];
73106
}
74107

108+
// well duh
75109
public void paint(Graphics g) {
76110
g.drawImage(framebuffer, 0, 0, this);
77111
}
@@ -116,25 +150,27 @@ public static void main(String[] args) throws FileNotFoundException, IOException
116150
}
117151

118152
framebuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
119-
zbuffer = new double[width][height]; // initialize the z-buffer
120-
for (int i=0; i<width; i++) {
121-
for (int j=0; j<height; j++) {
122-
zbuffer[i][j] = -1;
153+
{ // initialize the z-buffer with min possible values
154+
zbuffer = new double[width][height];
155+
for (int i=0; i<width; i++) {
156+
for (int j=0; j<height; j++) {
157+
zbuffer[i][j] = -1.;
158+
}
123159
}
124160
}
125-
Random rand = new Random();
126161

127162
for (int t=0; t<triangles.length/3; t++) { // iterate through all triangles
128-
int color = new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256)).getRGB();
163+
double[] xw = new double[3]; // triangle in world coordinates
164+
double[] yw = new double[3];
165+
double[] zw = new double[3];
129166
int[] x = new int[3]; // triangle in screen coordinates
130167
int[] y = new int[3];
131-
double[] z = new double[3];
132168
for (int v=0; v<3; v++) {
133-
double xw = vertices[triangles[t*3+v]*3+0]; // world coordinates
134-
double yw = vertices[triangles[t*3+v]*3+1];
135-
z[v] = vertices[triangles[t*3+v]*3+2];
136-
x[v] = (int)( width*(xw+1.)/2.+.5); // world-to-screen transformation
137-
y[v] = (int)(height*(1.-yw)/2.+.5); // y is flipped to get a "natural" y orientation (origin in the bottom left corner)
169+
xw[v] = vertices[triangles[t*3+v]*3+0]; // world coordinates
170+
yw[v] = vertices[triangles[t*3+v]*3+1];
171+
zw[v] = vertices[triangles[t*3+v]*3+2];
172+
x[v] = (int)( width*(xw[v]+1.)/2.+.5); // world-to-screen transformation
173+
y[v] = (int)(height*(1.-yw[v])/2.+.5); // y is flipped to get a "natural" y orientation (origin in the bottom left corner)
138174
}
139175

140176
int bbminx = width-1; // screen bounding box for the triangle to rasterize
@@ -147,16 +183,21 @@ public static void main(String[] args) throws FileNotFoundException, IOException
147183
bbmaxx = Math.min(width-1, Math.max(bbmaxx, x[v]));
148184
bbmaxy = Math.min(height-1, Math.max(bbmaxy, y[v]));
149185
}
150-
for (int px=bbminx; px<=bbmaxx; px++) { // rasterize the bounding box
151-
for (int py=bbminy; py<=bbmaxy; py++) {
152-
double[] coord = barycentric_coords(x[0], y[0], x[1], y[1], x[2], y[2], px, py);
153-
if (coord[0]<0 || coord[1]<0 || coord[2]<0) continue; // discard the point outside the triangle
154-
double pz = coord[0]*z[0] + coord[1]*z[1] + coord[2]*z[2]; // compute the depth of the fragment
155-
if (zbuffer[px][py]>pz) continue; // discard the fragment if it lies behind the z-buffer
156-
zbuffer[px][py] = pz;
157-
framebuffer.setRGB(px, py, color);
186+
try { // non-ivertible matrix (can happen if a triangle is degenerate)
187+
for (int px=bbminx; px<=bbmaxx; px++) { // rasterize the bounding box
188+
for (int py=bbminy; py<=bbmaxy; py++) {
189+
double[] coord = barycentric_coords(x[0], y[0], x[1], y[1], x[2], y[2], px, py);
190+
if (coord[0]<0. || coord[1]<0. || coord[2]<0.) continue; // discard the point outside the triangle
191+
double pz = coord[0]*zw[0] + coord[1]*zw[1] + coord[2]*zw[2]; // compute the depth of the fragment
192+
if (zbuffer[px][py]>pz) continue; // discard the fragment if it lies behind the z-buffer
193+
zbuffer[px][py] = pz;
194+
double[] normal = triangle_normal(xw, yw, zw);
195+
int intensity = (int)Math.min(255, Math.max(0, 255*dot_product(normal, new double[]{0., 0., 1.}))); // triangle intensity is the (clamped) cosine of the angle between the triangle normal and the view direction
196+
int color = new Color(intensity, intensity, intensity).getRGB();
197+
framebuffer.setRGB(px, py, color);
198+
}
158199
}
159-
}
200+
} catch (IllegalStateException ex) {}
160201
}
161202

162203
try {
@@ -170,3 +211,4 @@ public static void main(String[] args) throws FileNotFoundException, IOException
170211
frame.setVisible(true);
171212
}
172213
}
214+

drop.png

4.75 KB
Loading

0 commit comments

Comments
 (0)