Post Lists

레이블이 [Programming] LearnDirectX인 게시물을 표시합니다. 모든 게시물 표시
레이블이 [Programming] LearnDirectX인 게시물을 표시합니다. 모든 게시물 표시

2019년 1월 15일 화요일

DirectX 11 Braynzar Soft Tutorials - 04. Begin Drawing!

https://www.braynzarsoft.net/viewtutorial/q16390-4-begin-drawing

쉐에더 파이프라인..

Programmable Graphics Rendering Pipeline
만약 너가 Direct3D 10 경험이 있고, 그 파이프라인을 이해한다면, 너는 이러한 파이프라인 섹션의 대부분을 생략할 수 있다. 왜냐하면 Direct3D는 기본적으로 Direct3D 10의 확장이기 때문이다. 거기에서 그것은 같은 pipeline을 상요하지만, 몇 가지 추가 단계들이 있따. Direct3D 11은 그 프로그래밍 가능한 그래픽스 파이프라인에 3 개의 새로운 단계들을 추가했다. 그 뿐만 아니라, 그것은 또한 또 다른 별개의 그렇지만 느슨하게 연결된 파이프라인을 지원한다. 그것은 compute shader pipeline라고 불려진다. 그래픽스 렌더링 파이프라인(일명 draw pipeline)에서 그 세 개의 새로운 단계들은 Hull, Tesselator, and Domain shaders들이다. 그것들은 tesselation과 관련이 있는데, 그것은 기본적으로 한 오브젝트에 좀 더 많은 detail을 추가하는 것이다, 아주 많은 디테일을. 그것이 예제로 하는 것은 model로 부터 한 간단한 삼각형을 받아서, 더 많은 삼각형을 만들기 위해 좀 더 많은 정점들을 추가할지도 모르고, 그러고나서 그 삼각형이 좀 더 detailed해지도록 정점들을 재배치 한다. 그것은 simple low polygon model을 취해서 스크린에 보일 때 즈음엔 그것을 매우 디테일한 high poly model로 바꿀 수있다. 그것은 이 모든 것을 매우 빠르고 효율적으로 한다. 이것은 우리가 이 강의에서 구현하는 것을 배우지 않을 고급 주제이다.

그 compute shader(일명 Dispatch Pipeline)은 GPU를 일종의 병렬 프로세서의 종류로 사용하여 CPU의 연산력을 확장하여 매우 빠른 연산을 하기 위해 사용된다. 이것은 그래픽스와 관ㄹ녀이 있는 어떤 것을 가질 필요가 없다. 예를들어, 너는 매우 성능이 비싼 연산을 할 수 있다, compute shader pipeline을 사용해서 정확한 충돌 탐지 같은, gpu에서. 그 compute shader는 이 강의에서 이야기 되지 않을 것이다.

그 렌더링 파이프라인은 가상 카메라가 보는 것을 기반으로 2d image를 생성하기 위해 direct3d가 사용하는 단계들의 집합이다. 그것은 Direct3D 10에서 사용된 7개의 스테이지들과, Direct3D 11에 수반되는 3 개의 새로운 단계들이 있다. 그리고 다음과 같다:

1. Input Assembler (IA) Stage
2. Vertex Shader (VS) Stage
3. Hull SHader(HS) Stage
4. Tesselator Shader (TS) Stage
5. Domain Shader (DS) Stage
6. Geometry Shader (GS) Stage
7. Stream Output (SO) Stage
8. Rasterizer(RS) Stage
9. Pixel Shader (PS) Stage
10. Output Merger (OM) Stage

또 다른 것은 우리가 이제 각 쉐이더를 컴파일 해야만 하는 것이다. 이것은 그 쉐이더가 에러가 없도록 해야한다. 또한, effect file의 한 technique을 active technique으로 (shaders들의 연속) 설정하는 것 대신에, 우리는 우리가 코드에서 원할 때 마다 개별 쉐이더들을 설정할 수 있다. 나는 이것이 좀 더 dynamic approach라고 믿는다. 그것이 우리에게 그 active shaders들을 바꿀 더 많은 자유를 주기 때문이다. 동시에 다른 것들을 active shaders로 유지하면서. 예를들어, 우리는 final pixel color를 결정하는 lighting calculations을 상요하는 pixel shader를 lighting equations을 사용하지 않는 pixel shader로 바꿀 수 있다. 동시에 그 같은 vertex shader를 여전히 active하게 유지하면서.

그 둥그런 스테이지들이 "프로그래밍 가능한" 단계들이다. 거기에서 우리는 그것들을 생성할 수 있다. 그 square stages는 우리가 프로그램 하지 않는 것들이지만, 우리는 direct3d 11 device context를 사용해서 그것의 설정을 바꿀 수 있다.

Input Assembler (IA) Stage
너가 볼 수 있는 첫 번째 단계는 Input Assembler(IA)이다. 그 IA는 fixed function stage이고, 이것은 우리가 그것을 구현하는 프로그래밍을 할 수 없다는 것을 의미한다. 그 IA는 geometric data, vertices and indices를 읽는다. 그러고나서 그것은 삼각형, squares, lines, 그리고 점들 같은 geometric primitives를 생성하기 위해 그 데이터를 사용한다. 그 데이터는 다른 단계로 넘겨지거나 사용될 것이다. Indices는 그 primitives가 정점들로 어떻게 함께 모여야 하는지를 정의한다. 우리는 나중의 강의에서 indices를 이야기할 것이다.

IA에 어떤 것을 보내기전에, 우리는 처음에 두 가지 것을 할 필요가 있다. 한 버퍼를 생성하고, Primitive Topology, Input layout, and active buffers를 설정하는 것이다.

우선, 우리는 한 버퍼를 생성한다. IA에 의해 사용되는 두 개의 버퍼들은 vertex and index buffers이다. 이 강의에서 우리는 아직 index buffer에 걱정하지 않을 것이다. 그 버퍼를 생성하기 위해서, 우리는 D3D11_BUFFER_DESC 구조체를 채울 것이다.

버퍼 또는 버퍼들을 생성한 후에, 우리는 input-layout object를 생성할 필요가 있다. 이것이 하는 것은 direct3d에게 우리의 vertex 구조체가 어떻게 구성되는지를 말하고, 우리의 vertex 구조체에서 각 컴포넌트로 무엇을 할지를 말하는 것이다. 우리는 direct3d에게 D3D11_INPUT_ELEMENT_DESC elements로 정보를 제공한다. D3D11_INPUT_ELEMENT_DESC 배열에 있는 각 element는 vertex structure에 있는 한 원소를 설명한다. 만약 너의 Vertex structure가 position element, and a color element를 가지고 있따면, 그러면 너의 D3D10_INPUT_ELEMENT_DESC 배열은 그 포지션과 컬러에 대해 한 원소씩 가지고 있을 것이다. 여기에 예시가 있다:

~~

Vertex Shader (VS) Stage
VS는 첫 번째 프로그래밍 가능한 쉐이더이고, 우리가 그것을 코딩해야하는 것을 의미한다. VS Stage는 primitives가 AI에서 조립되고난 후에 모든 정점들이 거치는 곳이다. 모든 그려지는 vertex는 VS를 통해 넣어질 것이다. VS로, 너는 transformation, scaling, lighting, 텍스쳐를 위한 displacement mapping과 같은 것을 할 수 있다. vs는 항상 작동하는 파이프라인을 위해 구현되어야 한다, 비록 그 프로그램에 있는 정점들이 수정될 필요가 없을지라도. 그 파이프라인에 있는 쉐이더들은 HLSL 언어로 쓰여진다. 그 언어는 C++ 문장과 유사하고, 그래서 배우기에 어렵지 않다. 나는 우리가 그것을 바꾸는 각 레슨마다 그 effects file을 설명할 것이고, 나중에 우리는 HLSL만 보는 강의를 가질 것이다. 이 강의에 대해, 우리의 Vertex Shader는 어떠한 것도 하지 않는다. 그래서 우리는 그것을 수정하지 않고 각 vertex의 position을 반환한다. 그것은 이것처럼 보인다:

~~

Hull Shader (HS) Stage
HS는 direct3d 11 그래픽스 렌더링 파이프라인에 추가된 세 개의 새로운 부가 단계들 중 하나이다. 그 세 개의 새로운 단계들인 Hull Shader Stage, Tessellator Stage, Domain Shader Stage는 모두 tesselation이라고 불려지는 것을 구현하기 위해 작동한다. tesselation이 하는 것은 삼각형 또는 직선같은 primitive object를 취하고, 모델들의 세부사항을 증가시키기 위해 그것을 많은 작은 부분들로 빠르게 나눈다. 그것은 스크린에 넣어지기전에 GPU에서 모든 이러한 새로운 primitives를 생성하고, 그것들은 메모리에 저장되지 않는다. 그래서 이것은 CPU와 메모리에서 그것들을 생성하는 것보다 시간을 더 절약한다. 너는 간단한 low poly model을 가져와서 그것을 tessellation을 사용해서 매우 높은 디테일의 poly로 바꿀 수 있다.

그래서 Hull Shader로 돌아와서. 이것은 또 다른 프로그래밍 가능한 단계이다. 나는 상세히 들어가지 않을 것이지만, 이 스테이지가 하는 것은 한 primitive를 더 상세하게 만들기 위해 새로운 정점들을 어떻게 그리고 어디에 추가 할 지를 계산하는 것이다. 그것은 그러고나서 이 데이터를 Tessellator Stage와 Domain Shader Stage로 보낸다.

