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

はじめに

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

../../_images/planet_example.png

注釈

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

This tutorial assumes you are familiar with how to set up a basic scene including: a Camera, a light source, a Mesh Instance with a Primitive Mesh, and applying a SpatialMaterial to the mesh. The focus will be on using the Viewport to dynamically create textures that can be applied to the mesh.

In this tutorial, we'll cover the following topics:

  • 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:

Now we go into the Mesh Instance and add a SpatialMaterial 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).

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

Open the newly created SpatialMaterial and scroll down 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"

../../_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

Not too bad. If you look around, you can see that the seam has now disappeared, but in its place, we have pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its SpatialMaterial. It uses a projection technique called equirectangular projection, which translates a spherical map onto a 2D plane.

注釈

もしも、テクニック関して興味がある方へのちょっとした追加情報ですが、座標は球面座標からデカルト座標(直交座標)に変換して処理をします。 球体座標は球体の経度と緯度をマッピングしますが、変換後のデカルト座標は等距離で全方位に向いた球体の中心から点までのベクトルです。

各ピクセルについて、球上の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

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

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

../../_images/planet_ocean_reflect.png

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