본문 바로가기
DirectX

[DirectX 11] 스터디 3일 버퍼/셰이더

by Minok_nok 2020. 1. 19.

Direct x 11 스터디 3주 차

버퍼, 셰이더 및 HLSL

 

참고 블로그

https://copynull.tistory.com/

 

Thinking Different

Thread-safe 보다는 Reentrant 하게

copynull.tistory.com

이 블로그를 보며 작성한 글입니다.

이 게시글을 기준으로 보는 걸 추천드립니다.

 

개요

2주차 까지는 Directx에 관련한 요소들을 초기화를 했습니다.

 

RenderTargetView, Depth/StencilView, 레스터 라이저 설정, 뷰포트 설정 등 여러가지 설정을 했지만 단색의 화면만 나왔습니다.

 

이번에는 Direct 3D의 시작인 모델링 출력에 관해서 만들어봅시다.

 

렌더링 파이프라인

모델링을 출력하기 전에 먼저 알아야 될 개념 중 하나는 렌더링 파이프라인입니다.

 

렌더링 파이프라인은 Drect3D에서 메모리 자원들을 GPU로 처리하여 하나의 렌더링 이미지를 만드는 일련의 과정을 뜻합니다.

 

렌더링 파이프라인은 많은 단계로 이루어져 있으면서, 요소들의 종류는 크게 고정 기능단계프로그래밍 가능단계로 나누어져 있습니다.

 

고정 기능단계(Fixed Function)는 미리 정해진 특정한 연산들이 수행하고, 상태객체라는 개념을 이용하여 연산의 설정을 바꿀 수 있습니다.

하지만 프로그래머가 임의로 실행을 하지 않을수는 없습니다.

 

프로그래밍 가능단계(Programmable)는 HLSL(High Level Shading Language, 고수준 셰이딩 언어)로 셰이더 프로그래밍이 가능한 단계입니다. 고정기능단계와 달리 비활성화 하는것도 가능합니다.

 

아래의 이미지는 렌더링 파이프라인의 다이어그램입니다.

 

위 다이어그램에서 모서리가 직각인 사각형은 고정기능 단계이며, 둥근 사각형이 단계는 프로그래밍 가능단계입니다.

 

렌더링 파이프라인을 정리하자면.



Input-Assember Stage

입력 조립기 단계

파이프라인의 이후 단계들이 사용할 정점 데이터를 긁어 모으는 단계입니다.


정점들의 연결성을 파악하고 바람직한 렌더링 구성을 결정합니다.


이 데이터들은 정점 셰이더 단계로 넘어갑니다.

Vertex Shader

정점 셰이더

Input-Assember이 넘겨준 정점 자료의 정점들을 한번에 하나씩 처리합니다.


각 입력 정점마다 지정된 정점 셰이더 프로그램이 적용됩니다.

변환행렬 적용, 정점 별 조명계산 등 Pixel Shader단계에서 불필요한 연산들을 미리 할때 사용합니다.

Hull Shader

덮개 셰이더

Tessellation(테셀레이션)의 첫번째 단계입니다.


Vertex Shader가 넘겨준 기분 도형들을 받아서 두가지 작업을 수행합니다.


첫 작업은 각 기본도형마다 테셀레이션 계수를 결정합니다.

테셀레이션 계수는 이 기본도형을 얼마나 세밀하게 분할하는지 파악하는데 쓰입니다.


두번째 작업은 바람직한 출력 제어 패치 구성의 각 제이점마다 실행되는 것으로, Domain Shader단계에서 기본도형을 실제로 분할하는 데 사용할 점들을 만들어냅니다.

Tessellator

테셀레이터

Tessellation의 두번째 단계입니다.


현재 기본 도형 종류에 적합한 Sampling Pattern(표본 추출 패턴)을 결정합니다.


Tessellator단계는 테셀레이션 계수들과 자신만의 구성을 이용해서, 현재 기본 도형의 정점들 중 기본 도형을 더 작은 조각으로 분할하기 위한 표본으로 사용할 정점들을 결정합니다.


그 정점들로 부터 산출한 Barycentric coordinates(무게중심 좌표)들을 다음 단계인 DomainShader에게 넘깁니다.

Domain Shader

영역 셰이더

Tessellation의 세번째 단계입니다.


Barycentric Coordinates들과 Hull Shader가 생성한 제어점들을 입력으로 받아서 새 정점들을 생성합니다.


이 단계에서 생성된 제어점을 여러 과정을 통해 각 점마다 무게중심 위치들을 Geometry로 변환해서 다음 단계로 넘겨줍니다.


테셀레이션 단계에서 증폭된 정점 자료들로부터 출력 기하구조를 생성하는 부분의 유연성 덕분에 파이프 라인 안에서 좀 더 다양한 테셀레이션 알고리즘을 구현할 수 있는 여지가 생긴다.

Geometry Shader

기하 셰이더

완성된 형태의 기본도형을 처리하거나 생성합니다.

이 단계에서는 파이프라인에 새로운 자료 요소들을 추가하거나 제거할 수 있습니다.

Rasterizer

레스터라이저

주어진 기하구조가 렌더 대상의 어떤 픽셀들을 덮는지 파악해서 그 픽셀들에 대한 단편자료를 산출합니다.

각 단편은 모든 정점별 특성을 해당 픽셀 위치에 맞게 보간한 값을 가집니다.

이렇게 생성된 단편을 픽셀 셰이더로 전달합니다.

Pixel Shader

픽셀 셰이더

연결된 각 렌더 대상을 위한 색상값을 출력합니다.

Output Merger

출력 병합기

