Company
교육 철학

SpriteRenderer

개요

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 그래픽스의 핵심 개념들이 모두 녹아있습니다.