본문 바로가기
공부/DirectX3D

[DirectX3D] 눈내리는 효과(파티클효과 응용)

by MY블로그 2023. 8. 29.

 

DirectX11 3D 상수버퍼, 쉐이더를 사용한 눈내리는 효과

기능 구현에 필요한 코드외 생략 하도록 합니다.

 

구현된 기능은 카메라의 시점에 고정되어져 RTT, PostEffect 처럼 렌더에 적용되는 효과가아닙니다.

3D 공간에 정해진 범위 내에 효과가 적용됩니다.

 

단, 효과에 사용되는 이미지들은 BillBoard Rendering과 같은 방식으로 카메라가 바라보는 방향으로 정면이 위치하기 때문에 어느방향에서 보아도 같은 이미지로 보이게 되어있습니다.

환경효과(눈, 비, 흩날림...)를 적용하기 위한 클래스 생성 (접은글 참조)

더보기
// Rain.h
#pragma once
class Particle
{
protected:

    //현재 재생중인 시간
    float playTime = 0.0f;
    //재생중인가?
    bool isPlaying = false;
public:
    //총 재생시간
    float duration = 1.0f;
    virtual void    Play()
    {
        playTime = 0.0f;
        isPlaying = true;
    };
    virtual void    Stop()
    {
        isPlaying = false;
    };
    void            UpdateParticle();
    void            Gui();
    float           PlayTime() { return playTime / duration; } //재생시간 비율
    bool            IsPlaying() { return isPlaying; } //재생중인가?
};

//상수버퍼
struct RAIN_DESC
{
    Vector3 velocity;
    float padding;

    Vector3 range;
    float time;
    RAIN_DESC()
    {
        velocity = Vector3(0, -1, 0); // 기본방향 아래
        range = Vector3(500, 500, 500); // 기본범위
        time = 0.0f;
    }
};

class Rain : public Actor, public Particle
{
    static ID3D11Buffer* RainBuffer;
public:
    RAIN_DESC desc;
    Vector2             particleScale = Vector2(0, 0); //이미지 크기값
    int                 particleCount = 10;

    static void         CreateStaticMember();
    static void         DeleteStaticMember();
    static Rain*        Create(string name = "Rain");
    virtual void        Render();
    virtual void        Update();
    void                RenderDetail();
    void                Reset();
    virtual void        Play();
    virtual void        Stop();
};


// Rain.cpp ( Gui 관련 제외 )
#include "framework.h"

void Particle::UpdateParticle()
{
	if (isPlaying)
	{
		playTime += DELTA;
		if (playTime > duration)
		{
			Stop();
		}
	}
}

void Particle::Gui()
{
	if (ImGui::Button("Play"))
	{
		Play();
	}
	ImGui::SameLine();
	if (ImGui::Button("Stop"))
	{
		Stop();
	}
	//현재 재생 시간
	ImGui::Text("Playtime : %f", PlayTime());
	//총 재생할 시간
	ImGui::SliderFloat("duration", &duration, 0.0f, 100.0f);
}
ID3D11Buffer* Rain::RainBuffer = nullptr;
void Rain::CreateStaticMember()
{
	{
		D3D11_BUFFER_DESC desc = { 0 };
		desc.ByteWidth = sizeof(RAIN_DESC);
		desc.Usage = D3D11_USAGE_DYNAMIC;
		desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;//상수버퍼
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		desc.MiscFlags = 0;
		desc.StructureByteStride = 0;
		HRESULT hr = D3D->GetDevice()->CreateBuffer(&desc, NULL, &RainBuffer);
		assert(SUCCEEDED(hr));

	}
}

void Rain::DeleteStaticMember()
{
	SafeRelease(RainBuffer);
}

Rain* Rain::Create(string name)
{
	Rain* temp = new Rain();
	temp->name = name;

	temp->mesh = make_shared<Mesh>();
	temp->mesh->LoadFile("7.Billboard.mesh");
	temp->shader = RESOURCE->shaders.Load("7.Rain.hlsl");
	temp->shader->LoadGeometry();
	temp->type = ObType::Rain;
	//temp->visible = false;

	return temp;
}

