Texture를 건네주어서 받아오는 작업을 해본다.
1. TextureBuffer 클래스 만들기
Engine/05. ComputeShader/Buffer 필터에 TextureBuffer라는 클래스를 만든다.
일반적인 Raw buffer로 데이터를 주고 받는게 아니라 텍스쳐를 주고받는 형태가 될 것이다.
실습할 때는 veigar 이미지를 조작을 해서 다른 색상을 입혀서 return 받는 방식으로 작업을 해 볼 건데 texture 이미지 자체가 하나의 2D 이미지이다 보니까 픽셀 단위로 정보가 많다. 그걸 부숴서 여러 개의 스레드가 처리할 수 있게 분산을 해보도록 한다.
지난 시간에 작업했던 Raw buffer와 크게 다르지는 않다.
마찬가지로 기본 input, output과 관련된 정보가 있을 것이고, 그걸 기반으로 작업을 할 예정이다.
#pragma once
class TextureBuffer
{
public:
TextureBuffer(ComPtr<ID3D11Texture2D> src);
~TextureBuffer();
public:
void CreateBuffer();
private:
void CreateInput(ComPtr<ID3D11Texture2D> src);
void CreateSRV();
void CreateOutput();
void CreateUAV();
void CreateResult();
public:
uint32 GetWidth() { return _width; }
uint32 GetHeight() { return _height; }
uint32 GetArraySize() { return _arraySize; }
ComPtr<ID3D11Texture2D> GetOutput() { return (ID3D11Texture2D*)_output.Get(); }
ComPtr<ID3D11ShaderResourceView> GetOutputSRV() { return _outputSRV; }
public:
ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; }
ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; }
private:
ComPtr<ID3D11Texture2D> _input;
ComPtr<ID3D11ShaderResourceView> _srv; // Input
ComPtr<ID3D11Texture2D> _output;
ComPtr<ID3D11UnorderedAccessView> _uav; // Output
private:
uint32 _width = 0;
uint32 _height = 0;
uint32 _arraySize = 0;
DXGI_FORMAT _format;
ComPtr<ID3D11ShaderResourceView> _outputSRV;
};
#include "pch.h"
#include "TextureBuffer.h"
TextureBuffer::TextureBuffer(ComPtr<ID3D11Texture2D> src)
{
CreateInput(src);
CreateBuffer();
}
TextureBuffer::~TextureBuffer()
{
}
void TextureBuffer::CreateBuffer()
{
CreateSRV();
CreateOutput();
CreateUAV();
CreateResult();
}
void TextureBuffer::CreateInput(ComPtr<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(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;
CHECK(DEVICE->CreateTexture2D(&desc, NULL, _input.GetAddressOf()));
DC->CopyResource(_input.Get(), src.Get());
}
void TextureBuffer::CreateSRV()
{
D3D11_TEXTURE2D_DESC desc;
_input->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;
CHECK(DEVICE->CreateShaderResourceView(_input.Get(), &srvDesc, _srv.GetAddressOf()));
}
void TextureBuffer::CreateOutput()
{
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_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
CHECK(DEVICE->CreateTexture2D(&desc, nullptr, _output.GetAddressOf()));
}
void TextureBuffer::CreateUAV()
{
D3D11_TEXTURE2D_DESC desc;
_output->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(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf()));
}
void TextureBuffer::CreateResult()
{
D3D11_TEXTURE2D_DESC desc;
_output->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;
CHECK(DEVICE->CreateShaderResourceView(_output.Get(), &srvDesc, _outputSRV.GetAddressOf()));
}
TextureBuffer 에 src 이미지를 기입을 하고 뭔가가 만들어지게 되면, 그걸 사용하는 쪽에서 적절하게 GetOutputSRV를 이용해서 실질적으로 가공해서 사용하면 된다.
남은 건 가공하는 코드를 Client 쪽에 넣어줘서 CoputeShader로 만들어준 다음에 그 아이를 통해서 완료된 결과를 어떻게든 얻어오면 된다.
2. Texture::GetTexture2D() 정의하기
Engine에 한가지 추가해보자면
지금까지 작업한 Texure 클래스를 보면 Texture를 만든 다음에 Load를 이용해서 실시간으로 로드하는 부분을 만들어 놨다. 경우에 따라 Texture2D로 갖고 있던 걸 변환하고 있다고 하면 ShaderResourceView로는 안되고, Texture2D로 다시 갖고 와야 하는 경우가 생길 수 있는데 그 부분을 GetTexture2D라는 함수를 만들었다.
ComPtr<ID3D11Texture2D> GetTexture2D();
void SetSRV(ComPtr<ID3D11ShaderResourceView> srv) { _shaderResourceView = srv; }
SetSRV라 해서 원하는 걸 건네주면 SRV에 srv를 기입하는 형태의 함수를 만들어 줬다.
Microsoft::WRL::ComPtr<ID3D11Texture2D> Texture::GetTexture2D()
{
ComPtr<ID3D11Texture2D> texture;
_shaderResourceView->GetResource((ID3D11Resource**)texture.GetAddressOf());
return texture;
}
상속 구조상 ID3D11Texture2D가 Resource를 상속받고 있기 때문에 가능하다.
Engine을 빌드한다.
지금까지 Texture를 Load 해서 가공해 사용하는 실습을 위해 준비된 코드였다.
3. TextureBufferDemo클래스 만들기
이제 Client로 돌아가서 SceneDemo를 복붙 해서 이름을 TextureBufferDemo라고 한다.
Client/Game필터에 넣는다.
코드를 TextureBufferDemo에 맞게 수정한다.
Main에도 세팅한다.
4. 26. TextureBufferDemo.fx 만들기
TextureBufferDemo::Init에서
_shader = make_shared<Shader>(L"23. RenderDemo.fx");
이 부분은 그대로 쓸 것이지만 shader가 필요하기 때문에 25. GroupDemo.fx를 복붙해서 이름을 26. TextureBufferDemo.fx로 한다.
Client/Shaders/Week4에 넣는다.
TextureBufferDemo를 작업할 Texture를 가공할 ComputeShader를 파주고, 작업을 해보도록 한다.
오늘 해 볼 것은 ComputeShader를 통해서 실제로 Veigar라는 텍스쳐를 살짝 조작하는 작업을 하는 게 최종 목표이다.
Texture2DArray<float4> Input;
RWTexture2DArray<float4> Output;
[numthreads(32, 32, 1)] // 2차원 정보
void CS(uint3 id : SV_DispatchThreadID)
{
float4 color = Input.Load(int4(id, 0));
// Output[id] = color; // 원래 색깔 유지
Output[id] = 1.0f - color; // 색 반전
// Output[id] = 1.0f - (color.r + color.g + color.b) / 3.0f; // 회색
}
technique11 T0
{
pass P0
{
SetVertexShader(NULL);
SetPixelShader(NULL);
SetComputeShader(CompileShader(cs_5_0, CS()));
}
};
id가 넘버링이라는 걸 깨달은 순간 다양하게 응용할 수 있다.
SV_DispatchThreadID는
고유하게 있는 아이디였다. 32 * 32의 2차원 상태이고, 1024개의 스레드를 고용해서 일감을 떠넘기고 있다.
5. TextureBufferDemo에 MakeComputeShaderTexture 함수 만들기
TextureBufferDemo 쪽에다 ComputeShader를 돌려가지고 가공을 하는 코드를 만든다.
나머지 잡동사니는 건들지 말고 이 부분만 작업할 것이다.
TextureBufferDemo.h에
ComPtr<ID3D11ShaderResourceView> MakeComputeShaderTexture();
가공이 끝난 다음에 그 이미지 파일의 View를 리턴하는 함수를 만들어 준다.
구현을 위해 TextureBufferDemo.cpp에
#include "TextureBuffer.h"
을 추가하고
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> TextureBufferDemo::MakeComputeShaderTexture()
{
auto shader = make_shared<Shader>(L"26. TextureBufferDemo.fx");
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
shared_ptr<TextureBuffer> textureBuffer = make_shared<TextureBuffer>(texture->GetTexture2D());
}
복사본을 만들어 준 다음에 그에 해당하는 크기의 아웃풋도 만들어주게 하면 최종 준비가 끝날 것이다.
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> TextureBufferDemo::MakeComputeShaderTexture()
{
auto shader = make_shared<Shader>(L"26. TextureBufferDemo.fx");
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
shared_ptr<TextureBuffer> textureBuffer = make_shared<TextureBuffer>(texture->GetTexture2D());
shader->GetSRV("Input")->SetResource(textureBuffer->GetSRV().Get());
shader->GetUAV("Output")->SetUnorderedAccessView(textureBuffer->GetUAV().Get());
uint32 width = textureBuffer->GetWidth();
uint32 height = textureBuffer->GetHeight();
uint32 arraySize = textureBuffer->GetArraySize();
// 64 / 32 = 2
// 63 / 32 = 1.9 -> 2
uint32 x = max(1, (width + 31) / 32);
uint32 y = max(1, (height + 31) / 32);
shader->Dispatch(0, 0, x, y, arraySize);
return textureBuffer->GetOutputSRV();
}
중요한 건 어떻게 배분할 것인가에 대해서 26. TextureBufferDemo.fx에서
[numthreads(32, 32, 1)]
32*32로 하나의 단위를 만들어 줬고,
이걸 그룹으로 만든 다음에 그룹 개수는 처리해야 하는 총개수에 따라가지고 그룹 개수를 정해줬다가 핵심이다.
SV_DispatchThreadID가 고유하기 때문에 각기 담당하는 픽셀이 고유하게 된다. 그걸 이용해서 해당하는 정보를 고쳐주게끔 유도를 해준 상태다.
이렇게 코드를 만들어 주면 dispatch를 때리는 순간
[numthreads(32, 32, 1)] // 2차원 정보
void CS(uint3 id : SV_DispatchThreadID)
{
float4 color = Input.Load(int4(id, 0));
// Output[id] = color; // 원래 색깔 유지
Output[id] = 1.0f - color; // 색 반전
// Output[id] = 1.0f - (color.r + color.g + color.b) / 3.0f; // 회색
}
이 코드 실행되면서 가공이 될 테고
가공이 끝났으면
return textureBuffer->GetOutputSRV();
이걸 return 하면 수정이 된 상태의 이미지 파일이 나오게 될 테니까
그걸 이용해서 veigar 파일을 덮어쓴다.
6. TextureBufferDemo::init에서 MakeComputeShaderTexture 적용하기
TextureBufferDemo::Init으로 가서
auto newSrv = MakeComputeShaderTexture();
가공이 된 veigar텍스쳐를 newSrv로 들고 있는 상태고,
animation이랑 model코드는 삭제한다.
Mesh부분의
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
이 코드를 지우고
auto texture = make_shared<Texture>();
texture->SetSRV(newSrv);
이렇게 하면 가공이 된 형태의 텍스쳐를 들고 있는 상태가 된다.
#include "pch.h"
#include "TextureBufferDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "Light.h"
#include "TextureBuffer.h"
void TextureBufferDemo::Init()
{
auto newSrv = MakeComputeShaderTexture();
_shader = make_shared<Shader>(L"23. RenderDemo.fx");
// Camera
{
auto camera = make_shared<GameObject>();
camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
camera->AddComponent(make_shared<Camera>());
camera->AddComponent(make_shared<CameraScript>());
CUR_SCENE->Add(camera);
}
// Light
{
auto light = make_shared<GameObject>();
light->AddComponent(make_shared<Light>());
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.1f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
light->GetLight()->SetLightDesc(lightDesc);
CUR_SCENE->Add(light);
}
// Mesh
// Material
{
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(_shader);
auto texture = make_shared<Texture>();
texture->SetSRV(newSrv);
material->SetDiffuseMap(texture);
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetLocalPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->AddComponent(make_shared<MeshRenderer>());
{
obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
}
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
obj->GetMeshRenderer()->SetMesh(mesh);
obj->GetMeshRenderer()->SetPass(0); // Mesh
}
CUR_SCENE->Add(obj);
}
RENDER->Init(_shader);
}
void TextureBufferDemo::Update()
{
}
void TextureBufferDemo::Render()
{
}
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> TextureBufferDemo::MakeComputeShaderTexture()
{
auto shader = make_shared<Shader>(L"26. TextureBufferDemo.fx");
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
shared_ptr<TextureBuffer> textureBuffer = make_shared<TextureBuffer>(texture->GetTexture2D());
shader->GetSRV("Input")->SetResource(textureBuffer->GetSRV().Get());
shader->GetUAV("Output")->SetUnorderedAccessView(textureBuffer->GetUAV().Get());
uint32 width = textureBuffer->GetWidth();
uint32 height = textureBuffer->GetHeight();
uint32 arraySize = textureBuffer->GetArraySize();
// 64 / 32 = 2
// 63 / 32 = 1.9 -> 2
uint32 x = max(1, (width + 31) / 32);
uint32 y = max(1, (height + 31) / 32);
shader->Dispatch(0, 0, x, y, arraySize);
return textureBuffer->GetOutputSRV();
}
여기까지 잘 실행이 되는지 테스트를 해본다.
7. 테스트하기
실행을 하면
veigar 텍스쳐가 조작이 되어서 색이 바뀌어있다는 걸 볼 수 있다.
shader에서
[numthreads(32, 32, 1)] // 2차원 정보
void CS(uint3 id : SV_DispatchThreadID)
{
float4 color = Input.Load(int4(id, 0));
// Output[id] = color; // 원래 색깔 유지
//Output[id] = 1.0f - color; // 색 반전
Output[id] = 1.0f - (color.r + color.g + color.b) / 3.0f; // 회색
}
이렇게 하면
회색으로 가공해서 나온다.
이런 식으로 여러 가지 색상으로 조작을 하는 거를 연습해 볼 수 있다.
열심히 분석했던 SystemValue와 관련된 부분을 응용해 가지고 원하는 픽셀마다 내가 담당하는 픽셀을 수정하는 작업을 해본 상태라고 보면 된다.
ComputeShader 이용하는 방법을 어렴풋이 알 거 같다.
자기 자신이 누구인지 넘버링이 되기 때문에 그걸 이용해 분산하면 깔끔하게 처리할 수 있다.
'DirectX' 카테고리의 다른 글
77_RenderManager 구조정리 (0) | 2024.03.24 |
---|---|
76_StructureBuffer (0) | 2024.03.23 |
74_System Value 분석 (0) | 2024.03.22 |
73_RawBuffer (0) | 2024.03.21 |
72_Quaternion (0) | 2024.03.20 |
댓글