aboutsummaryrefslogtreecommitdiff
path: root/readme.md
blob: 6e0c622e5fde98487b32471af852eaceb57626e4 (plain)
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
# Individueel project

Doel: een clone van de Windows Media Player "Batterij" visualizer:

![voorbeeld](img/goal.png)

De batterij visualizer ondersteunt een aantal presets. Op de bovenstaande
afbeelding is de 'DrowningFlower' preset te zien.

Alle presets lijken te bestaan uit het volgende:

- een laag die patronen genereert
- een filter die (voor of na het patroon) het beeld op een unieke manier
  vervormt

Voor elk beeld wordt het nieuwe patroon boven op het vorige beeld getekend. Dit
zorgt voor een soort "visuele galm" die erg lijkt op bijv. een feedbacklus die
je met een analoge videocamera zou kunnen maken ([voorbeeld][september]).

## Logboek

Eerst heb ik geprobeerd de "[Hello Triangle][hellotriangle]" tutorial te maken
van <learnopengl.com>. Aan het einde van deze tutorial had ik een simpel
programma dat reageert op window resize events, en die een fragment shader
gebruikt om een enkele driehoek te tekenen in een effen kleur. Omdat het effect
dat ik probeer na te maken geen 3D nodig heeft, was ik van plan alles met
fragment shaders te implementeren.

Ik had de applicatie hierna nog aangepast om de driehoek schermvullend te
tekenen. Om dit te doen had ik al snel ondekt dat ik de z-coördinaat van
`gl_Position` uit mijn vertex shader op 0 kon zetten, omdat de xyz coördinaten
uiteindelijk door z gedeeld worden. Op mijn NVIDIA kaart zorgde dit zoals ik
verwachtte voor een schermvullende driehoek (die oneindig groot was omdat er
gedeeld wordt door 0), maar op Intel zorgde dit voor een leeg scherm. Ik heb
uiteindelijk de z-coördinaat op 0.001 gezet, en ben hierna direct bewust
geworden van de verschillen tussen graphics drivers.

Hierna had ik de fragment shader nog wat aangepast om via uniforms de huidige
tijd en schermgrotte binnen te krijgen. De schermgrotte gebruikte ik om een
schermvullende kleurovergang te maken (op het groene en blauwe kleurkanaal), en
de tijd waarde om een ruissignaal te maken (op het rode kleurkanaal):

![](img/hello-fragment.png)

Toen dit allemaal werkte vond ik het een goed moment om mijn tooling wat te
verbeteren voor het schrijven en compileren van GLSL. Ik heb gekozen om de
`glslc` 'compiler' te gebruiken, die GLSL omzet naar [SPIR-V][spirv]. Dit heb
ik gedaan zodat ik GLSL compilatiewaarschuwingen krijg tijdens het bouwen van
mijn complete applicatie inplaats van tijdens runtime, en zodat ik de C
preprocessor kan gebruiken tijdens compileertijd, waarmee ik geen broncode hoef
te bundelen met mijn applicatie. De compiler zelf werkt prima op zowel mijn
desktop PC als mijn laptop, maar ik liep hier gelijk weer tegen verschillen
tussen de Intel en NVIDIA graphics drivers aan.

Ik heb er een uitgebereide [vraag op StackOverflow][so-spirv-reflection] over
gesteld, en ben gewezen op het feit dat de SPIR-V specificatie geen eis stelt
dat drivers die SPIR-V programma's laden reflectie moeten ondersteunen. Dit zou
betekenen dat ik de uniform locaties van mijn fragment shader zou moeten
hard-coden, of toch GLSL gebruiken zodat ik de uniform locaties kan opvragen
via hun naam als string.

Ik heb ook het build systeem iets uitgebereid om de SPIR-V binaries mee te
linken met linker scripts en gegenereerde C headers om de data door te geven
aan OpenGL functies. Dit was niet bijzonder spannend.

Om verder te gaan met het maken van het effect dat in de eerste afbeelding in
dit document te zien is, heb ik eerst geprobeerd de spiraalvorm in
[Desmos][desmos-braids] na te maken. Dit was het makkelijkst te doen met een
parametrische functie, wat een duur wiskunde woord is voor een functie die (in
dit geval) een 2D punt geeft voor een willekeurige parameter (t). Deze functie
bestaat uit het dynamische duo, de sinus en cosinus, die samen een cirkel
maken. Bij deze cirkelvorm tel ik een kleinere 'snellere' cirkel op, en deze
functie heeft parameters voor het aantal wikkelingen, en de grootte van de
wikkelingen:

[![](img/desmos-braids.png)][desmos-braids]

Deze functie zou ontzettend snel en eenvoudig zijn om te implementeren op de
CPU. Omdat de functie periodiek is kun je met alleen een `for`-lus genoeg
punten uitrekenen om het te laten lijken alsof je een ononderbroken kromme
tekent. Helaas moet zoveel mogelijk in dit project met OpenGL, dus heb ik deze
functie als fragment shader geïmplementeerd. Dit is ontzettend inefficiënt,
omdat je voor elke pixel de parameter t probeert terug te rekenen, inplaats van
andersom. Hierdoor moet de functie een paar duizend keer uitgerekend worden per
pixel. Helaas valt mijn visualizer hiermee buiten de duurzaamheidsdoelen van de
vakbeschrijving.

![](img/toy-braids.png)

TODO:
- waarom de framebuffer
- hoe framebuffer
- hoe kleur??

[september]: https://www.youtube.com/watch?v=Gs069dndIYk
[hellotriangle]: https://learnopengl.com/Getting-started/Hello-Triangle
[spirv]: https://www.khronos.org/opengl/wiki/SPIR-V
[so-spirv-reflection]: https://stackoverflow.com/a/78500726/5006337
[desmos-braids]: https://www.desmos.com/calculator/yh3tuebwp8