여기까지의 작업내용: https://github.com/hwi-middle/HimchanSoftwareRenderer/tree/b7de9ae464050e28b4a0df087a3d53d1a5eaed63
stb 라이브러리
png, jpg, tga 등 다양한 포맷을 이미지 라이브러리에 대해 찾아보니 stb 라이브러리 얘기가 많았고, CK렌더러도 stb를 사용하고 있다는 것을 알았다. 검색해보니 사용법도 매우 간단하여, stb를 사용하기로 했다.
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
사용을 위해서는 cpp 파일에서 이렇게 include 해줘야한다고 한다.
텍스처 샘플링
class WinRenderer
{
// ...
private:
Color32* TextureBuffer;
int TexWidth, TexHeight, TexChannels;
};
일단 임시로 WinRenderer 클래스에 텍스처 버퍼를 만들었다. 제대로 구조를 짜려면 리소스 관리 해주는 클래스에서 Texture 클래스를 가지고 있는 식으로 해야하는데 일단 텍스처링 구현이 우선이기 때문에 이렇게 구현해보겠다.
void WinRenderer::InitTextureBuffer()
{
FILE* File = nullptr;
const std::string FileName = "texture.png";
unsigned char* LoadBuffer = stbi_load(FileName.c_str(), &TexWidth, &TexHeight, &TexChannels, 0);
if (LoadBuffer == nullptr)
{
std::cout << "Failed to load texture\n";
if (stbi_failure_reason())
{
std::cout << stbi_failure_reason();
}
return;
}
std::cout << "Texture loaded: " << TexWidth << "x" << TexHeight << " " << TexChannels << " channels\n";
TextureBuffer = new Color32[TexWidth * TexHeight];
for (int i = 0; i < TexWidth * TexHeight; ++i)
{
TextureBuffer[i].R = LoadBuffer[i * TexChannels];
TextureBuffer[i].G = LoadBuffer[i * TexChannels + 1];
TextureBuffer[i].B = LoadBuffer[i * TexChannels + 2];
TextureBuffer[i].A = 255;
}
stbi_image_free(LoadBuffer);
}
그리고 이렇게 텍스처 버퍼를 초기화할 수 있다. RGBA 채널을 모두 갖는 텍스처를 가져온다는게 보장 되면 LoadBuffer 없이 TextureBuffer에 넣을 수도 있긴 한데(그리고 나서 R이랑 B swap해줘야함), jpg 같은 포맷도 있기 때문에 이렇게 구현했다.
Color32 WinRenderer::SampleTexture(const Vector2& InUV) const
{
int X = Math::Clamp(static_cast<int>(InUV.X * TexWidth + 0.5f), 0, TexWidth - 1);
int Y = Math::Clamp(static_cast<int>(InUV.Y * TexHeight + 0.5f), 0, TexHeight - 1);
return TextureBuffer[Y * TexWidth + X];
}
Color32 WinRenderer::SampleTexture(const Vertex& InVertex) const
{
return SampleTexture(InVertex.UV);
}
텍스처 샘플링은 우선 최근접점 이웃으로 선택했다.
void WinRenderer::DrawTriangle(const Vertex& InVertex1, const Vertex& InVertex2, const Vertex& InVertex3, const Color InColor)
{
std::array<Vertex, 3> Vertices =
{
InVertex1,
InVertex2,
InVertex3
};
std::for_each(Vertices.begin(), Vertices.end(), [&](Vertex& InVertex) {
InVertex.Position = ScreenPoint::CartesianToScreen(InVertex.Position, Width, Height);
});
std::sort(Vertices.begin(), Vertices.end(), [](const Vertex& InLhs, const Vertex& InRhs) { return InLhs.Position.Y < InRhs.Position.Y; });
int32 X1 = Vertices[0].Position.X;
int32 Y1 = Vertices[0].Position.Y;
int32 X2 = Vertices[1].Position.X;
int32 Y2 = Vertices[1].Position.Y;
int32 X3 = Vertices[2].Position.X;
int32 Y3 = Vertices[2].Position.Y;
Vector2 UV1 = Vertices[0].UV;
Vector2 UV2 = Vertices[1].UV;
Vector2 UV3 = Vertices[2].UV;
// Degenerate triangle
if ((X2 - X1) * (Y3 - Y1) == (X3 - X1) * (Y2 - Y1))
{
return;
}
float A12 = Y1 != Y2 ? (X2 - X1) / static_cast<float>(Y2 - Y1) : 0.f;
float A13 = (X3 - X1) / static_cast<float>(Y3 - Y1);
float A23 = Y2 != Y3 ? (X3 - X2) / static_cast<float>(Y3 - Y2) : 0.f;
Vector2 DeltaColorStart = (Y2 != Y1) ? (UV2 - UV1) / static_cast<float>(Y2 - Y1) : Vector2(0.f, 0.f);
Vector2 DeltaColorEnd = (Y3 != Y1) ? (UV3 - UV1) / static_cast<float>(Y3 - Y1) : Vector2(0.f, 0.f);
Vector2 UvStart = UV1;
Vector2 UvEnd = UV1;
for (int Y = Y1; Y <= Y2; ++Y)
{
int32 XStart = X1 + A12 * (Y - Y1);
int32 XEnd = X1 + A13 * (Y - Y1);
bool bIsSwapped = false;
if (XStart > XEnd)
{
std::swap(XStart, XEnd);
std::swap(UvStart, UvEnd);
bIsSwapped = true;
}
XStart = Math::Clamp(XStart, 0, Width - 1);
XEnd = Math::Clamp(XEnd, 0, Width - 1);
for (int X = XStart; X <= XEnd; ++X)
{
Vector2 UV = Math::Lerp(UvStart, UvEnd, (X - XStart) / static_cast<float>(XEnd - XStart));
SetPixel(X, Y, SampleTexture(UV));
}
if (bIsSwapped)
{
std::swap(UvStart, UvEnd);
}
UvStart += DeltaColorStart;
UvEnd += DeltaColorEnd;
}
DeltaColorStart = (Y3 != Y2) ? (UV3 - UV2) / static_cast<float>(Y3 - Y2) : Vector2(0, 0);
// 첫 번째 루프를 건너뛰는 경우가 있으므로 다시 계산
UvStart = UV2;
UvEnd = UV1 + DeltaColorEnd * (Y2 - Y1);
for (int Y = Y2; Y <= Y3; ++Y)
{
int32 XStart = X2 + A23 * static_cast<float>(Y - Y2);
int32 XEnd = X1 + A13 * static_cast<float>(Y - Y1);
bool bIsSwapped = false;
if (XStart > XEnd)
{
std::swap(XStart, XEnd);
std::swap(UvStart, UvEnd);
bIsSwapped = true;
}
XStart = Math::Clamp(XStart, 0, Width - 1);
XEnd = Math::Clamp(XEnd, 0, Width - 1);
for (int X = XStart; X <= XEnd; ++X)
{
Vector2 UV = Math::Lerp(UvStart, UvEnd, (X - XStart) / static_cast<float>(XEnd - XStart));
SetPixel(X, Y, SampleTexture(UV));
}
if (bIsSwapped)
{
std::swap(UvStart, UvEnd);
}
UvStart += DeltaColorStart;
UvEnd += DeltaColorEnd;
}
}
그리고 이제 삼각형을 그릴 때 UV를 보간하고, UV에 따른 텍스처를 샘플링하도록 수정했다.
그리하여 출력한 이미지. RGB는 꼬이지 않게 잘 가져왔는지 등을 테스트하기 위해 만들어본 텍스처를 가져와봤다. stb라는 좋은 라이브러리가 있어서 생각보다 어렵지 않게 구현할 수 있었다.
앞으로의 계획
이제 2차원 공간을 3차원으로 확장할 차례다. 다만 그 전에 키 입력을 처리하는 부분을 구현하고 다시 돌아올 예정이다. 드디어 3차원 공간을 렌더링할 수 있겠구나. 조만간에 키 입력 처리 구현 후 3D 렌더러로 멋지게 돌아오도록 하겠다!
'개발일지 > 소프트 렌더러' 카테고리의 다른 글
소프트웨어 렌더러 만들기 - 16 (삼각형 클리핑 + 버그 수정) (0) | 2024.08.22 |
---|---|
소프트웨어 렌더러 만들기 - 15 (키보드 입력) (0) | 2024.07.27 |
소프트웨어 렌더러 만들기 - 13 (삼각형 채우기, 보간) (1) | 2024.07.22 |
소프트웨어 렌더러 만들기 - 12 (삼각형 그리기 + 버그 수정) (0) | 2024.07.19 |
소프트웨어 렌더러 만들기 - 11 (메모리 누수 해결 및 성능 최적화) (1) | 2024.07.17 |