この記事は、読者にマインクラフト統合版でのリソースパックを始めとしたアドオンの作成と、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
まず、お好きなディレクトリ上にファイルを作成し、manifest
と pack_icon
(任意)を生成して、リソースパックを作成します。そして、そこに shaders
と materials
というフォルダを追加するのですが、それらのテンプレートとなるファイルは、IObit Unlocker 等のソフトを使用し、ゲームのアプリケーション内部からコピーしてくる必要があります。どうしてもコピーすることが出来ない場合は、こちらの GitHub Repository からソースコードをダウンロードしてください。その vanillamaterials
と vanillashaders
、それぞれの中に入っています。
さて、早速シェーダーをコーディングしていきましょう。今回は HLSL を用いた解説なので、shaders/hlsl
を開きます。
沢山のファイルが並んでいますね。これらすべてがシェーダーのファイルで、ほとんどが HLSL 形式です。ゲーム内の UI、ブロック、エンティティ など、それぞれが部分的にグラフィックのレンダリングを担当しています。renderchunk.vertex.hlsl
と renderchunk.fragment.hlsl
をお好みのエディタで開いてみましょう。
HLSL は Microsoft が開発した DirectX 用のシェーディング言語のため、Visual Studio Code でサポートされており、拡張機能の導入が必要ありません。
処理の流れとしては、vertex
にデータが入力され、そこで座標等の計算を行い、それを fragment
に出力し、そこで vertex
から入力された座標を元にテクスチャの読み込みや陰影など、色彩に関わる計算を行い、最終的にグラフィックとして出力するといった具合です。つまり、vertex
で形づくり、fragment
で色づけるということですね。他にも geometry
という、頂点の加減等を行うシェーダーも存在しますが、それはゲームの GLSL ではサポートされておらず、ほとんどの場合において編集する必要はありませんので、ここでの説明は割愛させていただきます。
コードを見ると、構造体を利用して、
VSInput
→ vertex
→ PSInput
→ fragment
→ PSOutput
といった流れで処理が進んでいることが分かります。例えば、vertex から値を出力するには、void main() {}
内で PSInput.変数名 = 値;
と記述して代入します。これで fragment
内で PSInput.変数名
とすれば、その変数を使用できるということですね。
この値の入出力の方法を使って、vertex
内の情報の一部を fragment
で出力してみましょう。今回は worldPos
を使用します。それは、カメラの位置を原点とした、世界の相対座標です。まずは構造体で宣言してみましょう。vertex
と fragment
の両方に記述することを忘れないで下さい。
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
を新たに作成します。