Tessellator (TS) Stage
그 tessellator stage는 tessellation process에서 두 번째 단계이다. 이것은 Fixed Function stage이다. 이 스테이지가 하는 것은 Hull Shader로부터 입력을 취하고, 실제로 그 primitive를 나누기를 한다. 그것은 그러고나서 그 데이터를 Domain Shader로 전달한다.

Domain Shader (DS) Stage
이것은 tessellation process에서 세 개의 스테이지들 중 세 번째이다. 이것은 프로그래밍 가능한 함수 스테이지이다. 이 스테이지가 하는 것은 Hull Shader STage로부터 새로운 정점들의 위치를 취하고, 좀 더 디테일을 만들기 위해 tessellator로부터 받은 정점들을 transform 한다. 삼각형 또는 직선의 중심에서 더 많은 정점을 추가하는 것은 어떤 방식으로든 그 디테일을 증가시키지 않을 것이기 때문이다. 그러고나서 그것은 geometry shader stage에 정점을 보낸다.

Geometry Shader (GS) Stage
이 쉐이더 스테이지는 선택적이다. 그것은 또한 또 다른 프로그래밍 가능한 함수 스테이지이다. 그것은 입력으로 삼각형을 위해 3개의 정점, 직선을 위해 2개, 점을 위해 한 개 같은 primitives를 받는다. 그것은 또한 입력으로 edge-adjacent primitives의 데이터를 취할 수 있다. 그것은 한 직선에 대해 추가적인 2개의 정점, 한 삼각형을 위해 세 개의 추가적인 정점과 같다. GS에 대한 이점은 그것이 primitives를 생성하고 파괴할 수 있따는 것이다. 거기에서 VS는 할 수 없는 것이다 (VS는 한 정점을 취하고 그것을 output한다). 우리는 한 점을 quad 또는 삼각형으로 이 단계와 함께 만들 수 있다. 우리는 GS에서 rasterizer 단계로 데이터를 넘길 수 있고, and/or Stream Output 를 통해서 메모리에 있는 vertex buffer로 보낼 수 있다. 우리는 나중의 강의에서 이 쉐이더 단계에 대해 좀 더 배울 것이다.

Stream Output (SO) Stage
이 단계는 파이프라인의 Vertex data를 얻기위해 사용된다. 특히 만약 GS가 없다면 Geometry Shader stage또는 Vertex Shader Stage로 부터. SO를 통해 메모리에 보내진 vertex data는 한 개 이상의 vertex buffers에 넣어진다. SO로부터 온 Vertex data output은 line lists or triangle lists와 같은 lists들로 항상 보내진다. 불완전한 primitives는 결코 보내지지 않는다. 그것들은 그냥 조용히 vertex와 geometry에서 처럼 버려진다. 불완전한 primitives는 오직 2개의 정점만 있는 삼각형 또는 한 개의 정점만 있는 직선과 같은 prmitives이다.

Rasterization Stage (RS) Stage
RS stage는 그것에 보내진 (shapes and primitives) 벡터 정보들을 취하고, 그것들을 각 primitive에 대해 per-vertex values들을 보간하여 픽셀들로 바꾼다. 그것은 또한 clipping을 다룬다. clipping은 기본적으로 screen view의 밖에 있는 primitives를 잘라내는 것을 말한다. 우리는 RS에서 다음을 사용해서 viewport를 설정한다.

Pixel Shader (PS) Stage
이 단계는 스크린에서 보일 각 픽셀을 계산하고 수정한다. 픽셀 단위 기반의 라이팅 같은. 그것은 또 다른 프로그래밍 가능한 함수 그리고 선택적인 단계이다. RS는 한 primitive에서 각 픽셀에 대해 한 번씩 pixel shader를 불러온다. 우리가 이전에 말했던 것 처럼, 한 primitive에서 각 정점의 값과 attributes는 RS에서 전체 PRIMITIVE를 가로질러 보간된다. 기본적으로 그것은 vertex shader와 비슷한데, 거기에서 vertex shader는 1:1 mapping을 가진다 (그것은 한 정점을 취하고 한 정점을 반환한다), 그 Pixel Shader는 또한 1:1 매핑을 가진다 (한 픽셀을 취하고 한 픽셀을 반환한다).

픽셀 쉐이더의 일은 각 픽셀 fragment의 최정 컬러를 계산하는 것이다. pixel fragment는 스크린에 그려진 각 잠재적 픽셀이다. 예를들어, solid circle 뒤에 있는 solid square가 있다. square에 있는 픽셀들은 pixel fragments이고, 원에 있는 픽셀들은 pixel fragments이다. ~~~

Output Merger (OM) Stage
 파이프라인에서 마지막 단계는 Output Mergetr Stage이다. 기본적으로 이 단계는 pixel fragments와 depth/stencil buffers를 취하고, 어떤 픽셀들이 실제로 render target에 쓰일지를 결정한다. 우리는 마지막 가으이에서 다음을 호출하여 render target을 설정한다.

우리는 이것에 대해 나중에 이야기할 수 있다.

OM stage이후에, 해야 할 남은 것은 우리의 backbuffer를 스크린에 보여주는 것이다. 우리는 다음을 호출하여 이것을 할 수 있다.






















DirectX Tutorial Lesson 5: Drawing a Triangle

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-4-5

Lesson Overview
이곳이 우리가 실제로 어떤 것을 그리게 될 튜토리얼의 첫 번째 부분이다. 이 강의에서, 너는 스크린에서 삼각형을 그리는 법을 배울 것이다. 우리는 일련의 정점을 생성하고 하드웨어가 그것들을 스크린에 그리게 하여 이 삼각형을 구성할 것이다.

이것은 많은 코드가 필요하다. 나는 삼각형을 렌더링하는 것이 풀 스크린으로 가는 것 만큼 빠른척 하지 않을 것이지만, 그것은 끝에서 확실히 이해될 것이고, 상황은 우리가 진행함에 따라 더 쉬워질 것이다. 그 동안 들어가 보자.

삼각형을 렌더링 하는 것은 발생하는 많은 행동들을 요구한다. 이 강의는 길지만, 이러한 부분들로 분해된다:


  1. 우리는 GPU에게 우리의 geometry 렌더링을 어떻게 할 지를 말한다.
  2. 우리는 우리의 삼각형의 세 개의 정점들을 생성한다.
  3. 우리는 이러한 정점을 video memory에 저장한다.
  4. 우리는 GPU에게 이러한 정점들을 어떻게 읽을지를 말한다.
  5. 우리는 마지막으로 삼각형을 렌더링한다.
좋은 소식은 이러한 단계뜰 각각은 쉽다. 만약 우리가 한 번에 하나씩 한다면, 이 강의는 금방 끝날 것이다!

Using Shaders
첫 번째 단계를 이야기 하여 시작해보자. GPU에게 우리의 삼각형 렌더링을 어떻게 할 지에 대해 말하면서.
렌더링 프로세스는 렌더링 파이프라인에 의해 통제된다. 만약 너가 강의 1을 기억한다면, 너는 렌더링 파이프라인이 렌더링된 이미지를 만들어내는 연속의 단계들이라는 것을 상기할 것이다. 불행하게도, 그 파이프라인은 자동적으로 무엇을 할지 알지 못한다. 그것은 처음에 프로그래밍 되어야 하고, 그것은 쉐이더에 의해서 프로그래밍 된다.

쉐이더는 잘못 만들어진 용어인데, 쉐이더가 shade를 제공하지 않기 때문이다. 쉐이더는 실제로 파이프라인의 한 단계를 통제하는 mini-program이다.

몇 가지 다른 종류의 쉐이더들이 있고, 각각은 렌더링 동안 몇 번 작동된다. 예를들어, vertex shader는 그려질 각 정점에 대해 한 번씩 작동하고, 반면에 pixel shader는 그려질 각 pixel에 대해 작동하는 프로그램이다.

우리는 다음 강의에서 쉐이더 프로그래밍에 들어갈 것이다. 지금 당장, 우리는 삼각형을 렌더링할 것이고, 그것을 하기 위해서, 우리는 GPU에 어떤 쉐이더들을 불러와야만 한다.

쉐이더들을 불러오는 것은 몇 가지 단계들을 거친다. 여기에 우리가 해야할 것이 있다:
  1. .shader file로부터 두 개의 쉐이더들을 불러오고 컴파일 한다.
  2. 두 쉐이더들을 shader objects에 캡슐화한다.
  3. 두 쉐이더들을 active shaders로 설정한다.
이러한 단계들 각각은 매우 쉽다. 그것들을 빨리 해보자.

-------------------------------------------
Shader 컴파일 부분인데, 나는 d3dx 안쓰고 DirectXTK를 써서 하므로,
D3DCompileFormFile 함수를 쓴다. 그래서 msdn의 내용을 해석하고 공부하겠다.
함수에 대한 내용은 여기에

함수의 사용 코드 예제는 여기에

D3DCompileFromFile : Microsoft High Level Shader Language(HLSL) 코드를 주어진 타겟에 대해 bytecode로 컴파일한다.

Syntax


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

Parameters
- pFileName
쉐이더 코드를 포함하는 파일의 이름을 포함한 null로 끝나는 constant string에 대한 포인터.

- pDefines
shader macros를 정의하는 D3D_SHADER_MACRO의 부가적 배열. 각 macro definition은 한 이름과 NULL로 끝나는 정의를 포함한다. 만약 사용되지 않는다면 NULL로 설정.

