Grafik machen wie 1993
(staniks.github.io)- Catlantean 3D ist ein Side-Project, das einen vollwertigen Ego-Shooter mit den Beschränkungen von PC-Spielen der frühen 1990er bauen will, und zielt auf Rendering mit 320x240 Auflösung und einer 256-Farben-Palette
- Da der Renderer nur mit Palette-Indizes arbeitet, wird für distanzbasierte Abdunklung eine colormap mit 32 Stufen vorab berechnet; zur Laufzeit wird dann per O(1)-Lookup die dunklere Farbe gewählt
- Die Asset-Erstellung teilt sich in auf Blender basierende vorgerenderte Sprites, handgezeichnete Sprites und Texturen sowie prozedurale Texturen, die per Python-Skript erzeugt werden
- Ein handgezeichnetes HUD und Regeln für die pixelgenaue Skalierung sind zentrale Beschränkungen, um bei niedriger Auflösung Schärfe und Lesbarkeit zu erhalten; 1 Welt-Einheit entspricht 64 Pixeln
- Statt Tiled wird ein eigener Map-Editor gebaut, und nach Release soll derselbe Editor auch Spielern bereitgestellt werden; der Spielquellcode soll als Open Source auf GitHub veröffentlicht werden
Projektziele und Beschränkungen
- Catlantean 3D wird seit über einem Jahr langsam in der Freizeit als Side-Project entwickelt und zielt auf einen Steam-Release im nächsten Jahr
- Das Ziel ist, einen vollwertigen und veröffentlichungsreifen Ego-Shooter mit Techniken zu bauen, wie sie Anfang der 1990er verbreitet waren
- Moderne Compiler und Plattform-Abstraktionsschichten sind erlaubt, aber die Abstraktionsschicht ist auf einen Framebuffer zum Schreiben von Pixeln, Tastatur- und Mauseingaben, einen Audio-Buffer zum Schreiben von Samples sowie Dateisystem-I/O begrenzt
- Das Spiel inklusive aller Assets muss vollständig von Grund auf erstellt werden, und auch Rendering sowie Sound-Mixing müssen komplett selbst implementiert werden
- Die Zielauflösung ist 320x240, und jedes Pixel auf dem Bildschirm darf nur eine von 256 Farben verwenden
- Die Spiellogik nutzt Fixed Point, um deterministisches Verhalten zu garantieren, während das Rendering Floating Point verwendet, weil Determinismus dort weniger wichtig ist
- Das Ergebnis soll kein Tech-Demo sein, sondern ein ausgereiftes Spiel, das sich wirklich gut spielen lässt; AI-generierte Inhalte werden nicht verwendet
- Alle gezeigten Arbeiten sind Work in Progress und können sich noch stark verändern
Paletten-Rendering
-
VGA-Grafik
- Der Mode 13h der VGA-Hardware war ein 320x200-256-Farben-Grafikmodus und ein berühmter Modus, der eine ganze Generation von PC-Spielen geprägt hat
- Aus Programmierersicht bot Mode 13h einen linearen Framebuffer, in dem jedes Pixel als 1 Byte dargestellt wurde, also als Index in einer 256-Farben-Palette
- Um ein Pixel zu zeichnen, musste man nur 1 Byte an eine bestimmte Adresse schreiben, ohne sich mit Konzepten wie Shadern oder VRAM zu befassen
- Moderne Game-Assets können Millionen von Farben in einem Bild verwenden, aber unter einer 256-Farben-Beschränkung muss jede Farbwahl sorgfältig und bewusst getroffen werden
- Spiele wie Doom und Duke Nukem werden als Beispiele dafür genannt, wie technische Grenzen zu Schärfe und Klarheit in der Grafik führen konnten
- Catlantean 3D will dieses Gefühl nachbilden, wählt aber statt 320x200 ein 320x240, das näher an VGA Mode-X liegt
- Wird 320x200 auf einem 4:3-Display angezeigt, entstehen nichtquadratische Pixel; das wäre authentischer, wird hier aber aus Geschmacksgründen vermieden
-
Palette
- Die Palette beginnt als 768 Bytes und wurde durch viel Trial-and-Error und Iteration ausgewählt
- In der Palette sind jeweils eine grelle Pink-Farbe für Transparenz, ein reines Weiß und ein reines Schwarz reserviert
- Für die Darstellung von Blut wurden viele Rottöne benötigt, dazu Grün- und Blautöne für rote, grüne und blaue Schlüssel sowie farbcodierte Türen
- Der Hintergrund ist Catlantis, ein an das alte Ägypten erinnerndes Parodieland wegen Katzenverehrung, daher werden viele gelbe und braune Wüstenfarben gebraucht
- Da Catlantis in der Spielwelt von kybernetischen Hundemenschen besetzt ist, werden viele Grautöne für technische Anlagen benötigt
- Um die Monotonie von Grau aufzubrechen und beim Abdunkeln wärmere Ersatzfarben zu haben, sind auch Beige-Töne enthalten
- Die übrigen Farben wurden während der Texturerstellung nach Bedarf ergänzt und anhand der subjektiven Einschätzung festgelegt, dass es „richtig aussah“
- Die Palette entstand nicht in einem Schritt, sondern wurde bei Asset-Erstellung, Tests und Iterationen laufend angepasst
colormap und Lichtverarbeitung
-
Aufbau des Raycasters
- Catlantean 3D ist ein traditioneller Raycaster, und die Map besteht vollständig aus Tiles gleicher Größe
- Einige Tiles sind Wände, andere sind leere Räume mit nur Boden und Decke
- Für jede Bildschirmspalte läuft der Renderer mit dem DDA-Algorithmus durch die Tilemap und sucht den Punkt, an dem ein Strahl auf die Map-Geometrie trifft
- Anhand der Trefferposition wird dann eine Wandspalte aus den passenden Texturkoordinaten gesampelt und auf den Bildschirm gezeichnet
- Boden und Decke werden anschließend als horizontale Scanlines gerendert und füllen den restlichen Bildschirm
- Wenn die Spielwelt nur mit der Palette gerendert wird, wirkt das Bild flach und wenig eindrucksvoll
- Wenn mit zunehmender Entfernung weniger Licht ankommt und eine Seite eines Map-Tiles etwas dunkler ist als die andere, entsteht Tiefe
-
Palettenbasierte Abdunklung
- In modernen hardwarebeschleunigten Renderern lässt sich Abdunklung im Shader leicht erzeugen, indem man basierend auf der Vertex-Distanz einen Floating-Point-Faktor mit dem Farbvektor multipliziert
- Ein Paletten-Renderer arbeitet aber nicht mit Farben als Konzept, sondern nur mit Palette-Indizes; um eine dunklere Version einer Farbe zu finden, müsste man die gesamte Palette durchsuchen
- Würde man für jedes gerenderte Pixel die 256 Farben durchsuchen, wäre das zu langsam, deshalb wird vor dem Lauf eine schnelle Farbabfrage vorab berechnet
- Legt man die Palette als eine Zeile aus und wählt 32 Helligkeitsstufen, braucht jede Farbe 31 dunklere Varianten zusätzlich zum Original
- Aus den RGB-Werten jeder Farbe und dem Helligkeitsindex wird eine Zielfarbe für die Abdunklung berechnet, aber diese Farbe existiert möglicherweise gar nicht in der echten Palette
- Um die nächstgelegene Palettenfarbe zur Zielfarbe zu finden, wird die Palette durchsucht und daraus die colormap aufgebaut
- Anfangs wurde euklidische Distanz verwendet, aber viele Farben drifteten dadurch in Richtung Grau, und dunkle Farben wirkten kalt und leblos
- Danach wurden die Farben in den Oklab-Farbraum umgerechnet und eine wahrnehmungsbasierte Distanzformel verwendet, die näher an der menschlichen Farbwahrnehmung liegt
- Zusätzlich wurde Hue Shifting angewandt, ein Pixel-Art-Konzept, bei dem Farben beim Abdunkeln leicht in wärmere Farbtöne verschoben werden
- Die colormap ist eine zweidimensionale Matrix aus Palette-Indizes, die die Helligkeitsstufen jeder Farbe darstellt; da weiterhin nur Palettenfarben erlaubt sind, sind die Verläufe nicht perfekt
-
Senkung der Laufzeitkosten
- Sobald der distanzbasierte Zeilenindex in der colormap feststeht, wird aus dieser Helligkeitszeile der N-te Eintrag gewählt, um den Palettenindex der abgedunkelten Farbe N zu erhalten
- Damit wird die Wahl einer dunkleren Farbe zur Laufzeit als O(1)-Lookup ausgeführt
- Beim Rendern von Wänden wird der Zeilenindex der colormap nur einmal pro Bildschirmspalte berechnet, weil Wandspalten vollständig vertikal sind und alle Pixel derselben Spalte den gleichen Abstand zur Kamera haben
- Beim Rendern des Bodens wird nur einmal pro Bildschirmzeile gerechnet, weil alle Pixel derselben horizontalen Zeile den gleichen Abstand haben
- Sprites sind flache Billboards, deren Pixel alle den gleichen Abstand zur Kamera haben, daher wird nur einmal pro sichtbarem Sprite gerechnet
- Für Wände sind also 320 Berechnungen nötig, für den Boden höchstens 240, und für jedes sichtbare Sprite jeweils eine; Raycasting liefert die Entfernung verdeckter Objekte gratis mit
- Doom und viele andere Spiele nutzten einen ähnlichen Ansatz
Asset-Erstellung
-
Drei Asset-Kategorien
- Die Texturen und Sprites von Catlantean 3D teilen sich in drei Kategorien
- Die erste Kategorie sind vorgerenderte Sprites, bei denen 3D-Modelle aus Blender als Texturen gerendert werden
- Die zweite Kategorie sind handgezeichnete Sprites und Texturen
- Die dritte Kategorie sind prozedurale Texturen, die durch spezielle Python-Skripte erzeugt werden, welche handgezeichnete Art kombinieren
-
Vorgerenderte Sprites
- Komplexe animierte Sprites sind schwer und zeitaufwendig zu iterieren, weil mehrere Frames angepasst werden müssen
- Effizienter ist es, in Blender ein 3D-Modell zu bauen, Rigging und Animationen dort zu erledigen und dann mit einem Skript über die Blender Python API mehrere Texturen zu rendern
- Änderungen passieren am Modell, und das Render-Skript erledigt den Rest, wodurch sich die Iterationszeit stark verkürzt
- Das größte Problem war, dass die gerenderten Sprites sehr unscharf und farblich ausgewaschen wirkten
- Hochauflösend zu rendern und dann per Filter herunterzuskalieren führte zu gemischten Ergebnissen, weil Details vom Filter verschluckt wurden und die Kanten ihre Schärfe verloren
- Am wirksamsten und am besten wiederverwendbar war es, mit Blenders Compositing den passenden Kontrast und die nötige Schärfe zu erzeugen
- Sobald ein Bild fertig ist, übernimmt ein spezielles Python-Skript die Paletten-Quantisierung und erzeugt 1-Byte-Pixelbilder für die Engine
- Das Skript sucht für jedes Pixel des Quellbilds anhand von Oklab die wahrnehmungsmäßig nächstliegende Palettenfarbe und verwendet deren Index als Pixelwert
- Das Array aus Indizes und die Größeninformationen werden in ein einfaches TEX-Format gepackt, das vom Spiel verwendet wird
- Gegner-Sprites können mehrere Animationen haben, und jede Animation braucht Frames für die 8 Richtungen, in die ein Sprite zeigen kann
- Das Python-Skript rotiert das Sprite für jede Animation, rendert alle Frames und rotiert dann weiter
- Die Dateinamen der Sprites folgen einem Schema, das Sprite-Name, Aktionsname, Richtung und Frame-Index enthält
- Die gerenderten Sprites liegen nicht im Repository, sondern stehen in
.gitignore; auf anderen Rechnern rendert das Build-Skript alle Modelle und erzeugt daraus die Sprites - Auf einer RTX 3070 dauert die Verarbeitung von etwa 15 Modellen ungefähr 10 Sekunden
-
Handgezeichnete Sprites und Texturen
- Früh in der Entwicklung wurde für das Statusleisten-Gesicht ein katzenförmiger Kopf in Blender erstellt und mit einer Textur der Katze Vilko versehen
- Das Ergebnis wirkte faul und nach geringem Aufwand, transportierte Emotionen schlecht und war das Erste, was Leute beim Stimmungs-Feedback kritisierten
- Manche Elemente müssen einfach von Hand gezeichnet werden, und eine handanimierte Version wurde als deutlich besser beurteilt
- Wegen der Sprite-Größe muss jedes Pixel bewusst gesetzt sein; dafür bleibt kein Spielraum für den Blender-Renderer
- Dieselbe Logik galt auch für die meisten Pickup-Items, und die vorherigen vorgerenderten Ergebnisse lieferten in kleinem Maßstab keine zuverlässig guten Resultate aus Blenders Compositor
- Nach manueller Bearbeitung verbesserten sich Schärfe und Lesbarkeit der Pickup-Items deutlich
- Man könnte die Sprite-Auflösung einfach erhöhen und vom Rasterizer im Spiel skalieren lassen, aber das führt zu inkonsistenten Pixelgrößen und damit zu schlechten Ergebnissen
- Menschen erwarten unbewusst, dass beim Vor- und Zurückbewegen entlang derselben Bildschirmzeile oder -spalte die Pixelgröße gleich bleibt; unterschiedliche Pixel-Skalierung zwischen Sprites wirkt deshalb seltsam
- In Catlantean 3D entspricht 1 Welt-Einheit 64 Pixeln, und alle Sprites werden auf diesen Maßstab abgestimmt
- Ein Sprite mit einem Viertel der Höhe einer Welt-Einheit muss daher 64/4=16 Pixel hoch sein
HUD und Pipeline für prozedurale Erzeugung
-
HUD
- Das HUD und seine Bestandteile werden fast vollständig von Hand platziert und gezeichnet
- Zur Kategorie des handgezeichneten HUDs gehören die Statusleiste am unteren Bildschirmrand, verschiedene Übergangspanels und -Screens sowie die Schriftarten
- Statt alles direkt zu malen, wird stark mit Ebeneneffekten und Compositing in Affinity Photo gearbeitet
- Verwendet werden unter anderem Emboss-Effekte für einen 3D-Eindruck auf flachen Flächen, Rauschgenerator und Overlays für eine raue Oberfläche, Farb-Overlays, Blend-Modi und Glow-Effekte
- Da HUD-Elemente oft überarbeitet werden, ist die Flexibilität einer ebenenbasierten Neuordnung ebenfalls wichtig
- In der Regel wird zuerst in Affinity Photo in Truecolor gearbeitet; viele Elemente bestehen dort aus einfarbigen Rechtecken, auf die Spezialeffekte und Blending angewandt werden
- Die aus Affinity Photo exportierten Bilder enthielten merkwürdige Artefakte, die offenbar mit Anti-Aliasing zusammenhingen, und es gelang nicht, das zuverlässig abzuschalten
- Für pixelgenaue Arbeit war das ungeeignet, daher wurden in Aseprite zusätzliche Schritte erledigt, etwa pixelperfekter Text, das Aufteilen von Artwork und das Nachziehen schärferer Kanten
-
Prozedural erzeugte Texturen
- Einige Texturen sind einfach oder spezifisch genug, um direkt gezeichnet zu werden, aber viele teilen sich Verschleiß, Staub und Variationen von Oberflächendetails über demselben Grundmaterial
- Würde man jede Variante von Hand zeichnen, wäre das langweilig und inkonsistent, daher werden sie per Python-Skript erzeugt
- Die Erzeugungspipeline nutzt als Eingaben eine Heightmap zur Definition des Oberflächenreliefs, eine Noise-Map für Variationen, eine Grime-Map für Staub und Abnutzung, zwei Grundfarben und eine Brightmap
- Die Heightmap wird tatsächlich verwendet, um eine Normal Map zu erzeugen, und diese Normal Map dient dazu, einfache Beleuchtung und Schatten einzubacken
- Die Brightmap markiert Bereiche, deren Farbe unabhängig von anderen Parametern erhalten bleiben soll
- Das Skript erzeugt die finale Textur und führt auch gleich die Paletten-Quantisierung aus, damit sie direkt in der Engine verwendet werden kann
- Texturen zu ändern bedeutet dadurch, Parameter anzupassen statt Pixel neu zu zeichnen, was bei Solo-Entwicklung viel Zeit spart
Gibs und vorgerenderte Effekte
-
Gibs
- Wenn auf einen Gegner übermäßiger Schaden wie ein point-blank shotgun blast oder eine Explosion angewendet wird, kommt es typischerweise zum Gibbing
- Um die Wucht des hohen Schadens zu vermitteln, wird eine Animation verwendet, in der der Gegner in blutige Stücke zerplatzt
- Diese Pipeline wird von einem Python-Skript gesteuert, das ein Sprite, die Palette und einen Parametersatz nimmt und daraus Animationsframes für die Spieldaten erzeugt
- Im ersten Schritt, der Voronoi decomposition, werden K Seed-Pixel zufällig aus dem undurchsichtigen Körper des Sprites gewählt, und alle Pixel werden dem jeweils nächsten Seed zugeordnet
- Jede so entstandene Zelle wird zu einem wegfliegenden Fragment
- Im zweiten Schritt, wound bleeding, werden Grenzpixel, die an andere Fragmente angrenzen, als Wunde mit Tiefe 0 markiert, und ein BFS breitet sich nach innen aus und weist Tiefenwerte zu
- Beim Rendern werden Pixel in Grenznähe stärker in Richtung einer Blut-Farbe aus einer aus der Spielpalette abgeleiteten Ramp gemischt, während weiter innen mehr von der ursprünglichen Sprite-Farbe erhalten bleibt
- Die Wahl der Paletten-Ramp ist parametrisierbar, sodass für bestimmte Gegner auch grünes oder blaues „Blut“ verwendet werden kann
- Im dritten Schritt, physics, erhält jedes Fragment einen Mittelpunkt, eine zufällige Streugeschwindigkeit nach außen vom Sprite-Zentrum weg, Rotationsgeschwindigkeit, Schwerkraft und Drag
- Es gibt keine Kollisionserkennung, aber die Fragmente stoppen beim Auftreffen auf den Boden; das ist grob, liefert aber ausreichende Ergebnisse
- Fragmentanzahl, Explosionskraft, Schwerkraft, Drag, Streuung und Wundtiefe sind per Parameter einstellbar
- Es braucht etwas Trial-and-Error, um gut aussehende Seeds zu finden, aber es ist schneller, als die Animation von Hand zu zeichnen
- Dieselbe Technik wird auch für zerstörbare Umgebungsobjekte wie Blumentöpfe, Fässer und Kisten verwendet
- Wie vorgerenderte Animationen liegen auch Gibs-Ergebnisse nicht im Repository, sondern werden nach dem Checkout neu erzeugt; die Laufzeit dafür ist vernachlässigbar
-
Vorgerendertes Partikelsystem
- Die meisten Partikeleffekte werden in Aseprite von Hand gezeichnet, einige werden aber wie die Gibs erzeugt und vorab gebacken
- Ein Python-Skript führt die Simulation aus und erzeugt eine PNG-Frame-Sequenz, die anschließend in TEX quantisiert wird
- Es gibt kein Laufzeit-Partikelsystem; alle Effekte werden vorab gebacken, damit der Software-Rasterizer sie so schnell wie möglich rendern kann
- Das Wort „particle“ ist hier etwas irreführend, denn tatsächlich werden keine Partikel simuliert
- Jeder Frame berechnet ein radial energy field pro Pixel und setzt mehrere unabhängige Layer daraus zusammen
- Der core ist eine weiche Scheibe, die sich im Verlauf der Animation nach außen ausdehnt
- rays sind spitze Lichtstrahlen um den core herum; Sharpness und Length lassen sich einstellen, und jede ray erhält RNG-basiertes Längenjitter, damit sie unregelmäßig wirkt
- Der ring ist eine optionale expandierende shockwave, und noise multipliziert die Gesamtenergie mit Value Noise, damit saubere Formen rauer und unregelmäßiger wirken
- Die aufsummierte Energie pro Pixel wird auf eine Paletten-Ramp quantisiert, die über Skriptparameter festgelegt ist
- Aufgrund des Palettendesigns kann jede Zeile wie ein Verlauf von hell nach dunkel behandelt werden, sodass Pixel allein über Arithmetik auf Palette-Indizes abgedunkelt werden, ohne Blending oder Alpha-Berechnung
- Oberhalb bestimmter Schwellwerte werden Pixel in Richtung Weiß verschoben, um einen white-hot core zu erzeugen
- Optional lassen sich kleine sparkles darüber verteilen; diese kreuzförmigen Elemente bewegen sich nach außen und verblassen über ihre jeweilige Lebensdauer
- Die Animation unterstützt einen One-Shot-Modus, in dem ein Effekt wie eine Explosion oder ein Teleport-Flash größer wird und dann verschwindet, sowie einen nahtlosen Loop-Modus, bei dem erster und letzter Frame zusammenpassen
- Der Loop-Modus ist nützlich für dauerhaft wiederholte Effekte wie plasma bolts und energy projectiles
Map-Editor und Tool-Ökosystem
- Die Bearbeitung der Maps begann zunächst in Tiled; das war allgemein ein vernünftiges Tool, es fehlten aber konkrete Funktionen, die das Spiel brauchte
- Tiled hatte kein cell-by-cell light level painting, keine cell flags und kein Konzept für spielspezifische Eigenschaften, weshalb anfangs mit object properties als Workaround gearbeitet wurde
- Außerdem wurde ein Python-Skript benötigt, das die JSON-Ausgabe von Tiled in das von der Engine verwendete Binärformat umwandelt; das war ein zusätzliches Bauteil, um die Lücke zwischen Tool und Spielanforderungen zu schließen
- Wenn Spieler Tiled installieren, die Oberfläche lernen und ein Konvertierungsskript einrichten müssten, nur um Maps zu erstellen, wäre das eine so hohe Hürde, dass der Editor praktisch nicht genutzt würde
- Der eigene Editor unterstützt cell-by-cell light level painting, cell flags sowie alle Entity- und Property-Typen, die das Spiel kennt, nativ
- Nach Release sollen Spieler denselben Editor erhalten, der auch in der Entwicklung verwendet wurde
- Der Editor ist plug and play, und Levels lassen sich direkt aus dem Editor heraus starten
- Es ist bekannt, dass die Toolbar-Icons furchtbar aussehen, und genau deshalb bleiben sie so
- Der Editor wurde mit wxPython erstellt, das bei Widgets, Event Handling und Layout besser passte als tkinter
- Das Ergebnis mit wxPython wirkte nativer, und Iterationen gingen schneller
- Eine am MVP-Pattern orientierte Struktur trennt UI-Logik und Map-Daten sauber, was wichtig ist, weil sich beide Seiten bei einem noch nicht stabilen Map-Format häufig ändern
- Nicht jeder Teil des Editors ist in Python geschrieben; große Teile des Models hängen von der Bibliothek
pybastab pybastsind interne Python-Bindings der Engine via pybind und liefern das Lesen des Game-Data-Archivs, das Lesen von Spieltexturen, eine Fixed-Point-Klasse für Entity-Koordinaten und Serialisierung- Das ist eine bewusste Entscheidung, um Funktionalität, die bereits in C++ existiert, nicht noch einmal in Python zu implementieren; Engine und Tools bilden so ein kleines, eng verzahntes Ökosystem
Release-Pläne und Veröffentlichungsmodell
- Catlantean 3D wird voraussichtlich im ersten Quartal 2027 erscheinen
- Aktuell liegt der Fokus auf Level-Design, zusätzlichen Gegnern und Waffen sowie laufender Politur
- Das Preisziel liegt bei 5 bis 8 Dollar
- Der Spielquellcode soll als Open Source auf GitHub veröffentlicht werden
- Das eigentliche Data Archive mit Grafik, Levels, Sound und Musik soll nur Käufern des Spiels zugänglich sein
- Transparenz im Prozess wird als einer der wenigen Faktoren gesehen, die dauerhaft Vertrauen aufbauen
- Anders als AAA-Spiele sind Indie-Games auf ein kleineres Publikum angewiesen, dieses Publikum ist aber eher bereit, ein Projekt zu verfolgen, zu unterstützen und anderen davon zu erzählen
- Das Zeigen des Arbeitsprozesses wird als die ehrlichste Weise dargestellt, zu zeigen, dass einem das, was man baut, wirklich wichtig ist
1 Kommentare
Hacker-News-Kommentare
Wenn man mit Software-Rendering herumspielen möchte, gibt es ein Beispiel, das ziemlich nah am kürzesten Code liegt, um mit SDL2 und C plattformübergreifend ein ARGB8888-2D-Array im Hauptspeicher effizient auf den Bildschirm zu bringen: https://gist.github.com/CoryBloyd/6725bb78323bb1157ff8d4175d...
Die Umwandlung eines 320x200x8-Bit-Paletten-Framebuffers nach ARGB muss man selbst machen ;)
Wenn man sich inspirieren lassen will, was sich mit einem Paletten-Framebuffer machen lässt, kann man bei http://www.effectgames.com/demos/canvascycle/ auf Show Options klicken oder sich den GDC-Vortrag des Künstlers ansehen: https://youtu.be/aMcJ1Jvtef0
Und wenn man danach das klassische Deluxe-Paint-IIe-Gefühl haben will, startet man https://github.com/mriale/PyDPainter; wenn man ein moderneres Tool möchte, dann https://www.aseprite.org/
Zumindest in SDL3 braucht man keinen Renderer oder keine Texture mehr. Man holt sich die Surface mit SDL_GetWindowSurface und zeigt sie mit SDL_UpdateWindowSurface an
Soweit ich die Bibliothek verstanden habe, ist das der software-naheste Weg für Grafik, und SDL übernimmt weiterhin das Double Buffering
Das ist sicher die grundlegendste Herangehensweise. Wenn man in der inneren Schleife kleine Optimierungen will, kann man den Scanline-Offset vor dem Eintritt in die Pixel-Schleife vorausberechnen:
int s = y*screenRect.w;for (int x = 0; x < screenRect.w; x++) {pixels[s + x] = argb(255, frame>>3, y+frame, x+frame);}Danke fürs Teilen. Es gibt zwar bereits mehrere populäre Quake-Forks, aber Planimeter verteilt den Quake-VS2026-Fork ohne Änderungen
Das Team arbeitet an einem x64-Build und muss dafür die alte SciTech Multi-platform Graphics Library (nur x86) durch SDL3 ersetzen. Alternativ müsste man scitech-mgl auf x64 portieren, was eher unwahrscheinlich wirkt, und soweit ich zuletzt wusste, könnte dabei sogar der Software-Renderer wegfallen
Vielleicht lässt er sich aber mit dem Software-Renderer und SDL_Texture erhalten
Dieser Beitrag hat sich stark von Doom inspirieren lassen, aber die eigentliche Raycasting-Engine ist eher mit Dooms Vorgängern verwandt, am bekanntesten darunter Wolfenstein 3D
Es geht um vertikale Wände und feste Boden- und Deckenhöhen. Wolf3D hatte aus Performance-Gründen keine texturierten Böden und Decken, aber einige ähnliche Spiele hatten das durchaus
Doom und, wenn ich mich richtig erinnere, auch Duke Nukem verwendeten eine deutlich flexiblere BSP-Engine, bei der sich Wände in beliebigen Winkeln schneiden konnten und Boden- bzw. Deckenhöhen variieren durften. Die Levels waren aber immer noch „flach“, sodass man innerhalb eines Levels keine mehreren Stockwerke bauen konnte; eine Brücke, unter und über der man gleichzeitig hindurchgehen kann, ließ sich also zum Beispiel nicht entwerfen
Die Build-Engine nutzte kein BSP. Verbindungen zwischen Sektoren wurden als Portale behandelt, gegen die dann geclippt wurde, während Wände wie um 90 Grad gedrehte Trapeze rasterisiert wurden
Dadurch waren dynamische Wandgeometrien wie fahrende Züge oder rotierende Beleuchtung möglich, und „Raum über Raum“-Konstruktionen funktionierten ebenfalls, solange man nie zwei Räume gleichzeitig sehen konnte
In Blood und Shadow Warrior wurden noch stärker „3D“-artige Räume mit einem Workaround gebaut, bei dem gleich geformte Sektoren erstellt wurden und der Boden des einen Sektors als eine Art Portal zur Decke des anderen diente. Das war keine ursprünglich vom Engine unterstützte Funktion, aber flexibel genug, dass Studios es selbst umsetzen konnten, obwohl sie nicht einmal Zugriff auf den Quellcode hatten
Schon das erste Level von Duke Nukem 3D nutzt einige Build-Tricks. Sprites können sich zum Beispiel ohne Mitdrehen mit der Kamera achsenbündig ausrichten und auch Kollision haben; wenn man also jedes Sprite als achsenbündiges Rechteck behandelt, kann man primitive 3D-Geometrie erzeugen. Im ersten Level wurde das verwendet, um direkt vor dem Exit-Button die Brücke zwischen zwei Gebäuden zu bauen
Blake Stone und Rise of the Triad verwendeten spätere Versionen der Wolf3D-Engine und hatten texturierte Böden und Decken
Die Build-Engine von Duke Nukem nutzte kein BSP
https://www.jonof.id.au/forum/topic-137.html#msg1548
Später in Shadow Warrior ging so etwas meines Wissens auch. Soweit ich mich erinnere, wurde es mit Portalen umgesetzt, und im Editor war das ziemlich schmerzhaft einzurichten
Was Böden angeht: Soweit ich weiß, hat selbst DOOM das nicht exakt gemacht. Bei vertikalen Wänden braucht man für ein bestimmtes Wandsegment nur einmal pro Pixelspalte eine Perspektivdivision
Beim Boden hat man diesen Luxus leider nicht; wenn ich mich richtig erinnere, teilte DOOM den Boden in Patches auf, berechnete die korrekte Perspektive nur an den Ecken und interpolierte dazwischen
Anfangs dachte ich, es sei einfach nur ein Wolfenstein 3D mit anderem Skin. Das war eine ziemlich unfair harte Einschätzung, denn hier steckt wirklich sehr viel Arbeit drin
Ein hervorragender Beitrag. Besonders der Ansatz zur Erstellung der gib-Animationen war interessant.
Es war zwar eine technische Demo, aber ich habe Mitte der 90er auch einmal etwas Ähnliches gebaut. Ein Punkt, der in diesem Beitrag nicht zu sehen war: Ich habe 8x8- oder 16x16-Lightmaps auf Texturen verwendet, um Effekte wie flackernde Fackeln oder Raketen, die durch einen Korridor fliegen und dabei Licht werfen, einfach umzusetzen. Wenn man wollte, konnte man Lightmaps auch verwenden, um Beleuchtung „einzubacken“.
Da die Lightmap „nur“ 8x8 war, war die Mathematik noch beherrschbar: Für jedes Luxel, also jede Einheit der Lightmap, wurden Entfernung zur Lichtquelle und Sichtlinie berechnet, um einen Helligkeitswert zu bestimmen. Beim Rendern der Textur wurden die Luxel dann zusammen mit einer Lookup-Tabelle verwendet, um die tatsächliche Pixelfarbe zu bestimmen.
Aus Performancegründen wurden die Lightmaps meiner Erinnerung nach 15-mal pro Sekunde aktualisiert. Dank DJGPP habe ich beim Rendering Inline-Assembler verwendet, und weil Gleitkommaoperationen damals langsam waren, kam Festkommaarithmetik zum Einsatz, die sich gut optimieren ließ. Für damalige Computer war die Renderleistung erstaunlich.
Grafikprogrammierung in den frühen bis mittleren 1990ern hat ziemlich viel Spaß gemacht. Man schrieb Pixeldaten in das speicherabgebildete Video-RAM, und sie erschienen sofort auf dem Bildschirm.
Ein einzelner Zeiger auf 0xA0000 reichte aus, und man brauchte kein API. Der Grund für den erwähnten VGA-Modus 320×200 mit nichtquadratischen Pixeln war, dass der Videopuffer 64000 Byte groß war und damit in ein 16-Bit-Segment passte, was die Adressierung für 16-Bit-Code und CPU vereinfachte.
Andererseits konnte man wegen genau dieser Schwäche pro Pixel auf dem Bildschirm viel mehr machen, und dadurch waren Dinge wie Raycasting oder BSP-Baum-Systeme möglich.
Es gab keine dedizierten Prozessoren für Sprites und Hintergrundebenen, aber dafür war der PC nicht auf eine starre Fixed-Function-Architektur festgelegt.
Als dann Mitte bis Ende der 90er dedizierte 3D-Prozessoren aufkamen, war das kein Problem mehr, aber Anfang der 90er gab es für eine kurze Zeit einen einzigartigen Spielplatz für visuelles Rendering.
DJGPP und Free Pascal verwendeten beide denselben go32-Extender von DJ Delorie, und weil der keine vollständige lineare Abbildung machte, musste man ein wenig mehr Hand anlegen, um etwas auf den Bildschirm zu bekommen.
Am interessantesten sind die internen Tools. Dinge wie das Python-Skript zum Erzeugen der gib-Animationen oder ein anderes Python-Skript, das in Blender 2D-Spritesheets erzeugt.
Der Autor des Originalbeitrags wirkt ganz klar wie ein 10x-Ingenieur, der auch gute Bilder machen kann, und ich halte das für wirklich selten. Dass es eine konsistente Art Direction gibt, war ebenfalls ziemlich beeindruckend.
In den letzten 15 Jahren der Spielebranche kenne ich, abgesehen von CEOs oder leitenden Direktoren, fast keine Namen mehr.
Mir ist gerade aufgefallen, dass dieses Spiel vielleicht einer der seltenen Shooter mit einer weiblichen Hauptfigur sein könnte. Die Katze hat ein dreifarbiges Fellmuster, und solche Katzen sind fast immer weiblich (https://en.wikipedia.org/wiki/Calico_cat).
Sind Shooter mit weiblicher Hauptfigur wirklich so selten? Mir fallen auf Anhieb schon einige ziemlich bekannte Titel ein: Perfect Dark, Mirror's Edge, Dishonored Teil 1 oder 2, Metroid und andere — alles in gewisser Weise Shooter mit weiblicher Hauptfigur.
Wobei Mirror's Edge streng genommen eher „First-Person“ als wirklich ein „Shooter“ ist.
Außerdem gibt es unter „RPG + FPS“ viele Spiele, in denen man als Mann oder Frau spielen kann.
Der Autor scheint das Muster und die mögliche Geschlechtszuordnung der Katze ebenfalls zu kennen:
After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)Das ist kein Perfect-Dark-Spiel.
Heutzutage gibt es ziemlich viele Boomer-Shooter mit weiblicher Hauptfigur. Zum Beispiel Selaco[0], Supplice[1], The Citadel[2] und der Nachfolger[3], Zortch[4] und der angekündigte Nachfolger[5], Nightmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9] und weitere.
Eigentlich scheint es inzwischen mehr Boomer-Shooter mit weiblicher Hauptfigur zu geben als ohne :-P Wenn man bei Steam die Tags „boomer shooter“ und „female protagonist“ kombiniert, bekommt man 143 Treffer, wobei darunter auch Spiele sind, bei denen man das Geschlecht wählen kann oder meistens einen Mann spielt, aber in einigen Abschnitten eine Frau.
[0] https://store.steampowered.com/app/1592280/Selaco/
[1] https://store.steampowered.com/app/1693280/Supplice/
[2] https://store.steampowered.com/app/1378290/The_Citadel/
[3] https://store.steampowered.com/app/3371240/Beyond_Citadel/
[4] https://store.steampowered.com/app/2443360/Zortch/
[5] https://store.steampowered.com/app/3807500/Zortch_2/
[6] https://store.steampowered.com/app/1051690/Nightmare_Reaper/
Ich glaube nicht, dass das beabsichtigt war, aber insgesamt beeindruckt mich so etwas nicht besonders und ich sehe auch keinen großen Wert darin. Dasselbe gilt für Darstellungen in Hollywood, in denen Frauen Männer zu Boden schlagen, die doppelt so groß sind wie sie.
Ich halte das für unrealistisch, lächerlich und schädlich.
Wirklich cool. Ein anderer interessanter Trick aus den 90ern war Palettenanimation. Schon das bloße Ändern der Palette hatte geringe Laufzeitkosten, konnte aber extrem coole Effekte erzeugen.
Stimmt. Wer ein großartiges Beispiel für diese Technik sehen will, dem kann ich diese Website sehr empfehlen:
http://www.effectgames.com/demos/canvascycle/
Es macht auch Spaß, Paletten mitten im Frame zu wechseln. Auf dem PC gab es nichts wie den Copper des Amiga, daher musste man viel stärker auf das Timing achten, aber möglich war es trotzdem.
Wenn ich mich richtig erinnere, waren viele Gegner in Diablo 1 und 2 im Grunde dieselben Sprites mit unterschiedlichen Paletten. Ist das derselbe Trick?
Blau ist Wasser, Lila ist Plasma, Rot/Orange ist Blut oder Lava.
Ich war wirklich überrascht, wie gut die gerenderten quantisierten Sprites aussahen. Durch diese schnelle Umwandlung wirkten sie sehr knackig.
Als Mitentwickler, der eine 3D-Engine mit absurd harten Einschränkungen baut, gefällt es mir sehr, hier die Details und den Weg dorthin zu sehen.
Ich bastle gerade als PlayStation-Homebrew an einer Technikdemo für Voxel-Space-Rendering. Nach ein oder zwei Tagen Wochenendarbeit komme ich schon auf ganz brauchbare 10–15 FPS, und ich verwende noch weder DMA noch GTE, geschweige denn die Polyline-Grundprimitive.
Es ist erfrischend, wieder Trigonometrie und alte Low-Level-Optimierungstricks hervorzukramen. Wenn der Scratch-Buffer 1 KiB groß ist und man vom Stack nur einen Teil davon nutzen kann, merkt man erst, wie luxuriös die Mikrocontroller sind, die ich bei der Arbeit benutze. Dort bekommt jeder Thread einen 8-KiB-Stack, und man sieht Backtraces mit mehr als 50 verschachtelten C++-Template-Funktionen.