DXLibでのジオメトリシェーダーサンプル【シェル式ファーシェーダー】

DXライブラリではジオメトリシェーダーに対応していますが、リファレンスは特に用意されていませんのでそれらを用いることができるサンプルを作成しました。

以下注意点です。
・テクスチャとしてMV1のスペキュラマップ(2番目のレジスタに登録されるテクスチャ)にフィルター用のマップを入れています。これを用いない場合にはアレストネイバーなど、透過を補完しないようにしないといけません。(シェル法なので積層したポリゴンの描画順が正しくないことにより半透明描画が正しくない点を考慮する必要があります)
・WASD、RF、QEキーで積層方向のベクトルを弄ることができます。逆立てたりなびかせたりする際に使用します。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1581549/00cf4841-8fbc-87ea-eb4d-8e3ba0e788d2.png)


以下サンプルです。DXライブラリのサンプルプロジェクトと同じ場所に置くことで試すことができます。
https://drive.google.com/file/d/1CG01dC-WXnl3Sk8Id5Z9S_Up5mwvaX2_/view?usp=sharing

以下ソースコードです。
```cpp:test.cpp
#include "DxLib.h"
#include<functional>// ラムダ式を代入する際に必要なクラス
#include<algorithm>// clampなどを使用する際に必要なクラス(用Ｃ＋＋17)

// シェーダーを使用する際の補助クラス
class ShaderUseClass {
private:
	// シェーダーハンドル
	int m_VertexShaderhandle{-1};
	int m_GeometryShaderhandle{-1};
	int m_PixelShaderhandle{-1};
	// シェーダーに渡す追加パラメーターを配するハンドル
	int m_GeometryShaderMatcbhandle{-1};
	int m_GeometryShadercbhandle{-1};
private:
	// 影用の深度記録画像を作成した際のカメラのビュー行列と射影行列
	struct LIGHTCAMERA_MATRIX {
		MATRIX ViewMatrix;
		MATRIX ProjectionMatrix;
	};
	// DXLIBから引っ張ってきたシェーダー用の定義の一部
	typedef float DX_D3D11_SHADER_FLOAT4[4];
	struct DX_D3D11_GS_CONST_BUFFER_BASE {
		DX_D3D11_SHADER_FLOAT4		ProjectionMatrix[4];											// ビュー　→　プロジェクション行列
		DX_D3D11_SHADER_FLOAT4		ViewMatrix[3];												// ワールド　→　ビュー行列
	};
public:
	ShaderUseClass() {
		// シェーダーハンドル
		m_VertexShaderhandle = -1;
		m_PixelShaderhandle = -1;
		m_GeometryShaderhandle = -1;
		// シェーダーに渡す追加パラメーターを配するハンドル
		m_GeometryShaderMatcbhandle = -1;
		m_GeometryShadercbhandle = -1;
	}
	~ShaderUseClass() {
		Dispose();
	}
public:
	// 初期化
	void			Init(const char* VertexShader, const char* PixelShader) noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
		this->m_VertexShaderhandle = LoadVertexShader(VertexShader);		// シェーダーバイナリコードの読み込み
		this->m_PixelShaderhandle = LoadPixelShader(PixelShader);			// シェーダーバイナリコードの読み込み
	}
	void			AddGeometryShader(const char* GeometryShader) noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
		this->m_GeometryShaderhandle = LoadGeometryShader(GeometryShader);			// シェーダーバイナリコードの読み込み
		this->m_GeometryShaderMatcbhandle = CreateShaderConstantBuffer(sizeof(DX_D3D11_GS_CONST_BUFFER_BASE));	// 追加パラメーター用の定数バッファハンドルを作成
		this->m_GeometryShadercbhandle = CreateShaderConstantBuffer(sizeof(float) * 4);	// 追加パラメーター用の定数バッファハンドルを作成
	}
	// 後始末
	void			Dispose() noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
		DeleteShader(this->m_VertexShaderhandle);			// シェーダーバイナリコードの削除
		DeleteShader(this->m_PixelShaderhandle);			// シェーダーバイナリコードの削除
		DeleteShader(this->m_GeometryShaderhandle);			// シェーダーバイナリコードの削除
		DeleteShaderConstantBuffer(this->m_GeometryShaderMatcbhandle);	// 定数バッファハンドルの削除
		DeleteShaderConstantBuffer(this->m_GeometryShadercbhandle);	// 定数バッファハンドルの削除
	}
public:
	// シェーダ―の1番目のレジスタに情報をセット
	void			SetGeometryCONSTBUFFER(const MATRIX& ViewMatrix, const MATRIX& ProjectionMatrix) noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
		if (this->m_GeometryShaderhandle == -1) { return; }
		DX_D3D11_GS_CONST_BUFFER_BASE* LightCameraMatrixConst = (DX_D3D11_GS_CONST_BUFFER_BASE*)GetBufferShaderConstantBuffer(this->m_GeometryShaderMatcbhandle);

		// ビュー変換用行列をセットする
		LightCameraMatrixConst->ViewMatrix[0][0] = ViewMatrix.m[0][0];
		LightCameraMatrixConst->ViewMatrix[0][1] = ViewMatrix.m[1][0];
		LightCameraMatrixConst->ViewMatrix[0][2] = ViewMatrix.m[2][0];
		LightCameraMatrixConst->ViewMatrix[0][3] = ViewMatrix.m[3][0];
		LightCameraMatrixConst->ViewMatrix[1][0] = ViewMatrix.m[0][1];
		LightCameraMatrixConst->ViewMatrix[1][1] = ViewMatrix.m[1][1];
		LightCameraMatrixConst->ViewMatrix[1][2] = ViewMatrix.m[2][1];
		LightCameraMatrixConst->ViewMatrix[1][3] = ViewMatrix.m[3][1];
		LightCameraMatrixConst->ViewMatrix[2][0] = ViewMatrix.m[0][2];
		LightCameraMatrixConst->ViewMatrix[2][1] = ViewMatrix.m[1][2];
		LightCameraMatrixConst->ViewMatrix[2][2] = ViewMatrix.m[2][2];
		LightCameraMatrixConst->ViewMatrix[2][3] = ViewMatrix.m[3][2];
		// 投影変換用行列をセットする
		LightCameraMatrixConst->ProjectionMatrix[0][0] = ProjectionMatrix.m[0][0];
		LightCameraMatrixConst->ProjectionMatrix[0][1] = ProjectionMatrix.m[1][0];
		LightCameraMatrixConst->ProjectionMatrix[0][2] = ProjectionMatrix.m[2][0];
		LightCameraMatrixConst->ProjectionMatrix[0][3] = ProjectionMatrix.m[3][0];
		LightCameraMatrixConst->ProjectionMatrix[1][0] = ProjectionMatrix.m[0][1];
		LightCameraMatrixConst->ProjectionMatrix[1][1] = ProjectionMatrix.m[1][1];
		LightCameraMatrixConst->ProjectionMatrix[1][2] = ProjectionMatrix.m[2][1];
		LightCameraMatrixConst->ProjectionMatrix[1][3] = ProjectionMatrix.m[3][1];
		LightCameraMatrixConst->ProjectionMatrix[2][0] = ProjectionMatrix.m[0][2];
		LightCameraMatrixConst->ProjectionMatrix[2][1] = ProjectionMatrix.m[1][2];
		LightCameraMatrixConst->ProjectionMatrix[2][2] = ProjectionMatrix.m[2][2];
		LightCameraMatrixConst->ProjectionMatrix[2][3] = ProjectionMatrix.m[3][2];
		LightCameraMatrixConst->ProjectionMatrix[3][0] = ProjectionMatrix.m[0][3];
		LightCameraMatrixConst->ProjectionMatrix[3][1] = ProjectionMatrix.m[1][3];
		LightCameraMatrixConst->ProjectionMatrix[3][2] = ProjectionMatrix.m[2][3];
		LightCameraMatrixConst->ProjectionMatrix[3][3] = ProjectionMatrix.m[3][3];

		UpdateShaderConstantBuffer(this->m_GeometryShaderMatcbhandle);
		SetShaderConstantBuffer(this->m_GeometryShaderMatcbhandle, DX_SHADERTYPE_GEOMETRY, 1);		// 影用深度記録画像を描画したときのカメラのビュー行列と射影行列を定数に設定する
	}
	// 頂点シェーダ―の2番目のレジスタに情報をセット
	void			SetGeometryParam(float param1, float param2, float param3, float param4) noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
		FLOAT4* f4 = (FLOAT4 *)GetBufferShaderConstantBuffer(this->m_GeometryShadercbhandle);		// 頂点シェーダー用の定数バッファのアドレスを取得
		f4->x = param1;
		f4->y = param2;
		f4->z = param3;
		f4->w = param4;
		UpdateShaderConstantBuffer(this->m_GeometryShadercbhandle);								// 頂点シェーダー用の定数バッファを更新して書き込んだ内容を反映する
		SetShaderConstantBuffer(this->m_GeometryShadercbhandle, DX_SHADERTYPE_GEOMETRY, 2);		// 頂点シェーダーの定数バッファを定数バッファレジスタ４にセット
	}
	// 3D空間に適用する場合の関数(引数に3D描画のラムダ式を代入)
	void			Draw_lamda(std::function<void()> doing) noexcept {
		if (GetUseDirect3DVersion() != DX_DIRECT3D_11) {
			doing();
			return;
		}
		SetUseVertexShader(this->m_VertexShaderhandle);											// 使用する頂点シェーダーをセット
		SetUseGeometryShader(this->m_GeometryShaderhandle);										// 使用するジオメトリシェーダーをセット
		SetUsePixelShader(this->m_PixelShaderhandle);											// 使用するピクセルシェーダーをセット
		MV1SetUseOrigShader(TRUE);
		doing();
		MV1SetUseOrigShader(FALSE);
		SetUseVertexShader(-1);
		SetUseGeometryShader(-1);
		SetUsePixelShader(-1);
	}
};

// ウィンドウサイズ定数
#define WIDTH 1280
#define HIGHT 720

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	ShaderUseClass m_Shader;// シェーダー制御クラス

	int ModelHandle = -1;

	float RotateAngle = 0.f;// Y軸回転の角度指定
	MATRIX view, projection;// 変換行列の代入時に使用する一時変数

	float X = 0.0f;// X方向の揺れ 左右咆哮に各ファーをずらす
	float Y = 0.0f;// Y方向の揺れ 上下方向に各ファーをずらす
	float Z = 0.0f;// Z方向の揺れ 前後方向に各ファーをずらす
	float W = 2.4f;// 1層ごとの厚み

	int XM{0}, YM{0};// マウス座標を保持する関数

	// 処理開始
	ChangeWindowMode(TRUE);				// ウィンドウモードに変更
	SetGraphMode(WIDTH, HIGHT, 32);		// ウィンドウサイズを指定
	DxLib_Init();						// DXLIB初期化
	// シェーダー読み込み
	m_Shader.Init("shader/FurVS.vso", "shader/FurPS.pso");// 頂点シェーダー、ピクセルシェーダーを追加
	m_Shader.AddGeometryShader("shader/FurGS.pso");// ジオメトリシェーダーを追加
	// モデル読み込み
	ModelHandle = MV1LoadModel("model/NormalBox_NrmMap.mv1");// 法線マップとスペキュラマップ(2番テクスチャ)に必要なデータを追加したモデル
	// 開始前
	SetLightDirection(VGet(0, -1.f, -1.f));// 斜め前方向からライトを照らすように変更
	// ESCキーが押されるまでループ
	while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0) {
		// マウスのX座標を取得し
		auto prevX = XM;
		auto prevY = YM;
		GetMousePoint(&XM, &YM);
		RotateAngle += 0.02f*(XM - prevX);// 前フレームとの差分をカメラの角度とするように変更

		// パラメーターの調整

		// 揺れ調整
		if (CheckHitKey(KEY_INPUT_D) != 0) { X += 0.05f / 60.f; }
		if (CheckHitKey(KEY_INPUT_A) != 0) { X -= 0.05f / 60.f; }
		if (CheckHitKey(KEY_INPUT_W) != 0) { Z += 0.05f / 60.f; }
		if (CheckHitKey(KEY_INPUT_S) != 0) { Z -= 0.05f / 60.f; }
		if (CheckHitKey(KEY_INPUT_E) != 0) { Y += 0.05f / 60.f; }
		if (CheckHitKey(KEY_INPUT_Q) != 0) { Y -= 0.05f / 60.f; }
		// 厚み調整
		if (CheckHitKey(KEY_INPUT_R) != 0) { W += 0.1f / 60.f; }
		if (CheckHitKey(KEY_INPUT_F) != 0) { W -= 0.1f / 60.f; }
		// 値の制限を追加
		float Pow = 0.3f;
		X = std::clamp(X, -Pow, Pow);
		Y = std::clamp(Y, -Pow, Pow);
		Z = std::clamp(Z, -Pow, Pow);
		W = std::clamp(W, 0.f, 4.5f);
		// 描画を開始
		SetDrawScreen(DX_SCREEN_BACK);// 裏画面へ移動
		ClearDrawScreen();// 現在の画面を初期化
		SetCameraPositionAndTarget_UpVecY(
			VTransform(VGet(-480.0f, 360.0f, 0.0f), MGetRotY(RotateAngle)),// カメラの座標を指定
			VGet(0.0f, 0.0f, 0.0f)// 注視点を指定
		);
		{
			// 素肌の描画
			MV1DrawModel(ModelHandle);
			// ファーの描画
			GetTransformToViewMatrix(&view);// 現在のビュー変換行列を取得
			GetTransformToProjectionMatrix(&projection);// 現在のプロジェクション変換行列を取得
			m_Shader.SetGeometryCONSTBUFFER(view, projection);// レジスタ1にカメラのビュー、プロジェクション変換表列を代入
			m_Shader.SetGeometryParam(X, Y, Z, W);// レジスタ2に調整値を代入
			m_Shader.Draw_lamda([&]() { MV1DrawModel(ModelHandle); });// シェーダーを利用してモデルを描画
		}
		ScreenFlip();
	}
	MV1DeleteModel(ModelHandle);
	m_Shader.Dispose();
	DxLib_End();
	return 0;
}

```