- pInclude
Include 파일들을 처리하기 위해 컴파일러가 사용하는 ID3DInclude 인터페이스에 대한 optional pointer. 만약 너가 이 파라미터를 NULL로 설정하고, 그 shader가 #include를 포함한다면, compile 에러가 발생한다. 너는 D3D_COMPILE_STANDARD_FILE_INCLUDE 매크로를 넘길 수 있다. 이것은 default include handler에 대한 포인터이다. 이 default include handler는 현재 directory를 기준으로 handler includes files들을 포함한다.


- pEntryPoint
쉐이더 실행이 시작하는 shader entry point function의 이름을 포함하는 constant인 null 로 끝나는 스트링에 대한 포인터이다. 너가 한 effect를 컴파일 할 때, D3DCompileFromFile은 pEntrypoint를 무시한다; 우리는 너가 pEntrypoint를 NULL로 설정하기를 추천한다. 왜냐하면 만약 그 호출된 함수가 그것을 사용하지 않을 것이라면 그것은 pointer parameter를 NULL로 처리하는 것은 좋은 프로그래밍 연습이다.

- pTarget
컴파일 할 shader features의 집합 또는 쉐이더 타겟을 명시하는 constant인 null로 종료하는 string에 대한 포인터이다. 그 쉐이더 타겟은 shader model이 될 수 있다 (예를들어, shader model 2, shader model 3 4 , 5 and later). 그 타겟은 한 effect type이 될 수 있다 (예를들어, fx_4_1). 다양한 프로파일들을 지원하는 타겟에 대한 정보를 위해 Specifying Compiler Targets를 보아라.

- Flags1
bitwise OR 연산을 사용하여 결합되는 쉐이더 compile options의 조합. 그 결과 값은 컴파일러가 HLSL 코드를 어떻게 컴파일 할지를 명시한다.

- Flags2
bitwise OR 연산을 사용하여 결합되는 effect compile options의 조합. 그 최종 값은 그 컴파일러가 effect를 어떻게 컴파일 할 지를 명시한다. 너가 effect file이 아닌 shader를 컴파일 할 때, D3DCompileFromFile은 Flags2를 무시한다; 우리는 만약 그 호출된 함수가 파라미터를 사용하지 않는다면 nonpointer parameter를 zero로 설정하는 것은 좋은 연습이기 때문에, Flags2를 zero로 설정하는 것을 추천한다.

- ppCode
컴파일된 코드에 접근하기 위해 너가 사용할 수 있는 ID3DBlob interface에 대한 포인터를 받는 변수의 포인터

- ppErrorMsgs
너가 compiler error messages들을 접근하기 위해 사용할 수 있는 ID3DBlob interface에 대한 포인터를 받는 변수에 대한 부가적 포인터. 만약 에러가 없다면 NULL이다.
-------------------------------------------

2. Encapsulate both shaders into shader objects
각 쉐이더가 그것 자신의 COM object에 저장되었다.  우리는 vertex shader와 pixel shader를 만드는 것에 관심이 있다. 이러한 것에 대한 COM 오븢게트들은 ID3D11___SHhader라고 불려진다:

우리가 우리의 포인터들을 가지기만 한다면, 우리는 dev->Create___Shader()를 사용하여 그 오브젝트들을 만든다. 이렇게:

3. Set both shaders to be the active shaders
이 단계는 간단하다.

이러한 함수에 대해 첫 번째 함수는 설정한 쉐이더 오브젝트의 주소이고, 반면에 다른 두 오브젝트들은 고급이고 나중에 다뤄질 것이다.

~~

Vertex Buffers
만약 Lesson1에서 상세히 읽었다면, 너는 vertex의 정의를 상기할 것이다: 3D 공간에서 정확한 점의 location과 properteis. 그 위치는 간단히 세 개의 numerical values로 구성된다. 그리고 그것들은 그 정점의 좌표를 나타낸다. 그 vertex properties는 또한 numerical values를 사용하여 정의된다.

Direct3D는 input layout이라고 불려지는 것을 사용한다. input layout은 한 vertex의 location과 properties를 포함하는 데이터의 layut이다. 그것은 너가 수정할 수 있고, 너의 필요에 따라 설정할 수 있는 data의 한 format이다. 이것이 정확히 어떻게 작동하는지를 봐보자.

vertex는 구조체로 만들어지는데, 그것이 어떤 3D image를 위해서 만드는 지간에 적절한 데이터를 포함한다. 그 이미지를 보이기 위해서, 우리는 GPU에 모든 정보를 복사할 것이고, 그러고나서 Direct3D에게 그 데이터를 back buffer에 렌더링 하라고 명령한다. 그러나, 우리가 한 정점에 대해 가급적 원해질 수 있는 데이터를 보내려고 강요된다면 어떨까? 이것은 다음으로 발생한다.

물론, 너는 여기에서 문제가 무엇인지 모를 것이지만, 우리가 이러한 정보의 블럭 2개가 필요하다고 해보자. 우리는 그것을 GPU에게 더 빠르게 보낼 수 있다, 이렇게 해서:

이것이 우리가 input layout을 사용할 때 발생하는 것이다. 우리는 우리가 어떤 정보를 사용하고 싶은지를 고르고, 그것을 보낸다. 그리고 이것은 우리가 각 프레임 사이에 좀 더 많은 정점들을 보내도록 해준다.

~~~

Creating a Vertex Buffer
C++에서 구조체를 만들 때, 그 데이터는 system memory에 저장된다. 그러나 우리는 그것이 video memory에 있기를 원하고, 우리는 그것에 쉽게 접근하지 못한다.

우리가 video memory에 접근하기 위해서, Direct3D는 우리에게 우리가 두 시스템과 video memory에서 한 버퍼를 유지하도록 해주는 특정한 COM object를 제공한다.

어떻게 버퍼들은 두 시스템과 비디오 메모리에 존재하는가? 음, 초기에 그러한 버퍼에 있는 그 데이터는 시스템 메모리에 저장될 것이다. 그것에 대한 렌더링 호출이 발생할 때, Direct3D는 자동적으로 그것을 video memory로 너를 위해 복사할 것이다. 만약 그 비디오 카드가 메모리가 낮다면, Direct3D는 사용되지 않은 버퍼를 삭제할 것이고, 또는 더 새로운 자원을 위해 공간을 만들기 위해 "low priority"로 고려된다.

왜 우리는 Direct3D가 우리를 위해 이것을 하기를 필요하는가? 이것을 너 스스로 하는 것으 매우 어렵다, 비디오 메모리에 접근하는 것은 비디오 카드와 운영체제에 따라 다양하다. Direct3D를 갖는 것은 우리를 위해 이것을 관리하고 매우 편리하다.

이 COM object는 ID3D11 buffer라고 불려진다. 그것을 생성하기 위해, 우리는 CreateBuffer() 함수를 사용한다. 여기에 그 코드가 어떻게 생겼는지가 있다:

여기에 그것이 무엇을 의미하는지가 있다.

D3D11_BUFFER_DESC bd;
이것은 그 버퍼의 특징들을 포함하는 구조체이다.

ZeroMemory(&bd, sizeof(bd));
많은 Direct3D 구조체처럼, 우리는 그것을 zero로 만들어 초기화 할 필요가 있다.

bd.usage
버퍼들은 가능한 효율적으로 설정된다. 이것을 정확히 하기 위해서, Direct3D는 우리가 그것을 어떻게 접근하려는지 알 필요가 있다.

이 테이블은 여기에서 사용될 수 있는 다양한 플래그들을 보여준다. 우리는 이 튜토리얼을 위해 dynamic usage를 사용할 것이다.

bd.ByteWidth
이 값은 생성될 버퍼의 크기를 포함한다. 이것은 우리가 거기에 넣으려는 정점의 배열과 크기가 같다. 이 강의에서, 우리는 sizeof(VERTEX)* 3을 계산해서 이것을 얻는다.

bd.BindFlags
이 값은 Direct3D에게 어떤 종류의 버퍼를 만들지를 말한다. 우리가 할 수 있는 여러 종류의 버퍼들이 있지만, 우리가 만들고자 하는 종류는 vertex buffer이다. 이것을 위해서, 우리는 간단히 D3D11_BIND_VERTEX_BUFFER flag를 사용한다.

우리는 다른 플래그들을 여기에서 다루지 않을 것이다. 왜냐하면 그것들은 이 강의에 대해 조금 어렵기 때문이다. 그러나, 우리는 식나에 되면 그것들을 모두 다룰 것이다.

bd.CPUAccessFlags
이 멤버는 usage flags에 clarification을 더하는데, Direct3D에게 어떻게 우리가 CPU에 접근할 지를 말해서 한다. 여기에서 유일한 flags들은 D3D11_CPU_ACCESS_WRITE와 D3D11_CPU_ACCESS_READ이고, 그것들은 Table 2.1과 동일하게 사용되어질 수 있다.

~

Filling the Vertex Buffer
그래서 우리는 이제 삼각형에 대한 정점들을 가지고 있고, 우리는 그것들을 배치할 vertex buffer를 가지고 있다. 이제 우리가 할 필요가 있는 것은 그 정점들을 buffer에 복사하는 것이다.

그러나 Direct3D는 background에 있는 버퍼와 작동하기 때문에, 그것은 너에게 그곳에 대한 직접적은 CPU access를 주지 않는다. 그것에 접근하기 위해, 그 버퍼는 mapped 되어야만 한다. 이것은 Direct3D가 그 버퍼가 끝나도록 어떤 것이 처리되도록 하고, GPU가 그것이 unmapped 될 때 까지 버퍼로 작업하지 않게 막는 것을 의미한다.

