[Projekt] Prozedurales Universum

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

joeydee hat geschrieben:Super, der Thread ist jetzt schon ein klasse Nachschlagewerk :)

Was du wahrscheinlich brauchst, ist eine genauere, zumindest eine "sichere" Distanzfunktion zu einem Patch. Und zwar zur gewölbten Oberfläche, nicht zur planaren Fläche, also geht auch keine normale Distanzfunktion zu einem Polygon. Aber so ähnlich vielleicht. [...].
Interessant, danke für die Denkanstöße. In der Tat habe ich zwei Probleme oder Ungenauigkeiten:
a) Ich kenne die exakte Distanz zur Plane nicht, nur näherungsweise über die 4 Kanten
b) Im Horizon Culling prüfe ich nur einzelne Punkte (die in der Tat mal alle zwar hinter dem Horizon liegen können, das aber in extremen Situationen keine Aussage darüber trifft ob die Plane sichtbar ist oder nicht)

Die Lösung dürfte in der Beziehung der 4 Punkte zueinander liegen. Möglichkeiten:
- Ich nutze gemäß deinem Vorschlag die echte "nächste" Distanz zur gewölbten Plane, und nicht die Eckpunkte. Dann dürfte das VisibleSphere-Culling weiterhin funktionieren.
- Ich finde heraus wann eine Plane auf jedenfall NICHT hinter dem Horizont liegen KANN, bzw. ich den Horizon/VisibleSphere-Culling Algorithmus nicht aufrufen muss oder sollte. Das wäre eigentlich immer dann wenn die Kamera-Koordinate innerhalb des Volume ist welches vom Mittelpunkt über die 4 Kanten der Plane aufgespannt wird. Oder, wenn die Linie von Kamera zu M die Plane schneidet welche ich über die 4 Eckpunkte ermittle. Dann muss ich eigentlich immer davon ausgehen können dass die Plane sichtbar ist.

Mal schlau machen über welche Wege ich dass am einfachsten ermitteln kann. Das ganze muss im Nebenthread passieren, d.h. ich kann keine Unity3D Boardmittel verwenden.
joeydee
Establishment
Beiträge: 1044
Registriert: 23.04.2003, 15:29
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von joeydee »

wann [...] ich den Horizon/VisibleSphere-Culling Algorithmus nicht aufrufen muss oder sollte
Gerade der ist super billig und bringt viel. Also nicht jedes Patch in einen Algo schicken der true oder false zurückgibt, sondern bei geänderter Kameraposition den Horizont-Radius einmalig pro Frame berechnen. Dann ist es für jede Prüfung nur noch ein Distanzvergleich mit einer Frame-Konstanten. Billiger gehts nicht.
b) Im Horizon Culling prüfe ich nur einzelne Punkte (die in der Tat mal alle zwar hinter dem Horizon liegen können, das aber in extremen Situationen keine Aussage darüber trifft ob die Plane sichtbar ist oder nicht)
Einzelne Punkte und auch das Quad das sie aufspannen sind gerade am Anfang eine extrem schlechte Näherung an die tatsächliche Kugel. Also sind das gerade dann die typischen Fälle, keine Ausnahmen. So lange, bis gut genug unterteilt ist, dass es eine bessere Näherung darstellt. Aber gerade die (unnötigen) Unterteilungen sollen ja bis dahin vermieden werden, und genau da beißt sich die Katze in den Schwanz.
[...]Das wäre eigentlich immer dann wenn die Kamera-Koordinate innerhalb des Volume ist welches vom Mittelpunkt über die 4 Kanten der Plane aufgespannt wird.
Siehe meine Zeichnung.
Oder wenn die Linie von Kamera zu M die Plane schneidet welche ich über die 4 Eckpunkte ermittle. Dann muss ich eigentlich immer davon ausgehen können dass die Plane sichtbar ist.
Beidesmal ja. Mathematisch exakt dasselbe. Aber das trifft immer nur für genau ein Quad zu, nämlich das, über welchem die Kamera steht. Und hier ist die genaue Entfernung sogar bekannt (direkter Abstand Kamera zur Kugeloberfläche). Im Umkehrschluss kann man keine sichere Aussage treffen ("WENN alle Eckpunkte außerhalb der HorizonSphere UND das Quad nicht vom Strahl getroffen, DANN wird es auch nach beliebigen Unterteilungen nie sichtbar" - ist falsch), aber genau das wäre ja von Interesse. Ich kann mir z.B. einen Fall denken, wo ein Quad gerade so nicht von der Linie getroffen wird (Kante direkt neben dem Strahl), alle seine Eckpunkte außerhalb der Sphere liegen (weil noch zu groß) und es trotzdem höchste Prio für Unterteilung haben müsste da seine Kante fast direkt unter der Kamera liegt. Trifft auch immer wieder dann zu, wenn die Kamera sehr nah an der Oberfläche und die Unterteilung noch ganz am Anfang ist.

Selbst wenn das Sinn für weitere Prüfungen machen würde, wäre nichts gewonnen: Herauszufinden ob ein Strahl ein Polygon schneidet ist praktisch dasselbe wie die von mir vorgeschlagenen Ebenenprüfungen. Der Fall innerhalb wäre damit abgehakt, für die vielen Fälle außerhalb sind dann weitere Prüfungen notwendig, da nicht alle pauschal ausgeschlossen werden können.
Dabei wirst du zwangsläufig bei der Idee landen, zu prüfen, ob die Verbindung zweier Eckpunkte außerhalb der Sphere diese trotzdem schneidet (entspricht: Abstand Kamera zu Quadkante berechnen und mit Sphere-Radius vergleichen).
Und selbst da gibt es wieder Ausnahmen wo die Kante außerhalb verläuft, aber das gewölbte Quad trotzdem sichtbar wäre. In dem Fall weiter prüfen ob die Projektion dieser Kante auf die Kugeloberfläche die Sphere schneidet. Erst wenn auch das nicht zutrifft kann man ein Quad sicher ausschließen.

Und das entspricht der von mir vorgeschlagenen Berechnung. Sogar mit der Abbruchbedingung, wenn gleich zu Anfang der Strahltest positiv ist (dann der Fall, wenn Kamera diesseits aller aufgespannten Ebenen). Der Step von der geraden zu gebogenen Kante spart glaube ich nicht mehr viel, das ist nur noch eine Normierung und Skalierung. Die Unterscheidung kann man einbauen wenn man das Ganze gleich als Prüf- statt als Abstandsfunktion implementiert.
Allerdings würde ich eher den Abstandswert für jedes vorhandene Patch genau ermitteln, mit dem Horizon-Wert vergleichen und falls sichtbar den Wert gleich als Split-Kriterium weiterverwenden. Da hättest du zwei Fliegen mit einer Klappe geschlagen.
joeydee
Establishment
Beiträge: 1044
Registriert: 23.04.2003, 15:29
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von joeydee »

So müsste der Algo für die genaue Distanz zu gekrümmten Patches funktionieren:

Voraussetzungen:
Kugel mit M als Origin und rMax als Radius
Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)

- für jede Kante v0,v1:
-- Ebenennormale konstruieren: Normale=Kreuzprodukt(v0,v1)
-- Abstand der Kamera zu dieser Ebene: d=Punktprodukt(Kamera,Normale)
-- Wenn Abstand negativ, dann liegt die Kamera nicht im aufgespannten Volume (je nach Umlaufsinn umgekehrt)
-- (Achtung, der Umkehrschluss "wenn Abstand positiv, dann liegt die Kamera im Volume" wäre an dieser Stelle falsch - hierzu müssen alle Ebenen einbezogen werden, lässt sich erst außerhalb der Schleife bestimmen)
-- Projizierten Kamerapunkt auf dieser Ebene berechnen: p=Normale*d+Kamera
-- Diesen Punkt auf die Kugeloberfläche in Richtung M projizieren: p=p/längeVonP*rMax (p liegt jetzt auf dem Großkreis durch v0,v1)
-- Segmenttest:
--- wenn Punkt "unterhalb" von v0 in Richtung v1, dann p=v0 (wenn Punktprodukt (v1-v0,p-v0)<0)
--- wenn Punkt "unterhalb" von v1 in Richtung v0, dann p=v1 (wenn Punktprodukt (v0-v1,p-v1)<0)

- nachdem alle Kanten des Patches geprüft:
-- wenn Kamera nicht im aufgespannten Volume: patchDistanz=kleinste Distanz aller ermittelten p zur Kamera
-- sonst: patchDistanz=längeKameraVektor-rMax (Kamera ist direkt über diesem Patch -> direkter Abstand zum Boden)

Im Detail werden nur Standard-Vektorfunktionen (Kreuzprodukt, Punktprodukt, Länge, Skalieren) benötigt, die sogar einfach zu hardcoden wären. Also ohne Unity-Boardmittel machbar.
Wenn du eine Skizze brauchst sag bescheid.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Hi joeydee,

große klasse, you made my day, vielen Dank! Ich hatte schon angefangen mir zu überlegen wie ich die Berechnungen mache, aber du warst eine ganze Ecke schneller.
Ich werde das jetzt am Wochenende implementieren und dann ein Update geben!

Edit [2015-05-30 16:10]:
Erster Teilerfolg. Die Prüfung ob sich die Kamera im aufgespannten Volume befindet funktioniert schonmal (habe ich als Prüfung eingebunden ob ich Horizon Culling aufrufe oder nicht).
Er splittet jetzt immer, auch wenn ich direkt über der Oberfläche starte. :)
Jetzt werde ich den den zweiten Teil machen, den nächsten Punkt ermitteln. Das dürfte sich dann positiv auf die Genauigkeit und mit ein klein wenig Glück vielleicht auch neutral oder sogar positiv auf die Performance auswirken, da ich dann nur noch 1/5 der Culling Prüfungen machen muss (nur noch 1 statt 5 Koordinaten pro Patch). :lol:

Edit [2015-06-14 14:01]
Sieht gut aus. Habe jetzt die verbleibende Distanzprüfungen eingebaut. Einmal deinen Algorithmus um die nächste Distanz zu ermitteln, und ergänzt um einen der den nächsten Vector auf der Plane ermittelt.
Test auf der Console macht nen guten Eindruck, ein paar weitere muss ich noch machen, aber sieht so als passen die Werte. Genial.
Als nächstes entschlacke ich jetzt das Horizon/Visible-Sphere Culling indem ich nur noch den nächsten Vektor prüfe und nicht mehr die Kantenvektoren. Damit dürfte ich ein klein wenig Performance zurückkriegen, und das Problem beim Starten auf der Oberfläche ist damit auch entgültig gelöst.

Edit [2015-06-17 20:28]
Die weiteren Tests sahen auch gut aus. Horizon Culling laeuft nur noch den nächsten ermittelten Vektor aus deiner Formel. Performance ist kein Problem. joeydee, ganz großes Dankeschön!
Ich hab das ganze, jetzt da ich den nächsten Vektor und die Distanz kenne, mit "LODSpheres" kombiniert als Kriterium zum Splitten und Mergen. Dazu mehr im nächsten Post am Wochenende.

Bild

Anbei der Unity3D Code falls jemand das mal braucht. Denke ich werde ein paar Teile der ganzen Sphere-Algorithmen in ein paar verschlankte Service-Klassen auslagern und hier zum runterladen ablegen.

Code: Alles auswählen

	// ------- Plane Volume Check and Closest Point -------

	// PlaneVolumeCheck
	// Voraussetzungen:
	// Kugel mit M als Origin und rMax als Radius
	// Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)
	// Reference http://zfx.info/viewtopic.php?f=10&t=3075&p=47630&sid=32fd71d1e3d51dfdc3850667a3cf164d#p47630
	// Author: joeydee
	// Liefert zurück ob die Kamera innerhalb des aufgespannten Volume liegt (true = liegt im volume)
	// Plane vertices: 1 -- 2
	//                 |    |
	//                 3 -- 4
	public bool PlaneVolumeCheck(QuadtreeTerrain quadtreeTerrain, Vector3 cameraPosition) {
		if (BorderVolumeCheck (quadtreeTerrain.worldSpaceOuterVector1, quadtreeTerrain.worldSpaceOuterVector2, cameraPosition) 
		    && BorderVolumeCheck (quadtreeTerrain.worldSpaceOuterVector2, quadtreeTerrain.worldSpaceOuterVector4, cameraPosition) 
		    && BorderVolumeCheck (quadtreeTerrain.worldSpaceOuterVector4, quadtreeTerrain.worldSpaceOuterVector3, cameraPosition) 
		    && BorderVolumeCheck (quadtreeTerrain.worldSpaceOuterVector3, quadtreeTerrain.worldSpaceOuterVector1, cameraPosition)) {
			return true;
		} else {
			return false;
		}
	}
	
	// BorderVolumeCheck
	// Voraussetzungen:
	// Kugel mit M als Origin und rMax als Radius
	// Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)
	// Reference http://zfx.info/viewtopic.php?f=10&t=3075&p=47630&sid=32fd71d1e3d51dfdc3850667a3cf164d#p47630
	// Author: joeydee
	// Liefert zurück ob die Kamera im aufgespannten Volume, hier negativ oder positiv zur aufgespannten Seite, liegt.
	private bool BorderVolumeCheck(Vector3 v0, Vector3 v1, Vector3 cameraPosition) {
		//	- für jede Kante v0,v1:
		//		-- Ebenennormale konstruieren: Normale=Kreuzprodukt(v0,v1)
		Vector3 normal = Vector3.Cross (v0, v1);
		//			-- Abstand der Kamera zu dieser Ebene: d=Punktprodukt(Kamera,Normale)
		float d = Vector3.Dot (cameraPosition, normal);
		//			-- Wenn Abstand negativ, dann liegt die Kamera nicht im aufgespannten Volume (je nach Umlaufsinn umgekehrt)
		if (d >= 0) {
			return true; 
		} else { 
			return false;
		}
	}
	
	// ClosestPlaneDistance
	// Voraussetzungen:
	// Kugel mit M als Origin und rMax als Radius
	// Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)
	// Reference http://zfx.info/viewtopic.php?f=10&t=3075&p=47630&sid=32fd71d1e3d51dfdc3850667a3cf164d#p47630
	// Author: joeydee
	// Liefert die Distanz des nächsten Punktes einer Plane zur Kamera zurück, wobei zugrunde gelegt wird dass es sich um eine Sphäre mit rMax handelt
	// Plane vertices: 1 -- 2
	//                 |    |
	//                 3 -- 4
	public float ClosestPlaneDistance(QuadtreeTerrain quadtreeTerrain, Vector3 cameraPosition) {
		//	- nachdem alle Kanten des Patches geprüft:
		// 		-- wenn Kamera nicht im aufgespannten Volume: patchDistanz=kleinste Distanz aller ermittelten p zur Kamera
		if (!PlaneVolumeCheck (quadtreeTerrain, cameraPosition)) {
			Vector3[] closestBorderPoints = {new Vector3(0,0,0),new Vector3(0,0,0),new Vector3(0,0,0),new Vector3(0,0,0)};
			closestBorderPoints[0] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector1, quadtreeTerrain.worldSpaceOuterVector2, cameraPosition);
			closestBorderPoints[1] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector2, quadtreeTerrain.worldSpaceOuterVector4, cameraPosition);
			closestBorderPoints[2] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector4, quadtreeTerrain.worldSpaceOuterVector3, cameraPosition);
			closestBorderPoints[3] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector3, quadtreeTerrain.worldSpaceOuterVector1, cameraPosition);
			float[] closestDistancePointToCamera = {0,0,0,0};
			closestDistancePointToCamera [0] = Vector3.Distance(closestBorderPoints[0],cameraPosition);
			closestDistancePointToCamera [1] = Vector3.Distance(closestBorderPoints[1],cameraPosition);
			closestDistancePointToCamera [2] = Vector3.Distance(closestBorderPoints[2],cameraPosition);
			closestDistancePointToCamera [3] = Vector3.Distance(closestBorderPoints[3],cameraPosition);
			return closestDistancePointToCamera.Min ();
			// 			-- sonst: patchDistanz=längeKameraVektor-rMax (Kamera ist direkt über diesem Patch -> direkter Abstand zum Boden)
		} else {
			return cameraPosition.magnitude-this.rMax;
		}
	}

	// ClosestPlanePoint
	// Voraussetzungen:
	// Kugel mit M als Origin und rMax als Radius
	// Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)
	// Reference http://zfx.info/viewtopic.php?f=10&t=3075&p=47630&sid=32fd71d1e3d51dfdc3850667a3cf164d#p47630
	// Author: joeydee
	// Liefert die Distanz des nächsten Punktes einer Plane zur Kamera zurück, wobei zugrunde gelegt wird dass es sich um eine Sphäre mit rMax handelt
	// Plane vertices: 1 -- 2
	//                 |    |
	//                 3 -- 4
	public Vector3 ClosestPlanePoint(QuadtreeTerrain quadtreeTerrain, Vector3 cameraPosition) {
		//	- nachdem alle Kanten des Patches geprüft:
		// 		-- wenn Kamera nicht im aufgespannten Volume: patchDistanz=kleinste Distanz aller ermittelten p zur Kamera
		if (!PlaneVolumeCheck (quadtreeTerrain, cameraPosition)) {
			Vector3[] closestBorderPoints = {new Vector3(0,0,0),new Vector3(0,0,0),new Vector3(0,0,0),new Vector3(0,0,0)};
			closestBorderPoints[0] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector1, quadtreeTerrain.worldSpaceOuterVector2, cameraPosition);
			closestBorderPoints[1] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector2, quadtreeTerrain.worldSpaceOuterVector4, cameraPosition);
			closestBorderPoints[2] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector4, quadtreeTerrain.worldSpaceOuterVector3, cameraPosition);
			closestBorderPoints[3] = ClosestBorderPoint (quadtreeTerrain.worldSpaceOuterVector3, quadtreeTerrain.worldSpaceOuterVector1, cameraPosition);
			float[] closestDistancePointToCamera = {0,0,0,0};
			closestDistancePointToCamera [0] = Vector3.Distance(closestBorderPoints[0],cameraPosition);
			closestDistancePointToCamera [1] = Vector3.Distance(closestBorderPoints[1],cameraPosition);
			closestDistancePointToCamera [2] = Vector3.Distance(closestBorderPoints[2],cameraPosition);
			closestDistancePointToCamera [3] = Vector3.Distance(closestBorderPoints[3],cameraPosition);
			int indexOfClosestDistancePointToCamera = Array.IndexOf(closestDistancePointToCamera,closestDistancePointToCamera.Min());
			return closestBorderPoints[indexOfClosestDistancePointToCamera];
			// 			-- sonst: patchDistanz=längeKameraVektor-rMax (Kamera ist direkt über diesem Patch -> direkter Abstand zum Boden)
		} else {
          return cameraPosition*((this.rMax/cameraPosition.magnitude));
		}
	}
	
	// ClosestBorderPoint
	// Voraussetzungen:
	// Kugel mit M als Origin und rMax als Radius
	// Patch mit Vertices auf rMax (d.h. alle Vertices berühren die Kugeloberfläche, Patch liegt komplett in der Kugel)
	// Reference http://zfx.info/viewtopic.php?f=10&t=3075&p=47630&sid=32fd71d1e3d51dfdc3850667a3cf164d#p47630
	// Author: joeydee
	// Liefert den nächsten Punkt der Kante zwischen v0 und v1 zur Kamera zurück, wobei zugrunde gelegt wird dass es sich um eine Sphäre mit rMax handelt
	private Vector3 ClosestBorderPoint(Vector3 v0, Vector3 v1, Vector3 cameraPosition) {
		
		//	- für jede Kante v0,v1:
		//		-- Ebenennormale konstruieren: Normale=Kreuzprodukt(v0,v1)
		Vector3 normal = Vector3.Cross (v0, v1);
		//			-- Abstand der Kamera zu dieser Ebene: d=Punktprodukt(Kamera,Normale)
		float d = Vector3.Dot (cameraPosition, normal);
		//			-- Wenn Abstand negativ, dann liegt die Kamera nicht im aufgespannten Volume (je nach Umlaufsinn umgekehrt)
		//			-- (Achtung, der Umkehrschluss "wenn Abstand positiv, dann liegt die Kamera im Volume" wäre an dieser Stelle falsch - hierzu müssen alle Ebenen einbezogen werden, lässt sich erst außerhalb der Schleife bestimmen)
		//			-- Projizierten Kamerapunkt auf dieser Ebene berechnen: p=Normale*d+Kamera
		Vector3 p = normal * d + cameraPosition;
		//			-- Diesen Punkt auf die Kugeloberfläche in Richtung M projizieren: p=p/längeVonP*rMax (p liegt jetzt auf dem Großkreis durch v0,v1)
		p = p / p.magnitude * this.rMax;
		//			-- Segmenttest:
		//			--- wenn Punkt "unterhalb" von v0 in Richtung v1, dann p=v0 (wenn Punktprodukt (v1-v0,p-v0)<0)
		if (Vector3.Dot (v1 - v0, p - v0) < 0)
			p = v0;
		//			--- wenn Punkt "unterhalb" von v1 in Richtung v0, dann p=v1 (wenn Punktprodukt (v0-v1,p-v1)<0)
		if (Vector3.Dot (v0 - v1, p - v1) < 0)
			p = v1;
		return p;
		//	- nachdem alle Kanten des Patches geprüft:
		// 		-- wenn Kamera nicht im aufgespannten Volume: patchDistanz=kleinste Distanz aller ermittelten p zur Kamera
		// 			-- sonst: patchDistanz=längeKameraVektor-rMax (Kamera ist direkt über diesem Patch -> direkter Abstand zum Boden)
	}
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Ich habe ein wenig den Noise Algorithmus gepimmt. Auf der Suche wie man natürlichere Formen für Lanschaft und Täler hinbekommt bin ich auf RidgedMultifractals gestoßen. Mit diesen bekommt man entweder langläufigere natürlichere Bergformationen hin, oder auch solche Kanten wie sie sich manchmal auf Bergenspitzen und Dühnen ergeben, denn diese sind oft nicht völlig chatisch sind folgenden bestimmten Mustern und bilden Linien.