픽셀 셰이더 출력을 파이프 라인에 연결된 깊이/스텐실 자원 및 렌더 대상 자원에 제대로 합치는 작업을 당담합니다.

이 단계에서 Output Merger은 깊이 판정과 스텐실 판정, 혼합 함수 적용들을 수행하며, 최종적으로 해당 자원에 실제로 기록합니다.

https://grandstayner.tistory.com/entry/DirectX11-Rendering-Pipeline%EC%9D%98-%EB%8C%80%EB%9E%B5%EC%A0%81%EC%9D%B8-%EB%AA%A8%EC%8A%B5

 

DirectX11 - Rendering Pipeline의 대략적인 모습

컴퓨터 프로그램을 사용하여 Model 또는 Scene으로 부터 영상을 만들어 내는 과정을 Rendering이라고 하고, 한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조를 Pipeline이라고 한..

grandstayner.tistory.com

 

이번에 저희는 이런 과정을 통해 모델링을 렌더링 할것입니다.

 

버텍스 버퍼

렌더링 파이프라인을 사용하기 위한 자원으로 여러가지가 있는 그 중 하나는 버텍스(정점) 버퍼입니다.

 

이 구 모델링은 자그마한 삼각형이 이어져 구성이 되어 있습니다.

저 삼각형이 기본 도형이며, 폴리곤이라고 부릅니다.

 

저 폴리곤을 구성하고 있는 꼭짓점 하나하나가 버텍스입니다.

저 버텍스를 정점 버퍼라고 부르는 특수 데이터 배열에 넣어야 GPU가 모델링 처리를 할 수 있습니다.

 

인덱스 버퍼

인덱스 버퍼는 버텍스 버퍼와 관련이 깊습니다.

인덱스 버퍼의 목적은 정점 버퍼에 있는 각 정점의 위치를 기록하는것입니다.

 

GPU는 인덱스 버퍼를 사용하여 버텍스 버퍼의 특정 버텍스를 빠르게 찾을 수 있습니다.



프레임 워크 업데이트

GraphicsClass에 캡슐화된 클래스가 3개가 더 추가되었습니다.

 

CameraClass는 뷰 행렬을 포함 다른 행렬도 알아봅시다.

 

ModelClass는 3D객체의 기하학적인 부분을 다룹니다.

이번에는 간단하게 정육면체를 만들것입니다.

 

ColorShaderClass는 직접 작성한 HLSL 셰이더를 호출하여 객체들을 그리는 일을 맡게 될것입니다.

 

버텍스 셰이더 작성

 

HLSL로 버텍스 셰이더를 작성해 봅시다.

이 코드의 확장자는 .vs입니다.

 

Color.vs 코드입니다.

/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
  matrix worldMatrix;
  matrix viewMatrix;
  matrix projectionMatrix;
};

//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;
    

// 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
    input.position.w = 1.0f;

// 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 ​​계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
// 픽셀 쉐이더가 사용할 입력 색상을 저장합니다.
    output.color = input.color;
    
    return output;
}

버텍스 셰이더 작성: 상수 버퍼

cbuffer MatrixBuffer
{
  matrix worldMatrix;
  matrix viewMatrix;
  matrix projectionMatrix;
};

cbuffer형식의 MatrixBuffer을 정의합니다.

cbuffer은 상수버퍼라고 하며, CPU와 GPU간에 데이터를 전달 하는데 사용이 됩니다.

 

C++코드에서 이 정보를 넘겨 줄 수 있습니다.

 

https://bell0bytes.eu/shader-data/

 

DirectX Tutorial: Constant Buffers (HLSL)

In this short tutorial we will discuss constant buffers to manipulate GPU data at runtime in more details.

bell0bytes.eu

 

MatrixBuffer의 구성요소는 행렬 3개이며 월드, 뷰, 투영 행렬입니다

 

버텍스 셰이더 작성: Type 설정

struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

 

버텍스 셰이더에 필요한 구조체를 선언합니다.

 

VertextInputType은 버텍스 셰이더의 매개변수로 사용 될 구조체이고, 그 매개변수를 이용하여 만든 결과값을 PixelInputType으로 반환할 예정입니다.

 

VertexInputType의 변수 중 float4 position : POSITION; 부분이 익숙하면서 낮섭니다.

 

float4는 float 형태의 변수가 4개가 붙어있는 Vector이라고 생각하시면 됩니다.

 

position 변수 선언 뒤에 처음보는 형태가 보이는데 :POSITION 부분을 Semantic(시맨틱)이라고 합니다.

 

Semantic은 데이터의 출처와 역할에 대한 분명한 의미를 부여하기 위해서 함수, 변수, 인수 뒤에 선택적으로 붙여서 서술하는 인자입니다.

 

SV_ 접두사가 붙은 시맨틱은 시스템 값 시맨틱으로 레스터라이저단계에서 해석되는 시맨틱입니다.

 

버텍스 셰이더 작성: 셰이더 계산

PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;

// 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
    input.position.w = 1.0f;

// 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 ​​계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
// 픽셀 쉐이더가 사용할 입력 색상을 저장합니다.
    output.color = input.color;
    
    return output;
}

실제 셰이더 계산입니다.

 

먼저 input.position의 w값이 0일경우 Vector값으로 보고 1일경우 Position값으로 보기 때문에 w를 1로 바꾸어줍니다.

이는 행렬과의 원활한 계산을 위해서도 적용됩니다.

그리고 계산할 행렬들이 4X4사이즈이기 때문에 input의 사이즈도 4로 정의를 해 주어야 됩니다.

 

그 뒤 월드, 뷰, 투영행렬을 곱하여 최종 정점 위치를 계산합니다.

