Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Verwendung des Viewports als Textur

Einführung

In diesem Tutorial lernen Sie die Verwendung von Viewport als Textur kennen, die auf 3D-Objekte angewendet werden kann. Zu diesem Zweck erstellen wir einen prozeduralen Planeten wie dem folgenden hier:

../../_images/planet_example.png

Bemerkung

Dieses Tutorial behandelt nicht, wie man eine dynamische Atmosphäre, wie die dieses Planeten, programmiert.

Dieses Tutorial setzt voraus, dass Sie mit der Einrichtung einer grundlegenden Szene vertraut sind, einschließlich: einer Camera3D, einer Lichtquelle, einer MeshInstance3D mit einem Primitive Mesh, und der Anwendung eines StandardMaterial3D auf das Mesh. Der Schwerpunkt liegt auf der Verwendung des Viewport zur dynamischen Erstellung von Texturen, die auf das Mesh angewendet werden können.

In diesem Tutorial werden wir die folgenden Themen behandeln:

  • Verwendung von Viewport als Rendertextur

  • Abbilden einer Textur auf eine Kugel mit gleichwinkliger Zuordnung

  • Fragment-Shader-Techniken für prozedurale Planeten

  • Festlegen einer Rauheitskarte aus einer Viewport-Textur

Einrichten des Viewports

Fügen Sie zuerst Viewport zu der Szene hinzu.

Als nächstes setzen Sie die Größe des Viewport auf (1024, 512). Das Viewport kann eigentlich jede Größe haben, solange die Breite doppelt so groß ist wie die Höhe. Die Breite muss doppelt so groß sein wie die Höhe, damit das Bild genau auf die Kugel abgebildet werden kann, da wir eine gleichwinklige Projektion verwenden werden, aber dazu später mehr.

../../_images/planet_new_viewport.png

Als nächstes deaktivieren Sie HDR und 3D. Wir brauchen kein HDR, weil die Oberfläche unseres Planeten nicht besonders hell sein wird, also sind Werte zwischen 0 und 1 ausreichend. Und wir werden ein ColorRect zum Rendern der Oberfläche verwenden, also brauchen wir auch kein 3D.

Wählen Sie den Viewport und fügen Sie ein ColorRect als Child-Element hinzu.

Setzen Sie die Anker "Rechts" und "Unten" auf 1, und stellen Sie sicher, dass alle Ränder auf 0 gesetzt sind. Dadurch wird sichergestellt, dass das ColorRect den gesamten Viewport einnimmt.

../../_images/planet_new_colorrect.png

Als nächstes fügen wir ein Shader Material zum ColorRect hinzu (ColorRect > CanvasItem > Material > Material > Neu: ShaderMaterial).

Bemerkung

Grundlegende Vertrautheit mit Shading wird für dieses Tutorial empfohlen. Aber auch wenn Sie keine Erfahrung mit Shadern haben, wird der gesamte Code zur Verfügung gestellt, so dass Sie keine Probleme haben sollten, diesem Tutorial zu folgen.

ColorRect > CanvasItem > Material > Material > Klicken / Bearbeiten > ShaderMaterial > Shader > Neuer Shader > Klicken / Bearbeiten:

shader_type canvas_item;

void fragment() {
    COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}

Der obige Code rendert einen Farbverlauf wie den folgenden.

../../_images/planet_gradient.png

Jetzt haben wir die Grundlagen für einen Viewport, den wir rendern, und wir haben ein eindeutiges Bild, das wir auf die Kugel anwenden können.

Anwenden der Textur

MeshInstance3D > GeometryInstance > Geometrie > Material-Überschreibung > Neu: StandardMaterial3D:

Jetzt gehen wir in die MeshInstance3D und fügen ein StandardMaterial3D dazu. Es besteht keine Notwendigkeit für ein spezielles Shader Material (obwohl das eine gute Idee für fortgeschrittene Effekte wäre, wie die Atmosphäre im obigen Beispiel).

MeshInstance3D > GeometryInstance > Geometrie > Material-Überschreibung > Klicken / Bearbeiten:

Öffnen Sie das neu erstellte StandardMaterial3D und scrollen Sie nach unten zum Abschnitt "Albedo" und klicken Sie neben der Property "Texture", um eine Albedo-Textur hinzuzufügen. Hier werden wir die von uns erstellte Textur anwenden. Wählen Sie "Neu: ViewportTexture"

../../_images/planet_new_viewport_texture.png

Wählen Sie dann aus dem angezeigten Menü den Viewport aus, in den zuvor gerendert wurde.

../../_images/planet_pick_viewport_texture.png

Ihre Kugel sollte jetzt mit den Farben eingefärbt sein, die wir im Viewport gerendert haben.

../../_images/planet_seam.png

Haben Sie die hässliche Naht bemerkt, die sich an der Stelle bildet, an der die Textur um die Textur gewickelt wird? Das liegt daran, dass wir eine Farbe auf der Grundlage von UV-Koordinaten auswählen, und UV-Koordinaten lassen sich nicht um die Textur herumführen. Dies ist ein klassisches Problem bei der 2D-Kartenprojektion. Spieleentwickler haben oft eine 2-dimensionale Karte, die sie auf eine Kugel projizieren wollen, aber wenn sie sich um die Kugel wickelt, entstehen große Nähte. Es gibt eine elegante Lösung für dieses Problem, die wir im nächsten Abschnitt erläutern werden.

