프로그래밍 공부
작성일
2023. 12. 27. 16:34
작성자
WDmil
728x90

파티클 데이터 처리는 그래픽카드에서 정점을 생성하거나 표시하고, 렌더하는 데이터를 뭉치로 넘겨주어.

연산처리를 전부 GPU에서 처리하게 하는 방법이다.

 

그럼으로써, 많은 Vertice를 DrawCall을 줄이면서 출력할 수 있다.

 

주로 vector형태로 데이터를 뭉쳐서. Buffer로 넘기는 과정을 거친다.

 

자세히 살펴보자.

 

렌더링 파이프라인 상에서 있는

 

AI

VS

 

AS

TS

DS

 

GS

RS

PS

OM

 

순서에서 GS에서의 데이터 처리를 사용하는 방식을 이야기 한다.

 

지오메트리 쉐이더 는 DX10에서부터 나왔으며, AS TS DS는 DX11부터 사용할 수 있다.

 

지오메트리 쉐이더는

AS TS DS의 하위호환 급 느낌으로,

위 3개의 데이터처리를 조금씩 할 수 있다고 이해하면 된다.

 

 

쉐이더 안에서 메시를 늘릴 수 있으며, 데이터 정점을 추가할 수 있다.

 

지오메트리 쉐이더를 살펴보자.

 

#pragma once

class GeometryShader : public Shader
{
private:
    friend class Shader;

    GeometryShader(wstring file);
    ~GeometryShader();

public:
    virtual void Set() override;

private:
    ID3D11GeometryShader* shader;
};

우선, 쉐이더 객체를 생성하여 관리해준다.

 

일반적인 Shader를 생성하듯이 관리하면 된다.

#pragma once

// 중복된 쉐이더가 있으면 원래 호출된 쉐이더를 반환하도록 코딩
class Shader
{
protected:
	Shader() = default;
	virtual ~Shader();

public:
	static class VertexShader* AddVS(wstring file);
	static class PixelShader* AddPS(wstring file);
	static class ComputeShader* AddCS(wstring file);
	static class GeometryShader* AddGS(wstring file);

	static void Delete();

	virtual void Set() = 0;

protected:
	ID3DBlob* blob = nullptr;

	static unordered_map<wstring, Shader*> shaders; // 중복되는거면 있는거 반환하는 식으로
};

 

관리를 위와같이 정의한다.


#pragma once

class ParticleEditScene : public Scene
{
private:
    const UINT SIZE = 1000;

    struct ParticleData
    {
        bool isLoop = true;
        bool isAdditive = true;
        bool isBillboard = true;
        UINT count = 100;
        float duration = 1.0f;
        Vector3 minStartPos;
        Vector3 maxStartPos;
        Vector3 minVelocity = { -1, -1, -1 };
        Vector3 maxVelocity = { +1, +1, +1 };
        Vector3 minAccelation;
        Vector3 maxAccelation;
        Vector3 minStartScale = { 1, 1, 1 };
        Vector3 maxStartScale = { 1, 1, 1 };
        Vector3 minEndScale = { 1, 1, 1 };
        Vector3 maxEndScale = { 1, 1, 1 };
        float minSpeed = 1.0f;
        float maxSpeed = 3.0f;
        float minAngularVelocity = -10.0f;
        float maxAngularVelocity = +10.0f;
        float minStartTime = 0.0f;
        float maxStartTime = 0.0f;
        Float4 startColor = { 1, 1, 1, 1 };
        Float4 endColor = { 1, 1, 1, 1 };
    };

    struct ParticleInfo
    {
        Transform transform;
        Vector3 velocity;
        Vector3 accelation;
        Vector3 startScale;
        Vector3 endScale;

        float speed = 1.0f;
        float angularVelocity = 0.0f;
        float startTime = 0.0f;
    };
public:
    ParticleEditScene();
    ~ParticleEditScene();

    virtual void Update() override;
    virtual void PreRender() override;
    virtual void Render() override;
    virtual void PostRender() override;
    virtual void GUIRender() override;

private:
    void UpdatePhysical();
    void UpdateColor();

    void Init();

    void Save(string file);
    void Load(string file);

    void EditTexture();
    void SaveDialog();
    void LoadDialog();

private:
    Quad* quad;

    vector<Matrix> instances;
    vector<ParticleInfo> particleInfos;

    VertexBuffer* instanceBuffer;

    ParticleData data;

