最初のSpatial シェーダー: パート2

高いレベルから、Godotが行うことは、オプションで設定できる一連のパラメーターをユーザーに提供することです(AOSSS_StrengthRIM など)。これらのパラメーターは、さまざまな複雑な効果(アンビエントオクルージョン、サブサーフェススキャタリング、リムライティングなど)に対応しています。書き込まれていない場合、コードはコンパイルされる前に破棄されるため、シェーダーは追加機能のコストを負担しません。これにより、ユーザーは複雑なシェーダーを作成せずに、複雑なPBR補正シェーディングを簡単に作成できます。もちろん、Godotではこれらのパラメーターをすべて無視して、完全にカスタマイズされたシェーダーを作成することもできます。

これらのパラメーターの完全なリストについては、spatial shader リファレンスドキュメントを参照してください。

頂点関数とフラグメント関数の違いは、頂点関数は頂点ごとに実行され、VERTEX (位置)や NORMAL などのプロパティを設定しますが、フラグメントシェーダーはピクセルごとに実行され、最も重要なことは、MeshALBEDO 色を設定します。

最初のspatialフラグメント関数

このチュートリアルの前の部分で述べたように。 Godotのフラグメント関数の標準的な使用法は、さまざまなマテリアルプロパティを設定し、Godotに残りを処理させることです。さらに柔軟性を提供するために、Godotはレンダリングモードと呼ばれるものも提供します。レンダリングモードは、シェーダーの上部、shader_type のすぐ下に設定され、シェーダーのビルトイン要素にどのような機能を持たせるかを指定します。

たとえば、ライトをオブジェクトに影響させたくない場合は、レンダリングモードを unshaded に設定します:

render_mode unshaded;

複数のレンダリングモードをスタックすることもできます。たとえば、よりリアルなPBRシェーディングの代わりにトゥーンシェーディングを使用する場合は、拡散モードと鏡面反射モードをトゥーンに設定します:

render_mode diffuse_toon, specular_toon;

ビルトイン機能のこのモデルを使用すると、いくつかのパラメーターを変更するだけで複雑なカスタムシェーダーを作成できます。

レンダリングモードの完全なリストについては、Spatialシェーダーリファレンス を参照してください。

チュートリアルのこの部分では、前の部分からでこぼこの地形を取り、海に変える方法を説明します。

まず、水の色を設定しましょう。これを行うには、``ALBEDO``を設定します。

ALBEDO はオブジェクトの色を含む vec3 です。

青の素敵な色合いに設定してみましょう。

void fragment() {
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/albedo.png

水の青さのほとんどは空からの反射から来るため、非常に暗い青に設定しました。

Godotが使用するPBRモデルは、METALLICROUGHNESS の2つの主要なパラメーターに依存しています。

ROUGHNESS``は、材料の表面の滑らかさ/粗さを指定します。\ ``ROUGHNESS が低いと、素材は光沢のあるプラスチックのように見えますが、粗さが大きいと、素材の色はより均一になります。

METALLIC は、オブジェクトが金属にどれだけ似ているかを指定します。0 または 1 の近くに設定することをお勧めします。METALLIC は、反射と ALBEDO カラーのバランスを変えるものと考えてください。高い METALLICALBEDO をほとんど無視し、空を映す鏡のように見えます。低い METALLIC では、空の色と `` ALBEDO`` の色がより均等に表現されます。

ROUGHNESS は左から右に 0 から 1 に増加し、METALLIC は上から下に 0 から 1 に増加します。

../../../_images/PBR.png

注釈

適切なPBRシェーディングを行うには、METALLIC0 または 1 に近づける必要があります。それらの間でのみ設定して、マテリアル間でブレンドします。

水は金属ではないため、METALLIC``プロパティを ``0.0 に設定します。また、水は反射率が高いため、ROUGHNESS プロパティも非常に低く設定します。

void fragment() {
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/plastic.png

表面が滑らかなプラスチックになりました。エミュレートしたい水の特定の特性について考える時が来ました。これを奇妙なプラスチックの表面から素敵な定型化された水に変える主なものが2つあります。 1つは鏡面反射です。鏡面反射とは、太陽が目に直接反射する場所から見える明るいスポットです。 2つ目はフレネル反射です。フレネル反射率は、オブジェクトが浅い角度でより反射する特性です。それはあなたがあなたの真下の水を見ることができる理由ですが、それより遠くは空を反映しています。

鏡面反射を増やすために、2つのことを行います。最初に、トゥーンレンダリングモードの方が鏡面反射ハイライトが大きくなるため、スペキュラーのレンダリングモードをトゥーンに変更します。

render_mode specular_toon;
../../../_images/specular-toon.png

次に、リム照明を追加します。リム照明は、かすかな角度で光の効果を高めます。通常、オブジェクトの端にある布地を光が通過する方法をエミュレートするために使用されますが、ここでは素敵な水のような効果を達成するために使用します。

void fragment() {
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/rim.png

In order to add fresnal reflectance, we will compute a fresnel term in our fragment shader. Here, we aren't going to use a real fresnel term for performance reasons. Instead, we'll approximate it using the dot product of the NORMAL and VIEW vectors. The NORMAL vector points away from the mesh's surface, while the VIEW vector is the direction between your eye and that point on the surface. The dot product between them is a handy way to tell when you are looking at the surface head-on or at a glancing angle.

float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));

And mix it into both ROUGHNESS and ALBEDO. This is the benefit of ShaderMaterials over SpatialMaterials. With SpatialMaterials, we could set these properties with a texture, or to a flat number. But with shaders we can set them based on any mathematical function that we can dream up.

void fragment() {
  float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01 * (1.0 - fresnel);
  ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel);
}
../../../_images/fresnel.png