Ich habe das mal mit meinem SimplexNoise kombiniert, erstmal um die Kontinente etwas glaubwürdiger hinzubekommen bzw. ein wenig mehr Abwechslung zu erhalten. Funktioniert klasse, siehe Bild 3), eine Kombination aus Bild 1) und 2). Wenn man etwas Spökus macht (z.B. die Werte mehrfach miteinander multiplizieren) kann man auch recht abgefahrene Sachen damit machen, wie im letzten Bild.
Später muss ich mal schauen wie ich damit auch ähnliches hinbekomme wenn man sich näher auf der Oberfläche befindet (z.B. besagte Dühnenkanten, Kliffe oder Bergspitzen).

SimplexNoise
Bild

RidgedMultifractals
Bild

SimplexNoise + RidgedMultifractals
Bild

SimplexNoise + RidgedMultifractals (mehrfach miteinander multipliziert und gewichtet
Bild
Zuletzt geändert von sushbone am 20.06.2015, 12:00, insgesamt 3-mal geändert.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Und noch ein zweites kleines Update
Anbei wie ich die Regel für das Splitten und Mergen der Planes umgesetzt habe, mithilfe von "LODSpheres", abgespeichert in einer Lookup-Tabelle.

Bild

Ich verwende aktuell Variante zwei (die untere aus dem Bild), d.h. ich prüfe eine Kollision gegen den nächsten Vektor jeder Plane, die ich mithilfe von joeydee's Algorithmus ermittelt habe. In dem Falle ist das dann ein einfacher Vergleich der Distanz Vektor->Kamera gegen die ganzen Radien der LODSpheres.
Klappt sehr gut, dank der Sphere spart man sich viele Splits im Vergleich zu meiner bisherigen Strategie. Und man vermeidet gerade bei näherer Distanz dass zu viel gesplittet wird was tendenziell außerhalb des Blickfelds gerät.

Ich bin nicht sicher ob die AABB Prüfung effektiver wäre (im Infinity-Forum schlägt dies jemand vor, mit dem Hinweis dass AABB Checks auf dauer günstiger wären). Das würde bedeuten dass ich für
a) Für jede Plane den bzw. die Durchschnittswinkel zum Mittelpunkt berechne, dann die 4 Node-Kanten um eben diesen Winkel rotiere und als separate Kantenwerte im Quadtree-Node speichere (für den späteren AABBvsSphere Test).
b) Bei jedem Prüfvorgang die Position der Kamera um eben den gleichen Winkel rotiere, und dann besagten AABBvsSphere durchführe. Letzteren habe ich schon auf Basis dieses Eintrags nachprogrammiert, der tut.

Ein Vorteil auf den mich der Kollege auf dem anderen Forum hinwies liegt auf der Hand. Wenn ich als AABB Koordinaten nicht rMin und rMax heranziege, sondern die echten Daten die sich in Kombination mit dem Noise ergeben haben, dann erhält man ein genaueres LOD, weil dann die BoundingBox z.B. auch tiefe Täler aufgrund des Noise richtig berücksichten kann. Weiterhin muss man die AABB Eckpunkte nur einmal berechnen und kann sie dann im QuadTree für jeden Node mitspeichern. D.h. die späteren Berechnungen sind dann wirklich nur eine Kamerarotation pro Plane und der AABBvsSphere Test. Vermutlich wirklich günstiger als immer wieder den nächsten Punkt zu berechnen.

Bild

Mein Problem ist, die Durchschnittswinkel der Plane bekomme ich noch ausgerechnet (entweder als signed -180 bis +180 oder 0 bis 360). Mein Ziel wäre z.B. alle Koordinaten nach oben (Vector3.Up, Y-Achse) zeigen zu lassen.
Ich berechne dabei den Winkel des Plane-Mittelpunktes zur Y Achse (Vector3.Up) und Z-Achse (Vector3.Forward), das klappt noch soweit, aber dann kriege ich die korrekte Rotation nicht hin (die Planes zeigen nicht durchgängig nach oben, spätestens nach einigen Splits zeigen sie in ganz verschiedene Richtungen). Man sollte meinen es müsste ganz einfach sein?!
Ich versuche es wie folgt:

WSBBPatchAlignedVectorRMax und WSBBPatchAlignedVectorRMin sind die Kanten der BoundingBox (noch nicht AABB tranformiert). WSBBPatchAlignedVectorRMax ist die Plane-Kante mit Radius rMax, WSBBPatchAlignedVectorRMin ist die Plane-Kante mit Radius rMin. Lasse ich diese zeichnen sieht das so aus.

Bild
Bild

In WSBBAverageAngleX,Y und Z speichere ich den Winkel zum Plane-Center-Vektor (damit düfte ich den Average Winkel der Plane erhalten) gegen die einzelnen Koordinaten Vektoren Up, Right, Forward.
Nach meinem Verständnis muss ich dann die 8 Punkte der Plane um die Z-Achse um den Winkel -WSBBAverageAngleY rotieren sowie um die X-Achse um den Winkel WSBBAverageAngleZ. Dann muessten die Koordinaten, so mein naives Verstaendnis, doch nach oben zeigen? Hab ich irgendwo einen Denkfehler, mache ich es mir zu einfach?

Der (unschöne) Code:
Die Klasse zum prüfen des Winkels:

Code: Alles auswählen

public class VectorServiceProvider : MonoBehaviour {
	static public float SignedAngle(Vector3 a, Vector3 b){
		// the vector that we want to measure an angle from
		Vector3 referenceForward = a; /* some vector that is not Vector3.up */
		// the vector perpendicular to referenceForward (90 degrees clockwise)
		// (used to determine if angle is positive or negative)
		Vector3 referenceRight= Vector3.Cross(Vector3.up, referenceForward);
		// the vector of interest
		Vector3 newDirection = b; /* some vector that we're interested in */
		// Get the angle in degrees between 0 and 180
		float angle = Vector3.Angle(newDirection, referenceForward);
		// Determine if the degree value should be negative.  Here, a positive value
		// from the dot product means that our vector is on the right of the reference vector   
		// whereas a negative value means we're on the left.
		float sign = Mathf.Sign(Vector3.Dot(newDirection, referenceRight));
		float finalAngle = sign * angle;
		return finalAngle;
	}
}
Die Stelle wo ich den Winkel prüfe und rotiere:
quadtreeTerrain.WSBBAverageAngleX = VectorServiceProvider.SignedAngle(Vector3.right,quadtreeTerrain.centerVector);
quadtreeTerrain.WSBBAverageAngleY = VectorServiceProvider.SignedAngle(Vector3.up,quadtreeTerrain.centerVector);
quadtreeTerrain.WSBBAverageAngleZ = VectorServiceProvider.SignedAngle(Vector3.forward,quadtreeTerrain.centerVector);
Debug.Log ("centervector=" + quadtreeTerrain.centerVector + " avAngleX=" + quadtreeTerrain.WSBBAverageAngleX + " avAngleY=" + quadtreeTerrain.WSBBAverageAngleY + " avAngleZ=" + quadtreeTerrain.WSBBAverageAngleZ);
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1 = quadtreeTerrain.WSBBPatchAlignedVectorRMax1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2 = quadtreeTerrain.WSBBPatchAlignedVectorRMax2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3 = quadtreeTerrain.WSBBPatchAlignedVectorRMax3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4 = quadtreeTerrain.WSBBPatchAlignedVectorRMax4;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1 = quadtreeTerrain.WSBBPatchAlignedVectorRMin1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2 = quadtreeTerrain.WSBBPatchAlignedVectorRMin2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3 = quadtreeTerrain.WSBBPatchAlignedVectorRMin3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4 = quadtreeTerrain.WSBBPatchAlignedVectorRMin4;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleY, Vector3.forward) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3;
quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4 = Quaternion.AngleAxis (-quadtreeTerrain.WSBBAverageAngleZ, Vector3.right) * quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4;
Und das gruselige Ergebnis der eigentlich :) nach Norden ausgerichteten BoundingBoxes (einmal ohne Split, und dann nach einigen Splits, Chaos pur... :D :cry: )

