# Algorithmus zum Erzeugen von Kugeln?

27

Hat jemand einen Algorithmus zum Erzeugen einer Kugelprozedur mit einer `la`Anzahl von Breitengradlinien, einer `lo`Anzahl von Längengradlinien und einem Radius von `r`? Ich brauche es, um mit Unity zu arbeiten, also müssen die Scheitelpunktpositionen definiert und dann die Dreiecke über Indizes definiert werden ( weitere Informationen ).

BEARBEITEN Ich habe es geschafft, den Code in einer Einheit zum Laufen zu bringen. Aber ich glaube, ich hätte etwas falsch gemacht. Wenn ich das aufklappe `detailLevel`, füge ich einfach mehr Eckpunkte und Polygone hinzu, ohne sie zu verschieben. Hab ich was vergessen

BEARBEITEN 2 Ich habe versucht, das Netz entlang seiner Normalen zu skalieren. Das habe ich bekommen. Ich glaube, ich vermisse etwas. Soll ich nur bestimmte Normalen skalieren?

1
Warum schauen Sie sich nicht an, wie vorhandene Open Source-Implementierungen dies tun? Schauen Sie sich an, wie Three.js dies zum Beispiel mit Meshes macht.
Brice

3
Ein kleiner Hinweis: Wenn Sie nicht die Längen- und Breitengrade eingeben müssen, möchten Sie dies mit ziemlicher Sicherheit nicht , da die Dreiecke, die Sie erhalten, viel weiter von der Uniform entfernt sind als die, die Sie mit anderen Methoden erhalten. (Vergleichen Sie die Dreiecke am Nordpol mit denen am Äquator: Sie verwenden in beiden Fällen die gleiche Anzahl von Dreiecken, um eine Breitengradlinie zu erhalten, aber in der Nähe des Pols hat diese Breitengradlinie einen sehr kleinen Umfang, während am Äquator es ist der gesamte Umfang Ihres Globus.) Techniken wie die in David Livelys Antwort sind im Allgemeinen viel besser.

1
Sie normalisieren die Scheitelpunktpositionen nach der Unterteilung nicht. Ich habe diesen Teil nicht in mein Beispiel aufgenommen. Durch die Normalisierung sind sie alle gleich weit vom Zentrum entfernt, wodurch die gewünschte Kurvenapproximation erstellt wird.
3Dave

Denken Sie daran, einen Ballon in der Mitte des Ikosaeders aufzublasen. Wenn der Ballon auf das Netz drückt, passt er sich an die Form des Ballons (der Kugel) an.
3Dave

4
"Normalisieren" bedeutet, die Länge eines Vektors auf 1 zu setzen. Sie müssen so etwas tun `vertices[i] = normalize(vertices[i])`. Übrigens gibt Ihnen dies auch Ihre neuen, korrekten Normalen, also sollten Sie dies `normals[i] = vertices[i]`später tun .
Sam Hocevar

Antworten:

31

Um so etwas zu bekommen: Erstellen Sie ein Ikosaeder (20-seitiger regelmäßiger Körper) und unterteilen Sie die Flächen, um eine Kugel zu erhalten (siehe Code unten).

Die Idee ist im Grunde:

• Erstellen Sie ein reguläres n-Hedron (ein Körper, bei dem jedes Gesicht dieselbe Größe hat). Ich benutze ein Ikosaeder, weil es der Körper mit der größten Anzahl von Flächen ist, wobei jede Fläche die gleiche Größe hat. (Dafür gibt es irgendwo einen Beweis. Wenn Sie wirklich neugierig sind, wenden Sie sich an Google.) Auf diese Weise erhalten Sie eine Kugel, in der fast jedes Gesicht die gleiche Größe hat, was die Texturierung ein wenig vereinfacht. • Unterteilen Sie jedes Gesicht in vier gleich große Gesichter. Jedes Mal, wenn Sie dies tun, wird die Anzahl der Flächen im Modell vervierfacht.

``````///      i0
///     /  \
///    m02-m01
///   /  \ /  \
/// i2---m12---i1
``````

`i0`, `i1`Und `i2`sind die Eckpunkte des ursprünglichen Dreiecks. (Eigentlich Indizes in den Vertex-Puffer, aber das ist ein anderes Thema). `m01`ist der Mittelpunkt der Kante `(i0,i1)`, m12 ist der Mittelpunkt der Kante `(i1,12)`und `m02`ist offensichtlich der Mittelpunkt der Kante `(i0,i2)`.

Wenn Sie eine Fläche unterteilen, stellen Sie sicher, dass Sie keine doppelten Scheitelpunkte erstellen. Jeder Mittelpunkt wird von einer anderen Quellfläche geteilt (da die Kanten von Flächen geteilt werden). Der folgende Code berücksichtigt dies, indem er ein Wörterbuch mit benannten Mittelpunkten verwaltet, die erstellt wurden, und den Index eines zuvor erstellten Mittelpunkts zurückgibt, wenn er verfügbar ist, anstatt einen neuen zu erstellen.

• Wiederholen Sie diesen Vorgang, bis Sie die gewünschte Anzahl von Flächen für Ihren Würfel erreicht haben.

• Wenn Sie fertig sind, normalisieren Sie alle Scheitelpunkte, um die Oberfläche zu glätten. Wenn Sie dies nicht tun, erhalten Sie statt einer Kugel nur ein Ikosaeder mit höherer Auflösung.

• Voila! Sie sind fertig. Konvertieren Sie die resultierenden Vektor- und Indexpuffer in ein `VertexBuffer`und `IndexBuffer`und zeichnen Sie mit `Device.DrawIndexedPrimitives()`.