そして今、わずか5行のコードで、複雑な外観の水を使用できます。照明ができたので、この水は明るすぎます。暗くしましょう。これは、ALBEDO に渡す vec3 の値を減らすことで簡単に行えます。それらを ``vec3(0.01, 0.03, 0.05)``に設定しましょう。

../../../_images/dark-water.png

TIME でアニメートする

Going back to the vertex function, we can animate the waves using the built-in variable TIME.

TIME は、頂点関数とフラグメント関数からアクセス可能なビルトイン変数です。

最後のチュートリアルでは、高さマップから読み取ることで高さを計算しました。このチュートリアルでは、同じことを行います。ハイトマップコードを height() という関数に入れます。

float height(vec2 position) {
  return texture(noise, position / 10.0).x; // Scaling factor is based on mesh size (this PlaneMesh is 10×10).
}

In order to use TIME in the height() function, we need to pass it in.

float height(vec2 position, float time) {
}

そして、頂点関数内でそれを正しく渡すようにしてください。

void vertex() {
  vec2 pos = VERTEX.xz;
  float k = height(pos, TIME);
  VERTEX.y = k;
}

法線を計算するために法線マップを使用する代わりに。 vertex() 関数でそれらを手動で計算します。そのためには、次のコード行を使用します。

NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME)));

次のセクションでは数学を使用して複雑に見える波を作成するため、手動で NORMAL を計算する必要があります。

ここで、 TIME のコサインで position をオフセットすることにより、height() 関数をもう少し複雑にします。

float height(vec2 position, float time) {
  vec2 offset = 0.01 * cos(position + time);
  return texture(noise, (position / 10.0) - offset).x;
}

これにより、波はゆっくりと移動しますが、自然な方法ではありません。次のセクションでは、さらにいくつかの数学関数を追加して、シェーダーを使用してより複雑な効果(この場合は現実的な波)を作成する方法について詳しく説明します。

高度な効果: 波

シェーダーが非常に強力なのは、数学を使用して複雑な効果を実現できることです。これを説明するために、 height() 関数を修正し、wave() と呼ばれる新しい関数を導入することで、波を次のレベルに引き上げます。

wave() にはパラメーター position があり、これは height() と同じです。

波の見かけを模倣するために、height()wave() を複数回呼び出します。

float wave(vec2 position){
  position += texture(noise, position / 10.0).x * 2.0 - 1.0;
  vec2 wv = 1.0 - abs(sin(position));
  return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);
}

最初はこれは複雑に見えます。それでは、行ごとに見ていきましょう。

position += texture(noise, position / 10.0).x * 2.0 - 1.0;

Offset the position by the noise texture. This will make the waves curve, so they won't be straight lines completely aligned with the grid.

vec2 wv = 1.0 - abs(sin(position));

sin()position を使用して波のような関数を定義します。通常、sin() 波は非常に丸いです。絶対値にするために abs() を使用して、鋭い隆起を与え、0-1の範囲に制限します。そして、1.0 からそれを差し引いて、ピークを一番上に置きます。

return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);

x方向の波にy方向の波を掛け、それを累乗してピークをシャープにします。次に、1.0 からそれを差し引くと、尾根がピークになり、それを累乗して尾根をシャープにします。

height() 関数の内容を wave() に置き換えることができます。

float height(vec2 position, float time) {
  float h = wave(position);
}

これを使用すると以下が得られます:

../../../_images/wave1.png

正弦波の形状があまりにも明白です。それでは、波を少し広げてみましょう。これを行うには、position をスケーリングします。

float height(vec2 position, float time) {
  float h = wave(position*0.4);
}

今ではずっと良く見えます。

../../../_images/wave2.png

さまざまな周波数と振幅で複数の波を重ねると、さらに改善できます。これが意味することは、それぞれの位置をスケーリングして、波を細くしたり広くしたりすることです(周波数)。そして、波の出力を乗算して、それらをより短くまたはより高く(振幅)します。

見栄えの良い波を実現するために4つの波を重ねる方法の例を次に示します。

float height(vec2 position, float time) {
  float d = wave((position + time) * 0.4) * 0.3;
  d += wave((position - time) * 0.3) * 0.3;
  d += wave((position + time) * 0.5) * 0.2;
  d += wave((position - time) * 0.6) * 0.2;
  return d;
}

2に時間を追加し、他の2つからそれを減算することに注意してください。これにより、波がさまざまな方向に移動し、複雑な効果が生まれます。また、振幅(結果に乗算される数)がすべて 1.0 になることにも注意してください。これにより、波は0〜1の範囲に維持されます。

With this code you should end up with more complex looking waves and all you had to do was add a bit of math!

../../../_images/wave3.png

Spatialシェーダーの詳細については、Shading Language のドキュメントと Spatial Shaders のドキュメントを参照してください。Shading section および 3D セクションのより高度なチュートリアルも参照してください。