Bild

Bild
Zuletzt geändert von sushbone am 20.06.2015, 15:37, insgesamt 1-mal geändert.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von Zudomon »

Ich wollte auch mal sagen, dass mich dein Projekt sehr interessiert.
Wir machen ja quasi das gleiche... nur das ich nicht auf die aberwitzige Idee komme, gleich ein ganzen Planeten zu erzeugen... :D ;)
Bin mal gespannt, was da noch raus wird! :D
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von xq »

Ja, bin gespannt wie es hier weitergeht. Das ganze sieht schon mal vielversprechend aus!
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Danke euch, das motiviert! Mühsam isses, aber man lernt was dabei, und das Planetensetting macht Bock.
@Zudomon: Das mit dem ganzen Planeten-Größenwahn wird sich dann rächen wenn ich in den Bereich komme bodennahe Gebiete besser zu rendern :D . Aber schauen wir mal. Ich halte mich deswegen noch davon fern weil ich erstmal die Basics fertigkriegen will um das Quadtree-Parsen und splitten/mergen so schnell hinzukriegen wie es eben geht. Sonst ist auf Bodennähe ganz schnell Schluss mit Performance (geht zwar einigermaßen, aber darf noch besser werden) :D.

Ich glaube ich bin meinem Problem die BoundingBoxes alle Planes Axis-Aligned AA auszurichten (zum Nordpol, Vektor3.Up bzw. (0,1,0)) auf die Schliche gekommen.
Anbau ein Bild wo man die Kanten der AABB in Rot sieht (nach der Rotation von Ihrer ursprünglichen Position). Und zusätzlich in Grau die Verbindung der Kanten zu Ihrem Mittelpunkt vor der Rotation).

Bild

Code: Alles auswählen

	// Get Radius of each axis.
		quadtreeTerrain.WSBBAverageAngleX = VectorServiceProvider.SignedAngle(Vector3.right,quadtreeTerrain.WSCenterVector);
		quadtreeTerrain.WSBBAverageAngleY = VectorServiceProvider.SignedAngle(Vector3.up,quadtreeTerrain.WSCenterVector);
		quadtreeTerrain.WSBBAverageAngleZ = VectorServiceProvider.SignedAngle(Vector3.forward,quadtreeTerrain.WSCenterVector);
		// Calulcate the rotation
		Quaternion totalRotation;
		if (quadtreeTerrain.WSBBAverageAngleX > 0)
			totalRotation = Quaternion.AngleAxis( -quadtreeTerrain.WSBBAverageAngleY,Vector3.forward);
		else totalRotation = Quaternion.AngleAxis( quadtreeTerrain.WSBBAverageAngleY,Vector3.forward);
		// Do the rotation
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax1;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax2;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax3;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax4;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin1;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin2;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin3;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin4;
Man sieht in der Debug-Console die ermittelten Winkelwerte für die 6 Planes.
Die vier Planes links, rechts, oben und unten (initial hat diese ja eine Würfelform) wurden korrekt um die Z-Achse (Vector3.forward) (die die in den Screen hinein zeigt) gedreht, entweder gegen oder mit dem Uhrzeigersinn (siehe hierzu die Fallunterscheidung im Code). Man erkennt, z.B. (1,0,0) und (-1,0,0) haben den gleichen Winkel zur Y-Achse, da ich aber wissen muss ob ich gegen oder mit dem Uhrzeigersinn um Z rotieren muss kann/muss ich hier X prüfen welches mir sagt ob ich mich "links" oder "rechts" von der Y-Achse befinde. Die BoundingBoxes die ursprünglich links, rechts, oben und unten waren sind jetzt alle oben. Soweit so gut.

Problem sind jetzt die zwei verbleibenden BB Seiten vorne (in Richtung des Betrachters) sowie hinten. Man erkennt am Kreuz dass diese nicht bzw. nur um die eigene Achse rotiert wurden, was logisch ist, da die Z-Achse (Vector3.forward) durch diese hindurchgeht. Theoretisch müsste ich nun eine Rotation um die X-Achse ausführen, wie aber soll ich das anstellen ohne dass ich die obere und untere Plane rotiere (diese sind ja aktuell nach der Rotation da wo sie sein sollen)? Weiterhin, nach den ersten Splits werden auch die kleineren BBs links/rechts/oben/unten nicht mehr korrekt rotiert, ich habe dann keine funktionierenden Winkel mehr um um die Z-Achse zu rotieren.

Ich beginne zu ahnen dass die Unity Methode Quaternion.AngleAxis(Winkel/Achse-um-die-rotiert-wird) ggf. nicht so ohne weiteres das richtige ist. bzw. wenn, dass dann das Geheimnis in der individuellen Ermittlung der korrekten Achse um die einmal rotiert wird liegt, ich nicht mehrfach rotieren kann,und dies keine der Standard-Achsen (forward, up, right, etc) sein kann.

EDIT [2015-06-20 15:11]:
Das Kreuzprodukt hat mich auf die richtige Spur gebracht. Logisch eigentlich. Folgender simpler Code führt zu folgendem Ergebnis:

Code: Alles auswählen

		// Get Radius of each axis.
		quadtreeTerrain.WSBBAverageAngleX = VectorServiceProvider.SignedAngle(Vector3.right,quadtreeTerrain.WSCenterVector);
		quadtreeTerrain.WSBBAverageAngleY = VectorServiceProvider.SignedAngle(Vector3.up,quadtreeTerrain.WSCenterVector);
		quadtreeTerrain.WSBBAverageAngleZ = VectorServiceProvider.SignedAngle(Vector3.forward,quadtreeTerrain.WSCenterVector);
		// Get the rotation
		Quaternion totalRotation;
		Vector3 rotationAxis = Vector3.Cross (quadtreeTerrain.WSCenterVector, Vector3.up);
		totalRotation = Quaternion.AngleAxis(quadtreeTerrain.WSBBAverageAngleY,rotationAxis);
		// Do the rotation
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax1 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax1;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax2 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax2;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax3 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax3;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMax4 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMax4;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin1 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin1;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin2 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin2;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin3 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin3;
		quadtreeTerrain.WSBBPatchAlignedTransformedVectorRMin4 = totalRotation * quadtreeTerrain.WSBBPatchAlignedVectorRMin4;
Bild

Bild
Zuletzt geändert von sushbone am 28.06.2015, 14:02, insgesamt 1-mal geändert.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

So, das LODden mittels LODSphere und AABB Test klappt jetzt wunderbar. Habe mal zwei Papers gemacht wo ich beschrieben habe wie ich die Bounding Boxes der Overflächen rotiere und die Verformung (die Planes sind beim CubeSphere ja nicht rechtwinklig) handhabe. Ich habe letztlich eine leicht andere Variante gewählt als zuvor. Ich ermittle jetzt erst den Azimuth Winkel und rotiere alles Richtung X-Achse (vector3.right) und dann per Elevation Winkel Richtung Y-Achse (vector3.up). Dort mache ich dann den AABBvsSphere Test (die aktuelle Kameraposition wird dann pro AABB Test um den gleichen Wert rotiert).
Die Collision die ich dort anhand der LODSphere (22 verschiedene mit unterschiedlichen Radien rund um die Kameraposition) ermittle sind jetzt das Kriterium für SPlit und Merge.

Bild

