Vor einigen Jahren hat das SQL.ru-Forum
beschlossen, Ray-Tracer-Implementierungen in verschiedenen Programmiersprachen
zu vergleichen . Leider kann meine Bewerbung da nicht teilnehmen Das Wort "PIXAR" wird nicht angezeigt, daher veröffentliche ich es hier.
FĂŒr die Reinheit des Experiments habe ich SQLite ohne Erweiterungen verwendet. Es stellte sich heraus, dass es dort nicht einmal eine SQRT-Funktion gibt.
WITH RECURSIVE numbers AS (SELECT 0 AS n UNION ALL SELECT n+1 FROM numbers WHERE n<89), pixels AS (SELECT rows.n as row, cols.n as col FROM numbers as rows CROSS JOIN numbers as cols WHERE rows.n > 4 AND rows.n < 38 AND cols.n > 9 AND cols.n < 89), rawRays AS (SELECT row, col, -0.9049 + col * 0.0065 + row * 0.0057 as x, -0.1487 + row * -0.0171 as y, 0.6713 + col * 0.0045 + row * -0.0081 as z FROM pixels), norms AS (SELECT row, col, x, y, z, (1 + x * x + y * y + z * z) / 2 as n FROM rawRays), rays AS (SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms), iters AS (SELECT row, col, 0 as it, 0 as v FROM rays UNION ALL SELECT rays.row, rays.col, it + 1 AS it, v + MAX(ABS(0.7+v*x) - 0.3, ABS(0.7+v*y) - 0.3, ABS(-1.1+v*z) - 0.3, -((0.7+v*x) * (0.7+v*x) + (0.7+v*y) * (0.7+v*y) + (-1.1+v*z) * (-1.1+v*z)) * 1.78 + 0.28) AS v FROM iters JOIN rays ON rays.row = iters.row AND rays.col = iters.col WHERE it < 15), lastIters AS (SELECT it0.row, it0.col, it0.v AS v0, it1.v AS v1, it2.v AS v2 FROM iters as it0 JOIN iters AS it1 ON it0.col = it1.col AND it0.row = it1.row JOIN iters AS it2 ON it0.col = it2.col AND it0.row = it2.row WHERE it0.it = 15 AND it1.it = 14 AND it2.it = 13), res AS (SELECT col, (v0 - v1) / (v1 - v2) as v FROM lastIters) SELECT group_concat( substr('$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ', round(1 + max(0, min(66, v * 67))), 1) || CASE WHEN col=88 THEN X'0A' ELSE '' END, '') FROM res;
Hier können Sie den WĂŒrfel drehenUnter kat Zeile fĂŒr Zeile Analyse der Anfrage. Wie ĂŒblich sind ausreichende Kenntnisse der Grundlagen von SQL und Schulmathematik ausreichend.
Haftungsausschluss: Ich bin weit von der Welt der Datenbank entfernt, daher werde ich rechtzeitig fĂŒr Kommentare in PM sein.Version fĂŒr Postgres (UPD: Dank Flags wurde es um eine GröĂenordnung schneller, UPD2: eine Reihe von Verbesserungen, jetzt betrĂ€gt die AusfĂŒhrungszeit 150 ms)Vielen Dank an
XareH fĂŒr die Optimierung der Anfrage.
SET ENABLE_NESTLOOP TO OFF; WITH RECURSIVE numbers AS (SELECT n FROM generate_series(0,89) gs(n) ), pixels AS (SELECT rows.n as row, cols.n as col FROM numbers as rows CROSS JOIN numbers as cols WHERE rows.n > 4 AND rows.n < 38 AND cols.n > 9 AND cols.n < 89), rawRays AS (SELECT row, col, -0.9049::double precision + col * 0.0065 ::double precision + row * 0.0057::double precision as x, -0.1487::double precision + row * -0.0171::double precision as y, 0.6713::double precision + col * 0.0045::double precision + row * -0.0081::double precision as z FROM pixels), norms AS (SELECT row, col, x, y, z, (1 + x * x + y * y + z * z) / 2 as n FROM rawRays), rays AS (SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms), iters AS (SELECT row, col, 0 as it, 0.0::double precision as v FROM rays UNION ALL SELECT rays.row, rays.col, it + 1 AS it, v + GREATEST(ABS(0.7 +v*x) - 0.3 , ABS(0.7 +v*y) - 0.3 , ABS(-1.1 +v*z) - 0.3 , -(0.28 + ((0.7 +v*x) * (0.7 +v*x) + (0.7 +v*y) * (0.7 +v*y) + (-1.1 +v*z) * (-1.1 +v*z)) / 0.28 ) / 2.0 + 0.42 ) AS v FROM iters JOIN rays ON rays.row = iters.row AND rays.col = iters.col WHERE it < 15), lastIters AS (SELECT it0.row, it0.col, it0.v AS v0, it1.v AS v1, it2.v AS v2 FROM iters as it0 JOIN iters AS it1 ON it0.col = it1.col AND it0.row = it1.row JOIN iters AS it2 ON it0.col = it2.col AND it0.row = it2.row WHERE it0.it = 15 AND it1.it = 14 AND it2.it = 13), res AS (SELECT row,col, (v0 - v1) / (v1 - v2) as v FROM lastIters) SELECT string_agg(substring('$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. '::text FROM round(1 + GREATEST(0, LEAST(66, v * 67)))::integer FOR 1) || CASE WHEN col=88 THEN E'\n' ELSE '' END, ''::text order by row,col) FROM res; SET ENABLE_NESTLOOP TO ON;
Um die Terminologie und das Prinzip des Algorithmus zu verstehen, wird empfohlen, den
Artikel ĂŒber Ray Marching fĂŒr Excel zu lesen.
Allgemeine Struktur
Liste der Staging-Tabellen:
numbers (n)
- enthÀlt Zahlen von 0 bis 89.pixels (row, col)
- enthĂ€lt die Zeilen- und Spaltennummer fĂŒr jedes "Pixel".rawRays (row, col, x, y, z)
- enthÀlt abnormale Strahlen von der Kamera zum Bildschirm .norms (row, col, x, y, z, n)
- enthÀlt die LÀnge der Strahlen.rays (row, col, x, y, z)
- enthÀlt normalisierte Strahlen von der Kamera zum Bildschirm .iters (row, col, it, v)
- enthÀlt Iterationen Ray Marching.lastIters (row, col, v0, v1, v2)
- enthĂ€lt die letzten drei Iterationen aus der vorherigen Tabelle fĂŒr jedes "Pixel".res (col, v)
- enthÀlt die "Helligkeit" der Pixel.
In Bezug darauf, wie sie voneinander abhÀngen, ist alles einfach: Jede nÀchste Tabelle verwendet nur die vorherige, und die letzte Abfrage verwendet nur die
res
Tabelle.
Alle Tabellen (auĂer
numbers
und
iters
) enthalten 81 x 29 Zeilen (eine fĂŒr jedes âPixelâ), die
row
und
col
indizieren ihre Koordinaten. Die
iters
Tabelle enthĂ€lt 81 x 29 x 15 Zeilen (eine fĂŒr jede Ray-Marching-Iteration fĂŒr jedes âPixelâ). Die Iterationsnummer befindet sich in der Spalte
it
.
Die letzte Abfrage erzeugt eine Tabelle mit einer Zeile und Spalten mit Text. Alle anderen Tabellen enthalten nur reelle Zahlen (die Spalten
row
,
col
und nicht negative Ganzzahlen).
Es stellt sich heraus, wenn wir die Hilfstabellen weglassen, eine sehr einfache Abfragestruktur:
WITH RECURSIVE numbers AS (SELECT ...), pixels AS (SELECT ...), rawRays AS (SELECT ...), normsSq AS (SELECT ...), norms AS (SELECT ...), rays AS (SELECT ...), iters AS (SELECT ...), lastIters AS (SELECT ...), res AS (SELECT ...) SELECT group_concat(substr('$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ', round(1 + max(0, min(66, v * 67))), 1) || CASE WHEN col=88 THEN X'0A' ELSE '' END, '') FROM res;
Rekursive Abfragen
Hier ist eine Standardmethode, um eine Tabelle mit Zahlen von 0 bis 89 zu erhalten:
WITH RECURSIVE numbers AS ( SELECT 0 AS n UNION ALL SELECT n+1 FROM numbers WHERE n<89 ) ...
- Rekursive Abfragen funktionieren nur im
WITH
Konstrukt. Beachten Sie, dass der Name der Tabelle in ihrer Definition verwendet wird. SELECT 0 as n
ist die Zeile, in der die rekursive Abfrage beginnt.UNION ALL
bedeutet, dass alle Zeilen, die als Ergebnis zurĂŒckgegeben werden, ohne zusĂ€tzliche ĂberprĂŒfungen zu einer Tabelle verkettet werden. Wenn Sie einfach UNION
schreiben, werden alle Duplikate gelöscht.SELECT n+1 FROM numbers WHERE n<80
. Eine wichtige Nuance dabei ist, dass die numbers
immer eine Zeile mit der vorherigen Nummer enthÀlt. Irgendwann wird die Bedingung in WHERE
abschneiden und die Abfrage wird nicht mehr ausgefĂŒhrt. Erst danach werden alle vorherigen TabellenzustĂ€nde durch die Operation UNION ALL
.
Extrahieren Sie die Quadratwurzel
Wir werden
die Heron-Methode (babylonische Methode) zur Berechnung der Wurzel verwenden. Nehmen wir an, wir wollen berechnen
sqrtS . Wir bauen eine Serie
x0,x1, ldots nach folgender Formel:
xn+1= fracxn+ fracSxn2
Die Logik der Methode ist sehr einfach:
sqrtS liegt immer dazwischen
x und
fracSx fĂŒr eine beliebige Anzahl
x . Daher ist es natĂŒrlich, die Mitte des Segments zwischen diesen Zahlen als AnnĂ€herung zu nehmen.
Geometrisch kann dies wie folgt dargestellt werden:
Jeder nÀchste Wert bringt die Wurzel nÀher und nÀher, in einem Schritt verringert sich der Fehler mindestens zweimal.
Anfangswert
x0 kann eine beliebige positive Zahl sein, zum Beispiel 1. Im Quake-Spiel wurde dafĂŒr die
magische Konstante 0x5f3759df verwendet (genauer gesagt, sie wurde fĂŒr die invertierte Quadratwurzel verwendet, dennoch kann eine Ă€hnliche Methode fĂŒr die ĂŒbliche Wurzel
erfunden werden ), aber leider gibt es kein SQL Zugriff auf die binÀre Darstellung von Gleitkommazahlen.
In diesem Artikel wird die Wurzel an zwei Stellen benötigt:
- Wenn Sie Vektoren normalisieren, die die Kamera verlassen: Das Marschieren von Strahlen hÀngt stark von den Entfernungen ab. Um sie zu verschieben, benötigen Sie einen Vektor der LÀnge 1.
- bei der Berechnung des Abstands zur Grenze einer Kugel, die aus einem Quadrat herausgeschnitten ist.
Im ersten Fall liegen die Werte in einem engen Bereich
[0,7,1,5] und eine anfÀngliche AnnÀherung von 1 ist perfekt. Im zweiten Fall, beim Sammeln von Statistiken zu Anrufen, erhielt ich den Durchschnittswert
0,28 was als Start genommen wurde.
Es stellte sich heraus, dass mit dem richtigen Anfangswert
eine Iteration ausreicht ! Das heiĂt, in unserem Fall wird die Wurzel durch eine lineare Funktion angenĂ€hert:
sqrt1(x)= frac1+x2
sqrt2(x)= frac0,28+ fracx0,282=0,14+1,78x
Berechnen Sie die Strahlen der Kamera
Die Aufgabe der ersten vier Tabellen besteht darin, jedes "Pixel" einem dreidimensionalen Vektor der LĂ€nge 1 zuzuordnen, der aus der Kamera kommt und durch den entsprechenden Punkt auf dem
Bildschirm lÀuft .
Zuerst mĂŒssen Sie eine Tabelle mit der gewĂŒnschten Struktur erhalten, dh mit Zellen, fĂŒr die die Zeilennummer und die Spaltennummer angegeben sind. Dazu wird das kartesische Produkt einer Reihe von Zahlen von 0 bis 89 genommen und leere Zeilen und Spalten daraus herausgeschnitten:
... pixels AS ( SELECT rows.n as row, cols.n as col FROM numbers as rows CROSS JOIN numbers as cols WHERE rows.n >= 5 AND rows.n < 38 AND cols.n >= 10 AND cols.n < 89 ), ...
Als nÀchstes finden wir die nicht normalisierten Vektoren. Im Allgemeinen haben sie eine lange Formel trigonometrischer Funktionen. Um die Anfrage nicht zu komplizieren, habe ich die Kamera repariert und die Koeffizienten berechnet:
... rawRays AS ( SELECT row, col, -0.9049 + col * 0.0065 + row * 0.0057 as x, -0.1487 + row * -0.0171 as y, 0.6713 + col * 0.0045 + row * -0.0081 as z FROM pixels ), ...
Danach mĂŒssen wir die (ungefĂ€hren) LĂ€ngen dieser Vektoren nach der Formel berechnen
sqrt1 ::
... norms AS ( SELECT row, col, x, y, z, (1 + x * x + y * y + z * z) / 2.0 AS n FROM rawRays ), ...
Es bleibt, die Koordinaten der Vektoren durch ihre LĂ€nge zu teilen, um Vektoren der LĂ€nge 1 zu erhalten:
... rays AS (SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms), ...
Iterationen strahlen marschieren
Es wird ein etwas komplexeres rekursives Abfragekonstrukt verwendet, das
JOIN
. Wir wollen 15 Iterationen des Ray-Marching-Algorithmus fĂŒr jedes Pixel durchfĂŒhren. Wenn wĂ€hrend der rekursiven Berechnung der
numbers
jedes Mal die Tabelle eine Zeile enthielt, die dann kombiniert wurden, enthalten die Zwischentabellen hier 81 x 29 Zeilen und werden 15 Mal berechnet.
Die gesamte dreidimensionale Geometrie ist in der Formel enthalten
SDF beginpmatrixxyz endpmatrix= max left(|x|â0,3,|y|â0,3,|z|â0,3,â left(sqrt2(x2+y2+z2)â0,42 rechts) rechts)
- Funktion max bedeutet Kreuzung
- |x|â0,3,|y|â0,3,|z|â0,3 Definieren Sie drei Paare von Halbebenen, die einen WĂŒrfel mit einer Seite bilden 0.3 cdot2
- â left(sqrt2(x2+y2+z2)â0,42 right) - der Ă€uĂere Teil der Kugel mit dem Radius 0,42 . Der Radius wird gröĂer als der sichtbare genommen, um die Ungenauigkeit der QuadratwurzelnĂ€herung auszugleichen.
Als nĂ€chstes mĂŒssen wir nur die Sequenz berechnen
0=v0,v1,v2 ldots,v15 fĂŒr jedes Pixel nach der Formel:
vn+1=vn+SDF left( beginpmatrixcamXcamYcamZ endpmatrix+vn beginpmatrixxyz endpmatrix rechts)
wo
x,y,z - Koordinaten des normalisierten Vektors. Da die Kamerakoordinaten mehrmals wiederholt werden, habe ich sie auf eine Dezimalstelle gerundet.
... iters AS ( SELECT row, col, 0 as it, 0 as v FROM rays UNION ALL SELECT rays.row, rays.col, it + 1 AS it, v + MAX( ABS(0.7+v*x) - 0.3, ABS(0.7+v*y) - 0.3, ABS(-1.1+v*z) - 0.3, -( (0.7+v*x) * (0.7+v*x) + (0.7+v*y) * (0.7+v*y) + (-1.1+v*z) * (-1.1+v*z) ) * 1.78 + 0.28 ) AS v FROM iters JOIN rays ON rays.row = iters.row AND rays.col = iters.col WHERE it < 15 ), ...
Holen Sie sich die IntensitÀt der "Pixel"
Hier verwenden wir dieselbe Formel wie in Excel, die die diffuse Komponente aus der Phong-Schattierung approximiert:
IntensitĂ€t= max links(0, min links(1, fracv15âv14v14âv13 rechts) rechts)
Um es zu berechnen, mĂŒssen Sie zuerst eine Tabelle mit den letzten drei Iterationen des Strahlenmarschierens erstellen:
... lastIters AS ( SELECT it0.row, it0.col, it0.v AS v0, it1.v AS v1, it2.v AS v2 FROM iters as it0 JOIN iters AS it1 ON it0.col = it1.col AND it0.row = it1.row JOIN iters AS it2 ON it0.col = it2.col AND it0.row = it2.row WHERE it0.it = 15 AND it1.it = 14 AND it2.it = 13 ), ...
Und in der Tat die Formel selbst (Operationen
max und
min wird in der endgĂŒltigen Anfrage angewendet):
... res AS (SELECT row, col, (v0 - v1) / (v1 - v2) as v FROM lastIters) ...
Generieren Sie ASCII-Kunst
... SELECT group_concat( substr( '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ', round(1 + max(0, min(66, v * 67))), 1 ) || CASE WHEN col=88 THEN X'0A' ELSE '' END, '') FROM res;
Die Aufgabe der letzten Abfrage besteht darin, eine Tabelle mit PixelintensitÀten in eine ASCII-Zeile umzuwandeln. Bei der Eingabe erhÀlt es nur die
res
Tabelle, die die Spalten
col
und
v
.
group_concat(s, delim)
ist eine Aggregationsfunktion, die den Ausdruck s
fĂŒr alle Zeichenfolgen verkettet und die Zeichenfolge delim
als Trennzeichen verwendet.CASE WHEN cond1 THEN val1 WHEN cond2 THEN val2 ... ELSE valN END
- Bedingte Konstruktion, analog zum ternÀren Operator.X'0A'
ist das Zeilenumbruchzeichen, das vor dem ersten Zeichen jeder Zeile eingefĂŒgt wird.||
- String-Verkettungsoperator.substr(s, start, count)
- eine Funktion, die ZĂ€hlzeichen der Zeichenfolge s
zurĂŒckgibt, beginnend mit dem Zeichen mit der Zahl start
. Die Zeichenindizierung kommt von einem.'$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. '
Ist eine Zeichenfolge, die den" Farbverlauf "von Schwarz ( $
) bis enthĂ€lt "WeiĂ" (Leerzeichen) in ASCII-Zeichen. Entnommen von der Website http://paulbourke.net/dataformats/asciiart/ .round(1 + max(0, min(66, v * 67)))
- konvertiert reelle Zahlen aus dem Intervall [0,1] in eine ganze Zahl im Bereich [1,67] das Zeichen mit der entsprechenden Nummer zu nehmen.