Folgendes würden Sie in Ihrer "Sphere" -Klasse verwenden, um das Modell zu erstellen (XNA-Datentypen und C #, aber es sollte ziemlich klar sein):

``````        var vectors = new List<Vector3>();
var indices = new List<int>();

GeometryProvider.Icosahedron(vectors, indices);

for (var i = 0; i < _detailLevel; i++)
GeometryProvider.Subdivide(vectors, indices, true);

/// normalize vectors to "inflate" the icosahedron into a sphere.
for (var i = 0; i < vectors.Count; i++)
vectors[i]=Vector3.Normalize(vectors[i]);
``````

Und die `GeometryProvider`Klasse

``````public static class GeometryProvider
{

private static int GetMidpointIndex(Dictionary<string, int> midpointIndices, List<Vector3> vertices, int i0, int i1)
{

var edgeKey = string.Format("{0}_{1}", Math.Min(i0, i1), Math.Max(i0, i1));

var midpointIndex = -1;

if (!midpointIndices.TryGetValue(edgeKey, out midpointIndex))
{
var v0 = vertices[i0];
var v1 = vertices[i1];

var midpoint = (v0 + v1) / 2f;

if (vertices.Contains(midpoint))
midpointIndex = vertices.IndexOf(midpoint);
else
{
midpointIndex = vertices.Count;
}
}

return midpointIndex;

}

/// <remarks>
///      i0
///     /  \
///    m02-m01
///   /  \ /  \
/// i2---m12---i1
/// </remarks>
/// <param name="vectors"></param>
/// <param name="indices"></param>
public static void Subdivide(List<Vector3> vectors, List<int> indices, bool removeSourceTriangles)
{
var midpointIndices = new Dictionary<string, int>();

var newIndices = new List<int>(indices.Count * 4);

if (!removeSourceTriangles)

for (var i = 0; i < indices.Count - 2; i += 3)
{
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];

var m01 = GetMidpointIndex(midpointIndices, vectors, i0, i1);
var m12 = GetMidpointIndex(midpointIndices, vectors, i1, i2);
var m02 = GetMidpointIndex(midpointIndices, vectors, i2, i0);

new[] {
i0,m01,m02
,
i1,m12,m01
,
i2,m02,m12
,
m02,m01,m12
}
);

}

indices.Clear();
}

/// <summary>
/// create a regular icosahedron (20-sided polyhedron)
/// </summary>
/// <param name="primitiveType"></param>
/// <param name="size"></param>
/// <param name="vertices"></param>
/// <param name="indices"></param>
/// <remarks>
/// You can create this programmatically instead of using the given vertex
/// and index list, but it's kind of a pain and rather pointless beyond a
/// learning exercise.
/// </remarks>

/// note: icosahedron definition may have come from the OpenGL red book. I don't recall where I found it.
public static void Icosahedron(List<Vector3> vertices, List<int> indices)
{

new int[]
{
0,4,1,
0,9,4,
9,5,4,
4,5,8,
4,8,1,
8,10,1,
8,3,10,
5,3,8,
5,2,3,
2,7,3,
7,10,3,
7,6,10,
7,11,6,
11,0,6,
0,1,6,
6,1,10,
9,0,11,
9,11,2,
9,2,5,
7,2,11
}
.Select(i => i + vertices.Count)
);

var X = 0.525731112119133606f;
var Z = 0.850650808352039932f;

new[]
{
new Vector3(-X, 0f, Z),
new Vector3(X, 0f, Z),
new Vector3(-X, 0f, -Z),
new Vector3(X, 0f, -Z),
new Vector3(0f, Z, X),
new Vector3(0f, Z, -X),
new Vector3(0f, -Z, X),
new Vector3(0f, -Z, -X),
new Vector3(Z, X, 0f),
new Vector3(-Z, X, 0f),
new Vector3(Z, -X, 0f),
new Vector3(-Z, -X, 0f)
}
);

}

}
``````

Gute Antwort. Vielen Dank. Ich kann nicht sagen, aber ist das ein Code? Oh, und das Lat / Long spielt keine Rolle, solange ich die Auflösung einstellen kann.
Daniel Pendergast

Es ist nicht Unity (XNA), aber es gibt Ihnen die Scheitelpunktkoordinaten und die Indexliste. Ersetzen Sie Vector3 durch das Unity-Äquivalent. Sie legen die Auflösung fest, indem Sie die Anzahl der Unterteilungsiterationen anpassen. Jede Schleife multipliziert die Anzahl der Flächen mit 4. 2 oder 3 Iterationen ergeben eine schöne Kugel.
3Dave

Ah ich sehe. Es ist fast identisch mit Unity C #. Nur ein paar Fragen ... Warum setzen Sie die Indizes, wenn sie definiert sind, in ein `int`Array? Und was macht der `.Select(i => i + vertices.Count)`?
Daniel Pendergast

Das `.Select(i => i + vertices.Count)`funktioniert bei mir überhaupt nicht. Ist es nur eine XNA-Funktion?
Daniel Pendergast,

1
Stellen Sie sicher, dass Sie "using System.Linq" einschließen, wie es definiert.Select, etc.
3Dave

5

Betrachten wir die parametrische Definition einer Kugel: wobei Theta und Phi zwei inkrementelle Winkel sind, die wir als `var t`und bezeichnen, `var u`und Rx, Ry und Rz die unabhängigen Radien (Radien) in allen drei kartesischen Richtungen sind, die im Fall einer Kugel als eine einzige definiert werden Radius `var rad`.

Betrachten wir nun die Tatsache, dass das `...`Symbol eine Iteration anzeigt, die auf die Verwendung einer Schleife hindeutet. Das Konzept von `stacks`und `rows`lautet "Wie oft werden Sie iterieren". Da jede Iteration den Wert von t oder u addiert, ist die Krümmung der Kugel umso genauer, je mehr Iterationen vorhanden sind.

Die Voraussetzung der ‚Sphäre Zeichnung‘ Funktion ist es, die folgenden Parameter gegeben haben: `int latitudes, int longitudes, float radius`. Die Nachbedingung (Ausgabe) ist, die berechneten Eckpunkte zurückzugeben oder anzuwenden. Abhängig davon, wie Sie dies verwenden `vector3`möchten , kann die Funktion ein Array von (dreidimensionalen) Vektoren zurückgeben. Wenn Sie vor Version 2.0 eine Art einfaches OpenGL verwenden, möchten Sie die Scheitelpunkte möglicherweise direkt auf den Kontext anwenden.

