【ゲーム制作】No.12: 無限平面の描画(Direct3D11)

今回は無限平面の描画エフェクトの実装になります。
以下みたいな実装のことですね。

 

自分が勝手に思っているだけなんですが、
無限平面の描画というのは、
ゲームプログラミングにおいて必ず通る道みたいなものであり
ただ実装するには必要となる数学も結構必要で
手ごわい中ボスって感じです。
これをこなすことでゲームプログラマーの四天王最弱になれる!!って
感じです。皆さんも是非試してください。

 

実装するにあたっての資料をネット上から探したところ、
有名どころとして以下の3点がありました。

 

①[Unity]無限平面を描画する過程でGPUの理解を深める

日本語で図も複数あり、丁寧に説明してあります。
ただ、環境がUnityだったので、デバッグ環境が無く
Unity専用の関数UnityWorldToClipPos()やTRANSFORM_TEXマクロ
なども複数使われており、チャレンジしてみましたが

私の技術が足りず、実装までは至りませんでした。

 

②【Unity道場 2月】シェーダを書けるプログラマになろう

これも技術的には①と同様。より説明メインで
とても勉強にはなりましたが、これで実装はちょっと無理でした。

 

③How to make an infinite grid.

英語ですが、説明が最小限でシンプル、1ステップ1ステップ
丁寧に説明されているのでわかりやすいです。
GLSLなのが残念でしたが、私は今回この資料で実装しました。
調べてみると多くの人が参考にしているようで
大人気(?)だと思いました。

以下にフルソースもあるので参考にしてください。
内容も100%同じではなく多少改善しているみたいです。

 

実際の実装は以下です。
Direct3D11への移植は、特に問題なくストレートに移植出来た様に

思います。

ソース全てのアップは難しいのでエフェクトファイルのみ

載せておきます。

--------------------------------------------

infinite_plane_effect.fx

--------------------------------------------

#include "common_definition.hlsl"
#include "math.hlsl"

//--------------------------------------------------------------------------------------
// Constant buffers
//--------------------------------------------------------------------------------------
cbuffer cb_view_transform
{
    float4x4_t    g_mView;
    float4x4_t    g_mProj;
    float4x4_t    g_m_inv_view;
    float4x4_t    g_m_inv_proj;
};

static const float3_t g_grid_plane[6] = 
{
    float3_t(1, 1, 0),   float3_t(-1, -1, 0), float3_t(-1, 1, 0),
    float3_t(-1, -1, 0), float3_t(1, 1, 0),   float3_t(1, -1, 0)
};

static const float_t x_axis_width    = 10.0;
static const float_t z_axis_width    = 10.0;
static const float_t near_plane    = 0.1;
static const float_t far_plane    = 100;
static const float_t plane_width_scale_0    = 0.1;        // plane四角形のサイズを指定。0.1がデフォルト。0.1→0.05で四角形のサイズが増える。;
static const float_t plane_width_scale_1    = 0.1;        // グリッド線の中のテクスチャ個数を指定。0.1がデフォルト。0.1→0.05でテクスチャ個数が増える。;
static const float_t plane_color_intensity    = 0.75;    // テクスチャ画像の色の濃さを指定。0.75がデフォルト。0.75→0.5→0.25で白→黒;

//--------------------------------------------------------------------------------------
// State
//--------------------------------------------------------------------------------------
RasterizerState g_state_render_solid
{
    FillMode                = SOLID;
    CullMode                = NONE;
    FrontCounterClockwise    = FALSE;
    DepthBias                = 0;
    SlopeScaledDepthBias    = 0.0f; 
    DepthBiasClamp            = 0.0f;
    DepthClipEnable            = FALSE; 
    ScissorEnable            = FALSE;
    MultisampleEnable        = FALSE;
    AntialiasedLineEnable    = FALSE;
};

BlendState g_state_no_blend
{
    AlphaToCoverageEnable        = FALSE;
    BlendEnable[0]                = FALSE;
    SrcBlend[0]                    = ONE;
    DestBlend[0]                = ZERO;
    BlendOp[0]                    = ADD;
    SrcBlendAlpha[0]            = ONE;
    DestBlendAlpha[0]            = ZERO;
    BlendOpAlpha[0]                = ADD;
    RenderTargetWriteMask[0]    = 0x0f;
    BlendEnable[1]                = FALSE;
    RenderTargetWriteMask[1]    = 0x00;
};

