이번 포스팅에서는 Window 초기화와 D3D 11 초기화에 대해 설명할 예정이다.

먼저 Window 초기화부터 코드와 함께 알아보겠다. 

 

 

 

 

InitWindow

Game.cpp에서 해당 함수에 대한 정의를 해줄 것이고, 아래 3단계로 진행된다.  1. Register the window class  2. Create window  3. Show window

 

 

 

 

InitWindow - Register window

// Library::Game.cpp
#include "Game.h"

LPCWSTR g_pszWindowClassName = L"GGPWindowClass";

HRESULT InitWindow(_In_ HINSTANCE hInstance, _In_ INT nCmdShow)
{
    WNDCLASSEX wcex =
    {
        .cbSize = sizeof(WNDCLASSEX),
        .style = CS_HREDRAW | CS_VREDRAW,
        .lpfnWndProc = WindowProc,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = hInstance,
        .hIcon = LoadIcon(hInstance,IDI_APPLICATION),
        .hCursor = LoadCursor(nullptr, IDC_ARROW),
        .hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1),
        .lpszMenuName = nullptr,
        .lpszClassName = g_pszWindowClassName,
        .hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION),
    };
}

 

좀 중요하게 볼 것들만 적자면

  • IpfnWndProc : 현재 window에서 발생한 이벤트를 처리할 window procedure (WindowProc 함수를 만들어줄 예정)
  • hInstance : 현재 실행하려는 application. Handle instance로, 식별 ID를 의미한다.
  • IpszClassName : 현재 window class의 이름이다. Window끼리 구분을 위해 사용하고 접두사 L은 const wchar_t*를 의미한다.

 

if (!RegisterClassEx(&wcex))
{
    DWORD dwError = GetLastError();

    MessageBox(
        nullptr,
        L"Call to RegisterClassEx failed!",
        L"Game Graphics Programming",
        NULL
    );

    if (dwError != ERROR_CLASS_ALREADY_EXISTS)
    {
        return HRESULT_FROM_WIN32(dwError);
    }

    return E_FAIL;
}

 

WNDCLASSEX를 RegisterClassEx를 통해 등록해준다. 추가로 문제가 있을 경우 오류 처리를 해주는 구문을 사용

 

 

 

 

InitWindow - Creating window

CreateWindow 함수를 통해서 등록한 window instance를 생성해준다. 

창에 대한 handle값을 g_hWnd로 저장한다. 

// Library::Game.cpp

// Global Variable
HWND g_hWnd = nullptr;
HINSTANCE g_hInstance = nullptr;
LPCWSTR g_pszWindowName = L"GGP02: Direct3D 11 Basics";


// InitWindow() 이어서
g_hInstance = hInstance;
RECT rc = { 0, 0, 800, 600 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);

g_hWnd = CreateWindow(
    g_pszWindowClassName,
    g_pszWindowName,
    WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
    CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
    nullptr,
    nullptr,
    hInstance,
    nullptr
);

 

 

 

 

 

InitWindow - Show Window

마지막으로 윈도우 창을 보여주면 초기화 단계는 끝이 난다! winMain에서 InitWindow를 해주면 실행할 수 있다.

하지만 아직 WindowProc을 구현하지 않았기 때문에 아무것도 보여지지 않고 사용자 입력처리하는 부분도 없다. 

이제 사용자와 OS로부터 event를 어떤식으로 받고 응답하게 되는지 살펴보자. 

// Library::Game.cpp

ShowWindow(g_hWnd, nCmdShow);

return S_OK;

 

 

 

 

Window Messages

사용자와 OS로부터 받는 event는 message의 형태로 전달된다. 이때 Message는 단순한 숫자 형태로 정의된다.

  • 사용자 입력 : 마우스 클릭, 키보드 입력
  • OS 입력 : 새로운 하드웨어 연결, 저전력 모드 전환

이러한 event들은 프로그램이 돌아가는 중에 언제든 일어날 수 있고, 어떤 순서로 들어올 지 모른다. 이를 유연하게 해결하기 위해 window에서는 message-passing model을 사용한다.

 

 

 

 

 

Messages Loop

올바른 window에 message가 전달되도록 프로그램이 while문에서 message를 체크해주는 것

 

Application은 실행되는 동안 수천개의 메세지를 받는다. 하지만 여러 개의 window를 가진 프로그램일 수도 있기 때문에 올바른 window에 message가 전달되어야 한다. 이를 위해서는 프로그램이 실행되어 있는 동안, while문으로 무한 반복문을 돌리며 message를 체크해줘야 하고, 이를 message loop라고 부른다.

  • OS는 이 message들을 다루기 위한 message queue를 만들어 둔다. 이 queue 순서대로 while문에서 처리를 해준다.
  • 이때 while문에서  PeekMessage()(GetMessage()) 함수를 통해 queue에서 message를 받아온다.

GetMessage()

  • 일반적으로는 queue에서 message를 받아올 때 GetMessage()를 활용한다.
  • 이 함수는 queue가 비어 있을 경우, message가 올 때까지 무한히 기다리게 된다.
  • 특정 이벤트가 없으면 렌더링도 못하고 대기를 해버리기 때문에,, 게임에는 부적합하다. → 대신 PeekMessage() 사용

Message Loop 동작 예시

  • 사용자가 마우스 왼쪽 클릭
  • OS가 WM_LBUTTONDOWN message를 queue에 넣음
  • Message Loop에서 PeekMessage()함수가 호출됨
  • WM_LBUTTONDOWN message를 pull하고,  MSG structure에 message 정보를 넣음.
  • 이후, TranslationMessage()와 DispatchMessage() 함수가 실행됨
  • DispatchMessage() 함수에서 메시지에 해당하는 window의 procedure를 실행
  • Window procedure에서 해당 메시지를 처리
  • 다시 Message Loop로 돌아가 다음 메시지를 처리

 

 

 

 

Window Procedure

◇  OS에 의해 호출되는 콜백 함수로 message가 발생할 때 해당 이벤트를 처리해준다.

 

우리는 직접 WindowProc 함수를 만들어서 사용할 예정이다. 

