SimpleMath 기반 SRT/WVP 완전 복습 가이드

이 문서는 SimpleMath 강의를 기반으로 정리한 완전 복습·학습 패키지입니다. 핵심 개념, 코드 구조, 파이프라인 설명, 실습, 퀴즈, 디버깅 가이드까지 한 번에 복습할 수 있도록 구성했습니다.


1) 한눈에 보는 전체 흐름

  • 목표: 오브젝트의 로컬 공간에서 시작해 World → View → Projection을 거쳐 Clip Space로 변환하고, 뷰포트 매핑 후 화면에 렌더링.
  • 핵심 데이터: TransformData { matWorld, matView, matProjection }
  • GPU 전달 경로: CPU(C++) → Constant Buffer → HLSL cbuffer → VS에서 mul()로 WVP 계산

파이프라인: IA → VS → RS → PS → OM (Input Assembler → Vertex Shader → Rasterizer → Pixel Shader → Output Merger)


2) 프로젝트 구조 & 핵심 타입

2-1. SimpleMath 타입 별칭 (Types.h)

using Vec2   = DirectX::SimpleMath::Vector2;
using Vec3   = DirectX::SimpleMath::Vector3;
using Vec4   = DirectX::SimpleMath::Vector4;
using Matrix = DirectX::SimpleMath::Matrix;
  • DirectXMath(XMFLOAT) 대신 간결하고 사용성 높은 SimpleMath를 사용
  • 연산자 오버로딩, 생성자, 내장 헬퍼(행렬 생성/회전/스케일 등)로 코드가 짧고 안전해짐

2-2. 상수 버퍼 데이터 (Struct.h)

struct TransformData
{
    Matrix matWorld      = Matrix::Identity;
    Matrix matView       = Matrix::Identity;
    Matrix matProjection = Matrix::Identity;
};
  • W/V/P를 항등 행렬로 초기화 후, 프레임마다 갱신
  • 16바이트 정렬을 고려해 Matrix 3개를 그대로 넣는 구성이 가장 안전

2-3. 로컬 변환 상태 (Game.h)

Vec3 _localPosition = { 0.f, 0.f, 0.f };
Vec3 _localRotation = { 0.f, 0.f, 0.f };
Vec3 _localScale    = { 1.f, 1.f, 1.f };
  • Unity의 Transform과 동일한 개념
  • 매 프레임 Update()에서 SRT 행렬을 구성해 matWorld로 반영

3) CPU 측 연산: SRT → World, 그리고 Constant Buffer 업데이트

3-1. SRT 구성과 곱셈 순서

Matrix matScale       = Matrix::CreateScale(_localScale);
Matrix matRotation    = Matrix::CreateRotationX(_localRotation.x);
matRotation          *= Matrix::CreateRotationY(_localRotation.y);
matRotation          *= Matrix::CreateRotationZ(_localRotation.z);
Matrix matTranslation = Matrix::CreateTranslation(_localPosition);

Matrix matWorld = matScale * matRotation * matTranslation; // S * R * T (중요!)
_transformData.matWorld = matWorld;

중요: S → R → T 순서를 바꾸면 전혀 다른 결과가 나옵니다.

3-2. Constant Buffer 갱신 (CPU → GPU)

D3D11_MAPPED_SUBRESOURCE subResource = {};
_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
::memcpy(subResource.pData, &_transformData, sizeof(_transformData));
_deviceContext->Unmap(_constantBuffer.Get(), 0);
  • D3D11_MAP_WRITE_DISCARD로 매 프레임 쓰기
  • memcpy로 Struct 전체를 그대로 복사 → 간단·안전

3-3. Constant Buffer 생성

D3D11_BUFFER_DESC desc = {};
desc.Usage          = D3D11_USAGE_DYNAMIC;     // CPU write + GPU read
desc.BindFlags      = D3D11_BIND_CONSTANT_BUFFER;
desc.ByteWidth      = sizeof(TransformData);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
_device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());