Erzeugen der Planetentextur

Wenn wir nun auf unser Viewport rendern, erscheint es wie von Zauberhand auf der Kugel. Aber es gibt eine hässliche Naht, die durch unsere Texturkoordinaten entsteht. Wie bekommen wir also einen Bereich von Koordinaten, der die Kugel auf schöne Weise umhüllt? Eine Lösung besteht darin, eine Funktion zu verwenden, die sich auf dem Bereich unserer Textur wiederholt. sin und cos sind zwei solcher Funktionen. Wenden wir sie auf die Textur an und sehen wir, was passiert.

COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
../../_images/planet_sincos.png

Gar nicht so schlecht. Wenn Sie sich umsehen, können Sie sehen, dass die Naht jetzt verschwunden ist, aber an ihrer Stelle haben wir eine Quetschung an den Polen. Das liegt an der Art und Weise, wie Godot in seinem StandardMaterial3D Texturen auf Kugeln abbildet. Es verwendet eine Projektionstechnik namens gleichwinklige Projektion, die eine sphärische Karte auf eine 2D-Ebene überträgt.

Bemerkung

Wenn Sie sich für ein paar zusätzliche Informationen über die Methode interessieren, werden wir von sphärischen Koordinaten in kartesische Koordinaten umrechnen. Sphärische Koordinaten bilden die Längen- und Breitengrade der Kugel ab, während kartesische Koordinaten im Grunde ein Vektor vom Mittelpunkt der Kugel zum aktuellen Punkt sind.

Für jedes Pixel wird seine 3D-Position auf der Kugel berechnet. Daraus ermitteln wir mit Hilfe von 3D-Rauschen einen Farbwert. Indem wir das Rauschen in 3D berechnen, lösen wir das Problem des Quetschens an den Polen. Um zu verstehen, warum das so ist, stellen Sie sich vor, dass das Rauschen über die Oberfläche der Kugel und nicht über die 2D-Ebene berechnet wird. Wenn man über die Oberfläche der Kugel rechnet, stößt man nie auf eine Kante und erzeugt somit auch keine Naht oder einen Quetschungspunkt am Pol. Der folgende Code wandelt die UVs in kartesische Koordinaten um.

float theta = UV.y * 3.14159;
float phi = UV.x * 3.14159 * 2.0;
vec3 unit = vec3(0.0, 0.0, 0.0);

unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
unit = normalize(unit);

Und wenn wir unit als Ausgabe-COLOR-Wert verwenden, erhalten wir:

../../_images/planet_normals.png

Da wir nun die 3D-Position der Kugeloberfläche berechnen können, können wir 3D-Rauschen verwenden, um den Planeten zu erstellen. Wir werden diese Rauschfunktion direkt aus einem Shadertoy verwenden:

vec3 hash(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));

    return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  vec3 u = f * f * (3.0 - 2.0 * f);

  return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
                     dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
                     dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
             mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
                     dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
                     dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
}

Bemerkung

Alle Anerkennung gebührt dem Autor Inigo Quilez. Es wird unter der MIT-Lizenz veröffentlicht.

Um nun Rauschen zu verwenden, fügen Sie der Funktion Fragment folgendes hinzu:

float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
../../_images/planet_noise.png

Bemerkung

Um die Textur hervorzuheben, setzen wir das Material auf unschattiert.

Sie können jetzt sehen, dass das Rauschen tatsächlich nahtlos um die Kugel herumgeht. Allerdings sieht das nicht aus wie der Planet, der Ihnen versprochen wurde. Gehen wir also zu etwas Bunterem über.

Einfärben des Planeten

Nun zum Einfärben des Planeten. Es gibt zwar viele Möglichkeiten, dies zu tun, aber für den Moment werden wir bei einem Farbverlauf zwischen Wasser und Land bleiben.

Um einen Gradienten in GLSL zu erzeugen, benutzen wir die Funktion mix. Die Funktion mix nimmt zwei Werte, zwischen denen interpoliert werden soll, und ein drittes Argument, um festzulegen, wie stark zwischen ihnen interpoliert werden soll; im Wesentlichen mischt sie die beiden Werte zusammen. In anderen APIs wird diese Funktion oft lerp genannt. Allerdings ist lerp typischerweise für das Mischen von zwei Float-Zahlen reserviert; mix kann beliebige Werte annehmen, seien es Float-Zahlen oder Vektortypen.

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);

Die erste Farbe ist blau für den Ozean. Die zweite Farbe ist eine Art rötliche Farbe (weil alle außerirdischen Planeten rotes Terrain brauchen). Und schließlich werden sie durch n * 0,5 + 0,5 zusammengemischt. n variiert gleichmäßig zwischen -1 und 1. Also übertragen wir es auf den Bereich 0-1, den mix erwartet. Jetzt kann man sehen, dass die Farben zwischen blau und rot wechseln.