LRESULT CALLBACK WindowProc(
  _In_ HWND hWnd, 
  _In_ UINT uMsg, 
  _In_ WPARAM wParam, 
  _In_ LPARAM lParam
);
  • 매개변수 : Message 대상이 되는 window의 handle / Message 내용 / Message 추가 정보(wParam, IParam)
  • return : LRESULT 값으로, procedure의 결과 값을 나타내는 정수 형태 값이다. 
LRESULT CALLBACK WindowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE: // window resizing
        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

 

보통 이런식으로 switch 문을 활용한다. Message 종류가 늘어나면 대응하는 함수를 추가해주면 된다.

OS가 보내는 모든 message에 대해 처리를 할 수 없기 때문에, (해당 window에 필요 없는 message도 포함된) 기본적으로 처리하지 않을 message들에 대해서는 DefaultWindowProc() 함수로 처리해준다.

 

 

 

 

Closing Window

◇  DestroyWindow() 함수를 호출해서 창을 닫아준다.

  • 사용자가 window를 끌 때 닫기 버튼을 누를 수도 있고 Alt+F4를 누를 수도 있다. 이 경우 모두 WM_CLOSE message로 전달된다.
  • 아래 처럼 종료 시에 MessageBox를 통해 확인하는 창을 띄워줄 수도 있다.
  • WM_DESTROY : Window가 destroy 되기 전에 맞는 Message. 여기서 PostQuitMessage()를 호출해 Message Loop를 멈추고, 할당한 메모리들을 해제해준다.