그래서 vertex buffer를 채우기 위해 우리는:
  1. 그 vertex buffer를 Map한다 (그리고 그것으로 ㅇ니해 buffer의 location을 얻는다).
  2. 그 data를 buffer에 복사한다 (memcpy()를 사용해서).
  3. buffer를 unmap 한다.
DED11_MAPPED_SUBVRESOURCE ms;
이것은 우리가 mapped하고나서 그 버퍼에 대한 정보로 채워질 구조체이다. 이 정보는 그 버퍼의 location에 대한 포인터를 포함한다. 그리고 우리는 ms.Pdata를 사용해서 이 포인터에 접근할 수 있다.

devcon->Map()
그 다음 line은 그 버퍼를 maps하고, 우리가 그것에 접근하게 허용한다. 그 파라미터는 꽤 쉽다.

그 첫 번째 것은 버퍼 오브젝트의 주소이다. 우리의 버퍼 포인터는 pVBuffer라고 불려지고, 그래서 그것을 사용한다.

그 두 번째 것은 고급이다. 우리는 그것을 나중에 할 것이다. 운 좋게도 우리는 지금 그것을 NULL로 설정할 수 있다.

그 네 번째 파라미터는 그것이 mapped되는 동안 버퍼에 CPU 접근을 통제하게 해주는 플래그들의 집합이다. 우리는 DED11_MAP_WRITE_DISCARD를 쓸 것이지만, 다른 것들은 이 테이블에서 발견될 수 있다.

~

그 네 번째 파라미터는 또 다른 FLAG이고, 그것은 NULL or D3D11_MAPFLAG_DO_NOT_WAIT이 될 수 있다. 이 플래그는 프로그램이 계속하게 강요한다, 비록 GPU가 여전히 그 버퍼와 작업중일지라도.

그 마지막 파라미터는 D3D11_MAPPED_SUBRESOURCE 구조체의 주소이다. 그 함수는 우리가 여기에서 지정한 구조체를 채운다. 이것은 우리에게 필요한 정보를 준다.

~~

Verifying the Input Layout
지금까지 이 강의에서 우리는

A) 쉐이더들을 불러오고 파이프라인을 제어하도록 설정했고
B) 정점을 사용하여 shape를 만들고, 그것들을 GPU가 사용하도록 읽어들였다.

너는 GPU가 어떻게 우리의 정점들을 읽을 수 있는지 궁금할지도 모른다. 우리가 그것들을 우리 자신의 self-created struct로 배치했을 때. 어떻게 그것은 우리가 color 앞에 location을 배치했는지를 알 수 있는가? 어떻게 그것은 우리가 어떤 다른 것을 의미하지 않았느지 알 수 있는가?

그 답은 input layout이다.

이전에 언급했듯이, 우리는 렌더링 속도를 개선하기 위해 각 정점과 함께 무슨 저옵가 저장되었는지를 고를 수 있다. 그 input layout은 그 정점 구조체의 layout을 포함하는 오브젝트 이다. 그리고 이것은 GPU가 그 데이터를 적절하고 효율적으로 구성하도록 한다.

ID3D11InputLayout object는 우리의 VERTEX 구조체의 layout을 저장한다. 이 오브젝트를 만들기 위해 우리는 CreateInputLayout() 함수를 호출한다.

이것에 대해 두 부분이 있다. 처음에, 우리는 그 정점의 대한 각 원소를 정의할 필요가 있다. 두 번째, 우리는 그 input layout object를 만들 필요가 있다.

~~~
























2019년 1월 14일 월요일

DirectX Tutorial Lesson 4 : Going Fullscreen

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-4-4

Lesson Overview
너의 게임을 풀스크린으로 만드는 것은 쉽지만, 그 프로그램의 몇 가지 세부사항을 바꾸는 것을 요구한다. 뿐만 아니라 몇 가지 코드 라인도 추가해야 한다.

이 강의에서, 우리는 두 가지 것을 다룰 것이다. 첫 번째로, 우리는 너의 screen resolution을 globalize할 것이고, 왜 너가 이것을 해야하는지를 다룰 것이다. 두 번째로, 우리는 widnow를 fullscreen mode로 가게하거나 되돌리는 것의 방법을 다룰 것이다.

~~~~~~~~~~

Changing to Fullscreen Mode
거의 대부분 모든 게임들이 풀스크린 모드로 플레이 되고 있고, 많은 게임들은 풀스크린과 윈도우 모드 사이를 전환하는 능력을 포함한다. 우리가 기본적으로 풀스크린을 원할지라도, 우리는 또한 사용자가 쉽게 전환하는 능력을 갖게 하길 원한다. 이것은 항상 Alt-Enter 키를 사용하여 된다.

풀 스크린으로 업그레이드 할 때, 우리가 할 필요가 있는 몇 가지것 이 있다:


  1. 그 window가 어떠한 background도 갖지 않도록 수정해라
  2. 그 back buffer의 구체적 사이즈를 설정해라
  3. DirectX가 자동으로 Alt-Enter가 사용될 때 전환되도록 설정해라
  4. CleanD3D() 함수가 종료될 때 fullscreen을 끄도록 수정해라.
1. Modify the window to have no background.
그 윈도우 background를 제거하기 위해, 우리가 할 필요가 있는 모든 것은 그 WINDOWCLASSEX 구조체의 멤버 중 하나를 주석 처리하는 것이다. 그것은 background color를 설정하는데 사용된다. 이것을 하는 것은 그 background color가 손 닿지 않도록 만든다. 그리고 이것은 그것이 게임이 시작하기전에 1~2초 동안 윈도우로서 보이지 않게 만들 것이다.

2. Set the back buffer to a specific size
다음으로, 우리는 DirectX에게 우리의 새로운 스크린 해상도를 말해야 한다. 우리는 우리가 지난 강의에서 구성한 scd struct에 몇 가지 변화를 해서 이것을 한다.

3. Set DirectX to automatically switch when Alt-Enter is used.
이 단계는 꽤 간단하다. 우리가 할 필요가 있는 모든 것은 그 scd struct에 flag를 추가하는 것이다. 그 나머지는 우리를 위해 처리된다. 우리는 DXGI_SWAP_CHAIN_DESC의 Flags 멤버에 이 flag를 넣을 수 있다.

4. Modify the CleanD3D() function tu turn off fullscreen when closing.
Direct3D는 실제로 fullscreen mode일 때 닫을 수 없다. 이것은 scenes 뒤에 발생하는 어떤 쓰레딩 문제에 의한 것이다. 정확히 종료하기 위해서, 우리는 windowed mode에 있도록 해야만 한다. 우리는 이것을 위해 SetFullscreenState()를 사용할 수 있다. 여기에 그 새로운 ClearD3D() 함수가 다음처럼 보이는 것이 있다:


~~

















DirectX Tutorial Lesson 3 : Rendering Frames

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-4-3

Lesson Overview
Direct3D가 초기화 되었다면, 몇 가지 실제 렌더링을 시작할 시간이다. 렌더링 그 자체로는 매우 쉽지만, 관련된 준비 작업들이 조금 있다. 이 강의에서, 우리는 blank frame을 렌더링하기 위한 모든 준비를 할 것이다.

그 관련된 설정은 간단하다. 되어야 할 두 가지 것들이 있다. 첫 번째, 울니느 GPU에게 메모리의 어디에서 최종 이미지를 만들지 말할 필요가 있다 (우리에게, 이것은 back buffer이다). 두 번째로, 우리는 GPU에게 back buffer 어디에서 그것이 그려야 하는지를 말할 필요가 있다.

그것만 된다면, 우리는 렌더링할 수 있다. 쉬운 것이다.

Setting the Render Target
어디에 렌더링할 지 부터 시작하자. 너는 논리적으로 이제 "back buffer"라고 말할 것이고 끝났다. 그러나, Direct3D는 실제로 이 지점에서 그것을 모른다. 너가 back buffer에 당장 렌더링하고 싶지 않는 것도 가능하다. 예를들어, 많은 게임들은 한 model의 surface에 렌더링하고, 그 model을 back buffer에 렌더링한다. 이 기법은 다양한 효과를 만들어 낼 수 있다. 만약 너가 Portal을 플레이 해봤다면, 너는 이것의 예를 보았을 것이다. Portal에서, 그 게임 엔진은 처음에 portal에 렌더링하고, 그러고나서 전체 scene을 포함된 portal image로 렌더링한다.

Direct3D에서 렌더링할 때, 너는 render target을 설정해야마 한다. 이것은 video memory에서 렌더링 할 위치를 유지하는 간단한 COM object이다. 대부분의 경우에 (우리의 경우를 포함해서) 이것은 back buffer이다.

여기에 그것을 어떻게 하는지가 있다:

그것은 복잡해 보이지만, 꽤 간단하다.

여기에서 해야 할 세 가지 것이 있따. 처음에, 우리는 back buffer의 주소를 결정한다. 두 번째, 우리는 render target을 나타내기 위해 그 주소를 사용하여 COM object를 생성한다. 세 번째, 우리는 그 오브젝트를 active render target으로 설정한다.

여기에 새로운 코드들이 있따. 그래서 그것이 무엇을 의미하는지 보자.

ID3D11RenderTargetView* backbuffer;