NB Das Anwenden eines Scheitelpunkts in openGL ruft die folgende Funktion auf `glVertex3f(x, y, z)`. In dem Fall, in dem wir die Eckpunkte speichern würden, würden wir `vector3(x, y, z)`zur einfachen Speicherung einen neuen hinzufügen .

Auch die Art und Weise, wie Sie das Längen- und Breitengradsystem zum Arbeiten aufgefordert haben, erforderte eine Anpassung der Definition der Kugel (im Grunde genommen Umschalten von z und y), aber dies zeigt nur, dass die Definition sehr formbar ist und dass Sie frei umschalten können x-, y- und z-Parameter, um die Richtung zu ändern, in die die Kugel gezeichnet wird (wo die Breiten- und Längengrade liegen).

Nun schauen wir uns an, wie wir die Breiten- und Längengrade machen werden. Die Breiten werden durch die Variable dargestellt `u`, sie iterieren von 0 bis 2π Radianten (360 Grad). Wir können seine Iteration daher wie folgt codieren:

``````float latitude_increment = 360.0f / latitudes;

for (float u = 0; u < 360.0f; u += latitude_increment) {
// further code ...
}
``````

Jetzt werden die Längengrade durch die Variable dargestellt `t`und für 0 bis π (180 Grad) iteriert. Daher sieht der folgende Code dem vorherigen ähnlich:

``````float latitude_increment = 360.0f / latitudes;
float longitude_increment = 180.0f / longitudes;

for (float u = 0; u <= 360.0f; u += latitude_increment) {
for (float t = 0; t <= 180.0f; t += longitude_increment) {
// further code ...
}
}
``````

(Beachten Sie, dass Schleifen die Endbedingung einschließen , da das Intervall für die parametrische Integration zwischen 0 und 2π liegt. Einschließlich . Sie erhalten eine Teilkugel, wenn Ihre Bedingungen nicht einschließen.)

Nach der einfachen Definition der Kugel können wir nun die Variablendefinition wie folgt ableiten (annehmen `float rad = radius;`):

``````float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
``````

Noch eine wichtige Warnung! In den meisten Fällen verwenden Sie OpenGL in irgendeiner Form, und selbst wenn dies nicht der Fall ist, müssen Sie dies möglicherweise noch tun. Für ein dreidimensionales Objekt müssen mehrere Eckpunkte definiert werden. Dies wird im Allgemeinen erreicht, indem der nächste berechenbare Scheitelpunkt bereitgestellt wird. Wie in der Abbildung oben die verschiedenen Koordinaten sind `x+∂`und `y+∂`, können wir leicht drei weitere Eckpunkte für jede gewünschte Verwendung generieren. Die anderen Eckpunkte sind (angenommen `float rad = radius;`):

``````float x = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u)));

``````

Schließlich ist hier eine funktionierende Vollfunktion, die alle Eckpunkte einer Kugel zurückgibt, und die zweite zeigt eine funktionierende OpenGL-Implementierung des Codes (dies ist eine C-Syntax und kein JavaScript, dies sollte mit allen C-Sprachen funktionieren, einschließlich C # bei Verwendung von Unity).

``````static Vector3[] generateSphere(float radius, int latitudes, int longitudes) {

float latitude_increment = 360.0f / latitudes;
float longitude_increment = 180.0f / longitudes;

// if this causes an error, consider changing the size to [(latitude + 1)*(longitudes + 1)], but this should work.
Vector3[] vertices = new Vector3[latitude*longitudes];

int counter = 0;

for (float u = 0; u < 360.0f; u += latitude_increment) {
for (float t = 0; t < 180.0f; t += longitude_increment) {

vertices[counter++] = new Vector3(x, y, z);

}
}

return vertices;

}
``````

OpenGL-Code:

``````static int createSphereBuffer(float radius, int latitudes, int longitudes) {

int lst;

lst = glGenLists(1);

glNewList(lst, GL_COMPILE);
{

float latitude_increment = 360.0f / latitudes;
float longitude_increment = 180.0f / longitudes;

for (float u = 0; u < 360.0f; u += latitude_increment) {

glBegin(GL_TRIANGLE_STRIP);

for (float t = 0; t < 180.0f; t += longitude_increment) {

vertex3f(x, y, z);

vertex3f(x1, y1, z1);

}

glEnd();

}

}
glEndList()

return lst;

}

// to render VVVVVVVVV

// external variable in main file
static int sphereList = createSphereBuffer(desired parameters)

// called by the main program
void render() {

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glCallList(sphereList);

}
``````

PS Möglicherweise haben Sie diese Aussage bemerkt `rad = radius;`. Auf diese Weise kann der Radius in der Schleife basierend auf der Position oder dem Winkel geändert werden. Dies bedeutet, dass Sie die Kugel rauschen können, um sie aufzurauen, sodass sie natürlicher aussieht, wenn der gewünschte Effekt einem Planeten ähnelt. Z.B`float rad = radius * noise[x][y][z];`

Claude-Henry.

Die Zeile `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` ist falsch. Sie haben bereits ein X, Y mit einer Hypotenuse von berechnet `rad`. Jetzt machst du das eine Bein eines Dreiecks und implizierst, dass die Hypotenuse des Dreiecks auch ist `rad`. Dies gibt Ihnen effektiv einen Radius von `rad * sqrt(2)`.
3Dave

@ DavidLively danke für den Hinweis, ich schrieb dies vor einiger Zeit, so bin ich nicht überrascht, wenn es schlecht oder sogar völlig falsch ist.
claudehenry

Es macht immer Spaß, wenn ich einen Fehler in einem meiner Beiträge vor Jahren finde. Es passiert. :)
3Dave