BlendState g_state_blend_alphablend_on
{
    AlphaToCoverageEnable        = FALSE;
    BlendEnable[0]                = TRUE;
    SrcBlend[0]                    = SRC_ALPHA;
    DestBlend[0]                = INV_SRC_ALPHA;
    BlendOp[0]                    = ADD;
    SrcBlendAlpha[0]            = ONE;
    DestBlendAlpha[0]            = ONE;
    BlendOpAlpha[0]                = ADD;
    RenderTargetWriteMask[0]    = 0x0f;
    BlendEnable[1]                = FALSE;
    RenderTargetWriteMask[1]    = 0x00;
};

DepthStencilState g_state_disable_depth
{
    DepthEnable        = FALSE;
    StencilEnable    = FALSE;
};

DepthStencilState g_state_enable_depth
{
    DepthEnable        = TRUE;
    DepthWriteMask    = ALL;
    DepthFunc        = LESS;
    StencilEnable    = FALSE;
};

//--------------------------------------------------------------------------------------
// Textures
//--------------------------------------------------------------------------------------
Texture2D    g_tex_decal;

//--------------------------------------------------------------------------------------
// Texture samplers
//--------------------------------------------------------------------------------------
SamplerState g_sam_linear
{
    Filter = ANISOTROPIC;
    AddressU = WRAP;
    AddressV = WRAP;
    //AddressU = Border;
    //AddressV = Border;
    //BorderColor    = float4_t(1,1,1,1);
};

struct draw_infinite_grid__VS_input_t
{
    float4_t    position    : POSITION;
    uint_t        vertex_id    : SV_VertexID;
};

struct draw_infinite_grid__VS_output_t
{
    float4_t    vertex_position_inWVP    : SV_POSITION;
    float3_t    near_point                : TEXCOORD0;
    float3_t    far_point                : TEXCOORD1;
};

//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
float3_t unproject_point(    const in float_t    x,
                            const in float_t    y,
                            const in float_t    z,
                            const in float4x4_t    inv_view,
                            const in float4x4_t    inv_proj)
{
    const float4x4_t    inv_view_inv_proj    = mul(inv_proj, inv_view);
    const float4_t        unprojected_point    = mul( float4_t(x, y, z, 1.0), inv_view_inv_proj);
    return unprojected_point.xyz / unprojected_point.w;
}