LRESULT CALLBACK WindowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        if (MessageBox(hWnd,
            L"진짜 종료하시겠습니까?",
            L"Game Graphics Programming",
            MB_OKCANCEL) == IDOK)
        {
            DestroyWindow(hWnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
        
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}
  • 아래와 같은 종료 루프를 가진다.

 

 


 

지금까지 Window 초기화에 대해 코드와 함께 알아보았고, 여기서부터는 D3D 초기화에 대해 다룰 예정이다.

 

 

 

 

DirectX APIs

◇  Direct2D(D2D) / Direct3D (D3D)는 각각 2D/3D Graphics를 나타내기 위한 API이다.

  • Direct Write는 text layout을 담당한다.
  • DirecX Graphics Infrastructure(DXGI)는 low-level에서 rendering 관련된 일을 수행한다. D3D와 그래픽 드라이버 사이를 연결해준다. 
  • D3D는 하드웨어를 가속시킨다. 즉, CPU가 아닌 GPU를 통해 그래픽 연산을 훨씬 빠르게 처리할 수 있다. 

 

 

 

 

 

D3D Initialization

이제 D3D를 통해 만들어진 window에 Scene을 만들어 볼 것이다. 

초기화를 위해서는 Swap Chain / Device / Immediate Context 이 세가지를 만들어줘야한다. 

 

 

 

 

DirectX Device Resource

  • 앞서 DXGI를 통해 low-level graphics driver와 D3D를 연결한다고 설명했다.
  • 버퍼(buffer) : DXGI를 통해 가져오는 그려야 할 공간 = 렌더링하는 메모리 공간. GPU 메모리 상에 버퍼가 있고, 이를 DXGI를 통해 화면에 보여주게 된다.
  • 더블버퍼링(Double Buffering) : 화면상에 보이고 있는 버퍼를 'Front Buffer'라고 하고(수정할 일이 없기 때문에 Read-only), 보이지 않는 버퍼(그리고 있는 버퍼)를 'Back Buffer'라고 한다. Front가 보여지는 동안 미리 Back Buffer에서 새로운 화면을 그린다. 그리고 화면이 바뀔 때 Front와 Back Buffer를 swap(present)한다. 복사하는 것이 아니라 두 버퍼를 가리키는 포인터를 서로 맞바꾸는 것임. 
  • 렌더 타겟(Render Target) : 실제로 렌더링이 일어나고 있는 Back Buffer를 의미한다.

 

  • Swap Chain : Flip을 하기 위해 순차적으로 쌓이는 DXGI에 있는 버퍼들의 집합을 의미한다. D3D에서는 IDXGISwapChain이라는 인터페이스를 사용한다.
  • 화면에 draw를 하기 위해 다음 과정을 수행하게 된다. 
    1. application을 위한 window만들기 
    2. D3D 사용하기 위한 interface 만들기.
    3. 생성된 windowrendering하기 위한 swap chain을 만들기.
    4. Render target을 만들고, pixel을 채운다.
    5. Swap chainpresent 한다. (2개의 buffer를 교환하는 것

 

 

 

 

D3D 11 Device & Context

D3D를 쓰기 위해서는 GPU를 위한 interface를 생성해야 한다. 이때  ID3D11DeviceID3D11DeviceContext가 생성된다.

 

  • ID3D11Device : 자원 관리 (할당/해제)
    • 픽셀에 draw하기 위한 자원을 할당하고 구성하는 역할
    • 필요할 때 호출되는 method들로 이루어져 있다.
  • ID3D11DeviceContext : 기능 관리 (렌더링 과정)
    • 버퍼, 뷰, 기타 자원 로드, drawing과 같은 매 프레임 호출되는 method 들이 포함되어 있다.

InitDevice에서 D3D Initialization을 해준다. 아래와 같은 다양한 초기화를 담당한다. (자세한 코드는 생략)

HRESULT InitDevice()
{
    // Create D3D 11 device & context
    // Obtain DXGI Factory from device
    // Create swap chain
    // Create render target view
    // Setup the viewport
}

 

초기화 과정에서 D3D11CreateDevice() 함수를 통해 devicedevice context을 모두 만들어준다.
HRESULT D3D11CreateDevice(
    [in, optional]  IDXGIAdapter*           pAdapter,
                    D3D_DRIVER_TYPE         DriverType,
                    HMODULE                 Software,
                    UINT                    Flags,
    [in, optional]  const D3D_FEATURE_LEVEL*pFeatureLevels,
                    UINT                    FeatureLevels,
                    UINT                    SDKVersion,
    [out, optional] ID3D11Device**          ppDevice,
    [out, optional] D3D_FEATURE_LEVEL*      pFeatureLevel,
    [out, optional] ID3D11DeviceContext**   ppImmediateContext
);

 

 

 

 

 

 

 

Reference KHU 강형엽 교수님 강의의 실습 수업을 수강하며 정리한 내용입니다.

 

목표 : 3D 렌더링 엔진 만들기

  • Windlow Initialization / D3D Initialization
  • Support System
  • Cameras / Renderer
  • Models
  • Materials / Lights
  • Etc…

 

 

 

폴더 구조

  • Library Project : 일반적인 렌더링 엔진 구조, 게임에서 공통되게 사용하는 기능들 구현
  • Game Project : 어플리케이션에 따라 구체적인 기능들 구현

 

 

 

 

Window란? 

  • Application window 또는 Main window라고 불란다.
  • Frame 구성은 다음과 같다.
    • Title bar / Minimize, Maximize button / Other standard UI
  • Frame 안에 영역을 Client Area 라고 한다. 
  • OK 버튼이나 EditBox들도 Window의 한 종류이다. 

 

 

 

 

 

Parent Windows & Owner Windows

  • UI control window는 application window의 자식 관계이다.
  • Parent window는 Child window에게 좌표계를 제공하기 때문에 child는 parent의 좌표계 영역 밖에서 나타나지 않는다.
  • 새로운 window 알림 창이 나타나는 경우를 dialog라고 한다. 

 

 

 

Window Handles

  • Windows들은 object지만 C++의 class는 아니다.
  • 특정 Window를 나타내기 위해서 handle이라는 변수를 사용한다. 이때 Handle이란 OS가 object를 구분하기 위한 ID 숫자값이다. (포인터가 아니라 단순한 숫자임)
  • Window handle의 자료형은 HWND를 사용한다.

 

 

 

Screen & Window Coordinates

  • 컴퓨터 상에서 좌표계는 pixel 값에 따른다.
  • 기준에 따라 다음과 같이 3개의 좌표계가 존재한다.
    • Screen/Window/Client 좌표계
  • 모든 좌표계는 좌상단 점을 원점으로 한다.

 

 

 

 

WinMain

◇  모든 window 프로그램은 WinMain이라는 entry-point가 있다. 

  • C/C++에서 in main 함수와 같은 역할이다.

  • hInstance : handle to an instance라고 불리며 OS가 이 값을 이용해서 해당 프로그램이 메모리에 로드될 때 EXE파일의 ID를 구분하게 된다. Handle이기 때문에 포인터가 아닌 정수값이다. 
  • hPrevInstance : 지금은 중요하지 않은 값. 항상 0으로 사용할 것이다.
  • pCmdLind : command-lind argument를 Unicode로 인코딩된 string으로 가진다. 
  • pCmdShow : min applicaiton이 minimize/maximize/일반 중 어떤식으로 보여줄 지 나타내는 flag값
  • Return 값 : int (C/C++과 동일하게 프로그램의 상태를 나타내는 값이다.)

 

 

 

Game::Main.cpp - 전처리

UNICODE를 사용해 빌드하기 위해 전처리로 매크로 정의를 해준다.

그리고 Windows desktop 프로그램은 <windows.h>를 include 해야한다. 

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>

 

 

 

 

Game::Main.cpp - wWinMain

기본적으로 사용되는 엔진구조는 아래와 같다. 

  • Initialization : 어플리케이션이 시작하면서 초기화한다. 필요한 에셋과 데이터를 로드하고 메모리 할당과 같은 초기화 작업을 진행한다.
  • Game Loop : 사용자의 입력의 받아 게임 로직을 수행하고, 이를 렌더링한다.
  • Destruction : 게임이 종료되면 메모리 해제와 같은 Destroy 과정을 거치며 종료한다. 
INT WINAPI wWinMain(
    _In_		HINSTANCE hInstance,
    _Int_opt 	HINSTANCE hPrevInstance,
    _In_		LPWSTR IpCmdLine,
    _In_		INT nCmdShow)
{
	// Initialization
    
    while(! /* is game terminated condition set? */)
    {
    	// Handle Input
        // Update (Game Logic)
        // Draw
    }
    
    // Destroy
    
    return 0;
}

 

wWinMain의 매개변수도 간단히 설명하자면,,

  • _In_ : 호출된 함수에 전달되고, 읽기 전용으로 취급
  • _Out_ : 호출된 함수가 쓸 공간만 제공. 호출된 함수는 해당 공간에 데이터를 쓴다..
  • _In_opt_ : _In_과 같으며, 매개변수가 선택 사항임을 나타낸다. 

 

 

 

 

다음에는 이어서 Window 초기화와 D3D 초기화에 대해 알아보겠다.

 

 

Reference KHU 강형엽 교수님 강의의 실습 수업을 수강하며 정리한 내용입니다.

 

Direct3D 12 graphics Pipeline

이번 포스팅에는 마지막 단계인 Output Merger를 다룰 예정이다. 여러 요소들을 고려해서 최종 렌더링될 pixel의 섹상을 만들어주는 역할을 한다. 해당 픽셀이 실제 화면에 그려질지, 뒤에 가려지는지, 색을 섞여야하는지 등을 판단하게 된다.

 

 

 

 

Output-Merger(OM) Stage

◇  Pixel Shader의 output인 pixel은 Depth-Stencil Test, Color Blending의 과정을 거친다.

  • Depth-Stencil Test : Z-buffer를 활용하여 픽셀이 가려질 경우에 버린다.
  • Color Blending : 반투명한 물체나 파티클에서 기존 색과 Blending하는 과정이 필요하다

 

 

 

Pipeline State

◇   GPU가 '이 데이터를 어떻게 그릴지' 결정하는 하드웨어 설정들의 집합

  • Rasterizer State : Cull Mode(Black-face culling), Fill Mode(Wireframe/Solid), Viewport 설정
  • Blend State : Alpha Blending, Additive Blending(파티클) 등
  • Depth-Stencil State : 그릴지 말지 판단 - Depth Test, Stencil Test
  • Primitive Topology : 어떤 형태로 그릴 지 - Triangle, Line 등
  • Shader State : 어떤 셰이더를 쓸 지 - Vertex Shader, Pixel Shader

◇  Pipeline State Object (PSO) : Pipeline State를 하나로 묶어서 미리 생성한 객체

  • Direct3D 12에서 대부분의 그래픽 파이프라인 상태는 PSO를 사용하여 설정된다.
  • 초기화 시 일반적으로 여러 개의 PSO가 미리 생성되고, 이후 렌더링 시에는 상황에 맞는 PSO로 교체한다.
  • ex) PSO 1 : 기본 오브젝트(Depth Test On, Blending Off), PSO 2 : 투명 오브젝트(Depth Test ON, Blending ON)

 

 

 

 

 

Render Target

◇   GPU가 “그릴 대상”으로 사용하는 버퍼(텍스처)

  • 렌더링할 때 back buffer에 렌더링하여 바로 화면에 표시하는 대신, temporary intermediate buffer에 렌더링하여 추가적인 작업이나 후처리 효과를 적용할 수 있다. 

◇   Frame Buffer

  • 색(Color) 정보를 저장하는 메모리 영역 = RAM에 저장됨 

◇  Double Buffering

  • Front Buffer : 현재 화면에 보여지고 있는 버퍼
  • Back Buffer : 지금 그리고 있는 버퍼
  • Back Buffer에 미리 렌더링 → 작업 끝나면 → Front Buffer와 교체(Swap)
  • 이 과정을 통해서 화면 깜빡임 문제를 방지할 수 있다. back buffer 없이 바로 화면에 그린다면 중간 상태가 보여지면서 깜빡이는 문제가 발생한다.

 

 

Depth-Stencil Testing

 

◇  Depth-Stencil Buffer를 활용해서 픽셀을 그릴지 말지 결정한다.

  • depth data : 어떤 픽셀들이 camera에 가장 가까운지
  • stencil data : 어느 pixel을 update할 것인지

 

 

 

 

Depth Test

◇  Depth Buffer를 이용해 어떤 픽셀이 보일지 결정 = Z-buffering

  • 1) Depth 값 계산 후 Clamp  ( z = min(MaxDepth, max(MinDepth, z)) 활용 )  = Viewport 범위 [0~1] 또는 설정 범위로 제한하는 것
  • 2) Depth Buffer와 비교
    • pixel depth < stored depth : 픽셀이 업데이트 되어야 하는 군 → Color Buffer, Depth Buffer 업뎃
    • pixel depth > stored depth : 안보이는 픽셀 이므로 버림

 

