DEUTSCHE VERSION |
|
Links | | | Forums | | | Comments | | | Report news |
Chat | | | Polls | | | Newsticker | | | Archive |
amiga-news.de Forum > Programmierung > Prozedurales Texturieren | [ - Search - New posts - Register - Login - ] |
-1- 2 | [ - Post reply - ] |
2008-02-15, 22:03 h Reth Posts: 1858 User |
Hallo zusammen, also hier nun der eigene Thread zum Ganzen. Auch wieder ein Sammelthread, beginnen möchte ich mal mit Perlin Noise, da ich immer noch völlig planlos der Sache gegenüberstehe, auch nach dem Lesen des 10. Tutorials. Die Funktionen sind mir ja nun soweit bekannt, allerdings habe ich noch nichts über deren grafische Umsetzung gefunden! Ich habe die Funktion über das Erzeugen von Zufallszahlen implementiert und auf Fließkomma angepasst. Leider fehlt mir völlig die Erfahrung zur Umsetzung in Pixel, da diese Funktion Werte zw. -1 und 1 zurückgibt. Eine einfache Multiplikation mit 255 reicht da leider nicht zum Erzeugen eines RGB-Wertes. Nun meine Frage (mangels Erfahrung), nehm ich hier einfach den Absolutbetrag des Rückgabewertes der Funktion, oder etwas anderes? Welche anderen Dinge muss ich noch beachten, wenn ich prozedural texturieren möchte (erst einmal speziell bei Perlin Noise)? Und vielleicht kann mir auch jemand erklären, warum in diesem Tutorial hier: Perlin Noise In den Beispielen vom 1- bzw. 2-Dimensionalen Code alle Noise-Funktionen auf dieselbe Koordinate angewendet werden? Also im Hauptprogramm der 2-dimensionalen Funktion: code:function PerlinNoise_2D(float x, float y) ... loop i from 0 to n frequency = 2i amplitude = pi total = total + InterpolatedNoisei(x * frequency, y * frequency) * amplitude end of i loop ... Ich dachte, dass in einem 2. Durchgang jede 2. Koordinate mit der 2. Noise-Funktion (also hier 2. Schleifendurchlauf) usw. behandelt wird? Was bringt es auf einen Punkt immer gleich alle Noise-Funktionen anzuwenden? Ach ja! Und das Interpolieren in diesem Bsp. hab ich leider auch nicht verstanden! Kann das evtl. auch jmd. anschaulicher erklären (wenn ich nun von Koordinaten ausgehe, warum soll ich zw. den Punkten (x,y) und (x+1,y) interpolieren? Das kann ich doch gar nicht anzeigen?)?! Scheint mir eher so zu sein, als ob jeder Pixel mit einigen Pixeln seiner Umgebung (rechts und unterhalb) interpoliert wird! Und dann noch die Frage: Wie lege ich denn die erzeugten Ergebnisse (wie hier: Perlin Noise Bilder) übereinander (algorithmisch)? Das erste Bild bekomme ich ja noch hin, da ich hier einfach die 2-dimensionale Noise-Funktion agieren lasse (ohne Smoothing und ohne Interpolation), aber dann? Wie mach ich dann weiter? Ich find es absolut unbefriedigend, die ganzen Algorithmen nur abzutippen (hab ich nur zum Teil, drum funktionierts bei mir auch nicht), ich möchte gern wissen, was da überall passiert! Danke schon mal! Ciao [ Dieser Beitrag wurde von Reth am 15.02.2008 um 23:45 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-16, 22:15 h Reth Posts: 1858 User |
Keiner? Schade, stecke hier wirklich fest und will das Zeug wie gesagt nicht nur runter tippen! Das erste Bild (von rechts) der blauen Bilder bekomme ich hin, die anderen nicht (ohne Interpolation und Smoothing). Wie funktioniert das denn nun? Und wie lege ich denn die Bilder rechnerisch übereinander (bzw. addiere die Funktionen)? Wenn gewünscht kann ich auch gern meinen Code hier posten. Ciao [ - Answer - Quote - Direct link - ] |
2008-02-17, 00:29 h Der_Wanderer Posts: 1229 User |
Die Werte aus den unterschiedlichen Schichten werden addiert. Sagen wir mal wir haben drei Schichten. Dann berechnest du ein 2x2 Zufalls Quadrat, ein 4x4 und ein 8x8. Das resultierende Bild hat dann idealerweise 8x8. Die Pixel bekommst du dann so, indem du alle Schichten pixelweise aufaddierst, allerdings musst du das 2x2 und das 4x4 auf 8x8 "strecken", also interpolieren. Da kannst du erstmal lineare Interpolation nehmen, daraus kann man einfach später Cosinus Interpolation machen, was sehr gut aussieht und nicht sehr auswendig ist. "procedural" wird das Pattern dadruch, dass du zur Interpolation am rechten und unteren Rand nicht eine weitere, imaginäre Zufallszahlenreihe benutzt, sondern wieder die linke und obere. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-17, 01:11 h Reth Posts: 1858 User |
@Der_Wanderer: Danke schön! Ich dachte, es heißt prozedural, weil die Texturen berechnet werden und nicht aus vorgegebenen Grafiken übernommen? Wie addiere ich denn pixelweise die Bilder auf? Wenn ich die 3 Bilder habe und bei allen dreien (nach Interpolation) an einer Position ein Noise-Wert (meinetwegen grau auf schwarzem Hintergrund) ist, wie addiere ich die dann? Mal rein von den Farbwerten her gesehen (wenn mein Maximum bei 255 liegt), was mach ich, wenn ich die 3 Grauwerte addiere (also Weißanteil) und dann bei >255 lande? Aber bei mir muss man glaub ich viel weiter vorn anfangen und gaaaanz langsam vorgehen, v.a. weil die ganze Mathe verschütt gegangen ist. Nach stundenlangem Lesen und Grübeln über die Interpolation hier: Interpolation bin ich zu folgendem grundsätzlichen Ergebnis gekommen: Für zwischenliegende Punkte werden die Noise-Werte der interpolierten Werte (interpoliert zw. den beiden angrenzenden Punkten) verwendet und nicht die Noise-Werte der Punkte selbst! Also wenn Noise-Werte für die Punkte (1,1) und (3,1) direkt berechnet wurden, wird für (2,1) der Noise-Wert des Interpolationsergebnisses mit Startwert (1,1) und Endwert (3,1) verwendet! Stimmt das erstmal so, oder ist da immer noch ein Denkfehler drin? Oder wird für zwischenliegende Punkte der interpolierte Wert der beiden Noise-Werte der benachbarten Punkte gebildet? Oder muss ich bei 2-dimensionalen Punkten mehrfach über alle benachbarten Punkte (also in x- und y-Richtung) interpolieren? D.h. für einen Punkt in der Ebene alle 8 benachbarten Punkte betrachten? Und zu guter Letzt: Wie interpoliere ich denn z.B. das 2x2 Feld auf 8x8, wenn man doch immer zwischen 2 Punkten interpoliert? Setze ich dann die Punkte aus dem 2x2 an die Ecken des 8x8 und interpoliere zwischen ihnen? Allerdings ist mir nicht so klar, wie der 2-dimensionale Algorithmus hier funktioniert: 2-dimensional Perlin Noise Pseudocode , da er ja bei jedem Punkt versucht zu interpolieren! Ist es da so, dass ich mit diesem Algo, wenn ich nur jeden 2. Punkt betrachte den Interpolations-Aufruf für zwischenliegende Punkte mit 0.5 als dritten Parameter mache? Blick da noch nicht ganz durch! Und wieso betrachtet der Algorithmus immer nur die drunter und rechts neben dem aktuellen Punkt liegenden Punkte? Und wieso läuft er für jeden Punkt über alle Noise-Funktionen (hab ich glaub schon mal gefragt)? Ciao [ Dieser Beitrag wurde von Reth am 17.02.2008 um 17:05 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-17, 22:21 h Reth Posts: 1858 User |
Also ich geb bald auf, da es mich langsam wütend macht, dass ich offenbar total aufm Schlauch stehe! Tutorials hin oder her, entweder ham die noch Fehler, oder (wahrscheinlicher) raff ichs nicht! Hier mal meine Vorgehensweise: Java code:float p = 1f; // Persistenz, der Einfachheit halber 1 // der Einfachheit halber nur ein Durchgang (also nur eine Noise-Funktion) for (int round=1; round<=1; round++) { // Frequenz und Amplitude der Einfachheit halber 1 int frequency = (int)Math.pow(2, round); float amplitude = (float)Math.pow(p, round); // Abstand zwischen den zu berechnenden Punkten, alle // dazwischenliegenden werden interpoliert int offset = 1; // ein Bereich von 100x100 Pixeln in der Mitte eines schwarzen // Quadrates, Schrittweite = offset for (int x=20; x<=100; x+=offset) { for (int y=20; y<100; y+=offset) { // Noise-Wert des aktuellen Pixels berechnen float rand = getNoise(x*frequency, y*frequency) * amplitude; // Farbe mit Hilfe des Noise-Wertes setzen und Pixel malen g.setColor(new Color(Math.abs((int)(rand*125f)), Math.abs((int)(rand*125f)), Math.abs((int)(rand*255f)))); g.fillRect(x, y, 1, 1); // Wenn Schrittweite>1 => interpolieren aller Zwischenpixel if ((offset > 1)) { // Alle Zwischenpunkte interpolieren for (int j=1; j<offset; j++) { // Y, aber nicht für ersten Durchlauf if (y > 20) { // Noise-Wert des 2. Randpunktes bestimmen float randInt = getNoise(x*frequency, (y-offset)*frequency) * amplitude; // interpolierte Noise-Wert, der 3. Parameter // bestimmt sich aus 1/Schrittweit multipliziert // mit dem aktuellen Durchlauf (also bei // Schrittweite 2 ergibt sich 0,5; bei // Schrittweite 3 ergeben sich 0,33 und 0,66 etc.) rand = interpolateCos(rand, randInt, j*(1f/(float)offset)); // Farbwert setzen und Pixel zeichnen g.setColor(new Color(Math.abs((int)(rand*125f)), Math.abs((int)(rand*125f)), Math.abs((int)(rand*255f)))); g.fillRect(x, y-j, 1, 1); // X, aber für alle y-offset if (x > 20) { for (int k=1; k<offset; k++) { randInt = getNoise((x-offset)*frequency, (y-k)*frequency) * amplitude; rand = interpolateCos(rand, randInt, (offset-j)*(1f/(float)offset)); g.setColor(new Color(Math.abs((int)(rand*125f)), Math.abs((int)(rand*125f)), Math.abs((int)(rand*255f)))); g.fillRect(x-j, y-k, 1, 1); } } } } } } } } Hier noch die Interpolationsfunktion, die ist abgeschrieben: Java code:private float interpolateCos(float a, float b, float x) { return (float)(((1+Math.cos(x*Math.PI))/2 ) * a + ( 1 - (1+Math.cos(x*Math.PI))/2 ) * b); } Das seltsame an dieser ist, dass laut diesem Beitrag hier: Cosinus Interpolation Die Funktion nur Werte zw. 0 und 1 zurückliefern sollte (was ich nachvollziehen konnte). Ich bekomme aber bei negativen Eingaben (wenn die eingegebenen Noise-Werte negativ sind) auch negative Rückgabewerte! Das verstehe ich nun wiederum nicht! Hier die Noise-Funktion, auch abgeschrieben: Java code:private float getNoise(int x, int y) { int n = x + y * 57; n = (n << 13) ^ n; return 1.0f - ((float)( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff)) / (float)1073741824; } Was mach ich denn bitte alles falsch? Bekomme alles Mögliche raus, nur nicht die Ergebnisse wie auf den Bildern! Mir fehlt hier komplett die Ahnung, wie man das Verfahren richtig anwendet und wie es genau funktioniert! Wenn ich mehr als einen Durchlauf mit entsprechender Persistenz, Frequenz und Amplitude mache, werden die Muster sehr regelmäßige dünne Linien! Bitte um erklärende Antworten, auch für meine darüber liegenden Post! Würde sehr gern verstehen, was da vor sich geht und wie! Ciao [ Dieser Beitrag wurde von Reth am 17.02.2008 um 23:10 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-18, 12:32 h Madcat Posts: 247 User |
Zitat: Das finde ich eigentlich schon logisch. In dem Beitrag vom Wiki wird ja der Start- und Endwert nicht vorzeichenmaessig beachtet (man ging wohl von positiven Werten aus). Wenn nun der Start- wie auch der Endwert negativ ist muss natuerlich auch die Interpolation negative Werte liefern da diese nur Werte zwischen Start- und Endwert liefern kann. -- Zeit ist eine Droge. Zuviel davon bringt einen um. [ - Answer - Quote - Direct link - ] |
2008-02-18, 12:46 h Reth Posts: 1858 User |
@Madcat: Stimmt! Jetzt müsst ich "nur" noch meinen anderen Fragen geklärt bekommen! Habe momentan gar keinen Ansatzpunkt mehr! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-18, 13:53 h Madcat Posts: 247 User |
Irgendwie hat das Ganze mein Interesse geweckt Ich versuch immer so Teilfragen zu beantworten. Zitat: Ich wuerde sagen, dass man hier erstmal die Bilder wichten muss, bevor man sie addiert. Zum Beispiel: Bild 1 geht zu zwei drittel ein, Bild 2 und 3 zu jeweils ein sechstel. Dann muss ich ja nur noch die Werte entsprechend addieren code:Pixel(x,y)_Endbild=Pixel(x,y)_Bild1*2/3+Pixel(x,y)_Bild2*1/6+Pixel(x,y)_Bild3*1/6 Am einfachsten ist natuerlich, wenn die Bilder alle den gleichen Einfluss haben sollen: code:Pixel(x,y)_Endbild=(Pixel(x,y)_Bild1+Pixel(x,y)_Bild2+Pixel(x,y)_Bild3)/3 Wichtig waere halt immer darauf zu achten, wenn mein Wertebereich fuer eine Farbe von 0-255 geht, ich bei der Summe nicht ueber 255 komme da mir ja sonst Informationen verloren gehen. Vielleicht bist du da schon darauf gekommen oder dieses kurze Statement gibt dir den Denkanstoss dazu, den du benoetigst. -- Zeit ist eine Droge. Zuviel davon bringt einen um. [ - Answer - Quote - Direct link - ] |
2008-02-18, 14:28 h Der_Wanderer Posts: 1229 User |
Och Reth, ein bisschen anstengen solltest du dich schon. Und schreibe am besten alles von Grund auf selbst, dann verstehst du es am besten. Ich beschreibe dir mal den Algo für Perlin Noise, so wie er am einfachsten nachvollziehbar ist, das ist nicht unbedingt wie man ihn am effizientesten implementiert (das vorweg, falls kritiken kommen). Ein 24 bit RGB Wert besteht aus Rot, Grün und Blau Werten jeweils zwischen 0-255 (8-Bit). Was wir machen werden ist, einen (Pseudo-)Zufallswert X ermitteln und den dann sichtbar machen, indem wir ihn in einen RGB Wert verwandeln. Es gibt viele Möglichkeiten das zu tun, die einfachste ist, X sofort zwischen 0..255 zu berechnen und dann R=X, G=X, B=X zu machen. Dadurch bekommen wir Grauwerte. Wenn du einen bestimmten Farbwert vorgeben willst, dann geht das so: R' = R * X / 256 G' = G * X / 256 B' = B * X / 256 Also wir nehmen die vorgegeben Basisc RGB Werte R G B und gewichten sie mit X und erhalten R' G' B', die wir dann nutzen um den Pixel zu setzen. Ok, jetzt brauchen wir einen coolen Zufallszahlengenerator. Ich schlage seed = seed * 196314165 + 907633515 vor. Schliesslich ist die Speed auch wichtig. Das sollte ein 32bit int sein. Einen Wert X zwischen 0..255 bekommen wir, indem wir X = seed & $FF machen und danach den seed neu berechnen. Somit sind die Muster auch 100% reproduzierbar, indem man nämlich mit dem gleichen initalwert für seed anfängt. Sagen wir, wir wollen ein 8x8 Pixel grossen Muster berechnen. Dann berchnen wir 64 solcher X Werte und plotten sie der Reihe nach in das 8x8 Quadrat. Das sieht dann so aus: Bild: http://www.hd-rec.de/pics/8x8.png Das ist bis jetzt noch kein Perlin Noise Muster, sondern nur "triviale" Noise. Die Idee die Perlin hatte ist, dass man verschiedene Schichten von Rauschen übereinanderlegt, die sehr unterschiedlich grob gekörnt sind. In der Natur hast du z.B. die Berge als Zufallsmuster mit riessigen Erhebungen und Tälern. Darauf liegen grosse Felsbrocken, die genauso zufällig sind, aber schon um einiges feiner sind. Auf den Felsen liegen wiederum Steine herum, die genauso ausehen wie die Felsen, nur eben viel kleiner, und darauf wiederum Sandkörner. Fraktal eben. Natürlich sollten die Sandkörner nicht so grosse erhebungen haben wie ein ganzer Berg, deshalb sind die feineren Muster klein scaliert, "gedämpft", das nennt man bei Perlin Noise "Persistenz". Eine Persistenz von 0.5 bedeutet, dass wir von einer Schicht zur nächsten die höhe der Erhebungen/Täler halbieren. So eine Schicht nennt man auch "Oktave", weil bei Perlin Noise üblicherweise die nächste schicht immer doppelt so fein ist, wie die Frequenzen in der Musik sich von einer Oktave zur nächsten verdoppeln. Was wir jetzt 8x8 berechnet haben wäre eigentlich schon die höchste (feinste) Schicht, die man berechnen kann. Also brauchen wir noch die Schicht 1x1,2x2,4x4, oder eben ab welcher Grundfeinheit wir anfangen wollen. 1:1 macht kaum Sinn, da es nur ein einziger Wert ist. Also schauen wir uns erstmal mal 4x4 an. Da gehen wir so vor, dass wir wie die 8x8 Schicht das berechnen, nur eben 4x4 gross, also 16 Zufallswerte. Wenn wir das jetzt mit 8x8 mischen wollen, müssen wie 4x4 auf 8x8 "zoomen". Also zwischen den einzelnen Werten brauchen wir noch einen Zwischenwert, den wir durch Interpolation der beiden drumherum leigenden bekommen, also irgendwie ein mittel finden. Im einfachsten Fall ist das die lineare Interpolation, also setzen wir immer das mittel dazwischen. Bild: http://www.hd-rec.de/pics/4x4.png => Bild: http://www.hd-rec.de/pics/8x8ic.png => Bild: http://www.hd-rec.de/pics/8x8i.png Bei Verdopplung ist die lineare inerpolation gerade das arithmetische mittel zwischen den benachbarten Punken. So erstellen wir uns also alle 3 Bilder, wobei es erstmal 2D Arrays aus Zufallzahlen sind. 2x2 auf 8x8 gezoomed 4x4 auf 8x8 gezoomed 8x8 haben wir eine Persistenz von 0.5, dann nehmen wir das 2x2 Array, addieren die Zahlen von 4x4 drauf mit 0.5 multipliziert und die von 8x8 mit 0.5*0.5 = 0.25 multipliziert. Also X = X_2 + X_4*0.5 + X_8*0.25 Da der Wertebereich von X jetzt von 0..255*(1+0.5+0.25) = 0..446.25 geht, scalieren wir X noch X = X * 255 / 446.25 und landen wieder zwischen 0...255. Man kann aber auch Saturieren oder Wrappen, dann gibt es eben andere Muster. So, jetzt können wir das wieder umwandeln in Pixel und erhalten das Perlin Noise Bild. In 256x256 sieht das dann so aus: Bild: http://www.hd-rec.de/pics/256x256.png Das dient dann als Ausgangsmaterial für alle möglichen Spielereien. Bild: http://www.hd-rec.de/pics/perlin1.jpg Bild: http://www.hd-rec.de/pics/perlin2.jpg Bild: http://www.hd-rec.de/pics/perlin3.jpg Bild: http://www.hd-rec.de/pics/growgrass.png Bild: http://www.hd-rec.de/pics/mountain_grass4.jpg -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ Dieser Beitrag wurde von Der_Wanderer am 18.02.2008 um 14:32 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-18, 14:31 h Reth Posts: 1858 User |
@Madcat: Danke. Stimmt! Wenn man keine Gewichtung braucht, dann reicht es den Anteil jedes Pixels am Farbwert des Endresultates als gleich zu betrachten. Was mir noch eingefallen ist, zu meiner Frage, wieso in dem Bsp. alle Noise-Funktionen für jeden Punkt auf einmal durchlaufen werden: Wird damit dann nicht schon diese Übereinanderlegung der Bilder erreicht? Ein weiterer Gedanke meinerseits war, dass der Gesamtcode der 2-dimensionalen Funktion, wenn man ihn nur mit normalen ganzzahligen Koordinaten (für Pixel) füttert, dieser ja nie wirklich interpolieren muss, da die Interpolationsfunktion pro Aufruf nur den ersten oder 2. Wert zurückgeben würde. Heißt das dann, ich bekomme das Ergebnis des letzten Bildes (also der übereinandergelegten Bilder) auch, wenn ich jeden Pixel einzeln eingebe, alle Noise-Funktionen drüberlasse und das Ergebnis ausgebe (muss ich mal probieren, kanns aber kaum glauben)? Dann braucht man ja nix zu interpolieren!? Denn das ist mir noch nicht so ganz klar, wie das genau funktionieren soll (s. mein Codebsp., dort wird bei jedem Schritt von einem y-Wert zum nächsten für alle zwischenliegenden Werte interpoliert und der t-Wert in Abhängigkeit der Schrittweite gewählt - entsprechend jeweils für alle x-Werte; und das Ergebnis sieht übel aus!)! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-18, 14:59 h Reth Posts: 1858 User |
Zitat:Danke für Deine Erklärung! Aber genau das versuche ich ja! Ich dachte, dass kommt aus meinen Posts deutlich raus? Darum ja die grundsätzlichen Verständnisfragen und mein bisheriger Code. Ich sagte doch, dass ich die Funktionen nicht einfach nur abtippen, sondern verstehen will! Was glaubst Du, was ich die letzten Tage stundenlang versucht habe (und davon einige Stunde mit dem Versuch verbracht habe, die mathematischen Grundlagen aufzufrischen/zu verstehen, v.a. für Interpolation - allerdings haben die gefundenen Artikel im Wiki und sonstwo eher zur Verzögerung beigetragen; so eine anschauliche Beschreibung, wie Du sie ablieferst wirkt da schon deutlich schneller!)! Wie soll ich denn was von Grund auf selbst schreiben, von dem ich nicht weiß wie es geht? Da muss ich mich ja erst einlesen, und dabei tauchten dann genau die Fragen auf! Zitat: So hab ichs ja auch im Bsp.-Code gemacht, nur mit Blauwerten. Zitat: Das hab ich auch noch hinbekommen! Zitat: Das hab ich auch verstanden, konnte es aber nicht umsetzen! Zitat: Jetzt ist mir die Persistenz auch klarer, die hatte ich vorher nur als Vereinfachung der Abhängigkeit zw. Amplitude und Frequenz der aufeinanderfolgenden Noise-Funktionen verstanden. Zitat: Das waren ja auch meine Fragen: Wird nur das Mittel der links und rechts daneben liegenden Punkte genommen, oder aller umliegenden Punkte (wie auf der freespace-Seite)? Zitat: Muss ich dann für das 2x2 Bild 2 Mal interpolieren, immer mit einer Verdoppelung (also erst einmal auf 4x4 und das dann auf 8x8)? Zitat: So funktioniert also die Aneinanderreihung. Aber nicht im Bsp. auf der Freespace-Seite, da dort für jeden Punkt gleich alle Funktionen durchlaufen werden! Zitat: Ich trau mich ja gar nicht mehr zu fragen, wie man dann das mit dem Gras macht, oder Sachen wie auf dem letzten Bild, bzw. auf dem ersten (PN mit anderen Farbwerten, aber nur in bestimmten nicht-rechteckigen Regionen?)! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-18, 17:10 h Der_Wanderer Posts: 1229 User |
> Muss ich dann für das 2x2 Bild 2 Mal interpolieren, immer mit einer Verdoppelung (also erst einmal auf 4x4 und das dann auf 8x8)? Kannst du machen. Dann sind aber nur 2er Potenzen möglich. Bei linearer Interpolation betrachtet man immer vier Pixel, um einen zu berechnen, und zwar einen 2x2 block. Man berechnet den Wert dann in Abhängigkeit von der Nähe, linear interpoliert. In etwa so (Pseudo code): code:source_width = ... // dimensionen des quell bildes source_height = ... For dest_y=0 to dest_width-1 // gehe über alle ziel bild pixel For dest_x=0 to dest_height-1 source_x = dest_x * source_width/dest_width // wo liegt unser pixel source_y = dest_y * source_height/dest_height // auf dem quell bild ? source_intx = Floor(source_x) // ganzzahlige coos ermitteln source_inty = Floor(source_y) x_frac = source_x - source_intx // überschüssige bruchteile ermitteln y_frac = source_y - source_inty weight11 = (x_frac * y_frac) // gewichte für die umliegenden weight21 = ((1-x_frac) * y_frac) // pixel bestimmen weight12 = (x_frac * (1-y_frac)) weight22 = ((1-x_frac) * (1-y_frac)) dest_pixel(dest_x,dest_y) = source_pixel(source_intx ,source_inty ) * weight11 // neuen ziel pixel zusammen setzen + source_pixel(source_intx+1,source_inty ) * weight21 // aus den vier umliegenden + source_pixel(source_intx ,source_inty+1) * weight12 // quell pixeln + source_pixel(source_intx+1,source_inty+1) * weight22 Next Next Der obige Code kann also von jeder auf jede Größe eines Bilder/2D Array scalieren und interpoliert dabei linear. hier sieht man aber auch sofort das problem: bei der letzen rechten Pixelspalte gibt es keinen rechten Nachbarn, und bei der untern letzen Pixelreihe keinen unteren Nachbarn. Das ist aber auch der Trick, um das pattern kontinuierlich zu bekommen, also dass man es beliebig aneinander reihen kann. Man nimmt dann einfach den Pixel auf deranderen Seite. Um daraus Cosinus interpolation zu machen (was viel besser aussieht), muss man nur die berechnung der gewichte ändern, und folgendes hinzufügen: ... x_frac = source_x - Floor(source_x) // bruchteile ermitteln y_frac = source_y - Floor(source_y) x_frac = (1-Cos(Pi*x_frac))*0.5 y_frac = (1-Cos(Pi*y_frac))*0.5 ... andere interpolationsarten, z.b. Cubisch, sehen noch besser aus, sind aber schwieriger zu implementieren und viel langsamer, da sie mehr als vier Pixel zur berechnung benötigen. Meine Beispielbilder nutzen cosinus interpolation Das Grass ist ein anderer Algo, der ein Perlin Noise muster benutzt um die grashalmlänge zu bestimmen. "Löcher" gibt es dadruch, dass man die werte einfach ab einem bestimmen schwellwert abclippt, z.B. unter 50 setzt man gar keinen Pixel. Keine Interpolation: Bild: http://www.hd-rec.de/pics/int_nn.jpg Lineare Interpolation: Bild: http://www.hd-rec.de/pics/int_lin.jpg Cosinus Interpolation: Bild: http://www.hd-rec.de/pics/int_cos.jpg -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ Dieser Beitrag wurde von Der_Wanderer am 18.02.2008 um 17:21 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-18, 18:01 h Reth Posts: 1858 User |
Vielen Dank für Deine Antwort!Zitat: Das war z.B. eine Information, die mir bisher gänzlich gefehlt hat und die ich bisher weder aus einem Posting (einschließlich Deinen) noch aus Wikipedia oder Tutorials entnommen habe (bzw. deren Warum)! Da hieß es immer nur die benachbarten Punkte (das können bis zu 8 sein). Auf der Freespaceseite werden zum Glätten sogar alle 8 Punkte betrachtet, wenn ich das richtig verstanden habe! Gibt es irgendwo einen Wikieintrag o.ä., in dem das Warum und wie erklärt wird (z.B. die Gewichtung der umliegenden Pixel)? Zitat: Danke, das wusste ich auch noch nicht (wäre mir auch nicht so schnell eingefallen!)! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-18, 18:56 h Der_Wanderer Posts: 1229 User |
@Reth: Ich weiss nicht was du hast. Auf der freespace seite wird das alles ausführlich erklärt. Dort wird die lineare und cosinus interpolation mit zwei Punkten und die cubische mit mehr punkten erklärt. (1 Dimensional) In dem Sourcecode von meinem letzen Posting siehst du genau wie es geht. Lineare Interpolation: Sagen wir du hast an der stelle 5 der Wert 100 und an der stelle 6 den Wert 200. Dann hast du an der stelle 5.2 den Wert frac = 5.2 - Floor(5.2) = 5.2 - 5.0 = 0.2 y = (1-frac) * 100 + frac * 200 = 0.8 * 100 + 0.2 * 200 = 80 + 40 = 120 Wenn es zweidimensional ist, dann kommen eben noch unten zwei pixel dazu, und es gibt einen frac_x und frac_y. Bei der Cosinus interpolation verzerrt man lediglich die Nachbarschaft, ist also nicht mehr linear wie oben. Dadurch ergeben sich rundere Bilder. Was man nun mit dem Perlin Noise muster alles machen kann, da musst du deine Fantasie gebrauchen und ausprobieren. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-18, 19:20 h Reth Posts: 1858 User |
Zitat: Konnte dort aber nicht entnehmen, warum man bei der linearen und Cosinusinterpolation die 4 benachbarten Pixel betrachtet und auch nicht, was denn dann als 3. Parameter an die Interolationsfunktion gegeben wird, weil ich mir nicht erklären konnte, wo bei ganzzahligen Koordinaten auf einmal die Dezimalanteile zw. 0 und 1 für die Interpolationsfunktion herkommen sollen! So ganz ist mir das immer noch nicht klar, trotz Deiner tollen Veranschaulichungen, muss mich noch weiter damit beschäftigen! Wie gesagt werden dort bei der Glättung ja alle Nachbarn betrachtet. Zitat: Und hier hab ich eben nicht begriffen, welches Pixel an der Koordinate 5.2 liegen soll. Aus Deinem Bsp.-Code geht das eher hervor, denke ich. Man betrachtet z.B. im gewünschten Zielbild (z.B. 8x8) den Wert (5,0) und ermittelt im aktuellen Quellbild (z.B. 2x2) den entsprechenden Wert dazu. Und nach Deinem Code (source_x = dest_x * source_width/dest_width) bekommt man dann für (5,0) z.B. 1.25 raus. Mein Problem ist, das zwar schon alles dasteht, ich aber immer noch nicht alles verstanden habe, muss es mir noch selbst erklärbar machen und versuchen nachzuvollziehen, was da passiert. Hab da nicht so den Zugang zu solchen Sachen. Ciao [ - Answer - Quote - Direct link - ] |
2008-02-18, 21:15 h Der_Wanderer Posts: 1229 User |
Wenn du für das 8x8 Bild den Pixel (5,0) aus dem 2x2 Bild extrahieren willst, dann geht das so:code:dest_x = 5 // coos auf 8x8 dest_y = 0 => source_x = 5 * source_width/dest_width // coos auf 2x2 = 5 * 2 / 8 = 1.25 source_y = 0 * ... = 0 Zur berechnung brauchen wir nun alle umliegenden Pixel von (1.25,0), was i.A. 4 Stück sind: (1,0); (2,0); (1,1); (2,1) source_pixel(1,0) source_pixel(2,0) = source_pixel(0,0) source_pixel(1,1) source_pixel(2,1) = source_pixel(0,1) Da es (2,0) auf der 2x2 nicht gibt, müssen wir wrappen und landen wieder auf 0, ist also ein spezialfall weil es am rand liegt. Jetzt müssen wir uns berechnen, wie nahe der gewünschte dest_pixel(5,0) = source_pixel(1.25,0) an den jeweiligen Nachbarn liegt. Der dest_pixel(5,0) entspricht ja dem source_pixel(1.25,0), wie wir jetzt wissen, nur können wir ja keine viertel Pixel auslesen. Von (1,0) hat (1.25,0) in x richtung den abstand dist_x = 0.25 und in y Richtung dist_y = 0. Er ist also näher an (1,0) als an (2,0), aber bekommt noch etwas von (2,0) ab. Er bekommt daher den Wert anteilig von beiden, so dass die anteile zusammen 1 ergeben: wert_oben = source_pixel(1,0) * (1-dist_x) + source_pixel(2,0) * dist_x = source_pixel(1,0) * 0.75 + source_pixel(2,0) * 0.25 Jetzt fehlen aber noch die unteren beiden. Also kommt noch dazu nach dem gleichen x-Verhältnis. wert_unten = source_pixel(1,1) * 0.75 + source_pixel(2,1) * 0.25 Die beiden müssen wir jetzt noch im y-verhältnis zusammen addieren: wert = wert_oben * 1 + wert_unten * 0 Normalerweise ist das untere natürlich nicht 0, das passiert nur weil es genau auf der reihe liegt. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-19, 09:00 h Reth Posts: 1858 User |
Vielen Dank! So versteh ich das auch. Noch 2 kurze Verständnisfragen: Zitat: Gibt es einen Grund, warum nur die darunter und rechts daneben liegenden Pixel betrachtet werden und nicht alle umliegenden? Zitat: Das gilt dann auch für (2,1), oder (wenn man bei (0,0) beginnt)? Ciao [ - Answer - Quote - Direct link - ] |
2008-02-19, 12:16 h Der_Wanderer Posts: 1229 User |
> Gibt es einen Grund, warum nur die darunter und rechts daneben liegenden Pixel betrachtet werden und nicht alle umliegenden? Ja. Es gibt nur 4 umgebende Pixel. Beispiel: Nimm einen Bleistif und mache zufällig einen Punkt auf karriertes Papier. Jetzt gibt es genau 4 Ecken (=Pixel), die diesen Punkt einschliessen. > Das gilt dann auch für (2,1), oder (wenn man bei (0,0) beginnt)? Ja, alle Koordinaten (x und y) werden gewrapped (modulo genommen), sonst würde man ja aus der Bitmap/Array rauslesen. Das passiert aber nur ganz am Rand. Bei unserer 2x2 "Bitmap" ist man ja aber immer sofort am Rand ;-) -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-19, 13:00 h Reth Posts: 1858 User |
Zitat: Wenn der Punkt in einem Kästchen liegt, dann gibt es die 4 Ecken/Kanten des Kästchens, in dem der Punkt liegt, falls Du die meinst. Das wäre dann aber links oben (x-1,y-1), rechts oben (x+1,y-1), links unten (x-1,y+1) und rechts unten (x+1,y+1)?! Wenn ich mir nun ein Kästchen als Pixel vorstelle, so ist es (außer am Rand) aber von 9 anderen Kästchen (Pixeln) umgeben. Hab ich nun nen krummen x-Wert wie 1,25 habe, dann liegt der zu 0,25 im 2. Pixel der betrachteten Zeile, daher dachte ich, dass ich nun das 2. Pixel und alle umliegenden 8 für die Interpolation brauche! (Duck) Ciao [ - Answer - Quote - Direct link - ] |
2008-02-19, 13:22 h Der_Wanderer Posts: 1229 User |
Nein, ein Pixel ist auf dem karrierten Papier eine Ecke, kein Kästchen. Sagen wir das Papier ist karriert mit 1cm Linienabstand. Jetzt machst du einen Punkt bei (3.7cm|5.1cm) Dann sind die VIER umliegenden Pixel code:(3 | 5) = (3|5) (3+1| 5) = (4|5) (3 |5+1) = (3|6) (3+1|5+1) = (4|6) -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-19, 22:14 h Reth Posts: 1858 User |
@Der_Wanderer: Nochmals Danke! Wie kommt man denn auf den Algorithmus, der einem für z.B. das (8x8)-Bild einen Pixel aus dem (2,2)-Bild ermittelt? Habe mir die Freespace-Seite noch ein paar mal angesehen, finde aber nicht, dass dort die lineare Interpolation erklärt wird, sondern einfach nur vorgesetzt (á la hier ist die Formel). Dagegen ist sie hier: Lineare Interpolation wirklich erklärt (also hergeleitet). Dasselbe gilt für die Cosinus-Interpolation. Allerdings bin ich immer noch zu blöd die dort erklärte Lineare Interpolation in Deinem Code wiederzufinden, also die Formel a*(1-x) + b*x für die Punkte a und b, sowie den dazwischenliegenden Dezimalteil x (ich weiß, dass sie da ist, liegt an der 2. Dimension, die dazu kommt). Immerhin hab ich aber nun verstanden, wieso man die 4 angrenzenden Pixel betrachtet! Geht halt leider alles seeeehhhhrrrr laaaaaannnngggsaaaammm bei mir, noch dazu mit dem jahrelangen Abstand zu solcherlei Dingen! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-19, 22:29 h Der_Wanderer Posts: 1229 User |
Also die Formel ist hier, nur in mehreren Schritten:code:Hier wird der Bruchteil ermittelt, der zum rechten/oberen Pixel fehltsource_intx = Floor(source_x) // ganzzahlige coos ermitteln source_inty = Floor(source_y) x_frac = source_x - source_intx // überschüssige bruchteile ermitteln y_frac = source_y - source_inty code:Hier werden die gewichte Vorberechnet für die vier umliegenden Pixel,weight11 = (x_frac * y_frac) // gewichte für die umliegenden weight21 = ((1-x_frac) * y_frac) // pixel bestimmen weight12 = (x_frac * (1-y_frac)) weight22 = ((1-x_frac) * (1-y_frac)) also das x und 1-x code:und hier wird die Zeile a * (1-x) + b * x durchgeführt, wobei eben 2 dimensional und (1-x) vorberechnet.dest_pixel(dest_x,dest_y) = source_pixel(source_intx ,source_inty ) * weight11 // neuen ziel pixel zusammen setzen + source_pixel(source_intx+1,source_inty ) * weight21 // aus den vier umliegenden + source_pixel(source_intx ,source_inty+1) * weight12 // quell pixeln + source_pixel(source_intx+1,source_inty+1) * weight22 Man könnte es auch splitten und folgendes machen: code:--obere_reihe = source_pixel(source_intx ,source_inty) * (1-x_frac) + source_pixel(source_intx+1 ,source_inty) * (x_frac) untere_reihe = source_pixel(source_intx ,source_inty+1) * (1-x_frac) + source_pixel(source_intx+1 ,source_inty+1) * (x_frac) // ... und jetzt die beiden Reihen y interpolieren: dest_pixel(dest_x,dest_y) = obere_reihe * (1-y_frac) + unter_reihe * y_frac Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-19, 22:46 h Reth Posts: 1858 User |
Danke! Nun hab ichs auch gerafft. Irgendwie muss man ja aber auch von dem Bsp. der Interpolation des 3-dimensionalen Pixels in dem von mir verlinktem Artikel auf Dein Codebsp. oben kommen, ohne es in Reihen aufzudröseln. Muss ich noch probieren! Zitat: Hm? Zum linken/oberen Pixel, oder versteh ich das falsch (will nicht pedantisch sein, aber kann ja sein, dass jmd. das Ganze später auch mal zu Rate zieht, wird glaub ich gerade ein tolles Tutorial !)? Zitat: Das hat mir gefehlt, danke! Dieser Zwischenschritt. Damit sollte ich ja durch Einsetzen auf die Berechnung der Gewichtungen kommen. Muss ich auch noch aufm Papier ausprobieren! Da sieht mans ja! Ich brauch einige Zwischenschritte mehr, um den Kram zu kapieren. Mir sind solcherlei Dinge nicht so selbstverständlich klar, weil ich damit seit knapp 10 Jahren absolut nichts mehr am Hut habe! Vielen Dank nochmals! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-20, 18:04 h Der_Wanderer Posts: 1229 User |
@Reth Ähm, ja, links/oben natürlich. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-20, 20:02 h akl Posts: 265 User |
@Reth: Beim Dithering oder bei Matrizen in der Bildverarbeitung nutzt man 3x3, 5x5, 7x7 oder 9x9 Matritzen - der Punkt in der Mitte ist jeweils der aktuell betrachtete. Es kann Dich aber niemand hindern, bestimmte Punkte mit "0" zu gewichten, so dass man eine Matrix erhält, bei der nur die vier horizontalen/vertikalen Nachbarn in die Berechnung einfliessen. [ - Answer - Quote - Direct link - ] |
2008-02-20, 20:32 h Der_Wanderer Posts: 1229 User |
@akl Bei der Interpolation ist das aber was anderes. Da betrachtet man nicht einen Pixel und seine Nachbarn drumherum. Sondern da betrachtet man einen AbtastPUNKT (ohne Ausdenhung), und der liegt immer zwischen 4 Pixeln, wenn er nicht zufällig direkt auf einem Pixel landet. (Sonderfall). -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ - Answer - Quote - Direct link - ] |
2008-02-21, 11:08 h Reth Posts: 1858 User |
@Der_Wanderer: In welche Richtung (Stichwörter) muss man denn suchen, wenn man solche Sachen wie Baumstämme, Bäume, Sträucher mit entsprechenden Schatten rendern lassen möchte? Die Sachen machen zwar ne Menge Spass zu pixeln, aber darin bin ich nicht sehr gut! Ciao [ - Answer - Quote - Direct link - ] |
2008-02-21, 11:47 h Der_Wanderer Posts: 1229 User |
Suche nach "Tree Generator". Da wirst du aber nur fertige Programme finden. Der Trick, wie sie es machen, werden sie dir nicht verraten, da die meisten Programme kommerziell oder Shareware sind. Ich kann dir aber verraten, wie die meisten Tree-Gens funktionieren, und zwar mit einer (i.A. contextfreien) Grammatik. Das thema hatten wir hier in einem Thread schonmal besprochen. http://www.amiga-news.de/forum/thread.php?id=27069&BoardID=7 Ansonsten kannst du dir den "TreeGen" angucken im Amiblitz3 Archive, allerdings ist das sehr (sehr!) Alpha und der Sourcecode nicht sehr aufgeräumt. Aber ich werde das ausbauen und mit einer hübschen GUI versehen. Allerdings solltest du dich vorher mehr in Mathe und 3D Sachen fitter machen. Das ist schon anspruchsvoller als Perlin Noise. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ Dieser Beitrag wurde von Der_Wanderer am 21.02.2008 um 12:27 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
2008-02-21, 13:32 h Reth Posts: 1858 User |
Zitat: Ich weiß, auf den hab ich auch angespielt. Das mit der Grammatik ist sehr einleuchtend allerdings fehlt mir hier auch die Erfahrung, wie man von dieser auf einen Baum (Bild) kommt. Da wird dann der Teil mit Deinen Röhren aus Ellipsen interessant, doch die Zuordnung von Pixeln aus einem Patternbild auf die Röhrenoberfläche hört sich wieder nach üblen Berechnungen an, ebenso die Projektion von 3D->2D Iso. Zitat: Denk ich mir (s.o), drum meine Frage nach Stichwörtern, betrifft auch die zugrunde liegende Mathematik! Und wie sieht es mit Dingen wie Gräsern, Geröll, Sumpf etc. aus? Gibt es da auch was in die Richtung? Ciao [ - Answer - Quote - Direct link - ] |
2008-02-22, 14:03 h Der_Wanderer Posts: 1229 User |
> Denk ich mir (s.o), drum meine Frage nach Stichwörtern, betrifft auch die zugrunde liegende Mathematik! also am besten mal mit "Trigonometrie" anfangen. > Und wie sieht es mit Dingen wie Gräsern, Geröll, Sumpf etc. aus? > Gibt es da auch was in die Richtung? Alles, was flach auf dem Boden liegt, kann man gut mit PerlinFX machen. Grässer auch, indem man einfach einen Algo macht, der einen Grashalm zeichnet, und man nimmt ein Perlin Noise Muster als Grundlage wie lang er wird. So ist das Grass auf den Bilder oben gemacht. Ähnliches geht auch mit Geröll, wenn man das Perlin Noise als Bumpmap oder Displacement map nimmt, so ist das Bild mit dem rauhen Rot-Sandstein gemacht. -- Thilo Köhler, Author von: HD-Rec, Sweeper, Samplemanager, ArTKanoid, Monkeyscript, Toadies, AsteroidsTR, TuiTED, PosTED, TKPlayer, AudioConverter, ScreenCam, PerlinFX, MapEdit, TK AB3 Includes und viele mehr... Homepage: http://www.hd-rec.de [ Dieser Beitrag wurde von Der_Wanderer am 22.02.2008 um 15:00 Uhr geändert. ] [ - Answer - Quote - Direct link - ] |
-1- 2 | [ - Post reply - ] |
amiga-news.de Forum > Programmierung > Prozedurales Texturieren | [ - Search - New posts - Register - Login - ] |
Masthead |
Privacy policy |
Netiquette |
Advertising |
Contact
Copyright © 1998-2024 by amiga-news.de - all rights reserved. |