Moddot
Interessante effetto originariamente realizzato con un pixel shader scritto in GLSL
utilizzando le funzioni mod
e dot
.
Non essendo riuscito a trovare informazioni a riguardo ho pensato di battezzarlo moddot.
L'effetto è dovuto all'interferenza che si crea tra la griglia dei pixel dell'immagine
e una funzione dipendente dalla distanza dei pixel dal centro dell'immagine.
La cosa più simile che mi viene in mente sono i vari tipi di effetti moiré
(forse queste figure di interferenza possono effettivamente essere incluse in questa categoria).
In termini matematici data una costante $k \in \mathbb{R}$ si considera una funzione $f_k$ da $\mathbb{Z}^2$ in $\left[ 0 , 1 \right)$ che associa ad ogni coppia $\left( i , j \right) \in \mathbb{Z}^2$ il valore $\left( k \, v \right)^2 - \left\lfloor \left( k \, v \right)^2 \right\rfloor$ dove $\left( k \, v \right)^2$ indica il prodotto scalare $\left( k \, v \right) \cdot \left( k \, v \right) = k^2 \left( {v_x}^2 + {v_y}^2 \right)$ dove il vettore $v \in \mathbb{R}^2$ tale che $v_x = i , v_y = j$.
Un altro approccio (del tutto equivalente a livello numerico) è considerare, invece di una famiglia di funzioni $f_k$, un'unica funzione $f: \mathbb{R}^2 \to \left[ 0 , 1 \right)$ data da $v \mapsto v^2 - \left\lfloor v^2 \right\rfloor$ e considerare l'effetto come un artefatto dovuto al sottocampionamento di tale funzione. Moltiplicare ogni coppia di $\mathbb{Z}^2$ per un fattore $k \in \mathbb{R}$ equivale infatti a considerare la funzione $f$ con dominio $\mathbb{R}^2$ campionandola con frequenza $1 / k$.
Forse questo approccio è migliore in quanto permette di utilizzare tecniche proprie della teoria dei segnali (quali?). Potrebbe essere interessante iniziare a valutare il sottocampionamento anche solo per la funzione $x \mapsto x^2 - \left\lfloor x^2 \right\rfloor$ con $x \in \mathbb{R}$ (vedi grafico nella sezione Origine dell'effetto).
Implementazione GPU
La funzione
float mod(float a, float n)
restituisce a
modulo n
(nient'altro che una versione a virgola mobile del modulo dell'aritmetica modulare),
mentre la funzione
float dot(vec2 v, vec2 w)
restituisce il prodotto scalare tra v
e w
,
cioè
dot(v, w) = v.x * w.x + v.y * w.y
dot
si riferisce al termine inglese "dot product" con cui si indica il prodotto scalare nei paesi anglosassoni,
che a sua volta fa riferimento alla notazione standard $v \cdot w$ per indicare il prodotto scalare tra $v$ e $w$.
La luminosità di ogni pixel, da nero a bianco, è dato da mod(dot(v, v), 1.0)
,
che restituisce un numero tra 0.0
e 1.0
.
Il modulo $1$ di un numero reale è equivalente al prenderne la parte frazionaria
(in GLSL esiste anche la funzione fract
, ma fractdot non suona bene tanto quanto moddot).
dot(v, v)
restituisce il quadrato della lunghezza di v
.
L'origine dello spazio è al centro dell'immagine e le coordinate del vettore vengono riscalate di un fattore k
per ottenere le differenti figure.
void main()
{
const float k = 1.698518;
vec2 v = gl_FragCoord.xy - resolution.xy / 2.0;
v *= k;
float moddot = mod(dot(v, v), 1.0);
vec3 color = vec3(moddot);
gl_FragColor = vec4(color, 1.0);
}
Implementazione CPU
Anche se originariamente derivato da uno shader, il calcolo per generare i moddot non è molto pesante e si possono facilmente generare immagini in tempo reale (nell'ordine di diverse decine al secondo) anche senza ricorrere a tecnologie particolari come gli shader programmabili delle schede video. Attualmente ad esempio sto usando un programma che ho scritto in C# con cui riesco facilmente a generare e salvare immagini singole e le GIF mostrate in questa pagina.
In un linguaggio C-style (come C, C++, C#, eccetera) il codice sarà:
int i0 = width / 2;
int j0 = height / 2;
for (int i = 0; i < width; ++i)
{
for (int j = 0; j < height; ++j)
{
double x = k * (i - i0);
double y = k * (j - j0);
double dot = x * x + y * y;
double mod = dot - (int)dot;
// set pixel color
}
}
Periodicità
Cambiando il valore di $k$ c'è uno schema che si ripete in continuazione. Un pattern emerge e inizia a riempire parti di piano circolari sempre più grandi. Quando sembra ricoprire l'intero piano, cambia, e progressivamente si riduce in cerchi sempre più piccoli. Per fare un paio di esempi tra i tanti: intorno a $3.9250796$ e a $10.80985385$. Cos'hanno di particolare questi valori critici e tutti gli altri in cui avviene questo fenomeno?
Origine dell'effetto
Le immagini in questa pagina hanno risoluzione $800 \times 450$, ma sono state ingrandite di un fattore $2$ rispetto alle originali, quindi sono in realtà griglie di $400 \times 225$ punti. L'ascissa varia quindi da $-112$ a $112$. Ponendo $k = 1 / 56$ si ottiene al centro un'area circolare che occupa metà dell'immagine in senso verticale.
All'interno di quest'area c'è un gradiente dal nero al bianco con dipendenza quadratica rispetto alla distanza dal centro. Questo semplicemente perché prima che il modulo del vettore $k \, v$ raggiunga il valore $1$ la funzione $f_k$ si riduce a $\left( k \, v \right)^2$. Al di fuori di questo cerchio si avranno delle onde concentriche sempre più piccole, visto che il quadrato della distanza cresce sempre più rapidamente mano a mano che aumenta la distanza.
Aumentare il valore di $k$ ha l'effetto di rimpicciolire sempre di più questa semplice immagine. Ad un certo punto la successione di onde concentriche e la griglia di pixel inizieranno a generare figure di interferenza. Inizialmente (da $k = 0.02$ a $k = 0.2$ circa) non si ottengono figure particolarmente complesse, in sostanza cerchi ripetuti in modo periodico in tutte le direzioni.
Aumentando ancora il valore di $k$ si passa da questi primi pattern banali ad altri più interessanti. Superato $k = 0.5$ la dimensione dell'area circolare iniziale diventa uguale alla dimensione di un pixel. A questo punto le figure che si ottengono si fanno davvero interessanti e diventa più difficile ricondurle all'immagine di partenza.