◇  Z-buffering은 처리되는 순서와 상관없이 동일한 결과가 나온다.

 

 

 

 

 

Stencil Test

◇   Stencil Buffer 값이 1인 pixel만 Back Buffer로 채운다.

◇   Tansition effect 적용

 

◇  UI에 가려지는 부분 버리기 = fragment 수를 줄여서 성능을 최적화할 수 있다.

 

 

 

 

Color Blending

◇  새로 그려질 픽셀(c_src)과 기존 화면 색(c_dst)을 섞어서 최종 색(c)을 만드는 과정

  • 기본 렌더링은 Depth Test 통과 시에 덮어쓰게 되지만 반투명한 물체가 있는 경우 뒤의 오브젝트와 Blending이 필요하다. 
  • Output value가 렌더 타겟에 찍히기 전에 Pixel Shader의 Output에 Blending Operation이 동작한다.
  • 블렌딩을 위해서는 불투명한 물체와 투명한 물체를 구분하고, z값이 높은 순으로(back-to-front) 처리하게 된다.

 

 

 

 

 

 

이렇게 Rendering Piepline 포스팅을 마무리하겠습니다--

 

 

 

Reference KHU 강형엽 교수님 강의를 수강하며 정리한 내용입니다.

'그래픽스' 카테고리의 다른 글

[DirectX12] 4. Pixel Shader - Lighting  (0) 2026.03.13
[DirectX12] 3. Pixel Shader - Texturing  (0) 2026.03.13
[DirectX12] 2. Rasterizer  (1) 2026.03.13
[DirectX12] 1. Input Assembler, Vertex Shader  (0) 2026.03.12

 

 

Direct3D 12 graphics Pipeline

지난 포스팅에서는 pixel shader가 하는 일 중 texturing과 관련해서 texel과 pixel의 해상도 차이로 인한 확대,축소 문제를 어떻게 해결하는지 살펴봤다. 오늘 다룰 내용은 Lighting이다. 전통적인 phong 모델부터 per-pixel lighting, blinn-phong model까지 살펴볼 예정이다. 

 

 

 

 

 

Phong Lighting Model

◇  Diffuse / Specular / Ambiend / Emissive 4가지를 섞어 그럴듯하게 빛을 표현하자

  • 가장 유명하고 전통적인 방식이다.
  • approximation을 어떻게 잘 하는지가 중요하다. 

 

 

 

Light Sources

◇ Point light

  • 하나의 point로부터 전방향으로 빛을 뿜어낸다. 
  • point로부터 거리에 따라 intensity가 결정된다

Directional light

  • scene으로부터 멀리 떨어진 곳에서 보내는 빛  ex) 태양
  • scene 전체에서 one direction이다.
  • 내가 어디에 존재하든 일정한 direction과 크기를 가진다 

◇ Spotlight

  • cone shaped light
  • 거리와 각도에 따라 intensity가 달라진다.
  • ex) 형광등, 백열등

◇ Ambient

  • 은은하게 밝혀주는 간접광
  • 반사되어 떠도는 빛을 계산하기 어렵기 때문에 constant value로 뭉개서 계산한다.

 

 

 

Diffuse (난반사광)

물체 표면에 빛이 반사되었을 때 전방향으로 uniform하게 퍼지는 것을 modeling한다.

  • Lambert's law를 따른다  = 입사각이 작을 수록 밝게 보인다.
    • Diffuse 조명은 표면의 밝기가 빛의 입사각 cos(θ)에 비례한다. 
    • θ: 빛의 방향과 표면 법선 벡터(normal) 사이의 각도
    • 관찰자의 위치와 밝기는 무관하다. 

  • Diffuse computation

  • n : surface normal (표면 법선 벡터)
  • l : light direction (빛 방향 벡터)
  • n · l : 두 벡터의 내적
  • max(n·l, 0) : 음수 제거
  • s_d : light color (빛 색)
  • m_d : material diffuse color (물체 색)

 