마지막으로는 정점의 색상을 설정하여 반환합니다.

 

버텍스 셰이더 작성:  월드,뷰,투영 행렬 개요

버텍스 셰이더에서 쓰이는 월드, 뷰, 투영행렬을 간단하게 소개하고 넘어가겠습니다.

이 행렬들을 float4지료형인 position과 곱하면 화면의 출력되는 버텍스인 position값으로 변환됩니다.

 

월드 행렬

물체들은 각각의 고유 좌표계인 로컬 좌표계를 가지고 있습니다.

그 물체들은 0,0,0 값을 기준으로 있는데, 이를 통합하는 월드 좌표계로 옮겨야합니다.

 

월드 좌표계로 옮기기 위해 특정 행렬을 곱하는데 곱하는 결과는 이와 같습니다.

dx dx dz가 월드 좌표계에서 얼마나 떨어져 있는지를 나타내는 값입니다.

 

이를 Position값과 곱하면 로컬 + 월드좌표가 나옵니다.

 

뷰 행렬

 

DirectX에서 물체를 비추는 카메라를 만들 수 있습니다.

이번에 만들 CameraClass가 그 역할을 합니다.

 

월드 좌표까지 변환된 포지션을 뷰 행렬에 곱해주면 카메라 공간에 위치하게 됩니다.

 

Directx에서는 카메라가 비추는 뷰 행렬을 만들어줍니다.

이는 추후 코드에서 소개하겠습니다.

 

 

투영 행렬

 

투영행렬은 3차원 좌표계를 2차원 좌표계로 변환하는 행렬입니다.

이는 2D 모니터 상에 물체를 표현하기 위해 필요합니다.

 

투영행렬은 D3DClass에서 FOV값과 Near, Far값 그리고 화면 비율을 써서 만들었습니다.

 

그렇게 만들어진 투영행렬을 곱해서 Z 버퍼를 이용해 출력유무까지 따져 최종 결과값을 만들어줍니다.

 

 

https://mooneegee.blogspot.com/2015/02/directx9-world-transform-viewing.html

 

[DirectX9]월드 변환(World Transform), 뷰 변환(Viewing Transform), 투영 변환(Projection Transform)

기계식 키보드, 프로그래밍, 컴퓨터 관련 정보, IT 제품 관련 블로그입니다.

mooneegee.blogspot.com

 

픽셀 셰이더 작성

화면에 그릴 픽셀에 대한 설정을 하는 셰이더입니다.

//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
    return input.color;
}

버텍스 셰이더와 비슷합니다.

셰이더의 메인 함수는 input의 색상값을 반환하는 간단한 구조입니다.

 

ColorShaderClass 헤더

#pragma once

class ColorShaderClass : public AlignedAllocationPolicy<16>
{
  private:
    struct MatrixBufferType
    {
    XMMATRIX world;
    XMMATRIX view;
    XMMATRIX projection;
    };

    public:
    ColorShaderClass();
    ColorShaderClass(const ColorShaderClass&);
    ~ColorShaderClass();

    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, XMMATRIX, XMMATRIX, XMMATRIX);

  private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

    bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX);
    void RenderShader(ID3D11DeviceContext*, int);

    private:
    ID3D11VertexShader* m_vertexShader = nullptr;
    ID3D11PixelShader* m_pixelShader = nullptr;
    ID3D11InputLayout* m_layout = nullptr;
    ID3D11Buffer* m_matrixBuffer = nullptr;
};


MatrixBufferType이라는 구조체를 선언 해 주었습니다.

 

그 뒤 간단하게 초기화, 삭제, 프레임마다 도는 Render함수를 Public으로 선언하고,

Private함수로 Shader관한 함수들을 선언해주었습니다.

 

그 뒤 Directx에서 지원하는 셰이더 및 버퍼 COM들을 선언 해 주었습니다.

ColorShaderClass 초기화: 개요



Input Assembler단계를 만드는 ColoShaderClass 초기화 부분입니다.

 

좌측 상단은 버텍스의 요소를 결정하는 레이아웃을 세팅하는 부분입니다.

우측 상단은 모델의 버텍스들의 데이터와 인덱스를 이용해 버퍼로 넘겨주는 과정입니다.

 

먼저 저희는 위에서 작성한 셰이더를 컴파일 한 후

버텍스의 요소를 결정하는 레이아웃을 세팅 할 예정입니다.

 

https://m.blog.naver.com/PostView.nhn?blogId=sorkelf&logNo=40162437338&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

DirectX 11 기초 (Tutorial) - 입력 어셈블러 스테이지 Input Assembler Stage

입력 어셈블러 스테이지(Input Assembler 이하 IA) 는 렌더링 파이프라인의 젤 처음 스테이지이자 어플...

blog.naver.com

 

ColorShaderClass 초기화 : Shader 컴파일

GraphicsClass의 초기화 함수에서 ColorShaderClass를 초기화 시켜줍니다.

// m_ColorShader 객체 생성
m_ColorShader = new ColorShaderClass;
if (!m_ColorShader)
{
	return false;
}

// m_ColorShader 객체 초기화
if (!m_ColorShader->Initialize(m_Direct3D->GetDevice(), hwnd))
{
  MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
  return false;
}


Initialize함수의 매개변수로 D3DClass의 Device와 윈도우 핸들을 보냈습니다.

bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
  // 정점 및 픽셀 쉐이더를 초기화합니다.
  return InitializeShader(device, hwnd, L"../Dx11Demo_04/color.vs", L"../Dx11Demo_04/color.ps");
}

ColorShaderClass의 Initialize함수입니다.

