Babylon.jsを使用したシェーダーの構築
公開: 2022-03-10GPUの生のパワーを解き放ちたい場合、シェーダーは重要な概念です。 Babylon.jsのおかげで、それらがどのように機能するかを理解し、簡単な方法でそれらの内部の力を試すことさえできます。
それはどのように機能しますか?
実験する前に、内部でどのように機能するかを確認する必要があります。
ハードウェアアクセラレーションによる3Dを扱う場合は、メインCPUとGPUの2つのCPUを扱う必要があります。 GPUは一種の非常に特殊なCPUです。
SmashingMagの詳細:
- Babylon.jsを使用したクロスプラットフォームWebGLゲームの構築
- WebゲームでのゲームパッドAPIの使用
- ポリゴンモデリングとThree.jsの紹介
- レスポンシブな8ビットドラムマシンを作成する方法
GPUは、CPUを使用してセットアップしたステートマシンです。 たとえば、CPUは三角形の代わりに線をレンダリングするようにGPUを構成します。 透明度がオンかどうかを定義します。 等々。
すべての状態が設定されると、CPUはレンダリングするもの(ジオメトリ)を定義できます。
ジオメトリは次のもので構成されます。
- 頂点と呼ばれ、頂点バッファと呼ばれる配列に格納されているポイントのリスト。
- インデックスバッファという名前の配列に格納されている面(または三角形)を定義するインデックスのリスト。
CPUの最後のステップは、ジオメトリをレンダリングする方法を定義することです。 このタスクでは、CPUがGPUにシェーダーを定義します。 シェーダーは、GPUがレンダリングする必要のある頂点とピクセルごとに実行するコードの一部です。 (頂点(または頂点がいくつかある場合は頂点)は、3Dの「ポイント」です)。
シェーダーには、頂点シェーダーとピクセル(またはフラグメント)シェーダーの2種類があります。
グラフィックスパイプライン
シェーダーを掘り下げる前に、一歩下がってみましょう。 ピクセルをレンダリングするために、GPUはCPUによって定義されたジオメトリを取得し、次のことを行います。
- インデックスバッファを使用して、三角形を定義するために3つの頂点が収集されます。
- インデックスバッファには、頂点インデックスのリストが含まれています。 これは、インデックスバッファの各エントリが、頂点バッファの頂点の番号であることを意味します。
- これは、頂点の重複を避けるのに非常に役立ちます。
たとえば、次のインデックスバッファは、2つの面のリストです:[1 2 3 1 34]。 最初の面には頂点1、頂点2、頂点3が含まれます。2番目の面には頂点1、頂点3、頂点4が含まれます。したがって、このジオメトリには4つの頂点があります。
頂点シェーダーは、三角形の各頂点に適用されます。 頂点シェーダーの主な目的は、各頂点(3D頂点の2D画面への投影)のピクセルを生成することです。
これらの3つのピクセル(画面上の2D三角形を定義する)を使用して、GPUはピクセルにアタッチされたすべての値(少なくともそれらの位置)を補間し、ピクセルシェーダーは2D三角形に含まれるすべてのピクセルに適用されます。すべてのピクセルの色を生成します。
このプロセスは、インデックスバッファによって定義されたすべての面に対して実行されます。
明らかに、その並列性により、GPUはこのステップを多数の面に対して同時に処理し、非常に優れたパフォーマンスを実現できます。
GLSL
三角形をレンダリングするには、GPUに頂点シェーダーとピクセルシェーダーの2つのシェーダーが必要であることを確認しました。 これらのシェーダーは、Graphics Library Shader Language(GLSL)という名前の言語で記述されています。 Cのように見えます。
一般的な頂点シェーダーのサンプルを次に示します。
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
頂点シェーダー構造
頂点シェーダーには次のものが含まれます。
- 属性。 属性は頂点の一部を定義します。 デフォルトでは、頂点には少なくとも位置(
vector3:x, y, z
)が含まれている必要があります。 ただし、開発者は、さらに情報を追加することを決定できます。 たとえば、前者のシェーダーには、uv
という名前のvector2
があります(つまり、2Dテクスチャを3Dオブジェクトに適用できるテクスチャ座標)。 - ユニフォーム。 ユニフォームは、シェーダーによって使用され、CPUによって定義される変数です。 ここにある唯一のユニフォームは、頂点(x、y、z)の位置を画面(x、y)に投影するために使用される行列です。
- 変化する。 変化する変数は、頂点シェーダーによって作成され、ピクセルシェーダーに送信される値です。 ここで、頂点シェーダーは
vUV
(uv
の単純なコピー)値をピクセルシェーダーに送信します。 これは、ピクセルが位置とテクスチャの座標でここに定義されていることを意味します。 これらの値はGPUによって補間され、ピクセルシェーダーによって使用されます。 - メイン。
main
という名前の関数は、各頂点に対してGPUによって実行されるコードであり、少なくともgl_position
(画面上の現在の頂点の位置)の値を生成する必要があります。
サンプルでは、頂点シェーダーが非常に単純であることがわかります。 gl_positionという名前のシステム変数( gl_
で始まる)を生成して、関連付けられたピクセルの位置を定義し、 gl_position
と呼ばれる可変変数を設定しvUV
。
行列の背後にあるブードゥー
シェーダーについては、 worldViewProjection
という名前の行列があり、この行列を使用して頂点位置をgl_position
変数に投影します。 それは素晴らしいことですが、このマトリックスの値をどのように取得するのでしょうか。 ユニフォームなので、CPU側で定義する必要があります(JavaScriptを使用)。
これは、3Dを行う上での複雑な部分の1つです。 複雑な数学を理解する必要があります(または、後で説明するBabylon.jsなどの3Dエンジンを使用する必要があります)。
worldViewProjection
マトリックスは、次の3つの異なるマトリックスの組み合わせです。
結果の行列を使用すると、現在のオブジェクトの位置、スケール、回転に関連するすべての視点とすべてを考慮しながら、3D頂点を2Dピクセルに変換できます。
これは、3D開発者としてのあなたの責任です。このマトリックスを作成し、最新の状態に保つことです。
シェーダーに戻る
頂点シェーダーがすべての頂点で実行されると(その後3回)、正しいgl_position
とvUV
値を持つ3つのピクセルが作成されます。 GPUは、これらのピクセルで生成された三角形に含まれるすべてのピクセルでこれらの値を補間します。
次に、ピクセルごとに、ピクセルシェーダーを実行します。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
ピクセル(またはフラグメント)シェーダー構造
ピクセルシェーダーの構造は、頂点シェーダーの構造に似ています。
- 変化する。 変化する変数は、頂点シェーダーによって作成され、ピクセルシェーダーに送信される値です。 ここで、ピクセルシェーダーは頂点シェーダーから
vUV
値を受け取ります。 - ユニフォーム。 ユニフォームは、シェーダーによって使用され、CPUによって定義される変数です。 ここにある唯一のユニフォームは、テクスチャの色を読み取るために使用されるツールであるサンプラーです。
- メイン。
main
という名前の関数は、各ピクセルに対してGPUによって実行されるコードであり、少なくともgl_FragColor
の値(つまり、現在のピクセルの色)を生成する必要があります。
このピクセルシェーダーは非常に単純です。頂点シェーダーからのテクスチャ座標を使用してテクスチャから色を読み取ります(次に、頂点から色を取得します)。
問題は、シェーダーが開発されたとき、多くのWebGLコードを処理する必要があるため、そこにいるのは半分だけであるということです。 実際、WebGLは非常に強力ですが、非常に低レベルであり、バッファの作成から頂点構造の定義まで、すべて自分で行う必要があります。 また、すべての計算を実行し、すべての状態を設定し、テクスチャの読み込みを処理する必要があります。
あまりにもハード? BABYLON.ShaderMaterial To the Rescue
私はあなたが何を考えているか知っています:「シェーダーは本当にクールですが、WebGLの内部配管や数学さえも気にしたくありません。」
そして、あなたは正しいです! これは完全に正当な質問であり、それがまさに私がBabylon.jsを作成した理由です!
Babylon.jsを使用するには、最初に簡単なWebページが必要です。
<!DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas></canvas> </body> </html>
シェーダーが<script>
タグで定義されていることに気付くでしょう。 Babylon.jsを使用すると、それらを個別のファイル( .fx
ファイル)で定義することもできます。
- Babylon.jsソース
- GitHubリポジトリ
最後に、主なJavaScriptコードは次のとおりです。
"use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } };
シェーダーのコンパイル、リンク、および処理の負担を取り除くために、 BABYLON.ShaderMaterial
を使用していることがわかります。
BABYLON.ShaderMaterial
を作成するときは、シェーダーを格納するために使用されるDOM要素、またはシェーダーが存在するファイルのベース名を指定する必要があります。 ファイルを使用する場合は、シェーダーごとにファイルを作成し、 basename.vertex.fx
およびbasename.fragment.fx
のパターンを使用する必要があります。 次に、次のようなマテリアルを作成する必要があります。
var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });
使用する属性とユニフォームの名前も指定する必要があります。
次に、 setTexture
、 setFloat
、 setFloats
、 setColor3
、 setColor4
、 setVector2
、 setVector3
、 setVector4
、 setMatrix
関数を使用して、ユニフォームとサンプラーの値を直接設定できます。
とても簡単ですよね?
また、Babylon.jsとBABYLON.ShaderMaterial
を使用した以前のworldViewProjection
マトリックスを覚えていますか。 あなたはそれについて心配する必要はありません! ユニフォームのリストで宣言するため、 BABYLON.ShaderMaterial
が自動的に計算します。
BABYLON.ShaderMaterial
は、次のマトリックスも処理できます。
-
world
、 -
view
、 -
projection
、 -
worldView
、 -
worldViewProjection
。
もう数学は必要ありません。 たとえば、 sphere.rotation.y += 0.05
を実行するたびに、球のworld
マトリックスが生成され、GPUに送信されます。
ライブ結果をご覧ください。
独自のシェーダーを作成する(CYOS)
それでは、大きくして、独自のシェーダーを動的に作成して結果をすぐに確認できるページを作成しましょう。 このページでは、前に説明したのと同じコードを使用し、 BABYLON.ShaderMaterial
オブジェクトを使用して、作成するシェーダーをコンパイルおよび実行します。
Create Your Own Shader(CYOS)にはACEコードエディターを使用しました。 これは、構文が強調表示された、信じられないほどのコードエディタです。 お気軽にご覧ください。
最初のコンボボックスを使用して、事前定義されたシェーダーを選択できるようになります。 すぐにそれぞれを見ていきます。
2番目のコンボボックスを使用して、シェーダーのプレビューに使用されるメッシュ(つまり3Dオブジェクト)を変更することもできます。
コンパイルボタンは、シェーダーから新しいBABYLON.ShaderMaterial
を作成するために使用されます。 このボタンで使用されるコードは次のとおりです。
// Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;
信じられないほどシンプルですよね? マテリアルは、事前に計算された3つのマトリックス( world
、 worldView
、 worldViewProjection
)を送信する準備ができています。 頂点には、位置、法線、テクスチャの座標が含まれます。 2つのテクスチャもすでにロードされています。
最後に、 renderLoop
は、2つの便利なユニフォームを更新する場所です。
- 1つは
time
と呼ばれ、いくつかの面白いアニメーションを取得します。 - もう1つは
cameraPosition
と呼ばれ、カメラの位置をシェーダーに取り込みます(照明方程式に役立ちます)。
engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });
基本的なシェーダー
CYOSで定義された最初のシェーダーである基本的なシェーダーから始めましょう。
このシェーダーはすでに知っています。 gl_position
を計算し、テクスチャ座標を使用してすべてのピクセルの色をフェッチします。
ピクセル位置を計算するには、 worldViewProjection
マトリックスと頂点の位置が必要です。
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
テクスチャ座標( uv
)は、変更されずにピクセルシェーダーに送信されます。
Chromeで必要なため、頂点シェーダーとピクセルシェーダーの両方の最初の行にprecision mediump float
を追加する必要があることに注意してください。 これは、パフォーマンスを向上させるために、完全精度の浮動小数点値を使用しないことを指定しています。
テクスチャ座標を使用してテクスチャカラーをフェッチするだけでよいため、ピクセルシェーダはさらに単純です。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
以前、 textureSampler
のユニフォームがamiga
テクスチャで満たされていることを確認しました。 したがって、結果は次のようになります。
黒と白のシェーダー
新しいシェーダーである黒と白のシェーダーを続けましょう。 このシェーダーの目的は、前のシェーダーを使用することですが、白黒のみのレンダリングモードを使用します。
そのために、同じ頂点シェーダーを維持できます。 ピクセルシェーダーはわずかに変更されます。
最初のオプションは、緑色のコンポーネントなど、1つのコンポーネントのみを使用することです。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }
ご覧のとおり、 .rgb
(この操作はスウィズルと呼ばれます)を使用する代わりに、 .ggg
を使用しました。
しかし、本当に正確な白黒効果が必要な場合は、輝度の計算(すべてのコンポーネントを考慮に入れる)の方が適しています。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); }
dot
演算(またはdot
積)は次のように計算されます: result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z
したがって、この場合、 luminance = r * 0.3 + g * 0.59 + b * 0.11
です。 (これらの値は、人間の目が緑に対してより敏感であるという事実に基づいています。)
かっこいいですね。
セルシェーディングシェーダー
より複雑なシェーダーであるセルシェーディングシェーダーに移りましょう。
これでは、頂点の法線と頂点の位置をピクセルシェーダーに入れる必要があります。 したがって、頂点シェーダーは次のようになります。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }
位置と法線は変換なしで保存されるため、ワールドマトリックスも使用することに注意してください。また、オブジェクトの回転を考慮してワールドマトリックスを適用する必要があります。
ピクセルシェーダーは次のとおりです。
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }
このシェーダーの目的は、光をシミュレートすることです。滑らかなシェーディングを計算する代わりに、特定の明るさのしきい値に従って光を適用します。 たとえば、光の強度が1(最大)から0.95の間の場合、オブジェクトの色(テクスチャから取得)が直接適用されます。 強度が0.95〜0.5の場合、色は0.8倍減衰します。 等々。
このシェーダーには主に4つのステップがあります。
まず、しきい値とレベル定数を宣言します。
次に、Phongの式を使用して照明を計算します(光は動いていないと見なします)。
vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));
ピクセルあたりの光の強度は、法線方向と光方向の間の角度によって異なります。
次に、ピクセルのテクスチャカラーを取得します。
最後に、しきい値を確認し、レベルを色に適用します。
結果は漫画のオブジェクトのように見えます。
フォンシェーダー
以前のシェーダーでPhong方程式の一部を使用しました。 今すぐ使いましょう。
ここでは、すべてがピクセルシェーダーで行われるため、頂点シェーダーは明らかに単純です。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }
方程式によれば、光の方向と頂点の法線を使用して、「拡散」部分と「鏡面反射」部分を計算する必要があります。
precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }
我々はすでにので、ここで私達はちょうど鏡面部分を追加する必要があり、以前のシェーダで拡散部分を使用します。 フォンシェーディングの詳細については、ウィキペディアをご覧ください。
私たちの球の結果:
シェーダーを破棄します
破棄シェーダーについては、新しい概念であるdiscard
キーワードを紹介します。
このシェーダーは、赤以外のすべてのピクセルを破棄し、掘られたオブジェクトのような錯覚を作り出します。
頂点シェーダーは、基本的なシェーダーで使用されているものと同じです。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
側面のピクセルシェーダーは、色をテストし、たとえば緑のコンポーネントが高すぎる場合は破棄を使用する必要があります。
precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }
結果は少し面白いです:
ウェーブシェーダー
ピクセルシェーダーで多くのことをプレイしましたが、頂点シェーダーでも多くのことができることもお知らせしたいと思います。
ウェーブシェーダーには、フォンピクセルシェーダーを再利用します。
頂点シェーダーは、統一された名前のtime
を使用して、アニメーション化された値を取得します。 このユニフォームを使用して、シェーダーは頂点の位置で波を生成します。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }
副鼻腔がposition.y
に適用され、結果は次のようになります。
球形環境マッピング
これは主に、「球面反射/環境マッピングシェーダーの作成」という記事に触発されました。 その優れた記事を読んで、関連するシェーダーで遊んでみましょう。
フレネルシェーダー
この記事を私のお気に入りのフレネルシェーダーで締めくくりたいと思います。
このシェーダーは、ビューの方向と頂点の法線との間の角度に応じて異なる強度を適用するために使用されます。
頂点シェーダーはセルシェーディングシェーダーで使用されるものと同じであり、ピクセルシェーダーでフレネル項を簡単に計算できます(ビューの方向を評価するために使用できる法線とカメラの位置があるため)。
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); }
あなたのシェーダー?
これで、独自のシェーダーを作成する準備が整いました。 Babylon.jsフォーラムに投稿して、実験を共有してください。
さらに詳しく知りたい場合は、次の便利なリンクをご覧ください。
- Babylon.js、公式ウェブサイト
- Babylon.js、GitHubリポジトリ
- Babylon.jsフォーラム、HTML5ゲーム開発者
- 独自のシェーダー(CYOS)、Babylon.jsを作成する
- OpenGLシェーディング言語」、ウィキペディア
- OpenGLシェーディング言語、ドキュメント