white light (1,1,1)이 들어올 때, 물체가 노란색이면 R,G값은 반사하고 B는 흡수한다.

 

 

 

Specular (정반사광)

 

 물체 표면에 빛이 닿을 때, 특정 방향으로 강하게 반사되는 조명 효과

  • 표면 특정 부분에 highlight 효과를 줄 수 있다. 
  • 관찰자의 방향에 따라 밝기가 달라진다.
    •  : view vector
    •  : reflection vector
    •  : shininess (클수록 날카롭고 작은 하이라이트)
    •  : light source의 gray-scale value
    •  : highlight on the surface

 

 

Ambient (간접광)

주변에 퍼져있는 일관된 빛 

  • 씬 내의 다양한 물체에 반사된 빛
  • 표면에 도달한 주변 조명은 모든 방향에서 동일한 강도로 산란된다.

 

 

Emissive 

표면 자체에서 방출되는 빛

emissive 적용 전(좌), emissive 적용 후(우)

 

 

 

Phong Lighting Model

 

◇  Diffuse / Specular / Ambiend / Emissive 4가지 term을 합쳐서 표현한다.

 

 

 

Per-pixel Lighting

◇  각 픽셀 단위에서 조명을 계산하는 방식

  • per-vertex 방식에 비해 high quality
  • per-vertex 방식은 정점만 밝기 계산을 하고, 그 사이 픽셀은 선형 보간해서 부드럽지 않은 부분이 생긴다.

per-vertex(좌), per-pixel(우)

 

◇  pixel shader에서 per-pixel 조명 계산을 위해 필요한 벡터

  • l : light vector
  • n : normal
  • v : view vector
  • r : reflection vector  ( r = 2n(n·l) - l )
  • (이때 l은 directional light로 모든 surface에서 동일 / n,r,v는 픽셀마다 다름)
  • 중요한 점은 l이 world space 기준이기 때문에 n,v도 world space 기준 좌표가 필요하다!
  • n과 v는 vertex shader에서 pixel shader로 넘겨주는 값이고, r은 직접 계산이 가능하기 때문에 파라메터를 하나 줄일 수 있다. 

◇  Normal이 pixel마다 생기는 과정

  • vertex shader : vertex normal을 world space로 변환
  • rasterizer : vertex normal을 보간  

◇  View vector가 pixel마다 생기는 과정

  • vertex shader : v = cameraPos - vertexPos (world space 좌표임)
  • rasterizer : vector 보간

◇   최종 pixel 별 Phong lighting 계산

  • n,v,l이 준비되었기 때문에 r은 계산을 통해서 구할 수 있다.
  • 다음 최종적으로 pixel마다 phong lighting을 계산하면 per-pixel을 구현할 수 있게 된다.

 

 

 

 

 

Blinn-Phong Reflection Model

◇  Phong model의 modified(수정된) version

  • Phong은 lighting approximation에 효율적이다.
  • 그러나 specular term에서 가끔 unrealistic한 결과를 낸다.

 

  • dot project는 cos 계산이기 때문에 90도를 넘기면 음수를 반환하고, 그대로 max()를 통과하면 0이 된다.
  • 이런 급격한 cut off 때문에 경계(boundray)가 생긴다.

 

 

◇  Blinn-Phong reflection model - specular term

  • reflection vector(r) 대신에 halfway vector(h)를 이용한다.
  • h :  (view vector) 와  (light vector)을 이등분(bisects)하는 벡터
  • blinn phong의 장점
    • r과 v의 차이가 90도가 넘더라도, h와 n 사이 각도는 90도가 넘지 않기 때문에 이전의 문제점이 해결된다! 
    • 추가로 r을 구하기 위해서 내부적으로 내적을 했었는데, 이 과정이 없어지고 단순한 덧셈 연산으로 중간 벡터를 구하기 때문에 처리도 빨라진다. 

 

  • 적용 결과
    • specular term을 개선한 것이기 때문에, 바다에 반사되는 햇빛이나 젖은 땅에 멀리 떨어진 가로등이 반사되는 등 평면에 대한 반사를 표현할 때 훨씬 현실적인 표현이 가능해진다고 한다. 

 

 

 

 

 

 

Reference KHU 강형엽 교수님 강의를 수강하며 정리한 내용입니다.

'그래픽스' 카테고리의 다른 글

[DirectX12] 5. Output Merger  (1) 2026.04.10
[DirectX12] 3. Pixel Shader - Texturing  (0) 2026.03.13
[DirectX12] 2. Rasterizer  (1) 2026.03.13
[DirectX12] 1. Input Assembler, Vertex Shader  (0) 2026.03.12

 

Direct3D 12 graphics Pipeline

Rasterizer를 통해 pixel이 생성되고, 일부 값들을 보간해서 채워줬다. 이제 Pixel shader에서는 그 위에 Texturing과 Lighting 작업을 해준다. 이번 포스팅에서는 그 중에서 Texturing에 대해 알아볼 예정이다.

 

 

 

 

Image Texturing

◇  rasterizer가 보간한 texture 좌표에 texture 해상도를 곱해서 실제 texture에서 rgb값을 꺼내오는 것

  • 1) 모델링 단계에서 polygon mesh의 각 정점마다 texture coordination가 설정되어야 한다. (s,t)
  • 2) 이후 rasterizer가 각 pixel에 따라 interpolation 해서 넘겨준다. 
  • 3) texture 좌표 (s,t)에 실제 texture의 해상도를 곱해서 (s', t')를 구한다.
  • 4) (s', t') = c의 좌표를 활용해서 실제 texture(c)에서 rgb 값을 가져온다. 

(a) 정점 별 texture 좌표 (b) 보간된 texture 좌표 (c) texture 해상도를 곱한 후

 

  • texture coordintate의 s와 t는 0과 1 사이로 nomalize 되어있어 다양한 텍스쳐에 적용될 수 있다.

 

 

 

 

Surface Parameterization

◇  복잡한 2D,3D 표면을 단순한 2D 좌표계 (s,t)로 표현하는 방법 

 

 

 

 

Chart and Atlas

복잡한 polygon mesh는 patch(ex.머리, 몸 , 팔)들로 나누어 관리한다.

  • 각 patch들로 나누어 surface parameterization을 수행한다.
  • chart : 각 patch들의 이미지
  • atlas : 여러 개의 chart들이 texture에 합쳐진 것

 

 

 

 

