3d-Projekt verbraucht zu viel Speicher

  • PHP

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von Agita.

    3d-Projekt verbraucht zu viel Speicher

    Hio =)

    Zuallererst, dies ist in erster Linie ein Projekt, das mir beim Lernen hilft. Bevor ich es begonnen hab wusste ich nicht mal wie man mit Matrizen rechnet =)

    Bin gerade soweit, dass einfache Linien in einer Farbe und einem Alpha-Wert in 3d gezeichnet werden können. Die Matrix-Funktionen funktionieren soweit einwandfrei (Translation, Rotation, ViewAt, Inverse, ...).

    Schritte um eine Linie zu zeichnen:
    - Aufruf der Funktion mit den 3 Parametern $from, $to und $color (zb 0xaaffee11 - aa=alpha ffee11=rgb)
    - Ermittle 2d Coordinaten von $from und $to
    - Stutze die Linie (Crop)
    - Ermittle Richtung der Linie und Anzahl der zu zeichnenden Pixel
    - foreach (jeden zu zeichnenden Pixel)
    - - ermittle $x und $y Position
    - - speicher $color im globalen Array $data

    PHP-Quellcode

    1. public function drawColoredLine($from, $to, $color)
    2. {
    3. $from = vectorMultiMatrix($this->mFinal, $from);
    4. $to = vectorMultiMatrix($this->mFinal, $to);
    5. if ($from[0] == $to[0] && $from[1] == $to[1]) return false;
    6. if (!cropScreenLineDepth($from, $to)) return false;
    7. $from[0] /= $from[2];
    8. $from[1] /= $from[2];
    9. $to[0] /= $to[2];
    10. $to[1] /= $to[2];
    11. if (!cropScreenLineSide($from, normalized3(array(
    12. $to[0] - $from[0],
    13. $to[1] - $from[1],
    14. $to[2] - $from[2]
    15. )))) return false;
    16. if (!cropScreenLineSide($to, normalized3(array(
    17. $from[0] - $to[0],
    18. $from[1] - $to[1],
    19. $from[2] - $to[2]
    20. )))) return false;
    21. $from[0] = $from[0] * $this->widthHalf + $this->widthHalf;
    22. $from[1] = $from[1] * $this->heightHalf + $this->heightHalf;
    23. $to[0] = $to[0] * $this->widthHalf + $this->widthHalf;
    24. $to[1] = $to[1] * $this->heightHalf + $this->heightHalf;
    25. $zero = array(
    26. $to[0] - $from[0],
    27. $to[1] - $from[1],
    28. $to[2] - $from[2]
    29. );
    30. $drawParts = max(abs($zero[0]), abs($zero[1]));
    31. $ray = array(
    32. $zero[0] / $drawParts,
    33. $zero[1] / $drawParts,
    34. $zero[2] / $drawParts
    35. );
    36. for ($i=0; $i<=$drawParts; $i++)
    37. {
    38. $xy = ((floor($from[1] + $i*$ray[1]))*$this->width) + floor($from[0] + $i*$ray[0]);
    39. if (!isset($this->data[$xy])) $this->data[$xy] = array();
    40. $z = $from[2]+$i*$ray[2];
    41. $this->data[$xy][$z] = $color;
    42. }
    43. }


    Ich speicher also nicht nur die x- und y-Positionen, sondern auch die z-Positionen (Tiefe), damit ich
    später die tatsächliche Farbe in diesem Pixel berechnen kann.

    Nachdem alle Linien "gezeichnet", also in $data gespeichert wurden, wird die Funktion present() aufgerufen,
    welche nun wirklich berechnet welche Farbe auf welchem Pixel wirklich angezeigt werden soll:

    Für jeden xy-Pixel existiert ein Array: z => color
    Die anfangsfarbe für den pixel wird von der image ressource genommen
    (so kann man zb selber noch einen eigenen hintergrund wählen)

    von hinten nach vorne wird nun jede farbe aus dem array hinzu "addiert"

    PHP-Quellcode

    1. public function present()
    2. {
    3. foreach($this->data as $xy => $dataXY)
    4. {
    5. krsort($dataXY);
    6. $x = $xy % $this->width;
    7. $y = ($xy-$x) / $this->width;
    8. $back = imagecolorat($this->out, $x, $y);
    9. $r = ($back >> 16) & 0xFF;
    10. $g = ($back >> 8) & 0xFF;
    11. $b = $back & 0xFF;
    12. foreach($dataXY as $z => $color)
    13. {
    14. $a = (($color>>24)&0xFF)/255;
    15. $r = $r*($a) + (($color>>16)&0xFF)*(1-$a);
    16. $g = $g*($a) + (($color>>8)&0xFF)*(1-$a);
    17. $b = $b*($a) + (($color)&0xFF)*(1-$a);
    18. }
    19. imagesetpixel($this->out, $x, $y, (($r<<16)|($g<<8)|$b));
    20. }
    21. //$this->data = array();
    22. }



    Weshalb ich mich an euch wende ist folgendes:

    Ich Teste das Script mit einem 1200x600 großen Bild, sowie mit 100 waagerechten und 100 senkrechten Linien.
    Die image-Ressource verbraucht nur ca 2mb Speicher.

    Das Array allerdings besitzt in meinem Test am ende mehr als 100.000 Einträge und verbraucht 40mb
    Anfangs habe ich noch mit $data[$x][$y] gearbeitet und konnte mit der Änderung zu $data[$xy] den Speicherverbrauch um 3mb senken.
    Aber 40mb sind immer noch weiiiiit zu viel.

    Wie funktioniert eine Image-Ressource ? Ich meine, sie verbraucht so wenig mb, wo mein Array hingegen so viel verbraucht.
    Mit meinem data-Array will ich lediglich so eine ressource nachbauen und mit einem z-wert erweitern.
    Gibt es da evtl eine andere Möglichkeit? eine Möglichkeit die originale image-ressource "nachzubauen" ?
    Kennt wer eine Seite mit infos wie diese Image Ressource arbeitet? Oder evtl sogar Infos darüber,
    wie DirectX diesen Teil der Ausgabe handhabt?

    Bin für jede hilfreiche Antwort die mich ein bisschen weiterbringt dankbar =)
    DirectX hat ebenfalls die Koordinaten in 3D, also Anfang und Endpunkte der Linie, dann wird um die Matrizen rotiert und auf eine 2D Ebene Projeziert. Anschließend hat man 2 Koordinaten im 2D-Koordinatensystem(aus den ursprünglichen 2 3D-Koordinaten) und verbindet diese einfach über das zeichnen einer Linie. Somit ist der Speicherbedarf minimal
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Ja, aber bei directx ist es auch möglich den jeweiligen punkten eine eigene farbe zuzuweisen und diese dann ineinander verlaufen. ausserdem kann ich nicht mit imagedrawline() arbeiten, da andere linien mit anderen farben vor wiederum anderen linien liegen können =/
    Farbverläufe behandelst du ebenfalls 2D, in DirectX wird auch im Endeffekt alles 2D gezeichnet und auch nicht Pixel für Pixel extra berechnet, sonst wäre es niemals in der Lage solche Szenen zu rendern. Das mit mehreren Linien übereinander dürfte dann ein späteres Thema sein, das macht man im Normalfall erst, wenn der Rest funktioniert: de.wikipedia.org/wiki/Z-Buffer
    Alternativ kannst du dir noch das angucken, was schon eher an dein System geht: de.wikipedia.org/wiki/Raycasting
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hm, das bringt mich auf die Idee, dass ich aufhöre das multiarray $data zu benutzen und nur mit der image-ressource $out arbeite.
    jeder pixel hat dann ja die 4 werte alpha und r,g,b.... da ja das ausgegebene bild generell keine transparenz unterstützen soll kann ich ja auch den alphawert 0-255 für den zbuffer verwenden. doch kann die imageressource auch mit kommazahlen arbeiten? denn es gibt bestimmt auch linien, die weiter weg sind als 255 einheiten. um dennoch alle speichern zu können, müsste ich also "alpha=z/256" benutzen.

    weitere frage ist dann, wie ich weiterhin mit transparenten linien arbeite. derzeitig ist es so:
    der clear-hintergrund hat die farbe #000000 schwarz. $out ist nun iin jedem pixel schwarz. dann kommt eine blaue linie mit 50% transparenz. das blau wird also mit dem schwarz addiert und wird 50% dunkler. der pixel wird nun mit dieser neuen farbe ersetzt. nun liegt vor der blauen linie eine rote mit ebenfalls 50% transparenz. diese rote linie vermisscht sich dann mit der verdunkelten blauen linie. der pixel wird nun ebenfalls mit der neuen (50%rot + 50%blau + 100%schwarz) farbe ersetzt.

    problem ist: dass ich nicht bestimmen kann, welche linien zuerst gezeichnet werden, also in welcher reihenfolge. wenn ich nun zuerst die rote linie (nah, 50% alpha) zeichnen lasse dann würde die sich zuerst mit dem schwarzen hintergrund vermischen. diese verdunkelte farbe rot wird zwischengespeichert. nun kommt die blaue linie (fern, 50% alpha) die eigentlich zwischen rot und dem schwarz liegt ... und wie soll ich nun weiter vorgehen ?

    aber danke für die 2 links =) werd mir die nochmal gleich genauer anschauen. scheint auf jeeeeedenfalls äußerst interessant zu sein =)

    EDIT-----------

    Ein Array für zBuffer zu benutzen frisst zu viel speicher...

    PHP-Quellcode

    1. echo round(memory_get_usage()/1024/1024,4).'M / '.ini_get('memory_limit').'<br>';
    2. $arr = array_fill(0, 1200*600, 1000000);
    3. echo round(memory_get_usage()/1024/1024,4).'M / '.ini_get('memory_limit').'<br>';
    4. die();

    Verbraucht werden um die 37mb...

    Andere Idee: ein 2tes bild erstellen:

    PHP-Quellcode

    1. echo round(memory_get_usage()/1024/1024,4).'M / '.ini_get('memory_limit').'<br>';
    2. $out = @imagecreatetruecolor(1200, 600);
    3. echo round(memory_get_usage()/1024/1024,4).'M / '.ini_get('memory_limit').'<br>';
    4. die();

    Dies verbraucht lediglich um die 3.6mb
    und nur mit imagecreate() werden sogar nur 1.6mb benötigt

    Maximale Distanz wäre dann 256^3-1 und den neuen zbuffer wert könnt ich dann einfach mit "imagesetpixel" schreiben und mit "imagecolorat" lesen.
    Aber das oben beschriebene Problem existiert weiterhin =/

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Agita“ ()

    Hab nun die neue Idee mit dem zBuffer umgesetzt, was allerdings nun probleme macht.
    sie hier

    ich rufe die funktion mit $from und $to auf. Beide werden von 3d in 2d umgewandelt. ich erhalte für jedes ein Array mit den 3 Werten X, Y und Z für Tiefe.
    Um die Linie zu zeichnen berechne Berechne ich zuerst die Null-Line, so wie ich sie nenne... $zero = $to - $from
    Mit jedem Punkt auf der Linie setze ich den Pixel auf der x und y Position und speichere den z Wert in dem Buffer. ABER
    Mir ist eingefallen/aufgefallen, dass zwar die 2d-Linie kontinuierlich gerade verläuft. aber nicht der z-wert in 2d ebene.
    Funktioniert alles soweit super. Hab 2 Image-Ressourcen. Eine für die Farben pro Pixel und eine Für die Z-Tiefe.
    Aber... Nun würde ich doch gerne auch Transparenz ermöglichen. Problem ist aber, wie ich die einzelnen Werte jedes Pixels speicher. Gespeichert werden müssen sie ja. Denn wenn ich sie zusammenrechne noch bevor auch das letzte Objekt nicht gezeichnet wurde, kann es zu Fehlern kommen. Wie in dem einen Beispiel was ich bereits genann habe.

    Zwischen der kamera und einem schwarzen hintergrund liegen zuerst die farbe rot und dann die farbe blau, beide mit 50% transparenz. der sehstrahl geht zuerst durch rot, dann wird blau addiert und beim schwarzen hintergrund der nicht durchlässig ist endet er.. so wie ich es jetzt hab kann ich sowas aber nicht nachbauen da ich ja nur 2 einfache "paletten" habe =/