シェーダー API (surface.hlsl)
PackShade 関数のインターフェース、PackSurface、PackResult 構造体、組み込み関数およびエンジン変数について解説します。
エントリーポイントの契約
すべてのシェーダーパックの surface.hlsl は、以下の関数を 1 つだけ実装します:
PackResult PackShade(PackSurface s);
この関数はモデルの各材質ピクセルごとに実行され、MMD 標準のシェーディング処理を置き換えます。
コンパイル環境と注意点
- コンパイラ: DirectX Shader Compiler(DXC)を用い、Shader Model 6.0(レイトレーシング経路では 6.5)をターゲットにビルドされます。
- HLSL 規格:
-HV 2018(HLSL 2018 仕様)に準拠します。 - インクルード (
#include):surface.hlsl内の#includeは、そのパックフォルダを基準とした相対パスで解決されます。 - 予約語: DXC では
lineが予約語となっているため、変数名などに使用しないでください。 - シェーダーキャッシュ: 初回描画時にコンパイルされ、
<MMDX12>/shader_cacheに保存されます。パック内のファイルが更新されるとキャッシュは破棄され、自動的に再コンパイルされます。
入力構造体: PackSurface
エンジンからピクセルシェーダーへ渡される表面情報です:
| フィールド | 型 | 説明 |
|---|---|---|
worldPos | float3 | ワールド空間座標(MMD単位、+Y が上方向) |
N | float3 | ワールド法線ベクトル(裏面描画時は視線方向へ反転済み) |
V | float3 | 表面から視点(カメラ)への単位ベクトル |
L | float3 | 主光源(太陽光)への単位ベクトル |
uv | float2 | テクスチャ座標 |
pixel | float2 | 画面上のピクセル位置(SV_Position.xy) |
viewZ | float | ビュー空間の深度(カメラからの距離) |
tex | float4 | 基本テクスチャ色(ガンマ空間、材質モーフ適用済み、未設定時は白) |
alpha | float | 材質アルファ × テクスチャアルファ(0.004 未満はエンジン側ですでに破棄済み) |
shadow | float | 太陽光の影(0: 完全な影 .. 1: 光が当たる状態)。影を受けない材質は常に 1 |
materialClass | uint | 分類済み材質 ID:PACK_BODY(0), PACK_SKIN(1), PACK_FACE(2), PACK_EYE(3), PACK_HAIR(4), PACK_WEAPON(5) |
頭部ボーン座標系 (Head Bone Frame)
アニメ調モデルの顔では、鼻や頬の起伏による法線影が原因で顔が立体的に歪んで見えてしまうことがあります。これを防ぐため、頭部ボーンの姿勢情報が提供されます:
| フィールド | 型 | 説明 |
|---|---|---|
hasHead | bool | 頭部ボーン(頭 または 首)が存在するかどうか(存在しない場合はワールド座標軸) |
headPos | float3 | 頭部ボーンのワールド座標 |
headRight | float3 | モデル頭部の右方向ベクトル(+X、単位ベクトル) |
headUp | float3 | モデル頭部の上方向ベクトル(+Y、単位ベクトル) |
headForward | float3 | モデル頭部の正面ベクトル(MMD モデルは -Z 方向を向いています) |
headScale | float | 表示スケール係数(モデル単位あたりのワールド単位) |
出力構造体: PackResult
PackShade がエンジンのレンダラーへ返却する結果です:
struct PackResult {
float3 color; // 線形 HDR 輝度(エンジン仕様に従い gSunIntensity を乗算すること)
float alpha; // 最終不透明度
float reflectivity; // 0.0 .. 1.0(SSR およびレイトレーシング反射の強さ)
bool noAo; // true の場合、アンビエントオクルージョン(SSAO/RTAO)と反射が無効化(顔向け)
};
組み込み関数 (pack_api.hlsli)
float PackParam(uint i): マニフェストに登録されたi番目(0〜15)のパラメータの現在値を取得します。- 材質パラメータ(ガンマ空間、材質モーフ反映済み):
float4 PackDiffuse(): ディフューズ色およびアルファfloat3 PackAmbient(): アンビエント環境光係数float3 PackSpecular(): スペキュラ色float PackSpecularPower(): スペキュラ指数(光沢感)float PackEdgeReflectivity(): エッジ反射率
- MMD クラシックライティング:
float3 PackMmdLit(PackSurface s): 従来の MMD ライティングsaturate(ambient + diffuse * lightColor) * tex.rgbを計算します。
- スフィアマップ:
uint PackSphereMode(): スフィアの合成モード(0: なし、1: 乗算 Multiply、2: 加算 Add)float3 PackSampleSphere(float3 N): ビュー空間法線をもとにスフィアマップをサンプリングします。
- MMD トゥーンランプ:
bool PackHasToon(): 材質に MMD トゥーンテクスチャが設定されているかどうか。float3 PackSampleToon(float v): トゥーン階調テクスチャをサンプリングします(v=0は受光側、v=1は影側)。
エンジン環境変数
エンジン側のシェーダーで宣言されている共通変数を参照できます:
gLightColor(float3): 太陽光の光源色gSunIntensity(float): 全体照度係数(最終的な出力カラーに必ず乗算してください)gRimColor(float3),gRimStrength(float): 全体リムライト設定float3 Hemisphere(float3 n): 天頂(gSkyZenith)と地面(gGroundColor)の半球環境光float3 SrgbToLinear(float3 c): ガンマ sRGB カラーをリニア空間へ変換
パックテクスチャ (API 2)
pack.json で宣言したテクスチャ("textures"、マニフェスト を参照)は、配列の順番どおりのインデックスでサンプリングします。
float4 PackSampleTex(uint i, float2 uv):i番目のテクスチャを宣言したアドレスモード(wrap/clamp)でサンプリングします。float4 PackSampleTexLevel(uint i, float2 uv, float lod): ミップレベルを明示します(ランプ・データマップはlod = 0)。ピクセルステージ以外、たとえばPackEdgeでも使えます。uint2 PackTexSize(uint i): レベル 0 のサイズ(ピクセル)。iが範囲外なら0, 0。uint PackTexCount(): 宣言されたテクスチャの数。
"srgb": true(既定)のテクスチャはリニア値を返すので、SrgbToLinear を通さずにそのまま使ってください。s.tex と PackMmdLit は
これまでどおりガンマ空間です。"srgb": false のテクスチャは保存値をそのまま返します。存在しないテクスチャや範囲外のインデックスは白を返すため、
ユーザーがテクスチャフォルダーを設定していなくてもパックは描画されます。
float3 ramp = PackSampleTexLevel(0, float2(saturate(dot(s.N, s.L) * 0.5 + 0.5), 0.5), 0).rgb; // clamp ランプ、リニア
float4 lm = PackSampleTex(1, s.uv); // "srgb": false のライトマップ:チャンネル値そのまま
輪郭線: PackEdge(任意)
輪郭線は PackShade とは別に、エンジンのエッジパスが描きます。surface.hlsl の先頭で PACK_HAS_EDGE を定義し PackEdge を実装すると、
材質ごとに輪郭線の色と太さを決められます。
#define PACK_HAS_EDGE 1
PackEdgeResult PackEdge(uint materialClass, float4 mmdEdgeColor, float mmdEdgeSize) {
PackEdgeResult e;
e.color = materialClass == PACK_HAIR ? float4(0.25, 0.12, 0.10, 1.0) : mmdEdgeColor; // ガンマ空間 RGBA
e.widthScale = materialClass == PACK_FACE ? 0.5 : 1.0; // MMD の太さに掛ける。0 で輪郭線なし
return e;
}
mmdEdgeColor / mmdEdgeSize は PMX 材質のエッジ値です。PACK_HAS_EDGE がなければ輪郭線はエンジンの既定のままです。
実際の #define PACK_HAS_EDGE 行だけが有効で、コメント内の記述は無視されます。