ビューポートをテクスチャとして使用する

はじめに

このチュートリアルでは、3Dオブジェクトに適用できるテクスチャとして Viewport を使用する方法を紹介します。 そのためには、以下のような手続き型惑星を作成するプロセスを順を追って説明します:

../../_images/planet_example.png

注釈

このチュートリアルでは、この惑星のような動的な大気をコーディングする方法については説明しません。

このチュートリアルでは、Cameralight sourceMesh Instance などの基本的なシーンの設定方法に慣れていることを前提としています。Primitive Mesh を使用し、Spatial Material をメッシュに適用します。Viewport を使用して、メッシュに適用できるテクスチャを動的に作成することに焦点が置かれます。

このチュートリアルでは、次のトピックについて説明します:

  • Viewport をレンダーテクスチャとして使用する方法
  • 正距円筒図法でテクスチャを球にマッピングする
  • 手続き型惑星のフラグメントシェーダーテクニック
  • ビューポートテクスチャ から粗さマップを設定する

ビューポートを設定する

最初に、シーンに Viewport を追加します。

次に、Viewport のサイズを (1024, 512) に設定します。Viewport は、幅が高さの2倍である限り、実際には任意のサイズにできます。正距円筒図法を使用するため、画像は球体に正確にマッピングされるように、幅は高さの2倍にする必要がありますが、これについては後で詳しく説明します。

../../_images/planet_new_viewport.png

次に、HDRを無効にし、3Dを無効にします。惑星の表面はそれほど明るくないため、HDRは必要ありません。そのため、01 の間の値で問題ありません。そして ColorRect を使用してサーフェスをレンダリングするため、3Dも必要ありません。

ビューポートを選択し、ColorRect を子として追加します。

アンカー(anchors)の「右」と「下」を 1 に設定し、すべてのマージンが 0 に設定されていることを確認します。これにより、ColorRectViewport 全体を使用するようになります。

../../_images/planet_new_colorrect.png

次に、Shader MaterialColorRect に追加します(ColorRect → CanvasItem → Material → Material → New ShaderMaterial)。

注釈

このチュートリアルでは、シェーディングに関する基本的な知識があることが推奨されます。ただし、シェーダーを初めて使用する場合でも、すべてのコードが提供されるため、順を追っていけば問題が発生することはありません。

ColorRect → CanvasItem → Material → Material →クリック/編集 → ShaderMaterial → Shader → New Shader → クリック/編集:

shader_type canvas_item;

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

上記のコードは、以下のようなグラデーションをレンダリングします。

../../_images/planet_gradient.png

これで、レンダリングする Viewport の基本ができ、球体に適用できる一意の画像ができました。

テクスチャを適用する

MeshInstance → GeometryInstance → Geometry → Material Override → New SpatialMaterial:

ここで Mesh Instance に進み、それに Spatial Material を追加します。特別な Shader Material は必要ありません(ただし、上記の例の雰囲気のような、より高度な効果を得るには良い考えです)。

MeshInstance → GeometryInstance → Geometry → Material Override → クリック / 編集:

新しく作成された Spatial Material を開き、"Albedo"セクションまでスクロールダウンし、"Texture"プロパティの横をクリックして、Albedo Textureを追加します。ここで、作成したテクスチャを適用します。選択 - "New ViewportTexture"

../../_images/planet_new_viewport_texture.png

次に、ポップアップするメニューから、以前にレンダリングしたビューポートを選択します。

../../_images/planet_pick_viewport_texture.png

これで、ビューポートにレンダリングした色で球体が色付けされるはずです。

../../_images/planet_seam.png

テクスチャが回り込む場所に形成されるい縫い目に注目してください。これは、UV座標に基づいて色を選択しており、UV座標がテクスチャをラップしていないためです。これは、2Dマップ投影の古典的な問題です。ゲーム開発者は多くの場合、球体に投影したい2次元のマップを持っていますが、それをラップすると大きな継ぎ目ができます。次のセクションで説明するこの問題に対するエレガントな回避策があります。

惑星のテクスチャを作る

したがって、Viewport にレンダリングすると、魔法のように球体に表示されます。しかし、テクスチャ座標によって作成されたい縫い目があります。それでは、どのようにすれば、球体を適切に包み込む座標範囲を取得できますか? 1つの解決策は、テクスチャのドメインで繰り返される関数を使用することです。sincos はそのような関数です。それらをテクスチャに適用して、何が起こるか見てみましょう。

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