Texture Wrapping

range를 벗어난 (s,t)들을 처리해주는 방법

  • Clamp-to-Edge (c) : 벗어날 경우 edge color로 렌더링한다.
  • Repeat (d) : 반복으로 타일링하는 방식
  • Mirrored-Repeat (e),(f) : 그냥 Repeat을 하면 경계가 부자연스러울 수 있기 때문에 mirrored 방식을 사용할 수 있다.

 

 

 

 

Texture Filtering

텍셀(texel) → 화면 픽셀(pixel)로 맵핑할 때, 해상도 차이를 보정하는 기술 

  • 텍스처 해상도와 화면 해상도가 다를 경우 생기는 왜곡/노이즈 문제가 발생할 수 있다. 
  • Magnification (확대) : pixel > texel

 

  • Minification (축소) : pixel < texel

 

 

Filtering for Magnification

pixel이 texel보다 많은 경우 = 하나의 texel이 여러 pixel을 표현하게 된다.

  • Option 1: Nearest point sampling
    • 현재 픽셀에 가장 가까운 텍셀 하나의 값을 그대로 가져온다. 
    • 가장 간단하지만 같은 texture 값을 여러 번 가져오면서 block image 문제가 발생한다.

  • Option 2 : Bilinear interpolation
    • 주변 4개의 텍셀 값을 이용해 보간(Interpolation)하여 중간색을 계산
    • 수평/수직 방향으로 보간을 2번 해서 최종 c를 구한다.
    • 경계가 부드럽고 자연스러운 색 표현이 가능하지만, 연산량이 많은 단점이 있다.

 

 

 

Filtering for Minification

 pixel이 texel보다 적은 경우, texel의 숫자를 줄여야 한다! = 확대보다 심각한 문제 

  • Aliasing 문제 
    • 어떤 texel을 샘플링하느냐에 따라 결과가 달라지는 aliasing 문제가 발생한다.
    • 표현하고자 하는 texture와 전혀 다르게 표현될 수 있는 심각한 문제다! 
    • 이를 위해 Mipmapping을 활용할 예정 

 

 

 

Mipmapping

Aliasing 문제를 해결하기 위한 방법 - down sampling을 통해 minmap을 만들자!

  • 목표 : texel의 수를 줄여서 pixel 수에 최대한 가까워지게 할 것
  • mipmap : downsampling 한 level-λ texture의 모음
  • 원래 텍스처의 해상도가 인 경우, (l+1)개의 레벨로 구성된 피라미드(mipmap)를 구축한다.

  • 이때 각 pixel에 맞는 level-λ texture는 각 pixel이 차지하는 texel의 가로 or 세로 개수 m을 log_2(m)으로 표현하면 해당 level이 된다. 즉, λ = log_2(m) 이다. 
  • 로그 계산이 딱 떨어지면 좋겠지만, 아래 처럼 m=3인 예시에서만 봐도 문제가 발생한다.
  • Option 1) 가장 가까운 level 고르기.

m = 3인 경우

 

 

 

 

 

 

Reference KHU 강형엽 교수님 강의를 수강하며 정리한 내용입니다.

'그래픽스' 카테고리의 다른 글

[DirectX12] 5. Output Merger  (1) 2026.04.10
[DirectX12] 4. Pixel Shader - Lighting  (0) 2026.03.13
[DirectX12] 2. Rasterizer  (1) 2026.03.13
[DirectX12] 1. Input Assembler, Vertex Shader  (0) 2026.03.12

 

Direct3D 12 graphics Pipeline

이중에서 오늘 알아볼 단계는 Rasterizer다. Shader와 달리 하드웨어로 고정된 연산이 이루어지고, 따로 프로그래밍이 불가능하다.

 

 

Rasterizer

 

◇  primitive를 2차원 이미지 요소(pixels)로 분할한다.

 

앞선 vertex shader가 vectex들을 clip space로 옮긴 후에 rasterizer에게 넘겨준다.

이후 raterizer는 크게 5가지의 역할을 수행한다.

  • Primitive Clipping
  • Perspective division
  • Back-face culling
  • Viewport transform
  • Rasterization

요약하면 primitive를 pixel로 쪼개고, pixel 마다 필요한 data들을 넣어준다. 그리고 pixel shader가 연산량이 많기 때문에 이전에 불필요한 pixel들을 제거해주는 역할도 하게 된다.

 

 

Primitive Clipping

◇  안보이는(= clipping volume 밖) primitive 자르기

  • 이전 포스팅에서 설명했듯이 사다리꼴 모양이 아닌 clipping volume을 기준으로 일어난다.
  • 점(Point), 선(Line), 삼각형(Triangle)에 따라 겹쳤을 때의 처리 방식이 조금씩 다르다.
  • Point : 크기가 1 pixel보다 클 경우에 중점을 체크해서 버릴 지 결정
  • Line : 새로운 vertex 생성 후 자르기
  • Triangle : 경계선에 vertex를 생성하고 자르기

 


Perspective Division

◇  w로 나누기를 통해 원근 구현 

  • homogeneous(clip) 좌표를 Cartesian 좌표로 변환하는 과정에서 모든 좌표를 w로 나눈다 (이때 w = z) 
  • 아래 사진을 보면 연산 후에는 camera에 멀리 떨어져있던 선분이 더 짧아지게 된다.
  • 이 결과로 얻은 좌표를 NDC(Normalized Device Coordinates)라고 부른다.

 

 

 


Viewport 

◇  실제 window에서 내가 그림을 그릴 영역  

  • screen space, viewport 모두 3차원 공간이다. (DirectX = 2×2×1/OpenGL = 2×2×2)
  • 아래처럼 여러가지 변수들로 Viewport를 정의한다.  

 

 

 

 

Viewport Transform

◇  NDC space에서 window space로 변환하는 과정

  • Perspective Division을 해서 원근까지 표현했지만, 그 값들은 camera가 보는 시점에서 기준이다.
  • 이 단계에서 모니터의 비율에 맞게 NDC 좌표들을 변환하게 된다.
  • scaling과 translation을 통해 계산된다.

 

  • 보통의 경우 vieport가 전체 모니터 스크린을 의미하기 때문에 아래 값을 대입해주면 깔끔한 matrix가 나온다.
  • TopLeftx = 0, TopLeftY = 0, MinDepth = 0, MaxDepth = 1 

