본문 바로가기
공부/DirectX3D

[DirectX3D] Slash trail

by MY블로그 2023. 9. 4.

게임에서 공격시에 잔상이 남는 효과를 구현하도록 합니다.

Slash Trail 클래스 + 상수버퍼 + 쉐이더 를 사용한 Slash Trail 효과

효과구현을 위한 클래스 생성합니다.

(코드는 아래의 접은글참조, 코드의 내용은 프레임워크에 따라 달라질수있습니다)

더보기
// SlashTrail.h
#pragma once
class SlashTrail
{
    shared_ptr<Shader>      shader;

    vector<VertexPT>        vertices;
    ID3D11Buffer* vertexBuffer;
    D3D_PRIMITIVE_TOPOLOGY  primitiveTopology;
    VertexType              vertexType;
    UINT                    byteWidth;

    Vector3 lastTop;
    Vector3 lastBottom;
    shared_ptr<Material>      material;



    float       time;
public:
    float       interval;       //생성 간격
    int         maxTrail;       //사각형 갯수
    bool        isPlaying;
    GameObject* Top = nullptr;
    GameObject* Bottom = nullptr;
    SlashTrail();
    ~SlashTrail();
    void Play();
    void Stop();
    void Update(); //잔상메시 갱신
    void Render(); //잔상메시 렌더
    void RenderDetail();
};

// SlashTrail.cpp
#include "framework.h"

SlashTrail::SlashTrail()
{
    vertexType = VertexType::PT;
    primitiveTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    byteWidth = sizeof(VertexPT);


    shader = RESOURCE->shaders.Load("6.Trail.hlsl");
    isPlaying = false;
    time = 0.0f;
    interval = 0.1f;
    maxTrail = 10;

    material = make_shared<Material>();
}

SlashTrail::~SlashTrail()
{
}

void SlashTrail::Play()
{
    isPlaying = true;
    lastTop = Vector3();
    lastBottom = Vector3();
    time = 0.0f;
}

void SlashTrail::Stop()
{
    isPlaying = false;
    //생성했던 트레일은 삭제
    vertices.clear();
    SafeRelease(vertexBuffer);
}

void SlashTrail::Update()
{
    if (not isPlaying) return;

    if (lastTop != Vector3() and lastBottom != Vector3())
    {
        if (TIMER->GetTick(time, interval))
        {
            vertices.push_back(VertexPT(lastTop, Vector2(0, 0)));
            vertices.push_back(VertexPT(lastBottom, Vector2(0, 0)));
            vertices.push_back(VertexPT(Top->GetWorldPos(), Vector2(0, 0)));

            vertices.push_back(VertexPT(lastBottom, Vector2(0, 0)));
            vertices.push_back(VertexPT(Bottom->GetWorldPos(), Vector2(0, 0)));
            vertices.push_back(VertexPT(Top->GetWorldPos(), Vector2(0, 0)));

            //내가 최대 사각형 갯수를 넘었을때

            if (vertices.size() > maxTrail * 6)
            {
                vertices.erase(vertices.begin());
                vertices.erase(vertices.begin());
                vertices.erase(vertices.begin());
                vertices.erase(vertices.begin());
                vertices.erase(vertices.begin());
                vertices.erase(vertices.begin());
            }

            //사각형 갯수만큼 반복
            int count = vertices.size() / 6;

            for (int i = 0; i < count; i++)
            {
                vertices[i * 6 + 0].uv = Vector2((float)(count - i) / (float)count, 0);
                vertices[i * 6 + 1].uv = Vector2((float)(count - i) / (float)count, 1);
                vertices[i * 6 + 2].uv = Vector2((float)(count - i + 1) / (float)count, 0);
                vertices[i * 6 + 3].uv = Vector2((float)(count - i) / (float)count, 1);
                vertices[i * 6 + 4].uv = Vector2((float)(count - i + 1) / (float)count, 1);
                vertices[i * 6 + 5].uv = Vector2((float)(count - i + 1) / (float)count, 0);

            }

            //CreateVertexBuffer
            {
                D3D11_BUFFER_DESC desc;
                desc = { 0 };
                desc.Usage = D3D11_USAGE_DEFAULT;
                desc.ByteWidth = byteWidth * vertices.size();
                desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

                D3D11_SUBRESOURCE_DATA data = { 0 };
                data.pSysMem = &vertices[0];
                SafeRelease(vertexBuffer);
                HRESULT hr = D3D->GetDevice()->CreateBuffer(&desc, &data, &vertexBuffer);
                assert(SUCCEEDED(hr));
            }

            lastTop = Top->GetWorldPos();
            lastBottom = Bottom->GetWorldPos();
        }
    }
    else
    {
        lastTop = Top->GetWorldPos();
        lastBottom = Bottom->GetWorldPos();
    }

}

void SlashTrail::Render()
{
    if (not isPlaying) return;

    if (vertices.size() < 1) return;

    UINT offset = 0;

    D3D->GetDC()->IASetVertexBuffers(0,
        1,
        &vertexBuffer,
        &byteWidth,
        &offset);
    D3D->GetDC()->IASetPrimitiveTopology
    (primitiveTopology);
    material->Set();
    shader->Set();

    BLEND->Set(true);
    RASTER->Set(D3D11_CULL_NONE);
    D3D->GetDC()->Draw(vertices.size(), 0);
    RASTER->Set(D3D11_CULL_BACK);
    BLEND->Set(false);
}

void SlashTrail::RenderDetail()
{
    if (ImGui::Button("PLAY"))
    {
        Play();
    }
    ImGui::SameLine();
    if (ImGui::Button("STOP"))
    {
        Stop();
    }

    ImGui::SliderFloat("interval", &interval, 0.0f, 0.1);

    ImGui::SliderInt("maxTrail", &maxTrail, 1, 100);
    material->RenderDetail();
}

// hlsl 쉐이더
#include "Common.hlsl"

struct VertexInput
{
    float4 Position : POSITION0;
    float2 Uv : UV0;
};
struct PixelInput
{
    float4 Position : SV_POSITION;
    float2 Uv : UV0;
};

PixelInput VS(VertexInput input)
{
   
    PixelInput output;
    output.Uv = input.Uv;
	output.Position = mul(input.Position, ViewProj);
    return output;
}

float4 PS(PixelInput input) : SV_TARGET
{
    float4 BaseColor = float4(1, 1, 1, 1);
    if(Kd.a)
    {
        BaseColor.a = TextureD.Sample(SamplerD, input.Uv).r;
    }

    BaseColor.rgb += Kd.rgb * 2.0f - 1.0f; // 0.5가 디폴트
    BaseColor = saturate(BaseColor);
    
    return BaseColor;
}

 

잔상효과는 Actor에서 잔상이 발생할 위치 2개의 지점을 설정하여 매프레임 정점을 생성후 토폴로지를 생성합니다.

 

생성된 정점은 일정시간(interval)동안 존재할수있으며 설정한 잔상의수(maxTrail)만큼만 존재하도록 합니다.

interval이 짧고 maxTrail의 갯수가 많을수록 부드럽고 자연스러운 잔상이 생성됩니다.

 

함수를 통하여 잔상의 재생(Play)&정지(Stop)을 지정할수 있기때문에 Actor의 Animation진행중 원하는 구간내에서만 재생을 지정하기 쉽습니다.

 

Diffuse Mapping으로 이미지를 사용하여 잔상에 이미지를 출력시킬 수 있습니다.

 

영상에 사용된 3D 모델 및 Animation 출처 : mixamo

영상에 사용된 Trail 이미지

출처 : google 이미지 검색

 

댓글