SubViewportのテクスチャ化
はじめに
このチュートリアルでは、3Dオブジェクトに適用できるテクスチャとして SubViewport を使用する方法を紹介します。 そのために、以下のようなプロシージャル惑星を作成するプロセスを順を追って説明します:
注釈
このチュートリアルでは、この惑星のような動的な大気をコーディングする方法については説明しません。
このチュートリアルでは、Camera3D、Light source、MeshInstance3D などの基本的なシーンの設定方法に慣れていることを前提としています。Primitive Mesh を使用し、StandardMaterial3D をメッシュに適用します。SubViewport を使用して、メッシュに適用できるテクスチャを動的に作成することに焦点が置かれます。
このチュートリアルでは、次のトピックについて説明します:
SubViewport をレンダーテクスチャとして使用する方法
正距円筒図法でテクスチャを球にマッピングする
プロシージャル惑星のフラグメントシェーダーテクニック
ビューポートテクスチャ からラフネスマップを設定する
シーンの設定
新しいシーンを作成し、以下に示すようにノードを正確に追加します。
MeshInstance3Dに移動し、メッシュにSphereMeshを指定します
SubViewportを設定する
次に、SubViewport のサイズを (1024, 512) に設定します。SubViewport は、幅が高さの2倍である限り、実際には任意のサイズにできます。正距円筒図法を使用するため、画像は球体に正確にマッピングされるように、幅は高さの2倍にする必要がありますが、これについては後で詳しく説明します。
次に3Dを無効にします。 ColorRect を使用してサーフェスをレンダリングするため、3Dは必要ありません。
アンカー(anchors)の「右」と「下」を 1 に設定し、すべてのマージンが 0 に設定されていることを確認します。これにより、ColorRect が SubViewport 全体を使用するようになります。
次に、Shader Material を ColorRect に追加します(ColorRect → CanvasItem → Material → Material → New ShaderMaterial)。
注釈
このチュートリアルでは、シェーディングに関する基本的な知識があることが推奨されます。ただし、シェーダーを初めて使用する場合でも、すべてのコードが提供されるため、順を追っていけば問題が発生することはありません。
Click the dropdown menu button for the shader material and click / Edit. From here go to Shader > New Shader.
give it a name and click "Create". click the shader in the inspector to open the shader editor. Delete the default code
and add the following:
shader_type canvas_item;
void fragment() {
COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}
シェーダーコードを保存すると、上記のコードが以下のグラデーションをレンダリングしていることがインスペクターで確認できます。
これでレンダリングする SubViewport の基本ができ、球体に適用できる一意の画像ができました。
テクスチャを適用する
Now go into the MeshInstance3D and add a StandardMaterial3D to it. No need for a special Shader Material (although that would be a good idea for more advanced effects, like the atmosphere in the example above).
MeshInstance3D > GeometryInstance > Geometry > Material Override > New StandardMaterial3D
Then click the dropdown for the StandardMaterial3D and click "Edit"
Go to the "Resource" section and check the Local to scene box. Then, go to the "Albedo" section
and click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made.
Choose "New ViewportTexture"
Click on the ViewportTexture you just created in the inspector, then click "Assign". Then, from the menu that pops up, select the Viewport that we rendered to earlier.
これで、ビューポートにレンダリングした色で球体が色付けされるはずです。
テクスチャが回り込む場所に形成されるい縫い目に注目してください。これは、UV座標に基づいて色を選択しており、UV座標がテクスチャをラップしていないためです。これは、2Dマップ投影の古典的な問題です。ゲーム開発者は多くの場合、球体に投影したい2次元のマップを持っていますが、それをラップすると大きな継ぎ目ができます。次のセクションで説明するこの問題に対するエレガントな回避策があります。
惑星のテクスチャを作る
So now, when we render to our SubViewport, it appears magically on the sphere. But there is an ugly
seam created by our texture coordinates. So how do we get a range of coordinates that wrap around
the sphere in a nice way? One solution is to use a function that repeats on the domain of our texture.
sin and cos are two such functions. Let's apply them to the texture and see what happens. Replace the
existing color code in the shader with the following:
COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
悪くありません。周りを見ると、縫い目が消えていることがわかりますが、その代わりに極をつまんでいます。このピンチは、Godotが StandardMaterial3D でテクスチャを球体にマップする方法によるものです。これは、正距円筒図法と呼ばれる投影手法を使用します。これは、球面地図を2D平面に変換します。
注釈
もしも、テクニック関して興味がある方へのちょっとした追加情報ですが、座標は球面座標からデカルト座標(直交座標)に変換して処理をします。 球体座標は球体の経度と緯度をマッピングしますが、変換後のデカルト座標は等距離で全方位に向いた球体の中心から点までのベクトルです。
各ピクセルについて、球上の3D位置を計算します。それから、3Dノイズを使用して色の値を決定します。 3Dでノイズを計算することにより、極での挟み込みの問題を解決します。理由を理解するために、2D平面ではなく球の表面全体で計算されるノイズを想像してください。球の表面を横切って計算すると、エッジにヒットすることはないため、極に継ぎ目やピンチポイントを作成することはありません。次のコードは、UV をデカルト座標に変換します。
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);
そして、出力する COLOR 値として unit を使用すると、次のようになります:
球の表面の3D位置を計算できるようになったので、3Dノイズを使用して惑星を作成できます。Shadertoy のノイズ関数をそのまま使用します。
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 );
}
注釈
すべての功績は著者であるInigo Quilezに帰属します。MIT ライセンスの下で公開されています。
noise を使用するには、以下を fragment 関数に追加します:
float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
注釈
テクスチャを強調表示するために、マテリアルをシェーディングなしに設定します。
ノイズが実際に球の周りにシームレスに巻き付いていることがわかります。これはあなたに約束された惑星のようには見えませんが。それでは、もっとカラフルなものに移りましょう。
惑星を彩る
今から惑星の色を作ります。これを行うには多くの方法がありますが、今のところ、水と土地の間の勾配にこだわります。
GLSLでグラデーションを作成するには、mix 関数を使用します。mix は、2つの値の間を補間し、3番目の引数はそれらの間を補間する量を選択します。本質的には、2つの値を混合します。他のAPIでは、この関数はしばしば lerp と呼ばれます。ただし、lerp は通常、2つの浮動小数点を混合するために予約されています。 ``mix``は、浮動小数点型であろうとベクトル型であろうと、任意の値を取ることができます。
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);
海の最初の色は青です。 2番目の色は一種の赤みがかった色です(すべてのエイリアンの惑星には赤い地形が必要なため)。そして最後に、それらは n * 0.5 + 0.5 で混合されます。n は -1 と 1 の間で滑らかに変化します。したがって、mix が期待する 0-1 の範囲にマッピングします。これで、色が青と赤の間で変化することがわかります。
それは私たちが望むよりも少しぼやけています。惑星は通常、陸と海の間の比較的明確な分離を持っています。そのためには、最後の用語を smoothstep(-0.1,0.0,n) に変更します。したがって、行全体は次のようになります:
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));
smoothstep が行うことは、3番目の引数が最初の引数よりも小さい場合は 0 を返し、3番目の引数が2番目よりも大きい場合は``1`` を返し、3番目の数値が1番目と2番目の間にある場合は 0 と 1 の間をスムーズにブレンドします 。したがって、この行では、n が -0.1 未満の場合は常に smoothstep が 0 を返し、n が 0 を超える場合は 1 を返します。
これをもう少し惑星らしくするもう一つの作業を行います。このままでは地形がぼんやりとしているはずです。なので、エッジを少し粗くしましょう。シェーダーでノイズの多い荒い地形を作るためによく使用されるトリックは、さまざまな周波数とレベルのノイズを相互に重ねることです。 1つのレイヤーを使用して、大陸全体の大雑把な構造を作成します。次に、別のレイヤーが少しエッジを分割し、次に別のレイヤーが続きます。ここでは、1行ではなく4行のシェーダーコードで n を計算します。n は次のようになります:
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;
新たに、惑星は次のようになります:
海を作る
これを惑星のように見せるための最後の1つです。海と陸では光の反射が異なります。そのため、海は陸地よりも少しだけ輝かせたいです。これを行うには、4番目の値を出力 COLOR``の ``alpha チャンネルに渡し、それを粗さマップとして使用します。
COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);
この行は、水に対して 0.3 を返し、土地に対して 1.0 を返します。これは、水が非常に滑らかになる一方で、土地が非常に荒くなることを意味します。
そして、マテリアルの Metallic セクションで、Metallic が 0 に設定され、Specular が 1 に設定されていることを確認します。これは、水が光を非常によく反射するが、金属ではないためです。これらの値は物理的に正確ではありませんが、このデモには十分です。
Next, under the "Roughness" section set the roughness texture to a
Viewport Texture pointing to our planet texture SubViewport.
Finally, set the Texture Channel to Alpha. This instructs the renderer to use the alpha
channel of our output COLOR as the Roughness value.
You'll notice that very little changes except that the planet is no longer reflecting the sky.
This is happening because, by default, when something is rendered with an
alpha value, it gets drawn as a transparent object over the background. And since the default background
of the SubViewport is opaque, the alpha channel of the
Viewport Texture is 1, resulting in the planet texture being
drawn with slightly fainter colors and a Roughness value of 1 everywhere. To correct this, we
go into the SubViewport and enable the "Transparent Bg" property. Since we are now
rendering one transparent object on top of another, we want to enable blend_premul_alpha:
render_mode blend_premul_alpha;
これにより、色に alpha 値が事前に乗算され、それらが正しくブレンドされます。通常、ある透明色を別の透明色の上にブレンドすると、背景の alpha が 0 であっても(この場合のように)、奇妙な色にじみの問題が発生します。これは blend_premul_alpha を設定すると修正されます。
Now the planet should look like it is reflecting light on the ocean but not the land. move around the OmniLight3D in the scene so you can see the effect of the reflections on the ocean.
And there you have it. A procedural planet generated using a SubViewport.