    float lifeTime = 0.0f;
    UINT drawCount = 0;
    UINT particleCount = 100;

    BlendState* blendState[2];
    DepthStencilState* depthState[2];

    string projectPath;
};

 

위와같은 순서대로 데이터를 처리한다.

 

Quad를 기본적으로 생성하여 렌더를 처리하며, 해당 객체의 Texture를 GPU로 보내고, 인스턴싱을 작업하는 것 이 아니라,

인스턴싱 데이터를 GPU로 보내고, Random으로 생성한 좌표값을 GPU에서 알아서 출력하게 하는 방식을 사용한다.

 

인스턴싱 방식을 사용하여 파티클을 출력해도 무방하다.

 

인스턴싱과 GS의 장단점은 명확한데,

 

인스턴싱은 데이터의 실시간 처리와 이동같은 연산처리가 많아야할 때 유리하다.

 

지오메트릭쉐이더 는 데이터를 많이 생성하나, 실시간 연산처리가 크게 필요하지 않을때 사용하면 유리하다.

 

#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"

struct VertexInput
{
	float4 pos : POSITION;
	float2 size : SIZE;
	float2 random : RANOM;
};

struct GeometryInput
{
	float3 pos : POSITION0;
	float2 size : SIZE;
	matrix invView : INVVIEW;
	matrix view : VIEW;
	matrix projection : PROJECTION;
	float diatance : DISTANCE;
	float4 color : COLOR;
	float3 velocity : VELOCITY;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float2 uv : UV;
	float4 color : COLOR;
};

cbuffer SnowBuffer : register(b10)
{
	float3 velocity;
	float drawDistance;
	
	float4 color;
	
	float3 origin;
	float time;
	
	float3 size;
	float tubulence;
}

GeometryInput VS(VertexInput input)
{
	GeometryInput output;
	
	float3 displace = time * velocity;
	
	input.pos.x += cos(time - input.random.x) * tubulence;
	input.pos.z += cos(time - input.random.y) * tubulence;
	
	input.pos.xyz = origin + (size + (input.pos.xyz + displace) % size) % size - (size * 0.5f);
	
	output.velocity = velocity;
	output.diatance = drawDistance;
	output.color = color;	
	output.pos = input.pos;
	output.size = input.size;
	output.invView = invView;
	output.view = view;
	output.projection = projection;
	
	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 GeometryInput input[1], inout TriangleStream<PixelInput> output)
{
	float3 up = normalize(-input[0].velocity);
	float3 forward = normalize(input[0].pos.xyz - input[0].invView._41_42_43);
	float3 right = normalize(cross(up, forward));
	
	float2 halfSize = input[0].size * 0.5f;
	
	float4 vertices[4];
	vertices[0] = float4(input[0].pos.xyz - right * halfSize.x - up * halfSize.y, 1.0f);
	vertices[1] = float4(input[0].pos.xyz - right * halfSize.x + up * halfSize.y, 1.0f);
	vertices[2] = float4(input[0].pos.xyz + right * halfSize.x - up * halfSize.y, 1.0f);
	vertices[3] = float4(input[0].pos.xyz + right * halfSize.x + up * halfSize.y, 1.0f);
	
	PixelInput element;
	element.color = input[0].color;
	
	[unroll(4)]
	for (uint i = 0; i < 4; i++)
	{
		element.pos = mul(vertices[i], input[0].view);
		element.pos = mul(element.pos, input[0].projection);
		
		element.uv = TEXCOORD[i];
		
		element.color.a = saturate(1 - element.pos.z / input[0].diatance) * 0.5f;
		
		output.Append(element);
	}
}

float4 PS(PixelInput input) : SV_TARGET
{
	return diffuseMap.Sample(samp, input.uv) * input.color;
}

 

snow를 표현하는 지오메트릭 쉐이더이다. 원하는 Texture를 생성하여 보내주고, 개수를 Buffer로 쏴주면

해당 데이터를 활용해서 GPU에서 좌표값을 기준으로 Rect를 생성하고, 눈을 표현해준다.

 

무작위 난수로 pos를 생성하고, 해당 pos의 위치값을 참조하여 위아래로 움직이거나, 좌우로 흔들어준다.

 

 

위의 방식을 다르게 사용해서 text데이터로 저장하고 불러올 수 있다.

 

 

 

위 객체는 인스턴싱 형태로 데이터를 전달하고, 처리한다.

728x90