본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 62 - ComputeShader(CS)(2) - TextureBuffer

 

필요한 개념


TextureBuffer->2차원 배열, 3차원 배열로 가능

이미지를 CS로 넘겨서 연산을 수행하도록 한다.


스레드 ID랑 맞춰서 쓸수있도록 32개씩 쪼개고 그룹이 여러개가 생기고 그룹전체에서 스레드ID(dispatchThreadID) 맞춘다.
이미지 Width/32로 나누고 +1함

// 스레드 그룹의 갯수를 구함
float x = ((float)width / 32) < 1.0f ? 1.0f : ((float)width / 32);
float y = ((float)height / 32) < 1.0f ? 1.0f : ((float)height / 32);

// 100픽셀이 라고 하면 100/32 = 3.125인데 0.125부분이 짤려서 96픽셀 밖에 안나올 것이다.
// 그래서 ceil()로 소수점이 있다면 무조건 올림 처리 해준다.
// ex) 3.125 -> 4가 된다.
// 즉 4픽셀을 제외한 나머지 28픽셀 스레드 부분은 처리 안하면 그만이다.
shader->Dispatch(0, 0, (UINT)ceil(x), (UINT)ceil(y), arraySize);

*DispatchThreadID는 그룹에 상관없이 자신의 스레드 방향으로 계속 증가하므로, 결국 픽셀의 값이랑 일치하게 된다.

== 나중에 배울것==
화면을 렌더타켓을 텍스쳐로 빼내서 화면을 조작하는 기법을 실제 게임화면을 다시한번 후에 처리한다해서 포스트 프로세싱이라고 부른다.
포스트 프로세싱(PostEffect) :  실시간으로 렌더링 되는 화면을 다시 픽셀 세이더로 넘겨서 화면에 효과를 주는 방식(피가 딸았을때 붉은 빛을 입힌 다던가,
가속을 할때 빠른 효과를 내기위해 모션블러를 준다던가, 우리가 지금 처리한 기법과 비슷하다.)

 

 

 

RawBuffer & TextureBuffer 코드 구조 분석


더보기

 

- RawBuffer는 따로 result 리소스를 생성해서 출력용으로 사용하고 있다.

 

- 반면, TextureBuffer는 output을 그대로 shader로 넘겨줌(다시 SRV를 통해 Shader로 넘어갈 수 있도록 세팅(계산 완료된 2D Texture를 outputSRV로 해서 다른 Render2D에 넘기기 위해))

 

 

 

 

 

Buffers.h 추가된 내용


더보기
...

///////////////////////////////////////////////////////////////////////////////

class TextureBuffer : public CsResource
{
public:
	TextureBuffer(ID3D11Texture2D* src);
	~TextureBuffer();

private:
	void CreateSRV() override;

	void CreateOutput() override;
	void CreateUAV() override;

	void CreateResult() override;

public:
	UINT Width() { return width; }
	UINT Height() { return height; }
	UINT ArraySize() { return arraySize; }

	ID3D11Texture2D* Output() { return (ID3D11Texture2D*)output; }
	ID3D11ShaderResourceView* OutputSRV() { return outputSRV; }

private:
	UINT width, height, arraySize;
	DXGI_FORMAT format;

	ID3D11ShaderResourceView* outputSRV;
};

 

 

 

 

 

 

 

Buffers.cpp 추가된 내용


더보기
...

///////////////////////////////////////////////////////////////////////////////

TextureBuffer::TextureBuffer(ID3D11Texture2D* src)
{
	D3D11_TEXTURE2D_DESC srcDesc;
	src->GetDesc(&srcDesc);

	width = srcDesc.Width;
	height = srcDesc.Height;
	arraySize = srcDesc.ArraySize;
	format = srcDesc.Format;


	D3D11_TEXTURE2D_DESC desc;
	ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
	desc.Width = width;
	desc.Height = height;
	desc.ArraySize = arraySize;
	desc.Format = format;
	desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	desc.MipLevels = 1;
	desc.SampleDesc.Count = 1;

	ID3D11Texture2D* texture = NULL;
	Check(D3D::GetDevice()->CreateTexture2D(&desc, NULL, &texture));
	D3D::GetDC()->CopyResource(texture, src);

	input = (ID3D11Resource*)texture;

	CreateBuffer();
}

TextureBuffer::~TextureBuffer()
{

}

void TextureBuffer::CreateSRV()
{
	ID3D11Texture2D* texture = (ID3D11Texture2D*)input;

	D3D11_TEXTURE2D_DESC desc;
	texture->GetDesc(&desc);

	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
	srvDesc.Format = desc.Format;
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
	srvDesc.Texture2DArray.MipLevels = 1;
	// 스레드 그룹이 x,y,z이었다. z가 있을 경우 arraySize를 준다.
	// Texture2Darray == Texture3D
	srvDesc.Texture2DArray.ArraySize = arraySize;

	Check(D3D::GetDevice()->CreateShaderResourceView(texture, &srvDesc, &srv));
}