이 변수는 render target에 대한 모든 정보를 포함하는 한 오브젝트에 대한 포인터이다. 우리는 back buffer에 렌더링할 것이고, 그래서 우리는 이것을 'backbuffer'라고 부를 것이다.

ID3D11Texutre2D* pBackBuffer;

3D 렌더링에서, texture는 image의 또 다른 이름이다. ID3D11Texture2D는 flat image를 저장하는 한 오브젝트이다. 어느 COM object처럼, 우리는 처음에 그 포인터를 정의하고, 나중에 한 함수가 우리를 위해 그 오브젝트를 생성한다.

swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

너가 믿을지 모르겠찌만, 이 명령어는 실제로 간단하다. 이 GetBuffer() 함수가 하는 것은 swap chain에서 back buffer를 찾고, pBackBuffer texture object를 생성하기 위해 그것을 사용한다.

첫 번째 파라미터는 얻어질 back buffer의 개수이다. 우리는 이 chain에서 하나의 back buffer만을 사용하고, 그것은 back buffer #0이다. 그러므로, 첫 번째 파라미터는 0이 될 것이다.

두 번째 파라미터는 ID3D11Texture2D COM object를 확인하는 숫자이다. COM object의 각 type은 그것 자신의 unique ID를 갖는데, 이것은 그것에 대한 정보를 얻기 위해 사용된다. 이 ID를 얻기 위해, 우리는 __uuidof 연산자를 사용한다. 이것에 어떻게 작동하는지에 대한 정확한 세부사항은 중요하지 않지만, 우리가 그것을 하는 이유는, GetBuffer() 함수가 오브젝트의 type이 무엇으로 만들어져야 하는지를 알아야 하기 때문이다.

세 번째 파라미터는 너에게 이해가 되지 않을 것이다. 만약 너가 적어도 C++ 중급자가 아니라면. 기본적으로 void*는 어떠한 특수한 변수 타입을 가리키지 않는 포인터이다. 예를들어, double* 은 double를 가리키고, 반면에 int* 는 int를 가리킨다. void*는 어떤 것을 가리키는데 상요되고, 그것은 어떤 다른 type의 포인터로 변환될 수 있다.

이 세 번째 파라미터에서, 우리는 void* 포인터를 갖는다. 이 void*는 ID3D11Texture2D object의 위치로 채워진다. 그것은 void*가 될 필요가 있는데, 왜냐하면, 우리가 요청할 다른 타입의 오브젝트들이 있기 때문이다.

이 특별한 함수는 너가 너무 많이 어지러워지지 않을 것이다. 그래서 의문에 있더라도 걱정하지 말라. 너는 Direct3D에 대해 배울 더 쉬운 시간을 얻게 될 것이다.

dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);

이 함수는 render target object를 만든다. 우리는 그 프로그램의 맨 위에서 이 오브젝트에 대한 포인터를 만들었따.

그 첫 번째 파라미터는 텍스쳐에 대한 포인터이다. 그것은 윌의 프로그램에서 'pBackBuffer'
여야 한다.

두 번쨰 파라미터는 render target을 설명하는 구조체이다. 우리는 이것을 back buffer를 위해 채울 필요가 없다. 우리는 그것을 NULL로 설정할 것이다.

그 세 번째 파라미터는 object pointer의 주소이다. ~

~~

~~

devcon->ClearREnderTargetView()

이것은 render target buffer를 특정 color로 채운다. 윌의 경우에 우리의 back buffer에 채울 것이다. 그것은 꽤 간단하다. 두 개의 파라미터가 있다.

첫 번째 것은 render target object의 주소이다. ~

두 번째 파라미터는 너가 back buffer에 채우고 싶은 컬러이다. 이것을 하기 위해서, 우리는 D3DXCOLOR라고 불리는 간단한 구조체를 사용한다. 그 네 개의 생성자 파라미터들은 컬러를 채우기 위해 사용된다. ~~

swapchain->Present()
다음으로, 우리는 Present() 함수를 호출한다. 이 함수는 back buffer에 그려진 모든 것이 실제로 나타나는 곳이다. 그것의 일은 기본적으로 swap chain에 있는 "swap"을 수행하는 것이고, back buffer가 front buffer가 되게하기 위해서이다.

그 두 번째 파라미터는 둘다 0으로 설정되고, 이 튜토리얼에서 더 많이 사용되지 않으 것이다.

2019년 1월 12일 토요일

DirectX Tutorial Lesson 2 : Initializing Direct3D

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-4-2

Lesson Overview
너가 Direct3D를 작동시키는 개념을 공부했으니, 간단한 Direct3D 프로그램을 구성하여 실제적인 것으로 들어가보자. 이 프로그램에서, 우리는 Direct3D를 초기화하고 그것을 다시 닫을 것이다. 그것은 많은 것은 아니고, 심지어 "Hello World"도 아니지만, 그것은 좋은 싲가이다.

COM
그래서 COM이 무엇이냐?

COM은 Component Object Model을 나타낸다. COM은 실제로 레고같이 행동하는 매우 고급 오브젝트들을 만드는 한 방법이다.

너도 알 듯이, 레고는 좀 더 고급 모양을 만들기 위해 함께 뭉쳐진다. 어떠한 단일 Lego도 실제로 세트에서 다른 레고를 신경쓰지 않는다. 그것들은 서로 호환가능하고, 너가 해야할것은 그것들이 작동하도록 서로 붙이는 것이다. 만약 너가 조각들을 바꾸고 싶다면, 너가 해야할 것은 한 조각을 빼서 다른 것을 그것의 자리에 넣는 것이다.

그것이 COM이 하는 것이다. COM objects는 실제로 C++ 클래스이거나 클래스들의 그룹인데, 거기에서 너는 함수를 호출할 수 있고, 어떤 목표를 달성할 수 있다. 어떠한 class도 작동하기 위해 다른 것을 요구하지 않고, 그래서 그것들은 어떤 일을 처리하기 위해 함께 작업할 필요가 없다. 그러나 너는 그것들을 너의 바람대로 그 프로그램의 나머지를 바꾸지 않고 plug or unplug할 수 있다.

예를들어, 가령 너가 광범위하게 배포된 한 게임을 가졌다고 하자. 그리고 너는 그것을 업그레이드 하고 싶다. 너의 게임을 구매한 모든 단일 사용자에게 새로운 copy를 추적하여 배송하는 대신에, 너가 해야할 것은 "Upgrade! Right Here!"이라고 말하느 ㄴ것이ㅏㄷ. 그들은 업데이트된 COM object를 다운 받고, 그리고 그 새로운 object는 너의 프로그램에 문제 없이 plugs in 들어간다. 좋지 않는가?

나는 COM에 상세히 들어가지 않을 것이다. 왜냐하면 우리가 필요한 것에 대해 너무 복잡하기 때문이다. ~~~~

그래서 왜 COM이냐? DirectX는 실제로 COM objects의 연속이고, 그것들 중 하나는 Direct3D이다. Direct3D는 그것 내부에 다른 COM object를 갖고있는 COM object이다. 궁극적으로, 그것은 소프트웨어, 하드웨어 또는 무슨 웨어든지 사용하여 2D와 3D 그래픽스를 작동시키게 하는데 필요한 모든 것을 포함한다.

그래서 Direct3D는 클래스들에 이미 저장되어 있기 때문에, Direct3D 함수들이 이렇게 호출되는 것을 보았을 때 놀라지 마라:

우리는 함수들 CreateRenderTargetView()와 Release()를 Direct3D class로부터 접근하기 위해 indriect member access operator를 사용한다. 우리는 우리가 그것이 실제로 어떻게 적용되는지를 볼 때 이것에 더욱 알아볼 것이다. 나는 여기에서 필요하지 않ㅇ는 이론을 피하려고 할 것이다.

비록 COM의 일이 모든 복잡성을 숨기는 것일지라도, 너가 그것에 대해 알아야 할 네 가지가 있다.

1. COM object는 interface에 의해 통제되는 한 클래스 이거나 클래스들의 집합이다. interface는 함수들의 한 집합인데, COM object를 통제한다. 위의 예제에서, "device"는 COM object이고, 그 함수들은 그것을 통제한다.

2. COM object의 각 type은 unique ID를 갖는다. 예를들어, 그 Direct3D object는 그것 자신의 ID를 갖고, 그 DirectSound object는 그것 자신의 ID를 갖는다. 가끔씩 너는 이 ID를 코드에서 상요할 필요가 있다.

3. COM object를 사용할 때, 너는 항상 Release()함수를 호출해야만 한다. 이것은 그 object가 그것의 메모리를 해제하고 그것의 쓰레드를 종료하라고 말할 것이다.

4. COM objects는 인식하기에 쉽다. 왜냐하면 그것들은 일반적으로 "I"와 시작하기 때문이다. 'ID3D10Device' 처럼.

이러한 4개의 세부사항에 걱정하지 말아라. 우리는 그것들이 어떻게 적용되는지를 볼 ㅓㄳ이다. 이제 실제 코드에 들어가보자:

Direct3D Headers
우리가 실제 Direct3D 코드에 들어가기전에, 헤더 파일, 라이브러리 파일, 그리고 다른 재미있는 것들에 말해보자. 우리의 데모 프로그램에서, 우리는 꼭대기에 이러한 것들을 가질 것이고, 이것은 우리에게 Direct3D에 대한 global access를 준다.

이러한 것들을 보고 그것들이 무엇인지 보자.

~~~
ID3D11Device* dev;

