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.
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"!
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.