../../_images/planet_noise_color.png

Das ist ein bisschen unschärfer, als wir wollen. Planeten haben normalerweise eine relativ klare Trennung zwischen Land und Meer. Um das zu erreichen, ändern wir den letzten Term in smoothstep(-0.1, 0.0, n). Und so wird die ganze Zeile zu:

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));

Was smoothstep macht, ist 0 zurückzugeben, wenn das dritte Argument kleiner als das erste ist und 1, wenn das dritte Argument größer als das zweite ist. Es gibt einen gleitenden Wert zwischen 0 und 1 zurück, wenn die dritte Zahl zwischen dem ersten und dem zweiten liegt. In dieser Zeile liefert smoothstep also 0, wenn n kleiner als -0.1 ist und 1, wenn n größer als 0 ist.

../../_images/planet_noise_smooth.png

Noch eine Sache, um das Ganze ein wenig planetarer zu machen. Das Land sollte nicht so klumpig sein; wir sollten die Kanten ein wenig rauer gestalten. Ein Trick, der oft in Shadern verwendet wird, um rau aussehendes Terrain mit Rauschen zu erzeugen, ist das Übereinanderschichten von Rauschebenen mit verschiedenen Frequenzen. Wir verwenden eine Ebene, um die allgemeine Klecksstruktur der Kontinente zu erzeugen. Eine weitere Ebene bricht die Kanten ein wenig auf, dann eine weitere und so weiter. Wir werden n mit vier Zeilen Shader-Code berechnen, anstatt nur mit einer. Aus n wird:

float n = noise(unit * 5.0) * 0.5;
n += noise(unit * 10.0) * 0.25;
n += noise(unit * 20.0) * 0.125;
n += noise(unit * 40.0) * 0.0625;

Nun sieht der Planet so aus:

../../_images/planet_noise_fbm.png

Und wenn das Shading wieder aktiviert ist, sieht es so aus:

../../_images/planet_noise_fbm_shaded.png

Erzeugen eines Ozeans

Eine letzte Sache, damit es mehr wie ein Planet aussieht. Der Ozean und das Land reflektieren das Licht unterschiedlich. Wir wollen also, dass der Ozean ein wenig mehr leuchtet als das Land. Wir können dies erreichen, indem wir einen vierten Wert in den Alpha-Kanal unserer Ausgabe COLOR eingeben und ihn als Rauheitskarte verwenden.

COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);

Diese Zeile liefert 0.3 für Wasser und 1.0 für Land. Das bedeutet, dass das Land ziemlich rau sein wird, während das Wasser ziemlich glatt sein wird.

Dann stellen Sie im Material unter dem Abschnitt "Metallic" sicher, daß Metallic auf 0 und Specular auf 1 gesetzt ist. Der Grund dafür ist, daß das Wasser das Licht sehr gut reflektiert, aber nicht metallisch ist. Diese Werte sind nicht physikalisch genau, aber für diese Demo sind sie gut genug.

Als nächstes setzen Sie im Abschnitt "Rauheit" den Wert Rauheit auf 1 und setzen die Rauheitstextur auf eine Viewport Texture, die auf unsere Planetentextur Viewport zeigt. Schließlich setzen wir den Texturkanal auf Alpha. Dies weist den Renderer an, den Alpha-Kanal unserer COLOR-Ausgabe als Rauheits-Wert zu verwenden.

../../_images/planet_ocean.png

Sie werden feststellen, dass sich nur sehr wenig ändert, außer dass der Planet den Himmel nicht mehr spiegelt. Dies geschieht, weil standardmäßig, wenn etwas mit einem Alpha-Wert gerendert wird, es als transparentes Objekt über den Hintergrund gezeichnet wird. Und da der Default-Hintergrund des Viewport undurchsichtig ist, hat der Alpha-Kanal der Viewport Texture den Wert 1, was dazu führt, dass die Planetentextur überall mit etwas blasseren Farben und einem Rauheits-Wert von 1 gezeichnet wird. Um dies zu korrigieren, gehen wir in den Viewport und aktivieren die "Transparent Bg"-Property. Da wir nun ein transparentes Objekt über ein anderes legen, müssen wir blend_premul_alpha aktivieren:

render_mode blend_premul_alpha;

Dadurch werden die Farben mit dem Alpha-Wert vormultipliziert und dann korrekt zusammengemischt. Wenn man eine transparente Farbe über eine andere mischt, selbst wenn der Hintergrund einen Alpha-Wert von 0 hat (wie in diesem Fall), kommt es normalerweise zu seltsamen Farbverläufen. Die Einstellung blend_premul_alpha behebt das.

Jetzt sollte der Planet so aussehen, als würde er das Licht auf dem Meer reflektieren, aber nicht auf dem Land. Wenn Sie es noch nicht getan haben, fügen Sie ein OmniLight3D zur Szene hinzu, damit Sie es bewegen und den Effekt der Reflexionen auf dem Ozean sehen können.

../../_images/planet_ocean_reflect.png

Und da haben Sie es. Ein prozeduraler Planet, der unter Verwendung eines Viewports generiert wurde.