シェーダー設計 - actnwit/RhodoniteTS GitHub Wiki
Rhodoniteのシェーダー設計は主に次の3つから成り立っています。
- マテリアル
- パラメータ受け渡しクラス
- シェーダーコード
マテリアル
Rhodoniteでは、Meshが1つまたは複数のPrimitiveを持ち、それぞれのPrimitiveが必ず1つのMaterialを持ちます。 RhodoniteのMaterialはWebGLで例えるならシェーダープログラム(頂点シェーダーやフラグメントシェーダーをあわせたもの)に相当します。 Materialインスタンスには、setParameterメソッドなどで、そのマテリアル(シェーダー)が持つシェーダーパラメーターの具体的な値(ベースカラー等)を設定することができます。
3つのシェーダーコード構築方法
Rhodoniteには以下の3つのシェーダーコードの構築方法があります。
- TypeScriptクラスベース
- Shaderityベース
- ノードエディタ
Rhodoniteでは、この3つのシェーダー機構どれを使ってもシェーダーを開発できます。
TypeScriptクラスベース
シェーダーコードの断片をTypeScriptクラスの中に文字列として埋め込み、組み合わせることで最終的なシェーダーコードを構築する機構です。 この方法はRhodoniteの初期の開発で構築された機構ですが、現在は非推奨となっており、すでに多くのマテリアル・シェーダーコードがShaderityベースに移行しています。さらに将来はノードエディタベースに移行する予定です。
Shaderityベース
Rhodoniteで現在主流のシェーダー作成方法になります。 Rhodoniteとは別プロジェクトとして開発しているShaderユーティリティライブラリ「Shaderity」を利用してシェーダーを構築する機構です。 Shaderityとは、シェーダーのインポート機能によるコード結合機能と、コード変換等のユーティリティ機能を備えたシェーダー開発支援ライブラリです。 Shaderityベースでは通常のGLSLファイルを作成し、それをShaderityのWebPack向けローダーを使って、Rhodoniteライブラリ内に取り込むことでシェーダーを生成します。 その際に、Shaderityのインポート機能を使うことで、複数のGLSLコード断片のファイルからシェーダーを構築できます。
ShaderityベースのGLSLの書き方
以下はRhodoniteのClassicシェーダーのピクセルシェーダーの内容です。
#pragma shaderity: require(../common/version.glsl)
#pragma shaderity: require(../common/enableExtensions.glsl)
#pragma shaderity: require(../common/glslPrecision.glsl)
/* shaderity: @{definitions} */
#pragma shaderity: require(../common/prerequisites.glsl)
in vec3 v_color;
in vec3 v_normal_inWorld;
in vec4 v_position_inWorld;
in vec2 v_texcoord_0;
in vec3 v_baryCentricCoord;
uniform int u_shadingModel; // initialValue=0
uniform float u_alphaCutoff; // initialValue=0.01
uniform float u_shininess; // initialValue=5
uniform vec4 u_diffuseColorFactor; // initialValue=(1,1,1,1)
uniform sampler2D u_diffuseColorTexture; // initialValue=(0,white)
uniform sampler2D u_normalTexture; // initialValue=(1,blue)
uniform vec4 u_diffuseColorTextureTransform; // initialValue=(1,1,0,0)
uniform float u_diffuseColorTextureRotation; // initialValue=0
#pragma shaderity: require(../common/rt0.glsl)
#pragma shaderity: require(../common/utilFunctions.glsl)
/* shaderity: @{getters} */
#pragma shaderity: require(../common/opticalDefinition.glsl)
void main ()
{
#pragma shaderity: require(../common/mainPrerequisites.glsl)
// Normal
vec3 normal_inWorld = normalize(v_normal_inWorld);
vec4 diffuseColorFactor = get_diffuseColorFactor(materialSID, 0);
// diffuseColor (Considered to be premultiplied alpha)
vec3 diffuseColor = vec3(0.0, 0.0, 0.0);
float alpha = 1.0;
if (v_color != diffuseColor && diffuseColorFactor.rgb != diffuseColor) {
diffuseColor = v_color * diffuseColorFactor.rgb;
alpha = diffuseColorFactor.a;
} else if (v_color == diffuseColor) {
diffuseColor = diffuseColorFactor.rgb;
alpha = diffuseColorFactor.a;
} else if (diffuseColorFactor.rgb == diffuseColor) {
diffuseColor = v_color;
} else {
diffuseColor = vec3(1.0, 1.0, 1.0);
}
// diffuseColorTexture (Considered to be premultiplied alpha)
vec4 diffuseColorTextureTransform = get_diffuseColorTextureTransform(materialSID, 0);
float diffuseColorTextureRotation = get_diffuseColorTextureRotation(materialSID, 0);
vec2 diffuseColorTexUv = uvTransform(diffuseColorTextureTransform.xy, diffuseColorTextureTransform.zw, diffuseColorTextureRotation, v_texcoord_0);
vec4 textureColor = texture2D(u_diffuseColorTexture, diffuseColorTexUv);
diffuseColor *= textureColor.rgb;
alpha *= textureColor.a;
#pragma shaderity: require(../common/alphaMask.glsl)
// Lighting
vec3 shadingColor = vec3(0.0, 0.0, 0.0);
#ifdef RN_IS_LIGHTING
int shadingModel = get_shadingModel(materialSID, 0);
if (shadingModel > 0) {
vec3 diffuse = vec3(0.0, 0.0, 0.0);
vec3 specular = vec3(0.0, 0.0, 0.0);
for (int i = 0; i < /* shaderity: @{Config.maxLightNumberInShader} */ ; i++) {
if (i >= lightNumber) {
break;
}
// Light
Light light = getLight(i, v_position_inWorld.xyz);
// Diffuse
diffuse += diffuseColor * max(0.0, dot(normal_inWorld, light.direction)) * light.attenuatedIntensity;
float shininess = get_shininess(materialSID, 0);
int shadingModel = get_shadingModel(materialSID, 0);
float cameraSID = u_currentComponentSIDs[/* shaderity: @{WellKnownComponentTIDs.CameraComponentTID} */];
vec3 viewPosition = get_viewPosition(cameraSID, 0);
// Specular
if (shadingModel == 2) {// BLINN
// ViewDirection
vec3 viewDirection = normalize(viewPosition - v_position_inWorld.xyz);
vec3 halfVector = normalize(light.direction + viewDirection);
specular += pow(max(0.0, dot(halfVector, normal_inWorld)), shininess);
} else if (shadingModel == 3) { // PHONG
vec3 viewDirection = normalize(viewPosition - v_position_inWorld.xyz);
vec3 R = reflect(light.direction, normal_inWorld);
specular += pow(max(0.0, dot(R, viewDirection)), shininess);
}
}
shadingColor = diffuse + specular;
} else {
shadingColor = diffuseColor;
}
#else
shadingColor = diffuseColor;
#endif
rt0 = vec4(shadingColor * alpha, alpha);
// rt0 = vec4(u_lightNumber, 0.0, 0.0, 1.0);
// rt0 = vec4(1.0, 0.0, 0.0, 1.0);
// rt0 = vec4(normal_inWorld*0.5+0.5, 1.0);
#pragma shaderity: require(../common/setAlphaIfNotInAlphaBlendMode.glsl)
#pragma shaderity: require(../common/glFragColor.glsl)
}
次のpragma構文で、外部のglslファイルを読み込むことができます。これはバンドラーによって静的に解決されます。
#pragma shaderity: require(./foo.glsl)
一方、次のコメント構文は、Rhodoniteによって動的に解決され、Rhodonite内部のgettersという変数の内容がここに展開されます。
/* shaderity: @{getters} */
また、以下のuniform変数宣言は、レンダリングアプローチにDataTextureを用いている場合、実際にはUniformではなく浮動小数点テクスチャ経由のパラメーター渡しに変換されます。また、宣言の後ろにコメントでさまざまな付加情報を与えることができます。
uniform int u_shadingModel; // initialValue=0
uniform float u_alphaCutoff; // initialValue=0.01
uniform float u_shininess; // initialValue=5
uniform vec4 u_diffuseColorFactor; // initialValue=(1,1,1,1)
uniform sampler2D u_diffuseColorTexture; // initialValue=(0,white)
uniform sampler2D u_normalTexture; // initialValue=(1,blue)
uniform vec4 u_diffuseColorTextureTransform; // initialValue=(1,1,0,0)
uniform float u_diffuseColorTextureRotation; // initialValue=0
例えば// initialValue=
というコメントによって、そのシェーダーパラメータに初期値を与えることができます。
uniform int u_shadingModel; // initialValue=0
Shaderity独自の構文はpragmaまたはコメント形式で記述されるため、Shaderityコード全体としてはGLSLの文法から逸脱しないものになっています。
ノードエディタ
Rhodoniteには独自のノードシステムを搭載しており、ノード編集によってシェーダーのリアルタイム構築が可能です(開発中です)。