이 부분은 다시 InitializeShader함수를 실행하여 그 결과 값을 반환합니다.

bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
  ID3D10Blob* errorMessage = nullptr;

      // 버텍스 쉐이더 코드를 컴파일한다.
  ID3D10Blob* vertexShaderBuffer = nullptr;
  if(FAILED(D3DCompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &vertexShaderBuffer, &errorMessage)))
  {
    // 셰이더 컴파일 실패시 오류메시지를 출력합니다.
    if(errorMessage)
    {
    	OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
    }
    // 컴파일 오류가 아니라면 셰이더 파일을 찾을 수 없는 경우입니다.
    else
    {
    	MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
    }

    return false;
    }

  .

  .

  .

  .

  .

}

InitializeShader함수의 초입부분입니다.

 

먼저 버텍스 셰이더 코드를 컴파일하는 부분입니다.

 

ID3D10Blob

 

ID3D10Blob은 임의의 길이의 데이터를 반환하는 인터페이스입니다.

이 인터페이스는 정점, 인접성 및 데이터 정보를 저장하는 데이터 버퍼로 사용할 수 있습니다.

또는 셰이더를 컴파일하는 API에서 객체 코드 및 오류 메시지를 반환하는데 사용됩니다.

 

D3DCompileFromFile

 

HLSL코드를 바이트 코드로 컴파일하는 함수입니다.

HRESULT D3DCompileFromFile(
  LPCWSTR                pFileName,
  const D3D_SHADER_MACRO *pDefines,
  ID3DInclude            *pInclude,
  LPCSTR                 pEntrypoint,
  LPCSTR                 pTarget,
  UINT                   Flags1,
  UINT                   Flags2,
  ID3DBlob               **ppCode,
  ID3DBlob               **ppErrorMsgs
);

함수의 형태입니다.

 

pFileName은 컴파일할 파일의 이름입니다.

버텍스 셰이더를 컴파일 할것이기 때문에  L"../Dx11Demo_04/color.vs"값을 넣었습니다.

 

pDefines는 셰이더 매크로를 정의하는 선택적 배열입니다.

따로 정의를 하지 않는다면 NULL로 설정합니다.

 

pInclude는 컴파일러가 Include파일을 처리하는데 사용하는 ID3DInclude 인터페이스에 대한 선택적 포인터입니다.

만약 NULL을 매개변수로 넣고 HLSL셰이더 코드에 #Include가 있다면 오류가 발생합니다.

 

pEntryPoint는 셰이더 함수의 진입점입니다.

Color.vs의 메인함수격인 ColorVertexShader을 매개변수로 넘겨주었습니다.

 

pTarget은 컴파일 할 컴파일러를 지정합니다.

ds_5_0은 Domain Shader

gs_5_0은 Geomety Shader

hs_5_0은 Hull Shader

ps_5_0은 Pixel Shader

vs_5_0은 Vertex Shader

각각의 셰이더를 가르킵니다.

 

Flag1과 Flag2는 OR연산을 사용하여 만드는 컴파일 옵션입니다.

 

**ppCode는 컴파일 된 코드에 엑세스 하는데 사용할 수 있는 ID3DBlob를 받습니다.

 

**ppErrorMsgs는 컴파일러 오류 메시지를 엑세스를 할 수 있습니다.

오류가 없을경우 Null입니다.

 

픽셀 셰이더도 똑같은 함수를 사용하며 컴파일을 합니다.

 

ColorShaderClass 초기화 : Shader 객체 생성

// 버퍼로부터 정점 셰이더를 생성한다. if(FAILED(device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader)))
{
	return false;
}

 

디바이스에 컴파일한 버텍스 셰이더를 생성합니다.

 

첫번째 두번째는 컴파일한 바이너리 코드의 포인터와 길이를 전달해 주고,

 

세번째는 클래스 연계 인터페이스에 대한 포인터를 넣어줍니다.

NULL을 넣을 수 있습니다.

 

네번째는 초기화 할 ID3D11VertexShader 인터페이스에 대한 주소입니다.

 

// 버퍼에서 픽셀 쉐이더를 생성합니다.
if(FAILED(device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader)))
{
	return false;
}

픽셀 셰이더도 똑같이 진행합니다.

 

ColorShaderClass 초기화 : 정점 구조체 정의

버텍스는 여러가지 정보를 가지고 있습니다.

기본적으로 위치값을 갖기도 하고, Normal값, Color값 등 여러가지 값을 가지고 있습다.

이를 구조체를 이용하여 미리 정해주어야 합니다.

// 정점 입력 레이아웃 구조체를 설정합니다.
// 이 설정은 ModelClass와 셰이더의 VertexType 구조와 일치해야합니다.
D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
polygonLayout[0].SemanticName = "POSITION";
polygonLayout[0].SemanticIndex = 0;
polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[0].InputSlot = 0;
polygonLayout[0].AlignedByteOffset = 0;
polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[0].InstanceDataStepRate = 0;

polygonLayout[1].SemanticName = "COLOR";
polygonLayout[1].SemanticIndex = 0;
polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[1].InputSlot = 0;
polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[1].InstanceDataStepRate = 0;

생성할 정점의 개수만큼 구조체 배열의 길이를 늘립니다.

D3D11_INPUT_ELEMENT_DESC 구조체는 이와같은 속성을 가집니다.

SemanticName

시맨틱 이름은 이 요소를 설명하는 단어입니다.

SemanticIndex

시맨틱 인덱스는 시맨틱 이름을 보충합니다.

예를들어 POSITION이란 시맨틱이 2개가 있을 때 이름을 POSITION0, POSITION1같이 설정하는게 아닌 SemanticIndex를 설정합니다.