4

Ich habe so etwas vor einiger Zeit geschaffen, um aus Spaß und Wissenschaft eine Kugel aus Würfeln zu machen. Es ist nicht zu schwer. Im Grunde genommen nehmen Sie eine Funktion, die einen Kreis von Eckpunkten erzeugt, und gehen dann die Höheninkremente durch, bei denen Sie Kreise in jeder Höhe mit dem Radius erzeugen möchten, der zum Erzeugen einer Kugel erforderlich ist. Hier habe ich den Code geändert, damit er nicht für Cubes gilt:

``````public static void makeSphere(float sphereRadius, Vector3f center, float heightStep, float degreeStep) {
for (float y = center.y - sphereRadius; y <= center.y + sphereRadius; y+=heightStep) {
if (radius == 0) {//for the top and bottom points of the sphere add a single point
} else { //otherwise step around the circle and add points at the specified degrees
for (float d = 0; d <= 360; d += degreeStep) {
}
}
}
}

}``````

Jetzt würde dieser Code nur noch Punkte für den Breitengrad erzeugen. Sie können jedoch fast den gleichen Code verwenden, um die Längengradlinien zu erstellen. Es sei denn, Sie müssen zwischen den einzelnen Iterationen rotieren und jeweils einen vollständigen Kreis bilden `degreeStep`.

Entschuldigung, dies ist keine vollständige Antwort oder Unity-spezifisch, aber hoffentlich können Sie damit anfangen.

Dies ist ziemlich gut, wenn Sie eine Lat / Long-Kugel benötigen, aber Sie können es ein wenig vereinfachen, indem Sie bis zum letzten Schritt in sphärischen Koordinaten arbeiten.
3Dave

1
Vielen Dank @ David. Ich stimme zu, wenn ich dazu komme, eine Version mit sphärischen Koordinaten zu schreiben, werde ich sie hier posten.
MichaelHouse

3

Könnte man nicht einfach mit einer einfachen Form beginnen, könnte es sich um ein Kästchen mit einem Abstand von der Mitte zur Ecke handeln. Um eine detailliertere Kugel zu erstellen, unterteilen Sie alle Polygone und verschieben Sie die Scheitelpunkte in einen Abstand von der Mitte, sodass der Vektor ihre aktuelle Position durchläuft.

Wiederholen Sie den Vorgang, bis er kugelförmig genug für Ihren Geschmack ist.

Dies entspricht im Wesentlichen dem ikosaedrischen Ansatz, nur mit einer anderen Ausgangsform. Ein Vorteil, mit einem Würfel zu beginnen, für den ich glaube, dass er nicht erwähnt wurde: Es ist wesentlich einfacher, anständige UV-Karten zu erstellen, da Sie eine Cubemap verwenden können und wissen, dass Ihre Texturnähte perfekt mit den Kanten in Ihrem Kugelnetz übereinstimmen.

@StevenStadnicki Das einzige Problem, das ich mit Würfeln habe, ist, dass die Gesichter nach einigen Unterteilungen sehr unterschiedlich groß werden.
3Dave

@DavidLively Das hängt stark davon ab, wie Sie die Flächen unterteilen. Wenn Sie die quadratischen Flächen Ihres Würfels in ein gleichmäßiges Raster schneiden und dann nach außen projizieren / normalisieren, stimmt dies Vorsprung entlang der Bögen der Kanten gleichmäßig beabstandet sein; das klappt ganz gut.

3Dave

@ EricJohansson Übrigens, als Lehrer fühle ich mich gezwungen zu erwähnen, dass dies eine ziemlich wichtige Erkenntnis für jemanden ist, der die Unterteilungsmethode anscheinend noch nie zuvor gesehen hat. Sie haben mein Vertrauen in die Menschheit für die nächsten 12 Stunden erneuert.
3Dave

2

Benötigen Sie tatsächlich die 3D-Geometrie oder nur die Form?

Sie können eine gefälschte Kugel mit einem einzigen Quad erstellen. Setzen Sie einfach einen Kreis darauf und schattieren Sie es richtig. Dies hat den Vorteil, dass es unabhängig von der Entfernung zur Kamera oder der Auflösung genau die erforderliche Auflösung hat.

Hier gibt es ein Tutorial .

1
Nizza Hack, aber scheitert, wenn Sie es texturieren müssen.
3Dave

@DavidLively Es sollte möglich sein, die Texturkoordinaten pro Pixel basierend auf der Drehung zu berechnen, es sei denn, Sie müssen Polygone einzeln texturieren.
David C. Bishop

@DavidCBishop Sie müssten die "Linsenbildung" der Oberfläche berücksichtigen - Texelkoordinaten werden aus Gründen der Perspektive nahe der Kreisgrenze eingeklemmt - an welchem ​​Punkt Sie die Drehung vortäuschen. Dazu gehört auch, dass viel mehr Arbeit in den Pixel-Shader verlagert wird, als im Vertex-Shader ausgeführt werden kann (und wir alle wissen, dass VS viel billiger sind!).
3Dave

0

Hier ist ein Code für eine beliebige Anzahl von gleich beabstandeten Eckpunkten einer Kugel, der wie eine Orangenschale eine Linie von Punkten in einer Spirale um eine Kugel windet. Danach liegt es an Ihnen, wie Sie die Scheitelpunkte verbinden. Sie können Nachbarpunkte in der Schleife als 2 jedes Dreiecks verwenden und dann feststellen, dass der dritte Punkt eine proportionale Verdrehung um die Kugel ist, die höher oder tiefer liegt Kennen Sie einen besseren Weg?

``````var spherevertices = vector3 generic list...

public var numvertices= 1234;
var size = .03;

