Ich habe mich einmal ausführlich mit der Problematik einer Raycasting-Engine auseinander gesetzt - und nachdem ich die halbe Nacht Grundlagen gepaukt habe, habe ich eine ganz simple Engine gebastelt. Nachdem diese im Grafikmodus recht lahm war, habe ich sie für den Textmode 80x50 umgestrickt und noch ein Assembler-Programm gebastelt, dass die "Szene" darstellt und gleichzeitig die Farbabstufungen/Schraffuren vornimmt.
Das Problem ist: Die eigentliche Distanzberechnung geht über Realzahlen - und das wird - je größer die Distanz zu Wänden wird - extrem langsam auf meinen 486SX-25. Hat jemand von Euch Profis eine Idee, wie man um dieses ganze Realzahlen-Disaster herumkommt? Mein Ziel ist es, eine einfache, aber schnelle Engine zu basteln, um diese ggf. in zukünftigen Projekten (RPG?) zu nutzen. Nur nervt es mich extremst, dass es eben nur auf dem P1 gut läuft
Mir geht es auch nicht darum, hier fertige Quellcodes "abzugreifen" - ich erarbeite mir meinen Code lieber durch ausprobieren, Fehlschläge und Erfolge. Aber ein paar hilfreiche Tipps, für einen echten Dummi erklärt - dafür wäre ich echt dankbar
RayCasting ohne Realzahlen?
RayCasting ohne Realzahlen?
- Dateianhänge
-
- Rayast.jpg (40.85 KiB) 3100 mal betrachtet
Re: RayCasting ohne Realzahlen?
Ich bin zwar kein Rechenprofi, eher das Gegenteil davon, aber ich würde um das Komma wegzubekommen die Werte mit zehntausend multiplizieren und dann das Ergebniss wieder durch zehntausend teilen. Auf diese Weise kann man sich auch eine Sinus-Tabelle mit reinen Integerwerten anlegen. (Oder dort auch noch weitere Berechnungstufen gleich mit einbauen.)Brueggi hat geschrieben:Hat jemand von Euch Profis eine Idee, wie man um dieses ganze Realzahlen-Disaster herumkommt? Mein Ziel ist es, eine einfache, aber schnelle Engine zu basteln, um diese ggf. in zukünftigen Projekten (RPG?) zu nutzen. Nur nervt es mich extremst, dass es eben nur auf dem P1 gut läuft
Beispiel:
Code: Alles auswählen
.MODEL SMALL
.386P
.387
;────────────────────────────────────────────────────────────────────────────
Scale = 4
Grad = 360 * Scale
Endtab = 450 * Scale
Foktor = 10000h
;────────────────────────────────────────────────────────────────────────────
CODE SEGMENT use16 'CODE'
assume cs:CODE,ds:DATEN,ss:STAPEL
;---------------------------------------------------------------------------
org 100h
;────────────────────────────────────────────────────────────────────────────
START: mov ax, DATEN
mov ds, ax
finit
call TABLE
mov dx, OFFSET TABNAM
call MAKDAT
xor dx, dx
mov cx, Endtab*4
call WRITE
call CLOSE
;---------------------------------------------------------------------------
xor al, al ; kein Fehler
DOS: mov ah, 4Ch ; Dos-Rücksprung, Programm-Ende
int 21h
;────────────────────────────────────────────────────────────────────────────
; U N T E R - R O U T I N E N
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16)+16
;─────────────────────────────────────────────────────────────────────────────
; S i n u s - T a b e l l e
;─────────────────────────────────────────────────────────────────────────────
TABLE: xor di, di ; Sinus-Tabelle anlegen
;-------------------------------------
TAB: fldpi ; PI laden
fimul DWORD PTR[I] ; Zaehler mal PI
fidiv DWORD PTR[TEIL] ; durch 180(INT) teilen
fsin ; davon den Sinus
fimul DWORD PTR[FAKT]
fistp DWORD PTR[di] ; in die Sinus-Tabelle schreiben
;-------------------------------------
inc WORD PTR[I] ; Grad-Zähler erhöhen
add di, 4 ; Tabellen-Index erhöhen
cmp WORD PTR[I], Endtab ; Tabellen-Ende erreicht ?
jnz TAB
ret
;─────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------
MAKDAT: mov ah, 3Ch ; Datei erstellen (ah=3C/5B)
xor cx, cx ; ---------------
int 21h
mov bx, ax
ret
;─────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------
WRITE: mov ah, 40h ; Datei beschreiben
int 21h
ret
;─────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------
CLOSE: mov ah, 3Eh ; Datei schließen
int 21h
ret
;---------------------------------------------------------------------------
CODE ends
;────────────────────────────────────────────────────────────────────────────
; D A T E N - B E R E I C H
;────────────────────────────────────────────────────────────────────────────
DATEN SEGMENT use32 'DATA'
org 0
SINTAB DB Endtab DUP (?,?,?,?)
;-------------------------------------
TEIL DW Grad/2, ?
I DW 0, 0
FAKT DD Foktor
TABNAM DB "Sin2Int.tab", 0
DATEN ends
;────────────────────────────────────────────────────────────────────────────
STAPEL SEGMENT use16 STACK 'STACK'
DB 10h dup (?)
;---------------------------------------------------------------------------
STAPEL ends
end
Zuletzt geändert von freecrac am Di 15. Jan 2013, 13:33, insgesamt 1-mal geändert.
Re: RayCasting ohne Realzahlen?
(Im Code steht übrigens 10000h - was auch sinnvoller ist - nämlich 65536dez)
Es geht darum: Man benutzt einfach FESTKOMMA - was soviel bedeutet wie: GAR KEIN KOMMA.
Will sagen: Man benutzt z.B. 32bit Zahlen, die oberen 16 Bit stellen das "Vorkomma" dar, die unteren 16bit das "Nachkomma". Kann man natürlich beliebig variieren, wieviel Vorkomma- und Nachkomma-Bits man haben will, je nach Gesamtgröße der "Objekte" (oder Levels) und der gewünschten Genauigkeit. Auf jeden Fall ist Festkommaberechnung WESENTLICH schneller als Fließkomma - und gerade im Assemblerbereich würde ich mich auf Fließkomma gar nicht erst einlassen. (Ich könnte das zwar - aber wozu sollte ich?)
Bei "Festkomma"berechnung rechnet man quasi einfach mit Integerzahlen. Man denkt sich das Komma dazu. Selbst im Hochsprachenbereich vermeide ich Fließkomma, wo ich nur kann.
Und ja: Sinustabellen sind/waren schon immer eine hervorragende Idee. Sinus/Cosinus BERECHNEN lassen ist die reinste Pestilenz, das klaut Rechenzeit ohne Ende. Achja, und dabei ist es eine gute Idee, Winkel nicht mit Reichweite 0 bis 359 Grad oder sowas anzugeben, sondern mit einer Reichweite, die einer 2er Potenz entspricht, z.B. 0 bis 255, 0 bis 1023, 0 bis 8191 oder so etwas (je nachdem, wie genau man es braucht).
Zusatzbemerkung:
Leider führt "Hochsprachen-Only-Coding" (verzeiht dieses Wortkonstrukt), dadurch, daß "ja alles irgendwie geht" oft zum Programmieren von "Worst Case Szenarien", also, daß man Sourcecode bastelt, der von außen "schön und elegant" wirkt, weil er so klein ist und wie ein Gedicht aussieht - aber in compiliertem Zustand dann Code mit maximalem Aufwand an Ressourcen (Rechenzeit, Speicherplatz) erzeugt.
Ein Beispiel: Es gibt Programmiersprachen, die bieten Variablentypen wie "VARIANT" an. Klingt für den Programmierer toll: Es ist ein Datentyp, der ALLES enthalten kann: Strings, Integers, Fließkomma, Booleans, vielleicht sogar Matrizen. Denkt man aber einmal kurz darüber nach, was die Verwendung eines solchen Datentyps bedeutet, ergibt sich, daß bei jeder Verwendung einer Variable dieses Typs zu verschiedenen Zwecken viel Code (und Ausführungszeit) benötigt wird, um die Daten in das jeweils benötigte Format zu wandeln bzw zu prüfen, ob dies möglich ist, usw... - was genau DANN ein Problem (Geschwindigkeit, Codegröße) werden kann, wenn man so etwas - aus Bequemlichkeit - für JEDES und ALLES benutzt, anstatt nur da, wo absolut notwendig.
Ein anderes Beispiel: Compiler bieten an, die sogenannte Bereichsüberprüfung einzubauen - d.h. daß eine Variable nicht den für sie vorgesehenen Zahlenbereich überschreitet (und damit wrapt). Nette Idee für das DEBUGGEN von Programmen - aber nicht für ein fertig laufendes. Warum? Bei JEDER Verwendung einer Variable solcher Typen in einer Formel/Berechnung wird eine solche Bereichsüberprüfung durchgeführt - man muß kein Genie sein, um sich auszumalen, daß solche Dinge Zeit kosten. Besser ist es, gleich selbst durch die Programmierung dafür zu sorgen, daß solche Fälle nicht auftreten - oder, wenn dies nicht ohne weiteres möglich ist (da manche Werte erst zur Laufzeit bekannt sind, z.B. Usereingaben), die Bereiche SELBST zu prüfen, und dann nur einmal und nur an dieser Stelle.
(Abgesehen davon kann man mit dem Wrappen von Werten über den definierten Zahlenbereich hinaus oftmals auf einfachste Weise Dinge bewirken, die man anderenfalls umständlich "außenherum" programmieren müßte. Ein Beispiel? OK, ein einfaches: Nehmen wir an, das Ergebnis einer Berechnung landet in einer 16bit-Integer-Variable. Nun soll getestet werden, ob diese zwischen einschließlich 0 und 999 liegt. Also: IF (I>=0) AND (I<=999) THEN... Man kann aber auch an dieselbe Stelle im Speicher eine WORD (unsigned integer) legen und fragt einfach ab: IF (W<=999) THEN... oder auch IF W<1000) THEN... Das gleiche natürlich in Assembler. Spart schon wieder eine Abfrage. Wer weiß, wie negative Zahlen im Computer gespeichert werden, weiß auch, warum das funktioniert... Das geht aber natürlich nur, wenn die Datentypen feste binäre Größen haben und klar ist, wie die Zahlen abgelegt sind...) Das sind so schöne Dinge, an die ich manchmal denke, und auf die Leute, die "nur Hochsprache" oder "nur Skriptsprache" programmieren, gar nicht erst kommen, weil sich manche von denen mit Binärarithmetik gar nicht mehr auskennen...
Achja: Alles, was ich ab "Zusatzbemerkung:" geschrieben habe, gilt selbstredend nur, wenn das ausführende Zielsystem bekannt ist. Durch systemübergreifende Programmierung verbieten sich quasi die meisten dieser o.g. Dinge von selbst - d.h. hier können solche Dinge, die Performance erhöhen und Speicherverbrauch verringern, meist nicht genutzt werden (weshalb ich auch nicht gerade ein glühender Verehrer systemferner/systemübergreifender Programmierung bin - aber das ist jetzt nicht so wichtig).
Achja, seufz... ich hätte vielleicht gar nicht so viel schreiben sollen. War gerade wieder so in Fahrt...
Es geht darum: Man benutzt einfach FESTKOMMA - was soviel bedeutet wie: GAR KEIN KOMMA.
Will sagen: Man benutzt z.B. 32bit Zahlen, die oberen 16 Bit stellen das "Vorkomma" dar, die unteren 16bit das "Nachkomma". Kann man natürlich beliebig variieren, wieviel Vorkomma- und Nachkomma-Bits man haben will, je nach Gesamtgröße der "Objekte" (oder Levels) und der gewünschten Genauigkeit. Auf jeden Fall ist Festkommaberechnung WESENTLICH schneller als Fließkomma - und gerade im Assemblerbereich würde ich mich auf Fließkomma gar nicht erst einlassen. (Ich könnte das zwar - aber wozu sollte ich?)
Bei "Festkomma"berechnung rechnet man quasi einfach mit Integerzahlen. Man denkt sich das Komma dazu. Selbst im Hochsprachenbereich vermeide ich Fließkomma, wo ich nur kann.
Und ja: Sinustabellen sind/waren schon immer eine hervorragende Idee. Sinus/Cosinus BERECHNEN lassen ist die reinste Pestilenz, das klaut Rechenzeit ohne Ende. Achja, und dabei ist es eine gute Idee, Winkel nicht mit Reichweite 0 bis 359 Grad oder sowas anzugeben, sondern mit einer Reichweite, die einer 2er Potenz entspricht, z.B. 0 bis 255, 0 bis 1023, 0 bis 8191 oder so etwas (je nachdem, wie genau man es braucht).
Zusatzbemerkung:
Leider führt "Hochsprachen-Only-Coding" (verzeiht dieses Wortkonstrukt), dadurch, daß "ja alles irgendwie geht" oft zum Programmieren von "Worst Case Szenarien", also, daß man Sourcecode bastelt, der von außen "schön und elegant" wirkt, weil er so klein ist und wie ein Gedicht aussieht - aber in compiliertem Zustand dann Code mit maximalem Aufwand an Ressourcen (Rechenzeit, Speicherplatz) erzeugt.
Ein Beispiel: Es gibt Programmiersprachen, die bieten Variablentypen wie "VARIANT" an. Klingt für den Programmierer toll: Es ist ein Datentyp, der ALLES enthalten kann: Strings, Integers, Fließkomma, Booleans, vielleicht sogar Matrizen. Denkt man aber einmal kurz darüber nach, was die Verwendung eines solchen Datentyps bedeutet, ergibt sich, daß bei jeder Verwendung einer Variable dieses Typs zu verschiedenen Zwecken viel Code (und Ausführungszeit) benötigt wird, um die Daten in das jeweils benötigte Format zu wandeln bzw zu prüfen, ob dies möglich ist, usw... - was genau DANN ein Problem (Geschwindigkeit, Codegröße) werden kann, wenn man so etwas - aus Bequemlichkeit - für JEDES und ALLES benutzt, anstatt nur da, wo absolut notwendig.
Ein anderes Beispiel: Compiler bieten an, die sogenannte Bereichsüberprüfung einzubauen - d.h. daß eine Variable nicht den für sie vorgesehenen Zahlenbereich überschreitet (und damit wrapt). Nette Idee für das DEBUGGEN von Programmen - aber nicht für ein fertig laufendes. Warum? Bei JEDER Verwendung einer Variable solcher Typen in einer Formel/Berechnung wird eine solche Bereichsüberprüfung durchgeführt - man muß kein Genie sein, um sich auszumalen, daß solche Dinge Zeit kosten. Besser ist es, gleich selbst durch die Programmierung dafür zu sorgen, daß solche Fälle nicht auftreten - oder, wenn dies nicht ohne weiteres möglich ist (da manche Werte erst zur Laufzeit bekannt sind, z.B. Usereingaben), die Bereiche SELBST zu prüfen, und dann nur einmal und nur an dieser Stelle.
(Abgesehen davon kann man mit dem Wrappen von Werten über den definierten Zahlenbereich hinaus oftmals auf einfachste Weise Dinge bewirken, die man anderenfalls umständlich "außenherum" programmieren müßte. Ein Beispiel? OK, ein einfaches: Nehmen wir an, das Ergebnis einer Berechnung landet in einer 16bit-Integer-Variable. Nun soll getestet werden, ob diese zwischen einschließlich 0 und 999 liegt. Also: IF (I>=0) AND (I<=999) THEN... Man kann aber auch an dieselbe Stelle im Speicher eine WORD (unsigned integer) legen und fragt einfach ab: IF (W<=999) THEN... oder auch IF W<1000) THEN... Das gleiche natürlich in Assembler. Spart schon wieder eine Abfrage. Wer weiß, wie negative Zahlen im Computer gespeichert werden, weiß auch, warum das funktioniert... Das geht aber natürlich nur, wenn die Datentypen feste binäre Größen haben und klar ist, wie die Zahlen abgelegt sind...) Das sind so schöne Dinge, an die ich manchmal denke, und auf die Leute, die "nur Hochsprache" oder "nur Skriptsprache" programmieren, gar nicht erst kommen, weil sich manche von denen mit Binärarithmetik gar nicht mehr auskennen...
Achja: Alles, was ich ab "Zusatzbemerkung:" geschrieben habe, gilt selbstredend nur, wenn das ausführende Zielsystem bekannt ist. Durch systemübergreifende Programmierung verbieten sich quasi die meisten dieser o.g. Dinge von selbst - d.h. hier können solche Dinge, die Performance erhöhen und Speicherverbrauch verringern, meist nicht genutzt werden (weshalb ich auch nicht gerade ein glühender Verehrer systemferner/systemübergreifender Programmierung bin - aber das ist jetzt nicht so wichtig).
Achja, seufz... ich hätte vielleicht gar nicht so viel schreiben sollen. War gerade wieder so in Fahrt...
Re: RayCasting ohne Realzahlen?
ja richtig das stimmt natürlich, in der Praxis müssen wir dieses Prinzip noch an die binäre Welt anpassen.DOSferatu hat geschrieben:(Im Code steht übrigens 10000h - was auch sinnvoller ist - nämlich 65536dez)
Danke für den Hinweis. Das hatte ich ja wirklich vergessen zu erwähnen.
Aber im Code-Beispiel wird es zum Glück schon etwas deutlicher mit welchen Werten wir es zu tun bekommen.
Dirk