3 | 3 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,153 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+from sympy import * |
|
4 |
+ |
|
5 |
+ |
|
6 |
+def mat(*args): |
|
7 |
+ return Matrix(list(map(list, args))).T |
|
8 |
+ |
|
9 |
+def vec(*args): |
|
10 |
+ return Matrix(args) |
|
11 |
+ |
|
12 |
+def normalize(v): |
|
13 |
+ return v / sqrt(v.dot(v)) |
|
14 |
+ |
|
15 |
+def elementwise(a, b, f): |
|
16 |
+ return Matrix([ |
|
17 |
+ [ |
|
18 |
+ f(a[r, c], b[r, c]) |
|
19 |
+ for c in range(a.cols) |
|
20 |
+ ] |
|
21 |
+ for r in range(a.rows) |
|
22 |
+ ]) |
|
23 |
+ |
|
24 |
+def mul(a, b): |
|
25 |
+ return elementwise(a, b, lambda a, b: a * b) |
|
26 |
+ |
|
27 |
+def div(a, b): |
|
28 |
+ return elementwise(a, b, lambda a, b: a / b) |
|
29 |
+ |
|
30 |
+ |
|
31 |
+def window(viewport, depth_range): |
|
32 |
+ size = vec(*viewport[2:4], depth_range[1]-depth_range[0]) |
|
33 |
+ offs = vec(*viewport[0:2], depth_range[0]) |
|
34 |
+ s = 0.5 * size |
|
35 |
+ t = 0.5 * size + offs |
|
36 |
+ return Matrix([ |
|
37 |
+ [s[0], 0, 0, t[0]], |
|
38 |
+ [0, s[1], 0, t[1]], |
|
39 |
+ [0, 0, s[2], t[2]], |
|
40 |
+ [0, 0, 0, 1], |
|
41 |
+ ]) |
|
42 |
+ |
|
43 |
+def ortho(left, right, bottom, top, near, far): |
|
44 |
+ size = vec(right-left, top-bottom, far-near) |
|
45 |
+ offs = vec( -left, -bottom, +near) |
|
46 |
+ s = div(vec(2, 2, -2), size) |
|
47 |
+ t = mul(s, offs) - vec(1, 1, 1) |
|
48 |
+ return Matrix([ |
|
49 |
+ [s[0], 0, 0, t[0]], |
|
50 |
+ [0, s[1], 0, t[1]], |
|
51 |
+ [0, 0, s[2], t[2]], |
|
52 |
+ [0, 0, 0, 1], |
|
53 |
+ ]) |
|
54 |
+ |
|
55 |
+def frustum(left, right, bottom, top, near, far): |
|
56 |
+ z = vec(0, 0, near+far, -1) |
|
57 |
+ w = vec(0, 0, near*far, 0) |
|
58 |
+ return ortho(left, right, bottom, top, near, far) * Matrix([ |
|
59 |
+ [near, 0, z[0], z[0]], |
|
60 |
+ [0, near, z[1], w[1]], |
|
61 |
+ [0, 0, z[2], w[2]], |
|
62 |
+ [0, 0, z[3], w[3]], |
|
63 |
+ ]) |
|
64 |
+ |
|
65 |
+def perspective(fovy, aspect, near, far): |
|
66 |
+ y = near * tan(0.5 * fovy) |
|
67 |
+ x = y * aspect |
|
68 |
+ return frustum(-x, +x, -y, +y, near, far) |
|
69 |
+ |
|
70 |
+def lookat(position, target, up): |
|
71 |
+ z = normalize(position - target) |
|
72 |
+ x = normalize(up.cross(z)) |
|
73 |
+ y = z.cross(x) |
|
74 |
+ R_inv = mat(x, y, z).T |
|
75 |
+ t_inv = -position |
|
76 |
+ return mat(R_inv.row(0), R_inv.row(0), R_inv.row(0), R_inv * t_inv) |
|
77 |
+ |
|
78 |
+ |
|
79 |
+# https://registry.khronos.org/OpenGL-Refpages/gl4/html/glViewport.xhtml |
|
80 |
+# https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml |
|
81 |
+# https://www.khronos.org/opengl/wiki/Vertex_Post-Processing#Viewport_transform |
|
82 |
+# https://www.songho.ca/opengl/gl_viewport.html |
|
83 |
+viewport = symbols('x, y, width, height') |
|
84 |
+depth_range = symbols('near, far') |
|
85 |
+pprint(simplify(window(viewport, depth_range))) |
|
86 |
+# ⎡0.5⋅width 0 0 0.5⋅width + x ⎤ |
|
87 |
+# ⎢ ⎥ |
|
88 |
+# ⎢ 0 0.5⋅height 0 0.5⋅height + y ⎥ |
|
89 |
+# ⎢ ⎥ |
|
90 |
+# ⎢ 0 0 0.5⋅far - 0.5⋅near 0.5⋅far + 0.5⋅near⎥ |
|
91 |
+# ⎢ ⎥ |
|
92 |
+# ⎣ 0 0 0 1 ⎦ |
|
93 |
+ |
|
94 |
+# https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml |
|
95 |
+# https://www.songho.ca/opengl/gl_projectionmatrix.html#ortho |
|
96 |
+left, right, bottom, top, near, far = symbols('left, right, bottom, top, near, far') |
|
97 |
+pprint(simplify(ortho(left, right, bottom, top, near, far))) |
|
98 |
+# ⎡ -2 left + right⎤ |
|
99 |
+# ⎢──────────── 0 0 ────────────⎥ |
|
100 |
+# ⎢left - right left - right⎥ |
|
101 |
+# ⎢ ⎥ |
|
102 |
+# ⎢ -2 bottom + top⎥ |
|
103 |
+# ⎢ 0 ──────────── 0 ────────────⎥ |
|
104 |
+# ⎢ bottom - top bottom - top⎥ |
|
105 |
+# ⎢ ⎥ |
|
106 |
+# ⎢ -2 -far - near ⎥ |
|
107 |
+# ⎢ 0 0 ────────── ─────────── ⎥ |
|
108 |
+# ⎢ far - near far - near ⎥ |
|
109 |
+# ⎢ ⎥ |
|
110 |
+# ⎣ 0 0 0 1 ⎦ |
|
111 |
+ |
|
112 |
+# https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml |
|
113 |
+# https://www.songho.ca/opengl/gl_projectionmatrix.html#perspective |
|
114 |
+left, right, bottom, top, near, far = symbols('left, right, bottom, top, near, far') |
|
115 |
+pprint(simplify(frustum(left, right, bottom, top, near, far))) |
|
116 |
+# ⎡ -2⋅near -left - right ⎤ |
|
117 |
+# ⎢──────────── 0 ───────────── 0 ⎥ |
|
118 |
+# ⎢left - right left - right ⎥ |
|
119 |
+# ⎢ ⎥ |
|
120 |
+# ⎢ -2⋅near -bottom - top ⎥ |
|
121 |
+# ⎢ 0 ──────────── ───────────── 0 ⎥ |
|
122 |
+# ⎢ bottom - top bottom - top ⎥ |
|
123 |
+# ⎢ ⎥ |
|
124 |
+# ⎢ -far - near -2⋅far⋅near ⎥ |
|
125 |
+# ⎢ 0 0 ─────────── ────────────⎥ |
|
126 |
+# ⎢ far - near far - near ⎥ |
|
127 |
+# ⎢ ⎥ |
|
128 |
+# ⎣ 0 0 -1 0 ⎦ |
|
129 |
+ |
|
130 |
+# https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml |
|
131 |
+# https://www.songho.ca/opengl/gl_projectionmatrix.html#fov |
|
132 |
+fovy, aspect, near, far = symbols('fovy, aspect, near, far') |
|
133 |
+pprint(simplify(perspective(fovy, aspect, near, far))) |
|
134 |
+# ⎡ 1 ⎤ |
|
135 |
+# ⎢──────────────────── 0 0 0 ⎥ |
|
136 |
+# ⎢aspect⋅tan(0.5⋅fovy) ⎥ |
|
137 |
+# ⎢ ⎥ |
|
138 |
+# ⎢ 1 ⎥ |
|
139 |
+# ⎢ 0 ───────────── 0 0 ⎥ |
|
140 |
+# ⎢ tan(0.5⋅fovy) ⎥ |
|
141 |
+# ⎢ ⎥ |
|
142 |
+# ⎢ -far - near -2⋅far⋅near ⎥ |
|
143 |
+# ⎢ 0 0 ─────────── ────────────⎥ |
|
144 |
+# ⎢ far - near far - near ⎥ |
|
145 |
+# ⎢ ⎥ |
|
146 |
+# ⎣ 0 0 -1 0 ⎦ |
|
147 |
+ |
|
148 |
+# https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml |
|
149 |
+# https://www.songho.ca/opengl/gl_camera.html#lookat |
|
150 |
+position = Matrix(MatrixSymbol('position', 3, 1)) |
|
151 |
+target = Matrix(MatrixSymbol('target', 3, 1)) |
|
152 |
+up = Matrix(MatrixSymbol('up', 3, 1)) |
|
153 |
+# pprint(simplify(lookat(position, target, up))) |
... | ... |
@@ -85,17 +85,24 @@ struct Scene |
85 | 85 |
//// Camera |
86 | 86 |
struct Camera |
87 | 87 |
{ |
88 |
- float near; |
|
89 |
- float far; |
|
90 |
- uvec2 size; |
|
91 |
- Ray ray(vec4 const & frag_coord) const |
|
88 |
+ mat4 camera_inverse; |
|
89 |
+ explicit Camera( |
|
90 |
+ mat4 const & view_, |
|
91 |
+ mat4 const & projection_, |
|
92 |
+ mat4 const & window_ |
|
93 |
+ ) |
|
94 |
+ : |
|
95 |
+ camera_inverse{inverse(window_ * projection_ * view_)} |
|
96 |
+ {} |
|
97 |
+ vec3 unproject(vec3 const & frag_coord) const |
|
92 | 98 |
{ |
93 |
- auto const point = vec3( |
|
94 |
- (2.0F * vec2(frag_coord.xy) - vec2(size)) / float(size.y), |
|
95 |
- -1.0F |
|
96 |
- ); |
|
97 |
- auto const origin = near * point; |
|
98 |
- auto const stop = far * point; |
|
99 |
+ auto const world_coord = camera_inverse * vec4(frag_coord, 1.0F); |
|
100 |
+ return vec3(world_coord) / world_coord.w; |
|
101 |
+ } |
|
102 |
+ Ray ray(vec4 const & frag_coord, vec2 const & depth_range) const |
|
103 |
+ { |
|
104 |
+ auto const origin = unproject(vec3(frag_coord.xy, depth_range[0])); |
|
105 |
+ auto const stop = unproject(vec3(frag_coord.xy, depth_range[1])); |
|
99 | 106 |
auto const range = length(stop - origin); |
100 | 107 |
auto const direction = (stop - origin) / range; |
101 | 108 |
return Ray{origin, direction, range}; |
... | ... |
@@ -113,15 +120,16 @@ struct Framebuffer |
113 | 120 |
color((std::size_t)(size_.x * size_.y)) |
114 | 121 |
{} |
115 | 122 |
template<typename Shader> |
116 |
- void render(Shader const & shader) |
|
123 |
+ void render(uvec4 viewport, vec2 depth_range, Shader const & shader) |
|
117 | 124 |
{ |
118 |
- auto const begin = uvec2(0); |
|
119 |
- auto const end = size; |
|
125 |
+ auto const begin = max(uvec2(0), uvec2(viewport.xy)); |
|
126 |
+ auto const end = min(uvec2(size), uvec2(viewport.xy + viewport.zw)); |
|
127 |
+ auto const z = 0.5F * (depth_range[0] + depth_range[1]); |
|
120 | 128 |
for (auto y = begin.y; y < end.y; ++y) |
121 | 129 |
for (auto x = begin.x; x < end.x; ++x) |
122 | 130 |
{ |
123 | 131 |
auto const index = size.x * y + x; |
124 |
- auto const frag_coord = vec4(vec2(x, y) + 0.5F, 0.0F, 1.0F); |
|
132 |
+ auto const frag_coord = vec4(vec2(x, y) + 0.5F, z, 1.0F); |
|
125 | 133 |
auto const frag_color = vec4(shader(frag_coord).bgra); |
126 | 134 |
color[index] = clamp(frag_color, 0.0F, 1.0F) * 255.0F + 0.5F; |
127 | 135 |
} |
... | ... |
@@ -150,6 +158,58 @@ struct Framebuffer |
150 | 158 |
} |
151 | 159 |
}; |
152 | 160 |
|
161 |
+/// Transformations |
|
162 |
+ |
|
163 |
+//// window |
|
164 |
+mat4 window(uvec4 viewport, vec2 depth_range) |
|
165 |
+{ |
|
166 |
+ auto const size = vec3(uvec2(viewport.zw), depth_range[1]-depth_range[0]); |
|
167 |
+ auto const offs = vec3(uvec2(viewport.xy), depth_range[0]); |
|
168 |
+ auto const s = 0.5F * size; |
|
169 |
+ auto const t = 0.5F * size + offs; |
|
170 |
+ auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s)); |
|
171 |
+ return mat4(mat4x3(S[0], S[1], S[2], t)); |
|
172 |
+} |
|
173 |
+ |
|
174 |
+//// ortho |
|
175 |
+mat4 ortho(float left, float right, float bottom, float top, float near, float far) |
|
176 |
+{ |
|
177 |
+ auto const size = vec3(right-left, top-bottom, far-near); |
|
178 |
+ auto const offs = vec3( -left, -bottom, +near); |
|
179 |
+ auto const s = vec3(2.0F, 2.0F, -2.0F) / size; |
|
180 |
+ auto const t = s * offs - 1.0F; |
|
181 |
+ auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s)); |
|
182 |
+ return mat4(mat4x3(S[0], S[1], S[2], t)); |
|
183 |
+} |
|
184 |
+ |
|
185 |
+//// frustum |
|
186 |
+mat4 frustum(float left, float right, float bottom, float top, float near, float far) |
|
187 |
+{ |
|
188 |
+ auto const z = vec4(0.0F, 0.0F, near+far, -1.0F); |
|
189 |
+ auto const w = vec4(0.0F, 0.0F, near*far, 0.0F); |
|
190 |
+ auto const S = mat4(near); |
|
191 |
+ return ortho(left, right, bottom, top, near, far) * mat4(S[0], S[1], z, w); |
|
192 |
+} |
|
193 |
+ |
|
194 |
+//// perspective |
|
195 |
+mat4 perspective(float fovy, float aspect, float near, float far) |
|
196 |
+{ |
|
197 |
+ auto const y = near * tan(0.5F * radians(fovy)); |
|
198 |
+ auto const x = y * aspect; |
|
199 |
+ return frustum(-x, +x, -y, +y, near, far); |
|
200 |
+} |
|
201 |
+ |
|
202 |
+//// lookat |
|
203 |
+mat4 lookat(vec3 position, vec3 target, vec3 up) |
|
204 |
+{ |
|
205 |
+ auto const z = normalize(position - target); |
|
206 |
+ auto const x = normalize(cross(up, z)); |
|
207 |
+ auto const y = cross(z, x); |
|
208 |
+ auto const R_inv = transpose(mat3(x, y, z)); |
|
209 |
+ auto const t_inv = -position; |
|
210 |
+ return mat4(mat4x3(R_inv[0], R_inv[1], R_inv[2], R_inv * t_inv)); |
|
211 |
+} |
|
212 |
+ |
|
153 | 213 |
/// Main |
154 | 214 |
|
155 | 215 |
int main(int argc, char const * argv[]) |
... | ... |
@@ -163,12 +223,24 @@ int main(int argc, char const * argv[]) |
163 | 223 |
auto const * path = argv[1]; // NOLINT |
164 | 224 |
//// Configure |
165 | 225 |
auto size = uvec2(640, 480); |
226 |
+ auto aspect = (float)size.x / (float)size.y; |
|
227 |
+ auto viewport = uvec4(uvec2(0), size); |
|
228 |
+ auto depth_range = vec2(0.0F, 1.0F); |
|
166 | 229 |
auto framebuffer = Framebuffer(size); |
167 |
- auto camera = Camera{ |
|
168 |
- 1.0F, // near |
|
169 |
- 100.0F, // far |
|
170 |
- size, // size |
|
171 |
- }; |
|
230 |
+ auto camera = Camera( |
|
231 |
+ lookat( // view |
|
232 |
+ {0.0F, 0.0F, 0.0F}, // position |
|
233 |
+ {0.0F, 0.0F, -1.0F}, // target |
|
234 |
+ {0.0F, 1.0F, 0.0F} // up |
|
235 |
+ ), |
|
236 |
+ perspective( // projection |
|
237 |
+ 90.0F, // fovy |
|
238 |
+ aspect, // aspect |
|
239 |
+ 1.0F, // near |
|
240 |
+ 100.0F // far |
|
241 |
+ ), |
|
242 |
+ window(viewport, depth_range) |
|
243 |
+ ); |
|
172 | 244 |
auto scene = Scene{ |
173 | 245 |
{ // shapes |
174 | 246 |
Sphere{{ 6.0F, -4.0F, -24.0F}, 12.0F}, |
... | ... |
@@ -180,9 +252,9 @@ int main(int argc, char const * argv[]) |
180 | 252 |
}, |
181 | 253 |
}; |
182 | 254 |
//// Render |
183 |
- framebuffer.render([&](vec4 const & frag_coord) |
|
255 |
+ framebuffer.render(viewport, depth_range, [&](vec4 const & frag_coord) |
|
184 | 256 |
{ |
185 |
- auto const ray = camera.ray(frag_coord); |
|
257 |
+ auto const ray = camera.ray(frag_coord, depth_range); |
|
186 | 258 |
auto const trace = scene.trace(ray); |
187 | 259 |
auto const rgb = vec3(1.0F / (1.0F + trace.distance)); |
188 | 260 |
auto const alpha = (bool)trace; |