function sphere ( N:float){//<--- N is the number of vertices i.e 123

var inc =  Mathf.PI  * (3 - Mathf.Sqrt(5));
var off = 2 / N;
for (var k = 0; k < (N); k++)
{
var y = k * off - 1 + (off / 2);
var r = Mathf.Sqrt(1 - y*y);
var phi = k * inc;
var pos = Vector3((Mathf.Cos(phi)*r*size), y*size, Mathf.Sin(phi)*r*size);

}``````

};

-1

Obwohl David in seiner Antwort absolut richtig ist, möchte ich eine andere Perspektive anbieten.

Für meine Aufgabe zur prozeduralen Inhaltsgenerierung habe ich unter anderem Ikosaeder im Vergleich zu traditionelleren unterteilten Sphären betrachtet. Schauen Sie sich diese prozedural erzeugten Bereiche an: Beide sehen aus wie vollkommen gültige Kugeln, oder? Schauen wir uns die Drahtgitter an: Wow, was ist da passiert? Die Drahtmodellversion der zweiten Kugel ist so dicht, dass sie texturiert aussieht! Ich werde Ihnen ein Geheimnis verraten: Die zweite Version ist ein Ikosaeder. Es ist eine fast perfekte Kugel, aber sie hat einen hohen Preis.

Kugel 1 verwendet 31 Unterteilungen auf der x-Achse und 31 Unterteilungen auf der z-Achse für insgesamt 3.844 Flächen.

Sphere 2 verwendet 5 rekursive Unterteilungen für insgesamt 109.220 Gesichter.

Aber okay, das ist nicht wirklich fair. Verringern wir die Qualität erheblich: Kugel 1 verwendet 5 Unterteilungen auf der x-Achse und 5 Unterteilungen auf der z-Achse für insgesamt 100 Flächen.

Sphere 2 verwendet 0 rekursive Unterteilungen für insgesamt 100 Gesichter.

Sie verwenden die gleiche Anzahl von Gesichtern, aber meiner Meinung nach sieht die linke Kugel besser aus. Es sieht weniger klumpig und viel runder aus. Schauen wir uns an, wie viele Gesichter wir mit beiden Methoden erzeugen.

Ikosaeder:

• Level 0 - 100 Gesichter
• Level 1 - 420 Gesichter
• Level 2 - 1.700 Gesichter
• Level 3 - 6.820 Gesichter
• Level 4 - 27.300 Gesichter
• Level 5 - 109.220 Gesichter

Unterteilte Kugel:

• YZ: 5 - 100 Gesichter
• YZ: 10 - 400 Gesichter
• YZ: 15 - 900 Gesichter
• YZ: 20 - 1.600 Gesichter
• YZ: 25 - 2.500 Gesichter
• YZ: 30 - 3.600 Gesichter

Wie Sie sehen können, nimmt der Ikosaeder in Gesichtern exponentiell zu, um eine dritte Potenz! Das liegt daran, dass wir sie für jedes Dreieck in drei neue Dreiecke unterteilen müssen.

Die Wahrheit ist: Sie müssen nicht brauchen die Präzision ein Ikosaeder wird dir geben. Weil beide ein viel schwierigeres Problem verstecken: die Texturierung einer 2D-Ebene auf einer 3D-Kugel. So sieht die Oberseite aus: Oben links sehen Sie die verwendete Textur. Zufällig wird es auch prozedural generiert. (Hey, es war ein Kurs zur prozeduralen Generierung, oder?)

Es sieht schrecklich aus, oder? Nun, das ist so gut wie es nur geht. Ich habe Bestnoten für mein Textur-Mapping bekommen, weil die meisten Leute es nicht einmal so richtig verstehen.

Überlegen Sie sich also, Cosinus und Sinus zu verwenden, um eine Kugel zu erzeugen. Bei gleicher Detailgenauigkeit werden deutlich weniger Gesichter generiert.

6
Ich fürchte, ich kann das nur ablehnen. Die Icosphäre skaliert exponentiell? Das liegt nur daran, dass Sie entschieden haben, dass Sie exponentiell skalieren sollten. Eine UV-Kugel erzeugt bei gleicher Detailgenauigkeit weniger Gesichter als eine Icosphäre? Das ist falsch, absolut falsch, total rückwärts.
Sam Hocevar

4
Die Unterteilung muss nicht rekursiv sein. Sie können die Kante eines Dreiecks in beliebig viele gleiche Teile teilen. Wenn Sie `N`Teile verwenden, erhalten Sie `N*N`neue Dreiecke, die quadratisch sind, genau wie Sie es mit der UV-Kugel tun.
Sam Hocevar

6
Ich muss auch hinzufügen, dass die Kugel, die Sie sagen, "weniger klumpig und viel runder" aussieht, aus der besten Perspektive betrachtet wird, was diese Behauptung ebenfalls unehrlich macht. Mache einfach den gleichen Screenshot mit den Kugeln von oben, um zu sehen, was ich meine.
Sam Hocevar

4
Außerdem sehen Ihre Ikosaeder-Zahlen nicht richtig aus. Stufe 0 besteht aus 20 Gesichtern (per Definition), dann aus 80, 320, 1280 usw. Sie können eine beliebige Anzahl und ein beliebiges Muster nach Ihren Wünschen unterteilen. Die Glätte des Modells wird letztendlich durch die Anzahl und Verteilung der Gesichter im Endergebnis bestimmt (unabhängig von der Methode, mit der sie generiert werden), und wir möchten die Größe jedes Gesichts so gleichmäßig wie möglich halten (keine Polarität) Drücken), um ein gleichmäßiges Profil unabhängig vom Blickwinkel zu erhalten. Hinzu kommt, dass der Code der Unterteilung viel einfacher ist (imho) ...
3Dave