悪くありません。周りを見ると、縫い目が消えていることがわかりますが、その代わりに極をつまんでいます。このピンチは、Godotが Spatial Material でテクスチャを球体にマップする方法によるものです。これは、正距円筒図法と呼ばれる投影手法を使用します。これは、球面地図を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 を使用すると、次のようになります:

../../_images/planet_normals.png

球の表面の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);
../../_images/planet_noise.png

注釈

テクスチャを強調表示するために、マテリアルをシェーディングなしに設定します。

ノイズが実際に球の周りにシームレスに巻き付いていることがわかります。これはあなたに約束された惑星のようには見えませんが。それでは、もっとカラフルなものに移りましょう。

惑星を彩る

今から惑星の色を作ります。これを行うには多くの方法がありますが、今のところ、水と土地の間の勾配にこだわります。

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-11 の間で滑らかに変化します。したがって、mix が期待する 0-1 の範囲にマッピングします。これで、色が青と赤の間で変化することがわかります。

../../_images/planet_noise_color.png

それは私たちが望むよりも少しぼやけています。惑星は通常、陸と海の間の比較的明確な分離を持っています。そのためには、最後の用語を 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番目の間にある場合は 01 の間をスムーズにブレンドします 。したがって、この行では、n-0.1 未満の場合は常に smoothstep0 を返し、n0 を超える場合は 1 を返します。

../../_images/planet_noise_smooth.png

これをもう少し惑星らしくするもう一つの作業を行います。このままでは地形がぼんやりとしているはずです。なので、エッジを少し粗くしましょう。シェーダーでノイズの多い荒い地形を作るためによく使用されるトリックは、さまざまな周波数とレベルのノイズを相互に重ねることです。 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;

新たに、惑星は次のようになります:

../../_images/planet_noise_fbm.png

シェーディングをオンに戻すと、次のようになります:

../../_images/planet_noise_fbm_shaded.png

海を作る

これを惑星のように見せるための最後の1つです。海と陸では光の反射が異なります。そのため、海は陸地よりも少しだけ輝かせたいです。これを行うには、4番目の値を出力 COLOR``の ``alpha チャンネルに渡し、それを粗さマップとして使用します。

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

この行は、水に対して 0.3 を返し、土地に対して 1.0 を返します。これは、水が非常に滑らかになる一方で、土地が非常に荒くなることを意味します。

そして、マテリアルの Metallic セクションで、Metallic0 に設定され、Specular1 に設定されていることを確認します。これは、水が光を非常によく反射するが、金属ではないためです。これらの値は物理的に正確ではありませんが、このデモには十分です。

次に、"Roughness" セクションで、Roughness1 に設定し、粗さテクスチャを、惑星テクスチャ Viewport を指し示す Viewport Texture に設定します。 最後に、Texture ChannelAlpha に設定します。これは、出力 COLORalpha チャンネルを Roughness 値として使用するようにレンダラーに指示します。

../../_images/planet_ocean.png

惑星が空を反射しなくなったことを除いて、ほとんど変化がないことに気付くでしょう。これは、デフォルトでは、何かがアルファ値でレンダリングされると、背景上の透明なオブジェクトとして描画されるために発生します。そして、Viewport のデフォルトの背景は不透明であるため、Viewport Texturealpha チャンネルは 1 であり、惑星テクスチャはわずかに薄い色で描画され、Roughness の値はどこでも 1 になります。これを修正するために、Viewport に入り、 "Transparent Bg" をonに設定します。ある透明なオブジェクトを別の透明なオブジェクトの上にレンダリングしているので、blend_premul_alpha を有効にします:

render_mode blend_premul_alpha;

これにより、色に alpha 値が事前に乗算され、それらが正しくブレンドされます。通常、ある透明色を別の透明色の上にブレンドすると、背景の alpha0 であっても(この場合のように)、奇妙な色にじみの問題が発生します。これは blend_premul_alpha を設定すると修正されます。

これで、惑星は、海ではなく陸で光を反射しているように見えるはずです。まだ行っていない場合は、シーンに OmniLight を追加し、それを移動させて、海の反射の効果を確認できるようにします。

../../_images/planet_ocean_reflect.png

そして、あなたは手に入れました。:ref:`Viewport <class_Viewport>`を使用して生成された手続き型惑星を。