Bild
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Und mal eine kurze Video-Aufnahme. Die Qualität ist so lala und stottert unsäglich, ich und ich suche noch das eine Tool welches keine nervige Malware mitinstallieren will :-(
Wie auch immer, jetzt mal ein kleines Screencapture. Die Debugrenderings habe ich angelassen, rote Kästchen zeigen die Ränder der Planes die durch Split erzeugt wurden, Grün ist der Rand einer Plane die durch einen Merge erzeugt wurde.

[EDIT 2015-06-28]
Da die Aufnahmequalität der ersten Aufnahme wirklich bescheiden war und ich jetzt ein vernünftiges Tool für Capturing gefunden habe gibts gleich nochmal ein Update
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

So, jetzt das Video in guter Qualität. Nachdem das LOD jetzt gut funktioniert mach ich mich jetzt ans Code-Cleanup und Dokumentation, und dann gehts an einen der nächsten Themenbereiche.
Die Debugrenderings habe ich wie gesagt auch hier angelassen, rote Kästchen zeigen die Ränder der Planes die durch Split erzeugt wurden, Grün ist der Rand einer Plane die durch einen Merge erzeugt wurde.
Man sieht am Schluss des Videos dass ich den Space-Fog (unrealistisch, aber in jedem von uns steckt ein Abenteurer) vorbereite. Sobald hier noch dunkle Particles eingefügt werden wird das ganze schön atmosphärisch sein.
Erstmal happy.

[youtube]eeL3lZq2hkA[/youtube]
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Tja, heute habe ich mal extrem bruteforcemaessig probiert einen Waterbody zu rendern (zusätzlich zu der Planetenoberfläche).
Meine Hoffnung war dass es damit klappt dass ich einfach eine zweite Sphere rendere mit der gleichen Vertice-Dichte, LOD, und gleiche Center-Koordinate wie der Planet, nur dass ich eben kein Noise
ergänze. Diese zweite Sphere wäre dann somit die Wasseroberfläche gewesen da überall noise 0, die "feste Oberfläche" (der Boden) bewegt sich ja im Bereich -1/+1.
Anderer Shader für die den Waterbody der eine Reflektion zurückwirft, und fertig.

Leider kam es wie es zu erahnen war... schlimmstes Z-Fighting.

Bild

Mir bleibts ein absolutes Rätsel wie das hinzukriegen ist. Im Zweifel würd ich mich ja vom Gedanken einer halbtransparenten Wasseroberfläche verabschieden (auch wenns was feines wäre), aber zumindest mal eine Wasserreflektion wäre schon nett.
Kann mich jemand aufklären wie man sowas normalerweise macht?! Die Reflektion passiert ja, so mein Verständnis, immer im Shader den ich meiner Plane zuweise, aber wie krieg ichs hin dass die Landschaft nicht reflektiert, das Wasser aber schon (eine Plane besteht ja oftmals sowohl aus Wasser als auch aus Land)?
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von B.G.Michi »

Wie dir ja aufgefallen ist, funktionieren zwei Sphären schlecht. Also musst du wohl einen "Standard-"wassershader so umschreiben, dass er auch ohne vorher gerenderten Boden funktioniert. Sprich zusätzlich die Bodenfarbe aus der entsprechenden Textur laden und mit Wasserfarbe und Wassertiefe interpolieren. Das packst du dann mit derm Terrainshader und nem großen if(fHeight < 0){ ... } in nen großen Shader und nennst ihn Planetenshader ;) Wenn du nahe genug bist, damit Z-Fighting kein Problem mehr ist kannst du immer noch eine halbtransparente Wasseroberfläche rendern.
RedGuy
Establishment
Beiträge: 111
Registriert: 17.09.2002, 17:27
Echter Name: Manuel Hofmann
Wohnort: Rottweil
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von RedGuy »

Hallo !

Also ich wollte einfach nur sagen : die Bilder sehen einfach nur genial aus !!!

Hoechst interessantes Projekt !!!

Gruss
RedGuy
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Vielen Dank RedGuy.

Es gab lange kein Update mehr weil ich aktuell etwas hinter den Kulissen schraube und ein paar Optimierungen vornehmen, z.B. schnelleres Positionieren von Sonnen und Vorabladen von Materialien etc.
Im Moment hänge ich bei den Planeten noch bei den Shadern fest. Bzgl. Atmosphäre und zum Oberflächen-Texturieren (mir ist nicht ganz klar wie man im Shader Texturen, Highmaps etc. vorberechnet und ob und wie man solche Hightmaps wieder an die CPU zurückgeben kann (um so Sachen wie Noise auf der GPU zu berechnen und nicht auf der CPU). Naja ein freundlicher User im Inovae-Forum (Infinity) will da mal ein kleines Beispiel in Unity3D zusammenzimmern. Bin gespannt.

Solange habe ich meine Aufmerksamkeit nochmal auf Asteroiden gelegt, und bin da einen kleinen Schritt weitergekommen. Ich erzeuge jetzt Asteroiden mit eigenem LOD System, die Meshes werden je nach Kamera-Entwerfung in nem jeweiligen Hintergrund-Thread rasch neu berechnet. Im Moment hat jeder Asteroid 7 LOD Stufen. In den ersten vier niedrigen LODs werden die Asteroiden mittels Icosphere erzeugt (0-3 Subdivisions). Danach werden die höheren LOD Stufen mittels eines normalisierten Cube und unterschiedlich dichten Cubeseiten, die Vertices betreffend, erzeugt. Als Noise dient immer Worley Noise und anschließend Simplex Noise.

Als nächstes muss ich mich gezielt um die Positionierung und Generierung kümmern. Im Moment positioniere ich die Planeten noch völlig unabhängig von Sonnen per Simplex Noise mit unterschiedlichen Thresholds.
Das will ich dahin ändern dass ich nur noch die Sonnen per Simplex Noise Positioniere, und die Planeten dann in einem Orbit um die Sonne / immer in der Nähe einer Sonne. Dann klappts auch mit dem Lightning :-)
Und im Anschluss sollen die Asteroiden dann in Ringen um einzelne Planeten positioniert werden (mal schauen wie man die Ringzone um einen Planeten ideal berechnen kann fürs Positionieren).

Sobald ich im Inovae Forum dann einen Tip bzgl. Shader bekommen habe gehts dann bei den Planeten weiter.

[youtube]u9FreVhEdBI[/youtube]

Bild

Bild

Bild

Bild

Bild

Bild
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Prozedurales Universum

Beitrag von marcgfx »

sieht cool aus. wird nachher alles auch in bewegung sein? also die planeten rotieren um die sonne etc.
es ist übrigends lighting(beleuchtung) und nicht lightning(blitz) :P
Benutzeravatar
Schrompf
Moderator
Beiträge: 4854
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von Schrompf »

Ich finde es sehr geil, was Du da tust. Für meine persönliche Zufriedenheit wären irgendwelche Space Nebulae und so'n Zeugs im Hintergrund sehr wichtig. Müsstest Du dann ja natürlich auch generieren :-)
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Mal wieder ein kleines Update:

Bild

Der ein oder andere wird jetzt (zurecht) sagen, hoppla das sieht ja schlechter aus als die vorherigen Screenshots. :D

Stimmt. Was ist passiert? Ich habe mich vor einigen Wochen entschieden jetzt den steinigen Schritt zu gehen, den bisherigen Planeten-Code mehr oder weniger zu komplett skippen und das komplette Berechnung und Rendering des Planeten auf der GPU zu machen. Mit dem CPU basierten Code kam ich jetzt irgendwann an dem Punkt an wo man speziell dicht auf der Planetenoberfläche merklich die Schwächen der mehr oder weniger seriellen Berechnung des Noise für die Generierung der Oberfläche auf der CPU bemerkt hat. Außerdem will man ja etwas lernen und die nicht vorhandene Erfahrung mit jeglichem Shader-Code ist etwas was mich die ganze Zeit hindurch gewurmt hat. Naja, die gute Nachricht ist, das Managen des Quadtrees (Split/Merge) sowie einzelnes (Sphere-Visibility-Checks etc) dürfte ich mehr oder weniger 1:1 übernehmen können.

Heisst also jetzt zum aktuellen neuen Stand folgendes:
  • Die Quadtree-Nodes werden weiterhin auf der CPU gemanaged. Aktuell rendere ich, wie auf dem Bild, nur stumpf 6 Quadtrees (also eine Cubesphere) ohne jegliches Splitten oder Mergen, d.h. noch absolut kein LOD. Eine Plane hat nun 224x224 vertices (vorher hatte ich auf der CPU nur 32x32 bis max 96x96 (dann wurds merklich langsamer)).
  • Die Quadtrees beinhalten jetzt anstatt der vertice/normal/uv/triangle informationen in verschiedenen Arrays nur noch zwei ComputeBuffer (initial erzeugt auf der CPU), einen mit den Konstanten die zur Berechnung der Plane im Shader benötigt werden (Ausrichtung, Scale/Spacing, etc), und einen wo die Ergebnisse der Plane Berechung (Vertex-Positionen, Terrain Type, etc) in dem Shader reingeschrieben werden.
  • Pro plane werden die ComputeBuffer dann in einen Compute Shader zur GPU gegeben und dann die Berechnung in 32x32 threads angestoßen. Dieser berechnet dann die einzelnen Vertext-Positionen zunächst auf Basis eines [-1|-1] Cubes, dann wird normalisiert, der Radius hinzugezogen (der Planet also weg von [-1|1] auf seine echte Größe gebracht), und dann wird die ganze Plane (bzw. die Vertex-Positionen) nochmal in den Origin verschobene (0,0,0) um floating point precision issues für weitere Schritte zu vermeiden. Weiterhin wird hier noch das Terrain-Displacement ergänzt, d.h. fBm (Fractal Brownian Motion) Noise für die Erzeugung des Terrain. Das Ergebnis wird dann in einen der beiden ComputeBuffer geschrieben.
  • Zuletzt wird dann das eigentliche Rendern angestoßen (die jetzt fertigen Buffer bleiben auf der GPU). Der Vertex Shader erhält jetzt ein zuvor einmalig generiertes "Prototype"-Mesh aus Unity, und displaced dessen Vertices mit denen aus dem (in dem ComputeShader befüllten) ComputeBuffer. Im Surface Shader wird dann die Farbe auf Basis der Noise Results (auch im ComputeBuffer) ermittelt.
Nächste Schritte sind jetzt
  • Aufruf eines zweiten Kernels im ComputeShader, nachdem die Vertex-Positionierung im ersten Schritt im ersten Kernel abgeschlossen ist. In diesem zweiten Schritt sollen dann die Normalen berechnet werden, sowie Slope-Informationen, und der Terrain Type dort festgelegt werden und nicht erst im Surface Shader. Letzterer soll dann nur noch die Terrain Type Information aus dem Compute Shader erhalten und für das Einfärben (etc) zuständig sein.
Wenn die Schritte erledigt sind mache ich mich daran das LOD mittels des Quadtrees nachzuziehen aus meinem alten Code. Wie gesagt, das wird hoffentlich straightforward. Ich freue mich jetzt schon darauf zu schauen wie sich die Performance dann entwickelt und welche Möglichkeiten sich hier ergeben. Ich bin jetzt schon schwer beeindruckt wie schnell die 6 Planes mit sehr hohen Noise-Oktaven (+20, in der Praxis werdens dann ja weniger) auf der GPU berechnet sind. Dann dürfte das Spielen mit den Noise Parametern und das rendern von detailierterem Terrain nochmal richtig mehr Spaß machen.