scaling과 translation을 적용한 최종 matrix

 

 

 

 

Back-face Culling

 

◇  Camera를 등지고 있는 면들을 없애버리자!

  • camera 기준 3D 오브젝트의 뒷면에 해당하는 부분은 보이지 않기 때문에 Culling을 해준다.
  • GPU 비용이 비싼 Rasterization, Pixel Shader 단계 전에 제거해준다.
  • 삼각형의 앞면/뒷면 여부는 삼각형의 vectex 순서로 결정된다.

  • 위 사진을 참고하면 Projection Transform 이후에는 모든 projection line이 z축과 평행하게 된다.
  • 삼각형을 xy 평면에 투영한 뒤 간단한 행렬식을 계산하여 front/back을 판단한다.
  • 행렬식 < 0 = Front face = CW
  • 행렬식 > 0  = Back face = CCW
  • 행렬식 = 0  =  edge on

  • 이 행렬식은 사실 두 edge vector의 외적이다. 즉, normal vector의 방향을 나타낸다고 볼 수 있다.
  • 따라서 행렬식의 부호에 따라 해당 삼각형이 바라보는 방향을 판단할 수 있다. 

 

◇  만약 반투명 물체라면? 

  • 반투명일 경우 back face를 날려버리면 안된다.
  • DirectX에서는 Rasterizer 상태에서 cull mode를 설정해서 옵션을 변경할 수 있다.
  • None : 아무것도 제거 X
  • Front : front face 제거 = 내부 단면을 보이게 한다.
  • Back : 일반적인 back face culling (DirectX 기본 설정)

 

 

 

 

 

Rasterization 

◇  vector 정보를 pixel로 쪼개자!

  • 크게 두가지 단계로 진행된다.
  • 1) 그리드에서 어떤 픽셀들이 삼각형에 포함되어있는지 확인한다. (Edge Equation 활용)
  • 2) 선택된 픽셀마다 color와 depth value 정보를 넣어준다. 
  • 이후 pixel shader가 나중에 최종 색 렌더링을 한다.

 

 

Edge Equation 

◇  픽셀이 삼각형 안에 포함되는 지를 판단할 때 사용 

  • point가 왼쪽에 있는 경우 : negative number(음수)
  • point가 오른쪽에 있는 경우 : positive number(양수)
  • point가 line 위에 있는 경우 : zero number

  • point가 삼각형 내부에 있다면 모든 edge들에 right side에 위치한 것이다.
  • 즉, 세 개의 edge모두 edge function이 양수를 return한 경우이다.
  • 함수에 대해 좀 더 설명해보자면,, E =  A x B = A와 B의 외적이다.
  • A = v1 - v0, B = p - v0 (아래 사진에서 파란색, 초록색으로 표시되어있다.)
  • 외적을 했을 때 아래와 같은 성질 때문에, 결과의 부호로 p의 위치를 판단할 수 있다.
  • 0 < θ < 180 : positive value
  • 180 < θ < 360 : negative value

 

 

 

 

Attribute Interpolation

◇  fragment(pixel)에 속성값(color,depth,normal 등)을 보간해서 부여하자! 

  • 삼각형 내부 점은 세 꼭짓점의 가중합으로 표현 가능
  • C0, C1, C2 = 꼭짓점 색상
  • p = 내부 픽셀
  • 𝜆0, 𝜆1, 𝜆2 = 픽셀 p가 삼각형 내에서 얼마나 각 꼭짓점에 가까운지를 나타내는 가중치

 

 

 

Barycentric coordinate (중심 좌표계)

 

◇  픽셀 p에 대한 𝜆0, 𝜆1, 𝜆2를 구하는 수학적인 방법 (Edge Equation 활용)

  • T : 전체 삼각형 면적
  • 예를 들어) T0 = area(p,v1,v2) , λ0 = p가 v0쪽에 얼마나 가까운지를 의미한다.

  • 이때 T0 = 0.5 ∗ E(p, v1 , v2) 이다! 왜냐면 Edge Equation은 외적, 즉 평행사변형의 넓이를 의미하기 때문에 반띵만 해주면 된다. 0.5는 약분되어 사라지고, 아래와 같은 식이 완성된다.

 

 

 

Rasterization Rule (Top-left Rule)

◇  겹치는 픽셀이 있을 때 한쪽만 포함 시키자!

  • 삼각형의 edge가 겹칠 때, 겹치는 픽셀이 두 번 계산되지 않도록 처리해야 한다.
  • 겹치는 edge를 기준으로 left edge와 top edge를 선택하게 된다.

 

 

 

 

Reference KHU 강형엽 교수님 강의를 수강하며 정리한 내용입니다.

 

Direct3D 12 graphics Pipeline

  • 아래와 같은 구조의 pipeline을 통해 GPU는 rendering을 한다.
  • 전체 pipeline을 frame 마다 수행
  • 한 스테이지의 output은 다음 stage의 input이 된다.
  • Shader : programming 가능
  • Input assembler, Tessellator, Rasterizer, Output merger : hard-wired(역할, 목적이 고정되어있는) stage이다. 고정된 함수만 사용하여 일을 수행한다.

 

 

 

먼저 Shader에 대한 간단한 정보들..

Shader

  • 프로그래밍 가능한 영역 (programmable)
  • floating point 연산이 배우 빠르다
  • 특정 program guideline(ex. input output structure)에 맞게 디자인 되어야 함.
  • 랜더링 파이프라인에는 5가지 종류가 있음 (Vertex, Hull, Domain, Geometry, Pixel)
  • vertex shader : 생략 불가능
  • pixel shader : 생략 가능
  • DirectX : HLSL / OpenGL : GLSL

 

 

Input-Assembler Stage (IA)

◇  vertex 정보들을 primitive(도형 - 삼각형, 선 등) 단위로 묶는 역할

  • user-specified buffer(vertex, index buffer)에서 vertex 정보를 읽는다.
  • user들은 IASetPrimitiveTopolgy(data를 어떻게 자를 건지)를 활용하여 primitive type를 정할 수 있다. 

 

 

