void URenderer::RenderWorldGrid()
{
    PrepareWorldGrid();
    
    AActor* CameraActor = FEditorManager::Get().GetCamera();
    if (CameraActor == nullptr)
    {
        return;
    }
    
    float GridGap = UEngine::Get().GetWorldGridGap();

    FTransform CameraTransform = CameraActor->GetActorTransform();
    FVector CameraLocation = CameraTransform.GetPosition();

    int32 StepX = static_cast<int32>(CameraLocation.X / GridGap);
    int32 StepY = static_cast<int32>(CameraLocation.Y / GridGap);

    FVector GridOffset(StepX * GridGap, StepY * GridGap, 0.f);
    FVector GridScale(GridGap, GridGap, 1.f);

    FTransform GridTransform(GridOffset, FVector::ZeroVector, GridScale);
    
    ConstantUpdateInfo UpdateInfo
    {
        GridTransform.GetMatrix(),
        FVector4(0.2f, 0.2f, 0.2f, 1.0f),
        false,
    };

    UpdateObjectConstantBuffer(UpdateInfo);

    DeviceContext->Draw(GridVertexNum, 0);

    // restore
    DeviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    // 나머지는 PrepareMainShader에서 작업중이므로, 생략
}

GridGap 값에 따라 스냅하며 따라다니기 때문에 카메라에 고정된 것이 아닌 월드에 고정된 듯 보인다.


쉐이더

////////
/// Input, Output
////////
struct VS_INPUT
{
    float3 Position : POSITION; // Input position from vertex buffer
};

struct PS_INPUT
{
    float4 Position : SV_POSITION; // Transformed position to pass to the pixel shader
    float4 Color : COLOR;          // Color to pass to the pixel shader
    float4 WorldPosition : POSITION;
};

////////
/// Function
////////
PS_INPUT mainVS(VS_INPUT input)
{
    PS_INPUT output;
    output.Position = float4(input.Position.xyz, 1.0f);
    output.Position = mul(output.Position, WorldMatrix);
    output.WorldPosition = output.Position;
    output.Position = mul(output.Position, ViewMatrix);
    output.Position = mul(output.Position, ProjectionMatrix);

    output.Color = CustomColor;
    return output;
}

float4 mainPS(PS_INPUT input) : SV_TARGET
{
    float Dist = length(input.WorldPosition.xyz - ViewPosition);

    float MaxDist = FarClip * 1.2f;
    float MinDist = MaxDist * 0.5f;

    // Fade out grid
    float Fade = saturate(1.f - (Dist - MinDist) / (MaxDist - MinDist));
    input.Color.a *= Fade * Fade * Fade;
    
    return input.Color;
}

WorldPosition 은 그리드 정점의 월드 포지션으로, 부드럽게 Fade out 되는 효과를 위해 버텍스 쉐이더에서 계산한다.

버텍스 쉐이더에서 계산된 정점 정보는 레스터라이저를 거치면서 선형 보간되어, 두 정점 사이의 Fade out 효과가 부드럽게 이어지게 된다.

Fade out은 색상의 알파 값을 조정하여 투명해지도록 설정해야하므로, 별도의 블렌드 스테이트를 추가했다.

D3D11_BLEND_DESC BlendState;
ZeroMemory(&BlendState, sizeof(D3D11_BLEND_DESC));
BlendState.RenderTarget[0].BlendEnable = TRUE;
BlendState.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;  // 소스 색상: 알파값 사용
BlendState.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; // 대상 색상: (1 - 알파)
BlendState.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
BlendState.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;   // 알파값 유지
BlendState.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
BlendState.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
BlendState.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
Device->CreateBlendState(&BlendState, &GridBlendState);