4) GPU 측 연산: HLSL cbuffer & WVP

4-1. cbuffer 정의 (Default.hlsl)

cbuffer TransformData : register(b0)
{
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}
  • row_major: CPU(C++)의 행렬 레이아웃과 일치시켜 불일치/전치 문제 방지

4-2. 정점 셰이더에서 WVP 곱셈

struct VS_INPUT  { float4 position : POSITION; float2 uv : TEXCOORD; };
struct VS_OUTPUT { float4 position : SV_POSITION; float2 uv : TEXCOORD; };

VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT o;
    float4 p = mul(input.position, matWorld);      // W
    p        = mul(p,               matView);      // V
    p        = mul(p,               matProjection);// P
    o.position = p;
    o.uv = input.uv;
    return o;
}
  • mul(vector, matrix) 순서에 유의
  • VS 출력 위치는 Clip Space(동차 좌표) → Rasterizer로 전달

4-3. 텍스처 샘플링 (PS)

Texture2D texture0 : register(t0);
SamplerState sampler0 : register(s0);

float4 PS(VS_OUTPUT input) : SV_Target
{
    return texture0.Sample(sampler0, input.uv);
}

5) 렌더링 파이프라인 체크리스트 (IA → VS → RS → PS → OM)

  1. IA: 정점/인덱스 버퍼, 입력 레이아웃, 토폴로지
  2. VS: TransformData(b0) 바인딩, WVP 곱
  3. RS: 뷰포트/래스터라이저 상태
  4. PS: 텍스처 SRV(t#), 샘플러(s#), 블렌드 스테이트
  5. OM: RTV/DSV 바인딩, 드로우콜

디버깅 시 단계별 상태가 원하는 대로 세팅되어 있는지 순서대로 점검하세요.


6) 좌표계 주의 (RH vs LH)

  • SimpleMath 원본은 오른손 좌표계 가정
  • 프로젝트가 왼손 좌표계라면 Look/Front 축의 부호 등에 주의
  • View 행렬을 만들 때 카메라 World역행렬을 사용하는 패턴을 기억

7) 실습 아이디어

  1. 스케일 애니메이션: _localScale = Lerp(_localScale, target, dt);
  2. 회전 애니메이션: _localRotation.y += 0.5f * dt;
  3. 이동 애니메이션: _localPosition.x += 0.1f * dt;
  4. 카메라 도입: matView = Matrix::CreateLookAt(...), matProjection = Matrix::CreatePerspectiveFieldOfView(...)
  5. 피킹 준비: Viewport.Unproject() 경로 탐색 및 Ray 생성

8) 자주 발생하는 실수 & 해결법

  • SRT 순서 뒤바뀜S R T 순서 고정
  • ❌ HLSL에서 row_major 누락 → 변환 왜곡/전치 발생
  • ❌ Constant Buffer 크기/정렬 불일치 → sizeof(TransformData) 사용, 16바이트 정렬 보장
  • ❌ RH/LH 혼용 → 카메라/Look 축 부호 확인, 라이브러리 가정 체크
  • ❌ 잘못된 셰이더 컴파일 파라미터 → D3DCompileFromFile()의 프로파일/엔트리명 확인

9) 퀴즈 (Self-Check)

1) 왜 SRT 순서가 중요할까요?
2) VS에서 W → V → P 순서로 곱하는 이유는?
3) row_major를 쓰지 않으면 어떤 문제가 생기나요?
4) LH 프로젝트에서 SimpleMath(RH 가정)를 쓸 때 무엇을 주의해야 하나요?
5) Constant Buffer를 매 프레임 업데이트할 때 안전한 Map 플래그는?

정답 예시: (1) 비가환 연산이라 결과가 달라짐 (2) 로컬→월드→카메라→클립 순서이기 때문 (3) 전치/왜곡 (4) Look 축 부호/뷰 행렬 생성 시 부호 (5) D3D11_MAP_WRITE_DISCARD