이 변수는 device에 대한 변수이다. Direct3D에서, 한 device는 너의 video adapter의 virtual representation이 되려고 하는 한 오브젝트이다. 이 코드 라인이 의미하는 것은 우리가 ID3D11Device라고 불려지는 COMobject를 만들 거라는 것이다. COM이 이 오브젝트를 만들 때, 우리는 그것을 무시할 것이고, 그것에 이 포인터를 간접적으로 사용하여 접근할 것이다. 우리는 이것이 어떻게 발생하는지 볼 것이다.

ID3D11DeviceContext* devcon;

device context는 device와 유사하지만, 그것은 GPU와 렌더링 파이프라인을 관리하는데 책임이 있다 (그 device는 대개 video memory를 관리한다). 이 오브젝트는 그래픽스를 렌더링하고, 그것들이 어떻게 렌더링될지를 결정하는데 사용된다.

IDXGISwapChain* swapchain;
우리가 지난 강의에서 다루었듯이, 그 swap chain은 차례대로 렌더링될 버퍼들의 연속이다. 이 변수는 그러한 chain에 대한 포인터이다.

이 오브젝트가 Direct3D에 속하지 않는다는 것을 유의해라. 그러나 그것은 실제로 DXGI의 일부이고, 그것은 Direct3D 아래에 있다.

Launching Direct3D
Direct3D를 실제로 코딩하는 첫 번째 단계는 위의 세 개의 COM objects들을 생성하고 그것들을 초기화하는 것이다. 이것은 단일의 함수와 graphics device information을 포함하는 구조체를 사용하여 처리 된다. 이 함수를 여기에서 봐보자. 그러고나서 그것의 부분들을 보자. 나는 새로운 부분을 bold하려고 하지 않았다. 왜냐하면 전체가 새롭기 때문이다.

만약 그 코드의 주석이 너에게 충분하다면, 훌륭한다. 그렇지 않다면, 나는 이러한 명령어들 각각을 아래에서 설명했다.





























2019년 1월 8일 화요일

DirectX Tutorial 공부할 곳

http://www.directxtutorial.com/LessonList.aspx?listid=11

http://rastertek.com/tutdx11.html

https://www.braynzarsoft.net/viewtutorial/q16390-braynzar-soft-directx-11-tutorials

https://docs.microsoft.com/en-us/windows/desktop/direct3d11/atoc-dx-graphics-direct3d-11


DirectX 개발을 위한 Tool:
https://blogs.msdn.microsoft.com/chuckw/2013/08/20/living-without-d3dx/
이 사람이 만든 DirectXTK 이용하거나, 여기 있는 글 보면서 Deprecated된 함수들 찾아서 쓰면 된다.

DirectX Tutorial Lesson 1: Understanding Graphics Concepts

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-4-1

Lesson Overview
우선, 나는 공식적으로 Direct3D에 온 너를 환영한다. 나는 너에게 3D 프로그래밍의 기본과 고급 주제들을 가르치고 싶다. 너가 너 자신의 엔진을 구성하길 원하던가, 빌리던가 수정하든, 또는 사서 그것을 사용한든, 너가 그것의 밑에 있는 개념을 이해하는 것은 중요하다.

게임엔진의 밑에 있는 개념들이 중요하듯이, Direct3D 그 자체 밑에 있는 개념들도 중요하다. 이 강의는 Direct3D의 기본을 다룰 것이고, 너가 알 필요가 있는 것은 너의 첫 번째 Direct3D Program을 시도하는 것이다.

이 첫 번째 강의는 이론 강의이다. 우리는 다음 강의에서 관련된 연습을 다룰 것이다.

Graphics Hardware
그래픽스 프로그래밍의 좋은 이해는 그래픽스와 연관된 하드웨어의 좋은 이해를 요구한다. Direct3D는 게임 플랫폼이라기 보다는, 하드웨어 인터페이스이다. 너가 Direct3D에서 하는 모든 것은 그래픽스 하드웨어 그 자체를 조작하기 위해서 처리된다. 그래픽스 하드웨어에 대한 이해없이, Direct3D는 거의 쓸모없다.

우리가 주로 그래픽스 프로그래밍과 관련있는 하드웨어는 GPU이다, 즉 Graphics Processing Unit. 이것은 CPU (central processing unit)과 몇 가지 구조적인 면에서 다르지만, 우선적으로 그것이 사용되는곳에서 다르다. CPU는 계산을 수행하고, 전체 컴퓨터를 지도한다. GPU는 그래픽스와 관련된 계산을 수행하고, 그래픽스 결과를 모니터에 옮긴다.

그것 자신의 별개의 프로세서를 갖는 것 외에도, 그래픽스 프로그래밍은 video memory라고 불려지는 완전히 고립된 메모리 부분으로 작업한다. 그것은 실제로 물리적으로 분리되어 있다. motherboard에 존재하는 대신에, video memory는 일반적으로 video card에 존재하고, 그래서 그것은 빠르게 GPU에 의해서 접근되어질 수 있다. 그 비디오 MEMORY는  스크린에 있는 현 재 이미지를 저장하기 위해서 사용되고, 뿐만 아니라 다음 이미지를 컴파일 하기 위해 상요될 어떤 데이터를 저장하기 위해 사용된다.

물리적 하드웨어가 더 나아가듯이, 그것이 우리가 걱정할 필요가 있는 모든 것이다. Direct3D는 대부분, 데이터가 system memory or video memory에 언제 저장될지를 관리할 것이고, 너는 그것에 대해 걱정할 필요가 없다.

DirectX Graphics Infrastructure
DirectX Graphics Infrastructure는 Direct3D의 가장 최근 버전의 기반에 놓여있는 컴포넌트이다. 그것의 일은 이미지를 스크린에 보이게하고, 그 모니터가 무슨 해상도이고, 무슨 video card를 다룰 수 있는지 같은 기본 일들을 다룬다.

DXGI는 실제로 Direct3D의 부분이 아니다. 그것은 다른 그래픽스 컴포넌트아래에 있고, 그것은 Direct3D와 harware 사이의 인터페이스로서 작동한다.

DXGI를 직접적으로 다루는 방법들이 있지만, 우리는 이것에 깊게 들어가지 않을 것이다. 중요한 것은 너가 이 컴포넌트가 존재한다는 것을 인지하는 것이다. DXGI를 독점적으로 다루는 Direct3D가 있기 때문이다.

The Swap Chain
GPU는 그것의 메모리에서 픽셀 버퍼에 대한 포인터를 포함한다. 그 버퍼는 현재 스크린에 보여지는 이미지를 포함한다. 너가 어떤 것을 렌더링할 필요가 있을 때, 3D model or image같은, 그 GPU는 이 배열을 업데이트하고, 그 정보를 전시할 모니터에 보낸다. 그 모니터는 그러고나서 그 스크린을 위에서부터 바닦까지 다시 그리고, 오래된 이미지를 새것으로 교체한다.

그러나, 이것에 문제가 있는데, 모니터가 real-time rendering에 필요한만큼 빠르게 refresh 하지 않는다는점에서 그렇다.  대부분의 refresh rates는 60Hz에서 약 100Hz의 범위이다. 만약 또 다른 모델이 GPU에 렌더링되었다면, 모니터가 refreshing하는 동안, 그 보여지는 이미지는 두 개로 잘릴 것인데, 그 top portion은 old image를 포함하고, bottom portion은 새 것을 포함할 것이다. 이 효과는 tearing이라고 불려진다.

이것을 피하기 위해서, DXGI는 swapping이라고 불려지는 특징을 구현한다.

모니터에 직접적으로 새로운 이미지들을 렌더링하는 대신에, DXGI는 너의 이미지들을 픽셀의 2차 버퍼에 그려놓는다, back buffer라고 불려지는. 그 front buffer는 현재 보여지고 있는 버퍼가 될 것이다. 너는 모든 너의 이미지를 back buffer에 그리고, 너가 끝났을 떄, DXGI는 back buffer의 내용으로 front buffer를 업데이트할 것이고, old image를 버릴 것이다.

그러나, 이것을 하는 것은 여전히 tearing을 발생시킬 수 있다. 왜냐하면 그 이미지 transfer가 여전히 모니터가 refreshing하는 동안 발생할 수 있기 때문이다 (GPU는 모니터보다 더욱 매우 빠르다).

이것을 피하기 위해 (그리고 전체를 더 빠르게 하기 위해), DXGI는 각 버퍼에 대한 포인터를 사용한다 (both front and back) 그리고 그것들의 값을 간단히 바꾼다. 그 back buffer는 그러고나서 front buffer가 되고 (끄리고 역으로), 그래서 어떠한 tearing이 발생하지 않는다.

물론, 우리는 이렇게 부가적인 back buffers를 추가하여 우리의 게임이 더 좋은 성능을 갖도록 할 수 있다.

이 설정은 swap chain이라고 불려지는데, 그것이 buffers의 chain이기 때문이고, 새로운 프레임이 렌더링될 때 마다, 위치를 swap한다.

The Rendering Pipeline
만약 그래픽스 프로그래밍에서 너가 들을 한 가지 것이 있따면, 그것은 렌더링 파이프라인이다. 이것은 왜냐하면 그 렌더링 파이프라인은 모든 것이 발생하는 곳이기 때문이다.

렌더링 파이프라인은 스크리엔 렌더링된 3D image를 만드는 프로세스이다. 그것은 assembly-line처럼 작동한다, 한 단계 긑나고 한 단계씩. 그것은 GPU에서 실행되는 단계들로 구성된다.