2
In diese Antwort wurde einiges investiert, weshalb ich mich ein wenig schlecht fühle, wenn ich sie ablehne. Aber es ist völlig und gänzlich falsch, also muss ich. Eine perfekt runde Icosphere, die in FullHD den gesamten Bildschirm ausfüllt, benötigt 5 Unterteilungen, wobei ein einfaches Ikosaeder keine Unterteilungen hat. Ein Ikosaeder ohne Unterteilungen hat nicht 100 Gesichter, es hat 20. Icosa = 20. Es ist der Name! Jede Unterteilung multipliziert die Anzahl der Gesichter mit 4, also 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20.480. Bei einer Geosphäre brauchen wir mindestens 40'000 Gesichter, um eine ebenso runde Kugel zu erhalten.
Peter

-1

Das folgende Skript erstellt ein Ikosaeder mit n Polygonen ... Basis 12. Es unterteilt die Polygone auch in separate Maschen und berechnet die Gesamtzahl der Vert-Duplikate und Polygone.

Ich konnte nichts Ähnliches finden, also habe ich das erstellt. Hängen Sie das Skript einfach an ein GameObject an und legen Sie die Unterteilungen im Editor fest. Als nächstes wird an der Modifikation des Rauschens gearbeitet.

``````/* Creates an initial Icosahedron with 12 vertices...
* ...And a couple other Icosahedron C# for Unity scripts
*
* Allows an Icosahedron to be created with multiple separate polygon meshes
* I used a dictionary of Dictionary<int, List<Vector3>> to represent the
* Polygon index and the vertice index
* polygon corresponds to vertice
* so that all vertices in dictionary vertice will correspond to the polygons in polygon
*
* If you need help understanding Dictionaries
* https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx
*
* --I used dictionaries because I didn't know what programming instrument to use, so there may be more
*
* Essentially int represents the index, and
* List<Vector3> represents the actual Vector3 Transforms of the triangle
* OR List<Vector3> in the polygon dictionary will act as a reference to the indice/index number of the vertices
*
* For example the polygon dictionary at key will contain a list of Vector3's representing polygons
* ... Vector3.x , Vector3.y, Vector3.z in the polygon list would represent the 3 indexes of the vertice list
* AKA the three Vector3 transforms that make up the triangle
*    .
*  ./_\.
*
* Create a new GameObject and attach this script
*  -The folders for the material and saving of the mesh data will be created automatically
*    -Line 374/448
*
* numOfMainTriangles will represent the individual meshes created
* numOfSubdivisionsWithinEachTriangle represents the number of subdivisions within each mesh
*
* Before running with Save Icosahedron checked be aware that it can take several minutes to
*   generate and save all the meshes depending on the level of divisions
*
* There may be a faster way to save assets - Line 430 - AssetDatabase.CreateAsset(asset,path);
* */

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class UnityIcosahedronGenerator : MonoBehaviour {
IcosahedronGenerator icosahedron;
public const int possibleSubDivisions = 7;
public static readonly int[] supportedChunkSizes = { 20, 80, 320, 1280, 5120, 20480, 81920};

[Range(0, possibleSubDivisions - 1)]
public int numOfMainTriangles = 0;
[Range(0,possibleSubDivisions - 1)]
public int numOfSubdivisionsWithinEachTriangle = 0;
public bool saveIcosahedron = false;

// Use this for initialization
void Start() {
icosahedron = ScriptableObject.CreateInstance<IcosahedronGenerator>();

// 0 = 12 verts, 20 tris
icosahedron.GenBaseIcosahedron();
icosahedron.SeparateAllPolygons();

// 0 = 12 verts, 20 tris - Already Generated with GenBaseIcosahedron()
// 1 = 42 verts, 80 tris
// 2 = 162 verts, 320 tris
// 3 = 642 verts, 1280 tris
// 5 = 2562 verts, 5120 tris
// 5 = 10242 verts, 20480 tris
// 6 = 40962verts, 81920 tris
if (numOfMainTriangles > 0) {
icosahedron.Subdivide(numOfMainTriangles);
}
icosahedron.SeparateAllPolygons();

if (numOfSubdivisionsWithinEachTriangle > 0) {
icosahedron.Subdivide(numOfSubdivisionsWithinEachTriangle);
}

icosahedron.CalculateMesh(this.gameObject, numOfMainTriangles,numOfSubdivisionsWithinEachTriangle, saveIcosahedron);
icosahedron.DisplayVertAndPolygonCount();
}
}

public class Vector3Dictionary {
public List<Vector3> vector3List;
public Dictionary<int, List<Vector3>> vector3Dictionary;

public Vector3Dictionary() {
vector3Dictionary = new Dictionary<int, List<Vector3>>();
return;
}

public void Vector3DictionaryList(int x, int y, int z) {
vector3List = new List<Vector3>();

return;
}

public void Vector3DictionaryList(int index, Vector3 vertice) {
vector3List = new List<Vector3>();

if (vector3Dictionary.ContainsKey(index)) {
vector3List = vector3Dictionary[index];
vector3Dictionary[index] = vector3List;
} else {
}

return;
}

public void Vector3DictionaryList(int index, List<Vector3> vertice, bool list) {
vector3List = new List<Vector3>();

if (vector3Dictionary.ContainsKey(index)) {
vector3List = vector3Dictionary[index];
for (int a = 0; a < vertice.Count; a++) {
}
vector3Dictionary[index] = vector3List;
} else {
for (int a = 0; a < vertice.Count; a++) {
}
}

return;
}

public void Vector3DictionaryList(int index, int x, int y, int z) {
vector3List = new List<Vector3>();

if (vector3Dictionary.ContainsKey(index)) {
vector3List = vector3Dictionary[index];
vector3Dictionary[index] = vector3List;
} else {
}

return;
}

public void Vector3DictionaryList(int index, float x, float y, float z, bool replace) {
if (replace) {
vector3List = new List<Vector3>();

vector3Dictionary[index] = vector3List;
}

return;
}
}

