개요
SpriteRenderer는 2D 게임에서 스프라이트(2D 이미지)를 화면에 렌더링하는 핵심 컴포넌트입니다. 3D 메시 렌더러와 유사하지만, 2D 그래픽에 최적화되어 있으며 단순하고 효율적인 인터페이스를 제공합니다.
SpriteRenderer의 필요성
2D 게임 개발의 핵심
•
캐릭터, 배경, UI 요소 등 모든 2D 시각 요소 렌더링
•
텍스처를 평면(Quad)에 매핑하여 화면에 표시
•
스프라이트 애니메이션, 플리핑, 색상 조정 등 다양한 기능 지원
성능 최적화
•
3D 메시보다 가벼운 2D 전용 렌더링
•
배치(Batching)를 통한 드로우 콜 최소화
•
텍스처 아틀라스(Texture Atlas)와의 효율적인 연동
개발 편의성
•
간단한 API로 빠른 프로토타이핑
•
에디터에서 실시간 프리뷰
•
다양한 블렌딩 모드와 정렬 옵션
1. 스프라이트(Sprite) 개념
스프라이트란?
정의
스프라이트는 2D 그래픽 오브젝트로, 본질적으로는 텍스처(이미지)이지만 2D 게임 개발에 최적화된 추가 메타데이터와 기능을 포함합니다.
3D와의 차이점
•
3D 모델: 정점(Vertex), 삼각형(Triangle), UV 좌표로 구성된 복잡한 메시
•
스프라이트: 단순한 사각형 평면(Quad)에 2D 이미지를 매핑
스프라이트의 구성 요소
텍스처 (Texture)
•
실제 이미지 데이터 (PNG, JPG 등)
•
GPU 메모리에 로드되어 사용
•
여러 스프라이트가 하나의 텍스처를 공유 가능 (아틀라스)
메타데이터
•
Pivot Point: 스프라이트의 중심점 (회전, 스케일의 기준)
•
Pixels Per Unit (PPU): 스프라이트 1유닛당 픽셀 수 (스케일 기준)
•
Border: 9-슬라이스 스케일링을 위한 경계
•
Rect: 텍스처 내에서 스프라이트가 차지하는 영역 (아틀라스용)
스프라이트의 장점
효율성
•
단순한 지오메트리 (4개 정점, 2개 삼각형)
•
빠른 렌더링 성능
•
메모리 사용량 최소화
유연성
•
런타임에 쉽게 교체 가능 (애니메이션)
•
다양한 블렌딩 모드 지원
•
동적 색상 조정
관리 편의성
•
하나의 텍스처에 여러 스프라이트 패킹 (Sprite Atlas)
•
자동 배치(Batching)로 드로우 콜 감소
•
에디터 도구와의 원활한 통합
2. SpriteRenderer 컴포넌트
컴포넌트 아키텍처
SpriteRenderer는 Component 기반 아키텍처를 따르며, GameObject에 부착되어 해당 오브젝트의 시각적 표현을 담당합니다.
핵심 책임
렌더링 관리
•
스프라이트를 화면에 그리기
•
카메라 공간으로 변환
•
깊이 정렬 (Z-Order)
재질 및 텍스처 관리
•
Material을 통한 셰이더 제어
•
텍스처 바인딩
•
블렌딩 모드 설정
변환 적용
•
GameObject의 Transform 정보 반영
•
위치, 회전, 스케일 적용
•
부모-자식 계층 구조 지원
클래스 구조
#pragma once
#include "yaEntity.h"
#include "yaComponent.h"
#include "yaTexture.h"
#include "yaMaterial.h"
#include "yaMesh.h"
namespace ya
{
class SpriteRenderer : public Component
{
public:
SpriteRenderer();
~SpriteRenderer();
// Component 생명주기 메서드
void Initialize() override;
void Update() override;
void LateUpdate() override;
void Render() override;
// Sprite 설정
void SetSprite(graphics::Texture* sprite) { mSprite = sprite; }
graphics::Texture* GetSprite() const { return mSprite; }
// Material 설정
void SetMaterial(Material* material) { mMaterial = material; }
Material* GetMaterial() const { return mMaterial; }
// 색상 설정
void SetColor(const Vector4& color) { mColor = color; }
Vector4 GetColor() const { return mColor; }
// 플리핑 (좌우/상하 반전)
void SetFlipX(bool flip) { mFlipX = flip; }
void SetFlipY(bool flip) { mFlipY = flip; }
bool GetFlipX() const { return mFlipX; }
bool GetFlipY() const { return mFlipY; }
private:
graphics::Texture* mSprite; // 렌더링할 스프라이트 텍스처
Material* mMaterial; // 셰이더와 렌더 상태
Mesh* mMesh; // 쿼드 메시 (4개 정점)
Vector4 mColor; // 색상 tint (RGBA)
bool mFlipX; // X축 반전
bool mFlipY; // Y축 반전
};
}
C++
복사
3. 구현 세부사항
생성자 및 초기화
SpriteRenderer::SpriteRenderer()
: Component(eComponentType::SpriteRenderer)
, mSprite(nullptr)
, mMaterial(nullptr)
, mMesh(nullptr)
, mColor(1.0f, 1.0f, 1.0f, 1.0f) // 기본 색상: 흰색 불투명
, mFlipX(false)
, mFlipY(false)
{
}
SpriteRenderer::~SpriteRenderer()
{
// 참조만 가지고 있으므로 삭제하지 않음
// 실제 리소스는 ResourceManager가 관리
}
void SpriteRenderer::Initialize()
{
// 쿼드 메시 생성 또는 가져오기
mMesh = Resources::Find<Mesh>(L"RectMesh");
if (mMesh == nullptr)
{
// 기본 쿼드 메시 생성
mMesh = CreateQuadMesh();
Resources::Insert(L"RectMesh", mMesh);
}
// 기본 Material 설정 (설정되지 않은 경우)
if (mMaterial == nullptr)
{
mMaterial = Resources::Find<Material>(L"SpriteMaterial");
}
}
C++
복사
쿼드 메시 생성
Mesh* SpriteRenderer::CreateQuadMesh()
{
// 정점 데이터 (시계 반대 방향)
std::vector<Vertex> vertices;
vertices.resize(4);
// 좌하단
vertices[0].pos = Vector3(-0.5f, -0.5f, 0.0f);
vertices[0].uv = Vector2(0.0f, 1.0f);
// 우하단
vertices[1].pos = Vector3(0.5f, -0.5f, 0.0f);
vertices[1].uv = Vector2(1.0f, 1.0f);
// 우상단
vertices[2].pos = Vector3(0.5f, 0.5f, 0.0f);
vertices[2].uv = Vector2(1.0f, 0.0f);
// 좌상단
vertices[3].pos = Vector3(-0.5f, 0.5f, 0.0f);
vertices[3].uv = Vector2(0.0f, 0.0f);
// 인덱스 (2개의 삼각형)
std::vector<UINT> indices = {
0, 1, 2, // 첫 번째 삼각형
0, 2, 3 // 두 번째 삼각형
};
// 메시 생성 및 버퍼 초기화
Mesh* mesh = new Mesh();
mesh->CreateVertexBuffer(vertices.data(), vertices.size());
mesh->CreateIndexBuffer(indices.data(), indices.size());
return mesh;
}
C++
복사
Update 메서드
void SpriteRenderer::Update()
{
// 스프라이트 렌더러는 보통 Update에서 할 일이 없음
// 애니메이션 등은 별도의 Animator 컴포넌트가 담당
}
void SpriteRenderer::LateUpdate()
{
// 렌더링 전 최종 상태 업데이트
// 예: 카메라 방향으로 회전 (빌보드 효과)
}
C++
복사
Render 메서드 (핵심)
void SpriteRenderer::Render()
{
// 유효성 검사
if (mSprite == nullptr || mMaterial == nullptr || mMesh == nullptr)
return;
// Transform 가져오기
Transform* tr = GetOwner()->GetComponent<Transform>();
// 1. Transform 행렬 계산
Matrix world = tr->GetWorldMatrix();
// 플리핑 적용
if (mFlipX || mFlipY)
{
Vector3 scale = tr->GetScale();
if (mFlipX) scale.x *= -1.0f;
if (mFlipY) scale.y *= -1.0f;
Matrix flipScale = Matrix::CreateScale(scale);
Matrix rotation = Matrix::CreateFromQuaternion(tr->GetRotation());
Matrix translation = Matrix::CreateTranslation(tr->GetPosition());
world = flipScale * rotation * translation;
}
// 2. 상수 버퍼 설정
ConstantBuffer* cb = renderer::constantBuffers[(UINT)eCBType::Transform];
TransformCB transformData;
transformData.world = world;
transformData.view = Camera::GetMainCamera()->GetViewMatrix();
transformData.projection = Camera::GetMainCamera()->GetProjectionMatrix();
cb->SetData(&transformData);
cb->Bind(eShaderStage::VS);
// 3. 색상 버퍼 설정
ColorCB colorData;
colorData.color = mColor;
ConstantBuffer* colorCB = renderer::constantBuffers[(UINT)eCBType::Color];
colorCB->SetData(&colorData);
colorCB->Bind(eShaderStage::PS);
// 4. 텍스처 바인딩
mSprite->BindShader(eShaderStage::PS, 0); // 슬롯 0에 바인딩
// 5. Material 바인딩 (셰이더, 블렌드 상태 등)
mMaterial->Bind();
// 6. 메시 렌더링
mMesh->Render();
// 7. 리소스 언바인딩
mSprite->Clear();
}
C++
복사
4. 스프라이트 셰이더
Vertex Shader
Pixel Shader
5. 사용 예제
기본 사용법
// GameObject 생성
GameObject* player = new GameObject();
player->SetName(L"Player");
// Transform 설정
Transform* tr = player->GetComponent<Transform>();
tr->SetPosition(Vector3(0.0f, 0.0f, 0.0f));
tr->SetScale(Vector3(2.0f, 2.0f, 1.0f)); // 2배 크기
// SpriteRenderer 추가
SpriteRenderer* renderer = player->AddComponent<SpriteRenderer>();
// 스프라이트 텍스처 로드 및 설정
Texture* sprite = Resources::Load<Texture>(L"PlayerIdle", L"Textures/player_idle.png");
renderer->SetSprite(sprite);
// Material 설정
Material* material = Resources::Find<Material>(L"SpriteMaterial");
renderer->SetMaterial(material);
// 씬에 추가
SceneManager::GetActiveScene()->AddGameObject(player, eLayerType::Player);
C++
복사
색상 Tint 적용
// 빨간색 Tint (데미지 표현)
renderer->SetColor(Vector4(1.0f, 0.3f, 0.3f, 1.0f));
// 투명도 조절 (페이드 인/아웃)
float alpha = 0.5f;
renderer->SetColor(Vector4(1.0f, 1.0f, 1.0f, alpha));
C++
복사
플리핑 (좌우 반전)
// 캐릭터가 왼쪽을 보도록
renderer->SetFlipX(true);
// 원래대로 복원
renderer->SetFlipX(false);
C++
복사
스프라이트 애니메이션 (간단한 방법)
class SimpleAnimator : public Component
{
private:
SpriteRenderer* mRenderer;
std::vector<Texture*> mFrames;
int mCurrentFrame;
float mFrameTime;
float mElapsedTime;
public:
void Update() override
{
mElapsedTime += Time::DeltaTime();
if (mElapsedTime >= mFrameTime)
{
mElapsedTime = 0.0f;
mCurrentFrame = (mCurrentFrame + 1) % mFrames.size();
mRenderer->SetSprite(mFrames[mCurrentFrame]);
}
}
};
C++
복사
6. 고급 기능
정렬 레이어 (Sorting Layer)
class SpriteRenderer : public Component
{
private:
int mSortingOrder; // 렌더링 순서 (높을수록 앞에)
public:
void SetSortingOrder(int order) { mSortingOrder = order; }
int GetSortingOrder() const { return mSortingOrder; }
};
// 사용 예시
backgroundRenderer->SetSortingOrder(0); // 배경
playerRenderer->SetSortingOrder(10); // 플레이어
uiRenderer->SetSortingOrder(100); // UI
C++
복사
9-슬라이스 스케일링
class SpriteRenderer : public Component
{
private:
bool mUseSlicing;
Vector4 mBorder; // 좌, 우, 상, 하 경계
public:
void SetSlicing(bool use, const Vector4& border)
{
mUseSlicing = use;
mBorder = border;
}
void Render() override
{
if (mUseSlicing)
{
Render9Slice();
}
else
{
RenderNormal();
}
}
private:
void Render9Slice()
{
// 9개의 쿼드로 나누어 렌더링
// 모서리는 고정 크기, 중앙과 엣지는 늘어남
// UI 패널 등에 유용
}
};
C++
복사
마스킹
// 스텐실 버퍼를 이용한 마스킹
void SpriteRenderer::Render()
{
if (mUseMask)
{
// 1단계: 마스크 렌더링 (스텐실 버퍼에 기록)
renderer::BindDepthStencilState(eDepthStencilState::StencilWrite);
mMaskSprite->Render();
// 2단계: 스프라이트 렌더링 (마스크 영역만)
renderer::BindDepthStencilState(eDepthStencilState::StencilRead);
RenderNormal();
// 복원
renderer::BindDepthStencilState(eDepthStencilState::Default);
}
}
C++
복사
7. 최적화 기법
배치(Batching)
Static Batching
•
같은 Material을 사용하는 정적 스프라이트를 하나의 드로우 콜로 합침
•
메모리 사용량 증가, 하지만 드로우 콜 대폭 감소
Dynamic Batching
•
작은 스프라이트들을 런타임에 동적으로 배치
•
CPU 오버헤드 있지만 메모리 효율적
class SpriteBatcher
{
private:
struct BatchedSprite
{
Matrix transform;
Texture* texture;
Vector4 color;
};
std::vector<BatchedSprite> mBatch;
public:
void AddSprite(SpriteRenderer* renderer)
{
BatchedSprite sprite;
sprite.transform = renderer->GetTransform()->GetWorldMatrix();
sprite.texture = renderer->GetSprite();
sprite.color = renderer->GetColor();
mBatch.push_back(sprite);
}
void Flush()
{
if (mBatch.empty()) return;
// 모든 스프라이트를 하나의 드로우 콜로 렌더링
// GPU 인스턴싱 사용
RenderInstanced(mBatch);
mBatch.clear();
}
};
C++
복사
텍스처 아틀라스
// 여러 스프라이트를 하나의 큰 텍스처에 패킹
class TextureAtlas
{
private:
Texture* mAtlasTexture;
std::map<std::wstring, Rect> mSpriteRects;
public:
void AddSprite(const std::wstring& name, const Rect& uvRect)
{
mSpriteRects[name] = uvRect;
}
Rect GetSpriteUV(const std::wstring& name)
{
return mSpriteRects[name];
}
};
// Vertex Shader에서 UV 오프셋 적용
output.UV = input.UV * UVScale + UVOffset;
C++
복사
오브젝트 풀링
class SpritePool
{
private:
std::vector<GameObject*> mPool;
public:
GameObject* GetSprite()
{
for (auto& obj : mPool)
{
if (!obj->IsActive())
{
obj->SetActive(true);
return obj;
}
}
// 풀이 비었으면 새로 생성
GameObject* newObj = CreateSpriteObject();
mPool.push_back(newObj);
return newObj;
}
void ReturnSprite(GameObject* sprite)
{
sprite->SetActive(false);
}
};
C++
복사
8. 디버깅 및 시각화
디버그 렌더링
class SpriteRenderer : public Component
{
public:
void RenderDebug()
{
if (!DebugSystem::IsEnabled()) return;
Transform* tr = GetOwner()->GetComponent<Transform>();
// 바운딩 박스 그리기
Vector3 pos = tr->GetPosition();
Vector3 scale = tr->GetScale();
DebugRenderer::DrawRect(pos, scale, Color::Green);
// Pivot 포인트 그리기
DebugRenderer::DrawPoint(pos, 5.0f, Color::Red);
// 이름 표시
DebugRenderer::DrawText(GetOwner()->GetName(), pos, Color::White);
}
};
C++
복사
성능 모니터링
void SpriteRenderer::Render()
{
// 프로파일링
PROFILE_SCOPE("SpriteRenderer::Render");
mRenderStats.drawCalls++;
mRenderStats.triangles += 2; // 쿼드 = 2 삼각형
// 실제 렌더링...
}
C++
복사
결론
SpriteRenderer는 2D 게임 개발의 근간을 이루는 핵심 컴포넌트입니다.
핵심 요약
기본 구조
•
Component 기반 아키텍처
•
Texture, Material, Mesh를 조합하여 렌더링
•
Transform 정보를 반영한 월드 공간 렌더링
주요 기능
•
스프라이트 렌더링 (텍스처 매핑)
•
색상 Tint 및 알파 블렌딩
•
플리핑 (좌우/상하 반전)
•
정렬 레이어를 통한 깊이 제어
최적화
•
배치(Batching)로 드로우 콜 감소
•
텍스처 아틀라스로 메모리 효율화
•
오브젝트 풀링으로 할당 비용 절약
확장 가능성
애니메이션 시스템
•
Animator 컴포넌트와 통합
•
프레임 기반 또는 본 애니메이션
•
블렌딩 및 전환 효과
특수 효과
•
셰이더를 통한 다양한 시각 효과
•
디졸브, 아웃라인, 그림자 등
•
커스텀 Material로 무한한 표현력
물리 시스템 통합
•
Collider와 결합하여 충돌 처리
•
Rigidbody로 물리 시뮬레이션
•
레이캐스트를 통한 상호작용
SpriteRenderer의 이해를 통해 2D 게임의 렌더링 파이프라인을 깊이 있게 파악할 수 있으며, 이는 고급 2D 효과 구현과 성능 최적화의 기반이 됩니다. 단순해 보이는 2D 렌더링이지만, 그 안에는 3D 그래픽스의 핵심 개념들이 모두 녹아있습니다.