void Rain::Render()
{
	desc.time = TIMER->GetWorldTime();
	{
		D3D11_MAPPED_SUBRESOURCE mappedResource;
		D3D->GetDC()->Map(RainBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
		memcpy_s(mappedResource.pData, sizeof(RAIN_DESC), &desc,
			sizeof(RAIN_DESC));
		D3D->GetDC()->Unmap(RainBuffer, 0);
		D3D->GetDC()->VSSetConstantBuffers(10, 1, &RainBuffer);
	}
	if (isPlaying)
		Actor::Render();
}

void Rain::Update()
{
	Particle::UpdateParticle();
	Actor::Update();
}



void Rain::Reset()
{
	delete[](VertexPS*)mesh->vertices;
	delete[] mesh->indices;
	mesh->vertices = new VertexPS[particleCount];
	mesh->indices = new UINT[particleCount];
	mesh->vertexCount = particleCount;
	mesh->indexCount = particleCount;

	Vector2 scale;


	for (UINT i = 0; i < particleCount; i++)
	{
		//이미지 크기 가로세로를 랜덤값
		//4~8 사이값

		//오차값
		scale.x = RANDOM->Float(-particleScale.x, particleScale.x);
		scale.y = RANDOM->Float(-particleScale.y, particleScale.y);
		scale.x = S._11 + scale.x;
		scale.y = S._22 + scale.y;
		if (scale.x < 1.0f)scale.x = 1.0f;
		if (scale.y < 1.0f)scale.y = 1.0f;

		Vector3 position;
		//생성될위치   //-4~8   ~ 4~ 8
		position.x = RANDOM->Float(-desc.range.x, desc.range.x);
		position.y = RANDOM->Float(-desc.range.y, desc.range.y);
		position.z = RANDOM->Float(-desc.range.z, desc.range.z);

		((VertexPS*)mesh->vertices)[i].position = position;
		((VertexPS*)mesh->vertices)[i].size = scale;
		mesh->indices[i] = i;
	}
	SafeRelease(mesh->vertexBuffer);
	SafeRelease(mesh->indexBuffer);

	//CreateVertexBuffer
	{
		D3D11_BUFFER_DESC desc;
		desc = { 0 };
		desc.Usage = D3D11_USAGE_DEFAULT;
		desc.ByteWidth = sizeof(VertexPS) * particleCount;
		desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

		D3D11_SUBRESOURCE_DATA data = { 0 };
		data.pSysMem = mesh->vertices;

		HRESULT hr = D3D->GetDevice()->CreateBuffer(&desc, &data, &mesh->vertexBuffer);
		assert(SUCCEEDED(hr));
	}

	//Create Index Buffer
	{
		D3D11_BUFFER_DESC desc;
		ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
		desc.ByteWidth = sizeof(UINT) * particleCount;
		desc.BindFlags = D3D11_BIND_INDEX_BUFFER;

		D3D11_SUBRESOURCE_DATA data = { 0 };
		data.pSysMem = mesh->indices;

		HRESULT hr = D3D->GetDevice()->CreateBuffer(&desc, &data, &mesh->indexBuffer);
		assert(SUCCEEDED(hr));
	}
}

void Rain::Play()
{
	Reset();
	Particle::Play();
}

void Rain::Stop()
{
	Particle::Stop();
}

Mesh, Shader 생성 ( Shader 파일 접은글 참조 )

Mesh 는 프레임워크에 따라 다르며, 기본 1개의 정점으로되어있는 메쉬를 사용합니다.

더보기
// Rain 클래스에 사용할 Shader
#include "Common.hlsl"

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

cbuffer VS_Data : register(b10) // 상수버퍼
{
    float3 velocity;
    float padding;
    
    float3 range;
    float time;
}
VertexInput VS(VertexInput input)
{
    VertexInput output;
    
    float3 displace = time * velocity;
    
    //                       중심 위치
    output.Position.xyz = World._41_42_43 +
    
    (range.xyz + (input.Position.xyz + displace.xyz) % range.xyz) 
    % range.xyz - (range.xyz * 0.5f);
    
	output.Position.w = 1.0f;
	output.Position = mul(output.Position, View);
    
    output.Size = input.Size;
    return output;
}

static const float2 TEXCOORD[4] =
{
    float2(0.0f, 1.0f),
    float2(0.0f, 0.0f),
    float2(1.0f, 1.0f),
    float2(1.0f, 0.0f)
};
[maxvertexcount(4)]
void GS(point VertexInput input[1], inout TriangleStream<PixelInput> output)
{
    //한개의 점을 네개로 나누기
    
    // 월드변환후 뷰 프로젝션변환
    
	float3 up = float3(0, 1, 0);
	float3 forward = float3(0, 0, 1);
	float3 right = float3(1, 0, 0);
    
    float2 halfSize = input[0].Size * 0.5f;
    
    float4 vertices[4];
    //input[0].Position.xyz (기준좌표,중점)
    
    //왼쪽 아래
   // vertices[0] = float4(input[0].Position.xyz - halfSize.x * right - halfSize.y * up, 1.0f);
    vertices[0] = float4(input[0].Position.xyz - right * halfSize.x - up * halfSize.y, 1.0f);
    // 왼 위
    vertices[1] = float4(input[0].Position.xyz - right * halfSize.x + up * halfSize.y, 1.0f);
    // 오 아래
    vertices[2] = float4(input[0].Position.xyz + right * halfSize.x - up * halfSize.y, 1.0f);
    // 오 위
    vertices[3] = float4(input[0].Position.xyz + right * halfSize.x + up * halfSize.y, 1.0f);
    
    PixelInput pixelInput;
    
    [unroll(4)]
    for (int i = 0; i < 4; i++)
    {
        //월드에서 다시 ndc까지 변환
		pixelInput.Position = mul(vertices[i], GSProj);
        pixelInput.Uv = TEXCOORD[i];
        
        output.Append(pixelInput);
    }
    
}

float4 PS(PixelInput input) : SV_TARGET
{
    
    float4 BaseColor = DiffuseMapping(input.Uv);
       
    if (BaseColor.a == 0)
        discard;
    return BaseColor;
}

조심해야 할것!

현재 Rain 클래스는 Actor, Particle 두개의 다중 상속을 받는 클래스입니다.

다중 상속의 경우 메소드의 이름이 같을경우 문제가 생길수 있기때문에 주의 해야합니다.

 

클래스에 사용할 Mesh는 RESOURCE 매니저를 통하여 관리하지 않습니다.

해당 클레스에 사용될 Mesh를 모두 같은것으로 사용하게 된다면 객체 하나의 이펙트가 재생될때에 모든 객체가 동시에 재생되기 때문입니다.

따라서 스마트포인터(make_shared<Mesh>)를 사용하여 객체마다 각자 다른 메쉬를 사용하도록 합니다.

댓글