Search
Duplicate

multiple camera rendeing(다중 카메라 렌더링)

1. 다중 카메라 렌더링 시스템 구현

다중 카메라를 사용하는 주요 이유

1. 게임 뷰 + 에디터 뷰

실제 게임이 보여질 시점 → Game View Camera
에디터나 디버깅용 뷰 → Scene View Camera
서로 다른 시점에서 씬을 동시에 렌더링해야 할 경우 (Unity 씬뷰처럼)

2. UI와 3D 월드 분리

UI는 깊이/광원/투명도 등과 상관없이 항상 앞에 떠야 함
그래서 보통 UI 전용 카메라에서 orthographic + depth off 로 처리
[World Camera] → G-Buffer / Lighting 처리 [UI Camera] → Overlay / No Depth
Plain Text
복사

3. 미니맵 / CCTV / 거울 / 보안카메라

화면 구석에 다른 위치나 시점을 보여줘야 할 때
각각의 미니맵, CCTV가 자기 전용 카메라로 씬을 렌더링

4. Split Screen 멀티플레이

2명 이상이 같은 씬을 다른 시점에서 동시에 보는 경우
각 플레이어마다 독립된 카메라 필요 → 분할 화면 구성

5. 후처리 효과 분리

특정 카메라는 Bloom, DOF 적용
다른 카메라는 그냥 렌더링 → 예: Portal, XR, Virtual Camera 시스템

6. 렌더 타겟에 별도로 그리고 싶을 때

예: Game View는 화면에, Scene View는 Texture로 그려서 저장하거나 디버깅
이때 **카메라별로 Render Target (RTV)**를 따로 갖게 됨

1. 핵심 메인 루프 (Scene::Render)

for (Camera* camera : mCameras)
C++
복사
씬에 등록된 모든 카메라에 대해 반복 수행
각 카메라의 뷰에서 오브젝트를 렌더링
UI 카메라, 게임 카메라 등 분리 가능

2. 카메라별 렌더링 처리 흐름

viewMatrix, projectionMatrix, cameraPos