10) 미니 레퍼런스

  • Matrix::CreateScale/RotationX/Y/Z/Translation
  • Matrix::Identity
  • Viewport.Project / Unproject
  • D3D11_BUFFER_DESC (Usage/Bind/CPUAccess)
  • D3DCompileFromFile (엔트리/프로파일/플래그)

11) 확장 과제

  • View/Projection 채우기: 카메라 클래스 도입, CreateLookAt, CreatePerspectiveFieldOfView
  • 다중 오브젝트: 오브젝트마다 TransformData 작성 후, Draw 호출 직전 해당 CB를 갱신·바인딩
  • UI vs 3D UI: 실제 게임 UI(화면 고정)와 3D 월드 공간 UI 분리 구현

12) 디버깅 포인트 (컴파일/런타임)

  • 셰이더 시그니처가 C++ 호출과 일치하는지 (엔트리/프로파일/정점포맷)
  • HLSL cbuffer 레이아웃과 C++ struct멤버 순서/크기 일치 여부
  • 크래시 시 Map/Unmap 짝, 버퍼 바인딩 상태, 드로우콜 직전 파이프라인 상태 확인

부록 A) 샘플 코드 조각 모음

A-1) Constant Buffer 생성/갱신 (C++)

// Create
D3D11_BUFFER_DESC desc = {};
desc.Usage          = D3D11_USAGE_DYNAMIC;
desc.BindFlags      = D3D11_BIND_CONSTANT_BUFFER;
desc.ByteWidth      = sizeof(TransformData);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
_device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());

// Update
D3D11_MAPPED_SUBRESOURCE subResource = {};
_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
::memcpy(subResource.pData, &_transformData, sizeof(_transformData));
_deviceContext->Unmap(_constantBuffer.Get(), 0);

A-2) HLSL 정점·픽셀 셰이더 틀

struct VS_INPUT  { float4 position : POSITION; float2 uv : TEXCOORD; };
struct VS_OUTPUT { float4 position : SV_POSITION; float2 uv : TEXCOORD; };

cbuffer TransformData : register(b0)
{
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}

Texture2D    texture0 : register(t0);
SamplerState sampler0 : register(s0);

VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT o;
    float4 p = mul(input.position, matWorld);
    p        = mul(p, matView);
    p        = mul(p, matProjection);
    o.position = p;
    o.uv = input.uv;
    return o;
}

float4 PS(VS_OUTPUT input) : SV_Target
{
    return texture0.Sample(sampler0, input.uv);
}

A-3) SRT 실험

_localScale      /= 3.f;      // 더 작게
_localPosition.x += 0.001f;   // 우측으로 이동

마무리

이 가이드를 기준으로 SRT 구성 → World 작성 → ConstantBuffer 업데이트 → VS에서 WVP 적용의 전체 흐름을 반복 학습하세요. 확장 과제(카메라/투영/다중 오브젝트)까지 진행하면, 렌더링 파이프라인의 핵심을 완성하게 됩니다. 화이팅!
좋아요. 강의 블로그 글 + 코드 + 녹음 텍스트를 한데 묶어 완전 복습용 학습 파일로 정리했어요. 먼저 바로 쓰실 수 있게 마크다운 학습 노트를 만들어 두었습니다:

학습 노트 내려받기 (Markdown)

아래에는 그 노트의 핵심을 요약하고, 각 항목이 파일에서 나온 근거를 함께 달아 두었습니다.


핵심 요약 (근거 포함)

1) 전체 목표와 파이프라인

  • 목표: 로컬 공간의 정점 → World → View → Projection 을 거쳐 Clip Space로 보낸 뒤 뷰포트에 그리기. VS에서 이 WVP 곱을 수행합니다.
  • 렌더링 단계: IA → VS → RS → PS → OM 순서로 생각하면 점검이 쉬워요.