draw_infinite_grid__VS_output_t VS_main( const in    draw_infinite_grid__VS_input_t input)
{
    draw_infinite_grid__VS_output_t output = (draw_infinite_grid__VS_output_t)0;

    float3_t    p        = g_grid_plane[input.vertex_id].xyz;
    output.near_point    = unproject_point(p.x, p.y, 0.0, g_m_inv_view, g_m_inv_proj).xyz; // unprojecting on the near plane
    output.far_point    = unproject_point(p.x, p.y, 1.0, g_m_inv_view, g_m_inv_proj).xyz; // unprojecting on the far plane
    output.vertex_position_inWVP    = float4_t(p, 1.0);

    return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------


float4_t    grid(const in float3_t fragPos3D, const in float_t scale)
{
    const float2_t    coord        = fragPos3D.xz * scale; // use the scale variable to set the distance between the lines
    const float2_t    derivative    = fwidth(coord);
    const float2_t    grid        = abs( frac(coord - 0.5) - 0.5) / derivative;
    const float_t    line_        = min(grid.x, grid.y);
    const float_t    minimumz    = min(derivative.y, 1);
    const float_t    minimumx    = min(derivative.x, 1);
    float4_t        color        = float4_t(0.2, 0.2, 0.2, 1.0 - min(line_, 1.0));
    // z axis
    if(fragPos3D.x > -z_axis_width * minimumx && fragPos3D.x < z_axis_width * minimumx)
        color.z = 1.0;
    // x axis
    if(fragPos3D.z > -z_axis_width * minimumz && fragPos3D.z < z_axis_width * minimumz)
        color.x = 1.0;
    
    return color;
}

float_t computeDepth(const in float3_t pos)
{
    const float4x4_t    view_proj    = mul(g_mView, g_mProj);

    const float4_t        unprojected_point    = mul( float4_t(pos.xyz, 1.0), view_proj);
    return (unprojected_point.z / unprojected_point.w);
}

float_t computeLinearDepth(const in float3_t pos)
{
    const float4x4_t    view_proj    = mul(g_mView, g_mProj);
    const float4_t        clip_space_pos    = mul( float4_t(pos.xyz, 1.0), view_proj);

    float_t clip_space_depth    = (clip_space_pos.z / clip_space_pos.w) * 2.0 - 1.0; // put back between -1 and 1
    float_t linearDepth            = (2.0 * near_plane * far_plane) / (far_plane + near_plane - clip_space_depth * (far_plane - near_plane)); // get linear value between 0.01 and 100
    return linearDepth / far_plane; // normalize
}

//[earlydepthstencil]
float4_t PS_main( draw_infinite_grid__VS_output_t input, out float_t depth : SV_Depth) : SV_Target
//float4_t PS_main( draw_infinite_grid__VS_output_t input) : SV_Target
{
    //// 「 Step 3 : Draw the plane when it intersects the floor 」の描画
    //const float_t    t            = -input.near_point.y / (input.far_point.y - input.near_point.y);
    //return float4_t(1.0, 0.0, 0.0, 1.0 * float(t > 0)); // opacity = 1 when t > 0, opacity = 0 otherwise

    //// 「 Step 4 : Draw the actual grid 」+ 「 Step 5 : Output depth 」の描画
    //const float_t    t            = -input.near_point.y / (input.far_point.y - input.near_point.y);
    //const float3_t    fragPos3D    = input.near_point + t * (input.far_point - input.near_point);
    //depth    = computeDepth(fragPos3D);
    //return grid(fragPos3D, plane_width_scale_1) * float(t > 0);

    //// 「 Step 4 : Draw the actual grid 」+ 「 Step 5 : Output depth 」+ テクスチャの描画
    //const float_t    t            = -input.near_point.y / (input.far_point.y - input.near_point.y);
    //const float3_t    fragPos3D    = input.near_point + t * (input.far_point - input.near_point);
    //const float4_t    tex_decal    = g_tex_decal.Sample( g_sam_linear, fragPos3D.xz * float2_t(plane_width_scale_0,-plane_width_scale_0));
    //depth    = computeDepth(fragPos3D);
    //return (tex_decal * plane_color_intensity + grid(fragPos3D, plane_width_scale_1)) * float(t > 0);

    //「 Step 6 : Fade out the grid 」+ テクスチャの描画
    const float_t    t            = -input.near_point.y / (input.far_point.y - input.near_point.y);
    const float3_t    fragPos3D    = input.near_point + t * (input.far_point - input.near_point);
    const float4_t    tex_decal    = g_tex_decal.Sample( g_sam_linear, fragPos3D.xz * float2_t(plane_width_scale_0,-plane_width_scale_0));
    depth    = computeDepth(fragPos3D);
    float_t linearDepth = computeLinearDepth(fragPos3D);
    float_t fading = max(0, (0.5 - linearDepth));

    float4_t    outColor;
    outColor = (tex_decal * plane_color_intensity + grid(fragPos3D, plane_width_scale_1)) * float(t > 0); // adding multiple resolution for the grid
    outColor.a *= fading;
    return outColor;
}

VertexShader    g_VS_main    = CompileShader(vs_5_0, VS_main());
PixelShader        g_PS_main    = CompileShader(ps_5_0, PS_main());

//--------------------------------------------------------------------------------------
// technique
//--------------------------------------------------------------------------------------
technique11 t0
{
    pass p0
    {
        SetVertexShader( g_VS_main);
        SetHullShader(NULL);
        SetDomainShader(NULL);
        SetGeometryShader( NULL);
        SetPixelShader( g_PS_main);
        //SetBlendState( g_state_no_blend, float4_t(0,0,0,0), 0xFFFFFFFF);
        SetBlendState( g_state_blend_alphablend_on, float4_t(0,0,0,0), 0xFFFFFFFF);
        SetDepthStencilState( g_state_enable_depth, 0);
        SetRasterizerState( g_state_render_solid);
    }
}

--------------------------------------------

 

最後に③ですが、テクスチャ画像の貼り付けが無かったので
その実装を載せておきます。

 

    static const float_t plane_width_scale_0    = 0.1;        // plane四角形のサイズを指定。0.1がデフォルト。0.1→0.05で四角形のサイズが増える。;
    static const float_t plane_width_scale_1    = 0.1;        // グリッド線の中のテクスチャ個数を指定。0.1がデフォルト。0.1→0.05でテクスチャ個数が増える。;
    const float4_t    tex_decal    = g_tex_decal.Sample( g_sam_linear, fragPos3D.xz * float2_t(plane_width_scale_0,-plane_width_scale_0));
    return (tex_decal * plane_color_intensity + grid(fragPos3D, plane_width_scale_1)) * float(t > 0);