void Scene::Render() { for (Camera* camera : mCameras) { if (camera == nullptr) continue; Matrix viewMatrix = camera->GetViewMatrix(); Matrix projectionMatrix = camera->GetProjectionMatrix(); Vector3 cameraPos = camera->GetOwner()->GetComponent<Transform>()->GetPosition(); std::vector<GameObject*> opaqueList = {}; std::vector<GameObject*> cutoutList = {}; std::vector<GameObject*> transparentList = {}; // collect randerables(game objects) CollectRenderables(opaqueList, cutoutList, transparentList); // soring renderables by distance (between camera and game object) SortByDistance(opaqueList, cameraPos, true); SortByDistance(cutoutList, cameraPos, true); SortByDistance(transparentList, cameraPos, false); // render game objects RenderRenderables(opaqueList, viewMatrix, projectionMatrix); RenderRenderables(cutoutList, viewMatrix, projectionMatrix); RenderRenderables(transparentList, viewMatrix, projectionMatrix); } }
C++
복사
현재 카메라 기준으로 시점 및 좌표 변환 준비

렌더 리스트 초기화

std::vector<GameObject*> opaqueList = {}; std::vector<GameObject*> cutoutList = {}; std::vector<GameObject*> transparentList = {};
C++
복사
재질 타입에 따라 리스트 분류 (불투명, 컷아웃, 반투명)

3. 오브젝트 수집 - CollectRenderables

void Scene::CollectRenderables(std::vector<GameObject*>& opaqueList, std::vector<GameObject*>& cutoutList , std::vector<GameObject*>& transparentList) const { for (Layer* layer : mLayers) { if (layer == nullptr) continue; std::vector<GameObject*>& gameObjects = layer->GetGameObjects(); for (GameObject* gameObj : gameObjects) { if (gameObj == nullptr) continue; // to do : renderer 상속구조 만들기 SpriteRenderer* renderer = gameObj->GetComponent<SpriteRenderer>(); if (renderer == nullptr) continue; switch (renderer->GetMaterial()->GetRenderingMode()) { case graphics::eRenderingMode::Opaque: opaqueList.push_back(gameObj); break; case graphics::eRenderingMode::CutOut: cutoutList.push_back(gameObj); break; case graphics::eRenderingMode::Transparent: transparentList.push_back(gameObj); break; } } } }
C++
복사
Opaque - 디폴트입니다. 투명한 영역이 없는 일반 솔리드 오브젝트에 적합합니다.
Cutout - 투명 영역과 불투명 영역 사이에 하드 에지가 있는 투명 효과를 만들 수 있습니다. 이 모드에서는 반투명 영역이 없고 텍스처가 100% 불투명이거나 보이지 않습니다. 이 모드는 나뭇잎이나 구멍과 찢어진 부분이 있는 옷감 같은 머티리얼의 형상을 만들기 위해 투명도를 사용할 때 유용합니다.
Transparent - 투명한 플라스틱이나 유리처럼 투명한 머티리얼을 사실적으로 렌더링하는 데 적합합니다. 이 모드에서는 머티리얼 자체에 (텍스처의 알파 채널과 틴트 컬러의 알파에 기반한)투명도 값이 있지만, 반사 및 조명 하이라이트는 실제 투명 머티리얼과 마찬가지로 계속 완전히 투명하게 보입니다.
각 게임 오브젝트에서 SpriteRenderer 컴포넌트를 찾아 렌더링 타입 분류:
Opaque: 깊이 테스트 + Z 쓰기
CutOut: 깊이 테스트 + 알파 테스트
Transparent: 깊이 정렬 필요

4. 거리 정렬 - SortByDistance

void Scene::SortByDistance(std::vector<GameObject*>& renderList, const Vector3& cameraPos, bool bAscending) const { // opaqueList and cutoutList are sorted in ascending order // trasparentList is sorted in descending order auto comparator = [cameraPos, bAscending](GameObject* a, GameObject* b) { float distA = Vector3::Distance(a->GetComponent<Transform>()->GetPosition(), cameraPos); float distB = Vector3::Distance(b->GetComponent<Transform>()->GetPosition(), cameraPos); return bAscending ? (distA < distB) : (distA > distB); }; std::ranges::sort(renderList, comparator); }
C++
복사
거리 기준 정렬:
불투명 / 컷아웃: 가까운 순 (앞 → 뒤)
반투명: 먼 순 (뒤 → 앞) → 블렌딩용

렌더링 타입별 깊이 테스트 변경

깊이 버퍼(Depth Buffer)란?

GPU가 "화면에서 가장 앞에 있는 픽셀만 출력" 하기 위해 사용하는 버퍼입니다.
모든 픽셀은 그려지기 전에 Z값(깊이값)을 기존 값과 비교해서 그릴지 말지를 결정합니다.

Depth Test 함수 종류 요약

함수 이름
의미
예시
Less
현재 픽셀이 더 가까우면 출력
기본값
Equal
깊이가 같을 때만 출력
스텐실 등
Always
항상 출력 (Z비교 안 함)
투명 객체에 자주 사용
LessEqual
더 가깝거나 같으면 출력
이중 패스 등

Opaque vs Transparent 의 깊이 테스트 차이

D3D11_DEPTH_STENCIL_DESC dsDesc = {}; dsDesc.DepthEnable = true; dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; dsDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; dsDesc.StencilEnable = false; GetDevice()->CreateDepthStencilState( &dsDesc, depthStencilStates[static_cast<UINT>(eDepthStencilState::LessEqual)].GetAddressOf()); dsDesc = {}; dsDesc.DepthEnable = false; dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; dsDesc.DepthFunc = D3D11_COMPARISON_NEVER; dsDesc.StencilEnable = false; GetDevice()->CreateDepthStencilState( &dsDesc, depthStencilStates[static_cast<UINT>(eDepthStencilState::DepthNone)].GetAddressOf()); dsDesc = {}; dsDesc.DepthEnable = true; dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; dsDesc.DepthFunc = D3D11_COMPARISON_ALWAYS; dsDesc.StencilEnable = false; GetDevice()->CreateDepthStencilState( &dsDesc, depthStencilStates[static_cast<UINT>(eDepthStencilState::Always)].GetAddressOf());
C++
복사

Opaque, Cutout (불투명)

깊이 비교 활성화 (ZTest: LessEqual)
픽셀이 더 앞에 있으면 그려짐, 뒤에 있으면 무시됨
→ 가장 앞쪽의 불투명 픽셀만 화면에 나옴
*Z Write(깊이 쓰기)**도 켜져 있음 → 다른 객체는 이 값을 기준으로 비교
목적: Z버퍼를 적극 활용해서 오버드로우 최소화

Transparent (투명)

깊이 비교를 아예 하지 않음
ZWrite도 보통 꺼져 있음 (ZWrite: Off)
순서대로 그려야 함 (Back to Front 정렬 필수)
목적: 투명도 계산을 위해 겹치는 픽셀 전부를 그려야 하므로,
ZTest를 하면 뒤에 있는 픽셀이 잘려버려 투명 블렌딩이 깨짐

왜 Always?

투명 렌더링은 GPU에게 “깊이로 거르지 마! 걍 그려봐!” 라고 말하는 것과 같음
대신 개발자가 순서를 보장해야 함
보통 카메라로부터 거리 정렬 → 먼 것부터 그리기 (Back to Front)

주의: 투명 객체는 Z Write도 꺼져야

깊이 값을 쓰면 뒤에 있는 투명한 것도 앞에 있는 투명 오브젝트가 가려버림
→ 투명끼리의 겹침도 깨짐
그래서 Unity Standard Shader도 이런 식으로 동작:
shader 복사편집 Tags { "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
Plain Text
복사

결론 요약

설정 항목
Opaque
Transparent
DepthFunc
LessEqual
Always
ZWrite
On
Off
정렬 필요?
X
(Back to Front)
이유
깊이 테스트로 오버드로우 방지
겹쳐서 그려야 투명하게 보임

5. 오브젝트 렌더링 - RenderRenderables

D3D11_BLEND_DESC bsDesc = {}; bsDesc.RenderTarget[0].BlendEnable = FALSE; bsDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; GetDevice()->CreateBlendState(&bsDesc, blendStates[static_cast<UINT>(eBlendState::Opaque)].GetAddressOf()); bsDesc = {}; bsDesc.RenderTarget[0].BlendEnable = FALSE; bsDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; GetDevice()->CreateBlendState(&bsDesc, blendStates[static_cast<UINT>(eBlendState::Cutout)].GetAddressOf()); bsDesc = {}; bsDesc.RenderTarget[0].BlendEnable = TRUE; bsDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; bsDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; bsDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; bsDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; bsDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; bsDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; bsDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; GetDevice()->CreateBlendState(&bsDesc, blendStates[static_cast<UINT>(eBlendState::Transparent)].GetAddressOf());
C++
복사
정렬된 오브젝트 리스트를 순회하며 렌더링 호출

렌더링 모드별 거리 정렬시 사용할 블렌딩 모드

렌더 모드
BlendEnable
SrcBlend
DestBlend
설명
Opaque
FALSE
-
-
깊이 테스트만 사용, 가장 빠름
Cutout
FALSE
-
-
알파테스트(discard) 처리
Transparent
TRUE
SRC_ALPHA
INV_SRC_ALPHA
알파 블렌딩 처리

6. 카메라 추가/제거

void Scene::AddCamera(Camera* camera); void Scene::RemoveCamera(Camera* camera);
C++
복사
mCameras 벡터에 카메라를 추가/삭제
씬에 존재하는 카메라 수를 유동적으로 제어 가능
UI 카메라, 월드 카메라 등을 구분하여 등록 가능

2. BaseRenderer 도입

BaseRenderer 도입 이유 정리

렌더링 계층의 다형성 확보

BaseRenderer* renderer = gameObj->GetComponent<BaseRenderer>(); renderer->Render(view, proj);
C++
복사
여러 종류의 렌더러를 하나의 리스트에 넣고 순회하면서 렌더링 가능
GameObject의 컴포넌트가 SpriteRenderer이든 MeshRenderer이든 상관없이 공통 렌더링 인터페이스 제공
yaBaseRenderer라는 공통 렌더링 인터페이스를 정의하여, SpriteRenderer 등 구현체들이 이를 상속 가능하게 구조화.
추후 다양한 렌더러 추가 확장을 고려한 구조 개선.
구조 예시
// Base class BaseRenderer : public Component { public: virtual void Render(const Matrix& view, const Matrix& proj) = 0; }; // 구현 class SpriteRenderer : public BaseRenderer { public: void Render(const Matrix& view, const Matrix& proj) override; }; class MeshRenderer : public BaseRenderer { public: void Render(const Matrix& view, const Matrix& proj) override; };
C++
복사
항목
효과
코드 일관성
Renderer마다 개별 처리할 필요 없음
유지보수성
공통 속성은 상위에서 처리
다형성 활용
모든 렌더러를 같은 리스트로 처리 가능
렌더링 최적화
정렬, 컬링, Batch 처리 가능
구조 확장
신규 렌더러 도입이 쉬움

3. DirectXTex 외부 프로젝트 추가

Vendor/DirectXTex 디렉토리 추가.
다양한 이미지 포맷 지원과 압축 해제, GPU 연산 기반 텍스처 전처리 가능.
.vcxproj, .filters 파일 포함으로 Visual Studio 통합 완료.
라이브러리 프로젝트를 솔루션에 추가하여 보관하면 내부 함수 디버깅이 가능해진다.

외부 프로젝트를 직접 추가하는 방식의 장점

1. 내부 함수 디버깅 가능

가장 큰 장점!
F11 또는 Call Stack으로 외부 라이브러리 내부까지 소스 추적 디버깅 가능

2. 라이브러리 빌드 설정을 직접 조정 가능

디버그/릴리즈 설정, 정적/동적 링킹, 런타임 설정 등을 직접 커스터마이징 가능
예: DirectXTex에서 특정 압축 포맷만 제거하거나, SSE 최적화 꺼버릴 수 있음

3. 의존성 자동 해결 없이 바로 사용 가능

시스템에 vcpkg 설치되어 있지 않아도 프로젝트 복제 후 바로 빌드 가능
팀원이나 협업자 입장에서 빌드 환경 독립성 확보

4. 패키징 또는 정적 링크 없이 배포 가능

빌드 아티팩트가 .lib 등 외부 파일로 흩어지지 않고, 솔루션 안에 묶여 있음
릴리즈 배포 시 번들 관리가 쉬움

5. Visual Studio 필터, 그룹 관리 쉬움

.filters 포함 시 솔루션 탐색기에서 파일 정리 + 수정 편리

단점도 명확히 존재합니다

1. 업데이트가 불편

외부 프로젝트에 버그 수정이나 새 기능이 생겨도,
Git Submodule이 아니면 수동으로 교체/머지해야 함

2. 의존성 충돌 위험

여러 외부 프로젝트가 STL 버전, CRT 설정, Warning Level 등 충돌할 수 있음
특히 Unity Build나 C++ 전처리기 확장 사용 시 컴파일 충돌 가능성 있음

3. 빌드 시간 증가

빌드 대상이 .lib가 아니라 .cpp 전체이므로 전체 빌드 시 시간 증가

4. 빌드 플랫폼에 따라 설정 추가 필요

예: x64 Debug와 Win32 Release 빌드 옵션을 모두 직접 맞춰야 함
경로/플랫폼별 설정 자동화가 어려움 (vcpkg는 자동 처리)

5. 모듈화를 깨뜨릴 가능성

프로젝트 내부로 끌어오면 의존성이 흐려지고,
“직접 고치기 시작하면” → 향후 유지보수 포인트가 외부까지 퍼짐

정리: 직접 추가 vs 패키지 설치

항목
직접 프로젝트 추가 (소스 포함)
vcpkg / Conan 등 설치 방식
디버깅
가능 (F11 추적)
보통 .lib 링크됨
커스터마이징
자유도 높음
제한적
설정 관리
직접 조정 필요
자동화됨
빌드 시간
느림 (전체 포함됨)
빠름 (링크만 함)
협업 일관성
디펜던시 일체화
vcpkg.json으로 고정 가능
유지보수
버전 업 어려움
자동 업그레이드 가능

추천 전략

상황
전략
디버깅이 중요한 이미지/수학/툴킷
직접 추가 (예: DirectXTex, ImGuizmo 등)
안정된 릴리즈 버전 사용만 필요
vcpkg 설치 (예: GLFW, stb, imgui 등)
패키지화해서 릴리즈할 경우
정적 링크해서 .lib만 포함