Außerem habe ich einen tollen Artikel für die Erzeugung von Planetenringen gefunden, was ich gerne einmal ausprobieren will wenn ich mit Shadern sattelfest bin.
http://johnwhigham.blogspot.de/2011/11/ ... rings.html
In diesem Zuge werde ich mich dann auch um die Positionierung der Asteroiden kümmern, da passt das Thema gut hin.
Schrompf hat geschrieben:Ich finde es sehr geil, was Du da tust. Für meine persönliche Zufriedenheit wären irgendwelche Space Nebulae und so'n Zeugs im Hintergrund sehr wichtig. Müsstest Du dann ja natürlich auch generieren :-)
Zu meiner Zufriedenheit auch :D Ich hab das erstmal wieder hinten angestellt um jetzt die Planeten nochmal neu auf der GPU zu machen. Was meine Szenen bis dato immer relativ leer gemacht hat war der Mangel an Atmosphere-Effekten der Objekte und speziell Planeten. Meine Hoffnung ist dass ich mit dem gelernten der Erzeugung des Planeten auf der GPU dann genug Shader-Erfahrung gesammelt habe um das dann direkt im Anschluss zu machen. Und um dann ggf. auch Dinge wie Nebel auf der GPU hinzubekommen. Da das ganze volumetrisch und anfliegbar sein soll ists für mich immer weniger ein Hintergrund-Eyecandy Thema sondern eher die Frage wie ich das generell als etwas "echtes" umsetzen kann.
marcgfx hat geschrieben:sieht cool aus. wird nachher alles auch in bewegung sein? also die planeten rotieren um die sonne etc.
es ist übrigends lighting(beleuchtung) und nicht lightning(blitz) :P
LOL danke für die Korrektur, du hast natürlich recht :lol:
Gute Frage bzgl. Bewegung. Also bislang nein, aus zweierlei Gründen. Zum einen spiele ich immer noch im Hinterkopf mit dem (etwas verrückten) Gedanken das ganze mal auch Multiplayer-seitig zu ergänzen. Aktuell bin ich noch der Meinung dass das auf Basis der aktuellen Implementierung machbar ist (auch wenn ich aufgrund der Floating Origin Strategie wohl kaum die Funktionen von Unity out of the box verwenden kann). Ich denke je mehr dynamic in Bezug auf die Position einzelner Objekte ins Spiel kommt umso schwieriger wird das.
Zum anderen ist der schwerwiegendere Grund dass ich die Positionierung jeglicher Objekte mithilfe von Noise-Calls berechne. Das hat zwei Nachteile, zum einen ist das Ergebnis zwar recht "natürlich zufällig" und reproduzierbar, auf der anderen Seite aber nicht immer realistisch (da ich so z.B. keine Planeten um Sonnen positioniere sondern per noise im Weltraum verteile). Zum anderen wird so natürlich jegliche Bewegung von Objekten quasi unmöglich.

Meine Idee ist das so anzupassen dass ich Sonnen, Nebel und Asteroidenfelder weiterhin stationär positioniere mithilfe von Noise, dann aber die Planeten per Orbitberechnung um die Sonnen positioniere. Mein Gedanke ist dann, z.B. über die Zuhilfenahme der Systemzeit, diese konrolliert um die Sonne instanziieren und kreisen lassen zu können. Sowie Planeten und Asteroiden, wieder unter Zuhilfenahme der Systemzeit, zu rotieren. Wenn die Systemzeit für alle Clients gleich sind so müsste ich, so die Hoffnung, ein Setting erzeugen können wo Planeten um die Sonne kreisen und einzelne Objekte rotieren, und alle Clients den gleichen Zustand haben. Aber wie gesagt, das ist ein Gedankenspiel, daran habe ich mich noch nicht gemacht. Früher oder später werde ich aber die Positionierung der Planeten um die Sonnen herum anpassen, allein schon um sinnvolle Beleuchtung der Planeten machen zu können.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Ich muss mal fragen kennt sich einer von euch mit ComputeShadern aus. Ich knabbere da an einem Problem rum dass ich seit einigen Tagen nicht gelöst kriege. Ich würde das gerne noch hinkriegen bevor ich mein einen Teil meines Projekts exportiere und woanders uploade, da es dann einmal die ganze Kette in Unity3D zur Generierung einer Quadsphere auf der GPU zeigt. Für den Fall dass mal jemand den gleichen Weg wie ich gehen muss wenn er keine Ahung von Shadern hat. ;-)

Folgendes Problem:
Ich möchte in meinen ComputeShader, der für die Generierung einer Terrain Plane zuständig ist, gerne zwei Kernels packen. Ziel ist das Terrain in zwei Stages zu berechnen.