2) SimpleMath를 쓰는 이유와 도입

  • SimpleMath는 Microsoft가 DirectXMath 위에 얹은 고수준 수학 유틸(벡터/행렬/뷰포트 등)이라, 타입 별칭만으로 깔끔하게 교체해 쓰기 좋습니다.
  • 프로젝트에선 다음처럼 별칭을 둬 사용합니다.
    Vec2/Vec3/Vec4/MatrixDirectX::SimpleMath 타입.

3) 필수 구조체와 상수 버퍼

  • TransformData(CPU): matWorld / matView / matProjection 3개 행렬을 항등으로 초기화해 두고 매 프레임 갱신합니다.
  • Constant Buffer 생성(GPU): D3D11_USAGE_DYNAMIC + D3D11_CPU_ACCESS_WRITE, 크기는 sizeof(TransformData).

4) CPU에서 SRT → World, 그리고 CB 업데이트

  • SRT 구성: CreateScaleCreateRotationX/Y/ZCreateTranslation 생성 후 matWorld = S * R * T. (순서 중요!)
  • GPU로 전달: Map(DISCARD) → memcpy → Unmap 패턴으로 TransformData를 상수 버퍼에 복사합니다.

5) HLSL에서 cbuffer와 WVP

  • cbuffer 일치: HLSL에 동일 구조를 b0로 정의, 특히 row_major를 명시해 CPU와 행렬 메모리 정렬을 맞춥니다.
  • WVP 곱: 정점 셰이더에서 mul(input.position, matWorld)matViewmatProjection 순서로 누적해 최종 위치를 구합니다.
  • 텍스처 샘플링: 픽셀 셰이더에서 Texture2D.Sample()로 색을 가져옵니다.

6) 좌표계 주의 (RH ↔ LH)

  • SimpleMath 원본은 오른손 좌표계 가정입니다. 프로젝트가 왼손 좌표계라면 Look/Front 축 부호 등 방향 정의를 조심하세요.

7) 뷰포트/프로젝션 관련 확장

  • SimpleMath ViewportProject/Unproject를 제공하므로, 피킹(스크린→월드) 등 역변환 실습에도 바로 활용 가능합니다.

8) 실제로 돌려 본 SRT 실험

  • 스케일 1/3: 화면에서 대상이 작아짐.
  • x 위치 증가: 시간이 지날수록 우측으로 이동.

9) 셰이더 컴파일 유의

  • D3DCompileFromFile(path, ..., entryName, profile, flags, ...) 시그니처와 엔트리/프로파일(예: VS, vs_5_0 등)을 정확히 맞춰야 합니다.

바로 적용 체크리스트

  • Types.hVec2/Vec3/Vec4/Matrix 별칭 선언했는가?
  • TransformData 3행렬을 항등으로 초기화했는가?
  • Constant Buffer를 Dynamic/Write-Access로 생성했는가?
  • Update()에서 S * R * T 순서로 matWorld를 만들었는가?
  • Map → memcpy → Unmap로 CB를 갱신하는가?
  • HLSL cbufferrow_major와 동일 멤버 순서로 선언했는가?
  • VS에서 W → V → P로 곱하고 있는가?

“자주 막히는 포인트” 빠른 디버깅

  • 행렬 순서 오류: 결과가 이상하면 우선 S*R*T 및 VS에서 W→V→P 순서를 재확인.
  • 행렬 정렬 문제: 결과가 뒤틀리면 HLSL에 row_major가 빠졌는지 확인.
  • 좌표계 혼동: RH/LH 혼용 시 카메라/룩축 부호부터 점검.
  • 컴파일 파라미터: D3DCompileFromFile 엔트리/프로파일/플래그 재확인.

필요하면 이 노트에 문제/퀴즈, 과제, 체크리스트도 더 확장해 드릴 수 있어요. 우선 위 파일로 복습해 보시고, 보강하고 싶은 파트를 알려 주세요!

profile
李家네_공부방

0개의 댓글