public class IcosahedronGenerator : ScriptableObject {
public Vector3Dictionary icosahedronPolygonDict;
public Vector3Dictionary icosahedronVerticeDict;
public bool firstRun = true;

public void GenBaseIcosahedron() {
icosahedronPolygonDict = new Vector3Dictionary();
icosahedronVerticeDict = new Vector3Dictionary();

// An icosahedron has 12 vertices, and
// since it's completely symmetrical the
// formula for calculating them is kind of
// symmetrical too:

float t = (1.0f + Mathf.Sqrt(5.0f)) / 2.0f;

icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, t, 0).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, t, 0).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, -t, 0).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, -t, 0).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, t).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, t).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, -t).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, -t).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, -1).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, 1).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, -1).normalized);
icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, 1).normalized);

// And here's the formula for the 20 sides,
// referencing the 12 vertices we just created.
// Each side will be placed in it's own dictionary key.
// The first number is the key/index, and the next 3 numbers reference the vertice index
icosahedronPolygonDict.Vector3DictionaryList(0, 0, 11, 5);
icosahedronPolygonDict.Vector3DictionaryList(1, 0, 5, 1);
icosahedronPolygonDict.Vector3DictionaryList(2, 0, 1, 7);
icosahedronPolygonDict.Vector3DictionaryList(3, 0, 7, 10);
icosahedronPolygonDict.Vector3DictionaryList(4, 0, 10, 11);
icosahedronPolygonDict.Vector3DictionaryList(5, 1, 5, 9);
icosahedronPolygonDict.Vector3DictionaryList(6, 5, 11, 4);
icosahedronPolygonDict.Vector3DictionaryList(7, 11, 10, 2);
icosahedronPolygonDict.Vector3DictionaryList(8, 10, 7, 6);
icosahedronPolygonDict.Vector3DictionaryList(9, 7, 1, 8);
icosahedronPolygonDict.Vector3DictionaryList(10, 3, 9, 4);
icosahedronPolygonDict.Vector3DictionaryList(11, 3, 4, 2);
icosahedronPolygonDict.Vector3DictionaryList(12, 3, 2, 6);
icosahedronPolygonDict.Vector3DictionaryList(13, 3, 6, 8);
icosahedronPolygonDict.Vector3DictionaryList(14, 3, 8, 9);
icosahedronPolygonDict.Vector3DictionaryList(15, 4, 9, 5);
icosahedronPolygonDict.Vector3DictionaryList(16, 2, 4, 11);
icosahedronPolygonDict.Vector3DictionaryList(17, 6, 2, 10);
icosahedronPolygonDict.Vector3DictionaryList(18, 8, 6, 7);
icosahedronPolygonDict.Vector3DictionaryList(19, 9, 8, 1);

return;
}

public void SeparateAllPolygons(){
// Separates all polygons and vertex keys/indicies into their own key/index
// For example if the numOfMainTriangles is set to 2,
// This function will separate each polygon/triangle into it's own index
// By looping through all polygons in each dictionary key/index

List<Vector3> originalPolygons = new List<Vector3>();
List<Vector3> originalVertices = new List<Vector3>();
List<Vector3> newVertices = new List<Vector3>();
Vector3Dictionary tempIcosahedronPolygonDict = new Vector3Dictionary();
Vector3Dictionary tempIcosahedronVerticeDict = new Vector3Dictionary();

// Cycles through the polygon list
for (int i = 0; i < icosahedronPolygonDict.vector3Dictionary.Count; i++) {
originalPolygons = new List<Vector3>();
originalVertices = new List<Vector3>();

// Loads all the polygons in a certain index/key
originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

// Since the original script was set up without a dictionary index
// It was easier to loop all the original triangle vertices into index 0
// Thus the first time this function runs, all initial vertices will be
// redistributed to the correct indicies/index/key

if (firstRun) {
originalVertices = icosahedronVerticeDict.vector3Dictionary;
} else {
// i - 1 to account for the first iteration of pre-set vertices
originalVertices = icosahedronVerticeDict.vector3Dictionary[i];
}

// Loops through all the polygons in a specific Dictionary key/index
for (int a = 0; a < originalPolygons.Count; a++){
newVertices = new List<Vector3>();

int x = (int)originalPolygons[a].x;
int y = (int)originalPolygons[a].y;
int z = (int)originalPolygons[a].z;

// Adds three vertices/transforms for each polygon in the list

// Overwrites the Polygon indices from their original locations
// index (20,11,5) for example would become (0,1,2) to correspond to the
// three new Vector3's added to the list.
// In the case of this function there will only be 3 Vector3's associated to each dictionary key
tempIcosahedronPolygonDict.Vector3DictionaryList(0, 1, 2);

// sets the index to the size of the temp dictionary list
int tempIndex = tempIcosahedronPolygonDict.vector3Dictionary.Count;
// adds the new vertices to the corresponding same key in the vertice index
// which corresponds to the same key/index as the polygon dictionary
tempIcosahedronVerticeDict.Vector3DictionaryList(tempIndex - 1, newVertices, true);
}
}
firstRun = !firstRun;

// Sets the temp dictionarys as the main dictionaries
icosahedronVerticeDict = tempIcosahedronVerticeDict;
icosahedronPolygonDict = tempIcosahedronPolygonDict;
}

public void Subdivide(int recursions) {
// Divides each triangle into 4 triangles, and replaces the Dictionary entry

var midPointCache = new Dictionary<int, int>();
int polyDictIndex = 0;
List<Vector3> originalPolygons = new List<Vector3>();
List<Vector3> newPolygons;

for (int x = 0; x < recursions; x++) {
polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;
for (int i = 0; i < polyDictIndex; i++) {
newPolygons = new List<Vector3>();
midPointCache = new Dictionary<int, int>();
originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

for (int z = 0; z < originalPolygons.Count; z++) {
int a = (int)originalPolygons[z].x;
int b = (int)originalPolygons[z].y;
int c = (int)originalPolygons[z].z;

// Use GetMidPointIndex to either create a
// new vertex between two old vertices, or
// find the one that was already created.
int ab = GetMidPointIndex(i,midPointCache, a, b);
int bc = GetMidPointIndex(i,midPointCache, b, c);
int ca = GetMidPointIndex(i,midPointCache, c, a);

// Create the four new polygons using our original
// three vertices, and the three new midpoints.
}
// Replace all our old polygons with the new set of
// subdivided ones.
icosahedronPolygonDict.vector3Dictionary[i] = newPolygons;
}
}
return;
}