우리는 여기에서 모든 단계들을 다루지 않을 것이지만, 우리는 몇 가지만 다룰 것이다. 우리는 시작하기 위해서 이러한 단계들을 이해할 필요가 있을 것이고, 그러고나서 우리가 진행함에 따라 나머지를 알 수 있다.

Input-Assembler Stage는 파이프라인에서 첫 번째 단계이다. 그것의 책임은 말하자면 "음식을 씹는 것"이다. 그것은 너가 렌더링하길 원하는 3D 모델에 관해 비디오 메모리로부터 정보들을 모은다. 그러고나서, 그것은 그 정보를 컴파일 하고 렌더링을하기 위해 그것을 준비한다.

Rasterizer Stage는 그 back buffer images가 어디에 그려져야할 지를 결정하는데 책임이 있다. 좀 더 구체적으로, 어떤 정확한 픽셀이 그려져야 하고, 그것들이 어떤 컬러가 되어야 할지를 결정한다.

Output-Merger Stage는 파이프라인에서 마지막 단계이다. 그것의 일은 개별 모델 이미지를 한 개의 전체 이미지로 결합하는 것이고, 그 이미지를 정확히 back buffer에 놓는 것이다.

--- The Coordinate System 생략 ---
-> 왼손 좌표계, 스크린 방향이 -Z 방향


--- 3D Geometry 생략 ---
-> Face Culling은 ClockWise로 해야, Counter ClockWise가 렌더링 안됌.






DirectX Tutorial Lesson 5: The Real-Time Message Loop

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-5

Lesson Overview
이 강의에서, 우리는 단일 함수, PeekMessage()를 다룰 것이고, 이 함수가 그것의 evil twin인 GetMessage()와 어떻게 다른지를 다룰 것이다.

실제로, GetMessage()에는 잘못이 없다. 그것이 작동하는 방법은 게임에 그리고 그것들의 지속적인 활동에 장관의 결과를 갖지 않는다. 우리는 이것이 어떻게 되어있는지, 그리고 PeekMessage()가 어떻게 솔루션인지를 다룰 것이다.

The Structure of the GetMessage() Loop
이전 강의에서, 우리는 GetMessage()함수를 사용하여, 간단한 윈도우즈 어플리케이션을 구성했다. 우리는 GetMessage()와 모든 보내진 윈도우 메세지를 처리했던 loop를 만들기 위해 다른 함수들을 사요했다. 그러나, 우리가 그 당시에 이야기 하지 않았던 부분이 있다.

다음의 다이어글매은 우리가 썼던 그 이벤트 loop가 어떻게 작동하는지를 보여준다:

우리가 그 윈도우를 만드록나면, 우리는 event loop에 들어간다. 거기에서 우리는 GetMessage() 함수를 본다. GetMessage()함수는 한 메세지를 기다리고, 하나를 받자마자, 그것을 다음 단계인 TranslateMessage()로 보낸다. 이것은 완벽히 윈도우즈 프로그래밍에 논리적이다. 왜냐하면 일반적으로 말해서 윈도우 프로그램들 예를들어 Word는 너가 움직일 때 까지 가만히 있고 아무것도 하지 않는 경향이 있기 때문이다.

그러나, 이것은 우리에게 작동하지 않는다. 이것이 기다리는 것이 발생하는 동안, 우리는 30에서 60개의 완전히 렌더링되는 3D images를 초당 만들어야하고, 그것들을 어떤 지연도 없이 스크린에 넣어야 할 필요가 있다. 그래서 우리는 오히려 교차 문제가 있따. 왜나하면 윈도우즈가 어떤 메세지를 보낸다면, 그것은 명백히 초당 그것들의 30장을 보내지 않을 것이다.

A New Function, PeekMessage()
이 딜레마를 해결하기 위해 우리가 할 것은 우리의 현재 GetMessage() 함수를 새로운 함수 PeekMessage()로 바꾸는 것이다. 이 함수는 본질적으로 같은 것을 하지만, 한 가지 중요한 차이점이 있다: 그것은 어떤 것도 기다리지 않는다. PeekMessage()는 메세지 큐를 보고, 어떤 메세지가 기다리고 있는지를 본다. 만약 없다면, 그 프로그램은 계속될 것이고, 우리가 필요한 것을 하게 해줄것이다.

우리가 더 가기전에, PeekMessage()를 봐보자. 여기에 그것의 프로토타입이 있다.

BOOL PeekMessage(LPMSG, lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);

처음에서 네 번째 파라미터들은 너에게 친숙할 것이다. 그것들은 GetMessage()와 동일하다. 그러나 5 번째 wRemoveMsg는 새로운 것이다.

그것이 하는 것은 이벤트 큐에서 가져와진 메세지가 이벤트큐에 머물러야 할지 나가야 할지를 가리킨다. 우리는 PM_REMOVE or RENOREMOVE 둘 중 하나를 넣을 수 있다. 첫 번째 것은 메세지가 읽혀질 때 큐에서 메세지를 가져온다. 바념ㄴ에 두 번째 것은 나중에 가져오기 위해 그 메세지를 남겨둔다. 우리는 PM_REMOVE를 여기에서 사용할 것이고, 상황을 간단하게 할 것이다.

그래서 우리는 어떻게 이것을 윌의 프로그램에 구현하는가? 다음이 우리가 만든 마지막 프로그램인데, PeekMessage()를 사용하여 변경되었다.

나머지 생략!

DirectX Tutorial Lesson 4: Window Size and Client Size

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-4

Lesson Overview
그래픽스로 작업할 때, 너가 그리고 있는 area의 정확한 크기를 아는 것은 중요하다. 지난 강의에서, 우리는 500 x 400으로 그것의 크기로 설정했다. 그러나, Direct3D가 그릴 area는 그 윈도우에 대해 500 x 400이 아니다.

이 강의에서, 우리는 그리는 area의 실제 크기를 발견하고, 그것을 좀 더 정확하게 설정하는 함수를 배울 것이다.

Window Size vs. Client Size
우리가 CreateWindowEx()를 호출할 때, 우리는 그 윈도우 크기를 500, 400으로 사용했다. 그러나, 이것은 클라이언트의 크기와 다르다. 그 클라이언트 면적은 그것의 가장자리를 포함하지 않는 부분이다.

너가 여기에서 볼 수 있듯이, 그 윈도우 크기는 그 가장자리의 edges로 부터 확장된다. 반면에 client size는 그 border의 내부에서 확장된다. 렌더링할 때, 우리는 윈도우의 client area에서 그릴 것이다. 그러므로, 그것의 정확한 사이즈를 아는 것은 중요하다.

왜 이것이 매우 중요한가? Direct3D를 사용하여 그릴 때, 너는 만들어질 그 이미지의 크기가 명시하도록 요청된다. 만약 너의 윈도우 클라이언트가 이 이미지와 다른 사이즈라면, 그것은 펼쳐지거나, 그 클라이언트 에어리어에 맞게 줄어든다.

나중의 강의에서 예제 프로그램의 두 스크린샷을 비교해보자. 왼쪽의 이밎는 일반적으로 취해졌고, 오른쪽의 것은 AdjustWindowRect() 없이 취해진 것이다.

너가 볼 수 있듯이, 오른쪽의 스크린샷은 명백한 왜곡을 가지고 있다. 이러한 것은 그 이미지가 client area에 맞게 줄어들었을 때 만들어진다.

The AdjustWindowRect() Function
윈도우 사이즈를 설정하고, 그 클라이언 사이즈를 결정하는 것보다, 클라이언트 사이즈를 미리 결정하고 적절한 윈도우 사이즈를 계산하는 것이 이상적이다. 이것을 하기 위해서, 우리는 윈도우를 만들기 전에 AdjustWindowRect() 함수를 사용할 것이다.

이것은 간단한 함수이다. 그것이 하는 것은 우리의 클라이언트 area의 크기와 위치를 취하고, 필요한 윈도우 position과 size를 그 client size를 만들기 위해 계산할 것이다.

여기에 그 함수의 프로토타입이 있다:
BOOL AdjustWindowRect(LPRECT lpRect, DWORD dwStyle, BOOL bMenu);

첫 번째 파라미터는 RECT 구조체에 대한 포인터이다. 그 RECT는 요구되는 client area의 좌표를 포함하도록 지목된다. 그 함수가 호출될 떄, 그 RECT는 윈도우 area의 좌표를 포함핟록 수정된다.

두 번쨰 파라미터는 widnow style이다. 그 함수는 윈도우 경계의 사이즈를 결정하기 위해 이 정보를 사용한다.

세 번째 파라미터는 BOOL value인데, 그 함수에게 우리가 메뉴를 사용할지 안할지를 말해준다. 메뉴는 기술적으로 클라이언트 area의 부분이 아니다. 그래서 그것은 고려되어져야 한다.

이 함수가 실제 코드에서 어떻게 보이는가? 한 번 봐보자. 다음이 CreateWindowEx()의 호출에 대한 우리의 변경이다:

DirectX Tutorial Lesson 3: Creating a Window

http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-3

DirectX 11을 공부하기 위해 이 자료를 보고 있다. 공부하던거 이어서 번역하겠다.

======================================

Handling Windows Events and Messages
우리가 일단 우리의 window를 만들었다면, 우리는 그것이 상호작용할 수 있도록 윈도우를 계속 유지할 필요가 있다. 물론, 만약 우리가 우리의 프로그램이 끝나면, 그것은 그렇지 않을 것이다. 그러나, 그것이 그렇다고 가정하여, 우리는 윈도우가 만들어지고 파괴되는 flash를 볼 것이다. 우리가 WinMain()에 도달했을 때.