Im ersten Kernel (CSMain1) sollen die Vertex-Positionen berechnet werden, mithilfe von Noise Calls etc. Dazu gebe ich zwei ComputeBuffer Referenzen in den ComputeShader: generationConstantsBuffer (enthält die Daten von der CPU die für die Generierung notwendig sind (Scale, Anzahl vertices für die Plane, Schnee-Grenze, etc) und patchGeneratedDataBuffer (dort soll das fertige Ergebnis des ComputeShader stehen), der später an den Vertex/Surface Shader zum Rendern weitergegeben wird.

Code: Alles auswählen

StructuredBuffer<GenerationConstantsStruct>	generationConstantsBuffer;
RWStructuredBuffer<OutputStruct>			patchGeneratedDataBuffer;
Im zweiten Kernel (CSMain2) soll dann auf Basis des Ergebnisses aus dem ersten Kernel die normalen berechnet werden, Slope [Gefälle] sowie der Terrain Typ. Hierfür ist es wichtig dass ich die Ergebnisse (alle Vertex-Positionen) aus dem ersten Kernel habe. D.h. Kernel1 muss erst vollständig ausgeführt werden bevor Kernel2 startet. In Unity3d stoße ich die Kernel über Graphics.Dispatch(Kernel, Threads) asynchron an.
Ich es leider bislang noch nicht hinbekommen dass erst Kernel1 und dann Kernel2 ausgeführt wird.

Um das Problem zu lösen habe ich folgenden Tip gefunden
All work on the GPU is done asynchronously from the CPU, but any command you send to the GPU is executed in the same sequence that you sent it. Sometimes half of the GPU will be working on one command and the other half on the next command. When the next command absolutely depends on the previous command having been completed, the graphics driver will 'flush' the GPU's command queue up to that point, forcing it to catch up. This can manifest in GPU stalls.
But the CPU will rarely stall waiting for the GPU. This only happens when a resource must be synchronized between the two, such as during a CPU 'read back' of GPU data. In that case the GPU will flush, and the CPU will 'block', waiting for the GPU to finish and grant access to its resource.
You should 'ping pong' or 'double buffer' your 2nd stage Compute Buffer data. In other words, don't overwrite data in a buffer that was generated during the same frame. This will force the GPU to finish the first task before beginning the second task, thus destroying parallelism.
Mein angepasstes Beispiel (Schreiben in einen dritten ComputeBuffer in Kernel1 und Lesen aus eben diesem in Kernel2) in welchem ich im zweiten Kernel einfach die Ergebnisse aus dem Zwischenbuffer in den "finalen" Buffer schreibe, funktioert leider nicht. Habt ihr noch eine Idee was ich ausprobieren kann um sicherzugehen dass die GPU erst Kernel1 beendet?

SpaceObjectProceduralPlanet.cs (CPU, macht den Dispatch)

Code: Alles auswählen

[...]
// Dispatch to the first kernel
// Set Buffers
computeShader.SetBuffer(_kernel[0], "generationConstantsBuffer", quadtreeTerrain.generationConstantsBuffer);
computeShader.SetBuffer(_kernel[0], "patchGeneratedDataBuffer", quadtreeTerrain.patchGeneratedDataBuffer);
// Dispatch first kernel
computeShader.Dispatch(_kernel[0], THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, THREADGROUP_SIZE_Z);
// Dispatch to the second kernel
// Set Buffers
// No change of buffers done here
// Dispatch second kernel
computeShader.Dispatch(_kernel[1], THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, THREADGROUP_SIZE_Z);
[...]
SpaceObjectProceduralPlanet.compute (GPU ComputeShader)

Code: Alles auswählen

#pragma kernel CSMain1
#pragma kernel CSMain2
#include "noiseSimplexOptimized.cginc"

//We define the size of a group in the x and y directions, z direction will just be one
#define threadsPerGroup_X 32
#define threadsPerGroup_Y 32
#define TWO_PI 6.283185

//#define   NOISE_SINGLE
#define   NOISE_FBM

#ifdef NOISE_FBM
	//Good FBM values
	#define     NoiseOctaves			10
	#define     NoiseFrequency			0.001
	#define     NoiseAmplitude			1.0
	#define     NoiseLacunarity		1.918
	#define     NoiseGain			0.5
#endif

// The structure of the input computebuffer
struct GenerationConstantsStruct
{
	int nVertsPerEdge;
   	float scale;
   	float spacing;
    	float3 patchCubeCenter;
	float3 cubeFaceEastDirection;
	float3 cubeFaceNorthDirection;
	float planetRadius;
	float terrainMaxHeight;
	float noiseSeaLevel;
	float noiseSnowLevel;
};

// The structure of the output computebuffer
struct OutputStruct
{
    	float4 position;
	float3 normal;
	float noise;
	float3 patchCenter;
};


//Various input buffers, and an output buffer that is written to by the kernel
StructuredBuffer<GenerationConstantsStruct>	generationConstantsBuffer;
RWStructuredBuffer<OutputStruct>			patchGenerationDataDoubleBuffer;
RWStructuredBuffer<OutputStruct>			patchGeneratedDataBuffer;

//An FBM [Fractal Brownian Motion] noise call
float FBM(float3 p, int octaves, float frequency, float amplitude, float lacunarity, float gain)
{
	float noise = 0.0f;           
	for (int i = 0; i < octaves; ++i)
	{
			noise += snoise(p.xyz * frequency) * amplitude;         
			frequency *= lacunarity;
			amplitude *= gain;
	}
    return noise;
}

//The kernel for this compute shader, each thread group contains a number of threads specified by numthreads(x,y,z)
//We lookup the index into the flat array by using x + y * x_stride
//The position is calculated from the thread index and then the z component is shifted by the Wave function
[numthreads(threadsPerGroup_X,threadsPerGroup_Y,1)]

// Do position calculations
void CSMain1 (uint3 id : SV_DispatchThreadID)
{
	// Get the constants
	GenerationConstantsStruct constants = generationConstantsBuffer[0];
	float patchWidth = constants.spacing* constants.nVertsPerEdge;

	// Get outBuffOffset
	int outBuffOffset = id.x + id.y * constants.nVertsPerEdge;
	
	[.. create the positions and everything before written into the temporary buffer ...]

	// Write to Output Buffer
	patchGenerationDataDoubleBuffer [outBuffOffset].position = float4(patchCoordCentered.x, patchCoordCentered.y, patchCoordCentered.z, 1);
	patchGenerationDataDoubleBuffer [outBuffOffset].normal = patchNormalizedCoord;
	patchGenerationDataDoubleBuffer [outBuffOffset].noise = noise2;	
	patchGenerationDataDoubleBuffer [outBuffOffset].patchCenter = patchCenter;
}

// Do normal calculations
void CSMain2 (uint3 id : SV_DispatchThreadID)
{
	// Get the constants
	GenerationConstantsStruct constants = generationConstantsBuffer[0];
	float patchWidth = constants.spacing* constants.nVertsPerEdge;

	// Get outBuffOffset
	int outBuffOffset = id.x + id.y * constants.nVertsPerEdge;

	// Read back from DoubleBuffer (write back into the final buffer used for rendering for this example)
	patchGeneratedDataBuffer[outBuffOffset] = patchGenerationDataDoubleBuffer [outBuffOffset];
}
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Falls mal jemand in das gleiche Problem laufen sollte wie ich die beiden Kernels im Compute Shader sauber anzusprechen:
Die Ursache lag daran dass die Computer Shader beim Dispatch nicht richtig ausgeführt worden, speziell der zweite. Woran lags?
Ich hatte gemäß einiger (von den ohnehin nur wenigen) Compute Shader Tutorials folgenden Code-Aufbau:

Code: Alles auswählen

#pragma kernel CSMain1
#pragma kernel CSMain2

[numthreads(threadsPerGroup_X,threadsPerGroup_Y,1)]

void CSMain1 (uint3 id : SV_DispatchThreadID)
{
    // code
}

void CSMain2 (uint3 id : SV_DispatchThreadID)
{
    // code
}
Korrekt sieht das aber so aus, dann funktioniert es:

Code: Alles auswählen

#pragma kernel CSMain1

[numthreads(threadsPerGroup_X,threadsPerGroup_Y,1)]

void CSMain1 (uint3 id : SV_DispatchThreadID)
{
    // code
}

#pragma kernel CSMain2

[numthreads(threadsPerGroup_X,threadsPerGroup_Y,1)]

void CSMain2 (uint3 id : SV_DispatchThreadID)
{
    // code
}
Ich hatte mich von dem Glauben Irreleiten lassen die Pragma Zeile mappt auf den Funktionsnamen, dem scheint aber nicht so zu sein. Wie auch immer, jetzt tuts, ich habe testweise um zu sehen ob der zweite Kernel wirklich ausgeführt wird in der zweiten Stage nochmal etwas Noise auf das Ergebnis der ersten draufgerechnet, man sieht dass das Terrain entsprechend "angehoben" ist und danach kaum noch Wasser existiert.

Bild

Bild

Wie gesagt, noch nicht schön, aber jetzt habe ich die Shader alle soweit im Griff dass ich meine alte Quadtree Implementierung inkl. Split/Merge etc. anwenden kann.
D.h. ich bin jetzt soweit dass ich das Terrain in zwei Compute Shader Kernels berechnen kann (der zweite Kernelaufruf ist wichtig für die Berechnung der Normalen und der Slope Werte, dazu brauche ich die fertigen Vertex-Positionen aus dem ersten Kernel), und dann wird das ganze über Compute Buffer an den Vertex / Surface Shader weitergereicht und gerendert. An den Normalen bin ich grad dran, aktuell zeigen die noch sphärisch vom Mittelpunkt, daher auch diese fiese kreisrunde Siluette auf den Screenshots. Aber ich bin fast durch damit, wird.
Geschwindkeitszuwachs ist im Vergleich zur CPU Implementierung echt enorm. Am Wochenende werde ich an der Berechnung der Normalen versuchen und den Quadtree weiter implementieren sodass ich die Planes splitte und merge.
Bin jetzt schon von den Möglichkeiten auf der GPU begeistert.
Zuletzt geändert von sushbone am 11.12.2015, 19:07, insgesamt 1-mal geändert.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4854
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Prozedurales Universum

Beitrag von Schrompf »

Sehr cool. Und Danke für's Berichten Deiner Erfahrungen. Ich habe auch Compute Shader langfristig auf dem Schirm und freue mich über jede Erfahrung, die ich nicht selbst machen muss :-)
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Bild

So die Normalen-Berechnung in einer zweiten Stage per zweitem Kernel ist ergänzt. Klappt wunderbar, es gibt auch keinen Stress die Kernel synchronisieren, das ist echt ziemlich straightforward. Einfach in Kernel zwei auf dem Ergebnisbuffer des ersten Kernels weiterarbeiten, fertig. Was noch fehlt ist die spezielle Behandlung der Normalen am Rand der Plane / seitlichen Vertices, weil da die Informationen über die umgebenen Vertices teils fehlt bzw. in den Buffers der anderen Planes steckt. Ich denke hier werde ich in einer Spezialbehandlung Extra-Noisecalls machen. Da ich pro Vertex das obere und rechte Vertex in die Normalenberechnung einbeziehe wären das 224x2 zusätzliche Calls, was ich akzeptabel finde bei einer Gesamtzahl von 224x224 Calls. Schauen wir mal, diese Kanten sind ein eckliges Thema hoffe ich kriege das rasch in den Griff.

Sobald das fertig ist gehts mit splitten und mergen weiter, um dann den Planet zu detailieren.

Bild
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Bild

Die Kanten sind jetzt bzgl. der Normalen-Berechnung in den Griff bekommen, keine Lücken mehr zwischen den Planes. :D
Die Lösung war eigentlich ganz einfach, im ersten Kernel ergänze ich pro Seitenlänge zwei Vertices und berechne so 226x226 Vertex-Positionen und speiche diese in einem Zwischen-Buffer.
Der zweite Kernel wird dann in der gewünschten 224x224 Matrix ausgeführt, jetzt habe in dem ersten Buffer aber alle umliegenden Vertex-Informationen parat für die innenliegenden 224x224, die ich dann inkl. der korrekten Normalen in den finalen Buffer zum Rendern der 224x224 Plane schreibe.

Jetzt kommt Split+Merge im Quadtree, es wird spannend. :)
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Bei der Arbeit an dem Split und Merge und der Strukturierung der Shader bin ich leider auf ein neues etwas difuses Problem gestoßen.Immer dann wenn ich denke ich habe die Shader im Griff werde ich eines besseren belehrt. :cry:

Ich würde gerne die Defintion der Terrainart (Wasser, Grass, Felsen, Schnee, etc....) in den Compute Shader verlegen. Der Surface Shader soll dann nur noch die fertigen Informationen aus einem Buffer auslesen und Färben/Texturieren. Bislang hat das auch gut geklappt, im Surface Shader habe ich bis jetzt die Vertex-Positione, Normalen, und Noise-Informationen in einen Buffer geschrieben, und im Vertex sowie Surface Shader wurden diese dann ausgelesen und weiterverarbeitet. Im Suface Shader war folgender simpler Code der zum richtigen Ergebnis geführt hat (korrekte Einfärbung basierend auf dem Noise-Value):

Code: Alles auswählen

void surf(Input IN, inout SurfaceOutputStandard o)
{
....
	if (IN.noise <= 0.0) {								
		terrainColor = fixed4(0.0, 0.2, 1.0, 1.0); // Blue
	}
	else if (IN.noise <= 0.1) {
		terrainColor = fixed4(0.6, 0.5, 0.1, 1.0); // Brown
	}
	else if (IN.noise <= 0.5) {
		terrainColor = fixed4(0.0, 0.9, 0.0, 1.0); // Green
	}
	else if (IN.noise <= 0.8) {
		terrainColor = fixed4(0.7, 0.7, 0.7, 1.0); // Grey
	}
	else {
		terrainColor = fixed4(1.0, 1.0, 1.0, 1.0); // White
	}
}
Jetzt will ich die Logik um welches Terrain es sich handelt in den Compute Shader verlegen, indem dort in dem Buffer eine Variable "TerrainType" (als int) die Art des Terrains festlegt. Der Surface Shader soll dies nur noch auslesen. Genaugenommen mache ich nichts anderes als die obige Prüfung (IN.noise <= 0.0, 0.1, 0.5 etc) in den Compute Shader zu verlegen und das Ergebnis per Variable weiterzugeben.

Und jetzt kommt das Problem zutage, dass der Surface Shader hier seltsame Ergebnisse rendered. Es wird nur ein Teil der Vertices richtig gefärbt. Ich habe versucht mich dem Problem zu nähern, und hänge jetzt daran dass es bei irgendwas mit der Weitergabe von Variablen zwischen Vertex Shader und Surface Shader zu tun haben muss. Versuche ich ggf. völlig falsch im Surface Shader an die Informationen zu kommen, in dem ich versuche alles über den INPUT struct durchzureichen? Das was im Endeffekt nicht funktioniert ist eigentlich ein trivialer Vorgang, ich will eine im Vertex Shader definierte o.terrainType fix mit dem Wert 4 belegen und im Surface Shader wieder korrekt auslesen.