Format

포맷은 데이터타입을 정의합니다.

DXGI_FORMAT_R32G32B32_FLOAT은 3개의 32비트 부동소수점을 가집니다.

InputSlot

D3D11 어플리케이션은 버텍스데이터를 동시에 16개의 데이터 를 GPU로 보낼 수 있습니다.

이 버퍼들은 0~15까지의 버텍스 버퍼 입력 슬롯에 바인드가 됩니다.


InputSlot은 GPU에게 이 요소를 어떤 버텍스 버퍼로부터 가져와야되는지 지정합니다.


D3D10부터는 여러개의 버텍스 버퍼를 하나의 배열에다 정의하기 때문에 이런 슬롯을 구분해야하기 때문에 값을 설정해주어야 합니다.

AlignedByteOffset

버텍스는 버텍스 버퍼에 저장되는데, 이 값은 GPU가 버텍스 메모리가 시작지점으로부터 얼마나 떨어진 위치에 있는지 가르쳐줍니다.

InputSlotClass

이 속성은 보통 D3D11_INPUT_PER_VERTEX_DATA값을 가집니다.

입력 데이터가 인스턴스 별 데이터라면 D3D11_INPUT_PER_INSTANCE_DATA를 대입합니다.

InstanceDataStepRate

이 필드에서 인스턴싱을 위해 사용됩니다. 사용하지 않는다면 0으로 설정합니다.

 

http://m.blog.daum.net/tjdgmlrnsk/11

 

DIRECTX11 다이렉트x 기초

다이렉트 x 11 프레임워크의 기초 삼각형을 그리기 위해선 이 3개의 점을 GPU로 보내야한다. D3D11에서 위치같은 버텍스정보는 버퍼리소스에 저장된다. 버텍스정보를 저장하는데 사용되는 버퍼는 당연하게도, 버텍스버퍼라 불린다. 버퍼리

blog.daum.net

 

저희는 float 타입 3개의 POSITION과

float 타입 4개의 COLOR를 버텍스 속성에 넣어주었습니다.

 

이 시맨틱은 버텍스 셰이더 단계에서 매개변수로 넘어가기 위해 잘 정해줍니다.

 

ID3D11InputLayout

 

D3D11은 ID3DInputLayout으로 정점들을 Direct3D에게 전달합니다.

위에서 만들어 놓은 D3D11_INPUT_ELEMENT_DESC를 이용해서 생성합니다

 

Direct x에서는 성능 향상을 위해 파이프라인 설정에 관한 타당성 검증을 렌더링 시가 아닌, 가능한 오브젝트를 찾을때 검사하기 때문에 미리 세팅 된 레이아웃이 필요합니다.

// 레이아웃의 요소 수를 가져옵니다.
unsigned int numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

// 정점 입력 레이아웃을 만듭니다.
if(FAILED(device->CreateInputLayout(polygonLayout, numElements, 
vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout)))
{
    return false;
}

 

초기화 한 D3D11_INPUT_ELEMENT_DESC와, D3D11_INPUT_ELEMENT_DESC의 요소 개수, 그럼 다음에 버텍스 셰이더를 컴파일한 버퍼를 넘겨주어 ID3D11InputLayout을 초기화 해 줍니다.

// 더 이상 사용되지 않는 정점 셰이더 퍼버와 픽셀 셰이더 버퍼를 해제합니다.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;

pixelShaderBuffer->Release();
pixelShaderBuffer = 0;

 

그 뒤 ID3D11VertexShader과 ID3D11PixelShader를 초기화 해주었으니 더 이상 컴파일 한 vertexShaderBuffer와 pixelShaderBuffer을 해제합니다.

 

ColorShaderClass 초기화 : 행렬 상수 버퍼 접근 설정

버텍스 셰이더를 작성할 때 cbuffer 형태로 상수버퍼를 만든 적 있었다.

상수버퍼는 파이프라인 안에서 실행되는 프로그래밍 가능 셰이더에 상수 정보를 제공하는데 쓰입니다.

 

상수라는 이름이 붙은 이유는, 이 상수버퍼안의 자료가 한버너의 그리기 호출이나 배분 호출이 실행되는 동안에는 결코 변하지 않기때문입니다.

 

여기서는 월드, 뷰, 투영 행렬을 담는데.

이는 변하지 않을것이기 때문에 미리 넣어둔다.

 

이러한 매커니즘은 CPU쪽 응용프로그램에서 각 프로그램 가능 셰이더 단계들에게 자료를 공급하는 주된 수단입니다.

// 정점 셰이더에 있는 행렬 상수 버퍼의 구조체를 작성합니다.
D3D11_BUFFER_DESC matrixBufferDesc;
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;

// 상수 버퍼 포인터를 만들어 이 클래스에서 정점 셰이더 상수 버퍼에 접근할 수 있게 합니다.
if(FAILED(device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer)))
{
	return false;
}



D3D11_BUFFER_DESC로 만들 버퍼의 설정을 하여 만듭니다.

MatrixBufferType의 정보를 보낼거기 때문에 사이즈도 그만큼 정해줍니다.

 

이 버퍼의 종류를 BindFlags에서 설정하는데, 상수 버퍼이기 때문에 D3D11_BIND_CONSTANT_BUFFER으로 적용시켜줍니다.

D3D11_BIND_VERTEX_BUFFER와 같이 다른 버퍼로 적용시킬 수 있습니다.

https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_bind_flag

 

D3D11_BIND_FLAG (d3d11.h) - Win32 apps

Identifies how to bind a resource to the pipeline.

