@@ -19,18 +19,65 @@ above , we'll need to take care of pretty much everything.
19
19
Dots, discs, circles
20
20
-------------------------------------------------------------------------------
21
21
22
+ Raw points
23
+ ++++++++++
24
+
25
+
26
+ .. figure :: images/chapter-07/points.png
27
+ :figwidth: 30%
28
+ :figclass: right
29
+
30
+ Figure
31
+
32
+ "Point" as drawn by OpenGL
33
+ (see `points.py <code/chapter-07/points.py >`_).
34
+
22
35
The most straightforward way to display points is to use the `gl.GL_POINTS `
23
- primitive that display a quad that is always facing the camera
24
- (i.e. billboard). This is very convenient because a mathematical point has no
25
- dimension, even though we'll use this primite to draw discs and circles as
26
- well. The size of the quad must be specified within the vertex shader using the
27
- `gl_PointSize ` variable (note that the size is expressed in pixels). As it has
28
- been explained in the previous chapter, the size of the quad must be slighlty
29
- larger than the actual diameter of the point because we need some extra space
30
- for the antialias area. Considering a point with a radius `r `, the size of the
31
- quad is thus `2+ceil(2*r) ` if we consider using 1 pixel for the antalias
32
- area. Finally, considering a point centered at `center ` with radius `radius `,
33
- our vertex shader reads:
36
+ primitive that displays a quad that is always facing the camera
37
+ (i.e. billboard). This is very convenient because a mathematical point has no
38
+ dimension, even though we'll use this primitive to draw discs and circles in
39
+ the next section. The size of the quad must be specified within the vertex
40
+ shader using the `gl_PointSize ` variable (note that the size is expressed in
41
+ pixels). As shown on the figure, the result is quite ugly.
42
+
43
+ .. code :: python
44
+
45
+ import numpy as np
46
+ from glumpy import app, gloo, gl
47
+
48
+ vertex = """
49
+ attribute vec2 position;
50
+ void main() {
51
+ gl_PointSize = 5.0;
52
+ gl_Position = vec4(position, 0.0, 1.0);
53
+ } """
54
+
55
+ fragment = """
56
+ void main() {
57
+ gl_FragColor = vec4(vec3(0.0), 1.0);
58
+ } """
59
+
60
+ window = app.Window(512 , 512 , color = (1 ,1 ,1 ,1 ))
61
+ points = gloo.Program(vertex, fragment, count = 1000 )
62
+ points[" position" ] = np.random.uniform(- 1 ,1 ,(len (points),2 ))
63
+
64
+ @window.event
65
+ def on_draw (dt ):
66
+ window.clear()
67
+ points.draw(gl.GL_POINTS )
68
+
69
+ app.run()
70
+
71
+
72
+ Antialiased points
73
+ ++++++++++++++++++
74
+
75
+ For drawing antialiased point, the size of the quad must be slighlty larger
76
+ than the actual diameter of the point because we need some extra space for the
77
+ antialias area. Considering a point with a radius `r `, the size of the quad is
78
+ thus `2+ceil(2*r) ` if we consider using 1 pixel for the antalias area. Finally,
79
+ considering a point centered at `center ` with radius `radius `, our vertex
80
+ shader reads (see also previous chapter on signed distance field):
34
81
35
82
.. code :: glsl
36
83
@@ -147,10 +194,84 @@ we simply have to get the absolute distance instead of the signed distance.
147
194
}
148
195
149
196
197
+ Ellipses
198
+ -------------------------------------------------------------------------------
199
+
200
+
201
+ .. figure :: movies/chapter-07/ellipses.mp4
202
+ :loop:
203
+ :autoplay:
204
+ :controls:
205
+ :figwidth: 30%
206
+ :figclass: right
207
+
208
+ Figure
209
+
210
+ Perfectly antialiases ellipse made of two triangles
211
+ (`ellipses.py <code/chapter-07/ellipses.py >`_)
212
+
213
+
214
+ Rendering ellipses is harder than it seems because, as we've explained in a
215
+ previous chapter, computing the distance from an arbitrary point to an ellipse
216
+ is surprinsingly difficult if you compare it to the distance to a circle. The
217
+ second difficulty for us is the fact that an ellipse can be very "flat" and if
218
+ we use the gl.GL_POINTS primitive, a lot of useless fragment will be
219
+ generated. This is the reason why we need to compute the bounding box
220
+ (including thickness and antialias area) and use two triangles to actually
221
+ display the ellipse. Last difficulty is that we cannot take advantage of the
222
+ `gl_FragCoord ` but we can now take advantage of the four vertices to have local
223
+ coordinate interpolation in the fragment shader.
224
+
225
+ .. code :: glsl
226
+
227
+ uniform vec2 resolution;
228
+ uniform float theta;
229
+ attribute vec2 position;
230
+ attribute float angle;
231
+ varying vec2 v_position;
232
+ void main() {
233
+ v_position = position;
234
+ vec2 p = position;
235
+ p = vec2(p.x*cos(angle+theta) - p.y*sin(angle+theta),
236
+ p.y*cos(angle+theta) + p.x*sin(angle+theta));
237
+ p = p + resolution/2.0;
238
+ gl_Position = vec4(2.0*p/resolution-1.0, 0.0, 1.0);
239
+ }
240
+
241
+ Note that in the vertex shader above, we pass the non-rotated coordinates to
242
+ the fragment shader. It makes things much simpler in the fragment shader that
243
+ reads:
244
+
245
+ .. code :: glsl
246
+
247
+ float SDF_fake_ellipse(vec2 p, vec2 size) {
248
+ float a = 1.0;
249
+ float b = size.x/size.y;
250
+ float r = 0.5*max(size.x,size.y);
251
+ float f = length(p*vec2(a,b));
252
+ return f*(f-r)/length(p*vec2(a*a,b*b));
253
+ }
254
+
255
+ uniform vec2 size;
256
+ varying vec2 v_position;
257
+ void main() {
258
+ float d = SDF_fake_ellipse(v_position, size) + 1.0;
259
+ float alpha;
260
+ if (abs(d) < 1.0) alpha = exp(-d*d)/ 4.0;
261
+ else if (d < 0.0) alpha = 1.0/16.0;
262
+ else alpha = exp(-d*d)/16.0;
263
+ gl_FragColor = vec4(vec3(0.0), alpha);
264
+ }
265
+
266
+
267
+
150
268
151
269
Spheres
152
270
-------------------------------------------------------------------------------
153
271
272
+ Flat sphere
273
+ +++++++++++
274
+
154
275
.. figure :: images/chapter-07/sphere.png
155
276
:figwidth: 30%
156
277
:figclass: right
@@ -248,6 +369,9 @@ below, this is quite easy and the result is flawless.
248
369
249
370
----
250
371
372
+ True sphere
373
+ +++++++++++
374
+
251
375
.. figure :: images/chapter-07/spheres-no-depth.png
252
376
:figwidth: 30%
253
377
:figclass: right
@@ -256,8 +380,8 @@ below, this is quite easy and the result is flawless.
256
380
257
381
A bunch of fake spheres.
258
382
259
- We can now use this technique to display several "spheres" having different
260
- sizes and positions as shown on the figure on the right. This can be used to
383
+ We can use this technique to display several "spheres" having different sizes
384
+ and positions as shown on the figure on the right. This can be used to
261
385
represent molecules for examples. Howewer, we have a problem with sphere
262
386
intersecting each other. If you look closely the figure, you might have notices
263
387
that no sphere intersect any sphere. This is due to the depth testing of the
@@ -295,6 +419,8 @@ the `gl_FragDepth` variable (that must be between 0 and 1):
295
419
float specular = pow(diffuse, 24.0);
296
420
gl_FragColor = vec4(max(diffuse*color, specular*vec3(1.0)), 1.0);
297
421
}
422
+
423
+ You can see on the figures that the spheres now intersect each other correctly.
298
424
299
425
300
426
Exercises
@@ -318,6 +444,21 @@ disc...
318
444
Solution: `spiral.py <code/chapter-07/spiral.py >`_
319
445
320
446
447
+ ----
448
+
449
+ .. figure :: movies/chapter-07/triangles.mp4
450
+ :loop:
451
+ :autoplay:
452
+ :controls:
453
+ :figwidth: 30%
454
+ :figclass: right
455
+
456
+ Antialiased triangles
457
+
458
+ Try to adapt the code from the ellipses section to remake the animation on the
459
+ right. Be careful with the computation of the bouding box.
460
+
461
+ Solution: `triangles.py <code/chapter-07/triangles.py >`_
321
462
322
463
----
323
464
@@ -330,33 +471,9 @@ Solution: `spiral.py <code/chapter-07/spiral.py>`_
330
471
A voronoi diagram computed on the GPU.
331
472
332
473
333
- We've seen when rendering sphere that the individual depth of eahc fragment can
474
+ We've seen when rendering sphere that the individual depth of each fragment can
334
475
be controled withing the fragment shader and we computed this depth by taking
335
476
the distance to the center of each disc/sphere. The goal of this exercise is
336
- thus to adapt this method to render a Voronoi diagram as shonw on the right.
477
+ thus to adapt this method to render a Voronoi diagram as shown on the right.
337
478
338
479
Solution: `voronoi.py <code/chapter-07/voronoi.py >`_
339
-
340
- ..
341
- .. figure:: movies/chapter-07/triangles.mp4
342
- :loop:
343
- :autoplay:
344
- :controls:
345
- :figwidth: 30%
346
- :figclass: right
347
-
348
- Figure
349
-
350
-
351
-
352
- .. figure :: movies/chapter-07/ellipses.mp4
353
- :loop:
354
- :autoplay:
355
- :controls:
356
- :figwidth: 30%
357
- :figclass: right
358
-
359
- Figure
360
-
361
-
362
-
0 commit comments