シェーダー 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

エンジンからピクセルシェーダーへ渡される表面情報です:

フィールド型説明
worldPosfloat3ワールド空間座標(MMD単位、+Y が上方向)
Nfloat3ワールド法線ベクトル(裏面描画時は視線方向へ反転済み)
Vfloat3表面から視点(カメラ)への単位ベクトル
Lfloat3主光源(太陽光)への単位ベクトル
uvfloat2テクスチャ座標
pixelfloat2画面上のピクセル位置(SV_Position.xy)
viewZfloatビュー空間の深度(カメラからの距離)
texfloat4基本テクスチャ色(ガンマ空間、材質モーフ適用済み、未設定時は白)
alphafloat材質アルファ × テクスチャアルファ(0.004 未満はエンジン側ですでに破棄済み)
shadowfloat太陽光の影(0: 完全な影 .. 1: 光が当たる状態)。影を受けない材質は常に 1
materialClassuint分類済み材質 ID:PACK_BODY(0), PACK_SKIN(1), PACK_FACE(2), PACK_EYE(3), PACK_HAIR(4), PACK_WEAPON(5)

頭部ボーン座標系 (Head Bone Frame)

アニメ調モデルの顔では、鼻や頬の起伏による法線影が原因で顔が立体的に歪んで見えてしまうことがあります。これを防ぐため、頭部ボーンの姿勢情報が提供されます:

フィールド型説明
hasHeadbool頭部ボーン(頭 または 首)が存在するかどうか(存在しない場合はワールド座標軸)
headPosfloat3頭部ボーンのワールド座標
headRightfloat3モデル頭部の右方向ベクトル(+X、単位ベクトル)
headUpfloat3モデル頭部の上方向ベクトル(+Y、単位ベクトル)
headForwardfloat3モデル頭部の正面ベクトル(MMD モデルは -Z 方向を向いています)
headScalefloat表示スケール係数(モデル単位あたりのワールド単位)

出力構造体: 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 行だけが有効で、コメント内の記述は無視されます。