docs.microsoft.com

 

그 후엔 ID3D11Device::CreateBuffer함수를 사용하여 ID3D11Buffer형태의 상수 버퍼를 초기화 시켜줍니다.



ColorShaderClass 초기화: 셰이더 파라미터 정의 (상수 버퍼 값 대입)

 

이 코드의 ColorShaderClass는 SetShaderParameters를 이용하여 상수버퍼에게 월드, 뷰, 투영 행렬을 넣어줍니다.

// 행렬을 transpose하여 셰이더에서 사용할 수 있게 합니다
worldMatrix = XMMatrixTranspose(worldMatrix);
viewMatrix = XMMatrixTranspose(viewMatrix);
projectionMatrix = XMMatrixTranspose(projectionMatrix);

DirectX에서 만든 행렬을 전치행렬로 변환시켜 HLSL로 보내주어야 합니다.

HLSL의 구조와 DirectX의 행렬 연산 방식이 서로 뒤집혀져 있기때문에 일어나는 현상입니다.

 

https://mumbi.tistory.com/420

 

[Shader] 전치 행렬( transposed matrix ).

Direct3D9 에서 상수 register 에 vertor 나 색상을 설정하기 위해서는 다음의 함수를 사용한다. //pDevice->SetVertexShaderConstantF( register 번호, data pointer, 설정 register 수 ) float color[] = { 1.0f..

mumbi.tistory.com

// 상수 버퍼의 내용을 쓸 수 있도록 잠급니다.
D3D11_MAPPED_SUBRESOURCE mappedResource;
if(FAILED(deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
{
	return false;
}

// 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
MatrixBufferType* dataPtr = (MatrixBufferType*)mappedResource.pData;

// 상수 버퍼에 행렬을 복사합니다.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;

// 상수 버퍼의 잠금을 풉니다.
deviceContext->Unmap(m_matrixBuffer, 0);

// 정점 셰이더에서의 상수 버퍼의 위치를 설정합니다.
unsigned bufferNumber = 0;

// 마지막으로 정점 셰이더의 상수 버퍼를 바뀐 값으로 바꿉니다.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

 

D3D11_MAPPED_SUBRESOURCE 구조체를 만들어 상수버퍼에 값을 넣도록 준비합니다.

 

Map함수로 상수버퍼에 락을 걸고 값을 쓸 수 있도록 설정합니다.

 

MatrixBufferType타입의 dataPtr변수를 만들고 이 변수안에는 16바이트로 정렬된 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.

 

dataPtr변수에 행렬값들을 복사하여 넣어줍니다.

 

다시 Unmap함수로 상수버퍼의 잠금을 풉니다.

 

VSSetConstantBuffers함수로 0번째에 있는 즉 Color.vs에 맨 처음에 생성된 1개의 cbuffer에게 값을 넣어줍니다.

 

ColorShaderClass 렌더하기

GraphicsClass에서는 매 프레임마다 Render함수를 사용합니다

그 안에는 ColorShaderClass의 Render 함수도 사용되어 있습니다.

// 색상 쉐이더를 사용하여 모델을 렌더링합니다.
if (!m_ColorShader->Render(m_Direct3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix))
{
	return false;
}

 

ColorShaderClass에게 DevicecContext와 모델의 인덱스 개수, 그리고 행렬들을 보냅니다.

bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, 
XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix)
{
  // 렌더링에 사용할 셰이더 매개 변수를 설정합니다.
  if(!SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix))
  {
  	return false;
  }

  // 설정된 버퍼를 셰이더로 렌더링한다.
  RenderShader(deviceContext, indexCount);

  return true;
}

맨 처음에는 상수버퍼에 행렬들을 보내는것이고 그 다음의 함수는 RenderShader로 ModelClass에서 설정된 버퍼로 셰이더를 렌더링합니다.

void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
  // 정점 입력 레이아웃을 설정합니다.
  deviceContext->IASetInputLayout(m_layout);

    // 삼각형을 그릴 정점 셰이더와 픽셀 셰이더를 설정합니다.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

  // 삼각형을 그립니다.
  deviceContext->DrawIndexed(indexCount, 0, 0);
}

 

RenderShader의 함수 내부입니다.

 

ID3D11DivceContext는 렌더링에 관한 기능들을 제공해주기 때문에 렌더링에 필요한 정점 레이아웃과 셰이더들을 설정 해 준 뒤, DrawIndexed를 사용하여 그려줍니다.

물론 단순하게 인덱스 개수만 사용하여 그리는게 아니며, ModelClass에서 버텍스 버퍼로 정보를 보냈기 때문에 바로 가능합니다.

 

그렇기 때문에 바로 ModelClass로 넘어가봅시다.

 

ModelClass헤더

#pragma once

class ModelClass : public AlignedAllocationPolicy<16>
{
  private:
    struct VertexType
    {
      XMFLOAT3 position;
      XMFLOAT4 color;
    };

  public:
    ModelClass();
    ModelClass(const ModelClass&);
    ~ModelClass();

    bool Initialize(ID3D11Device*);
    void Shutdown();
    void Render(ID3D11DeviceContext*);

    int GetIndexCount();

  private:
    bool InitializeBuffers(ID3D11Device*);
    void ShutdownBuffers();
    void RenderBuffers(ID3D11DeviceContext*);

  private:
    ID3D11Buffer* m_vertexBuffer = nullptr;
    ID3D11Buffer* m_indexBuffer = nullptr;
    int m_vertexCount = 0;
    int m_indexCount = 0;
};

ModelClass헤더에서는 VertexLayout과 같은 형태인 VertexType을 정의해 주었습니다.

 