Wenn ich im Vertex Shader also o.terrainType = 4; fix belege, und dies im Surface Shader über INPUT.terrainType auslese, dann flackert das Rendering zwischen folgenden zwei Bildern. Die komplett blaue Fläche ist das eigentlich richtige Ergebnis, dort wo Abschnitte rot sind konnte ich offensichtlich im Surface Shader nicht richtig aus INPUT.terrainType auslesen. Ich poste hier auch mal den vollständigen Code (noch nicht sehr umfangreich). Das interessante ist, wenn ich das ganze im Vollbild laufen lasse, dann sieht man fast nur das zweite Bild (mit roten Abschnitten). Im kleinen Fenster Modus in der Unity GUI springt das Rendering wie gesagt zwischen erstem und zweiten Bild.

Bild

Bild
Shader "Custom/ProceduralPatch3" {
Properties{
_Color("Color", Color) = (1,1,1,1)
_colorDeepWater("Deep Water", Color) = (0.03, 0.16, 0.35, 1.0)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_Glossiness("Smoothness", Range(0,1)) = 0.5
_Metallic("Metallic", Range(0,1)) = 0.0
}
SubShader{
Tags{ "RenderType" = "Opaque" }
LOD 200

CGPROGRAM

#define nVerticesPerSide 224.0
//#define SHOW_GRIDLINES
//#define SHOW_PATCH_BORDER

#include "UnityCG.cginc"
#include "noiseSimplexOptimized.cginc"

#pragma surface surf Standard fullforwardshadows
#pragma vertex vert

struct appdata_full_compute {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
half4 texcoord4 : TEXCOORD4;
half4 texcoord5 : TEXCOORD5;
#endif
fixed4 color : COLOR;
#ifdef SHADER_API_D3D11
uint id: SV_VertexID;
#endif
};

struct OutputStruct {
float4 position;
float3 normal;
float noise;
float slope;
float terrainType;
float3 patchCenter;
};


float globalNoise;

#pragma target 5.0
sampler2D _MainTex;

#ifdef SHADER_API_D3D11
StructuredBuffer<OutputStruct> patchGeneratedFinalDataBuffer;
#endif

struct Input {
float2 uv_MainTex;
float noise;
float terrainType;
};

void vert(inout appdata_full_compute v, out Input o)
{
#ifdef SHADER_API_D3D11
// Read Data from buffer
float4 position = patchGeneratedFinalDataBuffer[v.id].position;
float3 normal = patchGeneratedFinalDataBuffer[v.id].normal;
float noise = patchGeneratedFinalDataBuffer[v.id].noise;
float slope = patchGeneratedFinalDataBuffer[v.id].slope;
float terrainType = patchGeneratedFinalDataBuffer[v.id].terrainType;
float3 patchCenter = patchGeneratedFinalDataBuffer[v.id].patchCenter;

// Perform changes to the data
// Translate the patch to its 'planet-space' center:
position.xyz += patchCenter;

// Apply data
v.vertex = float4(position);
v.normal = float3(normal);
o.uv_MainTex = v.texcoord.xy;
o.noise = noise;
o.terrainType = 4; // 4 = FOR DEBUG PURPOSE. Normale we read from above terrainType variable.
#endif
}

half _Glossiness;
half _Metallic;
fixed4 _Color;

void surf(Input IN, inout SurfaceOutputStandard o)
{
// Terrain color variable
fixed4 terrainColor;

// Read out terrainType and define base color.
if (IN.terrainType == 0.0) {
terrainColor = fixed4(0.0, 0.2, 1.0, 1.0); // Blue
}
else if (IN.terrainType == 1.0) {
terrainColor = fixed4(0.6, 0.5, 0.1, 1.0); // Brown
}
else if (IN.terrainType == 2.0) {
terrainColor = fixed4(0.0, 0.9, 0.0, 1.0); // Green
}
else if (IN.terrainType == 3.0) {
terrainColor = fixed4(0.7, 0.7,0.7, 1.0); // Grey
}
else if (IN.terrainType == 4.0) {
terrainColor = fixed4(1.0, 1.0, 1.0, 1.0); // White
}
else terrainColor = fixed4(1.0, 0.0, 0.0, 1.0);


o.Albedo = clamp(c.rgb, fixed3(0, 0, 0), fixed3(1, 1, 1));
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;

}
ENDCG
}
FallBack "Diffuse"
}
Es verhält sich übrigends unterschiedlich ob ich TerrainType als float oder int definiere. Dann, bei int, springt das Bild zwischen weiß und in Teilen grau.

Bild
Habt ihr irgendeine Idee woran das liegen kann? Ich bin völlig ratlos :? :cry: :(
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Prozedurales Universum

Beitrag von marcgfx »

was passiert denn wenn du deine IN.noise ifs weglässt und eine fixe terrain farbe angibst? wenn die farbe nicht korrekt übergeben werden würde, müsste der effect auch in diesem fall eintreten, oder sehe ich das falsch? vermutlich bin ich keine grosse hilfe, aber wie weit kannst du den code vereinfachen und den fehler weiterhin reproduzieren?
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Nun was komisch ist, in meiner ersten Implementierung wo ich den Noise Wert über IN.noise übergebe (der erste obige Abschnitt in meinem Post), nach der gleichen Strategie d.h. auslesen des Buffer und belegen von o.noise im Vertex Shader, sowie auslesen im Surface Buffer, so funktioniert noch alles.

Was nicht mehr klappt ist dass ich jetzt versuche den INPUT Struct zu erweitern um "terrainType", diesen Wert im Vertex Buffer mit 4 zu belegen und im Surface Shader wieder auszulesen. Tue ich das kommt es zu dem Problem dass ein Teil der INPUT.terrainType Werten in den If Schleifen nicht erkannt wird, und dann per else rot eingefärbt wird.
Lasse ich den ganzen IF Teil weg und lege einfach nur eine fixe Farbe fest, so greift das korrekt.

Code: Alles auswählen

	void surf(Input IN, inout SurfaceOutputStandard o)
		{
			...

			// Fix Terrain color
			fixed4 terrainColor = fixed4(0.0, 1.0, 1.0, 1.0);
					
			...
		} 
Bild

Was also offensichtlich nicht klappen will ist folgender Vorgang, reduziert auf das wesentliche:

Code: Alles auswählen

Shader "Custom/ProceduralPatch3" {
	SubShader{
		struct Input {
			float2 uv_MainTex;
			float noise;
			float terrainType;
		};

		void vert(inout appdata_full_compute v, out Input o)
		{
			o.terrainType = 4.0;
		}

		void surf(Input IN, inout SurfaceOutputStandard o)
		{	
			if (IN.terrainType == 4.0) {
				terrainColor = fixed4(0.0, 0.2, 1.0, 1.0);	// Blue (funktioniert)
			}
			else terrainColor = fixed4(1.0, 0.0, 0.0, 1.0);		// Rot (funktioniert nicht)
		}
		ENDCG
	}
FallBack "Diffuse"
}
Darf man ggf. nicht davon ausgehen dass ein Vertex Shader immer vor dem Surface Shader ausgeführt wird?
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Prozedurales Universum

Beitrag von marcgfx »

kann es irgendwie sein dass es an einer interpolation oder präzision liegt? anstatt == 4.0 mal > 3.0 probieren? wär auch komisch...
die reihenfolge müsste immer die gleiche sein, daran wirds nicht liegen.
Benutzeravatar
sushbone
Beiträge: 78
Registriert: 02.06.2013, 15:31

Re: [Projekt] Prozedurales Universum

Beitrag von sushbone »

Hmm spannend. Du könntest Recht haben. Folgende Anpassung funktioniert :shock:

Code: Alles auswählen

Shader "Custom/ProceduralPatch3" {
	SubShader{
		struct Input {
			float2 uv_MainTex;
			float noise;
			float terrainType;
		};

		void vert(inout appdata_full_compute v, out Input o)
		{
			o.terrainType = 4.0;
		}

		void surf(Input IN, inout SurfaceOutputStandard o)
		{
					
			if (IN.terrainType <= 0.1) {
				terrainColor = fixed4(0.0, 0.2, 1.0, 1.0);	// Blue
			}
			else if (IN.terrainType <= 1.1) {
				terrainColor = fixed4(0.6, 0.5, 0.1, 1.0);	// Brown
			}
			else if (IN.terrainType <= 2.1) {
				terrainColor = fixed4(0.0, 0.9, 0.0, 1.0);	// Green
			}
			else if (IN.terrainType <= 3.1) {
				terrainColor = fixed4(0.7, 0.7,0.7, 1.0);	// Grey
			}
			else if (IN.terrainType <= 4.1) {
				terrainColor = fixed4(1.0, 1.0, 1.0, 1.0);	// White
			}
			else terrainColor = fixed4(1.0, 0.0, 0.0, 1.0);
		}
		ENDCG
	}
FallBack "Diffuse"
}
Jetzt kommt je nach Belegung o.terrainType = 0.0 , 1.0 , 2.0 etc. im Vertex Shader die richtige Farbe.
Scheint in der Tat so zu sein dass IF (IN.terrainType == Floatwert) basierend auf einem INPUT wert nicht zuverlässig funktioniert, obwohl ich mit der Variable nichts weiter mache als sie durch das Input Struct vom Vertex zum Surface Shader zu übergeben. Hmm oha, was ist denn hier ein sauberes Vorgen ob das möglichst eindeutige IF conditions auf Fixwerten zu machen?
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Prozedurales Universum

Beitrag von marcgfx »

ich weiss nur dass ich auch mit präzisionsproblemen zu kämpfen hatte, als ich meine webgl shader erstellt habe. leider kenn ich mich nicht in den details aus.
es gibt so was, evtl. hats damit was zu tun:
precision highp float; // Defines precision for float and float-derived (vector/matrix) types.
Antworten