マインクラフト統合版:シェーダーを作る方法【HLSL】


 この記事は、読者にマインクラフト統合版でのリソースパックを始めとしたアドオンの作成と、C 言語でのプログラミングに関する最低限の知識があることを前提として作成されています。

Prologue

 グラフィックエンジンである RenderDragon が実装されていないマインクラフト統合版(Android・iOS・Windows 10, 11 用の x86 リリース(32bit 版用))では、リソースパック内で、それぞれの機種のレンダラ用のシェーディング言語(GLSL・HLSL)を編集することによって、ゲーム内のグラフィックを変更することが出来ます。この記事では、HLSL を用いて解説します。今後、GLSL 用にも記事を作製する予定です。

Get Started

 前述の通り、通常 Microsoft Store からダウンロードできる 64bit のパソコン向けに配信されているマインクラフト統合版には、ゲームのバージョン 1.16.200 より実装された、グラフィックエンジンである RenderDragon の影響で、リソースパックからシェーダーを適用することができなくなっています。そのため、リソースパックからシェーダーを編集するには、32bit のパソコン向けに配信されている、ゲームの x86 リリースをインストールする必要があります。その手順については、こちらの動画を参照してください。

Let’s Try Coding

 まず、お好きなディレクトリ上にファイルを作成し、manifestpack_icon(任意)を生成して、リソースパックを作成します。そして、そこに shadersmaterials というフォルダを追加するのですが、それらのテンプレートとなるファイルは、IObit Unlocker 等のソフトを使用し、ゲームのアプリケーション内部からコピーしてくる必要があります。どうしてもコピーすることが出来ない場合は、こちらの GitHub Repository からソースコードをダウンロードしてください。その vanillamaterialsvanillashaders、それぞれの中に入っています。

 さて、早速シェーダーをコーディングしていきましょう。今回は HLSL を用いた解説なので、shaders/hlsl を開きます。

 沢山のファイルが並んでいますね。これらすべてがシェーダーのファイルで、ほとんどが HLSL 形式です。ゲーム内の UI、ブロック、エンティティ など、それぞれが部分的にグラフィックのレンダリングを担当しています。renderchunk.vertex.hlslrenderchunk.fragment.hlsl をお好みのエディタで開いてみましょう。

 HLSL は Microsoft が開発した DirectX 用のシェーディング言語のため、Visual Studio Code でサポートされており、拡張機能の導入が必要ありません。
 処理の流れとしては、vertex にデータが入力され、そこで座標等の計算を行い、それを fragment に出力し、そこで vertex から入力された座標を元にテクスチャの読み込みや陰影など、色彩に関わる計算を行い、最終的にグラフィックとして出力するといった具合です。つまり、vertex で形づくり、fragment で色づけるということですね。他にも geometry という、頂点の加減等を行うシェーダーも存在しますが、それはゲームの GLSL ではサポートされておらず、ほとんどの場合において編集する必要はありませんので、ここでの説明は割愛させていただきます。

 コードを見ると、構造体を利用して、

VSInputvertexPSInputfragmentPSOutput

といった流れで処理が進んでいることが分かります。例えば、vertex から値を出力するには、void main() {} 内で PSInput.変数名 = 値; と記述して代入します。これで fragment 内で PSInput.変数名 とすれば、その変数を使用できるということですね。
 この値の入出力の方法を使って、vertex 内の情報の一部を fragment で出力してみましょう。今回は worldPos を使用します。それは、カメラの位置を原点とした、世界の相対座標です。まずは構造体で宣言してみましょう。vertexfragment の両方に記述することを忘れないで下さい。

struct PS_Input {
    float4 position : SV_Position;

#ifndef BYPASS_PIXEL_SHADER
    lpfloat4 color : COLOR;
    snorm float2 uv0 : TEXCOORD_0_FB_MSAA;
    snorm float2 uv1 : TEXCOORD_1_FB_MSAA;
#endif

    float3 cameraPos : cameraPos;

#ifdef FOG
    float4 fogColor : FOG_COLOR;
#endif
#ifdef GEOMETRY_INSTANCEDSTEREO
    uint instanceID : SV_InstanceID;
#endif
#ifdef VERTEXSHADER_INSTANCEDSTEREO
    uint renTarget_id : SV_RenderTargetArrayIndex;
#endif
};

 ここでは変数名を cameraPos としました。もちろん、任意の名前でかまいません。float3 という型は、3 次元の座標を表すために、おなじみの float 型が 3 つ並んで作られています。直接値を代入するときは、float3 coord = float3(0.0, 1.0, 0.0) と記述します。それでは vertex 内のコードに目を通しましょう。

    float3 worldPos = (VSInput.position.xyz * CHUNK_ORIGIN_AND_SCALE.w) + CHUNK_ORIGIN_AND_SCALE.xyz;

 コードの中盤あたりで、このようにworldPos に値が代入されていますね。この下で cameraPos に代入しましょう。

    PSInput.cameraPos = worldPos;

 これで fragment 内に vertex から cameraPos が入力され、fragment 内でも使用できるようになりました。それでは renderchunk.fragment.hlsl を開きましょう。

    PSOutput.color = diffuse;

 コードの終盤で、このように出力されていますね。diffuse にはテクスチャ、陰影、霧などが格納されています。それを次のように書き換えてみましょう。

    PSOutput.color = float4(PSInput.cameraPos, 1.0);

 fragment で出力する値は、3 次元の情報に色としての透明度を加えた、4 次元である必要があるので、1.0 を加えています。編集したファイルを保存し、リソースパックをゲームに適応させ、読み込んでみましょう。

 現時点では、シェーダーのファイルの一部が、最新版のゲームとの互換性が失われているようです。UI が透明になるなどの不具合が発生する場合があるので、今回編集したファイル以外は削除することをお勧めします。


 世界の色が変わりましたね。これはカメラの位置を原点とした世界の相対座標を可視化したということになります。これでシェーダーを作成する要領がつかめましたね。これを応用して様々なことをやってみてください。バニラのゲームのレンダリングに使用されている機能だけを使うことになりますが、あなたのやる気があれば、結果は無限大です。公式のリファレンスも参照すると良いでしょう。

Editing Material File

 ゲームは、おそらく容量の確保を確保するために、複数のグラフィックの場面で 1 つのシェーダーファイルを流用していることがあります。そのため、例えば空を編集したら、他の場面も変わってしまった……。といった事態も起こりかねません。しかし、ゲームが、どの場面がどのシェーダーファイルを参照するかは、material ファイル内で JSON 形式で記述されています。では、空に関するシェーダーファイルの指定がされている materials/sky.material を開きましょう。

  "skyplane": {
    "states": [ "DisableDepthWrite", "DisableAlphaWrite" ],

        "vertexShader" : "shaders/sky.vertex",
        "vrGeometryShader" : "shaders/sky.geometry",
        "fragmentShader" : "shaders/color.fragment",
    "vertexFields": [
      { "field": "Position" },
      { "field": "Color" }
    ],
    "msaaSupport": "Both"
  },

 ここで、skyplane(ゲーム内での青空)を描画するために、color.fragment が参照されていることが分かります。これを sky.fragment に書き換えてください。sky でなくても、任意の名前で結構です。GLSL との互換性を保つために、拡張子の .hlsl 以降は記述しないでください。そして、shaders/hlsl 内に color.fragment.hlsl と中身が同じ、sky.fragment.hlsl を新たに作成します。