int GetMidPointIndex(int polyIndex, Dictionary<int, int> cache, int indexA, int indexB) {
// We create a key out of the two original indices
// by storing the smaller index in the upper two bytes
// of an integer, and the larger index in the lower two
// bytes. By sorting them according to whichever is smaller
// we ensure that this function returns the same result
// whether you call
// GetMidPointIndex(cache, 5, 9)
// or...
// GetMidPointIndex(cache, 9, 5)

int smallerIndex = Mathf.Min(indexA, indexB);
int greaterIndex = Mathf.Max(indexA, indexB);
int key = (smallerIndex << 16) + greaterIndex;

// If a midpoint is already defined, just return it.
int ret;
if (cache.TryGetValue(key, out ret))
return ret;

// If we're here, it's because a midpoint for these two
// vertices hasn't been created yet. Let's do that now!
List<Vector3> tempVertList = icosahedronVerticeDict.vector3Dictionary[polyIndex];

Vector3 p1 = tempVertList[indexA];
Vector3 p2 = tempVertList[indexB];
Vector3 middle = Vector3.Lerp(p1, p2, 0.5f).normalized;

ret = tempVertList.Count;
icosahedronVerticeDict.vector3Dictionary[polyIndex] = tempVertList;

return ret;
}

public void CalculateMesh(GameObject icosahedron, int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle, bool saveIcosahedron) {
GameObject meshChunk;
List<Vector3> meshPolyList;
List<Vector3> meshVertList;
List<int> triList;

CreateFolders(numOfMainTriangles, numOfSubdivisionsWithinEachTriangle);
CreateMaterial();

// Loads a material from the Assets/Resources/ folder so that it can be saved with the prefab later
Material material = Resources.Load("BlankSphere", typeof(Material)) as Material;

int polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;

// Used to assign the child objects as well as to be saved as the .prefab
// Sets the name
icosahedron.gameObject.name = "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle;

for (int i = 0; i < polyDictIndex; i++) {
meshPolyList = new List<Vector3>();
meshVertList = new List<Vector3>();
triList = new List<int>();
// Assigns the polygon and vertex indices
meshPolyList = icosahedronPolygonDict.vector3Dictionary[i];
meshVertList = icosahedronVerticeDict.vector3Dictionary[i];

// Sets the child gameobject parameters
meshChunk = new GameObject("MeshChunk");
meshChunk.transform.parent = icosahedron.gameObject.transform;
meshChunk.transform.localPosition = new Vector3(0, 0, 0);
meshChunk.GetComponent<MeshRenderer>().material = material;
Mesh mesh = meshChunk.GetComponent<MeshFilter>().mesh;

// Adds the triangles to the list
for (int z = 0; z < meshPolyList.Count; z++) {
}

mesh.vertices = meshVertList.ToArray();
mesh.triangles = triList.ToArray();
mesh.uv = new Vector2[meshVertList.Count];

/*
//Not Needed because all normals have been calculated already
Vector3[] _normals = new Vector3[meshVertList.Count];
for (int d = 0; d < _normals.Length; d++){
_normals[d] = meshVertList[d].normalized;
}
mesh.normals = _normals;
*/

mesh.normals = meshVertList.ToArray();

mesh.RecalculateBounds();

// Saves each chunk mesh to a specified folder
// The folder must exist
if (saveIcosahedron) {
string sphereAssetName = "icosahedronChunk" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "_" + i + ".asset";
AssetDatabase.CreateAsset(mesh, "Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/" + sphereAssetName);
AssetDatabase.SaveAssets();
}
}

// Removes the script for the prefab save
// Saves the prefab to a specified folder
// The folder must exist
if (saveIcosahedron) {
DestroyImmediate(icosahedron.GetComponent<UnityIcosahedronGenerator>());
PrefabUtility.CreatePrefab("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + ".prefab", icosahedron);
}

return;
}

void CreateFolders(int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle){
// Creates the folders if they don't exist
if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons")) {
AssetDatabase.CreateFolder("Assets", "Icosahedrons");
}
if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle)) {
AssetDatabase.CreateFolder("Assets/Icosahedrons", "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle);
}
if (!AssetDatabase.IsValidFolder("Assets/Resources")) {
AssetDatabase.CreateFolder("Assets", "Resources");
}

return;
}

static void CreateMaterial() {
if (Resources.Load("BlankSphere", typeof(Material)) == null) {
// Create a simple material asset if one does not exist
material.color = Color.blue;
AssetDatabase.CreateAsset(material, "Assets/Resources/BlankSphere.mat");
}

return;
}

// Displays the Total Polygon/Triangle and Vertice Count
public void DisplayVertAndPolygonCount(){
List<Vector3> tempVertices;
HashSet<Vector3> verticeHash = new HashSet<Vector3>();

int polygonCount = 0;
List<Vector3> tempPolygons;

// Saves Vertices to a hashset to ensure no duplicate vertices are counted
for (int a = 0; a < icosahedronVerticeDict.vector3Dictionary.Count; a++) {
tempVertices = new List<Vector3>();
tempVertices = icosahedronVerticeDict.vector3Dictionary[a];
for (int b = 0; b < tempVertices.Count; b++) {
}
}

for (int a = 0; a < icosahedronPolygonDict.vector3Dictionary.Count; a++) {
tempPolygons = new List<Vector3>();
tempPolygons = icosahedronPolygonDict.vector3Dictionary[a];
for (int b = 0; b < tempPolygons.Count; b++) {
polygonCount++;
}
}

Debug.Log("Vertice Count: " + verticeHash.Count);
Debug.Log("Polygon Count: " + polygonCount);

return;
}
}``````
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.