以下ジオメトリシェーダーのサンプルです。頂点シェーダー、ピクセルシェーダーに関してはサンプル及び法線マップ付き剛体メッシュのディレクショナルライトあり描画のピクセルシェーダ―5.0版を参照してください
```hlsl:FurGS.hlsl
struct GS_INPUT {
	float2 TexCoords0	 : TEXCOORD0;		// テクスチャ座標
	float3 WTan			 : TEXCOORD1;		// 接線( ワールド空間 )
	float3 WNormal		 : TEXCOORD2;		// 法線( ワールド空間 )
	float3 WBin			 : TEXCOORD3;		// 従法線( ワールド空間 )
	float4 WPosition	 : SV_POSITION;
};

struct PS_INPUT {
	float2 TexCoords0	 : TEXCOORD0;		// テクスチャ座標
	float3 VTan			 : TEXCOORD1;		// 接線( ビュー空間 )
	float3 VNormal		 : TEXCOORD2;		// 法線( ビュー空間 )
	float3 VBin			 : TEXCOORD3;		// 従法線( ビュー空間 )
	float2 Param0		 : TEXCOORD4;		// ピクセルシェーダーに渡すパラメーター
	float4 Position		 : SV_POSITION;
};

// 基本パラメータ
struct DX_D3D11_GS_CONST_BUFFER_BASE {
	float4 ProjectionMatrix[4]; // ビュー　→　プロジェクション行列
	float4 ViewMatrix[3]; // ワールド　→　ビュー行列
};
// 基本パラメータ
cbuffer cbD3D11_CONST_BUFFER_GS_BASE : register(b1) {
	DX_D3D11_GS_CONST_BUFFER_BASE g_Base;
};
// 基本パラメータ
cbuffer cbD3D11_USER_BUFFER_PARAM : register(b2) {
	float4 g_Param;
};
float3 ViewTrance(float3 lWorldPosition) {
	float3 Ret;
	// ワールド座標をビュー座標に変換
	Ret.x = dot(lWorldPosition, g_Base.ViewMatrix[0].xyz);
	Ret.y = dot(lWorldPosition, g_Base.ViewMatrix[1].xyz);
	Ret.z = dot(lWorldPosition, g_Base.ViewMatrix[2].xyz);
	return Ret;
}
float4 ViewTrance(float4 lWorldPosition) {
	float4 Ret;
	// ワールド座標をビュー座標に変換
	Ret.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
	Ret.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
	Ret.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
	Ret.w = 1.0f;
	return Ret;
}
float4 ProjectionTrance(float4 lViewPosition) {
	float4 Ret;
	// ビュー座標を射影座標に変換
	Ret.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
	Ret.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
	Ret.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
	Ret.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
	return Ret;
}


#define SHELL_LIMIT 8

[maxvertexcount(3 * SHELL_LIMIT)] // ジオメトリシェーダーで出力する最大頂点数
// ジオメトリシェーダー
void main(triangle GS_INPUT In[3], // トライアングル リストを構成する頂点配列の入力情報
		  inout TriangleStream<PS_INPUT> TriStream // 頂点情報を追加するためのストリームオブジェクト
) {
	PS_INPUT Out;

	float4 lWorldPosition;
	float4 LocalPosition;

	// 法線方向に膨らませたポリゴンを出力
	for (int count = 0; count < SHELL_LIMIT; count++) {
		for (int i = 0; i < 3; i++) {
			//ワールド座標をローカル座標に反映
			Out.VTan = normalize(ViewTrance(In[i].WTan));
			Out.VNormal = normalize(ViewTrance(In[i].WNormal));
			Out.VBin = normalize(ViewTrance(In[i].WBin));
			//パラメーターに根元からの割合を保存
			Out.Param0.x = (float)(count + 1) / SHELL_LIMIT;
			//膨らませたワールド座標を演算
			lWorldPosition = In[i].WPosition;
			lWorldPosition.xyz = lWorldPosition.xyz +
				In[i].WNormal * g_Param.w * count +
				pow(g_Param.xyz * count, 3);
			//プロジェクション座標に反映
			LocalPosition = ViewTrance(lWorldPosition);
			Out.Position = ProjectionTrance(LocalPosition);
			//パラメーターに深度を保存
			Out.Param0.y = LocalPosition.z;
			//テクスチャ座標を保存
			Out.TexCoords0 = In[i].TexCoords0;
			//1頂点分のデータを追加
			TriStream.Append(Out);
		}
		TriStream.RestartStrip();
	}
}
```