끝내는 대신에, 우리의 프로그램은, window creation을 완료하고, main loop라고 불리는 것에 들어간다. 우리가 이전에 말했듯이, 윈도우즈 프로그래밍은 이벤트 기반이다. 이것은 우리의 윈도우가 우리의 윈도우가 우리에게 go를 줄 때 어떤 것을 한다는 것을 의미한다. 그렇지 않다면, 우리는 기다린다.

윈도우즈가 우리에게 한 메세지를 넘겨줄 때, 몇 가지것이 즉시 발생한다. 그 메세지는 event queue에 위치해있다. 우리는 queue로부터 그 메세지를 가져오기 GetMessage() 함수를 사용하고, 우리는 어떤 메세지 포맷을 다루기 위해 TranslateMessage()를 사용하고, 우리는 DispatchMessage() 함수를 사용하여, WindowPRoc() 함수로 보낸다. 그러고나서 그것은 그 메세지의 결과로 무슨 코드가 실행되어질지를 고른다.

복잡한 경우에, 다음은 모든 것이 발생하는 그 순서를 보여주는 다이어 그램이다.

이벤트를 다루는 것은 우리의 프로그램의 다른 절반이다. 그것은 그 자체로 두 개의 부분으로 분리된다:

1. Main Loop
2. The WindowProc() function

그 main loop는 오직 GetMessage(), TranslateMessage() 그리고 DispatchMessage()로 구성된다. WindowProc() 함수는 오직 어떤 메세지들이 보내졌을 때에만 작동하는 코드로 구성된다. 그것은 놀랍게도 간단하다. 그것에 대해 이제 좀 더 알아보자.

1. The Main Loop
위의 다이어그램으로 부터 보여지듯이, 이 섹션은 오직 세 개의 함수로 구성된다. 각 함수는 실제로 꽤 간단하고, 우리는 그것에 너무 자세히 들어갈 필요가 없지만, 우리는 그것들을 간단히 다룰 것이다.

다음은 main loop에 요구되는 코드이다:

시간을 많이 쓰지 않고, 이러한 코드의 각 라인을 봐보자

MSG msg;
MSG는 single event message와 관련된 모든 데이터를 포함하는 구조체이다. 너는 보통 이 구조체 내요에 직접 접근할 필요가 없을 것이지만, 그런 경우에, 나는 그것이 무엇을 포함하는지를 살짝 보여줄 것이다.

HWND hwnd : 그 메세지를 받았던 window의 handle을 포함
UINT message : 보내진 message의 identifier를 포함한다.
WPARAM wParam : 메세지에 대한 부가적인 정보를 포함한다. 정확한 의미는 무슨 메세지가 보내졌는지에 따라 다르다.
LPARAM lParam : WPARAM과 동일하고, 간단히 좀 더 많은 정보를 포함한다.
DWORD time : 그 메세지가 event queue에서 어떤 정확한 시간에 보내졌는지를 포함한다.
POINT pt : 정확한 마우스 위치를 포함하는데, 스크린 좌표에서이고, 그 메세지가 보내질 때 이다.


while(GetMessage(&msg, NULL, 0, 0))

GetMessage()는 메세지 큐에서 어떤 메세지를 받아서 우리의 msg 구조체에 넣는 함수이다. 그것은 항상 TRUE를 반환하는데, 그 프로그램이 exit하려는 상황을 제외하고이다. 그 상황에서 그것은 FALSE를 반환한다. 그 방식으로, 우리의 while loop는 그 프로그램이 완전히 끝날 때에만 break된다.

GetMessage() 함수는 다뤄질 4개의 파라미터가 있찌만, 우리는 그것의 prototype을 처음에 다룰 것이다:

BOOL GetMessage(
LPMSG lpMsg : 이 파라미터는 message 구조체의 포인터이고, 우리가 다뤘던 것과 같다. 우리는 우리의 구조체의 주소를 여기다 넣는다.
HWND hWnd : 이것은 메세지가 들어올 윈도우에 대한 handle이다. 그러나, 너는 우리가 여기에 NULL을 넣은 것을 눈치챌지도 모른다. NULL은 우리의 윈도우 중 어떤 것에 대해 다음 메세지를 받는 것을 의미한다. 우리는 실제로 여기에 hWnd value를 넣을 수 있고, 그것은 어떠한 차이도 만들지 않을 것이다, 그러나 만약 우리가 많은 윈도우를 가진다면 그것은 차이가 있다. 이 파라미터에 나는 NULL를 넣는다.
UINT wMsgFilterMin, UINT wMsgFilterMax : 이 파라미터들은 메세지의 종류들을 제한하는데 사용될 수 있는데, messege queue를 retrieve off하기 위해서이다. 예를들어: WM_KEYFIRST를 wMsgFilterMin에 사용하고, WM_KEYLAST를 사용하는 것은 그 메세지 타입을 키보드 메세지로 제한한다. WM_KEYFIRST와 WM_KEYLAST는 첫 번째와 마지막 키보드 메세지의 정수값과 같다. 마찬가지로, WM_MOUSEFIRST와 WM_MOUSELAST는 메세지 유형을 마우스 메세지로 제한한다.

이러한 파라미터에 한 가지 특별한 경우가 있다. 만약 너가 각 값을 '0'으로 채운다면, 그러면 GetMessage()는 너가 어떤 메세지는 모으고 싶다는 것을 가정한다, 그것의 값이 무엇이든. 너는 우리의 프로그램에서 우리가 이것을 했다는 것을 알 것이다.
)

TranslateMessage, DispatchMessage 생략

2. The WindowProc() Function
우리가 main loop를 다뤘으니, 너가 계속해서 들은 이 WindowProc() 함수를 봐보자.

여기에 무슨일이 발생하는지에 대해 간단한 설명이 있다. GetMessage()는 message를 받고, 우리는 그것을 바꾸고, DispatchMessage()함수를 사용해서 그것을 보낸다.

DispatchMessage()함수는 그러고나서 적절한 WindowProc() 함수에 대한 호출을 한다. 운 좋게도, 우리는 걱정해야 할 오직 하나의 WindowProc 함수만을 가지고 있다. 그래서 상황은 상대적으로 간단할 것이다.

WindowProc() 함수가 호출될 때, MSG struct로부터 4개의 정보의 조각들이 보내진다. 이러한 것을 보기 위해 WindowProc() 함수에 대해 prototype을 봐보자.

너가 MSG 구조체의 파라미터들에 대해 이전에 보았다면, 너는 이것을 이미 보았따, 그래서 우리느 ㄴ여기에서 그것들을 다시 다루지 않을 것이다.

우리갸 여기에서 할 것은, 이 함수의 내용이다. 너가 코드로부터 보았듯이, 그것은 프로그램에 정의되는데, 이것은 우리가 그것의 코드를 제공한다는 내용이다.

한 message가 WindowProc에 진입할 때, 우리는 uMsg 인자를 무슨 메세지인지 결정하기 위해 사용할 수 있다. 많은 프로그래머들은 그 메세지를 결정하기 위해 switch() 문을 상요한다. 여기에 너가 이것을 하는 예제가 있다.

여기에서 그 스위치문은 작동시킬 코드를 찾는다. 물론, 우리는 WM_DESTROY message만을 제공하고, 만약 어떠 ㄴ다른 메세지가 보내진다면, 우린느 그것을 무시하는 것을 의미한다. WM_DESTROY 메세지는 윈도우가 닫혀질 때 보내진다. 그래서 윈도우가 끝날 떄, 우리는 우리가 우리의 프로그램을 정리하기 위해 필요한 것 무엇이든 할 수 있다. 지금 당장, 구성되어 있는 모든 것은 그 프로그램에게 우리가 끝났다고 말하는 것이고, 0을 반환하는 것인데, 이것은 모든 것이 정리되었다고 가리키는 것이다. 우리는 다음의 코드로 이것을 한다:

PostQuitMessage() 함수는 WM_QUIT 메세지를 보내는데, 그것은 0의 정수값을 가진다. 만약 너가 너의 main loop에 대해 다시 상기한다면, 너는 GetMessage() 함수가 그 프로그램이 그만 둘 때만 false를 반환한다는 것을 기억할 것이다. 좀 더 구체적으로, 0이 보내질 때만 FALSE를 반환한다. WM_QUIT의 겨웅에서 처럼. 그래서 이 함수가 기본적으로 하는 것은 우리의 프로그램에게 WinMain() 함수를 적절히 끝내라고 말하는 것이다.

그 다음에 우리는 0을 반환한다. 이것은 오히려 중요하다. 왜냐하면 그것은 우리가 다루었던 윈도우즈에 그 메세지를 말하기 때문이다. 만약 우리가 다른 것을 보낸다면, 윈도우즈는 혼란스러울 수 있따.

만약 너가 우리의 프로그램 코드를 더 공부한다면, 너는 설명되어야할 마지막 함수가 있다는 것을 발견할 것이다. DefWindowProc(). 이 함수가 하는 것은, 우리가 다루지 않았던 어떤 메세지들을 다루는 것이다. 요약해서, 그것은 우리가 0을 반환하지 않는 메세지를 다룬다. 우리는 그러므로 그것을 WindowProc() 함수의 끝에 반환하는데, 그것이 우리가 놓친 어떤 것을 잡도록 하기 위해서이다.