forked from ssloy/tinyrenderer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.cpp
169 lines (141 loc) · 6.36 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <limits>
#include <iostream>
#include <unistd.h>
#include "model.h"
#include "our_gl.h"
#include <err.h>
constexpr int width = 800; // output image size
constexpr int height = 800;
#ifdef WITH_WAYLAND
#include "wayland/shm.h"
#else
#define display_init(w, h)
#define display_show(x)
#define display_destroy()
#endif
#ifdef WITH_WAYLAND
vec3 light_dir(1,1,1); // light source
#else
const vec3 light_dir(1,1,1); // light source
#endif
const vec3 eye(1,1,3); // camera position
const vec3 center(0,0,0); // camera direction
const vec3 up(0,1,0); // camera up vector
extern mat<4,4> ModelView; // "OpenGL" state matrices
extern mat<4,4> Projection;
bool forever = false;
char *work_done_string = NULL;
unsigned long long work_done = 0;
int work_done_every = 1;
std::vector<Model> models;
struct Shader : IShader {
const Model &model;
vec3 l; // light direction in normalized device coordinates
mat<2,3> varying_uv; // triangle uv coordinates, written by the vertex shader, read by the fragment shader
mat<3,3> varying_nrm; // normal per vertex to be interpolated by FS
mat<3,3> ndc_tri; // triangle in normalized device coordinates
Shader(const Model &m) : model(m) {
l = proj<3>((Projection*ModelView*embed<4>(light_dir, 0.))).normalize(); // transform the light vector to the normalized device coordinates
}
virtual vec4 vertex(const int iface, const int nthvert) {
varying_uv.set_col(nthvert, model.uv(iface, nthvert));
varying_nrm.set_col(nthvert, proj<3>((Projection*ModelView).invert_transpose()*embed<4>(model.normal(iface, nthvert), 0.)));
vec4 gl_Vertex = Projection*ModelView*embed<4>(model.vert(iface, nthvert));
ndc_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3]));
return gl_Vertex;
}
virtual bool fragment(const vec3 bar, TGAColor &color) {
vec3 bn = (varying_nrm*bar).normalize(); // per-vertex normal interpolation
vec2 uv = varying_uv*bar; // tex coord interpolation
// for the math refer to the tangent space normal mapping lecture
// https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis-tangent-space-normal-mapping
mat<3,3> AI = mat<3,3>{ {ndc_tri.col(1) - ndc_tri.col(0), ndc_tri.col(2) - ndc_tri.col(0), bn} }.invert();
vec3 i = AI * vec3(varying_uv[0][1] - varying_uv[0][0], varying_uv[0][2] - varying_uv[0][0], 0);
vec3 j = AI * vec3(varying_uv[1][1] - varying_uv[1][0], varying_uv[1][2] - varying_uv[1][0], 0);
mat<3,3> B = mat<3,3>{ {i.normalize(), j.normalize(), bn} }.transpose();
vec3 n = (B * model.normal(uv)).normalize(); // transform the normal from the texture to the tangent space
double diff = std::max(0., n*l); // diffuse light intensity
vec3 r = (n*(n*l)*2 - l).normalize(); // reflected light direction, specular mapping is described here: https://github.com/ssloy/tinyrenderer/wiki/Lesson-6-Shaders-for-the-software-renderer
double spec = std::pow(std::max(r.z, 0.), 5+model.specular(uv)); // specular intensity, note that the camera lies on the z-axis (in ndc), therefore simple r.z
TGAColor c = model.diffuse(uv);
for (int i=0; i<3; i++)
color[i] = std::min<int>(10 + c[i]*(diff + spec), 255); // (a bit of ambient light, diff + spec), clamp the result
return false; // the pixel is not discarded
}
};
void print_usage(char *cmd) {
std::cerr << "Usage: " << cmd << " [-f] [-w work_done_string [-e num]] obj/model.obj..." << std::endl;
}
void tinyrender_run() {
TGAImage framebuffer(width, height, TGAImage::RGBA); // the output image
do {
std::vector<double> zbuffer(width*height, -std::numeric_limits<double>::max()); // note that the z-buffer is initialized with minimal possible values
#ifdef WITH_WAYLAND
static unsigned frame = 0;
float f = static_cast<double>(frame++) / 30;
vec3 eye(3*sin(f), 1, 3*cos(f));
light_dir = eye;
framebuffer.clear();
#endif
lookat(eye, center, up); // build the ModelView matrix
viewport(width/8, height/8, width*3/4, height*3/4); // build the Viewport matrix
projection(-1.f/(eye-center).norm()); // build the Projection matrix
for (auto &model : models) { // iterate through all input objects
Shader shader(model);
for (int i=0; i<model.nfaces(); i++) { // for every triangle
vec4 clip_vert[3]; // triangle coordinates (clip coordinates), written by VS, read by FS
for (int j=0; j<3; j++)
clip_vert[j] = shader.vertex(i, j); // call the vertex shader for each triangle vertex
triangle(clip_vert, shader, framebuffer, zbuffer); // actual rasterization routine call
}
}
if (work_done_string && (work_done++ % work_done_every == 0)) {
printf("%s=%lld\n", work_done_string, work_done - 1);
fflush(stdout);
}
framebuffer.flip_vertically();
display_show(framebuffer.buffer());
} while (forever);
if (getenv("TB_OPTS") == NULL)
framebuffer.write_tga_file("framebuffer.tga"); // the vertical flip is moved inside the function
}
void tinyrender_init(int argc, char** argv)
{
int opt;
while ((opt = getopt(argc, argv, "e:fw:")) != -1) {
switch (opt) {
case 'e':
work_done_every = atoi(optarg);
if (work_done_every <= 0)
errx(1, "Usage: -e 'number > 0'");
break;
case 'f':
forever = true;
break;
case 'w':
work_done_string = optarg;
break;
default: /* '?' */
print_usage(argv[0]);
return;
}
}
if (optind >= argc) {
print_usage(argv[0]);
exit(1);
}
if (getenv("TB_OPTS") && (forever || work_done_string != NULL || work_done_every != 1))
errx(1, "-f, -w and -e switches are incompatible with TB_OPTS environment variable");
if (work_done_string == NULL && work_done_every != 1)
errx(1, "-e only makes sense with -w");
display_init(width, height);
for (int m=optind; m<argc; m++) // iterate through all input objects
models.emplace_back(argv[m]);
}
int main(int argc, char** argv)
{
tinyrender_init(argc, argv);
tinyrender_run();
display_destroy();
return 0;
}