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

:::note info
　この記事は、読者にマインクラフト統合版でのリソースパックを始めとしたアドオンの作成と、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 リリースをインストールする必要があります。その手順については、[こちらの動画](https://youtu.be/kSRZ5DnRZUs "How to Download MCBE which supports 3rd Party Shaders")を参照してください。
# Let’s Try Coding
　まず、お好きなディレクトリ上にファイルを作成し、`manifest` と `pack_icon`（任意）を生成して、リソースパックを作成します。そして、そこに `shaders` と `materials` というフォルダを追加するのですが、それらのテンプレートとなるファイルは、[IObit Unlocker](https://www.iobit.com/en/iobit-unlocker.php "Official Site of IObit Unlocker") 等のソフトを使用し、ゲームのアプリケーション内部からコピーしてくる必要があります。どうしてもコピーすることが出来ない場合は、[こちらの GitHub Repository](https://github.com/Rinloid/vanilla-bedrock-shader-files "Vanilla Shader Files") からソースコードをダウンロードしてください。その `vanillamaterials` と `vanillashaders`、それぞれの中に入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/6485c39f-ebca-72e1-e622-f2856ca4d372.png)
　さて、早速シェーダーをコーディングしていきましょう。今回は HLSL を用いた解説なので、`shaders/hlsl` を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/b6ee1d30-39c4-538f-88fb-be781ace90e4.png)
　沢山のファイルが並んでいますね。これらすべてがシェーダーのファイルで、ほとんどが HLSL 形式です。ゲーム内の UI、ブロック、エンティティ など、それぞれが部分的にグラフィックのレンダリングを担当しています。`renderchunk.vertex.hlsl` と `renderchunk.fragment.hlsl` をお好みのエディタで開いてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/8be061b8-b199-f390-fc69-46fe1ee86a9b.png)
　HLSL は Microsoft が開発した DirectX 用のシェーディング言語のため、Visual Studio Code でサポートされており、拡張機能の導入が必要ありません。
　処理の流れとしては、`vertex` にデータが入力され、そこで座標等の計算を行い、それを `fragment` に出力し、そこで `vertex` から入力された座標を元にテクスチャの読み込みや陰影など、色彩に関わる計算を行い、最終的にグラフィックとして出力するといった具合です。つまり、`vertex` で形づくり、`fragment` で色づけるということですね。他にも `geometry` という、頂点の加減等を行うシェーダーも存在しますが、それはゲームの GLSL ではサポートされておらず、ほとんどの場合において編集する必要はありませんので、ここでの説明は割愛させていただきます。
![shaderexplanation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/935215ae-233e-2562-f652-8925f7f5bb85.png)
　コードを見ると、構造体を利用して、

`VSInput` → `vertex` → `PSInput` → `fragment` → `PSOutput` 

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

```hlsl
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` 内のコードに目を通しましょう。

```hlsl
    float3 worldPos = (VSInput.position.xyz * CHUNK_ORIGIN_AND_SCALE.w) + CHUNK_ORIGIN_AND_SCALE.xyz;
```
　コードの中盤あたりで、このように`worldPos` に値が代入されていますね。この下で `cameraPos` に代入しましょう。

```hlsl
    PSInput.cameraPos = worldPos;
```

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

```hlsl
    PSOutput.color = diffuse;
```

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

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

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

:::note warn
　現時点では、シェーダーのファイルの一部が、最新版のゲームとの互換性が失われているようです。UI が透明になるなどの不具合が発生する場合があるので、今回編集したファイル以外は削除することをお勧めします。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/e188ddd2-054b-486e-ae10-e0abfe51f7fd.png)
　世界の色が変わりましたね。これはカメラの位置を原点とした世界の相対座標を可視化したということになります。これでシェーダーを作成する要領がつかめましたね。これを応用して様々なことをやってみてください。バニラのゲームのレンダリングに使用されている機能だけを使うことになりますが、あなたのやる気があれば、結果は無限大です。[公式のリファレンス](https://docs.microsoft.com/windows/win32/direct3dhlsl/dx-graphics-hlsl-reference "Reference for HLSL")も参照すると良いでしょう。
# Editing Material File
　ゲームは、おそらく容量の確保を確保するために、複数のグラフィックの場面で 1 つのシェーダーファイルを流用していることがあります。そのため、例えば空を編集したら、他の場面も変わってしまった……。といった事態も起こりかねません。しかし、ゲームが、どの場面がどのシェーダーファイルを参照するかは、`material` ファイル内で JSON 形式で記述されています。では、空に関するシェーダーファイルの指定がされている `materials/sky.material` を開きましょう。

```json
  "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` を新たに作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2250486/b8d1b538-82ca-e637-8f52-e0bf74718244.png)