그 뒤 초기화 함수화 프레임마다 돌아가는 Reder함수, 그리고 인덱스개수를 받는 함수가 있습니다.

변수에는 버텍스와 인덱스의 버퍼와 개수를 저장하는 변수가 있습니다.

ModelClass 초기화

GraphicsClass에서 Model클래스를 초기화 해줍니다.

// m_Model 객체 생성
m_Model = new ModelClass;
if (!m_Model)
{
	return false;
}

// m_Model 객체 초기화
if (!m_Model->Initialize(m_Direct3D->GetDevice()))
{
  MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
  return false;
}

초기화 함수에서 ID3D11Device를 보내 ID3D11DeviceContext가 사용할 요소들을 저장합니다.

bool ModelClass::Initialize(ID3D11Device* device)
{
  // 정점 및 인덱스 버퍼를 초기화합니다.
  return InitializeBuffers(device);
}

 

Initialize함수에서는 InitializeBuffers의 결과값을 반환합니다.

 

ModelClass 초기화 : 삼각형 버텍스 배치

// 정점 배열의 정점 수를 설정합니다.
m_vertexCount = 3;

// 인덱스 배열의 인덱스 수를 설정합니다.
m_indexCount = 3;

// 정점 배열을 만듭니다.
VertexType* vertices = new VertexType[m_vertexCount];
if(!vertices)
{
	return false;
}

// 인덱스 배열을 만듭니다.
unsigned long* indices = new unsigned long[m_indexCount];
if(!indices)
{
	return false;
}

// 정점 배열에 데이터를 설정합니다.
vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f);  // Bottom left.
vertices[0].color = XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f);

vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f);  // Top middle.
vertices[1].color = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);

vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);  // Bottom right.
vertices[2].color = XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f);

// 인덱스 배열의 값을 설정합니다.
indices[0] = 0;  // Bottom left.
indices[1] = 1;  // Top left.
indices[2] = 2;  // Bottom right.


버텍스의 position과 color값을 가지고 있는 VertexType배열과 그런 버텍스의 순서를 결정하는 배열인 indices가 있습니다.

 

버텍스와 인덱스

버텍스를 배치하기 전에 한 폴리곤이 만들어지는 방법에 대해서 말해봐야 합니다.

 

일단 버텍스는 점 그자체를 나타냅니다.

삼각형이면 버텍스가 3개

사각형이면 4개,

정육면체면 8개가 됩니다.

 

인덱스는 그 점의 순서를 기록합니다.

이 순서가 중요한 이유는 물체의 앞 뒤를 구분하는 방식이기 때문인데, 위 사진의 왼쪽이 앞면, 오른쪽이 뒷면입니다.

 

즉 버텍스의 순서를 시계 방향으로 정해주어야 렌더링이 정상적으로 되며, 시계 반대방향으로 정해줄 시 렌더링이 되지 않습니다.

 

 

사각형의 같은 경우엔 삼각형 두개가 합쳐져 있는 모습입니다.

그렇기때문에 버텍스는 4개로 늘어나지만 인덱스는 삼각형 하나마다 정해주어야 하기때문에 개수가 삼각형의 개수만큼 늘어납니다.

 

이제 다시 위의 코드를 보면 어떤식으로 배열을 구성했는지 이해할 수 있습니다.

 

ModelClass 초기화 : 버퍼 생성

// 정적 정점 버퍼의 구조체를 설정합니다.
D3D11_BUFFER_DESC vertexBufferDesc;
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

// subresource 구조에 정점 데이터에 대한 포인터를 제공합니다.
D3D11_SUBRESOURCE_DATA vertexData;
    vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

// 이제 정점 버퍼를 만듭니다.
if(FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer)))
{
	return false;
}

정점 버퍼를 만드는 과정은 상수버퍼를 만드는 과정과 유사합니다.

하지만 여기서 새로운 구조체인 D3D11_SUBRESOURCE_DATA를 선언해주었습니다.

이는 버퍼의 부가적인 데이터를 넣어줍니다.

 

여기서는 vertices를 버퍼를 통해 전해줄거기 때문에 pSysMem의 값을 vertices로 정해주었습니다.

 

그 뒤 CreateBuffer를 사용하여 정점 버퍼를 만들어줍니다,

// 정적 인덱스 버퍼의 구조체를 설정합니다.
D3D11_BUFFER_DESC indexBufferDesc;
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// 인덱스 데이터를 가리키는 보조 리소스 구조체를 작성합니다.
D3D11_SUBRESOURCE_DATA indexData;
    indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

// 인덱스 버퍼를 생성합니다.
if(FAILED(device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer)))
{
	return false;
}

// 생성되고 값이 할당된 정점 버퍼와 인덱스 버퍼를 해제합니다.
delete [] vertices;
vertices = 0;

delete [] indices;
indices = 0;


인덱스 버퍼도 버텍스 버퍼와 유사하게 만들어집니다.

 

ModelClass 렌더

ModelClass는 GraphicsClass의 Render함수에서 실행됩니다.

// 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치하여 드로잉을 준비합니다.
m_Model->Render(m_Direct3D->GetDeviceContext());

 

이 Render은 ColorShaderClass의 Render가 실행되기 전에 호출이 되어야 버퍼에 모델 정보를 넣어준뒤 후에 정삭적으로 사용할 수 있습니다.

 

