Schräg: eine rotierende Kamera in einem einfachen CPU-basierten Voxel-Raycaster / Raytracer


7

TL; DR - In meinem ersten einfachen Software-Voxel-Raycaster kann ich keine Kameradrehungen zum Laufen bringen, trotz scheinbar korrekter Matrizen. Das Ergebnis ist verzerrt: Wie ein flaches Rendering, korrekt gedreht, jedoch verzerrt und ohne Tiefe. (Während die Achse ausgerichtet ist, dh nicht gedreht, sind Tiefe und Parallaxe wie erwartet.)

Ich versuche, einen einfachen Voxel-Raycaster als Lernübung zu schreiben. Dies ist vorerst rein CPU-basiert, bis ich herausgefunden habe, wie die Dinge genau funktionieren - jetzt wird OpenGL nur (ab) verwendet, um die generierte Bitmap so oft wie möglich auf den Bildschirm zu bringen.

Jetzt bin ich an einem Punkt angelangt, an dem sich eine Perspektivprojektionskamera durch die Welt bewegen kann und ich (meistens abzüglich einiger zu untersuchender Artefakte) perspektivisch korrekte dreidimensionale Ansichten der "Welt" rendern kann, die im Grunde genommen aber leer ist enthält einen Voxelwürfel des Stanford Bunny.

Ich habe also eine Kamera, mit der ich mich auf und ab bewegen, nach links und rechts strecken und "vorwärts / rückwärts gehen" kann - alle bisher achsenausgerichtet, keine Kameradrehungen. Hierin liegt mein Problem.

Screenshot Nr. 1: Richtige Tiefe, wenn die Kamera noch streng achsenausgerichtet ist, d. H. nicht gedreht.

Geben Sie hier die Bildbeschreibung ein

Jetzt habe ich seit ein paar Tagen versucht, die Rotation zum Laufen zu bringen. Die grundlegende Logik und Theorie hinter Matrizen und 3D-Rotationen ist mir theoretisch sehr klar. Ich habe jedoch immer nur ein "2,5-Rendering" erzielt, wenn sich die Kamera dreht ... fischaugenartig, ähnlich wie in Google Streetview: Obwohl ich eine volumetrische Weltdarstellung habe, scheint es - egal was ich versuche - so, als würde ich es zuerst tun Erstellen Sie ein Rendering aus der "Vorderansicht" und drehen Sie das flache Rendering entsprechend der Kameradrehung. Unnötig zu erwähnen, dass rotierende Strahlen nicht besonders notwendig und fehleranfällig sind.

In meinem letzten Setup mit dem einfachsten Raycast-Algorithmus für Position und Richtung von Raycasts erzeugt meine Rotation immer noch die gleichen fischaugenartigen, flach gerenderten Looks:

Screenshot Nr. 2: Kamera "um 39 Grad nach rechts gedreht" - beachten Sie, dass die blau schattierte linke Seite des Würfels von Bildschirm Nr. 2 in dieser Drehung nicht sichtbar ist, aber jetzt "sollte es wirklich"!

Geben Sie hier die Bildbeschreibung ein

Jetzt ist mir das natürlich bewusst: In einem einfachen achsenausgerichteten No-Rotation-Setup wie am Anfang durchläuft der Strahl einfach in kleinen Schritten die positive Z-Richtung und divergiert nach links oder rechts und oben oder nur unten, abhängig von der Pixelposition und der Projektionsmatrix. Wenn ich "die Kamera nach rechts oder links drehe" - dh ich drehe sie um die Y-Achse - sollten genau diese Schritte einfach durch die richtige Rotationsmatrix transformiert werden, oder? Beim Vorwärtsfahren wird der Z-Schritt also umso kleiner, je mehr sich der Nocken dreht, was durch eine "Erhöhung" des X-Schritts ausgeglichen wird. Für die pixelpositionsbasierte horizontale + vertikale Divergenz müssen jedoch zunehmende Anteile des x-Schritts zum z-Schritt "hinzugefügt" werden. Irgendwie keine meiner vielen Matrizen, mit denen ich experimentiert habe,

Hier ist mein grundlegender Per-Ray-Pre-Traversal-Algorithmus - Syntax in Go, aber nehmen Sie ihn als Pseudocode:

  • fx und fy : Pixelpositionen x und y
  • rayPos : vec3 für die Strahlstartposition im Weltraum (berechnet wie unten)
  • rayDir : vec3 für die xyz-Schritte, die während des Ray-Traversal in jedem Schritt zu rayPos hinzugefügt werden sollen
  • rayStep : ein temporäres vec3
  • camPos : vec3 für die Kameraposition im Weltraum
  • camRad : vec3 für Kameradrehung im Bogenmaß
  • pmat : typische perspektivische Projektionsmatrix

Der Algorithmus / Pseudocode:

// 1: rayPos is for now "this pixel, as a vector on the view plane in 3d, at The Origin"
rayPos.X, rayPos.Y, rayPos.Z = ((fx / width) - 0.5), ((fy / height) - 0.5), 0

// 2: rotate around Y axis depending on cam rotation. No prob since view plane still at Origin 0,0,0
rayPos.MultMat(num.NewDmat4RotationY(camRad.Y))

// 3: a temp vec3. planeDist is -0.15 or some such — fov-based dist of view plane from eye and also the non-normalized, "in axis-aligned world" traversal step size "forward into the screen"
rayStep.X, rayStep.Y, rayStep.Z = 0, 0, planeDist

// 4: rotate this too — 0,zstep should become some meaningful xzstep,xzstep
rayStep.MultMat(num.NewDmat4RotationY(CamRad.Y))

// set up direction vector from still-origin-based-ray-position-off-rotated-view-plane plus rotated-zstep-vector
rayDir.X, rayDir.Y, rayDir.Z = -rayPos.X - me.rayStep.X, -rayPos.Y, rayPos.Z + rayStep.Z

// perspective projection
rayDir.Normalize()
rayDir.MultMat(pmat)

// before traversal, the ray starting position has to be transformed from origin-relative to campos-relative
rayPos.Add(camPos)

Ich überspringe die Traversal- und Sampling-Teile - gemäß Screenshot Nr. 1 sind diese "im Grunde genommen meistens korrekt" (wenn auch nicht hübsch) -, wenn sie achsenausgerichtet / nicht gedreht sind.


Interessant ... Ich habe den Eindruck, dass Sie einen Screenshot zwischen # 1 und # 2 weggelassen haben, ist das so?
Benutzer nicht gefunden

Antworten:


2

Dein Pseudocode in Go- Sprache ist mir ein großes Rätsel, also werde ich versuchen, meinen Ansatz zu beschreiben :)

Zunächst benötigen Sie keine perspektivisch-projektive Matrix. Alles, was Sie benötigen, um einen aktuellen Ansichtsstrahl zu erstellen, ist:

  • Kameraposition
  • Kamera auf Vektor
  • Kamera Blick auf Vektor
  • aktuelles Pixel auf dem Bildschirm

Nehmen wir an, dass unsere kostenlose Kamera richtig funktioniert. Jetzt nehmen wir unsere Vektoren und führen den Gram-Schmidt-Prozess durch . Dies gibt uns eine orthogonale Kamerabasis. In GLSL sieht dieser Schritt ungefähr so ​​aus:

vec3 cameraW = normalize(cameraPosition - cameraLookAt);
vec3 cameraU = normalize(cross(cameraUp, cameraW));
vec3 cameraV = cross(cameraW, cameraU);

Ok, wir haben Basis. Diese Basis ist für alle Pixel auf dem Bildschirm gleich. Noch mehr ist es unveränderlich, bis wir die Kamera bewegen oder drehen. Um den Ansichtsstrahl für das aktuelle Pixel zu berechnen, muss Folgendes ausgeführt werden:

vec3 rayOrigin = cameraPosition;
vec3 rayDirection = normalize(p.x * cameraU + p.y * cameraV - cameraViewPlaneDistance * cameraW);

Dabei pist die Koordinate Ihres aktuellen Pixels.

Danach haben Sie Sichtstrahl. Es ist höchste Zeit, Raytracing oder Raymarching zu machen.

Kehren wir zur kostenlosen Kamera zurück. Um sich vorwärts oder rückwärts zu bewegen, verschieben wir einfach die Kameraposition in Richtung des Zielvektors. Um sich nach links oder rechts zu bewegen, extrahieren wir den rechten Vektor aus der Kameradrehungsmatrix und fügen ihn den Ziel- und Positionsvektoren hinzu.

Zum Drehen der Kamera verwenden wir eine Kameradrehungsmatrix. Die erste Kamerarotationsmatrix ist die Identitätsmatrix. Wir haben drei Schwimmer: Gieren, Pitch and Roll. Wenn der Benutzer die Maus bewegt, sammelt dieser Schwimmer Drehwinkel. Beim Update müssen wir diese Rotationen auf unsere Kameradrehungsmatrix anwenden. Nehmen wir ein Gieren, das Rotationen um die obere Achse entspricht. Zuerst extrahieren wir den Vektor aus der Rotationsmatrix und konstruieren dann eine neue Rotationsmatrix. Immerhin multiplizieren wir die Kameradrehungsmatrix und die neue. Hier ein C ++ - Code, der die glm- Bibliothek verwendet:

vec3 up(cameraRotation[0][1], cameraRotation[1][1], cameraRotation[2][1]);
cameraRotation *= glm::rotate(mat4(1.0), yaw, up);

Hoffe das hilft..

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.