void TextureBuffer::CreateOutput()
{
	ID3D11Texture2D* texture = NULL;

	D3D11_TEXTURE2D_DESC desc;
	ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
	desc.Width = width;
	desc.Height = height;
	desc.ArraySize = arraySize;
	desc.Format = format;
	// UAV와 SRV을 연결(Output 자체를 SRV를 사용)
	desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
	desc.MipLevels = 1;
	desc.SampleDesc.Count = 1;
	Check(D3D::GetDevice()->CreateTexture2D(&desc, NULL, &texture));

	output = (ID3D11Resource*)texture;
}

void TextureBuffer::CreateUAV()
{
	ID3D11Texture2D* texture = (ID3D11Texture2D*)output;

	D3D11_TEXTURE2D_DESC desc;
	texture->GetDesc(&desc);


	D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
	ZeroMemory(&uavDesc, sizeof(D3D11_UNORDERED_ACCESS_VIEW_DESC));
	uavDesc.Format = DXGI_FORMAT_UNKNOWN;
	uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
	uavDesc.Texture2DArray.ArraySize = arraySize;

	Check(D3D::GetDevice()->CreateUnorderedAccessView(texture, &uavDesc, &uav));
}

void TextureBuffer::CreateResult()
{
	ID3D11Texture2D* texture = (ID3D11Texture2D*)output;

	D3D11_TEXTURE2D_DESC desc;
	texture->GetDesc(&desc);

	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
	// 포맷은 그래도 가져다 씀
	srvDesc.Format = desc.Format;
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
	srvDesc.Texture2DArray.MipLevels = 1;
	srvDesc.Texture2DArray.ArraySize = arraySize;

	// Result는 다시 SRV를 통해 Shader로 넘어갈 수 있도록 세팅
	Check(D3D::GetDevice()->CreateShaderResourceView(texture, &srvDesc, &outputSRV));
}

 

 

Render2D.h


더보기
#pragma once

// Texture 화면에 보여주는 클래스

class Render2D : public Renderer
{
public:
	Render2D();
	~Render2D();

	void Update();
	void Render();

	void SRV(ID3D11ShaderResourceView* srv);

private:
	struct Desc
	{
		Matrix View;
		Matrix Projection;
	} desc;

private:
	ConstantBuffer* buffer;
	ID3DX11EffectShaderResourceVariable* sDiffuseMap;
};

 

Render2D.cpp


더보기
#include "Framework.h"
#include "Render2D.h"

Render2D::Render2D()
	: Renderer(L"62_Render2D.fx")
{
	// View는 -1 ~ 0 지점 전방 바라볼 수 있도록 세팅, 그렇게 하면 수직은 y가 1이 됨
	D3DXMatrixLookAtLH(&desc.View, &Vector3(0, 0, -1), &Vector3(0, 0, 0), &Vector3(0, 1, 0));
	// Projection 세팅
	// D3DXMatrixOrthographics - 직교 투영함수
	// 원근 투영(Perspective) - 물체가 멀면 멀수록 작게 보임 -> 우리가 써왔던
	// 직교 투영(Orthographic) - 거리에 따른 보이는 크기 차이가 없다.(거리가 다른데 각각 크기가 동일해보임) -> UI 같은 것에서 처리(작아지면 안되니까)
	// 2번 : left, 3번 : right, 4번 : 위, 5번 아래, 6번, 7번 : 깊이..(near, far)
	// 이거는 세팅이 간단함
	// 2D 보여줄 꺼니깐, 거리에 상관없도록 Orthographic으로 세팅한것
	D3DXMatrixOrthoOffCenterLH(&desc.Projection, 0, D3D::Width(), 0, D3D::Height(), -1, +1);

	// OrthoLH : 크기만으로 중앙점 잡음
	// OrthoOffCenterLH : 중앙점 임의로 설정

	buffer = new ConstantBuffer(&desc, sizeof(Desc));
	shader->AsConstantBuffer("CB_Render2D")->SetConstantBuffer(buffer->Buffer());

	VertexTexture vertices[6];
	vertices[0].Position = Vector3(-0.5f, -0.5f, 0.0f);
	vertices[1].Position = Vector3(-0.5f, +0.5f, 0.0f);
	vertices[2].Position = Vector3(+0.5f, -0.5f, 0.0f);
	vertices[3].Position = Vector3(+0.5f, -0.5f, 0.0f);
	vertices[4].Position = Vector3(-0.5f, +0.5f, 0.0f);
	vertices[5].Position = Vector3(+0.5f, +0.5f, 0.0f);

	vertices[0].Uv = Vector2(0, 1);
	vertices[1].Uv = Vector2(0, 0);
	vertices[2].Uv = Vector2(1, 1);
	vertices[3].Uv = Vector2(1, 1);
	vertices[4].Uv = Vector2(0, 0);
	vertices[5].Uv = Vector2(1, 0);

	vertexBuffer = new VertexBuffer(vertices, 6, sizeof(VertexTexture));
	sDiffuseMap = shader->AsSRV("DiffuseMap");
}