ModelClass의 Render함수에서는 RenderBuffers를 그대로 실행한다.

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
  // 정점 버퍼의 단위와 오프셋을 설정합니다.
  unsigned int stride = sizeof(VertexType); 
  unsigned int offset = 0;

  // 렌더링 할 수 있도록 입력 어셈블러에서 정점 버퍼를 활성으로 설정합니다.
  deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

      // 렌더링 할 수 있도록 입력 어셈블러에서 인덱스 버퍼를 활성으로 설정합니다.
  deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

      // 정점 버퍼로 그릴 기본형을 설정합니다. 여기서는 삼각형으로 설정합니다.
  deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

 

여기서는 버텍스버퍼와 인덱스버퍼를 등록하기만 하면 됩니다.

 

그 뒤 IASetPrimitiveTopolgy함수를 이용하여, 렌더하는 물체의 최소 단위도형을 삼각형으로만 이루어지도록 설정합니다.



CameraClass

CameraClass는 뷰 행렬을 만들기 위한 클래스입니다.

뷰 행렬을 CameraClass로 만들어 놓아 나중에 카메라를 조작할 수 있도록 할 수 있습니다.

void CameraClass::Render()
{
  XMFLOAT3 up, position, lookAt;
  XMVECTOR upVector, positionVector, lookAtVector;
  float yaw, pitch, roll;
  XMMATRIX rotationMatrix;


  // 위쪽을 가리키는 벡터를 설정합니다.
  up.x = 0.0f;
  up.y = 1.0f;
  up.z = 0.0f;

  // XMVECTOR 구조체에 로드한다.
  upVector = XMLoadFloat3(&up);

  // 3D월드에서 카메라의 위치를 ​​설정합니다.
  position = m_position;

  // XMVECTOR 구조체에 로드한다.
  positionVector = XMLoadFloat3(&position);

  // 기본적으로 카메라가 찾고있는 위치를 설정합니다.
  lookAt.x = 0.0f;
  lookAt.y = 0.0f;
  lookAt.z = 1.0f;

  // XMVECTOR 구조체에 로드한다.
  lookAtVector = XMLoadFloat3(&lookAt);

  // yaw (Y 축), pitch (X 축) 및 roll (Z 축)의 회전값을 라디안 단위로 설정합니다.
  pitch = m_rotation.x * 0.0174532925f;
  yaw = m_rotation.y * 0.0174532925f;
  roll = m_rotation.z * 0.0174532925f;

  //  yaw, pitch, roll 값을 통해 회전 행렬을 만듭니다.
  rotationMatrix = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);

  // lookAt 및 up 벡터를 회전 행렬로 변형하여 뷰가 원점에서 올바르게 회전되도록 합니다.
  lookAtVector = XMVector3TransformCoord(lookAtVector, rotationMatrix);
  upVector = XMVector3TransformCoord(upVector, rotationMatrix);

  // 회전 된 카메라 위치를 뷰어 위치로 변환합니다.
  lookAtVector = XMVectorAdd(positionVector, lookAtVector);

  // 마지막으로 세 개의 업데이트 된 벡터에서 뷰 행렬을 만듭니다.
  m_viewMatrix = XMMatrixLookAtLH(positionVector, lookAtVector, upVector);
}

 

이 Render함수도 GraphicsClass의 Render함수에서 바로 시작되기 때문에 바로 봅시다.

 

Yaw Pitch Roll

먼저 들어가기 전에 Yaw Pitch Roll에 대해서 알아봅시다.

 

이는 회전하는 기준점입니다.

 

Yaw는 Y를 기준으로 돌아가는 축

Pitch는 X를 기준으로 돌아가는 축

Roll은 Z를 기준으로 돌아가는 축입니다.

 

이는 라디안 값을 사용하기 때문에 일반적인 오일러 각에서 0.0174532925f를 곱해주어야 합니다.

 

이를 XMMatrixRotationRollPitchYaw함수를 통해 회전 행렬을 만들수 있습니다.

 

카메라 구성요소

 

 

Directx에서 카메라의 구성요소를

 

카메라의 포지션, 카메라가 볼 포인트, 카메라의 상단으로 놓아 이를 통해 뷰 행렬을 생성합니다.

// lookAt 및 up 벡터를 회전 행렬로 변형하여 뷰가 원점에서 올바르게 회전되도록 합니다.
lookAtVector = XMVector3TransformCoord(lookAtVector, rotationMatrix);
upVector = XMVector3TransformCoord(upVector, rotationMatrix);

// 회전 된 카메라 위치를 뷰어 위치로 변환합니다.
lookAtVector = XMVectorAdd(positionVector, lookAtVector);

// 마지막으로 세 개의 업데이트 된 벡터에서 뷰 행렬을 만듭니다.
m_viewMatrix = XMMatrixLookAtLH(positionVector, lookAtVector, upVector);

 

이 부분에서는 yaw pitch roll을 이용하여 카메라가 돌아간 만큼의 회전행렬을 구한 다음

(0,1,0)로 초기화가 되어있는 카메라의 UpVector을 돌려줍니다.

 

그 뒤 카메라의 앞인 lookAtVector도 (0,0,1)로 초기화가 되있던것을 회전시켜 새로 새로 뷰어 위치를 생성합니다.

 

그 뒤 XMMatrixLookAtLH함수를 사용하여 뷰 행렬을 만들어줍니다.



프로그램 실행

프로그램 실행입니다.

 

저는 색상을 바꾸어 이렇게 됩니다.

이는 버텍스 셰이더 이후의 파이프라인에서 자동으로 색상값을 보정해주었기 때문에 자연스럽게 보입니다.

 


3번째의 글도 드디어 이식을 했네요.

아직 텍스쳐나 조명 등 해놓았지만 티스토리에 올리지 않은것들이 있는데.

따로 진도를 더 빼놓고서 올려야겠습니다.

댓글