3年目、笑也知郎です。
コノショクギョウナンモワカラン
今年もカレンダーの方に投稿いたします。Screen Space Reflectionの実装を紹介します。
ご注意
テスト版でDXLIB管理人様に追加いただいた機能を用いております。こちらを参照ください
つくるもの
以前管理人様がSSAO(スクリーンベースの環境遮蔽 者同士の間にできる陰影を画面上から生成する機能)を作成されておられました。
その際、SetRenderTargetToShaderを用いると同じ描画で普通の色味とノーマルマップ(カメラからの法線ベクトルを示したもの)の2枚を一気に描画することができるようにされておりました。いわゆるGバッファです。
さて、デフォルトの機能でGバッファを取得できるのであれば、SSAO以外の表現もできるのではないか…
それならばSSR(スクリーンベースの反射)を実装してみよう、といったところです
今回管理人さんには法線に加えて深度とスペキュラ値を出力いただいています。
基本描画
まずはベースとなるソースを作りました。
ベース
Windows版 VisualStudio( C++ )用のVisualStudio_2017用を使用し、言語をC++17に指定
#include "DxLib.h"
#include <array>
#include <functional>
// 画面のサイズ
#define SCREEN_W 1920
#define SCREEN_H 1080
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
float XPos = 0.f;
float ZPos = -1.92f;
int ModelHandle;
int OutputScreenHandle;
// ウインドウモードで起動
ChangeWindowMode(TRUE);
// 画面解像度を設定
SetGraphMode(SCREEN_W, SCREEN_H, 32);
SetWaitVSyncFlag(FALSE);
// DXライブラリの初期化
if (DxLib_Init() < 0) return -1;
ModelHandle = MV1LoadModel("SSAO_Stage.mqo"); // 3Dモデルの読み込み
OutputScreenHandle = MakeScreen(SCREEN_W, SCREEN_H, TRUE); // 出力先バッファの作成
SetUsePixelLighting(TRUE); // ピクセル単位のライティングを行う描画モードに設定する
// メインループ
float DrawTimer = 0.f;
while (ProcessMessage() == 0) {
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) { break; }
//
float fov_cam = 60.f * DX_PI_F / 180.0f;
// カメラを設定
SetDrawScreen(OutputScreenHandle);
ClearDrawScreen();
SetCameraPositionAndTarget_UpVecY(VGet(XPos, 0.f, ZPos), VGet(0.f, 0.f, 0.f));
SetCameraNearFar(0.1f, 300.f);
SetupCamera_Perspective(fov_cam);
{
MV1DrawModel(ModelHandle); // モデルの描画
}
//
SetDrawScreen(DX_SCREEN_BACK); //使用するテクスチャをセット
ClearDrawScreen();
{
DrawBox(0, 0, SCREEN_W, SCREEN_H, GetColor(0, 0, 0), TRUE);
// パラメータを画面に表示
DrawFormatString(0, 32 * 0, GetColor(255, 255, 255), "W/S/A/D key (%6.2f,%6.2f,%6.2f) : カメラ座標", XPos, 0.f, ZPos);
DrawFormatString(0, 32 * 4, GetColor(255, 255, 255), "FPS %6.2f : FPS", GetFPS());
DrawFormatString(0, 32 * 5, GetColor(255, 255, 255), "DrawTime(ms) %6.2f : 描画時間", DrawTimer);
DrawFormatString(0, 32 * 7, GetColor(255, 255, 255), "Esc 終了");
}
// キーによるパラメータの操作
switch (GetInputChar(TRUE)) {
case 'a': XPos -= 0.3f; break;
case 'd': XPos += 0.3f; break;
case 'w': ZPos += 0.3f; break;
case 's': ZPos -= 0.3f; break;
default:break;
}
LONGLONG StartTime = GetNowHiPerformanceCount(); //ScreenFlip前に現在の時間を取る
ScreenFlip(); // 裏画面の内容を表画面に反映
DrawTimer = (float)(GetNowHiPerformanceCount() - StartTime) / 1000.f;//ScreenFlip出かかった時間をミリ秒単位で取得
}
// DXライブラリの後始末
DxLib_End();
// ソフトの終了
return 0;
}
SSAO_Stage.mqoを画面中央に表示させるサンプルです。
サンプルプロジェクトのtest.cppにこちらをコピペしてください。
シェーダー制御クラスを作る
さて、SSRをするためには画面に対して作用するシェーダーを書く必要があります。詳細は省きつつですが以下のものを用いることで画面に対してシェーダーを適用できます。
クリックして展開
//シェーダーを使用する際の補助クラス
class ShaderUseClass {
public:
//2Dにシェーダーを適用する際に使用する画面サイズの頂点情報
class ScreenVertex {
VERTEX3DSHADER Screen_vertex[6] = {};
public:
// 頂点データの取得
const auto* GetScreenVertex() noexcept { return Screen_vertex; }
// 頂点データの準備
void SetScreenVertex(int dispx, int dispy) noexcept {
int xp1 = 0;
int yp1 = dispy;
int xp2 = dispx;
int yp2 = 0;
Screen_vertex[0].pos = VGet((float)xp1, (float)yp1, 0.0f);
Screen_vertex[1].pos = VGet((float)xp2, (float)yp1, 0.0f);
Screen_vertex[2].pos = VGet((float)xp1, (float)yp2, 0.0f);
Screen_vertex[3].pos = VGet((float)xp2, (float)yp2, 0.0f);
Screen_vertex[0].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[1].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[2].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[3].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[0].u = 0.0f; Screen_vertex[0].v = 0.0f;
Screen_vertex[1].u = 1.0f; Screen_vertex[1].v = 0.0f;
Screen_vertex[2].u = 0.0f; Screen_vertex[3].v = 1.0f;
Screen_vertex[3].u = 1.0f; Screen_vertex[2].v = 1.0f;
Screen_vertex[4] = Screen_vertex[2];
Screen_vertex[5] = Screen_vertex[1];
}
};
private:
//シェーダーハンドル
int m_VertexShaderhandle{ -1 };
int m_PixelShaderhandle{ -1 };
//シェーダーに渡す追加パラメーターを配するハンドル
std::array<int, 4> m_VertexShadercbhandle{ -1 };
int m_PixelShaderSendDispSizeHandle{ -1 };
std::array<int, 4> m_PixelShadercbhandle{ -1 };
public:
ShaderUseClass() {
//シェーダーハンドル
m_VertexShaderhandle = -1;
m_PixelShaderhandle = -1;
//シェーダーに渡す追加パラメーターを配するハンドル
for (auto& h : m_VertexShadercbhandle) { h = -1; }
m_PixelShaderSendDispSizeHandle = -1;
for (auto& h : m_PixelShadercbhandle) { h = -1; }
}
~ShaderUseClass() {
Dispose();
}
public:
//初期化
void Init(const char* VertexShader, const char* PixelShader) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
//頂点シェーダー周り
for (auto& h : m_VertexShadercbhandle) {
h = CreateShaderConstantBuffer(sizeof(float) * 4);
}
this->m_VertexShaderhandle = LoadVertexShader(VertexShader); // 頂点シェーダーバイナリコードの読み込み
//ピクセルシェーダ―周り
this->m_PixelShaderSendDispSizeHandle = CreateShaderConstantBuffer(sizeof(float) * 4);
for (auto& h : m_PixelShadercbhandle) {
h = CreateShaderConstantBuffer(sizeof(float) * 4);
}
this->m_PixelShaderhandle = LoadPixelShader(PixelShader); // ピクセルシェーダーバイナリコードの読み込み
}
//後始末
void Dispose() noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
//頂点シェーダー周り
for (auto& h : m_VertexShadercbhandle) {
DeleteShaderConstantBuffer(h);
}
DeleteShader(this->m_VertexShaderhandle);
//ピクセルシェーダ―周り
DeleteShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle);
for (auto& h : m_PixelShadercbhandle) {
DeleteShaderConstantBuffer(h);
}
DeleteShader(this->m_PixelShaderhandle);
}
public:
//頂点シェーダ―のSlot番目のレジスタに情報をセット(Slot>=4)
void SetVertexParam(int Slot, float param1, float param2, float param3, float param4) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT4* f4 = (FLOAT4 *)GetBufferShaderConstantBuffer(this->m_VertexShadercbhandle[0]); // 頂点シェーダー用の定数バッファのアドレスを取得
f4->x = param1;
f4->y = param2;
f4->z = param3;
f4->w = param4;
UpdateShaderConstantBuffer(this->m_VertexShadercbhandle[0]); // 頂点シェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_VertexShadercbhandle[0], DX_SHADERTYPE_VERTEX, Slot); // 頂点シェーダーの定数バッファを定数バッファレジスタ4にセット
}
//ピクセルシェーダ―の2番目のレジスタに画面サイズの情報をセット
void SetPixelDispSize(int dispx, int dispy) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT2* dispsize = (FLOAT2*)GetBufferShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle); // ピクセルシェーダー用の定数バッファのアドレスを取得
dispsize->u = (float)dispx;
dispsize->v = (float)dispy;
UpdateShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle); // ピクセルシェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle, DX_SHADERTYPE_PIXEL, 2); // ピクセルシェーダー用の定数バッファを定数バッファレジスタ2にセット
}
//ピクセルシェーダ―のSlot番目のレジスタに情報をセット(Slot>=3)
void SetPixelParam(int Slot, float param1, float param2, float param3, float param4) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT4* f4 = (FLOAT4*)GetBufferShaderConstantBuffer(this->m_PixelShadercbhandle[0]); // ピクセルシェーダー用の定数バッファのアドレスを取得
f4->x = param1;
f4->y = param2;
f4->z = param3;
f4->w = param4;
UpdateShaderConstantBuffer(this->m_PixelShadercbhandle[0]); // ピクセルシェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_PixelShadercbhandle[0], DX_SHADERTYPE_PIXEL, Slot); // ピクセルシェーダー用の定数バッファを定数バッファレジスタ3にセット
}
//3D空間に適用する場合の関数(引数に3D描画のラムダ式を代入)
void Draw_lamda(std::function<void()> doing) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) {
doing();
return;
}
SetUseVertexShader(this->m_VertexShaderhandle); // 使用する頂点シェーダーをセット
SetUsePixelShader(this->m_PixelShaderhandle); // 使用するピクセルシェーダーをセット
MV1SetUseOrigShader(TRUE);
doing();
MV1SetUseOrigShader(FALSE);
SetUseVertexShader(-1);
SetUsePixelShader(-1);
}
//2D画像に適用する場合の関数
void Draw(ScreenVertex& Screenvertex) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
Draw_lamda([&] {DrawPolygon3DToShader(Screenvertex.GetScreenVertex(), 2); });
}
};
シェーダー制御
そして味噌、シェーダーです。シェーダーモデル4.0に対応したものとなっております。
頂点シェーダー
// 頂点シェーダーの入力
struct VS_INPUT
{
float3 pos : POSITION0 ; // 座標( ローカル空間 )
float4 spos : POSITION1 ; // 予備座標
float3 norm : NORMAL0 ; // 法線( ローカル空間 )
float3 tan : TANGENT0 ; // 接線( ローカル空間 )
float3 binorm : BINORMAL0 ; // 従法線( ローカル空間 )
float4 dif : COLOR0 ; // ディフューズカラー
float4 spc : COLOR1 ; // スペキュラカラー
float2 texCoords0 : TEXCOORD0 ; // テクスチャ座標
float2 texCoords1 : TEXCOORD1 ; // サブテクスチャ座標
} ;
// 頂点シェーダーの出力
struct VS_OUTPUT
{
float4 dif : COLOR0 ; // ディフューズカラー
float2 texCoords0 : TEXCOORD0 ; // テクスチャ座標
float4 pos : SV_POSITION ; // 座標( プロジェクション空間 )
} ;
// 基本パラメータ
struct DX_D3D11_VS_CONST_BUFFER_BASE
{
float4 AntiViewportMatrix[ 4 ] ; // アンチビューポート行列
float4 ProjectionMatrix[ 4 ] ; // ビュー → プロジェクション行列
float4 ViewMatrix[ 3 ] ; // ワールド → ビュー行列
float4 LocalWorldMatrix[ 3 ] ; // ローカル → ワールド行列
float4 ToonOutLineSize ; // トゥーンの輪郭線の大きさ
float DiffuseSource ; // ディフューズカラー( 0.0f:マテリアル 1.0f:頂点 )
float SpecularSource ; // スペキュラカラー( 0.0f:マテリアル 1.0f:頂点 )
float MulSpecularColor ; // スペキュラカラー値に乗算する値( スペキュラ無効処理で使用 )
float Padding ;
} ;
// その他の行列
struct DX_D3D11_VS_CONST_BUFFER_OTHERMATRIX
{
float4 ShadowMapLightViewProjectionMatrix[ 3 ][ 4 ] ; // シャドウマップ用のライトビュー行列とライト射影行列を乗算したもの
float4 TextureMatrix[ 3 ][ 2 ] ; // テクスチャ座標操作用行列
} ;
// 基本パラメータ
cbuffer cbD3D11_CONST_BUFFER_VS_BASE : register( b1 )
{
DX_D3D11_VS_CONST_BUFFER_BASE g_Base ;
} ;
// その他の行列
cbuffer cbD3D11_CONST_BUFFER_VS_OTHERMATRIX : register( b2 )
{
DX_D3D11_VS_CONST_BUFFER_OTHERMATRIX g_OtherMatrix ;
} ;
// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
VS_OUTPUT VSOutput ;
float4 lLocalPosition ;
float4 lWorldPosition ;
float4 lViewPosition ;
// 頂点座標変換 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
// ローカル座標のセット
lLocalPosition.xyz = VSInput.pos ;
lLocalPosition.w = 1.0f ;
// 座標計算( ローカル→ビュー→プロジェクション )
lWorldPosition.x = dot( lLocalPosition, g_Base.LocalWorldMatrix[ 0 ] ) ;
lWorldPosition.y = dot( lLocalPosition, g_Base.LocalWorldMatrix[ 1 ] ) ;
lWorldPosition.z = dot( lLocalPosition, g_Base.LocalWorldMatrix[ 2 ] ) ;
lWorldPosition.w = 1.0f ;
lViewPosition.x = dot( lWorldPosition, g_Base.ViewMatrix[ 0 ] ) ;
lViewPosition.y = dot( lWorldPosition, g_Base.ViewMatrix[ 1 ] ) ;
lViewPosition.z = dot( lWorldPosition, g_Base.ViewMatrix[ 2 ] ) ;
lViewPosition.w = 1.0f ;
VSOutput.pos.x = dot( lViewPosition, g_Base.ProjectionMatrix[ 0 ] ) ;
VSOutput.pos.y = dot( lViewPosition, g_Base.ProjectionMatrix[ 1 ] ) ;
VSOutput.pos.z = dot( lViewPosition, g_Base.ProjectionMatrix[ 2 ] ) ;
VSOutput.pos.w = dot( lViewPosition, g_Base.ProjectionMatrix[ 3 ] ) ;
// 頂点座標変換 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
// 出力パラメータセット ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
VSOutput.texCoords0 = VSInput.texCoords0 ;
VSOutput.dif = VSInput.dif ;
// 出力パラメータセット ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
// 出力パラメータを返す
return VSOutput ;
}
ピクセルシェーダ―
// ピクセルシェーダーの入力
struct PS_INPUT
{
float4 dif : COLOR0; // ディフューズカラー
float2 texCoords0 : TEXCOORD0; // テクスチャ座標
float4 pos : SV_POSITION; // 座標( プロジェクション空間 )
};
// ピクセルシェーダーの出力
struct PS_OUTPUT
{
float4 color0 : SV_TARGET0; // 色
};
// 定数バッファピクセルシェーダー基本パラメータ
struct DX_D3D11_PS_CONST_BUFFER_BASE
{
float4 FactorColor; // アルファ値等
float MulAlphaColor; // カラーにアルファ値を乗算するかどうか( 0.0f:乗算しない 1.0f:乗算する )
float AlphaTestRef; // アルファテストで使用する比較値
float2 Padding1;
int AlphaTestCmpMode; // アルファテスト比較モード( DX_CMP_NEVER など )
int3 Padding2;
float4 IgnoreTextureColor; // テクスチャカラー無視処理用カラー
};
// 基本パラメータ
cbuffer cbD3D11_CONST_BUFFER_PS_BASE : register(b1)
{
DX_D3D11_PS_CONST_BUFFER_BASE g_Base;
};
// プログラムとのやり取りのために使うレジスタ1
cbuffer cbMULTIPLYCOLOR_CBUFFER1 : register(b2)
{
float2 dispsize;
}
// プログラムとのやり取りのために使うレジスタ2
cbuffer cbMULTIPLYCOLOR_CBUFFER2 : register(b3)
{
float4 caminfo;
}
SamplerState g_DiffuseMapSampler : register(s0); // ディフューズマップサンプラ
Texture2D g_DiffuseMapTexture : register(t0); // ディフューズマップテクスチャ
SamplerState g_NormalMapSampler : register(s1); // 法線マップサンプラ
Texture2D g_NormalMapTexture : register(t1); // 法線マップテクスチャ
SamplerState g_DepthMapSampler : register(s2); // 深度マップサンプラ
Texture2D g_DepthMapTexture : register(t2); // 深度マップテクスチャ
float3 DisptoProj(float2 screenUV)
{
float2 pos;
pos.x = screenUV.x * 2.f - 1.f;
pos.y = 1.f - screenUV.y * 2.f;
float3 V_to_Eye;
V_to_Eye.x = pos.x * caminfo.z * dispsize.x / dispsize.y;
V_to_Eye.y = pos.y * caminfo.z;
V_to_Eye.z = 1.f;
return V_to_Eye * (g_DepthMapTexture.Sample(g_DepthMapSampler, screenUV).r / (caminfo.y*0.005f)); //距離
}
float2 ProjtoDisp(float3 position)
{
position = position / position.z;
float2 pos;
pos.x = position.x / caminfo.z * dispsize.y / dispsize.x;
pos.y = position.y / caminfo.z;
pos.x = pos.x / 2.f + 0.5f;
pos.y = 0.5f - pos.y / 2.f;
return pos;
/*
float4 projectPosition = mul(gProjectionMatrix, float4(position, 1.0));
float2 pos = projectPosition.xy / projectPosition.w * 0.5f + 0.5f;
pos.y = 1.0f - pos.y;
return pos;
//*/
}
bool Hitcheck(float3 position)
{
float2 screenUV = ProjtoDisp(position);
if (
(-1.f <= screenUV.x && screenUV.x <= 1.f) &&
(-1.f <= screenUV.y && screenUV.y <= 1.f)
) {
float z = g_DepthMapTexture.Sample(g_DepthMapSampler, screenUV).r / (caminfo.y*0.005f);
return (position.z < z && z < position.z + caminfo.w);
}
else {
return false;
}
}
float4 applySSR(float3 normal, float2 screenUV)
{
float3 position = DisptoProj(screenUV);
float4 color;
color.r = 0.f;
color.g = 0.f;
color.b = 0.f;
color.a = 0.f;
float3 reflectVec = normalize(reflect(normalize(position), normal)); // 反射ベクトル
int iteration = (int)(caminfo.x); // 繰り返し数
float maxLength = 500.f; // 反射最大距離
int BinarySearchIterations = 6; //2分探索最大数
float pixelStride = maxLength / (float)iteration;
float3 delta = reflectVec * pixelStride; // 1回で進む距離
int isend = 0;
for (int i = 0; i < iteration; i++)
{
if (isend == 0)
{
position += delta;
if (Hitcheck(position))
{
//交差したので二分探索
position -= delta; //元に戻し
delta /= BinarySearchIterations; //進む量を下げる
for (int j = 0; j < BinarySearchIterations; j++)
{
if (isend == 0)
{
pixelStride *= 0.5f;
position += delta * pixelStride;
if (Hitcheck(position))
{
pixelStride = -pixelStride;
}
if (length(pixelStride)<10.f) {
isend = 1;
}
}
}
// 交差したので色をブレンドする
color = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, ProjtoDisp(position));
isend = 1;
}
}
}
return color;
}
PS_OUTPUT main(PS_INPUT PSInput)
{
PS_OUTPUT PSOutput;
float3 normal = g_NormalMapTexture.Sample(g_NormalMapSampler, PSInput.texCoords0).xyz;
normal.x = normal.x * 2.f - 1.f;
normal.y = normal.y * 2.f - 1.f;
normal.z = normal.z * 2.f - 1.f;
PSOutput.color0 = applySSR(normal, PSInput.texCoords0);
if (PSOutput.color0.a == 0.f)
{
PSOutput.color0.r = 0.f;
PSOutput.color0.g = 0.f;
PSOutput.color0.b = 0.f;
PSOutput.color0.a = 0.f;
}
else {
float4 color = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, PSInput.texCoords0);
if (
abs(PSOutput.color0.r - color.r) < 2.f / 255.f &&
abs(PSOutput.color0.g - color.g) < 2.f / 255.f &&
abs(PSOutput.color0.b - color.b) < 2.f / 255.f) {
PSOutput.color0.r = 0.f;
PSOutput.color0.g = 0.f;
PSOutput.color0.b = 0.f;
PSOutput.color0.a = 0.f;
}
else {
PSOutput.color0 = lerp(float4(0.f, 0.f, 0.f, 0.f), PSOutput.color0, g_DepthMapTexture.Sample(g_DepthMapSampler, PSInput.texCoords0).g);
}
}
return PSOutput;
}
もう完成品を出してしまっていますが、各部の簡単な説明です
まず頂点シェーダー、こちらはほとんど弄っておりません。こちらのうち最低限を抜き出したものです。
ピクセルシェーダ―、DisptoProj、ProjtoDisp(画面座標とプロジェクション座標との相互変換)は普通によろしくないのですが、頂点シェーダー側のProjectionMatrixに当たるものが見当たらず…
自作しましたが、精度周りに問題があるのか縞々が見えてしまいます…
SSRの内容としましてはこちらをかみ砕いた形です。詳細はそちらで…
完成品です
#include "DxLib.h"
#include <array>
#include <functional>
// 画面のサイズ
#define SCREEN_W 1920
#define SCREEN_H 1080
//シェーダーを使用する際の補助クラス
class ShaderUseClass {
public:
//2Dにシェーダーを適用する際に使用する画面サイズの頂点情報
class ScreenVertex {
VERTEX3DSHADER Screen_vertex[6] = {};
public:
// 頂点データの取得
const auto* GetScreenVertex() noexcept { return Screen_vertex; }
// 頂点データの準備
void SetScreenVertex(int dispx, int dispy) noexcept {
int xp1 = 0;
int yp1 = dispy;
int xp2 = dispx;
int yp2 = 0;
Screen_vertex[0].pos = VGet((float)xp1, (float)yp1, 0.0f);
Screen_vertex[1].pos = VGet((float)xp2, (float)yp1, 0.0f);
Screen_vertex[2].pos = VGet((float)xp1, (float)yp2, 0.0f);
Screen_vertex[3].pos = VGet((float)xp2, (float)yp2, 0.0f);
Screen_vertex[0].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[1].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[2].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[3].dif = GetColorU8(255, 255, 255, 255);
Screen_vertex[0].u = 0.0f; Screen_vertex[0].v = 0.0f;
Screen_vertex[1].u = 1.0f; Screen_vertex[1].v = 0.0f;
Screen_vertex[2].u = 0.0f; Screen_vertex[3].v = 1.0f;
Screen_vertex[3].u = 1.0f; Screen_vertex[2].v = 1.0f;
Screen_vertex[4] = Screen_vertex[2];
Screen_vertex[5] = Screen_vertex[1];
}
};
private:
//シェーダーハンドル
int m_VertexShaderhandle{ -1 };
int m_PixelShaderhandle{ -1 };
//シェーダーに渡す追加パラメーターを配するハンドル
std::array<int, 4> m_VertexShadercbhandle{ -1 };
int m_PixelShaderSendDispSizeHandle{ -1 };
std::array<int, 4> m_PixelShadercbhandle{ -1 };
public:
ShaderUseClass() {
//シェーダーハンドル
m_VertexShaderhandle = -1;
m_PixelShaderhandle = -1;
//シェーダーに渡す追加パラメーターを配するハンドル
for (auto& h : m_VertexShadercbhandle) { h = -1; }
m_PixelShaderSendDispSizeHandle = -1;
for (auto& h : m_PixelShadercbhandle) { h = -1; }
}
~ShaderUseClass() {
Dispose();
}
public:
//初期化
void Init(const char* VertexShader, const char* PixelShader) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
//頂点シェーダー周り
for (auto& h : m_VertexShadercbhandle) {
h = CreateShaderConstantBuffer(sizeof(float) * 4);
}
this->m_VertexShaderhandle = LoadVertexShader(VertexShader); // 頂点シェーダーバイナリコードの読み込み
//ピクセルシェーダ―周り
this->m_PixelShaderSendDispSizeHandle = CreateShaderConstantBuffer(sizeof(float) * 4);
for (auto& h : m_PixelShadercbhandle) {
h = CreateShaderConstantBuffer(sizeof(float) * 4);
}
this->m_PixelShaderhandle = LoadPixelShader(PixelShader); // ピクセルシェーダーバイナリコードの読み込み
}
//後始末
void Dispose() noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
//頂点シェーダー周り
for (auto& h : m_VertexShadercbhandle) {
DeleteShaderConstantBuffer(h);
}
DeleteShader(this->m_VertexShaderhandle);
//ピクセルシェーダ―周り
DeleteShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle);
for (auto& h : m_PixelShadercbhandle) {
DeleteShaderConstantBuffer(h);
}
DeleteShader(this->m_PixelShaderhandle);
}
public:
//頂点シェーダ―のSlot番目のレジスタに情報をセット(Slot>=4)
void SetVertexParam(int Slot, float param1, float param2, float param3, float param4) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT4* f4 = (FLOAT4 *)GetBufferShaderConstantBuffer(this->m_VertexShadercbhandle[0]); // 頂点シェーダー用の定数バッファのアドレスを取得
f4->x = param1;
f4->y = param2;
f4->z = param3;
f4->w = param4;
UpdateShaderConstantBuffer(this->m_VertexShadercbhandle[0]); // 頂点シェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_VertexShadercbhandle[0], DX_SHADERTYPE_VERTEX, Slot); // 頂点シェーダーの定数バッファを定数バッファレジスタ4にセット
}
//ピクセルシェーダ―の2番目のレジスタに画面サイズの情報をセット
void SetPixelDispSize(int dispx, int dispy) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT2* dispsize = (FLOAT2*)GetBufferShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle); // ピクセルシェーダー用の定数バッファのアドレスを取得
dispsize->u = (float)dispx;
dispsize->v = (float)dispy;
UpdateShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle); // ピクセルシェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_PixelShaderSendDispSizeHandle, DX_SHADERTYPE_PIXEL, 2); // ピクセルシェーダー用の定数バッファを定数バッファレジスタ2にセット
}
//ピクセルシェーダ―のSlot番目のレジスタに情報をセット(Slot>=3)
void SetPixelParam(int Slot, float param1, float param2, float param3, float param4) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
FLOAT4* f4 = (FLOAT4*)GetBufferShaderConstantBuffer(this->m_PixelShadercbhandle[0]); // ピクセルシェーダー用の定数バッファのアドレスを取得
f4->x = param1;
f4->y = param2;
f4->z = param3;
f4->w = param4;
UpdateShaderConstantBuffer(this->m_PixelShadercbhandle[0]); // ピクセルシェーダー用の定数バッファを更新して書き込んだ内容を反映する
SetShaderConstantBuffer(this->m_PixelShadercbhandle[0], DX_SHADERTYPE_PIXEL, Slot); // ピクセルシェーダー用の定数バッファを定数バッファレジスタ3にセット
}
//3D空間に適用する場合の関数(引数に3D描画のラムダ式を代入)
void Draw_lamda(std::function<void()> doing) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) {
doing();
return;
}
SetUseVertexShader(this->m_VertexShaderhandle); // 使用する頂点シェーダーをセット
SetUsePixelShader(this->m_PixelShaderhandle); // 使用するピクセルシェーダーをセット
MV1SetUseOrigShader(TRUE);
doing();
MV1SetUseOrigShader(FALSE);
SetUseVertexShader(-1);
SetUsePixelShader(-1);
}
//2D画像に適用する場合の関数
void Draw(ScreenVertex& Screenvertex) noexcept {
if (GetUseDirect3DVersion() != DX_DIRECT3D_11) { return; }
Draw_lamda([&] {DrawPolygon3DToShader(Screenvertex.GetScreenVertex(), 2); });
}
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
static const int EXTEND = 4;
ShaderUseClass::ScreenVertex m_ScreenVertex; // 頂点データ
ShaderUseClass m_Shader2D; // 使用するシェーダー
float XPos = 0.f;
float ZPos = -1.92f;
int RayInterval = 50;//レイの分割間隔
float SSRScale = 1.f;
float DepthThreshold = 8.f;
int ModelHandle;
int NormalScreenHandle;
int DepthScreenHandle;
int ColorScreenHandle;
int AberrationScreen;
int OutputScreenHandle;
// ウインドウモードで起動
ChangeWindowMode(TRUE);
// 画面解像度を設定
SetGraphMode(SCREEN_W, SCREEN_H, 32);
SetWaitVSyncFlag(FALSE);
// DXライブラリの初期化
if (DxLib_Init() < 0) return -1;
m_ScreenVertex.SetScreenVertex(SCREEN_W, SCREEN_H); // 頂点データの準備
m_Shader2D.Init("shader/VS_SSR.vso", "shader/PS_SSR.pso"); // レンズ
ModelHandle = MV1LoadModel("SSAO_Stage.mqo"); // 3Dモデルの読み込み
{
SetCreateGraphChannelBitDepth(24);
ColorScreenHandle = MakeScreen(SCREEN_W, SCREEN_H, FALSE); // カラーバッファの作成
NormalScreenHandle = MakeScreen(SCREEN_W, SCREEN_H, TRUE); // 法線バッファの作成
SetDrawValidFloatTypeGraphCreateFlag(TRUE);
SetCreateDrawValidGraphChannelNum(2);
SetCreateGraphChannelBitDepth(32);
DepthScreenHandle = MakeScreen(SCREEN_W, SCREEN_H, TRUE); // 法線バッファの作成
SetCreateGraphChannelBitDepth(24);
SetCreateDrawValidGraphChannelNum(4);
SetDrawValidFloatTypeGraphCreateFlag(FALSE);
}
AberrationScreen = MakeScreen(SCREEN_W / EXTEND, SCREEN_H / EXTEND, TRUE); // 描画スクリーン
OutputScreenHandle = MakeScreen(SCREEN_W, SCREEN_H, TRUE); // 出力先バッファの作成
// ライトの設定
SetLightEnable(FALSE);
//SetGlobalAmbientLight(GetColorF(0.01f, 0.01f, 0.01f, 0.0f));
int Light0Handle = CreatePointLightHandle(VGet(0.000f, 0.000f, 0.000f), 20.000f, 1.000f, 0.f, 0.000f);
SetLightDifColorHandle(Light0Handle, GetColorF(1.000f, 1.000f, 1.000f, 1.000f));
SetLightSpcColorHandle(Light0Handle, GetColorF(0.500f, 0.500f, 0.500f, 0.000f));
SetLightAmbColorHandle(Light0Handle, GetColorF(0.000f, 0.000f, 0.000f, 0.000f));
SetUsePixelLighting(TRUE); // ピクセル単位のライティングを行う描画モードに設定する
// メインループ
float DrawTimer = 0.f;
while (ProcessMessage() == 0) {
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) { break; }
//
float fov_cam = 60.f * DX_PI_F / 180.0f;
//もとになる画面とGバッファを描画
{
// カラーバッファを描画対象0に、法線バッファを描画対象1、深度+光沢グレースケールバッファを描画対象2、光沢RGBバッファを描画対象3に設定
SetRenderTargetToShader(0, ColorScreenHandle);
SetRenderTargetToShader(1, NormalScreenHandle);
SetRenderTargetToShader(2, DepthScreenHandle);
ClearDrawScreen(); // 画面のクリア
// カメラを設定
SetCameraPositionAndTarget_UpVecY(VGet(XPos, 0.f, ZPos), VGet(0.f, 0.f, 0.f));
SetCameraNearFar(0.1f, 300.f);
SetupCamera_Perspective(fov_cam);
{
MV1DrawModel(ModelHandle); // モデルの描画
}
// 描画対象をすべて解除
SetRenderTargetToShader(0, -1);
SetRenderTargetToShader(1, -1);
SetRenderTargetToShader(2, -1);
}
//SSRシェーダーを適用
SetDrawScreen(OutputScreenHandle);
{
SetUseTextureToShader(0, ColorScreenHandle); //使用するテクスチャをセット
SetUseTextureToShader(1, NormalScreenHandle);
SetUseTextureToShader(2, DepthScreenHandle);
m_Shader2D.SetPixelDispSize(SCREEN_W, SCREEN_H);
m_Shader2D.SetPixelParam(3, (float)RayInterval, SSRScale, std::tan(fov_cam / 2.f), DepthThreshold);
m_Shader2D.Draw(m_ScreenVertex);
SetUseTextureToShader(1, -1);
SetUseTextureToShader(2, -1);
SetUseTextureToShader(0, -1);
}
//SSRシェーダーにぼかしを入れる
{
GraphFilterBlt(OutputScreenHandle, AberrationScreen, DX_GRAPH_FILTER_DOWN_SCALE, EXTEND);
GraphFilter(AberrationScreen, DX_GRAPH_FILTER_GAUSS, 8, 500);
}
//
SetDrawScreen(DX_SCREEN_BACK); //使用するテクスチャをセット
{
DrawBox(0, 0, SCREEN_W, SCREEN_H, GetColor(0, 0, 0), TRUE);
DrawExtendGraph(0, 0, SCREEN_W, SCREEN_H, ColorScreenHandle, TRUE);
//DrawExtendGraph(0, 0, SCREEN_W, SCREEN_H, DepthScreenHandle, TRUE);
DrawExtendGraph(0, 0, SCREEN_W, SCREEN_H, AberrationScreen, TRUE);
// パラメータを画面に表示
DrawFormatString(0, 32 * 0, GetColor(255, 255, 255), "W/S/A/D key (%6.2f,%6.2f,%6.2f) : カメラ座標", XPos,0.f,ZPos);
DrawFormatString(0, 32 * 1, GetColor(255, 255, 255), "R/F key %6.2f : 画面に映っていない部分をレイマーチングするための閾値(深度の差がこれ以上であれば無視)", DepthThreshold);
DrawFormatString(0, 32 * 2, GetColor(255, 255, 255), "T/G key %6d : レイの分割間隔", RayInterval);
DrawFormatString(0, 32 * 3, GetColor(255, 255, 255), "Y/H key %6.2f : スケーリング(MMD等1m=1.0でない場合に使用,精度を下げる代わりにより遠くまで反射演算できるようになります)", SSRScale);
DrawFormatString(0, 32 * 4, GetColor(255, 255, 255), "FPS %6.2f : FPS", GetFPS());
DrawFormatString(0, 32 * 5, GetColor(255, 255, 255), "DrawTime(ms) %6.2f : 描画時間", DrawTimer);
DrawFormatString(0, 32 * 6, GetColor(255, 255, 255), "P SSR画面のみ表示");
DrawFormatString(0, 32 * 7, GetColor(255, 255, 255), "Esc 終了");
}
//高設定
if (CheckHitKey(KEY_INPUT_1) != 0) {
RayInterval = 50;
DepthThreshold = 8.0f;
}
//低設定
if (CheckHitKey(KEY_INPUT_2) != 0) {
RayInterval = 30;
DepthThreshold = 16.5f;
}
// キーによるパラメータの操作
switch (GetInputChar(TRUE)) {
case 'a': XPos -= 0.3f; break;
case 'd': XPos += 0.3f; break;
case 'w': ZPos += 0.3f; break;
case 's': ZPos -= 0.3f; break;
case 'r': DepthThreshold += 0.1f; break;
case 'f': DepthThreshold -= 0.1f; break;
case 't': RayInterval += 1; break;
case 'g': RayInterval -= 1; break;
case 'y': SSRScale += 0.1f; break;
case 'h': SSRScale -= 0.1f; break;
default:break;
}
if (DepthThreshold <= 0.f) { DepthThreshold = 0.1f; }
if (RayInterval <= 0) { RayInterval = 1; }
if (SSRScale <= 0.f) { SSRScale = 0.1f; }
LONGLONG StartTime = GetNowHiPerformanceCount(); //ScreenFlip前に現在の時間を取る
ScreenFlip(); // 裏画面の内容を表画面に反映
DrawTimer = (float)(GetNowHiPerformanceCount() - StartTime) / 1000.f;//ScreenFlip出かかった時間をミリ秒単位で取得
}
// DXライブラリの後始末
DxLib_End();
// ソフトの終了
return 0;
}
おわり
こんな感じでほぼほデフォルトの描画機能を損なうことなくSSRを実装することができました。皆さんも遅延シェーディングに取り組みましょう!