Render2D::~Render2D()
{
	SafeDelete(buffer);
}

void Render2D::Update()
{
	Super::Update();
}

void Render2D::Render()
{
	Super::Render();

	buffer->Render();
	shader->Draw(0, Pass(), 6);
}

void Render2D::SRV(ID3D11ShaderResourceView* srv)
{
	sDiffuseMap->SetResource(srv);
}

 

 

TextureBufferDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class TextureBufferDemo : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override {};
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	// 맨뒤에서 그린다는 얘기
	// 깊이를 끄고 그리는 애들(UI 등) 들은 PostRender()에서 최종적으로 그려주는게
	// 좋다.
	virtual void PostRender() override;
	virtual void ResizeScreen() override {};

private:
	Texture* texture;
	TextureBuffer* textureBuffer;

	Render2D* render2D;
};

 

 

TextureBufferDemo.cpp


더보기
#include "stdafx.h"
#include "TextureBufferDemo.h"

void TextureBufferDemo::Initialize()
{
	Shader* shader = new Shader(L"62_TextureBuffer.fx");

	// 이미지를 조작해서 화면에 보이도록 하기
	texture = new Texture(L"Environment/Compute.png");


	render2D = new Render2D();
	// 이미지 크기
	render2D->GetTransform()->Scale(D3D::Width(), D3D::Height(), 1);
	// 중앙점이 0
	render2D->GetTransform()->Position(D3D::Width() * 0.5f, D3D::Height() * 0.5f, 0);

	// 생성자에서 ID3D11Texture를 받음
	textureBuffer = new TextureBuffer(texture->GetTexture());

	// 입력은 항상 As붙음
	shader->AsSRV("Input")->SetResource(textureBuffer->SRV());
	shader->AsUAV("Output")->SetUnorderedAccessView(textureBuffer->UAV());

	// 이제 ID 계산
	// textureBuffer의 width, height는 원본 버퍼의 width, height를 그대로 저장된것
	UINT width = textureBuffer->Width();
	UINT height = textureBuffer->Height();
	// 1일 것이다.
	UINT arraySize = textureBuffer->ArraySize();

	// 그룹의 갯수를 구함
	float x = ((float)width / 32) < 1.0f ? 1.0f : ((float)width / 32);
	float y = ((float)height / 32) < 1.0f ? 1.0f : ((float)height / 32);

	// 100픽셀이 라고 하면 100/32 = 3.125인데 0.125부분이 짤려서 96픽셀 밖에 안나올 것이다.
	// 그래서 ceil()로 소수점이 있다면 무조건 올림 처리 해준다.
	// ex) 3.125 -> 4가 된다.
	// 즉 4픽셀을 제외한 나머지 28픽셀 스레드 부분은 처리 안하면 그만이다.
	shader->Dispatch(0, 0, (UINT)ceil(x), (UINT)ceil(y), arraySize);

	// 그냥 SRV()하면 입력용이 들어와서 OutputSRV()로 해준다.
	render2D->SRV(textureBuffer->OutputSRV());
}

void TextureBufferDemo::Update()
{
	render2D->Update();
}

void TextureBufferDemo::Render()
{
}

void TextureBufferDemo::PostRender()
{
	render2D->Render();
}

 

 

62_TextureBuffer.fx


더보기
// float4 자료형으로 다룬다.
Texture2DArray<float4> Input;
// Output은 RW가 붙는다.
RWTexture2DArray<float4> Output;

// 최대 1024개이다.
// SV_DispatchThreadID : 스레드 그룹내에서 전체 번호
// 들어오는 SV_DispatchThreadID는 어차피 계속 증가되서 들어온다.
// 텍스쳐를 쪼개었다. 결국 스레드의 ID와 텍스쳐의 픽셀이 일치함
[numthreads(32, 32, 1)]
void CS(uint3 id : SV_DispatchThreadID)
{
    // Texture2DArray Load는 ByteAddressBuffer와는 다르게 int4개가 들어간다.
    // x,y,z은 id 넣어주면 된다.(읽어들일 픽셀의 주소), w는 민맵(1개만 쓰니까 0)
    float4 color = Input.Load(int4(id, 0));

    // 색상 바꿔봄(컬러 반전됨)
    // Output도 픽셀과 일치해서
    // Output[id] = 1.0f - color;
    
    // 흑백화
    // 세 값의 픽셀의 평균내서 넣어줌
    Output[id] = (color.r + color.g + color.b) / 3.0f;
}

technique11 T0
{
    pass P0
    {
        SetVertexShader(NULL);
        SetPixelShader(NULL);

        SetComputeShader(CompileShader(cs_5_0, CS()));
    }
}

 

 

 

 

 

 

 

 

 

 

 

결과