Vertex Shader

◇  vertex의 최종 position 계산 + camera space에 표시될 vectex 선별

  • Input : vertex specifications
  • vertex specifications : input-assembler에서 vertex,index buffer를 통해 제공된다.
  • 각 vertex 마다 한번씩 동작한다.
  • 참고로 GPU는 for문이 안좋다 - dependancy 때문에 성능이 저하될 수 있기 때문

 

Vertex 좌표 변환 과정

Object space

  • 오브젝트 내에서의 위치
  • 오브젝트에 따라 axes가 바뀐다
  • = model space

World space

  • 3D World space에서의 위치

Camera space

  • Camera를 중점으로 한 상대좌표
  • world space coordinate가 target camera를 기준으로 다시 계산된다.
  • = eye space

Clip space

  • camera space에서 projection transform을 한 것

 

 

 

 

World Transform

◇  scaling, roation, translation 과정을 통해 object space → world space로 옮겨준다.

  • 기존의 object space에서는 각 model들끼리 독립적인 공간이다.
  • 반면 world space에서는 모든 model들을 assemble = 하나의 coordinate system으로 합치는 것
  • World Matrix =
  • L = combined linear transform
  • t = combined translation

 

 

 

World Transform - normal vector

◇  normal vector가 찌그러지는 것을 방지하기 위해 inverse transpose를 사용해야 한다.

  • non-uniform scaling : 객체의 각 축(x, y, z)을 서로 다른 비율로 확대/축소하는 스케일 변환
  • non-uniform scaling이 있을 때, normal에도 그대로 L을 곱해주게 되면 아래 사진처럼 normal이 직교하지 않고 찌그러진다

 

  • 해결 방법 : normal에는  

 

 

 

View Transform

◇   월드 좌표(World Space)를 카메라 기준 좌표(View Space)로 변환하는 과정

  • '카메라를 움직이는 것이 아니라, 세상을 카메라 기준 좌표계로 바꾸는 것' 이라고 생각하면 편하다.
  • 아까 world matrix가 있었던 것 처럼 여기도 View Matrix를 계산해서 사용한다.
  • camera space : (u, v, n, EYE)  - 이때 EYE는 카메라의 위치이다.
  • world space : (e1, e2, e3, O)

 

Step 1) 카메라 위치(EYE)를 world space의 원점으로 translation

  • 즉, world space 전체를 −EYE 만큼 이동한다.

 

 

Step 2) 좌표축 정렬 (Rotation / Basis Change)

  • 이제 카메라의 좌표축 (u, v, n)을 월드 좌표축 (e1, e2, e3)와 맞춰야 한다.

 

 

 

 

View Transform - View Matrix

그럼 위와 같은 과정을 거쳐서 최종 View Matrix는 아래와 같다.

 

 

 

 

View Frustum (절두체)

◇   우리가 볼 수 있는 영역을 의미하며 view volume이라고도 부른다.

  • field of view(fov)가 제한되어있기 때문에 카메라는 모든 물체를 볼 수 없다.
  • 4가지 변수들 : fovy(시야각), aspect(넓이-높이의 비율), n(가장 가까운 지점), f(가장 먼 지점)
  • 변수들을 활용해서 아래 사진 처럼 사다리꼴 또는 공간을 정의할 수 있다.
  • culling이나 clipping을 통해서 보여지지 않는 부분은 버려지게 된다.

 

 

View frustum culling

◇   시야 밖의 object를 없애버리자

  • out of frustum인 물체들은 GPU pipeline에 들어가기전에 버려진다(discard).
  • discard의 의미는 아예 memory에 올리지 않거나 음수로 만들어 다음 stage에서 제외시키는 방법이 있다.

 

Clipping (by the rasterization stage)

◇   시야에 걸리는 일부분만 가져오자

  • 레스터라이저가 하는 일이기 때문에 자세한 것은 이후에 설명할 예정,,

 

 

 

Projection Transform

  view frustum → 정규화된 cube 로 변환

  • view frustum을 원점을 중심으로 하는 axis-aligned cube에 다시 위치시킨다.
  • 위에 clipping을 언급했는데, 피라미드 형태에서 하는 것이 아니라 이 단계에서 큐브 형태로 만든 후에 clipping을 하게된다. 
  • 피라미드에서 clipping을 하게 되면 훨씬 복잡해지 때문..
  • homogeneous coordinate의 w에 깊이 정보를 저장하여 이후 수행되는 perspective divide를 통해 원근 투영 효과가 생성된다.

View Frustum(좌)은 Projection Transform을 통해 Clip Space(우)로 위치된다.

 

  • DirectX (LHS) vs OpenGL (RHS)

 

 

 

 

 

Projection Transform Matrix

   DirectX 기준 최종 Matrix 

  • 아까 위에서 설명했던 view frustum의 변수들로 구성되어있다. 
  • 마지막 행이 왜 (0 0 1 0) 일까? 이건 레스터라이저에서 perspective divide을 할 때 필요하다. 다음 정리에서 설명될 예정

 

 

   OpenGL 기준 최종 Matrix 

  • 요건 참고용으로 가져왔는데, 마지막 행 -1이 들어가는 것을 볼 수 있다. 

  • rasterizer는 shader와 달리 사용자가 수정할 수 없다.
  • rasterizer는 clip space를 LHS로 취급하기 때문에 shader에서 미리 RHS  LHS로 변환해줘야 한다.
  • 이를 위해 z축에 대해 -1을 곱해주게 되는데, 이 과정을 하지 않으면 아래 사진 처럼 뒤집어진 그림이 나오게 된다.

 

 

 

 

 

Reference KHU 강형엽 교수님 강의를 수강하며 정리한 내용입니다.

'그래픽스' 카테고리의 다른 글

[DirectX12] 5. Output Merger  (1) 2026.04.10
[DirectX12] 4. Pixel Shader - Lighting  (0) 2026.03.13
[DirectX12] 3. Pixel Shader - Texturing  (0) 2026.03.13
[DirectX12] 2. Rasterizer  (1) 2026.03.13

+ Recent posts