728x90

Form1.cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Windows.Forms;
using System.Threading;
namespace CsharpExample
{
    public partial class Form1 : Form
    {
  
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            Client c = new Client();
            MessageBox.Show("수신 완료!""알림");
            // 텍스트 박스에 서버로부터 받은 메시지 출력
            textBox1.Text = c.returnMessageFromServer();
        }
        private void button2_Click(object sender, EventArgs e)
        {
            // UI freeze(화면 정지)를 막기 위해 쓰레드 생성
            new Thread(new ThreadStart(Tread_CreateServer)).Start();
            MessageBox.Show("서버 생성됨""알림");
        }
        public void Tread_CreateServer()
        {
            Server s = new Server();
            s.Run();
        }
    }
}
cs




Form1.Designer.cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
namespace CsharpExample
{
    partial class Form1
    {
        /// <summary>
        /// 필수 디자이너 변수입니다.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
 
        /// <summary>
        /// 사용 중인 모든 리소스를 정리합니다.
        /// </summary>
        /// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region Windows Form 디자이너에서 생성한 코드
 
        /// <summary>
        /// 디자이너 지원에 필요한 메서드입니다.
        /// 이 메서드의 내용을 코드 편집기로 수정하지 마십시오.
        /// </summary>
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(1212);
            this.textBox1.Name = "textBox1";
            this.textBox1.ReadOnly = true;
            this.textBox1.Size = new System.Drawing.Size(50025);
            this.textBox1.TabIndex = 2;
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(51812);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(5025);
            this.button1.TabIndex = 3;
            this.button1.Text = "받기";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(1243);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(8725);
            this.button2.TabIndex = 4;
            this.button2.Text = "서버 생성";
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(63089);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.button2);
            this.Margin = new System.Windows.Forms.Padding(3434);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();
 
        }
 
        #endregion
 
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
    }
}
 
 
cs




Server.cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
 
namespace CsharpExample
{
    class Server
    {
        private IPEndPoint ipep;
        private Socket server;
 
        public Server()
        {
            Initialize();
        }
 
        private void Initialize()
        {
            ipep = new IPEndPoint(IPAddress.Any, 9999);
            server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
 
        public void Run()
        {
            server.Bind(ipep);
            server.Listen(20);
 
            Socket client = server.Accept();
            IPEndPoint ip = (IPEndPoint)client.RemoteEndPoint;
 
            String buf = "고귀양이 서버입니다. 반갑습니다.";
            Byte[] data = Encoding.Default.GetBytes(buf);
            client.Send(data);
            data = new Byte[1024];
            client.Receive(data);
            buf = Encoding.Default.GetString(data);
 
            client.Close();
            server.Close();
        }
    }
}
 
cs



Client.cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
 
namespace CsharpExample
{
    class Client
    {
        private IPEndPoint ipep;
        private Socket client;
 
        public Client()
        {
            Initialize();
        }
 
        private void Initialize()
        {
            ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
            client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
 
        public String returnMessageFromServer()
        {
            client.Connect(ipep);
 
            Byte[] _data = new Byte[1024];
            client.Receive(_data);
            String _buf = Encoding.Default.GetString(_data);
 
            client.Close();
 
            return _buf;
        }
    }
}
 
cs



실행한 모습







728x90
728x90

원문 : http://www.braynzarsoft.net/viewtutorial/q16390-28-skeletal-animation-based-on-the-md5-format


저번 강좌에 이어서 MD5 모델을 애니메이팅을 해보도록 하겠습니다. MD5 포맷은 애니메이션을 하기 위해 뼈대 시스템(더 정확히는 관절)을 사용하는데 그래서 어떻게 "md5anim" 파일을 읽고 우리의 모델에 적용할 수 있는지에 대해 배워보도록 할 것입니다. 뼈대 시스템은 애니메이션의 모든 프레임에 대해 기본적으로 새 모델을 가지는 키프레임 애니메이션을 저장하는 것보다 메모리를 적게 먹어서 아주 좋습니다. 관절 시스템(우리가 사용할)은 각 프레임에서 본(뼈)의 위치와 방향만 저장합니다. 본 시스템은 또 "랙돌(rag-doll, 보통 게임에서 굴러다니는 시체같은거에 적용시킴)" 물리를 적용하기 원할때 완전 좋습니다. 이번 강좌가 끝나면 어떤 모델이든 뼈대 애니메이션을 로드할 수 있고 실행 동안 여러분만의 애니메이션이나 랙돌 효과를 만들 수 있습니다.



Introduction

md5 강좌의 두번째 부분입니다. 저번 강좌에서 md5 모델 로딩하고 뼈대 시스템을 이용하여 정점 위치와 법선을 계산에 관해 다루었었습니다. 이번 강좌에서는 md5 애니메이션 파일인 "md5anim" 로드하고 파일에 저장된 데이터를 사용하여 모델을 움직이는것을 어떻게 하는지에 대해 배울 것입니다.


Animating the Model (Using the Joint Structure)

저번 시간에 뼈대 애니메이션을 간단하게 언급했습니다. 그래서 이제 이번 시간에는 모델을 애니메이팅 하깅 위해 뼈대 구조체를 (관절) 어떻게 사용할지에 집중할 것입니다. 천천히 하면 다 할 수 있습니다.

먼저 애니메이션 파일을 (.md5anim) 로드할 것인데 밑에서 다룰 것입니다. 그 다음에 애니메이션에서 현재 시간에 따른 뼈대를 계산해야합니다. 두 프레임사이 (이미 지나간 프레임과 구성중인 프레임) 현재 시간에 따른 두 뼈대의 "보간"에 의해 할 수 있습니다. 보간된 뼈대를 구한 후에 weight를 이용하여 정점 위치와 법선을 다시 계산해야 합니다. 마지막으로 정점 위치와 법선을 (법선과 같은 방식으로 tangent 와 bitangent를 구할 수 있습니다.) 구한 후 우리의 버텍스 버퍼에 갱신해주어야 합니다. 기본적으로 방금 얘기한 4단계와 같으며 첫번째 단계(애니메이션 로드)는 짧게 다뤄지며 그래서 2단계 보간된 뼈대 만들기로 넘어갑니다. Creating the Interplated Skeleton 애니메이션을 로드할때 모든 프레임에 대한 뼈대를 만듭니다. 애니메이션의 현재 프레임을 얻기 위해 두 프레임(예를들어 프레임1 과 프레임 2)을 "보간"할 수 있습니다. 물론 프레임당 보간된 뼈대를 생성하지 않고 애니메이션의 프레임들을 사용할 수 있지만 그렇게 하면 "뚝뚝끊겨"보일 수 있고 게임이 느려지면 날으는 총알이 여러개로 보이는 것처럼 매우 부드럽지 못한 애니메이션 될 것입니다. 또 애니메이션 파일은 초당 애니메이션이 실행되는 프레임이 얼마인지 표시합니다. 우리가 로딩하는 모델은 초당 30 프레임을 사용합니다. 현재 우리가 있는 프레임을 얻기 위해 현재 애니메이션 시간을(애니메이션 시작 이후 시간) 프레임 레이트와 곱할 수 있습니다. 이 계산은 "5.3667" 같은 실수(부동 소수점 수)로 계산됩니다. 이 "5.3667"의 경우 우리는 현재 프레임 "5"에 있으므로 가장 가까운 whole number(자연수와 0을 갖는 수, 정수에서 음수집합을 뺀 수)로 내림하기 위해 floorf() 함수를 사용할 수 있으며 이 경우 5가 됩니다. 바로 이게 우리가 frame0에 저장할 것이며 보간할 두 프레임 뼈대의 첫번째가 될 것입니다. frame0에 간단하게 "1"을 더함으로 두번째 뼈대 프레임을 얻을 수 있으며 frame1이 되는데 방금 예에서 "6"이 될 것입니다. 이제 보간된 뼈대를 구하기 위해 보간시킬 두 프레임 뼈대를 얻어 우리의 모델을 업데이트할 수 있게 되었지만 두 뼈대를 보간할때 각각의 것이 얼마나 사용될지를 알기 위해 0 과 1 ("0"은 frame0만, "0.5"는 frame0 반과 frame1 반, "1"은 완전히 frame1을 보간에 사용)사이 값이 필요합니다. 어떤 값을 우리가 보간 요소로 사용할 수 있을까? 아마도 간단한 질문일 것이지만 바로 위에서 현재 있는 프레임을 구할때 얻은 값의 나머지를 사용할 것이며 방금 예에서는 "0.3667"이 될 것입니다. (위의 계산 값에 frame0를 빼서 구합니다.) 이제 보간을 해봅시다. 이제 실제 보간된 뼈대를 구할 것입니다. 정말로 매우 간단합니다. 모델의 각 관절을 돌며 (애니메이션들은 단지 위치와 방향만 다른 같은 관절을 사용하기 때문에) 관절들의 위치와 방향을 보간합니다. 위치를 업데이트 하기 위해 다음과 같은 방정식을 사용할 수 있습니다.

interpolatedJoint.position = joint0.pos + (interpolation * (joint1.pos - joint0.pos))

두 프레임의 관절 사이 보간된 방향을 구하기 위해 구면 선형 보간 혹은 "Slerp"라 부르는 테크닉을 사용할 것입니다. 편리하게도 slerp이 가능한 xna math 함수가 있어서 다음 줄과 같이 보간된 방향을 얻을 수 있습니다. (xna를 쓰지 않는 관계로 공부겸 직접 만들어 사용하자)

interpolatedJoint.orientation = XMQuaternionSlerp(joint0.orientation, joint1.orientation, interpolation))

위 과정을 모든 관절에 대해 수행하면 보간된 뼈대를 얻습니다.

Updating the Vertex Position and Normal 다음으로 할 일은 정점과 법선을 보간된 뼈대를 이용하여 갱신하는 것입니다. 기본적으로 저번 강좌에서 정점 위치를 구했던 방법으로 할 수 있습니다. 각 정점에 대해 루프를 돌고 그 루프에서 각 정점의 weight 위치들에 대해 루프를 돕니다. 먼저 관절 방향을 이용하여 관절을 축으로 다음과 같은 방정식으로 weight를 회전시킵니다.

weightPos = XMQuaternionMultiply(XMQuaternionMultiply(jointOrientation, weightPos), jointOrientationConjugate))

위 식은 우리에게 관절 공간상의 weight 위치를 줄 것입니다. 이제 weight를 모델 공간상의 관절 위칠 옮겨야 합니다. weight 위치에 관절 위치를 더하는 것으로 쉽게 할 수 있습니다. 마지막으로 weight 위치를 가중치와 곱한 뒤 정점 최종 위치에 더합니다. 또 법선도 계산할 것입니다. 아직 설명하지는 않았지만 weight의 법선(md5mesh 파일을 로드하는 함수를 수정해서 얻음)을 이용할 것입니다. weight 위치를 관절 방향을 이용하여 회전시켰던 것처럼 같은 방법으로 weight 법선을 회전시킬 것입니다. 법선은 위치를 가지지 않기 때문에 (벡터라서) 법선에 관절 위치를 더할 필요는 없습니다. 그러고는 weight 법선과 가중치를 곱하고 정점의 최종 법선에 더합니다.

Updating Direct3D's Buffers 마지막으로 할 일은 버텍스 버퍼를 갱신하는 것입니다. 3가지 방법이 있습니다. (D3D11_USAGE_STAGING, D3D11_USAGE_DYNAMIC, D3D11_USAGE_DEFAULT중에 하나를 설정하는 것. D3D11_USAGE_IMMUTABLE 타입은 갱신이 불가합니다.)

첫번째 방법은 D3D11_USAGE_STAGING 버퍼로 D3D11_USAGE_DEFAULT buffer를 사용하는 것입니다. 스테이징 버퍼는 GPU에 보내진 버퍼로부터 정보를 얻는데 사용되는데 디폴트 버퍼는 기본적으로 스테이징 버퍼의 복사이기 때문입니다. 버퍼를 갱신하기 위해 먼저 ID3D11DeviceContext::Map와 ID3D11DeviceContext::Unmap를 이용하여 스테이징 버퍼를 갱신할 것입니다. 그러고는 스테이징 버퍼의 내용을 ID3D11DeviceContext::CopyResource를 이용하여 디폴트 버퍼로 복사할 것입니다. Map 메소드는 D3D11_MAPPED_SUBRESOURCE 객체를 반환하는데 객체의 pData 멤버는 버퍼내 데이터의 시작에 대한 포인터입니다. 그 다음에 memcpy() 함수로 버퍼내 정점 (혹은 여러분이 원하는 어떤 데이터든지) 을 저장할 수 있습니다. 다음처럼 할 수 있습니다.

D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
ID3D11DeviceContext->Map(stagingVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);

memcpy(mappedVertBuff.pData, &vertices[0], (sizeof(Vertex) * vertices.size()));

ID3D11DeviceContext->Unmap(stagingVertexBuffer, 0);

ID3D11DeviceContext->UpdateSubresource( defaultVertexBuffer, 0, NULL, &stagingVertexBuffer, 0, 0 );

두번째 방법은 스테이징 버퍼없이 그냥 디폴트 버퍼를 갱신하는 것입니다. 한줄이면 되며 ID3D11DeviceContext::CopyResource 메소드를 사용합니다. 다음 라인처럼 디폴트 버퍼를 갱신할 수 있습니다.

ID3D11DeviceContext->UpdateSubresource( defaultVertexBuffer, 0, NULL, &vertices[0], 0, 0 );

마지막 방법은 D3D11_USAGE_DYNAMIC 방식으로 만들어진 버퍼에 대한 것입니다. 이것은 우리가 사용할 방법인데 빠른 업데이트를 위해 만들어 졌기 때문입니다. (비록 GPU에 의해 느리게 읽어지지만, 동적 버퍼 정적 버퍼에 대해 이전 강좌(md5mesh 로드)에서 언급하였다) 스테이징 버퍼를 했던 것 처럼 Map 과 Unmap을 이용하여 버퍼를 갱신하 수 있습니다. 다음과 같이

D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
ID3D11DeviceContext->Map(dynamicVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);

memcpy(mappedVertBuff.pData, &vertices[0], (sizeof(Vertex) * vertices.size()));

ID3D11DeviceContext->Unmap(dynamicVertexBuffer, 0);

첫번째 두번째 방법 스테이징 버퍼와 디폴트 버퍼를 사용하는 것은 동적 버퍼에 비해 매우 느리게 갱신되어서 버퍼가 프레임당 한번 이하로 갱신된다면 첫 두 방법으로 버퍼를 갱신하는 것이 좋습니다. 디폴트 버퍼는 GPU에 의해 빠르게 읽어지고 느리게 갱신됩니다. 동적 버퍼는 GPU에 의해 느리게 읽어지지만 훨씬 빠르게 갱신됩니다.



The .md5anim Format

.md5anim 포맷은 기본적으로 5개의 섹션으로 분할되어 있는데 헤더, hierarchy, 바운드, 기본프레임, 프레임들 입니다. 헤더는 파일과 애니메이션에 관한 정보를 가지고 있는데 fps(frames per second), 파일 버전, 관절과 프레임 총 개수 등 입니다. hierarchy는 모델에 사용된 관절의 리스트입니다. hierarchy 섹션에서 관절은 .md5mesh 파일의 수와 부모 자식 관계와 일치해야 합니다. hierarchy 다음은 바운드입니다. 이 섹션은 mesh의 각 프레임에 대해 축 일치 바운딩 박스(AABB)를 정의합니다. AABB는 충돌검출이나 픽킹 같은 빠르지만 부정확한 계산에 사용됩니다. 바운드 다음은 기본프레임으로 디폴트 위치에 있을때 각 관절의 방향과 위치를 저장합니다. 모든 프레임들은 이 기본프레임 혹은 기본 뼈대를 시작으로 지어집니다. 마지막으로 프레임들이 있습니다. 이 섹션은 프레임당 하나씩 있으며 각 섹션은 관절이 어떻게 이동되고 회전될지를 설명하는부동소수점값(실수)의 리스트를 포함합니다. "MD5Version" 이 문자열 다음에는 파일의 버전을 설명하는 숫자가 옵니다. 우리의 로더는 버전 10에 특화되어 있습니다. (실제 로더 체크는 안하지만) 제가 다운로드한 모든 md5 모델은 (사실 많지는 않습니다.) 버전 10이지만 다른 버전에 대한 정보를 아실 수 있습니다.

MD5Version 10
"commandline" 이 라인은 신경쓰지 않으셔도 됩니다.
commandline ""
"numFrames" 우리의 애니메이션의 프레임 개수입니다. 이번 강좌 모델은 46프레임을 사용해서 파일에 46개의 프레임 섹션이 있을 것입니다.
numFrames 46

"numJoints" 모델의 관절 개수이며 관절은 hierarchy 섹션에 있습니다. 이 숫자는 .md5mesh 파일의 숫자와 일치해야 합니다.

numJoints 31

"frameRate" frame rate는 초당 몇 프레임으로 애니메이션이 실행되는지 입니다. 파일이 의도하는 같은 스피드로 부드럽고 지속적인 애니메이션을 유지하도록 확실히 하기 위해 이 값을 우리의 프로그램에서 고려해야 합니다.

frameRate 30

"numAnimatedComponents" 이 수는 컴포넌트 혹은 각 프레임 섹션의 부동소수점 수 객수 입니다. 각 컴포넌트들은 기본프레임 관절 위치 혹은 방향의 컴포넌트중 하나를 대체할 것입니다. 이는 나중에 설명할 것입니다.

numAnimatedComponents 186

"hierarchy" 관절 설명의 시작부분입니다. 관절 설명은 다음 라인 ("hierarchy {" 다음) 부터 시작하여 닫는 괄호 ("}")에 도달할때 까지 갑니다.

관절의 개수와 부모 인덱스와 관절 이름은 반드시 .md5mesh 파일의 것과 일치해야 합니다. 만약 일치하지 않으면 예산치 못한 애니메이션 결과를 가질 수 있습니다. "hierarchy {" 뒤 각 라인은 새 관절입니다. 각 라인은 두 따옴표 사이 문자열로 시작하는데 문자열은 각 관절의 이름입니다. 관절 이름 다음은 부모 인덱스 입니다. 부모 인덱스가 "-1" 일때 해당 관절은 hierarchy의 가장 위에 위치하며 부모를 가지지 않음을 의미합니다. 그 다음 숫자는 나중에 프레임 섹션에서 관절의 어떤 부분(위치와 방향의 x, y, z)이 갱신될지를 설명하는 플래그 입니다. 마지막 값은 시작 인덱스 입니다. 시작 인덱스는 프레임 데이터에서 특정 관절 갱신을 시작하는 값입니다. 밑에 코드 보시면 알 수 있듯이 "bip01"의 시작 인덱스는 "0"인데 프레임 데이터 에서 가장 첫번째 값으로 시작하며 갱신될 것을 의미합니다. 플래그 섹션은 관절을 어떻게 갱신할지 그리고 프레임 데이터에서 시작 인덱스로부터 얼마나 많은 값을 사용할지를 정의할 것 입니다. (1~6 사이, 3개는 위치 3개는 방향)

hierarchy {
    "Bip01"    -1 63 0
    ...
}

"bounds" 바운드는 각 프레임에 대한 축 정렬 바운딩 박스입니다. "bounds"뒤 각 라인은 프레임들의 AABB 인데 x, y, z 축별로 가장 크고 가장 작은 값을 의미하는 최대, 최소 점으로 구성되어 있습니다. 괄호 안 첫번째 실수 3개는 최소점을 나타내고 두번째 괄호의 실수 3개는 최대점을 나타냅니다.

( -30.4137325286 -23.4689254760 -42.4965248107 ) ( 35.7306137084 6.3094730377 48.7827911376 )

"baseframe" 애니메이션에서 각 관절의 기본 위치입니다. 이 값은 .md5mesh에 묘사된 bind-pose와 같을 필요는 없습니다. 각 프레임은 이 기본프레임으로부터 프레임 뼈대를 생성하는데 모든 프레임이 모든 관절 위치와 회전을 나타내지 않기때문입니다. 그 이유는 기본프레임이 0이나 다른 어떤 값으로 채워질 수 있으며 이 경우 모든 관절의 위치나 방향은 모든 프레임에서 갱신되개 때문입니다. "baseframe" 뒤 각 라인은 대응하는 관절 위치와 방향을 나태내는데 첫번째 실수 3개는 위치를 그다음 3개는 방향을 나타냅니다.

( 0.5196304321 1.7362534999 4.6482505798 ) ( 0.0000000000 0.0000000000 0.7071063041 )

"frame" .md5anim 파일에서 살펴 볼 마지막 섹션은 프레임 섹션입니다. 애니메이션의 각 프레임은 섹션을 하나씩 가지며 각 섹션은 실수 리스트로 이루어져 있습니다. 실수의 개수는 헤더의 "numAnimatedComponents"에 정의되어 있습니다. 각 실수는 기본프레임 과절의 위치와 방향값의 x, y, z를 대체할 것입니다. 관절(위치나 방향의 x, y, z)의 어떤 값 어떤 요소인지는 관절 플래그와 시작 인덱스에 의해 결정됩니다.

0.5196304321 1.7362534999 4.6482505798 0.0000000000 0.0000000000 0.7071063041


Animating the Normals

모든 정점에 대한 법선을 계산하는 것은 상당히 느릴 수 있습니다. 그래서 우리의 모델을 애니메이팅할때를 위한 방법이 따로 있으며 그래서 모든 프레임에 대해 법선을 계산하지 않아도 됩니다. 우리가 하는 방법은 .md5mesh 파일로부터 원래 모델을 로드할때 관절 공간상의 (각 weight에 대해) 법선을 계산하는 것입니다. (이번 강좌에서 이 작업을 하기위해 함수를 수정했습니다.) 관절 공간의 법선을 구한 뒤 관절 방향을 기인한 각 프레임에 대한 법선을 쉽게 회전시킬 수 있습니다. 마치 관절 방향을 이용하여 weight 위치를 회전시킨것과 똑같이 단, 법선을 재위치하지 않아도 됩니다. 관절 공간상의 법선을 계산할때 먼저 각 정점에 대해 보통 모델에 대해 하듯이 법선을 계산합니다. 그러면 정점 법선을 사용할 수 있고 관절 방향의 역으로 회전시킬 수 있습니다. (LoadMd5Model 함수의 내용이며 왜 역방향으로 회전시키는지 불분) 정점에 대해 각 weight에 대해 돌며 이 weight가 바인딩된 관절로 이 계산을 수행합니다.


Right/Left Handed Coordinat Systems

여러분은 아마도 제가 .md5mesh와 .md5anim을 로딩할때 "z" 와 "y" 값을 바꾸는 것을 알아차리실 겁니다. 왜냐하면 파일이 오른손 좌표계로 출력되는 반면에 directx는 왼손 좌표계를 사용하기 때문입니다. 이미 모델이 왼손 좌표계로 이루어져 있는 경우 데이터 로딩시 y와 z 값을 다시 바꾸시면 됩니다.



New Structures

여기 여러개의 새로운 구조체들이 있습니다. 보다시피 첫번쨰는 .md5anim 파일의 바운드 섹션에서 로드된 바운딩 박스를 위한 것 입니다. 다음 구조체는 프레임 데이터 구조체입니다. 이 구조체는 어떤 프레임인지 확인하는 숫자와 프레임 데이터인 실수 배열을 가질 것입니다. 이 실수값들은 각 프레임에 대한 기본프레임 뼈대를 갱신하는데 사용될 것입니다. 다음 새 구조체는 관절을 위한 것입니다. 이 구조체는 다른 관절 구조체(md5mesh때 생성한)와는 다른 데이터를 가지는데 각 애니메이션의 관절은 (md5mesh 파일은 하나 이상의 md5anim 파일을 가질 수 있기 때문에) 애니메이션 마다 다를 수 있습니다. 그래서 이 구조체는 플래그나 (프레임마다 관절의 어떤 요소가 업데이트 될지) 시작 인덱스같은 (업데이트를 시작하는 프레임 데이터 열에서 첫번째 값(frameID)) 애니메이션 전용 데이터를 가집니다. 다음은 ModelAnimation 구조체 입니다. 나중에 이 구조체의 벡터(STL)가 Model3D 구조체의 멤버로 있는것을 보실 겁니다. 그 이유는 각 모델은 하나 이상의 애니메이션을 가질수 있기 때문입니다. 어쨌든 이 구조체는 애니메이션에 따른 (각 프레임의 뼈대 포즈) 정보를 저장합니다. 멤버는 이미 위에서 설명했거나 제 설명없이도 이해하기 충분히 쉽습니다. 하지만 마지막 멤버인 frameSkeleton은 설명할 것입니다. 각 뼈대는 관절의 벡터(stl)로 정의되어 있고 애니메이션에서 각 프레임 별로 단일 뼈대를 가집니다. 첫번째 벡터(stl)는 애니메이션의 각 프레임을 위함이고 그중 한 요소 내부 벡터(stl)은 뼈대의 관절들의 벡터(stl)입니다. (std::vector<std::vector<Joint>> frameSkeleton 에 대한 설명이다.) 그러면 나중에 이 벡터(stl)의 멤버로부터 "frameSkeleton[frame][joint]" 이런식으로 관절 하나를 호출할 수 있으며 아니면 간단하게 "frameSkeleton[frame]" 이런식으로 프레임을 통째로 가져올 수 있습니다.

struct BoundingBox
{
    XMFLOAT3 min;
    XMFLOAT3 max;
};

struct FrameData
{
    int frameID;
    std::vector<float> frameData;
};
struct AnimJointInfo
{
    std::wstring name;
    int parentID;

    int flags;
    int startIndex;
};

struct ModelAnimation
{
    int numFrames;
    int numJoints;
    int frameRate;
    int numAnimatedComponents;

    float frameTime;
    float totalAnimTime;
    float currAnimTime;

    std::vector<AnimJointInfo> jointInfo;
    std::vector<BoundingBox> frameBounds;
    std::vector<Joint>    baseFrameJoints;
    std::vector<FrameData>    frameData;
    std::vector<std::vector<Joint>> frameSkeleton;
};


Updated Weight Structure

법선을 추가하기 위해 weight 구조체를 수정하였습니다. 그 이유는 법선을 관절 공간에 정의한다면 모든 프레임에 대해 모든 법선을 다시 계산해야하는 대신 애니메이션의 진전으로써 쉽게 바꿀 수 있습니다.

struct Weight
{
    int jointID;
    float bias;
    XMFLOAT3 pos;
    ///////////////**************new**************////////////////////
    XMFLOAT3 normal;
    ///////////////**************new**************////////////////////
};


Updated Model3D Structure

여기서 볼 수 있듯이 애니메이션의 배열을 가지기 위해 Model3D 구조체를 수정하였습니다.

struct Model3D
{
    int numSubsets;
    int numJoints;

    std::vector<Joint> joints;
    std::vector<ModelSubset> subsets;

    ///////////////**************new**************////////////////////
    std::vector<ModelAnimation> animations;
    ///////////////**************new**************////////////////////
};


Two New Functions

새로 추가된 함수들입니다. 첫번째 것은 .md5anim 파일에서 애니메이션을 로드하고 두번째 것은 애니메이션에 넘겨진 시간에 따라 애니메이션이 사용할 버텍스 버퍼를 갱신합니다.

bool LoadMD5Anim(std::wstring filename,    Model3D& MD5Model);

void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation);

The DetectInput() Function


키 검출 함수가 바뀌었습니다. "R"이 추가되었습니다. 이 키를 누를때 애니메이션이 갱신될 것입니다. "timeFactor" 라는 실수도 추가하였는데 이걸로 애니메이션 영역에서 빠르게 하거나 느리게 할 수 있습니다. 여러분이 정말로 게임 전반에서 속도를 조절하고 싶으면 이 time factor를 많은 영역에 두어야 합니다. (모델 뿐만 아니라 다른 것도 속도 조절을 하고 싶다면 전역 변수이거나 클래스 관계에서 상위단계에 위치시켜 코드 전반에 걸쳐 적용할 수 있어야 한다는 의미같습니다. ㅠ)

void DetectInput(double time)
{
    DIMOUSESTATE mouseCurrState;

    BYTE keyboardState[256];

    DIKeyboard->Acquire();
    DIMouse->Acquire();

    DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState);

    DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState);

    if(keyboardState[DIK_ESCAPE] & 0x80)
        PostMessage(hwnd, WM_DESTROY, 0, 0);

    float speed = 10.0f * time;

    if(keyboardState[DIK_A] & 0x80)
    {
        moveLeftRight -= speed;
    }
    if(keyboardState[DIK_D] & 0x80)
    {
        moveLeftRight += speed;
    }
    if(keyboardState[DIK_W] & 0x80)
    {
        moveBackForward += speed;
    }
    if(keyboardState[DIK_S] & 0x80)
    {
        moveBackForward -= speed;
    }
    ///////////////**************new**************////////////////////
    if(keyboardState[DIK_R] & 0X80)
    {
        float timeFactor = 1.0f;    // You can speed up or slow down time by changing this
        UpdateMD5Model(NewMD5Model, time*timeFactor, 0);
    }
    ///////////////**************new**************////////////////////
    if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY))
    {
        camYaw += mouseLastState.lX * 0.001f;

        camPitch += mouseCurrState.lY * 0.001f;

        mouseLastState = mouseCurrState;
    }

    UpdateCamera();

    return;
}


The LoadMD5Anim() Function

여기서 애니메이션을 로드할 것입니다. 또 애니메이션의 모든 프레임에 대한 뼈대를 생성할 것입니다. 함수를 부분 부분 살펴 보도록 하겠습니다.

bool LoadMD5Anim(std::wstring filename,    Model3D& MD5Model)
{    
    ModelAnimation tempAnim;                        // Temp animation to later store in our model's animation array

    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if ( checkString == L"MD5Version" )        // Get MD5 version (this function supports version 10)
            {
                fileIn >> checkString;
                /*MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numFrames" )
            {
                fileIn >> tempAnim.numFrames;                // Store number of frames in this animation
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> tempAnim.numJoints;                // Store number of joints (must match .md5mesh)
            }
            else if ( checkString == L"frameRate" )
            {
                fileIn >> tempAnim.frameRate;                // Store animation's frame rate (frames per second)
            }
            else if ( checkString == L"numAnimatedComponents" )
            {
                fileIn >> tempAnim.numAnimatedComponents;    // Number of components in each frame section
            }
            else if ( checkString == L"hierarchy" )
            {
                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)    // Load in each joint
                {
                    AnimJointInfo tempJoint;

                    fileIn >> tempJoint.name;        // Get joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    fileIn >> tempJoint.parentID;            // Get joints parent ID
                    fileIn >> tempJoint.flags;                // Get flags
                    fileIn >> tempJoint.startIndex;            // Get joints start index

                    // Make sure the joint exists in the model, and the parent ID's match up
                    // because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
                    // joint hierarchy must match up
                    bool jointMatchFound = false;
                    for(int k = 0; k < MD5Model.numJoints; k++)
                    {
                        if(MD5Model.joints[k].name == tempJoint.name)
                        {
                            if(MD5Model.joints[k].parentID == tempJoint.parentID)
                            {
                                jointMatchFound = true;
                                tempAnim.jointInfo.push_back(tempJoint);
                            }
                        }
                    }
                    if(!jointMatchFound)                    // If the skeleton system does not match up, return false
                        return false;                        // You might want to add an error message here

                    std::getline(fileIn, checkString);        // Skip rest of this line
                }
            }
            else if ( checkString == L"bounds" )            // Load in the AABB for each animation
            {
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numFrames; i++)
                {
                    BoundingBox tempBB;

                    fileIn >> checkString;                    // Skip "("
                    fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
                    fileIn >> checkString >> checkString;    // Skip ") ("
                    fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
                    fileIn >> checkString;                    // Skip ")"

                    tempAnim.frameBounds.push_back(tempBB);
                }
            }            
            else if ( checkString == L"baseframe" )            // This is the default position for the animation
            {                                                // All frames will build their skeletons off this
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)
                {
                    Joint tempBFJ;

                    fileIn >> checkString;                        // Skip "("
                    fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
                    fileIn >> checkString >> checkString;        // Skip ") ("
                    fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
                    fileIn >> checkString;                        // Skip ")"

                    tempAnim.baseFrameJoints.push_back(tempBFJ);
                }
            }
            else if ( checkString == L"frame" )        // Load in each frames skeleton (the parts of each joint that changed from the base frame)
            {
                FrameData tempFrame;

                fileIn >> tempFrame.frameID;        // Get the frame ID

                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
                {
                    float tempData;
                    fileIn >> tempData;                // Get the data

                    tempFrame.frameData.push_back(tempData);
                }

                tempAnim.frameData.push_back(tempFrame);

                ///*** build the frame skeleton ***///
                std::vector tempSkeleton;

                for(int i = 0; i < tempAnim.jointInfo.size(); i++)
                {
                    int k = 0;                        // Keep track of position in frameData array

                    // Start the frames joint with the base frame's joint
                    Joint tempFrameJoint = tempAnim.baseFrameJoints[i];

                    tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;

                    // Notice how I have been flipping y and z. this is because some modeling programs such as
                    // 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
                    // need to flip the y and z axes. If your having problems loading some models, it's possible
                    // the model was created in a left hand coordinate system. in that case, just reflip all the
                    // y and z axes in our md5 mesh and anim loader.
                    if(tempAnim.jointInfo[i].flags & 1)        // pos.x    ( 000001 )
                        tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 2)        // pos.y    ( 000010 )
                        tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 4)        // pos.z    ( 000100 )
                        tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 8)        // orientation.x    ( 001000 )
                        tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 16)    // orientation.y    ( 010000 )
                        tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 32)    // orientation.z    ( 100000 )
                        tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];


                    // Compute the quaternions w
                    float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
                        - ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
                        - ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempFrameJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempFrameJoint.orientation.w = -sqrtf(t);
                    }

                    // Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
                    // This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
                    // and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
                    // the child should never come before the parent in the files we loaded in.
                    if(tempFrameJoint.parentID >= 0)
                    {
                        Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];

                        // Turn the XMFLOAT3 and 4's into vectors for easier computation
                        XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
                        XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
                        XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);

                        // Calculate current joints position relative to its parents position
                        XMFLOAT3 rotatedPos;
                        XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));

                        // Translate the joint to model space by adding the parent joint's pos to it
                        tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
                        tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
                        tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;

                        // Currently the joint is oriented in its parent joints space, we now need to orient it in
                        // model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
                        XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
                        tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);

                        // Normalize the orienation quaternion
                        tempJointOrient = XMQuaternionNormalize(tempJointOrient);

                        XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
                    }

                    // Store the joint into our temporary frame skeleton
                    tempSkeleton.push_back(tempFrameJoint);
                }

                // Push back our newly created frame skeleton into the animation's frameSkeleton array
                tempAnim.frameSkeleton.push_back(tempSkeleton);

                fileIn >> checkString;                // Skip closing bracket "}"
            }
        }

        // Calculate and store some usefull animation data
        tempAnim.frameTime = 1.0f / tempAnim.frameRate;                        // Set the time per frame
        tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime;    // Set the total time the animation takes
        tempAnim.currAnimTime = 0.0f;                                        // Set the current time to zero

        MD5Model.animations.push_back(tempAnim);                            // Push back the animation into our model object
    }
    else    // If the file was not loaded
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),                // display message
            L"Error", MB_OK);

        return false;
    }
    return true;
}

Opening the File and Reading the Header Info

저번 강좌에서 설명할 것은 또 설명하는데 시간을 많이 할애하고 싶지 않기 때문에 여러분이 최소한 저번 강좌를 읽었다면 어떻게 동작하는지 대충 아실 겁니다.

bool LoadMD5Anim(std::wstring filename,    Model3D& MD5Model)
{    
    ModelAnimation tempAnim;                        // Temp animation to later store in our model's animation array

    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if ( checkString == L"MD5Version" )        // Get MD5 version (this function supports version 10)
            {
                fileIn >> checkString;
                /*MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numFrames" )
            {
                fileIn >> tempAnim.numFrames;                // Store number of frames in this animation
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> tempAnim.numJoints;                // Store number of joints (must match .md5mesh)
            }
            else if ( checkString == L"frameRate" )
            {
                fileIn >> tempAnim.frameRate;                // Store animation's frame rate (frames per second)
            }
            else if ( checkString == L"numAnimatedComponents" )
            {
                fileIn >> tempAnim.numAnimatedComponents;    // Number of components in each frame section
            }
            
            ...
    }
    else    // If the file was not loaded
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),                // display message
            L"Error", MB_OK);

        return false;
    }
    return true;
}

Load the Joint Hierarchy

다음은 hierarchy를 로드합니다. 위에서 제가 얻급하였듯이 이 hierarchy가 .md5mesh 파일의 것과 일치하는지 확인합니다. 그렇지 않다면 false를 리턴합니다.

            else if ( checkString == L"hierarchy" )
            {
                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)    // Load in each joint
                {
                    AnimJointInfo tempJoint;

                    fileIn >> tempJoint.name;        // Get joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    fileIn >> tempJoint.parentID;            // Get joints parent ID
                    fileIn >> tempJoint.flags;                // Get flags
                    fileIn >> tempJoint.startIndex;            // Get joints start index

                    // Make sure the joint exists in the model, and the parent ID's match up
                    // because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
                    // joint hierarchy must match up
                    bool jointMatchFound = false;
                    for(int k = 0; k < MD5Model.numJoints; k++)
                    {
                        if(MD5Model.joints[k].name == tempJoint.name)
                        {
                            if(MD5Model.joints[k].parentID == tempJoint.parentID)
                            {
                                jointMatchFound = true;
                                tempAnim.jointInfo.push_back(tempJoint);
                            }
                        }
                    }
                    if(!jointMatchFound)                    // If the skeleton system does not match up, return false
                        return false;                        // You might want to add an error message here

                    std::getline(fileIn, checkString);        // Skip rest of this line
                }
            }

Load Each Frames Bounding Box (AABB)


이제 각 프레임의 바운딩 박스를 로드하는데 바로 최소, 최대 점입니다. 이 강좌에서는 바운딩 박스를 사용하지 않을 것이지만 필요해 보이는 어떤 목적 가장 먼저 생각나는 것은 충돌 검출 같은 목적에 사용될 수 있습니다.

            else if ( checkString == L"bounds" )            // Load in the AABB for each animation
            {
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numFrames; i++)
                {
                    BoundingBox tempBB;

                    fileIn >> checkString;                    // Skip "("
                    fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
                    fileIn >> checkString >> checkString;    // Skip ") ("
                    fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
                    fileIn >> checkString;                    // Skip ")"

                    tempAnim.frameBounds.push_back(tempBB);
                }
            }            

Loading the Baseframe

여기 기본프레임을 로드하는데 다시말하지만 이것에 관해 모든 프레임 뼈대들이 이 기본프레임의 뼈대로 만든다는 것 외에 할 말은 없습니다.

            else if ( checkString == L"baseframe" )            // This is the default position for the animation
            {                                                // All frames will build their skeletons off this
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)
                {
                    Joint tempBFJ;

                    fileIn >> checkString;                        // Skip "("
                    fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
                    fileIn >> checkString >> checkString;        // Skip ") ("
                    fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
                    fileIn >> checkString;                        // Skip ")"

                    tempAnim.baseFrameJoints.push_back(tempBFJ);
                }
            }

Loading the Frame

여기는 프레임 섹션입니다. 프레임 섹션은 실수값(개수는 헤더의 numAnimatedComponents에 나와있습니다.)들의 배열을 가집니다.

            else if ( checkString == L"frame" )        // Load in each frames skeleton (the parts of each joint that changed from the base frame)
            {
                FrameData tempFrame;

                fileIn >> tempFrame.frameID;        // Get the frame ID

                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
                {
                    float tempData;
                    fileIn >> tempData;                // Get the data

                    tempFrame.frameData.push_back(tempData);
                }

                tempAnim.frameData.push_back(tempFrame);

Creating the Frame Skeleton

데이터에 대한 실수값들의 묶을을 가지는 프레임을 로드했고 이제 이 데이터를 이용하여 프레임 뼈대를 생성할 수 있습니다. "flag"와 덧붙여 시작 인덱스가 등장하는 곳입니다. 플래그을 설명하는 것으로 시작 해 보겠습니다. 플래그는 비트에 관한 숫자(0 과 1)로 더 정확하게 64의 비트 숫자(6비트)로 관절의 어느 부분이 갱신될지를 나타냅니다. 관절 위치의 x 값이 갱신될 여부를 나타내는 첫번째 비트 부터 관절 방향의 z 값인 마지막 비트순으로 정렬됩니다. 예를 들어 "1" 또는 (000001) 은 관절 위치의 x가 프레임 데이터 배열의 실수 값으로 대체될지를 나타냅니다. "7" 또는 (000111) 은 관절 위치의 모든 x, y, z 가 프레임 데이터의 다음 3개 값들로 갱신되는 것을 나타냅니다. "63" 또는 (111111) 은 위치와 방향 둘 다 x, y, z값이 갱신되는 것을 의미합니다. 관절의 시작 인덱스는 프레임 데이터 배열의 어디서 사직할지 즉 관절을 업데이트하는데 사용할 시작 값을 의미합니다. 관절에 대해 가능한 여섯 플래그를 각각 확인하며 설정되어 있으면 위치나 방향의 x, y, z를 대체하고 "k"를 증가시키는데 관절의 시작 인덱스로부터 프레임 데이터 배열에서 우리의 위치를 추적하고있습니다. 이걸 이해 하셨다면 그리 어렵지 않다는 것을 아실겁니다. 제가 단지 "장황"해짐 없이 설명한 것 같지 않은 것 같습니다. (장황하게 설명 했다는) 기본프레임으로부터 프레임 뼈대의 관절들을 갱신한 후에 방향 사원수의 "w" 값을 관절의 부모 위치와 방향을 고려한 프레임 관절을 갱신하기 전에 계산합니다.

                ///*** build the frame skeleton ***///
                std::vector<Joint> tempSkeleton;

                for(int i = 0; i < tempAnim.jointInfo.size(); i++)
                {
                    int k = 0;                        // Keep track of position in frameData array

                    // Start the frames joint with the base frame's joint
                    Joint tempFrameJoint = tempAnim.baseFrameJoints[i];

                    tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;

                    // Notice how I have been flipping y and z. this is because some modeling programs such as
                    // 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
                    // need to flip the y and z axes. If your having problems loading some models, it's possible
                    // the model was created in a left hand coordinate system. in that case, just reflip all the
                    // y and z axes in our md5 mesh and anim loader.
                    if(tempAnim.jointInfo[i].flags & 1)        // pos.x    ( 000001 )
                        tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 2)        // pos.y    ( 000010 )
                        tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 4)        // pos.z    ( 000100 )
                        tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 8)        // orientation.x    ( 001000 )
                        tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 16)    // orientation.y    ( 010000 )
                        tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 32)    // orientation.z    ( 100000 )
                        tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];


                    // Compute the quaternions w
                    float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
                        - ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
                        - ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempFrameJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempFrameJoint.orientation.w = -sqrtf(t);
                    }

Update Frame Skeleton Joints Based On Parent's Position and Orientation

다음은 위에서 말했던 부로를 기인하여 관절들을 업데이트해 보도록 하겠습니다. hierarchy 리스트에서 추정컨데 부모전에 자식이 먼저 오지 않음으로 부모보다 먼저 자식이 처리되지 않을 것을 알기때문에 그런 경우가 있다면 지금 우리가 하려는 것이 무의미합니다. (부모 관절이 먼저 오는 것이 전제이다.) 각 관절에 대해 돌며 부모 관절을 찾습니다. (부모가 있다면, 탑 관절은 부모가 없기 때문에) 그러고는 부모 방향으로 관절의 위치를 회전시킵니다. 그 다음에 모델 공간에 위치하기 위해 부모 위치를 자식 위치에 더해야 합니다. 그 다음에는 부모 관절에 맞게 자식 관절에 새 방향을 주어야 합니다. 이것을 하기 위해서는 간단하게 함께 곱하면 됩니다. (부모 * 자식 (둘다사원수로))

                    // Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
                    // This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
                    // and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
                    // the child should never come before the parent in the files we loaded in.
                    if(tempFrameJoint.parentID >= 0)
                    {
                        Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];

                        // Turn the XMFLOAT3 and 4's into vectors for easier computation
                        XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
                        XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
                        XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);

                        // Calculate current joints position relative to its parents position
                        XMFLOAT3 rotatedPos;
                        XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));

                        // Translate the joint to model space by adding the parent joint's pos to it
                        tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
                        tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
                        tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;

                        // Currently the joint is oriented in its parent joints space, we now need to orient it in
                        // model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
                        XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
                        tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);

                        // Normalize the orienation quaternion
                        tempJointOrient = XMQuaternionNormalize(tempJointOrient);

                        XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
                    }

                    // Store the joint into our temporary frame skeleton
                    tempSkeleton.push_back(tempFrameJoint);
                }

                // Push back our newly created frame skeleton into the animation's frameSkeleton array
                tempAnim.frameSkeleton.push_back(tempSkeleton);

                fileIn >> checkString;                // Skip closing bracket "}"
            }
        }

Calculating Some Frame Stuff

각 프레임의 시간 길이와 애니메이션 총 시간을 알아야 해서 빠르게 계산합니다. 또 확실하게 최근 애니메이션 시간도 0으로 설정합니다. 다 하면 temp 애니메이션을 우리의 애니메이션 벡터(stl)에 푸쉬하고 함수를 성공적으로 종료합니다.

        // Calculate and store some usefull animation data
        tempAnim.frameTime = 1.0f / tempAnim.frameRate;                        // Set the time per frame
        tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime;    // Set the total time the animation takes
        tempAnim.currAnimTime = 0.0f;                                        // Set the current time to zero

        MD5Model.animations.push_back(tempAnim);                            // Push back the animation into our model object


The UpdateMD5Model() Function

이 함수는 애니메이션을 사용하여 우리의 모델을 갱신하기 원할 때마다 호출 됩니다. 이 함수도 똑같이 부분 부분 살펴 볼 것입니다.

void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
    MD5Model.animations[animation].currAnimTime += deltaTime;            // Update the current animation time

    if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
        MD5Model.animations[animation].currAnimTime = 0.0f;

    // Which frame are we on
    float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;    
    int frame0 = floorf( currentFrame );
    int frame1 = frame0 + 1;

    // Make sure we don't go over the number of frames    
    if(frame0 == MD5Model.animations[animation].numFrames-1)
        frame1 = 0;

    float interpolation = currentFrame - frame0;    // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor

    std::vector<Joint> interpolatedSkeleton;        // Create a frame skeleton to store the interpolated skeletons in

    // Compute the interpolated skeleton
    for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
    {
        Joint tempJoint;
        Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i];        // Get the i'th joint of frame0's skeleton
        Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i];        // Get the i'th joint of frame1's skeleton

        tempJoint.parentID = joint0.parentID;                                            // Set the tempJoints parent id

        // Turn the two quaternions into XMVECTORs for easy computations
        XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
        XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);

        // Interpolate positions
        tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
        tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
        tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));

        // Interpolate orientations using spherical interpolation (Slerp)
        XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));

        interpolatedSkeleton.push_back(tempJoint);        // Push the joint back into our interpolated skeleton
    }

    for ( int k = 0; k < MD5Model.numSubsets; k++)
    {
        for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
        {
            Vertex tempVert = MD5Model.subsets[k].vertices[i];
            tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first
            tempVert.normal = XMFLOAT3(0,0,0);    // Clear vertices normal

            // Sum up the joints and weights information to get vertex's position and normal
            for ( int j = 0; j < tempVert.WeightCount; ++j )
            {
                Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
                Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];

                // Convert joint orientation and weight pos to vectors for easier computation
                XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                // We will need to use the conjugate of the joint orientation quaternion
                XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);

                // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                XMFLOAT3 rotatedPoint;
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                // Compute the normals for this frames skeleton using the weight normals from before
                // We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
                XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);

                // Rotate the normal
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));

                // Add to vertices normal and ake weight bias into account
                tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
                tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
                tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
            }

            MD5Model.subsets[k].positions[i] = tempVert.pos;                // Store the vertices position in the position vector instead of straight into the vertex vector
            MD5Model.subsets[k].vertices[i].normal = tempVert.normal;        // Store the vertices normal
            XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
        }

        // Put the positions into the vertices for this subset
        for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
        {
            MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
        }

        // Update the subsets vertex buffer
        // First lock the buffer
        D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
        hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);

        // Copy the data into the vertex buffer.
        memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));

        d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);

        // The line below is another way to update a buffer. You will use this when you want to update a buffer less
        // than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
        // of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
        // for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
        // of D3D11_USAGE_DYNAMIC
        //d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
    }
}

Which Frames to Use

최근 애니메이션 시간을 갱신하는것으로 함수를 시작합니다. 만약 최근 애니메이션 시간이 전체 애니메이션 시간 보다 크면 최근 애니메이션 시간을 0으로 하여 애니메이션을 재시작합니다. 그 다음 우리가 있는 프레임이 뭔지 알아냅니다. 최근 애니메이션 시간에 frameRate를 곱하는 것으로 할 수 있습니다. 이 결과 값을 가장 가까운 whole number(음이 아닌 정수)로 내림하여 현재 있는 프레임을 얻고 여기에 1을 더하여 다음 프레임을 구합니다. 아주 부드러운 애니메이션을 유지하도록 현재 프레임 뼈대를 구하기 위해 두 프레임을 보간할 것입니다. 이 두개를 보간할때 보간 요소가 필요한데 framerate와 곱한 현재 애니메이션 시간의 나머지를 구함으로 얻을 수 있습니다. (eg. currentFrameTime = 5.3667, currentFrame = 5, interpolationFactor = 0.3667).

void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
    MD5Model.animations[animation].currAnimTime += deltaTime;            // Update the current animation time

    if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
        MD5Model.animations[animation].currAnimTime = 0.0f;

    // Which frame are we on
    float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;    
    int frame0 = floorf( currentFrame );
    int frame1 = frame0 + 1;

    // Make sure we don't go over the number of frames    
    if(frame0 == MD5Model.animations[animation].numFrames-1)
        frame1 = 0;

    float interpolation = currentFrame - frame0;    // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor

    std::vector<Joint> interpolatedSkeleton;        // Create a frame skeleton to store the interpolated skeletons in

Computing the Interpolated Skeleton

이제 보간된 뼈대를 구할 수 있습니다. 구하는건 정말 상당히 쉽습니다. 여러분은 코드에서 우리가 관절의 보간된 위치를 구하는데 사용하는 방정식과 밑에 구면 선형 보간(Slerp)이라는 테크닉을 관절의 방향을 표현하는 두 사원수를 보간하기 위해 사용하는 것을 보실 수 있습니다. XMQuaternionSlerp()라는 함수로 보간 요소에 따른 두 사원수를 보간하도록 사용할 수 있습니다.

    // Compute the interpolated skeleton
    for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
    {
        Joint tempJoint;
        Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i];        // Get the i'th joint of frame0's skeleton
        Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i];        // Get the i'th joint of frame1's skeleton

        tempJoint.parentID = joint0.parentID;                                            // Set the tempJoints parent id

        // Turn the two quaternions into XMVECTORs for easy computations
        XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
        XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);

        // Interpolate positions
        tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
        tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
        tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));

        // Interpolate orientations using spherical interpolation (Slerp)
        XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));

        interpolatedSkeleton.push_back(tempJoint);        // Push the joint back into our interpolated skeleton
    }

Calculate the Vertex Position and Normal

이제 모델의 각 subset을 돌며 또 각 정점에 대해 루프를 돌고 마지막으로 각 정점의 weight들에 대해 돕니다. 정점 위치를 계산하기 위해 저번 강좌에서 우리의 bind-pose 모델을 만들때 했던 것을 똑같이 합니다. 정점 위치를 계산한 다음 법선을 계산합니다. 먼저 weight의 법선을 구하는 것으로 법선을 계산할 수 있습니다. weight의 법선을 구하기 위해 관절 방향을 축으로 법선을 회전시킵니다. weight 법선을 구한 뒤 가중치와 곱하고 정점 법선에 더합니다.

    for ( int k = 0; k < MD5Model.numSubsets; k++)
    {
        for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
        {
            Vertex tempVert = MD5Model.subsets[k].vertices[i];
            tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first
            tempVert.normal = XMFLOAT3(0,0,0);    // Clear vertices normal

            // Sum up the joints and weights information to get vertex's position and normal
            for ( int j = 0; j < tempVert.WeightCount; ++j )
            {
                Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
                Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];

                // Convert joint orientation and weight pos to vectors for easier computation
                XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                // We will need to use the conjugate of the joint orientation quaternion
                XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);

                // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                XMFLOAT3 rotatedPoint;
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                // Compute the normals for this frames skeleton using the weight normals from before
                // We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
                XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);

                // Rotate the normal
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));

                // Add to vertices normal and ake weight bias into account
                tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
                tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
                tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
            }

            MD5Model.subsets[k].positions[i] = tempVert.pos;                // Store the vertices position in the position vector instead of straight into the vertex vector
            MD5Model.subsets[k].vertices[i].normal = tempVert.normal;        // Store the vertices normal
            XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
        }

Updating the Buffer

이 함수에서 마지막으로 할 것은 정점 버퍼를 갱신하는 것입니다. 저번 강좌에서 여러분이 기억하실지 모르겠지만 동적 정점 버퍼를 만들었습니다. directx 에서 동적 버퍼를 갱신하기 위해 먼저 버퍼를 잠그고 원하는 내용을 버퍼에 복사합니다. map 함수는 버퍼에서 데이터의 시작을 가리키는 포인터로 D3D11_MAPPED_SUBRESOURCE의 멤버인 pData가 있는 채워진 D3D11_MAPPED_SUBRESOURCE 객체를 반환합니다. 포인터를 우리의 정점 배열을 버퍼에 카피하도록 사용할 수 있으며 그리고는 마지막으로 버퍼를 언매핑합니다. 주의할 점은 벡터(stl)를 버퍼에 카피할때 반드시 이 포인터를 사용해야 하는데 이 포인터가 여러분이 카피를 시작하기 원하는 곳을 첫번째 요소([0])로 가리키고 있습니다. 그 이유는 벡터의 시작을 직접적으로 가리키는 포인터가 벡터나 어떤 것을 나타내는 추가적인 것의 묶음을 가르킬 것이기 때문입니다.

코드에 참고로 두었지만 디폴트 버퍼를 사용한다면 UpdateSubresource 함수를 사용하여 버퍼를 갱신할 수 있습니다만 이런 류의 갱신은 정적 버퍼를 사용하는 것보다 훨씬 느려서 자주 사용되진 않습니다.

        // Put the positions into the vertices for this subset
        for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
        {
            MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
        }

        // Update the subsets vertex buffer
        // First lock the buffer
        D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
        hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);

        // Copy the data into the vertex buffer.
        memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));

        d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);

        // The line below is another way to update a buffer. You will use this when you want to update a buffer less
        // than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
        // of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
        // for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
        // of D3D11_USAGE_DYNAMIC
        //d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
    }
}



Call the LoadMD5Model() Function

이제 남은 것은 우리의 모델 애니메이션을 로드하는 함수를 호출하는 것입니다!

    if(!LoadMD5Anim(L"boy.md5anim", NewMD5Model))
        return false;

최소한 여러분에게 유용한 정보였기를 바랍니다! 즐기세요!


Exercise:

1. 늘어난 모델들에 대해 로드된 애니메이션을 사용할 수 있도록 (같은 관절 hierarchy를 가지기 때문에) 구조체들을 갱신해보세요. 2. 모델 녀석이 한 자리에서만 뛰게하지말고 원을 그리거나 여러분의 패턴대로 달리게 해보세요! 3. 생각하는 것을 알려주세요! (질문?) 4. 짱 좋은 하루 보내세요! 최종 코드입니다. main.cpp

//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
#pragma comment (lib, "D3D10_1.lib")
#pragma comment (lib, "DXGI.lib")
#pragma comment (lib, "D2D1.lib")
#pragma comment (lib, "dwrite.lib")
#pragma comment (lib, "dinput8.lib")
#pragma comment (lib, "dxguid.lib")

#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <xnamath.h>
#include <D3D10_1.h>
#include <DXGI.h>
#include <D2D1.h>
#include <sstream>
#include <dwrite.h>
#include <dinput.h>
#include <vector>
#include <fstream>
#include <istream>

//Global Declarations - Interfaces//
IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;
ID3D11DepthStencilView* depthStencilView;
ID3D11Texture2D* depthStencilBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D11PixelShader* D2D_PS;
ID3D10Blob* D2D_PS_Buffer;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;
ID3D11Buffer* cbPerObjectBuffer;
ID3D11BlendState* d2dTransparency;
ID3D11RasterizerState* CCWcullMode;
ID3D11RasterizerState* CWcullMode;
ID3D11SamplerState* CubesTexSamplerState;
ID3D11Buffer* cbPerFrameBuffer;

ID3D10Device1 *d3d101Device;    
IDXGIKeyedMutex *keyedMutex11;
IDXGIKeyedMutex *keyedMutex10;    
ID2D1RenderTarget *D2DRenderTarget;    
ID2D1SolidColorBrush *Brush;
ID3D11Texture2D *BackBuffer11;
ID3D11Texture2D *sharedTex11;    
ID3D11Buffer *d2dVertBuffer;
ID3D11Buffer *d2dIndexBuffer;
ID3D11ShaderResourceView *d2dTexture;
IDWriteFactory *DWriteFactory;
IDWriteTextFormat *TextFormat;

IDirectInputDevice8* DIKeyboard;
IDirectInputDevice8* DIMouse;

ID3D11Buffer* sphereIndexBuffer;
ID3D11Buffer* sphereVertBuffer;

ID3D11VertexShader* SKYMAP_VS;
ID3D11PixelShader* SKYMAP_PS;
ID3D10Blob* SKYMAP_VS_Buffer;
ID3D10Blob* SKYMAP_PS_Buffer;

ID3D11ShaderResourceView* smrv;

ID3D11DepthStencilState* DSLessEqual;
ID3D11RasterizerState* RSCullNone;

ID3D11BlendState* Transparency;
//Mesh variables. Each loaded mesh will need its own set of these
ID3D11Buffer* meshVertBuff;
ID3D11Buffer* meshIndexBuff;
XMMATRIX meshWorld;
int meshSubsets = 0;
std::vector<int> meshSubsetIndexStart;
std::vector<int> meshSubsetTexture;

//Textures and material variables, used for all mesh's loaded
std::vector<ID3D11ShaderResourceView*> meshSRV;
std::vector<std::wstring> textureNameArray;

std::wstring printText;

//Global Declarations - Others//
LPCTSTR WndClassName = L"firstwindow";
HWND hwnd = NULL;
HRESULT hr;

int Width  = 300;
int Height = 300;

DIMOUSESTATE mouseLastState;
LPDIRECTINPUT8 DirectInput;

float rotx = 0;
float rotz = 0;
float scaleX = 1.0f;
float scaleY = 1.0f;

XMMATRIX Rotationx;
XMMATRIX Rotationz;
XMMATRIX Rotationy;

XMMATRIX WVP;
XMMATRIX camView;
XMMATRIX camProjection;

XMMATRIX d2dWorld;

XMVECTOR camPosition;
XMVECTOR camTarget;
XMVECTOR camUp;
XMVECTOR DefaultForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR DefaultRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);
XMVECTOR camForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR camRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);

XMMATRIX camRotationMatrix;

float moveLeftRight = 0.0f;
float moveBackForward = 0.0f;

float camYaw = 0.0f;
float camPitch = 0.0f;

int NumSphereVertices;
int NumSphereFaces;

XMMATRIX sphereWorld;

XMMATRIX Rotation;
XMMATRIX Scale;
XMMATRIX Translation;

float rot = 0.01f;

double countsPerSecond = 0.0;
__int64 CounterStart = 0;

int frameCount = 0;
int fps = 0;

__int64 frameTimeOld = 0;
double frameTime;

//Function Prototypes//
bool InitializeDirect3d11App(HINSTANCE hInstance);
void CleanUp();
bool InitScene();
void DrawScene();
bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter);
void InitD2DScreenTexture();
void UpdateScene(double time);

void UpdateCamera();

void RenderText(std::wstring text, int inInt);

void StartTimer();
double GetTime();
double GetFrameTime();

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed);
int messageloop();

bool InitDirectInput(HINSTANCE hInstance);
void DetectInput(double time);

void CreateSphere(int LatLines, int LongLines);

LRESULT CALLBACK WndProc(HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam);

//Create effects constant buffer's structure//
struct cbPerObject
{
    XMMATRIX  WVP;
    XMMATRIX World;

    //These will be used for the pixel shader
    XMFLOAT4 difColor;
    BOOL hasTexture;
    //Because of HLSL structure packing, we will use windows BOOL
    //instead of bool because HLSL packs things into 4 bytes, and
    //bool is only one byte, where BOOL is 4 bytes
    BOOL hasNormMap;
};

cbPerObject cbPerObj;

//Create material structure
struct SurfaceMaterial
{
    std::wstring matName;
    XMFLOAT4 difColor;
    int texArrayIndex;
    int normMapTexArrayIndex;
    bool hasNormMap;
    bool hasTexture;
    bool transparent;
};

std::vector<SurfaceMaterial> material;

//Define LoadObjModel function after we create surfaceMaterial structure
bool LoadObjModel(std::wstring filename,            //.obj filename
    ID3D11Buffer** vertBuff,                    //mesh vertex buffer
    ID3D11Buffer** indexBuff,                    //mesh index buffer
    std::vector<int>& subsetIndexStart,            //start index of each subset
    std::vector<int>& subsetMaterialArray,        //index value of material for each subset
    std::vector<SurfaceMaterial>& material,        //vector of material structures
    int& subsetCount,                            //Number of subsets in mesh
    bool isRHCoordSys,                            //true if model was created in right hand coord system
    bool computeNormals);                        //true to compute the normals, false to use the files normals

struct Light
{
    Light()
    {
        ZeroMemory(this, sizeof(Light));
    }
    XMFLOAT3 pos;
    float range;
    XMFLOAT3 dir;
    float cone;
    XMFLOAT3 att;
    float pad2;
    XMFLOAT4 ambient;
    XMFLOAT4 diffuse;
};

Light light;

struct cbPerFrame
{
    Light  light;
};

cbPerFrame constbuffPerFrame;

struct Vertex    //Overloaded Vertex Structure
{
    Vertex(){}
    Vertex(float x, float y, float z,
        float u, float v,
        float nx, float ny, float nz,
        float tx, float ty, float tz)
        : pos(x,y,z), texCoord(u, v), normal(nx, ny, nz),
        tangent(tx, ty, tz){}

    XMFLOAT3 pos;
    XMFLOAT2 texCoord;
    XMFLOAT3 normal;
    XMFLOAT3 tangent;
    XMFLOAT3 biTangent;

    // Will not be sent to shader
    int StartWeight;
    int WeightCount;
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "NORMAL",     0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
UINT numElements = ARRAYSIZE(layout);

struct Joint
{
    std::wstring name;
    int parentID;

    XMFLOAT3 pos;
    XMFLOAT4 orientation;
};

///////////////**************new**************////////////////////
struct BoundingBox
{
    XMFLOAT3 min;
    XMFLOAT3 max;
};

struct FrameData
{
    int frameID;
    std::vector<float> frameData;
};
struct AnimJointInfo
{
    std::wstring name;
    int parentID;

    int flags;
    int startIndex;
};

struct ModelAnimation
{
    int numFrames;
    int numJoints;
    int frameRate;
    int numAnimatedComponents;

    float frameTime;
    float totalAnimTime;
    float currAnimTime;

    std::vector<AnimJointInfo> jointInfo;
    std::vector<BoundingBox> frameBounds;
    std::vector<Joint>    baseFrameJoints;
    std::vector<FrameData>    frameData;
    std::vector<std::vector<Joint>> frameSkeleton;
};
///////////////**************new**************////////////////////

struct Weight
{
    int jointID;
    float bias;
    XMFLOAT3 pos;
    ///////////////**************new**************////////////////////
    XMFLOAT3 normal;
    ///////////////**************new**************////////////////////
};

struct ModelSubset
{
    int texArrayIndex;
    int numTriangles;

    std::vector<Vertex> vertices;
    std::vector<XMFLOAT3> jointSpaceNormals;
    std::vector<DWORD> indices;
    std::vector<Weight> weights;

    std::vector<XMFLOAT3> positions;

    ID3D11Buffer* vertBuff; 
    ID3D11Buffer* indexBuff;
};

struct Model3D
{
    int numSubsets;
    int numJoints;

    std::vector<Joint> joints;
    std::vector<ModelSubset> subsets;

    ///////////////**************new**************////////////////////
    std::vector<ModelAnimation> animations;
    ///////////////**************new**************////////////////////
};

XMMATRIX smilesWorld;
Model3D NewMD5Model;

//LoadMD5Model() function prototype
bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray);

///////////////**************new**************////////////////////
bool LoadMD5Anim(std::wstring filename,    Model3D& MD5Model);

void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation);
///////////////**************new**************////////////////////

int WINAPI WinMain(HINSTANCE hInstance,    //Main windows function
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine,
    int nShowCmd)
{

    if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true))
    {
        MessageBox(0, L"Window Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitializeDirect3d11App(hInstance))    //Initialize Direct3D
    {
        MessageBox(0, L"Direct3D Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }


    if(!InitScene())    //Initialize our scene
    {
        MessageBox(0, L"Scene Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitDirectInput(hInstance))
    {
        MessageBox(0, L"Direct Input Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    messageloop();

    CleanUp();    

    return 0;
}

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed)
{
    typedef struct _WNDCLASS {
        UINT cbSize;
        UINT style;
        WNDPROC lpfnWndProc;
        int cbClsExtra;
        int cbWndExtra;
        HANDLE hInstance;
        HICON hIcon;
        HCURSOR hCursor;
        HBRUSH hbrBackground;
        LPCTSTR lpszMenuName;
        LPCTSTR lpszClassName;
    } WNDCLASS;

    WNDCLASSEX wc;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = WndClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Error registering class",    
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    hwnd = CreateWindowEx(
        NULL,
        WndClassName,
        L"Lesson 4 - Begin Drawing",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        width, height,
        NULL,
        NULL,
        hInstance,
        NULL
        );

    if (!hwnd)
    {
        MessageBox(NULL, L"Error creating window",
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    ShowWindow(hwnd, ShowWnd);
    UpdateWindow(hwnd);

    return true;
}

bool InitializeDirect3d11App(HINSTANCE hInstance)
{    
    //Describe our SwapChain Buffer
    DXGI_MODE_DESC bufferDesc;

    ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));

    bufferDesc.Width = Width;
    bufferDesc.Height = Height;
    bufferDesc.RefreshRate.Numerator = 60;
    bufferDesc.RefreshRate.Denominator = 1;
    bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    //Describe our SwapChain
    DXGI_SWAP_CHAIN_DESC swapChainDesc; 

    ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

    swapChainDesc.BufferDesc = bufferDesc;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 1;
    swapChainDesc.OutputWindow = hwnd; 
    ///////////////**************new**************////////////////////
    swapChainDesc.Windowed = true; 
    ///////////////**************new**************////////////////////
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // Create DXGI factory to enumerate adapters///////////////////////////////////////////////////////////////////////////
    IDXGIFactory1 *DXGIFactory;

    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory);    

    // Use the first adapter    
    IDXGIAdapter1 *Adapter;

    hr = DXGIFactory->EnumAdapters1(0, &Adapter);

    DXGIFactory->Release();    

    //Create our Direct3D 11 Device and SwapChain//////////////////////////////////////////////////////////////////////////
    hr = D3D11CreateDeviceAndSwapChain(Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        NULL, NULL,    D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);

    //Initialize Direct2D, Direct3D 10.1, DirectWrite
    InitD2D_D3D101_DWrite(Adapter);

    //Release the Adapter interface
    Adapter->Release();

    //Create our BackBuffer and Render Target
    hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer11 );
    hr = d3d11Device->CreateRenderTargetView( BackBuffer11, NULL, &renderTargetView );

    //Describe our Depth/Stencil Buffer
    D3D11_TEXTURE2D_DESC depthStencilDesc;

    depthStencilDesc.Width     = Width;
    depthStencilDesc.Height    = Height;
    depthStencilDesc.MipLevels = 1;
    depthStencilDesc.ArraySize = 1;
    depthStencilDesc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilDesc.SampleDesc.Count   = 1;
    depthStencilDesc.SampleDesc.Quality = 0;
    depthStencilDesc.Usage          = D3D11_USAGE_DEFAULT;
    depthStencilDesc.BindFlags      = D3D11_BIND_DEPTH_STENCIL;
    depthStencilDesc.CPUAccessFlags = 0; 
    depthStencilDesc.MiscFlags      = 0;

    //Create the Depth/Stencil View
    d3d11Device->CreateTexture2D(&depthStencilDesc, NULL, &depthStencilBuffer);
    d3d11Device->CreateDepthStencilView(depthStencilBuffer, NULL, &depthStencilView);

    return true;
}

bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter)
{
    //Create our Direc3D 10.1 Device///////////////////////////////////////////////////////////////////////////////////////
    hr = D3D10CreateDevice1(Adapter, D3D10_DRIVER_TYPE_HARDWARE, NULL,D3D10_CREATE_DEVICE_BGRA_SUPPORT,
        D3D10_FEATURE_LEVEL_9_3, D3D10_1_SDK_VERSION, &d3d101Device    );    

    //Create Shared Texture that Direct3D 10.1 will render on//////////////////////////////////////////////////////////////
    D3D11_TEXTURE2D_DESC sharedTexDesc;    

    ZeroMemory(&sharedTexDesc, sizeof(sharedTexDesc));

    sharedTexDesc.Width = Width;
    sharedTexDesc.Height = Height;    
    sharedTexDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    sharedTexDesc.MipLevels = 1;    
    sharedTexDesc.ArraySize = 1;
    sharedTexDesc.SampleDesc.Count = 1;
    sharedTexDesc.Usage = D3D11_USAGE_DEFAULT;
    sharedTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;    
    sharedTexDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;    

    hr = d3d11Device->CreateTexture2D(&sharedTexDesc, NULL, &sharedTex11);    

    // Get the keyed mutex for the shared texture (for D3D11)///////////////////////////////////////////////////////////////
    hr = sharedTex11->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex11);    

    // Get the shared handle needed to open the shared texture in D3D10.1///////////////////////////////////////////////////
    IDXGIResource *sharedResource10;
    HANDLE sharedHandle10;    

    hr = sharedTex11->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedResource10);

    hr = sharedResource10->GetSharedHandle(&sharedHandle10);    

    sharedResource10->Release();

    // Open the surface for the shared texture in D3D10.1///////////////////////////////////////////////////////////////////
    IDXGISurface1 *sharedSurface10;    

    hr = d3d101Device->OpenSharedResource(sharedHandle10, __uuidof(IDXGISurface1), (void**)(&sharedSurface10));

    hr = sharedSurface10->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex10);    

    // Create D2D factory///////////////////////////////////////////////////////////////////////////////////////////////////
    ID2D1Factory *D2DFactory;    
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), (void**)&D2DFactory);    

    D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties;

    ZeroMemory(&renderTargetProperties, sizeof(renderTargetProperties));

    renderTargetProperties.type = D2D1_RENDER_TARGET_TYPE_HARDWARE;
    renderTargetProperties.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED);    

    hr = D2DFactory->CreateDxgiSurfaceRenderTarget(sharedSurface10, &renderTargetProperties, &D2DRenderTarget);

    sharedSurface10->Release();
    D2DFactory->Release();    

    // Create a solid color brush to draw something with        
    hr = D2DRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &Brush);

    //DirectWrite///////////////////////////////////////////////////////////////////////////////////////////////////////////
    hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&DWriteFactory));

    hr = DWriteFactory->CreateTextFormat(
        L"Script",
        NULL,
        DWRITE_FONT_WEIGHT_REGULAR,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        24.0f,
        L"en-us",
        &TextFormat
        );

    hr = TextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
    hr = TextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);

    d3d101Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);    
    return true;
}

bool InitDirectInput(HINSTANCE hInstance)
{
    hr = DirectInput8Create(hInstance,
        DIRECTINPUT_VERSION,
        IID_IDirectInput8,
        (void**)&DirectInput,
        NULL); 

    hr = DirectInput->CreateDevice(GUID_SysKeyboard,
        &DIKeyboard,
        NULL);

    hr = DirectInput->CreateDevice(GUID_SysMouse,
        &DIMouse,
        NULL);

    hr = DIKeyboard->SetDataFormat(&c_dfDIKeyboard);
    hr = DIKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

    hr = DIMouse->SetDataFormat(&c_dfDIMouse);
    hr = DIMouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_NOWINKEY | DISCL_FOREGROUND);

    return true;
}

void UpdateCamera()
{
    camRotationMatrix = XMMatrixRotationRollPitchYaw(camPitch, camYaw, 0);
    camTarget = XMVector3TransformCoord(DefaultForward, camRotationMatrix );
    camTarget = XMVector3Normalize(camTarget);

    XMMATRIX RotateYTempMatrix;
    RotateYTempMatrix = XMMatrixRotationY(camYaw);

    // Walk
    //camRight = XMVector3TransformCoord(DefaultRight, RotateYTempMatrix);
    //camUp = XMVector3TransformCoord(camUp, RotateYTempMatrix);
    //camForward = XMVector3TransformCoord(DefaultForward, RotateYTempMatrix);

    // Free Cam
    camRight = XMVector3TransformCoord(DefaultRight, camRotationMatrix);
    camForward = XMVector3TransformCoord(DefaultForward, camRotationMatrix);
    camUp = XMVector3Cross(camForward, camRight);

    camPosition += moveLeftRight*camRight;
    camPosition += moveBackForward*camForward;

    moveLeftRight = 0.0f;
    moveBackForward = 0.0f;

    camTarget = camPosition + camTarget;    

    camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );
}

void DetectInput(double time)
{
    DIMOUSESTATE mouseCurrState;

    BYTE keyboardState[256];

    DIKeyboard->Acquire();
    DIMouse->Acquire();

    DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState);

    DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState);

    if(keyboardState[DIK_ESCAPE] & 0x80)
        PostMessage(hwnd, WM_DESTROY, 0, 0);

    float speed = 10.0f * time;

    if(keyboardState[DIK_A] & 0x80)
    {
        moveLeftRight -= speed;
    }
    if(keyboardState[DIK_D] & 0x80)
    {
        moveLeftRight += speed;
    }
    if(keyboardState[DIK_W] & 0x80)
    {
        moveBackForward += speed;
    }
    if(keyboardState[DIK_S] & 0x80)
    {
        moveBackForward -= speed;
    }
    ///////////////**************new**************////////////////////
    if(keyboardState[DIK_R] & 0X80)
    {
        float timeFactor = 1.0f;    // You can speed up or slow down time by changing this
        UpdateMD5Model(NewMD5Model, time*timeFactor, 0);
    }
    ///////////////**************new**************////////////////////
    if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY))
    {
        camYaw += mouseLastState.lX * 0.001f;

        camPitch += mouseCurrState.lY * 0.001f;

        mouseLastState = mouseCurrState;
    }

    UpdateCamera();

    return;
}


void CleanUp()
{
    SwapChain->SetFullscreenState(false, NULL);
    PostMessage(hwnd, WM_DESTROY, 0, 0);

    //Release the COM Objects we created
    SwapChain->Release();
    d3d11Device->Release();
    d3d11DevCon->Release();
    renderTargetView->Release();
    VS->Release();
    PS->Release();
    VS_Buffer->Release();
    PS_Buffer->Release();
    vertLayout->Release();
    depthStencilView->Release();
    depthStencilBuffer->Release();
    cbPerObjectBuffer->Release();
    Transparency->Release();
    CCWcullMode->Release();
    CWcullMode->Release();

    d3d101Device->Release();
    keyedMutex11->Release();
    keyedMutex10->Release();
    D2DRenderTarget->Release();    
    Brush->Release();
    BackBuffer11->Release();
    sharedTex11->Release();
    DWriteFactory->Release();
    TextFormat->Release();
    d2dTexture->Release();

    cbPerFrameBuffer->Release();

    DIKeyboard->Unacquire();
    DIMouse->Unacquire();
    DirectInput->Release();

    sphereIndexBuffer->Release();
    sphereVertBuffer->Release();

    SKYMAP_VS->Release();
    SKYMAP_PS->Release();
    SKYMAP_VS_Buffer->Release();
    SKYMAP_PS_Buffer->Release();

    smrv->Release();

    DSLessEqual->Release();
    RSCullNone->Release();

    meshVertBuff->Release();
    meshIndexBuff->Release();

    for(int i = 0; i < NewMD5Model.numSubsets; i++)
    {
        NewMD5Model.subsets[i].indexBuff->Release();
        NewMD5Model.subsets[i].vertBuff->Release();
    }
}

///////////////**************new**************////////////////////
bool LoadMD5Anim(std::wstring filename,    Model3D& MD5Model)
{    
    ModelAnimation tempAnim;                        // Temp animation to later store in our model's animation array

    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if ( checkString == L"MD5Version" )        // Get MD5 version (this function supports version 10)
            {
                fileIn >> checkString;
                /*MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numFrames" )
            {
                fileIn >> tempAnim.numFrames;                // Store number of frames in this animation
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> tempAnim.numJoints;                // Store number of joints (must match .md5mesh)
            }
            else if ( checkString == L"frameRate" )
            {
                fileIn >> tempAnim.frameRate;                // Store animation's frame rate (frames per second)
            }
            else if ( checkString == L"numAnimatedComponents" )
            {
                fileIn >> tempAnim.numAnimatedComponents;    // Number of components in each frame section
            }
            else if ( checkString == L"hierarchy" )
            {
                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)    // Load in each joint
                {
                    AnimJointInfo tempJoint;

                    fileIn >> tempJoint.name;        // Get joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    fileIn >> tempJoint.parentID;            // Get joints parent ID
                    fileIn >> tempJoint.flags;                // Get flags
                    fileIn >> tempJoint.startIndex;            // Get joints start index

                    // Make sure the joint exists in the model, and the parent ID's match up
                    // because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
                    // joint hierarchy must match up
                    bool jointMatchFound = false;
                    for(int k = 0; k < MD5Model.numJoints; k++)
                    {
                        if(MD5Model.joints[k].name == tempJoint.name)
                        {
                            if(MD5Model.joints[k].parentID == tempJoint.parentID)
                            {
                                jointMatchFound = true;
                                tempAnim.jointInfo.push_back(tempJoint);
                            }
                        }
                    }
                    if(!jointMatchFound)                    // If the skeleton system does not match up, return false
                        return false;                        // You might want to add an error message here

                    std::getline(fileIn, checkString);        // Skip rest of this line
                }
            }
            else if ( checkString == L"bounds" )            // Load in the AABB for each animation
            {
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numFrames; i++)
                {
                    BoundingBox tempBB;

                    fileIn >> checkString;                    // Skip "("
                    fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
                    fileIn >> checkString >> checkString;    // Skip ") ("
                    fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
                    fileIn >> checkString;                    // Skip ")"

                    tempAnim.frameBounds.push_back(tempBB);
                }
            }            
            else if ( checkString == L"baseframe" )            // This is the default position for the animation
            {                                                // All frames will build their skeletons off this
                fileIn >> checkString;                        // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numJoints; i++)
                {
                    Joint tempBFJ;

                    fileIn >> checkString;                        // Skip "("
                    fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
                    fileIn >> checkString >> checkString;        // Skip ") ("
                    fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
                    fileIn >> checkString;                        // Skip ")"

                    tempAnim.baseFrameJoints.push_back(tempBFJ);
                }
            }
            else if ( checkString == L"frame" )        // Load in each frames skeleton (the parts of each joint that changed from the base frame)
            {
                FrameData tempFrame;

                fileIn >> tempFrame.frameID;        // Get the frame ID

                fileIn >> checkString;                // Skip opening bracket "{"

                for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
                {
                    float tempData;
                    fileIn >> tempData;                // Get the data

                    tempFrame.frameData.push_back(tempData);
                }

                tempAnim.frameData.push_back(tempFrame);

                ///*** build the frame skeleton ***///
                std::vector<Joint> tempSkeleton;

                for(int i = 0; i < tempAnim.jointInfo.size(); i++)
                {
                    int k = 0;                        // Keep track of position in frameData array

                    // Start the frames joint with the base frame's joint
                    Joint tempFrameJoint = tempAnim.baseFrameJoints[i];

                    tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;

                    // Notice how I have been flipping y and z. this is because some modeling programs such as
                    // 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
                    // need to flip the y and z axes. If your having problems loading some models, it's possible
                    // the model was created in a left hand coordinate system. in that case, just reflip all the
                    // y and z axes in our md5 mesh and anim loader.
                    if(tempAnim.jointInfo[i].flags & 1)        // pos.x    ( 000001 )
                        tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 2)        // pos.y    ( 000010 )
                        tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 4)        // pos.z    ( 000100 )
                        tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 8)        // orientation.x    ( 001000 )
                        tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 16)    // orientation.y    ( 010000 )
                        tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];

                    if(tempAnim.jointInfo[i].flags & 32)    // orientation.z    ( 100000 )
                        tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];


                    // Compute the quaternions w
                    float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
                        - ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
                        - ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempFrameJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempFrameJoint.orientation.w = -sqrtf(t);
                    }

                    // Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
                    // This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
                    // and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
                    // the child should never come before the parent in the files we loaded in.
                    if(tempFrameJoint.parentID >= 0)
                    {
                        Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];

                        // Turn the XMFLOAT3 and 4's into vectors for easier computation
                        XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
                        XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
                        XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);

                        // Calculate current joints position relative to its parents position
                        XMFLOAT3 rotatedPos;
                        XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));

                        // Translate the joint to model space by adding the parent joint's pos to it
                        tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
                        tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
                        tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;

                        // Currently the joint is oriented in its parent joints space, we now need to orient it in
                        // model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
                        XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
                        tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);

                        // Normalize the orienation quaternion
                        tempJointOrient = XMQuaternionNormalize(tempJointOrient);

                        XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
                    }

                    // Store the joint into our temporary frame skeleton
                    tempSkeleton.push_back(tempFrameJoint);
                }

                // Push back our newly created frame skeleton into the animation's frameSkeleton array
                tempAnim.frameSkeleton.push_back(tempSkeleton);

                fileIn >> checkString;                // Skip closing bracket "}"
            }
        }

        // Calculate and store some usefull animation data
        tempAnim.frameTime = 1.0f / tempAnim.frameRate;                        // Set the time per frame
        tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime;    // Set the total time the animation takes
        tempAnim.currAnimTime = 0.0f;                                        // Set the current time to zero

        MD5Model.animations.push_back(tempAnim);                            // Push back the animation into our model object
    }
    else    // If the file was not loaded
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),                // display message
            L"Error", MB_OK);

        return false;
    }
    return true;
}

void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
    MD5Model.animations[animation].currAnimTime += deltaTime;            // Update the current animation time

    if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
        MD5Model.animations[animation].currAnimTime = 0.0f;

    // Which frame are we on
    float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;    
    int frame0 = floorf( currentFrame );
    int frame1 = frame0 + 1;

    // Make sure we don't go over the number of frames    
    if(frame0 == MD5Model.animations[animation].numFrames-1)
        frame1 = 0;

    float interpolation = currentFrame - frame0;    // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor

    std::vector<Joint> interpolatedSkeleton;        // Create a frame skeleton to store the interpolated skeletons in

    // Compute the interpolated skeleton
    for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
    {
        Joint tempJoint;
        Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i];        // Get the i'th joint of frame0's skeleton
        Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i];        // Get the i'th joint of frame1's skeleton

        tempJoint.parentID = joint0.parentID;                                            // Set the tempJoints parent id

        // Turn the two quaternions into XMVECTORs for easy computations
        XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
        XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);

        // Interpolate positions
        tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
        tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
        tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));

        // Interpolate orientations using spherical interpolation (Slerp)
        XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));

        interpolatedSkeleton.push_back(tempJoint);        // Push the joint back into our interpolated skeleton
    }

    for ( int k = 0; k < MD5Model.numSubsets; k++)
    {
        for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
        {
            Vertex tempVert = MD5Model.subsets[k].vertices[i];
            tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first
            tempVert.normal = XMFLOAT3(0,0,0);    // Clear vertices normal

            // Sum up the joints and weights information to get vertex's position and normal
            for ( int j = 0; j < tempVert.WeightCount; ++j )
            {
                Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
                Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];

                // Convert joint orientation and weight pos to vectors for easier computation
                XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                // We will need to use the conjugate of the joint orientation quaternion
                XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);

                // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                XMFLOAT3 rotatedPoint;
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                // Compute the normals for this frames skeleton using the weight normals from before
                // We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
                XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);

                // Rotate the normal
                XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));

                // Add to vertices normal and ake weight bias into account
                tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
                tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
                tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
            }

            MD5Model.subsets[k].positions[i] = tempVert.pos;                // Store the vertices position in the position vector instead of straight into the vertex vector
            MD5Model.subsets[k].vertices[i].normal = tempVert.normal;        // Store the vertices normal
            XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
        }

        // Put the positions into the vertices for this subset
        for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
        {
            MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
        }

        // Update the subsets vertex buffer
        // First lock the buffer
        D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
        hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);

        // Copy the data into the vertex buffer.
        memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));

        d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);

        // The line below is another way to update a buffer. You will use this when you want to update a buffer less
        // than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
        // of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
        // for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
        // of D3D11_USAGE_DYNAMIC
        //d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
    }
}
///////////////**************new**************////////////////////

bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray)
{
    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if(checkString == L"MD5Version")        // Get MD5 version (this function supports version 10)
            {
                /*fileIn >> checkString;
                MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> MD5Model.numJoints;        // Store number of joints
            }
            else if ( checkString == L"numMeshes" )
            {
                fileIn >> MD5Model.numSubsets;        // Store number of meshes or subsets which we will call them
            }
            else if ( checkString == L"joints" )
            {
                Joint tempJoint;

                fileIn >> checkString;                // Skip the "{"

                for(int i = 0; i < MD5Model.numJoints; i++)
                {
                    fileIn >> tempJoint.name;        // Store joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    fileIn >> tempJoint.parentID;    // Store Parent joint's ID

                    fileIn >> checkString;            // Skip the "("

                    // Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
                    fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

                    fileIn >> checkString >> checkString;    // Skip the ")" and "("

                    // Store orientation of this joint
                    fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    // Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
                    // direction the bone is facing. However, we need to turn this into a quaternion, and the way
                    // quaternions work, is the xyz values describe the axis of rotation, while the w is a value
                    // between 0 and 1 which describes the angle of rotation)
                    float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
                        - ( tempJoint.orientation.y * tempJoint.orientation.y )
                        - ( tempJoint.orientation.z * tempJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempJoint.orientation.w = -sqrtf(t);
                    }

                    std::getline(fileIn, checkString);        // Skip rest of this line

                    MD5Model.joints.push_back(tempJoint);    // Store the joint into this models joint vector
                }

                fileIn >> checkString;                    // Skip the "}"
            }
            else if ( checkString == L"mesh")
            {
                ModelSubset subset;
                int numVerts, numTris, numWeights;

                fileIn >> checkString;                    // Skip the "{"

                fileIn >> checkString;
                while ( checkString != L"}" )            // Read until '}'
                {
                    // In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
                    // Usually though, the name of a material (stored in a material library. Think back to the lesson on
                    // loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
                    // be an exercise to load the material from a material library such as obj's .mtl file, instead of
                    // just the texture like we will do here.
                    if(checkString == L"shader")        // Load the texture or material
                    {                        
                        std::wstring fileNamePath;
                        fileIn >> fileNamePath;            // Get texture's filename

                        // Take spaces into account if filename or material name has a space in it
                        if(fileNamePath[fileNamePath.size()-1] != '"')
                        {
                            wchar_t checkChar;
                            bool fileNameFound = false;
                            while(!fileNameFound)
                            {
                                checkChar = fileIn.get();

                                if(checkChar == '"')
                                    fileNameFound = true;

                                fileNamePath += checkChar;                                                                    
                            }
                        }

                        // Remove the quotation marks from texture path
                        fileNamePath.erase(0, 1);
                        fileNamePath.erase(fileNamePath.size()-1, 1);

                        //check if this texture has already been loaded
                        bool alreadyLoaded = false;
                        for(int i = 0; i < texFileNameArray.size(); ++i)
                        {
                            if(fileNamePath == texFileNameArray[i])
                            {
                                alreadyLoaded = true;
                                subset.texArrayIndex = i;
                            }
                        }

                        //if the texture is not already loaded, load it now
                        if(!alreadyLoaded)
                        {
                            ID3D11ShaderResourceView* tempMeshSRV;
                            hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                NULL, NULL, &tempMeshSRV, NULL );
                            if(SUCCEEDED(hr))
                            {
                                texFileNameArray.push_back(fileNamePath.c_str());
                                subset.texArrayIndex = shaderResourceViewArray.size();
                                shaderResourceViewArray.push_back(tempMeshSRV);
                            }
                            else
                            {
                                MessageBox(0, fileNamePath.c_str(),        //display message
                                    L"Could Not Open:", MB_OK);
                                return false;
                            }
                        }    

                        std::getline(fileIn, checkString);                // Skip rest of this line
                    }
                    else if ( checkString == L"numverts")
                    {
                        fileIn >> numVerts;                                // Store number of vertices

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numVerts; i++)
                        {
                            Vertex tempVert;

                            fileIn >> checkString                        // Skip "vert # ("
                                >> checkString
                                >> checkString;

                            fileIn >> tempVert.texCoord.x                // Store tex coords
                                >> tempVert.texCoord.y;    

                            fileIn >> checkString;                        // Skip ")"

                            fileIn >> tempVert.StartWeight;                // Index of first weight this vert will be weighted to

                            fileIn >> tempVert.WeightCount;                // Number of weights for this vertex

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.vertices.push_back(tempVert);        // Push back this vertex into subsets vertex vector
                        }
                    }
                    else if ( checkString == L"numtris")
                    {
                        fileIn >> numTris;
                        subset.numTriangles = numTris;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numTris; i++)                // Loop through each triangle
                        {
                            DWORD tempIndex;
                            fileIn >> checkString;                        // Skip "tri"
                            fileIn >> checkString;                        // Skip tri counter

                            for(int k = 0; k < 3; k++)                    // Store the 3 indices
                            {
                                fileIn >> tempIndex;
                                subset.indices.push_back(tempIndex);
                            }

                            std::getline(fileIn, checkString);            // Skip rest of this line
                        }
                    }
                    else if ( checkString == L"numweights")
                    {
                        fileIn >> numWeights;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numWeights; i++)
                        {
                            Weight tempWeight;
                            fileIn >> checkString >> checkString;        // Skip "weight #"

                            fileIn >> tempWeight.jointID;                // Store weight's joint ID

                            fileIn >> tempWeight.bias;                    // Store weight's influence over a vertex

                            fileIn >> checkString;                        // Skip "("

                            fileIn >> tempWeight.pos.x                    // Store weight's pos in joint's local space
                                >> tempWeight.pos.z
                                >> tempWeight.pos.y;

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.weights.push_back(tempWeight);        // Push back tempWeight into subsets Weight array
                        }

                    }
                    else
                        std::getline(fileIn, checkString);                // Skip anything else

                    fileIn >> checkString;                                // Skip "}"
                }

                //*** find each vertex's position using the joints and weights ***//
                for ( int i = 0; i < subset.vertices.size(); ++i )
                {
                    Vertex tempVert = subset.vertices[i];
                    tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first

                    // Sum up the joints and weights information to get vertex's position
                    for ( int j = 0; j < tempVert.WeightCount; ++j )
                    {
                        Weight tempWeight = subset.weights[tempVert.StartWeight + j];
                        Joint tempJoint = MD5Model.joints[tempWeight.jointID];

                        // Convert joint orientation and weight pos to vectors for easier computation
                        // When converting a 3d vector to a quaternion, you should put 0 for "w", and
                        // When converting a quaternion to a 3d vector, you can just ignore the "w"
                        XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                        XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                        // We will need to use the conjugate of the joint orientation quaternion
                        // To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
                        XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

                        // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                        // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                        XMFLOAT3 rotatedPoint;
                        XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                        // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                        // The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
                        tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                        tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                        tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                        // Basically what has happened above, is we have taken the weights position relative to the joints position
                        // we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
                        // the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
                        // the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
                        // so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
                        // or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
                        // must add up to 1.
                    }

                    subset.positions.push_back(tempVert.pos);            // Store the vertices position in the position vector instead of straight into the vertex vector
                    // since we can use the positions vector for certain things like collision detection or picking
                    // without having to work with the entire vertex structure.
                }

                // Put the positions into the vertices for this subset
                for(int i = 0; i < subset.vertices.size(); i++)
                {
                    subset.vertices[i].pos = subset.positions[i];
                }

                //*** Calculate vertex normals using normal averaging ***///
                std::vector<XMFLOAT3> tempNormal;

                //normalized and unnormalized normals
                XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

                //Used to get vectors (sides) from the position of the verts
                float vecX, vecY, vecZ;

                //Two edges of our triangle
                XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

                //Compute face normals
                for(int i = 0; i < subset.numTriangles; ++i)
                {
                    //Get the vector describing one edge of our triangle (edge 0,2)
                    vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;        
                    edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

                    //Get the vector describing another edge of our triangle (edge 2,1)
                    vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;        
                    edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

                    //Cross multiply the two edge vectors to get the un-normalized face normal
                    XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

                    tempNormal.push_back(unnormalized);
                }

                //Compute vertex normals (normal Averaging)
                XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                int facesUsing = 0;
                float tX, tY, tZ;    //temp axis variables

                //Go through each vertex
                for(int i = 0; i < subset.vertices.size(); ++i)
                {
                    //Check which triangles use this vertex
                    for(int j = 0; j < subset.numTriangles; ++j)
                    {
                        if(subset.indices[j*3] == i ||
                            subset.indices[(j*3)+1] == i ||
                            subset.indices[(j*3)+2] == i)
                        {
                            tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                            tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                            tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                            normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                            facesUsing++;
                        }
                    }

                    //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
                    normalSum = normalSum / facesUsing;

                    //Normalize the normalSum vector
                    normalSum = XMVector3Normalize(normalSum);

                    //Store the normal and tangent in our current vertex
                    subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
                    subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
                    subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);                    

                    ///////////////**************new**************////////////////////
                    // Create the joint space normal for easy normal calculations in animation
                    Vertex tempVert = subset.vertices[i];                        // Get the current vertex
                    subset.jointSpaceNormals.push_back(XMFLOAT3(0,0,0));        // Push back a blank normal
                    XMVECTOR normal = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);        // Clear normal

                    for ( int k = 0; k < tempVert.WeightCount; k++)                // Loop through each of the vertices weights
                    {
                        Joint tempJoint = MD5Model.joints[subset.weights[tempVert.StartWeight + k].jointID];    // Get the joints orientation
                        XMVECTOR jointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);

                        // Calculate normal based off joints orientation (turn into joint space)
                        normal = XMQuaternionMultiply(XMQuaternionMultiply(XMQuaternionInverse(jointOrientation), normalSum), jointOrientation);        

                        XMStoreFloat3(&subset.weights[tempVert.StartWeight + k].normal, XMVector3Normalize(normal));            // Store the normalized quaternion into our weights normal
                    }                
                    ///////////////**************new**************////////////////////
                    //Clear normalSum, facesUsing for next vertex
                    normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                    facesUsing = 0;
                }

                // Create index buffer
                D3D11_BUFFER_DESC indexBufferDesc;
                ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

                indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
                indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
                indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
                indexBufferDesc.CPUAccessFlags = 0;
                indexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA iinitData;
                iinitData.pSysMem = &subset.indices[0];
                d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

                //Create Vertex Buffer
                D3D11_BUFFER_DESC vertexBufferDesc;
                ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

                vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;                            // We will be updating this buffer, so we must set as dynamic
                vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
                vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
                vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;                // Give CPU power to write to buffer
                vertexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA vertexBufferData; 
                ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
                vertexBufferData.pSysMem = &subset.vertices[0];
                hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

                // Push back the temp subset into the models subset vector
                MD5Model.subsets.push_back(subset);
            }
        }
    }
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    // display message
            L"Error", MB_OK);

        return false;
    }

    return true;
}

bool LoadObjModel(std::wstring filename, 
    ID3D11Buffer** vertBuff, 
    ID3D11Buffer** indexBuff,
    std::vector<int>& subsetIndexStart,
    std::vector<int>& subsetMaterialArray,
    std::vector<SurfaceMaterial>& material, 
    int& subsetCount,
    bool isRHCoordSys,
    bool computeNormals)
{
    HRESULT hr = 0;

    std::wifstream fileIn (filename.c_str());    //Open file
    std::wstring meshMatLib;                    //String to hold our obj material library filename

    //Arrays to store our model's information
    std::vector<DWORD> indices;
    std::vector<XMFLOAT3> vertPos;
    std::vector<XMFLOAT3> vertNorm;
    std::vector<XMFLOAT2> vertTexCoord;
    std::vector<std::wstring> meshMaterials;

    //Vertex definition indices
    std::vector<int> vertPosIndex;
    std::vector<int> vertNormIndex;
    std::vector<int> vertTCIndex;

    //Make sure we have a default if no tex coords or normals are defined
    bool hasTexCoord = false;
    bool hasNorm = false;

    //Temp variables to store into vectors
    std::wstring meshMaterialsTemp;
    int vertPosIndexTemp;
    int vertNormIndexTemp;
    int vertTCIndexTemp;

    wchar_t checkChar;        //The variable we will use to store one char from file at a time
    std::wstring face;        //Holds the string containing our face vertices
    int vIndex = 0;            //Keep track of our vertex index count
    int triangleCount = 0;    //Total Triangles
    int totalVerts = 0;
    int meshTriangles = 0;

    //Check to see if the file was opened
    if (fileIn)
    {
        while(fileIn)
        {            
            checkChar = fileIn.get();    //Get next char

            switch (checkChar)
            {        
            case '#':
                checkChar = fileIn.get();
                while(checkChar != '\n')
                    checkChar = fileIn.get();
                break;
            case 'v':    //Get Vertex Descriptions
                checkChar = fileIn.get();
                if(checkChar == ' ')    //v - vert position
                {
                    float vz, vy, vx;
                    fileIn >> vx >> vy >> vz;    //Store the next three types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f));    //Invert the Z axis
                    else
                        vertPos.push_back(XMFLOAT3( vx, vy, vz));
                }
                if(checkChar == 't')    //vt - vert tex coords
                {            
                    float vtcu, vtcv;
                    fileIn >> vtcu >> vtcv;        //Store next two types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv));    //Reverse the "v" axis
                    else
                        vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv));    

                    hasTexCoord = true;    //We know the model uses texture coords
                }
                //Since we compute the normals later, we don't need to check for normals
                //In the file, but i'll do it here anyway
                if(checkChar == 'n')    //vn - vert normal
                {
                    float vnx, vny, vnz;
                    fileIn >> vnx >> vny >> vnz;    //Store next three types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f ));    //Invert the Z axis
                    else
                        vertNorm.push_back(XMFLOAT3( vnx, vny, vnz ));    

                    hasNorm = true;    //We know the model defines normals
                }
                break;

                //New group (Subset)
            case 'g':    //g - defines a group
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    subsetIndexStart.push_back(vIndex);        //Start index for this subset
                    subsetCount++;
                }
                break;

                //Get Face Index
            case 'f':    //f - defines the faces
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    face = L"";
                    std::wstring VertDef;    //Holds one vertex definition at a time
                    triangleCount = 0;

                    checkChar = fileIn.get();
                    while(checkChar != '\n')
                    {
                        face += checkChar;            //Add the char to our face string
                        checkChar = fileIn.get();    //Get the next Character
                        if(checkChar == ' ')        //If its a space...
                            triangleCount++;        //Increase our triangle count
                    }

                    //Check for space at the end of our face string
                    if(face[face.length()-1] == ' ')
                        triangleCount--;    //Each space adds to our triangle count

                    triangleCount -= 1;        //Ever vertex in the face AFTER the first two are new faces

                    std::wstringstream ss(face);

                    if(face.length() > 0)
                    {
                        int firstVIndex, lastVIndex;    //Holds the first and last vertice's index

                        for(int i = 0; i < 3; ++i)        //First three vertices (first triangle)
                        {
                            ss >> VertDef;    //Get vertex definition (vPos/vTexCoord/vNorm)

                            std::wstring vertPart;
                            int whichPart = 0;        //(vPos, vTexCoord, or vNorm)

                            //Parse this string
                            for(int j = 0; j < VertDef.length(); ++j)
                            {
                                if(VertDef[j] != '/')    //If there is no divider "/", add a char to our vertPart
                                    vertPart += VertDef[j];

                                //If the current char is a divider "/", or its the last character in the string
                                if(VertDef[j] == '/' || j ==  VertDef.length()-1)
                                {
                                    std::wistringstream wstringToInt(vertPart);    //Used to convert wstring to int

                                    if(whichPart == 0)    //If vPos
                                    {
                                        wstringToInt >> vertPosIndexTemp;
                                        vertPosIndexTemp -= 1;        //subtract one since c++ arrays start with 0, and obj start with 1

                                        //Check to see if the vert pos was the only thing specified
                                        if(j == VertDef.length()-1)
                                        {
                                            vertNormIndexTemp = 0;
                                            vertTCIndexTemp = 0;
                                        }
                                    }

                                    else if(whichPart == 1)    //If vTexCoord
                                    {
                                        if(vertPart != L"")    //Check to see if there even is a tex coord
                                        {
                                            wstringToInt >> vertTCIndexTemp;
                                            vertTCIndexTemp -= 1;    //subtract one since c++ arrays start with 0, and obj start with 1
                                        }
                                        else    //If there is no tex coord, make a default
                                            vertTCIndexTemp = 0;

                                        //If the cur. char is the second to last in the string, then
                                        //there must be no normal, so set a default normal
                                        if(j == VertDef.length()-1)
                                            vertNormIndexTemp = 0;

                                    }                                
                                    else if(whichPart == 2)    //If vNorm
                                    {
                                        std::wistringstream wstringToInt(vertPart);

                                        wstringToInt >> vertNormIndexTemp;
                                        vertNormIndexTemp -= 1;        //subtract one since c++ arrays start with 0, and obj start with 1
                                    }

                                    vertPart = L"";    //Get ready for next vertex part
                                    whichPart++;    //Move on to next vertex part                    
                                }
                            }

                            //Check to make sure there is at least one subset
                            if(subsetCount == 0)
                            {
                                subsetIndexStart.push_back(vIndex);        //Start index for this subset
                                subsetCount++;
                            }

                            //Avoid duplicate vertices
                            bool vertAlreadyExists = false;
                            if(totalVerts >= 3)    //Make sure we at least have one triangle to check
                            {
                                //Loop through all the vertices
                                for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
                                {
                                    //If the vertex position and texture coordinate in memory are the same
                                    //As the vertex position and texture coordinate we just now got out
                                    //of the obj file, we will set this faces vertex index to the vertex's
                                    //index value in memory. This makes sure we don't create duplicate vertices
                                    if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
                                    {
                                        if(vertTCIndexTemp == vertTCIndex[iCheck])
                                        {
                                            indices.push_back(iCheck);        //Set index for this vertex
                                            vertAlreadyExists = true;        //If we've made it here, the vertex already exists
                                        }
                                    }
                                }
                            }

                            //If this vertex is not already in our vertex arrays, put it there
                            if(!vertAlreadyExists)
                            {
                                vertPosIndex.push_back(vertPosIndexTemp);
                                vertTCIndex.push_back(vertTCIndexTemp);
                                vertNormIndex.push_back(vertNormIndexTemp);
                                totalVerts++;    //We created a new vertex
                                indices.push_back(totalVerts-1);    //Set index for this vertex
                            }                            

                            //If this is the very first vertex in the face, we need to
                            //make sure the rest of the triangles use this vertex
                            if(i == 0)
                            {
                                firstVIndex = indices[vIndex];    //The first vertex index of this FACE

                            }

                            //If this was the last vertex in the first triangle, we will make sure
                            //the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5))
                            if(i == 2)
                            {                                
                                lastVIndex = indices[vIndex];    //The last vertex index of this TRIANGLE
                            }
                            vIndex++;    //Increment index count
                        }

                        meshTriangles++;    //One triangle down

                        //If there are more than three vertices in the face definition, we need to make sure
                        //we convert the face to triangles. We created our first triangle above, now we will
                        //create a new triangle for every new vertex in the face, using the very first vertex
                        //of the face, and the last vertex from the triangle before the current triangle
                        for(int l = 0; l < triangleCount-1; ++l)    //Loop through the next vertices to create new triangles
                        {
                            //First vertex of this triangle (the very first vertex of the face too)
                            indices.push_back(firstVIndex);            //Set index for this vertex
                            vIndex++;

                            //Second Vertex of this triangle (the last vertex used in the tri before this one)
                            indices.push_back(lastVIndex);            //Set index for this vertex
                            vIndex++;

                            //Get the third vertex for this triangle
                            ss >> VertDef;

                            std::wstring vertPart;
                            int whichPart = 0;

                            //Parse this string (same as above)
                            for(int j = 0; j < VertDef.length(); ++j)
                            {
                                if(VertDef[j] != '/')
                                    vertPart += VertDef[j];
                                if(VertDef[j] == '/' || j ==  VertDef.length()-1)
                                {
                                    std::wistringstream wstringToInt(vertPart);

                                    if(whichPart == 0)
                                    {
                                        wstringToInt >> vertPosIndexTemp;
                                        vertPosIndexTemp -= 1;

                                        //Check to see if the vert pos was the only thing specified
                                        if(j == VertDef.length()-1)
                                        {
                                            vertTCIndexTemp = 0;
                                            vertNormIndexTemp = 0;
                                        }
                                    }
                                    else if(whichPart == 1)
                                    {
                                        if(vertPart != L"")
                                        {
                                            wstringToInt >> vertTCIndexTemp;
                                            vertTCIndexTemp -= 1;
                                        }
                                        else
                                            vertTCIndexTemp = 0;
                                        if(j == VertDef.length()-1)
                                            vertNormIndexTemp = 0;

                                    }                                
                                    else if(whichPart == 2)
                                    {
                                        std::wistringstream wstringToInt(vertPart);

                                        wstringToInt >> vertNormIndexTemp;
                                        vertNormIndexTemp -= 1;
                                    }

                                    vertPart = L"";
                                    whichPart++;                            
                                }
                            }                    

                            //Check for duplicate vertices
                            bool vertAlreadyExists = false;
                            if(totalVerts >= 3)    //Make sure we at least have one triangle to check
                            {
                                for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
                                {
                                    if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
                                    {
                                        if(vertTCIndexTemp == vertTCIndex[iCheck])
                                        {
                                            indices.push_back(iCheck);            //Set index for this vertex
                                            vertAlreadyExists = true;        //If we've made it here, the vertex already exists
                                        }
                                    }
                                }
                            }

                            if(!vertAlreadyExists)
                            {
                                vertPosIndex.push_back(vertPosIndexTemp);
                                vertTCIndex.push_back(vertTCIndexTemp);
                                vertNormIndex.push_back(vertNormIndexTemp);
                                totalVerts++;                    //New vertex created, add to total verts
                                indices.push_back(totalVerts-1);        //Set index for this vertex
                            }

                            //Set the second vertex for the next triangle to the last vertex we got        
                            lastVIndex = indices[vIndex];    //The last vertex index of this TRIANGLE

                            meshTriangles++;    //New triangle defined
                            vIndex++;        
                        }
                    }
                }
                break;

            case 'm':    //mtllib - material library filename
                checkChar = fileIn.get();
                if(checkChar == 't')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'l')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'l')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 'i')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'b')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        //Store the material libraries file name
                                        fileIn >> meshMatLib;
                                    }
                                }
                            }
                        }
                    }
                }

                break;

            case 'u':    //usemtl - which material to use
                checkChar = fileIn.get();
                if(checkChar == 's')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'e')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'm')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 't')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'l')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        meshMaterialsTemp = L"";    //Make sure this is cleared

                                        fileIn >> meshMaterialsTemp; //Get next type (string)

                                        meshMaterials.push_back(meshMaterialsTemp);
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            default:                
                break;
            }
        }
    }
    else    //If we could not open the file
    {
        SwapChain->SetFullscreenState(false, NULL);    //Make sure we are out of fullscreen

        //create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    //display message
            L"Error", MB_OK);

        return false;
    }

    subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here

    //sometimes "g" is defined at the very top of the file, then again before the first group of faces.
    //This makes sure the first subset does not conatain "0" indices.
    if(subsetIndexStart[1] == 0)
    {
        subsetIndexStart.erase(subsetIndexStart.begin()+1);
        meshSubsets--;
    }

    //Make sure we have a default for the tex coord and normal
    //if one or both are not specified
    if(!hasNorm)
        vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f));
    if(!hasTexCoord)
        vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f));

    //Close the obj file, and open the mtl file
    fileIn.close();
    fileIn.open(meshMatLib.c_str());

    std::wstring lastStringRead;
    int matCount = material.size();    //total materials

    //kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same)
    //If the diffuse color WAS set, then we don't need to set our diffuse color to ambient
    bool kdset = false;

    if (fileIn)
    {
        while(fileIn)
        {
            checkChar = fileIn.get();    //Get next char

            switch (checkChar)
            {
                //Check for comment
            case '#':
                checkChar = fileIn.get();
                while(checkChar != '\n')
                    checkChar = fileIn.get();
                break;

                //Set diffuse color
            case 'K':
                checkChar = fileIn.get();
                if(checkChar == 'd')    //Diffuse Color
                {
                    checkChar = fileIn.get();    //remove space

                    fileIn >> material[matCount-1].difColor.x;
                    fileIn >> material[matCount-1].difColor.y;
                    fileIn >> material[matCount-1].difColor.z;

                    kdset = true;
                }

                //Ambient Color (We'll store it in diffuse if there isn't a diffuse already)
                if(checkChar == 'a')    
                {                    
                    checkChar = fileIn.get();    //remove space
                    if(!kdset)
                    {
                        fileIn >> material[matCount-1].difColor.x;
                        fileIn >> material[matCount-1].difColor.y;
                        fileIn >> material[matCount-1].difColor.z;
                    }
                }
                break;

                //Check for transparency
            case 'T':
                checkChar = fileIn.get();
                if(checkChar == 'r')
                {
                    checkChar = fileIn.get();    //remove space
                    float Transparency;
                    fileIn >> Transparency;

                    material[matCount-1].difColor.w = Transparency;

                    if(Transparency > 0.0f)
                        material[matCount-1].transparent = true;
                }
                break;

                //Some obj files specify d for transparency
            case 'd':
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    float Transparency;
                    fileIn >> Transparency;

                    //'d' - 0 being most transparent, and 1 being opaque, opposite of Tr
                    Transparency = 1.0f - Transparency;

                    material[matCount-1].difColor.w = Transparency;

                    if(Transparency > 0.0f)
                        material[matCount-1].transparent = true;                    
                }
                break;

                //Get the diffuse map (texture)
            case 'm':
                checkChar = fileIn.get();
                if(checkChar == 'a')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'p')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == '_')
                        {
                            //map_Kd - Diffuse map
                            checkChar = fileIn.get();
                            if(checkChar == 'K')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'd')
                                {
                                    std::wstring fileNamePath;

                                    fileIn.get();    //Remove whitespace between map_Kd and file

                                    //Get the file path - We read the pathname char by char since
                                    //pathnames can sometimes contain spaces, so we will read until
                                    //we find the file extension
                                    bool texFilePathEnd = false;
                                    while(!texFilePathEnd)
                                    {
                                        checkChar = fileIn.get();

                                        fileNamePath += checkChar;

                                        if(checkChar == '.')
                                        {
                                            for(int i = 0; i < 3; ++i)
                                                fileNamePath += fileIn.get();

                                            texFilePathEnd = true;
                                        }                            
                                    }

                                    //check if this texture has already been loaded
                                    bool alreadyLoaded = false;
                                    for(int i = 0; i < textureNameArray.size(); ++i)
                                    {
                                        if(fileNamePath == textureNameArray[i])
                                        {
                                            alreadyLoaded = true;
                                            material[matCount-1].texArrayIndex = i;
                                            material[matCount-1].hasTexture = true;
                                        }
                                    }

                                    //if the texture is not already loaded, load it now
                                    if(!alreadyLoaded)
                                    {
                                        ID3D11ShaderResourceView* tempMeshSRV;
                                        hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                            NULL, NULL, &tempMeshSRV, NULL );
                                        if(SUCCEEDED(hr))
                                        {
                                            textureNameArray.push_back(fileNamePath.c_str());
                                            material[matCount-1].texArrayIndex = meshSRV.size();
                                            meshSRV.push_back(tempMeshSRV);
                                            material[matCount-1].hasTexture = true;
                                        }
                                    }    
                                }
                            }
                            //map_d - alpha map
                            else if(checkChar == 'd')
                            {
                                //Alpha maps are usually the same as the diffuse map
                                //So we will assume that for now by only enabling
                                //transparency for this material, as we will already
                                //be using the alpha channel in the diffuse map
                                material[matCount-1].transparent = true;
                            }

                            //map_bump - bump map (we're usinga normal map though)
                            else if(checkChar == 'b')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'u')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == 'm')
                                    {
                                        checkChar = fileIn.get();
                                        if(checkChar == 'p')
                                        {
                                            std::wstring fileNamePath;

                                            fileIn.get();    //Remove whitespace between map_bump and file

                                            //Get the file path - We read the pathname char by char since
                                            //pathnames can sometimes contain spaces, so we will read until
                                            //we find the file extension
                                            bool texFilePathEnd = false;
                                            while(!texFilePathEnd)
                                            {
                                                checkChar = fileIn.get();

                                                fileNamePath += checkChar;

                                                if(checkChar == '.')
                                                {
                                                    for(int i = 0; i < 3; ++i)
                                                        fileNamePath += fileIn.get();

                                                    texFilePathEnd = true;
                                                }                            
                                            }

                                            //check if this texture has already been loaded
                                            bool alreadyLoaded = false;
                                            for(int i = 0; i < textureNameArray.size(); ++i)
                                            {
                                                if(fileNamePath == textureNameArray[i])
                                                {
                                                    alreadyLoaded = true;
                                                    material[matCount-1].normMapTexArrayIndex = i;
                                                    material[matCount-1].hasNormMap = true;
                                                }
                                            }

                                            //if the texture is not already loaded, load it now
                                            if(!alreadyLoaded)
                                            {
                                                ID3D11ShaderResourceView* tempMeshSRV;
                                                hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                                    NULL, NULL, &tempMeshSRV, NULL );
                                                if(SUCCEEDED(hr))
                                                {
                                                    textureNameArray.push_back(fileNamePath.c_str());
                                                    material[matCount-1].normMapTexArrayIndex = meshSRV.size();
                                                    meshSRV.push_back(tempMeshSRV);
                                                    material[matCount-1].hasNormMap = true;
                                                }
                                            }    
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            case 'n':    //newmtl - Declare new material
                checkChar = fileIn.get();
                if(checkChar == 'e')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'w')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'm')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 't')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'l')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        //New material, set its defaults
                                        SurfaceMaterial tempMat;
                                        material.push_back(tempMat);
                                        fileIn >> material[matCount].matName;
                                        material[matCount].transparent = false;
                                        material[matCount].hasTexture = false;
                                        material[matCount].hasNormMap = false;
                                        material[matCount].normMapTexArrayIndex = 0;
                                        material[matCount].texArrayIndex = 0;
                                        matCount++;
                                        kdset = false;
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            default:
                break;
            }
        }
    }    
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    //Make sure we are out of fullscreen

        std::wstring message = L"Could not open: ";
        message += meshMatLib;

        MessageBox(0, message.c_str(),
            L"Error", MB_OK);

        return false;
    }

    //Set the subsets material to the index value
    //of the its material in our material array
    for(int i = 0; i < meshSubsets; ++i)
    {
        bool hasMat = false;
        for(int j = 0; j < material.size(); ++j)
        {
            if(meshMaterials[i] == material[j].matName)
            {
                subsetMaterialArray.push_back(j);
                hasMat = true;
            }
        }
        if(!hasMat)
            subsetMaterialArray.push_back(0); //Use first material in array
    }

    std::vector<Vertex> vertices;
    Vertex tempVert;

    //Create our vertices using the information we got 
    //from the file and store them in a vector
    for(int j = 0 ; j < totalVerts; ++j)
    {
        tempVert.pos = vertPos[vertPosIndex[j]];
        tempVert.normal = vertNorm[vertNormIndex[j]];
        tempVert.texCoord = vertTexCoord[vertTCIndex[j]];

        vertices.push_back(tempVert);
    }

    //////////////////////Compute Normals///////////////////////////
    //If computeNormals was set to true then we will create our own
    //normals, if it was set to false we will use the obj files normals
    if(computeNormals)
    {
        std::vector<XMFLOAT3> tempNormal;

        //normalized and unnormalized normals
        XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

        //tangent stuff
        std::vector<XMFLOAT3> tempTangent;
        XMFLOAT3 tangent = XMFLOAT3(0.0f, 0.0f, 0.0f);
        float tcU1, tcV1, tcU2, tcV2;

        //Used to get vectors (sides) from the position of the verts
        float vecX, vecY, vecZ;

        //Two edges of our triangle
        XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

        //Compute face normals
        //And Tangents
        for(int i = 0; i < meshTriangles; ++i)
        {
            //Get the vector describing one edge of our triangle (edge 0,2)
            vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x;
            vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y;
            vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z;        
            edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

            //Get the vector describing another edge of our triangle (edge 2,1)
            vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x;
            vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y;
            vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z;        
            edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

            //Cross multiply the two edge vectors to get the un-normalized face normal
            XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

            tempNormal.push_back(unnormalized);

            //Find first texture coordinate edge 2d vector
            tcU1 = vertices[indices[(i*3)]].texCoord.x - vertices[indices[(i*3)+2]].texCoord.x;
            tcV1 = vertices[indices[(i*3)]].texCoord.y - vertices[indices[(i*3)+2]].texCoord.y;

            //Find second texture coordinate edge 2d vector
            tcU2 = vertices[indices[(i*3)+2]].texCoord.x - vertices[indices[(i*3)+1]].texCoord.x;
            tcV2 = vertices[indices[(i*3)+2]].texCoord.y - vertices[indices[(i*3)+1]].texCoord.y;

            //Find tangent using both tex coord edges and position edges
            tangent.x = (tcV1 * XMVectorGetX(edge1) - tcV2 * XMVectorGetX(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
            tangent.y = (tcV1 * XMVectorGetY(edge1) - tcV2 * XMVectorGetY(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
            tangent.z = (tcV1 * XMVectorGetZ(edge1) - tcV2 * XMVectorGetZ(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));

            tempTangent.push_back(tangent);
        }

        //Compute vertex normals (normal Averaging)
        XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        XMVECTOR tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        int facesUsing = 0;
        float tX, tY, tZ;    //temp axis variables

        //Go through each vertex
        for(int i = 0; i < totalVerts; ++i)
        {
            //Check which triangles use this vertex
            for(int j = 0; j < meshTriangles; ++j)
            {
                if(indices[j*3] == i ||
                    indices[(j*3)+1] == i ||
                    indices[(j*3)+2] == i)
                {
                    tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                    tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                    tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                    normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                    //We can reuse tX, tY, tZ to sum up tangents
                    tX = XMVectorGetX(tangentSum) + tempTangent[j].x;
                    tY = XMVectorGetY(tangentSum) + tempTangent[j].y;
                    tZ = XMVectorGetZ(tangentSum) + tempTangent[j].z;

                    tangentSum = XMVectorSet(tX, tY, tZ, 0.0f); //sum up face tangents using this vertex

                    facesUsing++;
                }
            }

            //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
            normalSum = normalSum / facesUsing;
            tangentSum = tangentSum / facesUsing;

            //Normalize the normalSum vector and tangent
            normalSum = XMVector3Normalize(normalSum);
            tangentSum =  XMVector3Normalize(tangentSum);

            //Store the normal and tangent in our current vertex
            vertices[i].normal.x = XMVectorGetX(normalSum);
            vertices[i].normal.y = XMVectorGetY(normalSum);
            vertices[i].normal.z = XMVectorGetZ(normalSum);

            vertices[i].tangent.x = XMVectorGetX(tangentSum);
            vertices[i].tangent.y = XMVectorGetY(tangentSum);
            vertices[i].tangent.z = XMVectorGetZ(tangentSum);

            //Clear normalSum, tangentSum and facesUsing for next vertex
            normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
            tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
            facesUsing = 0;

        }
    }

    //Create index buffer
    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = &indices[0];
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff);

    //Create Vertex Buffer
    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = &vertices[0];
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff);

    return true;
}

void CreateSphere(int LatLines, int LongLines)
{
    NumSphereVertices = ((LatLines-2) * LongLines) + 2;
    NumSphereFaces  = ((LatLines-3)*(LongLines)*2) + (LongLines*2);

    float sphereYaw = 0.0f;
    float spherePitch = 0.0f;

    std::vector<Vertex> vertices(NumSphereVertices);

    XMVECTOR currVertPos = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);

    vertices[0].pos.x = 0.0f;
    vertices[0].pos.y = 0.0f;
    vertices[0].pos.z = 1.0f;

    for(DWORD i = 0; i < LatLines-2; ++i)
    {
        spherePitch = (i+1) * (3.14f/(LatLines-1));
        Rotationx = XMMatrixRotationX(spherePitch);
        for(DWORD j = 0; j < LongLines; ++j)
        {
            sphereYaw = j * (6.28f/(LongLines));
            Rotationy = XMMatrixRotationZ(sphereYaw);
            currVertPos = XMVector3TransformNormal( XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (Rotationx * Rotationy) );    
            currVertPos = XMVector3Normalize( currVertPos );
            vertices[i*LongLines+j+1].pos.x = XMVectorGetX(currVertPos);
            vertices[i*LongLines+j+1].pos.y = XMVectorGetY(currVertPos);
            vertices[i*LongLines+j+1].pos.z = XMVectorGetZ(currVertPos);
        }
    }

    vertices[NumSphereVertices-1].pos.x =  0.0f;
    vertices[NumSphereVertices-1].pos.y =  0.0f;
    vertices[NumSphereVertices-1].pos.z = -1.0f;


    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * NumSphereVertices;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = &vertices[0];
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &sphereVertBuffer);


    std::vector<DWORD> indices(NumSphereFaces * 3);

    int k = 0;
    for(DWORD l = 0; l < LongLines-1; ++l)
    {
        indices[k] = 0;
        indices[k+1] = l+1;
        indices[k+2] = l+2;
        k += 3;
    }

    indices[k] = 0;
    indices[k+1] = LongLines;
    indices[k+2] = 1;
    k += 3;

    for(DWORD i = 0; i < LatLines-3; ++i)
    {
        for(DWORD j = 0; j < LongLines-1; ++j)
        {
            indices[k]   = i*LongLines+j+1;
            indices[k+1] = i*LongLines+j+2;
            indices[k+2] = (i+1)*LongLines+j+1;

            indices[k+3] = (i+1)*LongLines+j+1;
            indices[k+4] = i*LongLines+j+2;
            indices[k+5] = (i+1)*LongLines+j+2;

            k += 6; // next quad
        }

        indices[k]   = (i*LongLines)+LongLines;
        indices[k+1] = (i*LongLines)+1;
        indices[k+2] = ((i+1)*LongLines)+LongLines;

        indices[k+3] = ((i+1)*LongLines)+LongLines;
        indices[k+4] = (i*LongLines)+1;
        indices[k+5] = ((i+1)*LongLines)+1;

        k += 6;
    }

    for(DWORD l = 0; l < LongLines-1; ++l)
    {
        indices[k] = NumSphereVertices-1;
        indices[k+1] = (NumSphereVertices-1)-(l+1);
        indices[k+2] = (NumSphereVertices-1)-(l+2);
        k += 3;
    }

    indices[k] = NumSphereVertices-1;
    indices[k+1] = (NumSphereVertices-1)-LongLines;
    indices[k+2] = NumSphereVertices-2;

    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * NumSphereFaces * 3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = &indices[0];
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &sphereIndexBuffer);

}

void InitD2DScreenTexture()
{
    //Create the vertex buffer
    Vertex v[] =
    {
        // Front Face
        Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f,-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex(-1.0f,  1.0f, -1.0f, 0.0f, 0.0f,-1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex( 1.0f,  1.0f, -1.0f, 1.0f, 0.0f, 1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
    };

    DWORD indices[] = {
        // Front Face
        0,  1,  2,
        0,  2,  3,
    };

    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = indices;
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &d2dIndexBuffer);


    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = v;
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &d2dVertBuffer);

    //Create A shader resource view from the texture D2D will render to,
    //So we can use it to texture a square which overlays our scene
    d3d11Device->CreateShaderResourceView(sharedTex11, NULL, &d2dTexture);
}

bool InitScene()
{
    InitD2DScreenTexture();

    CreateSphere(10, 10);

    if(!LoadObjModel(L"ground.obj", &meshVertBuff, &meshIndexBuff, meshSubsetIndexStart, meshSubsetTexture, material, meshSubsets, true, true))
        return false;

    if(!LoadMD5Model(L"boy.md5mesh", NewMD5Model, meshSRV, textureNameArray))
        return false;
    ///////////////**************new**************////////////////////    
    if(!LoadMD5Anim(L"boy.md5anim", NewMD5Model))
        return false;
    ///////////////**************new**************////////////////////

    //Compile Shaders from shader file
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_0", 0, 0, 0, &PS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "D2D_PS", "ps_4_0", 0, 0, 0, &D2D_PS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_VS", "vs_4_0", 0, 0, 0, &SKYMAP_VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_PS", "ps_4_0", 0, 0, 0, &SKYMAP_PS_Buffer, 0, 0);

    //Create the Shader Objects
    hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
    hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);

    hr = d3d11Device->CreatePixelShader(D2D_PS_Buffer->GetBufferPointer(), D2D_PS_Buffer->GetBufferSize(), NULL, &D2D_PS);
    hr = d3d11Device->CreateVertexShader(SKYMAP_VS_Buffer->GetBufferPointer(), SKYMAP_VS_Buffer->GetBufferSize(), NULL, &SKYMAP_VS);
    hr = d3d11Device->CreatePixelShader(SKYMAP_PS_Buffer->GetBufferPointer(), SKYMAP_PS_Buffer->GetBufferSize(), NULL, &SKYMAP_PS);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    light.pos = XMFLOAT3(0.0f, 7.0f, 0.0f);
    light.dir = XMFLOAT3(-0.5f, 0.75f, -0.5f);
    light.range = 1000.0f;
    light.cone = 12.0f;
    light.att = XMFLOAT3(0.4f, 0.02f, 0.000f);
    light.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
    light.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);

    //Create the Input Layout
    hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
        VS_Buffer->GetBufferSize(), &vertLayout );

    //Set the Input Layout
    d3d11DevCon->IASetInputLayout( vertLayout );

    //Set Primitive Topology
    d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

    //Create the Viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = Width;
    viewport.Height = Height;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;

    //Set the Viewport
    d3d11DevCon->RSSetViewports(1, &viewport);

    //Create the buffer to send to the cbuffer in effect file
    D3D11_BUFFER_DESC cbbd;    
    ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));

    cbbd.Usage = D3D11_USAGE_DEFAULT;
    cbbd.ByteWidth = sizeof(cbPerObject);
    cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbbd.CPUAccessFlags = 0;
    cbbd.MiscFlags = 0;

    hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerObjectBuffer);

    //Create the buffer to send to the cbuffer per frame in effect file
    ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));

    cbbd.Usage = D3D11_USAGE_DEFAULT;
    cbbd.ByteWidth = sizeof(cbPerFrame);
    cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbbd.CPUAccessFlags = 0;
    cbbd.MiscFlags = 0;

    hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerFrameBuffer);

    //Camera information
    camPosition = XMVectorSet( 0.0f, 5.0f, -8.0f, 0.0f );
    camTarget = XMVectorSet( 0.0f, 0.5f, 0.0f, 0.0f );
    camUp = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );

    //Set the View matrix
    camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );

    //Set the Projection matrix
    camProjection = XMMatrixPerspectiveFovLH( 0.4f*3.14f, (float)Width/Height, 1.0f, 1000.0f);

    D3D11_BLEND_DESC blendDesc;
    ZeroMemory( &blendDesc, sizeof(blendDesc) );

    D3D11_RENDER_TARGET_BLEND_DESC rtbd;
    ZeroMemory( &rtbd, sizeof(rtbd) );

    rtbd.BlendEnable             = true;
    rtbd.SrcBlend                 = D3D11_BLEND_SRC_COLOR;
    rtbd.DestBlend                 = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
    rtbd.SrcBlendAlpha             = D3D11_BLEND_ONE;
    rtbd.DestBlendAlpha             = D3D11_BLEND_ZERO;
    rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
    rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.RenderTarget[0] = rtbd;

    d3d11Device->CreateBlendState(&blendDesc, &d2dTransparency);

    ZeroMemory( &rtbd, sizeof(rtbd) );

    rtbd.BlendEnable             = true;
    rtbd.SrcBlend                 = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.DestBlend                 = D3D11_BLEND_SRC_ALPHA;
    rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
    rtbd.SrcBlendAlpha             = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.DestBlendAlpha             = D3D11_BLEND_SRC_ALPHA;
    rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
    rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.RenderTarget[0] = rtbd;

    d3d11Device->CreateBlendState(&blendDesc, &Transparency);

    ///Load Skymap's cube texture///
    D3DX11_IMAGE_LOAD_INFO loadSMInfo;
    loadSMInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;

    ID3D11Texture2D* SMTexture = 0;
    hr = D3DX11CreateTextureFromFile(d3d11Device, L"skymap.dds", 
        &loadSMInfo, 0, (ID3D11Resource**)&SMTexture, 0);

    D3D11_TEXTURE2D_DESC SMTextureDesc;
    SMTexture->GetDesc(&SMTextureDesc);

    D3D11_SHADER_RESOURCE_VIEW_DESC SMViewDesc;
    SMViewDesc.Format = SMTextureDesc.Format;
    SMViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
    SMViewDesc.TextureCube.MipLevels = SMTextureDesc.MipLevels;
    SMViewDesc.TextureCube.MostDetailedMip = 0;

    hr = d3d11Device->CreateShaderResourceView(SMTexture, &SMViewDesc, &smrv);

    // Describe the Sample State
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory( &sampDesc, sizeof(sampDesc) );
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;    
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;

    //Create the Sample State
    hr = d3d11Device->CreateSamplerState( &sampDesc, &CubesTexSamplerState );

    D3D11_RASTERIZER_DESC cmdesc;

    ZeroMemory(&cmdesc, sizeof(D3D11_RASTERIZER_DESC));
    cmdesc.FillMode = D3D11_FILL_SOLID;
    cmdesc.CullMode = D3D11_CULL_BACK;
    cmdesc.FrontCounterClockwise = true;
    hr = d3d11Device->CreateRasterizerState(&cmdesc, &CCWcullMode);

    cmdesc.FrontCounterClockwise = false;

    hr = d3d11Device->CreateRasterizerState(&cmdesc, &CWcullMode);

    cmdesc.CullMode = D3D11_CULL_NONE;
    //cmdesc.FillMode = D3D11_FILL_WIREFRAME;
    hr = d3d11Device->CreateRasterizerState(&cmdesc, &RSCullNone);

    D3D11_DEPTH_STENCIL_DESC dssDesc;
    ZeroMemory(&dssDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
    dssDesc.DepthEnable = true;
    dssDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    dssDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;

    d3d11Device->CreateDepthStencilState(&dssDesc, &DSLessEqual);

    return true;
}

void StartTimer()
{
    LARGE_INTEGER frequencyCount;
    QueryPerformanceFrequency(&frequencyCount);

    countsPerSecond = double(frequencyCount.QuadPart);

    QueryPerformanceCounter(&frequencyCount);
    CounterStart = frequencyCount.QuadPart;
}

double GetTime()
{
    LARGE_INTEGER currentTime;
    QueryPerformanceCounter(&currentTime);
    return double(currentTime.QuadPart-CounterStart)/countsPerSecond;
}

double GetFrameTime()
{
    LARGE_INTEGER currentTime;
    __int64 tickCount;
    QueryPerformanceCounter(&currentTime);

    tickCount = currentTime.QuadPart-frameTimeOld;
    frameTimeOld = currentTime.QuadPart;

    if(tickCount < 0.0f)
        tickCount = 0.0f;

    return float(tickCount)/countsPerSecond;
}

void UpdateScene(double time)
{
    //Reset sphereWorld
    sphereWorld = XMMatrixIdentity();

    //Define sphereWorld's world space matrix
    Scale = XMMatrixScaling( 5.0f, 5.0f, 5.0f );
    //Make sure the sphere is always centered around camera
    Translation = XMMatrixTranslation( XMVectorGetX(camPosition), XMVectorGetY(camPosition), XMVectorGetZ(camPosition) );

    //Set sphereWorld's world space using the transformations
    sphereWorld = Scale * Translation;

    //the loaded models world space
    meshWorld = XMMatrixIdentity();

    Rotation = XMMatrixRotationY(3.14f);
    Scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f );
    Translation = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );

    meshWorld = Rotation * Scale * Translation;

    Scale = XMMatrixScaling( 0.04f, 0.04f, 0.04f );            // The model is a bit too large for our scene, so make it smaller
    Translation = XMMatrixTranslation( 0.0f, 3.0f, 0.0f);
    smilesWorld = Scale * Translation;
}

void RenderText(std::wstring text, int inInt)
{

    d3d11DevCon->PSSetShader(D2D_PS, 0, 0);

    //Release the D3D 11 Device
    keyedMutex11->ReleaseSync(0);

    //Use D3D10.1 device
    keyedMutex10->AcquireSync(0, 5);    

    //Draw D2D content        
    D2DRenderTarget->BeginDraw();    

    //Clear D2D Background
    D2DRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));

    //Create our string
    std::wostringstream printString; 
    printString << text << inInt;
    printText = printString.str();

    //Set the Font Color
    D2D1_COLOR_F FontColor = D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f);

    //Set the brush color D2D will use to draw with
    Brush->SetColor(FontColor);    

    //Create the D2D Render Area
    D2D1_RECT_F layoutRect = D2D1::RectF(0, 0, Width, Height);

    //Draw the Text
    D2DRenderTarget->DrawText(
        printText.c_str(),
        wcslen(printText.c_str()),
        TextFormat,
        layoutRect,
        Brush
        );

    D2DRenderTarget->EndDraw();    

    //Release the D3D10.1 Device
    keyedMutex10->ReleaseSync(1);

    //Use the D3D11 Device
    keyedMutex11->AcquireSync(1, 5);

    //Use the shader resource representing the direct2d render target
    //to texture a square which is rendered in screen space so it
    //overlays on top of our entire scene. We use alpha blending so
    //that the entire background of the D2D render target is "invisible",
    //And only the stuff we draw with D2D will be visible (the text)

    //Set the blend state for D2D render target texture objects
    d3d11DevCon->OMSetBlendState(d2dTransparency, NULL, 0xffffffff);

    //Set the d2d Index buffer
    d3d11DevCon->IASetIndexBuffer( d2dIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    //Set the d2d vertex buffer
    UINT stride = sizeof( Vertex );
    UINT offset = 0;
    d3d11DevCon->IASetVertexBuffers( 0, 1, &d2dVertBuffer, &stride, &offset );

    WVP =  XMMatrixIdentity();
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &d2dTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 6, 0, 0 );    
}

void DrawScene()
{
    //Clear our render target and depth/stencil view
    float bgColor[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);    
    d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

    constbuffPerFrame.light = light;
    d3d11DevCon->UpdateSubresource( cbPerFrameBuffer, 0, NULL, &constbuffPerFrame, 0, 0 );
    d3d11DevCon->PSSetConstantBuffers(0, 1, &cbPerFrameBuffer);    

    //Set our Render Target
    d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, depthStencilView );

    //Set the default blend state (no blending) for opaque objects
    d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    UINT stride = sizeof( Vertex );
    UINT offset = 0;

    ///***Draw MD5 Model***///
    for(int i = 0; i < NewMD5Model.numSubsets; i ++)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( NewMD5Model.subsets[i].indexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &NewMD5Model.subsets[i].vertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = smilesWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(smilesWorld);        
        cbPerObj.hasTexture = true;        // We'll assume all md5 subsets have textures
        cbPerObj.hasNormMap = false;    // We'll also assume md5 models have no normal map (easy to change later though)
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[NewMD5Model.subsets[i].texArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        d3d11DevCon->DrawIndexed( NewMD5Model.subsets[i].indices.size(), 0, 0 );
    }

    /////Draw our model's NON-transparent subsets/////
    for(int i = 0; i < meshSubsets; ++i)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = meshWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(meshWorld);    
        cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
        cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
        cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        if(material[meshSubsetTexture[i]].hasTexture)
            d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
        if(material[meshSubsetTexture[i]].hasNormMap)
            d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        int indexStart = meshSubsetIndexStart[i];
        int indexDrawAmount =  meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];
        if(!material[meshSubsetTexture[i]].transparent)
            d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
    }

    /////Draw the Sky's Sphere//////
    //Set the spheres index buffer
    d3d11DevCon->IASetIndexBuffer( sphereIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    //Set the spheres vertex buffer
    d3d11DevCon->IASetVertexBuffers( 0, 1, &sphereVertBuffer, &stride, &offset );

    //Set the WVP matrix and send it to the constant buffer in effect file
    WVP = sphereWorld * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    cbPerObj.World = XMMatrixTranspose(sphereWorld);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    //Send our skymap resource view to pixel shader
    d3d11DevCon->PSSetShaderResources( 0, 1, &smrv );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    //Set the new VS and PS shaders
    d3d11DevCon->VSSetShader(SKYMAP_VS, 0, 0);
    d3d11DevCon->PSSetShader(SKYMAP_PS, 0, 0);
    //Set the new depth/stencil and RS states
    d3d11DevCon->OMSetDepthStencilState(DSLessEqual, 0);
    d3d11DevCon->RSSetState(RSCullNone);
    d3d11DevCon->DrawIndexed( NumSphereFaces * 3, 0, 0 );    

    //Set the default VS, PS shaders and depth/stencil state
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);
    d3d11DevCon->OMSetDepthStencilState(NULL, 0);

    /////Draw our model's TRANSPARENT subsets now/////
    //Set our blend state
    d3d11DevCon->OMSetBlendState(Transparency, NULL, 0xffffffff);

    for(int i = 0; i < meshSubsets; ++i)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = meshWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(meshWorld);    
        cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
        cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
        cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        if(material[meshSubsetTexture[i]].hasTexture)
            d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
        if(material[meshSubsetTexture[i]].hasNormMap)
            d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        int indexStart = meshSubsetIndexStart[i];
        int indexDrawAmount =  meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];

        if(material[meshSubsetTexture[i]].transparent)
            d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
    }

    RenderText(L"FPS: ", fps);

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}

int messageloop(){
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    while(true)
    {
        BOOL PeekMessageL( 
            LPMSG lpMsg,
            HWND hWnd,
            UINT wMsgFilterMin,
            UINT wMsgFilterMax,
            UINT wRemoveMsg
            );

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;
            TranslateMessage(&msg);    
            DispatchMessage(&msg);
        }
        else{
            // run game code    
            frameCount++;
            if(GetTime() > 1.0f)
            {
                fps = frameCount;
                frameCount = 0;
                StartTimer();
            }    

            frameTime = GetFrameTime();

            DetectInput(frameTime);
            UpdateScene(frameTime);
            DrawScene();
        }
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch( msg )
    {
    case WM_KEYDOWN:
        if( wParam == VK_ESCAPE ){
            DestroyWindow(hwnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,
        msg,
        wParam,
        lParam);
}
Effects.fx
struct Light
{
    float3 pos;
    float  range;
    float3 dir;
    float cone;
    float3 att;
    float4 ambient;
    float4 diffuse;
};

cbuffer cbPerFrame
{
    Light light;
};

cbuffer cbPerObject
{
    float4x4 WVP;
    float4x4 World;

    float4 difColor;
    bool hasTexture;
    bool hasNormMap;
};

Texture2D ObjTexture;
Texture2D ObjNormMap;
SamplerState ObjSamplerState;
TextureCube SkyMap;

struct VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float4 worldPos : POSITION;
    float2 TexCoord : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
};

struct SKYMAP_VS_OUTPUT    //output structure for skymap vertex shader
{
    float4 Pos : SV_POSITION;
    float3 texCoord : TEXCOORD;
};

VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
    VS_OUTPUT output;
    
    output.Pos = mul(inPos, WVP);
    output.worldPos = mul(inPos, World);

    output.normal = mul(normal, World);

    output.tangent = mul(tangent, World);

    output.TexCoord = inTexCoord;

    return output;
}


SKYMAP_VS_OUTPUT SKYMAP_VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
    SKYMAP_VS_OUTPUT output = (SKYMAP_VS_OUTPUT)0;

    //Set Pos to xyww instead of xyzw, so that z will always be 1 (furthest from camera)
    output.Pos = mul(float4(inPos, 1.0f), WVP).xyww;

    output.texCoord = inPos;

    return output;
}

float4 PS(VS_OUTPUT input) : SV_TARGET
{
    input.normal = normalize(input.normal);    

    //Set diffuse color of material
    float4 diffuse = difColor;

    //If material has a diffuse texture map, set it now
    if(hasTexture == true)
        diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );

    //If material has a normal map, we can set it now
    if(hasNormMap == true)
    {
        //Load normal from normal map
        float4 normalMap = ObjNormMap.Sample( ObjSamplerState, input.TexCoord );

        //Change normal map range from [0, 1] to [-1, 1]
        normalMap = (2.0f*normalMap) - 1.0f;

        //Make sure tangent is completely orthogonal to normal
        input.tangent = normalize(input.tangent - dot(input.tangent, input.normal)*input.normal);

        //Create the biTangent
        float3 biTangent = cross(input.normal, input.tangent);

        //Create the "Texture Space"
        float3x3 texSpace = float3x3(input.tangent, biTangent, input.normal);

        //Convert normal from normal map to texture space and store in input.normal
        input.normal = normalize(mul(normalMap, texSpace));
    }

    float3 finalColor;

    finalColor = diffuse * light.ambient;
    finalColor += saturate(dot(light.dir, input.normal) * light.diffuse * diffuse);
    
    return float4(finalColor, diffuse.a);
}

float4 SKYMAP_PS(SKYMAP_VS_OUTPUT input) : SV_Target
{
    return SkyMap.Sample(ObjSamplerState, input.texCoord);
}

float4 D2D_PS(VS_OUTPUT input) : SV_TARGET
{
    float4 diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );
    
    return diffuse;
}



728x90
728x90

원문 : http://www.braynzarsoft.net/viewtutorial/q16390-27-loading-an-md5-model


MD5 모델은 "md5mesh"와 "md5anim" 두개의 파일로 분리되어 있습니다. 그래서 강좌도 하나는 모델 로딩 하나는 애니메이션 두개로 구성하였습니다. 이번 강좌는 MD5 모델을 md5mesh파일에서 어떻게 불러오고 관절 레이아웃에 기초하여 정점위치를 설정하는지를 알려줄 것입니다. 다음 강좌는 md5anim파일로터 모델의 애니메이션을 불러오고 모델을 움직여 볼 것입니다. 이번 강좌는 다음의 것들을 설명하려 합니다.

- "md5mesh" 포맷 - 사원수에 대한 간단한 소개 - 모델의 "뼈대"의 동작 원리


Introduction

3D 모델을 로딩하는 또 다른 강좌를 진행하는 이유는 애니메이션 강좌(다음 강좌)를 하고싶기 때문입니다. obj 포맷은 애니메이션을 저장하지 않기때문에 (여러분이 애니메이션의 각 프레임마다 따로 분리된 모델을 저장하는 키프레임 애니메이션을 위해 obj 포맷을 사용해도 됩니다. 많은 장점이 있습니다. 모든 프레임마다 정확히 계획한대로 보이고 모델사이 스위치하는것도 매우 빨라서 성능상으로도 좋습니다. 하지만 단점으로 완전히 정적이고 메모리를 많이 차지합니다.) 이를 수행하는 다른 포맷을 정해야 했습니다. 조사 끝에 이번 강좌를 위한 잘 만든 포맷을 알아냈다고 생각합니다. 바로 MD5 입니다. 여러분은 MD5 포맷에 관해 모를 수 있는데 바로 Doom 3에 사용된 포맷입니다. MD5 포맷은 정점 위치를 정의하기위해 뼈대(관절) 구조를 사용하는데 이게 제가 이 포맷을 선택한 이유입니다. MD5 포맷은 2개의 파일로 구성되는데 "md5mesh"와 "md5anim"입니다. 이 파일들은 아스키값으로 저장되어 있어 읽기 매우 쉽습니다. "md5mesh"은 이번 강좌에서 기하, 물질같은 모델 정보를 저장하는데 초점에 맞출 것이고 "md5anim"은 다음 강좌에서 모델의 애니메이션을 저장하는데 초점을 맞출 것입니다. 진행하기 전에 제가 이 강좌를 만들때 참고한 두 기사를 언급하고 싶습니다. 여기여기입니다.



Skeletal Animation (Brief Intro.)

뼈대 애니메이션은 키프레임 애니메이션(애니메이션의 각 프레임에 대해 모델의 분리된 버전을 저장한다)의 대책입니다. 뼈대 애니메이션은 뼈대의 유형을 정의하여 각 모델 정점들에 뼈 혹은 관절에 대한 weight를 주는 곳입니다. 각 정점은 한 개 혹은 그 이상의 weight를 갖습니다. weight는 관절 또는 뼈에 대한 위치와 가중치를 정의합니다. 가중치는 특정 정점 이상을 갖는 weight를 어떻게 조절할지를 결정합니다. 각 정점은 한 개 이상의 weight를 포함하기 때문입니다. 한 정점의 weight의 가중치의 총합은 반드시 "1"이 되야 합니다.

2가지 다른 유형의 뼈대 애니메이션 구조가 있습니다. 하나는 관절을 사용하고 하나는 실제 뼈와 매우 유사한 시스템을 사용합니다.

우리가 사용할 관절 시스템은 과절의 위치와 관절의 방향, 부모 관절을 정의합니다. 모든 관절은 단 하나(루트 관절)을 제외하고 반드시 부모 관절을 가지며 루트 관절은 계층의 가장 상위에 있고 부모 관절을 -1로 설정합니다. 부모 관절이 회전되거나 움직이면 자식 관절도 똑같이 적용됩니다. 예를들어 만약 여러분이 사람의 위쪽 팔을 움직이면 아랫쪽 팔, 손 그리고 손가락들도 같이 움직입니다. 자식 관절은 항상 부모 관절에 붙어져있으며 관절을 분리하는 것은 불가능하며 모델을 폭발시키고 싶어도 안됩니다. 만약 총에 맞은 후 머리를 떨어뜨리고 싶으면 관절의 방향을 0으로 설정해야하며 그러면 머리가 날라가는 것처럼 보일 겁니다. 다른 뼈 시스템은 뼈에대해 위치 두개를 명시하는데 하나는 뼈의 끝을 하나는 부모것을 명시합니다. 이 시스템은 폭발하는 모델에 쓰면 좋은데 뼈들이 분리가 가능하기 때문입니다. 비록 이 사이트는 OpenGL로 되어있지만 애니메이션에 대한 좋은 기사입니다. (사이트 링크를 따라가면 도메인 만료로 뜨지 않아서 안썼습니다.)



The MD5 Format

위에서 설명한것 처럼 MD5 포맷은 두개의 파일을 포함합니다. 이번 강좌에서는 모델의 bind-pose를 저장하는 md5mesh만 사용할 것입니다. bind-pose는 모델의 기본 위치입니다. 다음 강좌에서는 여전히 서있지 않게 하도록 모델을 어떻게 움직이는지(animate) 배울것입니다. 또 포맷파일은 아스키값으로 되어있다고 언급했었는데 그래서 읽기가 쉽습니다. 모든 의미있는 줄은 해당 줄이 포함하는 것을 설명하는 문자열로 시작할것입니다. (혹은 많은 경우 그 뒤 라인도 포함하여) 다음 여러 섹션들은 라인이나 라인들을 정의하는 이름이 붙은 각 라인이 무엇을 위한것인지 설명할 것입니다. "MD5Version" 이 문자열은 파일의 버전을 설명하는 숫자입니다. 우리의 로더는 버전 10에 대해 맞추어 제작되었니다. (로더에서 실제 버전 확인을 하지는 않습니다) 여러분이 다른 버전에 대한 정보를 알 수 있습니다. 뭐 제가 다운받은 모든 md5 모델은(사실 많지는 않아요) 전부 버전 10이었지만..

MD5Version 10

"commandline" 이 라인은 신경 쓰실것 없습니다. 데헷

commandline ""
"numJoints" 이 라인은 모델에서 관절의 개수를 가집니다.
numJoints 27

"numMeshes" 모델에서 mesh(서브셋"들" 이라 부를; mesh내에 많은 서브셋(shader, vert 등등)이 있음)의 개수를 나타냅니다. 각 mesh(or 서브셋)는 정점(위치 x, 위치는 weight를 이용하여 계산될 것입니다. 법선도 마찬가지), 삼각형, weight를 정의합니다.

numMeshes 5

"joints" 관절 설명의 시작 부분입니다. 관절 설명은 다음 라인에서 시작하여("joints {" 뒤부터) 닫는 괄호("}")를 만나는 라인까지 입니다. "joints {" 뒤 각 라인은 새로운 관절입니다. 이 각 라인들은 안에 큰따옴표로 시작하며 따옴표 사이는 관절의 이름입니다. 관절 이름 바로 다음은 해당 관절의 ID 번호입니다. ID 번호 "-1"을 가진 관절은 루트 관절입니다. ID 다음은 관절의 위치를 나타내는 3D 벡터(괄호 사이)입니다. 그 다음 또 다른 3D 벡터(역시 괄호 사이)는 관절 "bind-pose" 방향을 나타냅니다. 관절 방향을 파일에서는 3D 벡터로 저장되지만 실제로는 사원수로 사용됩니다. (혹은 4D 벡터) 사원수는 밑에서 간단하게 설명할 것입니다.

joints {
    "Bip01"    -1 ( 0.569962 0.0 -6.39413 ) ( 0.0 0.0 0.707106 )
    ...
}

"mesh" 이 문자열은 mesh 또는 subset의 시작입니다. 열고 닫는 괄호({ ...}) 사이 모든 것은 이 특정 subset을 정의합니다. "md5mesh" 파일은 관절에 대한 하나의 섹션만 가지지만 각 mesh 또는 subset에 대한 섹션은 하나 이상 가질 수 있습니다. 다행히도 파일의 헤더가 subset의 개수를 정의하기 때문에 마지막 섹션을 읽었을때 알 수 있을 것입니다.

mesh {

"shader" 첫번째 중요 라인 (가끔 "mesh {" 라인 뒤에 바로 subset의 이름을 나타내는 주석이 달려있습니다.)은 shader입니다. 이 라인은 모델이 사용할 재질이나 텍스쳐의 이름을 가집니다. (문자열은 큰따옴표안에 있어서 문자열을 읽으신 후 따옴표를 제거하셔야 할 것입니다.) MD5 포맷은 OBJ 포맷처럼 material library 파일을 포함하지 않기 떄문에 여러분의 모델에 재질을 사용하고 싶으면 직접 여러분만의 material library를 만드셔야 합니다. (기본으로 적어도 제 3ds max 익스포터에서 텍스쳐 이름 대신 재질 이름이 사용됩니다.) 그와 다르게 간단하게 하기 위해 각 subset에 대한 재질 이름을 각 mesh에 대해 사용할 텍스쳐의 이름으로 변경하였으니 이에 대해서는 숙제입니다. 히힛

shader "face.jpg"
"numverts" 이 subset에 대한 정점의 개수입니다. 정점 정의는 이 라인밑에 바로 나옵니다.
numverts 99

"vert" 이 문자열 뒤 라인의 내용은 정점의 정의입니다. "vert" 다음은 정수값으로 정점의 인덱스를 나타냅니다. 그 다음은 이 정점에 대한 텍스쳐 좌표입니다. (괄호 안) 그 다음 또 다른 정수값이 있는데 정점에 대한 "시작 weight"의 인덱스 또는 ID입니다. "시작 weight" 뒤에는 정점이 사용할 weight 개수입니다. 각 정점은 틀림없이 하나 이상의 weight를 가지므로 첫번째 weight의 ID가 사용되고 이 시작 weight 바로 뒤로 "n-1"개의 weight가 정점의 위치를 계산하는데 사용될 것입니다. ("n"은 이 정점에서 저장된 weight의 개수입니다)

vert 0 ( 0.453487 0.77956 ) 0 4
"numtris" 여기는 subset 내 삼각형의 개수입니다. 이 뒤에 나오는 라인은 인덱스로 subset을 구성하는 삼각형입니다.
numtris 139

"tri" "tri"로 시작하는 라인들은 이 subset을 구성하는 인덱스 리스트 부분입니다. "tri" 오른쪽의 정수값은 이 삼각형의 ID 또는 인덱스 값이며 그 옆 정수 3개는 이 삼각형을 구성하는 정점값의 ID 또는 인덱스입니다.

tri 0 0 2 1

"numweights" 이 subset에 사용된 weight의 개수입니다. 다음 줄은 사용된 weight의 설명을 담고 있습니다.

numweights 391

"weight" 이 라인은 weight를 정의합니다. "weight" 다음 첫 값은 현재 weight의 인덱스 또는 ID값입니다. 그 다음 값은 정수로 이 weight가 묶일 관절의 ID 또는 인덱스 값을 나타냅니다. 각 weight는 한 관절에만 바인딩 될 수 있습니다. 그 다음은 "가중치" 값으로 이 weight를 사용하는 정점들에 weight가 얼마나 영향을 미치는지를 나타냅니다. 정점이 바인딩 된 모든 weight들은 반드시 "가중치"의 총합을 "1"로 되게 해야 합니다. 이 라인의 마지막 부분은 3D 벡터(괄호 안)로 "관절 공간"에서 weight 위치를 나타내는데 즉 관절 위치에 따른 weight 위치입니다. (weight의 관점에서 관절 위치를 볼때 관절 위치는 (0,0,0)이기 때문이다)

weight 0 23 0.0681065 ( -61.2806 8.07771 -3.0823 )

Quaternion rotations (Brief Intro.)

앞에서 말했듯이 MD5 포맷은 관절의 방향에 대해 사원수를 사용하며 weight 위치를 계산하고 궁극적으로 최종 정점 위치를 구합니다. 사원수의 수학적 배경은 꽤 복잡할 수 있지만(저도 완벽히는 모릅니다. 그 말인 즉슨 회전에 대해 사원수를 사용하는데 그 이상 필요되지 않습니다.) 쨌든 배경 아이디어는 어렵지 않습니다. 사원수는 회전에 사용되는 4D 벡터이며 회전 행렬을 대신할 수 있습니다. 뿐만 아니라 단지 4개의 요소만 사용하여(반면 동일 행렬은 16개를 사용한다) 계산 속도가 더 빠르다고 합니다. 또 짐벌락이라 부르는 현상도 피할 수 있습니다. 사원수에 대해 수학적으로 깊이 얘기하지는 않지만 특정 회전에 대해 어떻게 사용하는지를 설명하도록 노력할 것입니다.(weight를 관절 주위로 회전시켜야 하기 때문에) 사원수에 대한 많은 정의들을 찾았었지만 다 제가 찾는 것 이상으로 더 복잡해 보였습니다. 제가 얘기했듯이 사원수는 (w,x,y,z) 4개 요소를 가지며 directx는 (x,y,z,w) 순으로 저장하는 것 같습니다. (x,y,z) 요소는 회전"축"을 정의하며 그래서 만약 xyz가 (0,1,0)이라면 y축을 기준으로 회전이 이루어질 것입니다. w 요소는 사원수 회전을 동작시킵니다. w는 기본적으로 그 자체로 회전입니다. w는 0~1 사이 값이며 0은 0도 1은 360도가 됩니다. 사원수는 교환법칙이 성립하지 않습니다. 행렬처럼 곱하는 순서가 중요합다는 얘기입니다. 3D 벡터를 사원수로 바꾸기 위해 여러분이 할 것은 w요소를 "0"으로 저장하는 것입니다. 그리고 사원수에서 3D 벡터로 바꾸고 싶을때 그냥 w 요소를 무시할 수 있습니다. 관절 방향을 저장한 뒤 w 요소를 계산해야할 것입니다. 사원수로 회전을 할때 유닛 사원수(단위 사원수; 사원수의 크기가 "1")를 사용해야합니다. 유닛 사원수는 다음 방정식을 만족합니다.

sqrt(w² + x² + y² + z²) = 1
마치 3D 유닛 벡터가 이것을 만족시키는 것처럼
sqrt(x² + y² + z²) = 1
이걸 알면 사원수 w 요소를 이렇게 계산할 수 있습니다.
float t = 1.0f - ( x * x ) - ( y * y ) - ( z * z );
if ( t < 0.0f )
    w = 0.0f;
else
    w = -sqrtf(t);

이 방정식을 이용하여 점을 회전할 수 있습니다.

rotatedPoint = quaternionRotation * point * -quaternionRotation

"-quaternionRotation"는 "quaternionRotation"의 켤레 사원수라 부르는데 "quaternionRotation"의 x, y, z부호를 반전시켜 쉽게 얻을 수 있습니다. 바로 이렇게

quaternion q;
quaternion conjugate = quaternion(-q.x, -q,y, -q.z, q.w);

두 사원수를 곱하는 것은 엄청 간단하지는 않지만 우리가 항상 사용하는 xna 수학 라이브러리는 이를 위한 XMQuaternionMultiply(q1, q2) 함수를 가지고 있어 편리하게 해줍니다.

사원수에 관해 더 알고싶으시면 많은 곳이 있지만 제가 설명한 것의 더 자세한 설명을 한 여기 링크를 추천하고 싶습니다.



Calculating Vertex's Final Position

쭉 보면 MD5는 OBJ와는 매우 다른 것 같습니다. 최종 정점 위치를 계산하기 위해 먼저 관절 위치와 방향에 기인한 weight 위치를 계산해야 합니다. 가장 먼저 할일은 특정 정점이 바인딩 된 각 weight들을 확인하는 것입니다. 그리곤 관절 공간에서 weight의 최종 위치를 계산하고 관절이 위치한 객체 공간(로컬 좌표를 얘기하는 듯)으로 이동시켜야 합니다. 이 위치를 weight 가중치와 곱하고 최종 정점 위치에 더합니다. 제가 최대한 코드에서 일어나는 일을 상세히 설명했습니다. 자 이제 지금까지 간단한 인트로였고 자세히 살펴보도록 하겠습니다. 기억해보세요. 각 정점은 시작 weight의 인덱스 정수값과 사용할 weight 개수값을 저장하고 있습니다. 먼저 정점이 명시하는 weight 개수를 살펴볼 루프로 들어갑니다. 첫번째는 시작 weight가 되고 다음은 weight 인덱스 리스트의 시작 weight 다음 weight들을 따라갑니다. 이 weight가 바인딩된 관절을 찾으면 관절 방향의 켤레 사원수를 계산합니다. 그리곤 다음 방정식을 따라 점을 회전시킵니다.

XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

이 식은 (0,0,0) 주변에 위치한 weight를 회전시키며 이때 관절 공간으로 위치시킵니다. 이제 회전된 점을 모델 공간상의 관절 위치로 이동변환을 해야합니다. 이미 회전을 했기때문에 이해하는데 어렵지 않을 것입니다. 마지막으로 최종 위치에 weight 가중치 요인을 곱하고 그 결과를 최종 정점 위치에 더합니다.

정점 위치에 영향을 주는 각 weight에 대한 루프를 보도록 하겠습니다.



Updated Vertex Structure

우리는 정점이 바인딩 되는 시작 weight 인덱스 값, weight 개수를 저장할 vertex 구조체를 업데이트 해야합니다. 또 여기서 이 두 값(weight 인덱스와 개수)는 쉐이더가 사용하지 않기 때문에 쉐이더로 보내지지 않는 것을 아셨으면 합니다. 매우 쉽습니다. 우리가 해야할 일은 쉐이더로 보내지 않는 것은 구조체의 끝에 놓고 아래에서 볼 수 있듯이 정점 레이아웃에 포함시키지 안흔 것입니다.

struct Vertex    //Overloaded Vertex Structure
{
    Vertex(){}
    Vertex(float x, float y, float z,
        float u, float v,
        float nx, float ny, float nz,
        float tx, float ty, float tz)
        : pos(x,y,z), texCoord(u, v), normal(nx, ny, nz),
        tangent(tx, ty, tz){}

    XMFLOAT3 pos;
    XMFLOAT2 texCoord;
    XMFLOAT3 normal;
    XMFLOAT3 tangent;
    XMFLOAT3 biTangent;

///////////////**************new**************////////////////////
    // Will not be sent to shader
    int StartWeight;
    int WeightCount;
///////////////**************new**************////////////////////
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "NORMAL",     0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
UINT numElements = ARRAYSIZE(layout);


Joint Structure

어떤 것들을 쉽게 하기위해 여러 새 구조체를 가집니다. 그것은 바로 관절, weight, 모델 subsets, 모델 입니다. 첫번째로 볼 것은 관절 구조체로 md5mesh 파일로부터 앞서 얘기했던 모든 관절 정보를 저장할 것입니다.

struct Joint
{
    std::wstring name;
    int parentID;

    XMFLOAT3 pos;
    XMFLOAT4 orientation;
};

Weight Structure

위 관절 구조체처럼 weight 구조체도 md5mesh 파일로부터 조사했던 정보를 저장할 것입니다.

struct Weight
{
    int jointID;
    float bias;
    XMFLOAT3 pos;
};

The ModelSubset Structure

이 구조체는 우리의 모델의 중요한 모든 것들을 저장할 것입니다. 모델의 각 서브셋당 구조체 하나씩 가질 것입니다. 각 서브셋에 대해 새 버텍스, 인덱스 버퍼를 어떻게 만드는지 유심히 보시기 바랍니다. 그 이유는 애니메이션을 처리할 때 모델의 모든 버텍스 버퍼를 업데이트 할거 없이 서브셋의 버텍스 버퍼만 바꾸면 됩니다. 또 위치 배열도 가집니다. 충돌 체크, 픽킹( 마우스로 클릭한 오브젝트 검출) , 위치 배열이 필요한 모든것에 대해 모든 정점 배열과 기타 텍스쳐 좌표같은 것들을 쓰기보다 매우 유용할 것입니다.

struct ModelSubset
{
    int texArrayIndex;
    int numTriangles;

    std::vector<Vertex> vertices;
    std::vector<DWORD> indices;
    std::vector<Weight> weights;

    std::vector<XMFLOAT3> positions;

    ID3D11Buffer* vertBuff; 
    ID3D11Buffer* indexBuff;
};

The Model3D Structure

이 구조체는 모델에 적용할 모든 정보를 가질 것입니다. 그냥 훑어봐도 이해하실 거라고 믿어요.

struct Model3D
{
    int numSubsets;
    int numJoints;

    std::vector<Joint> joints;
    std::vector<ModelSubset> subsets;
};


New Globals

이번 강좌에는 전역변수가 단 2개만 있습니다. 첫번째 것은 우리의 모델을 위한(제가 만든 모델은 씬에 대해 완전 커서) 월드 행렬이고 Model3D 는 모델의 정보를 저장할 것입니다.

XMMATRIX smilesWorld;
Model3D NewMD5Model;

The LoadMD5Model() Function Prototype

여기 우리의 md5 모델을 불러오고 저장할 함수의 프로토타입이 있습니다. 첫번째 파라미터는 md5 파일 이름을 가지며 두번째는 Model3D 객체 포인터이고 세번째(OBJ 파일 로딩때 했던 것 처럼) 쉐이더 리소스 뷰의 벡터(STL) 포인터이며 마지막 네번째는 텍스쳐 파일들의 이름의 벡터(STL)로 텍스쳐가 로딩되어 있는지 아닌지 확인할 수 있습니다.

bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray);

CleanUp() function

CleanUp() 함수로 가보면 각 모델의 서브셋에 대한 버텍스, 인덱스 버퍼를 해제하는 곳입니다. 루프 안을 보면 모델의 서브셋 수 만큼 돌며 매 번 해당 서브셋의 버텍스, 인덱스 버퍼를 해제합니다.

    for(int i = 0; i < NewMD5Model.numSubsets; i++)
    {
        NewMD5Model.subsets[i].indexBuff->Release();
        NewMD5Model.subsets[i].vertBuff->Release();
    }



The LoadMD5Model() Function

함수를 그냥 복사하기 원하시는 분들을 위해 저는 그냥 먼저 이 함수를 파헤치기 전에 전체 함수를 보여주고 싶었습니다.

bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray)
{
    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if(checkString == L"MD5Version")        // Get MD5 version (this function supports version 10)
            {
                /*fileIn >> checkString;
                MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> MD5Model.numJoints;        // Store number of joints
            }
            else if ( checkString == L"numMeshes" )
            {
                fileIn >> MD5Model.numSubsets;        // Store number of meshes or subsets which we will call them
            }
            else if ( checkString == L"joints" )
            {
                Joint tempJoint;

                fileIn >> checkString;                // Skip the "{"

                for(int i = 0; i < MD5Model.numJoints; i++)
                {
                    fileIn >> tempJoint.name;        // Store joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    fileIn >> tempJoint.parentID;    // Store Parent joint's ID

                    fileIn >> checkString;            // Skip the "("

                    // Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
                    fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

                    fileIn >> checkString >> checkString;    // Skip the ")" and "("

                    // Store orientation of this joint
                    fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    //tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    // Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
                    // direction the bone is facing. However, we need to turn this into a quaternion, and the way
                    // quaternions work, is the xyz values describe the axis of rotation, while the w is a value
                    // between 0 and 1 which describes the angle of rotation)
                    float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
                        - ( tempJoint.orientation.y * tempJoint.orientation.y )
                        - ( tempJoint.orientation.z * tempJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempJoint.orientation.w = -sqrtf(t);
                    }

                    std::getline(fileIn, checkString);        // Skip rest of this line

                    MD5Model.joints.push_back(tempJoint);    // Store the joint into this models joint vector
                }

                fileIn >> checkString;                    // Skip the "}"
            }
            else if ( checkString == L"mesh")
            {
                ModelSubset subset;
                int numVerts, numTris, numWeights;

                fileIn >> checkString;                    // Skip the "{"

                fileIn >> checkString;
                while ( checkString != L"}" )            // Read until '}'
                {
                    // In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
                    // Usually though, the name of a material (stored in a material library. Think back to the lesson on
                    // loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
                    // be an exercise to load the material from a material library such as obj's .mtl file, instead of
                    // just the texture like we will do here.
                    if(checkString == L"shader")        // Load the texture or material
                    {                        
                        std::wstring fileNamePath;
                        fileIn >> fileNamePath;            // Get texture's filename

                        // Take spaces into account if filename or material name has a space in it
                        if(fileNamePath[fileNamePath.size()-1] != '"')
                        {
                            wchar_t checkChar;
                            bool fileNameFound = false;
                            while(!fileNameFound)
                            {
                                checkChar = fileIn.get();

                                if(checkChar == '"')
                                    fileNameFound = true;

                                fileNamePath += checkChar;                                                                    
                            }
                        }

                        // Remove the quotation marks from texture path
                        fileNamePath.erase(0, 1);
                        fileNamePath.erase(fileNamePath.size()-1, 1);

                        //check if this texture has already been loaded
                        bool alreadyLoaded = false;
                        for(int i = 0; i < texFileNameArray.size(); ++i)
                        {
                            if(fileNamePath == texFileNameArray[i])
                            {
                                alreadyLoaded = true;
                                subset.texArrayIndex = i;
                            }
                        }

                        //if the texture is not already loaded, load it now
                        if(!alreadyLoaded)
                        {
                            ID3D11ShaderResourceView* tempMeshSRV;
                            hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                NULL, NULL, &tempMeshSRV, NULL );
                            if(SUCCEEDED(hr))
                            {
                                texFileNameArray.push_back(fileNamePath.c_str());
                                subset.texArrayIndex = shaderResourceViewArray.size();
                                shaderResourceViewArray.push_back(tempMeshSRV);
                            }
                            else
                            {
                                MessageBox(0, fileNamePath.c_str(),        //display message
                                    L"Could Not Open:", MB_OK);
                                return false;
                            }
                        }    

                        std::getline(fileIn, checkString);                // Skip rest of this line
                    }
                    else if ( checkString == L"numverts")
                    {
                        fileIn >> numVerts;                                // Store number of vertices

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numVerts; i++)
                        {
                            Vertex tempVert;

                            fileIn >> checkString                        // Skip "vert # ("
                                >> checkString
                                >> checkString;

                            fileIn >> tempVert.texCoord.x                // Store tex coords
                                >> tempVert.texCoord.y;    

                            fileIn >> checkString;                        // Skip ")"

                            fileIn >> tempVert.StartWeight;                // Index of first weight this vert will be weighted to

                            fileIn >> tempVert.WeightCount;                // Number of weights for this vertex

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.vertices.push_back(tempVert);        // Push back this vertex into subsets vertex vector
                        }
                    }
                    else if ( checkString == L"numtris")
                    {
                        fileIn >> numTris;
                        subset.numTriangles = numTris;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numTris; i++)                // Loop through each triangle
                        {
                            DWORD tempIndex;
                            fileIn >> checkString;                        // Skip "tri"
                            fileIn >> checkString;                        // Skip tri counter

                            for(int k = 0; k < 3; k++)                    // Store the 3 indices
                            {
                                fileIn >> tempIndex;
                                subset.indices.push_back(tempIndex);
                            }

                            std::getline(fileIn, checkString);            // Skip rest of this line
                        }
                    }
                    else if ( checkString == L"numweights")
                    {
                        fileIn >> numWeights;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numWeights; i++)
                        {
                            Weight tempWeight;
                            fileIn >> checkString >> checkString;        // Skip "weight #"

                            fileIn >> tempWeight.jointID;                // Store weight's joint ID

                            fileIn >> tempWeight.bias;                    // Store weight's influence over a vertex

                            fileIn >> checkString;                        // Skip "("

                            fileIn >> tempWeight.pos.x                    // Store weight's pos in joint's local space
                                >> tempWeight.pos.z
                                >> tempWeight.pos.y;

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.weights.push_back(tempWeight);        // Push back tempWeight into subsets Weight array
                        }

                    }
                    else
                        std::getline(fileIn, checkString);                // Skip anything else

                    fileIn >> checkString;                                // Skip "}"
                }

                //*** find each vertex's position using the joints and weights ***//
                for ( int i = 0; i < subset.vertices.size(); ++i )
                {
                    Vertex tempVert = subset.vertices[i];
                    tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first

                    // Sum up the joints and weights information to get vertex's position
                    for ( int j = 0; j < tempVert.WeightCount; ++j )
                    {
                        Weight tempWeight = subset.weights[tempVert.StartWeight + j];
                        Joint tempJoint = MD5Model.joints[tempWeight.jointID];

                        // Convert joint orientation and weight pos to vectors for easier computation
                        // When converting a 3d vector to a quaternion, you should put 0 for "w", and
                        // When converting a quaternion to a 3d vector, you can just ignore the "w"
                        XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                        XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                        // We will need to use the conjugate of the joint orientation quaternion
                        // To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
                        XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

                        // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                        // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                        XMFLOAT3 rotatedPoint;
                        XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                        // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                        // The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
                        tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                        tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                        tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                        // Basically what has happened above, is we have taken the weights position relative to the joints position
                        // we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
                        // the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
                        // the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
                        // so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
                        // or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
                        // must add up to 1.
                    }

                    subset.positions.push_back(tempVert.pos);            // Store the vertices position in the position vector instead of straight into the vertex vector
                    // since we can use the positions vector for certain things like collision detection or picking
                    // without having to work with the entire vertex structure.
                }

                // Put the positions into the vertices for this subset
                for(int i = 0; i < subset.vertices.size(); i++)
                {
                    subset.vertices[i].pos = subset.positions[i];
                }

                //*** Calculate vertex normals using normal averaging ***///
                std::vector<XMFLOAT3> tempNormal;

                //normalized and unnormalized normals
                XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

                //Used to get vectors (sides) from the position of the verts
                float vecX, vecY, vecZ;

                //Two edges of our triangle
                XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

                //Compute face normals
                for(int i = 0; i < subset.numTriangles; ++i)
                {
                    //Get the vector describing one edge of our triangle (edge 0,2)
                    vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;        
                    edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

                    //Get the vector describing another edge of our triangle (edge 2,1)
                    vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;        
                    edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

                    //Cross multiply the two edge vectors to get the un-normalized face normal
                    XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

                    tempNormal.push_back(unnormalized);
                }

                //Compute vertex normals (normal Averaging)
                XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                int facesUsing = 0;
                float tX, tY, tZ;    //temp axis variables

                //Go through each vertex
                for(int i = 0; i < subset.vertices.size(); ++i)
                {
                    //Check which triangles use this vertex
                    for(int j = 0; j < subset.numTriangles; ++j)
                    {
                        if(subset.indices[j*3] == i ||
                            subset.indices[(j*3)+1] == i ||
                            subset.indices[(j*3)+2] == i)
                        {
                            tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                            tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                            tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                            normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                            facesUsing++;
                        }
                    }

                    //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
                    normalSum = normalSum / facesUsing;

                    //Normalize the normalSum vector
                    normalSum = XMVector3Normalize(normalSum);

                    //Store the normal and tangent in our current vertex
                    subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
                    subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
                    subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);

                    //Clear normalSum, facesUsing for next vertex
                    normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                    facesUsing = 0;
                }

                // Create index buffer
                D3D11_BUFFER_DESC indexBufferDesc;
                ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

                indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
                indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
                indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
                indexBufferDesc.CPUAccessFlags = 0;
                indexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA iinitData;

                iinitData.pSysMem = &subset.indices[0];
                d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

                //Create Vertex Buffer
                D3D11_BUFFER_DESC vertexBufferDesc;
                ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

                vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;                            // We will be updating this buffer, so we must set as dynamic
                vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
                vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
                vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;                // Give CPU power to write to buffer
                vertexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA vertexBufferData; 

                ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
                vertexBufferData.pSysMem = &subset.vertices[0];
                hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

                // Push back the temp subset into the models subset vector
                MD5Model.subsets.push_back(subset);
            }
        }
    }
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    // display message
            L"Error", MB_OK);

        return false;
    }

    return true;
}

Opening the File

먼저 입력 filestream을 만들고 filestream으로부터 반환된 문자열을 저장할 string을 만듭니다. 그리고는 파일이 오픈됐는지 확인합니다. 안열렸으면 메시지를 띄우고 열렸으면 EOF(end of file)에 도달할때까지 도는 루프로 들어갑니다.

bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray)
{
    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
        
        ...
        
        }
    }
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    // display message
            L"Error", MB_OK);

        return false;
    }

    return true;
}

Read the Header Info

filestream에서 문자열을 받아서 어떤 문자인지 확인합니다. 파일 헤더중 하나이면 그에 맞춰 정보를 저장합니다.

            fileIn >> checkString;                    // Get next string from file

            if(checkString == L"MD5Version")        // Get MD5 version (this function supports version 10)
            {
                /*fileIn >> checkString;
                MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> MD5Model.numJoints;        // Store number of joints
            }
            else if ( checkString == L"numMeshes" )
            {
                fileIn >> MD5Model.numSubsets;        // Store number of meshes or subsets which we will call them
            }

Reading In the Joints

문자열이 "joints"이면 뒤의 라인 수(또는 중괄호("}") 를 만날때까지)는 실제 모델의 관절인 것을 알고있습니다. 모델의 관절 수 만큼 도는 루프로 각 루프마다 관절 정보를 저장합니다. 그 정보를 임시 관절(결국 관절 벡터(stl)로 밀어 넣어질)에 저장합니다. 관절 방향 사원수의 w요소를 계산해야 하는데 아래 보시면 되고 위에 설명있으니 이해하실 수 있으실 겁니다. (원치 않으시면 이해하지 않으셔도 됩니다. (응?)) 관절들 이름에 관해 설명할게 2가지 있습니다. 하나는 관절 이름에 공백이 포함되는 것입니다. 이 경우 닫는 따옴표까지 이름을 읽었는지 확실히 해야 합니다. 또 하나는 이름을 다 읽은 후 따옴표를 지워야 할 것입니다.

            else if ( checkString == L"joints" )
            {
                Joint tempJoint;

                fileIn >> checkString;                // Skip the "{"

                for(int i = 0; i < MD5Model.numJoints; i++)
                {
                    fileIn >> tempJoint.name;        // Store joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    fileIn >> tempJoint.parentID;    // Store Parent joint's ID

                    fileIn >> checkString;            // Skip the "("

                    // Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
                    fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

                    fileIn >> checkString >> checkString;    // Skip the ")" and "("

                    // Store orientation of this joint
                    fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    // Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
                    // direction the bone is facing. However, we need to turn this into a quaternion, and the way
                    // quaternions work, is the xyz values describe the axis of rotation, while the w is a value
                    // between 0 and 1 which describes the angle of rotation)
                    float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
                        - ( tempJoint.orientation.y * tempJoint.orientation.y )
                        - ( tempJoint.orientation.z * tempJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempJoint.orientation.w = -sqrtf(t);
                    }

                    std::getline(fileIn, checkString);        // Skip rest of this line

                    MD5Model.joints.push_back(tempJoint);    // Store the joint into this models joint vector
                }

                fileIn >> checkString;                    // Skip the "}"
            }

Reading In the Subset Specific Information

이제 서브셋 특정 정보를 읽어들이는 부분을 할 것입니다. 대부분은 위에서 설명했습니다. 쉐이더(md5 서브셋; hlsl 쉐이더와 헷갈리지 말 것)를 로딩할때 obj 모델 로더에서 했던것 처럼 똑같은 걸 하는데 먼저 텍스쳐가 로딩 되어있는지 확인하고 안되있으면 로딩합니다. 그리고는 쉐이더 리소스 뷰를 쉐이더 리소스 뷰 벡터에 해당 리소스 인덱스를 우리의 서브셋에 저장합니다.

            else if ( checkString == L"mesh")
            {
                ModelSubset subset;
                int numVerts, numTris, numWeights;

                fileIn >> checkString;                    // Skip the "{"

                fileIn >> checkString;
                while ( checkString != L"}" )            // Read until '}'
                {
                    // In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
                    // Usually though, the name of a material (stored in a material library. Think back to the lesson on
                    // loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
                    // be an exercise to load the material from a material library such as obj's .mtl file, instead of
                    // just the texture like we will do here.
                    if(checkString == L"shader")        // Load the texture or material
                    {                        
                        std::wstring fileNamePath;
                        fileIn >> fileNamePath;            // Get texture's filename

                        // Take spaces into account if filename or material name has a space in it
                        if(fileNamePath[fileNamePath.size()-1] != '"')
                        {
                            wchar_t checkChar;
                            bool fileNameFound = false;
                            while(!fileNameFound)
                            {
                                checkChar = fileIn.get();

                                if(checkChar == '"')
                                    fileNameFound = true;

                                fileNamePath += checkChar;                                                                    
                            }
                        }

                        // Remove the quotation marks from texture path
                        fileNamePath.erase(0, 1);
                        fileNamePath.erase(fileNamePath.size()-1, 1);

                        //check if this texture has already been loaded
                        bool alreadyLoaded = false;
                        for(int i = 0; i < texFileNameArray.size(); ++i)
                        {
                            if(fileNamePath == texFileNameArray[i])
                            {
                                alreadyLoaded = true;
                                subset.texArrayIndex = i;
                            }
                        }

                        //if the texture is not already loaded, load it now
                        if(!alreadyLoaded)
                        {
                            ID3D11ShaderResourceView* tempMeshSRV;
                            hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                NULL, NULL, &tempMeshSRV, NULL );
                            if(SUCCEEDED(hr))
                            {
                                texFileNameArray.push_back(fileNamePath.c_str());
                                subset.texArrayIndex = shaderResourceViewArray.size();
                                shaderResourceViewArray.push_back(tempMeshSRV);
                            }
                            else
                            {
                                MessageBox(0, fileNamePath.c_str(),        //display message
                                    L"Could Not Open:", MB_OK);
                                return false;
                            }
                        }    

                        std::getline(fileIn, checkString);                // Skip rest of this line
                    }
                    else if ( checkString == L"numverts")
                    {
                        fileIn >> numVerts;                                // Store number of vertices

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numVerts; i++)
                        {
                            Vertex tempVert;

                            fileIn >> checkString                        // Skip "vert # ("
                                >> checkString
                                >> checkString;

                            fileIn >> tempVert.texCoord.x                // Store tex coords
                                >> tempVert.texCoord.y;    

                            fileIn >> checkString;                        // Skip ")"

                            fileIn >> tempVert.StartWeight;                // Index of first weight this vert will be weighted to

                            fileIn >> tempVert.WeightCount;                // Number of weights for this vertex

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.vertices.push_back(tempVert);        // Push back this vertex into subsets vertex vector
                        }
                    }
                    else if ( checkString == L"numtris")
                    {
                        fileIn >> numTris;
                        subset.numTriangles = numTris;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numTris; i++)                // Loop through each triangle
                        {
                            DWORD tempIndex;
                            fileIn >> checkString;                        // Skip "tri"
                            fileIn >> checkString;                        // Skip tri counter

                            for(int k = 0; k < 3; k++)                    // Store the 3 indices
                            {
                                fileIn >> tempIndex;
                                subset.indices.push_back(tempIndex);
                            }

                            std::getline(fileIn, checkString);            // Skip rest of this line
                        }
                    }
                    else if ( checkString == L"numweights")
                    {
                        fileIn >> numWeights;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numWeights; i++)
                        {
                            Weight tempWeight;
                            fileIn >> checkString >> checkString;        // Skip "weight #"

                            fileIn >> tempWeight.jointID;                // Store weight's joint ID

                            fileIn >> tempWeight.bias;                    // Store weight's influence over a vertex

                            fileIn >> checkString;                        // Skip "("

                            fileIn >> tempWeight.pos.x                    // Store weight's pos in joint's local space
                                >> tempWeight.pos.z
                                >> tempWeight.pos.y;

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.weights.push_back(tempWeight);        // Push back tempWeight into subsets Weight array
                        }

                    }
                    else
                        std::getline(fileIn, checkString);                // Skip anything else

                    fileIn >> checkString;                                // Skip "}"
                }

Calculating the Vertex Position

이제 이번 강좌의 가장 흥미로운 부분에 왔습니다. 실제로 관절들의 위치와 방향을 가지고 정점 위치를 계산하는 부분입니다. 제가 주석을 제 능력껏 잘 달아 놓아서 어떤 동작인지 아실테지만 한번 더(위에서 이미 설명했지만) 여기에 설명하려 합니다. 첫번째로 할 일은 서브셋의 각 정점만큼 루프를 도는 것입니다. 서브셋의 정점을 임시 정점 변수에 저장하고 위치를 원점으로 설정합니다. 그리고는 해당 정점이 붙여진 각 weight에 대한 새로운 루프로 들어갑니다. weight를 임시 weight 변수에 저장하고 그 weight가 붙여진 관절을 임시 관절 변수에 저장합니다. 그리곤 사원수(XMVECTORS는 다 사용하지 않더라도 항상 4개의 구성요소를 가집니다.)를 생성하는데 하나는 관절 방향, 또 하나는 weight 위치(weight 위치는 3D 벡터여서 "w" 요소는 "0"으로 설정하면 됩니다.), 마지막 하나는 관절 방향의 켤레 사원수입니다. 이제 또 다른 변수를 생성하는데 3D 벡터로 weight의 최종 위치를 저장할 것입니다. 그리고는 계산을 수행하는데 "관절 공간"에서 weight의 위치를 결정합니다. 무슨 말이냐면 관절은 "모델 공간"(weight 위치는 관절 위치에 관련있지 모델 공간의 원점과는 관련없다.)상의 정확한 위치에 있지 않더라도 실제로 원점을 중심으로 회전되고 있다는 얘기입니다. (음?) 그래서 weight를 관절 공간의 관절을 중심으로 회전시켰지만 weight 위치를 모델 공간으로 변환시켜야 합니다. 이건 엄청 쉽습니다. 우리가 할 일은 관절 위치를 weight 최종 위치에 더하면 되기 때문입니다. 이 최종 최종 최종 위치를 저장하기 전에 할 일이 하나 더 있는데 해당 정점에 weight 가중치를 고려하는 것입니다. 최종 최종 최종 위치에 가중치를 곱하고 그 결과를 정점에 더하면(어차피 현재 정점은 원점) 완전 최종 위치가 됩니다. 다 이해하셨을 거라고 믿습니다. 그렇지 않다면 제 생각에 여러분의 온 신경과 집중을 코드를 따라가는데 쏟는다면 사실 상당히 간단하다는 것을(사원수 곱셈 따위의 수학적 디테일은 눈감아 주세요 찡긋) 보시게 될 것입니다. 추신: "여러분이 충분히 열심히 보면 이해할 것입니다."라고 말하는 것으로 제가 아마도 여러분의 지성을 과소평가하지 말아야 합니다.(하찮게 여겨 미안하다?) 그래서 이해하지 못한 부분이 있다면 저에게 질문을 주시면 최대한 아는내에서 돕도록 하겠습니다.

                //*** find each vertex's position using the joints and weights ***//
                for ( int i = 0; i < subset.vertices.size(); ++i )
                {
                    Vertex tempVert = subset.vertices[i];
                    tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first

                    // Sum up the joints and weights information to get vertex's position
                    for ( int j = 0; j < tempVert.WeightCount; ++j )
                    {
                        Weight tempWeight = subset.weights[tempVert.StartWeight + j];
                        Joint tempJoint = MD5Model.joints[tempWeight.jointID];

                        // Convert joint orientation and weight pos to vectors for easier computation
                        // When converting a 3d vector to a quaternion, you should put 0 for "w", and
                        // When converting a quaternion to a 3d vector, you can just ignore the "w"
                        XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                        XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                        // We will need to use the conjugate of the joint orientation quaternion
                        // To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
                        XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

                        // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                        // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                        XMFLOAT3 rotatedPoint;
                        XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                        // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                        // The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
                        tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                        tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                        tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                        // Basically what has happened above, is we have taken the weights position relative to the joints position
                        // we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
                        // the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
                        // the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
                        // so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
                        // or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
                        // must add up to 1.
                    }

                    subset.positions.push_back(tempVert.pos);            // Store the vertices position in the position vector instead of straight into the vertex vector
                    // since we can use the positions vector for certain things like collision detection or picking
                    // without having to work with the entire vertex structure.
                }

                // Put the positions into the vertices for this subset
                for(int i = 0; i < subset.vertices.size(); i++)
                {
                    subset.vertices[i].pos = subset.positions[i];
                }

Calculating the Vertex Normal

이 섹션은 OBJ 로더에서 법선 벡터 계산했을때와 거의 판박이니 모르시겠다면 obj 로더 강좌를 확인하시기 바랍니다. 이 메소드는 또 정점에 대한 법선 매핑에 대한 tangent와 bitangent를 구하는데 사용됩니다.

                //*** Calculate vertex normals using normal averaging ***///
                std::vector<XMFLOAT3> tempNormal;

                //normalized and unnormalized normals
                XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

                //Used to get vectors (sides) from the position of the verts
                float vecX, vecY, vecZ;

                //Two edges of our triangle
                XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

                //Compute face normals
                for(int i = 0; i < subset.numTriangles; ++i)
                {
                    //Get the vector describing one edge of our triangle (edge 0,2)
                    vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;        
                    edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

                    //Get the vector describing another edge of our triangle (edge 2,1)
                    vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;        
                    edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

                    //Cross multiply the two edge vectors to get the un-normalized face normal
                    XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

                    tempNormal.push_back(unnormalized);
                }

                //Compute vertex normals (normal Averaging)
                XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                int facesUsing = 0;
                float tX, tY, tZ;    //temp axis variables

                //Go through each vertex
                for(int i = 0; i < subset.vertices.size(); ++i)
                {
                    //Check which triangles use this vertex
                    for(int j = 0; j < subset.numTriangles; ++j)
                    {
                        if(subset.indices[j*3] == i ||
                            subset.indices[(j*3)+1] == i ||
                            subset.indices[(j*3)+2] == i)
                        {
                            tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                            tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                            tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                            normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                            facesUsing++;
                        }
                    }

                    //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
                    normalSum = normalSum / facesUsing;

                    //Normalize the normalSum vector
                    normalSum = XMVector3Normalize(normalSum);

                    //Store the normal and tangent in our current vertex
                    subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
                    subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
                    subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);

                    //Clear normalSum, facesUsing for next vertex
                    normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                    facesUsing = 0;
                }

Creating the Index and Vertex Buffers

우리는 드디어 MD5 로더의 마지막에 왔습니다. 모델의 각 서브셋에 대한 버텍스 인덱스 버퍼를 만들 것입니다. 이전 강좌들에서 많이 했으므로 버텍스 버퍼를 제외하고 설명이 필요없습니다. 어떻게 버텍스 버퍼를 동적 버퍼로 cpu 쓰기가 가능하도록(동적 버퍼 설정 시 CPU와 GPU가 공유하는 AGP 메모리를 사용하게 된다. 그래픽 메모리로 옮기는 과정때문에 정적 버퍼보다 빠르지는 않지만 버퍼 갱신 속도가 매우 빨라 잘 안 바뀌는 맵, 지형 보다는 자주 바뀌는 데이터에 사용) 설정하는지 주의하시기 바랍니다. 그 이유는 (다음강좌) 우리의 씬 내내 애니메이션을 하기 위해 버퍼를 갱신해주어야 하기 때문입니다. 더 자세한 것은 다음 강좌에서 더 살펴볼 것입니다. 다 끝나면 임시 서브셋 변수를 우리의 모델 객체의 서브셋 배열에 푸쉬합니다.

                // Create index buffer
                D3D11_BUFFER_DESC indexBufferDesc;
                ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

                indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
                indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
                indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
                indexBufferDesc.CPUAccessFlags = 0;
                indexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA iinitData;

                iinitData.pSysMem = &subset.indices[0];
                d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

                //Create Vertex Buffer
                D3D11_BUFFER_DESC vertexBufferDesc;
                ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

                vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;                            // We will be updating this buffer, so we must set as dynamic
                vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
                vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
                vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;                // Give CPU power to write to buffer
                vertexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA vertexBufferData; 

                ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
                vertexBufferData.pSysMem = &subset.vertices[0];
                hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

                // Push back the temp subset into the models subset vector
                MD5Model.subsets.push_back(subset);
            }

Calling the LoadMD5Model() Function

이제 initscene() 함수로 가보면 우리의 모델을 로딩하는 함수를 호출합니다. 제대로 로딩됐는지 확실히 하기 위해 확인하여 그렇지 않다면 false를 리턴합니다.

    if(!LoadMD5Model(L"boy.md5mesh", NewMD5Model, meshSRV, textureNameArray))
        return false;

Updating the Models World Space Matrix

제가 만든 모델은 씬에 대해 정말 너무 커서 겁나 겁나 줄여야 합니다. 또 모델의 중심은 기본으로 우리 씬의 지면 아래 위치하고 있어 위로 조금 올려야 합니다.

    Scale = XMMatrixScaling( 0.04f, 0.04f, 0.04f );            // The model is a bit too large for our scene, so make it smaller
    Translation = XMMatrixTranslation( 0.0f, 3.0f, 0.0f );
    smilesWorld = Scale * Translation;

Drawing the Model

여기 drawscene() 함수안에 우리의 모델을 그릴 것입니다. 각 모델 서브셋들을 돌고 해당 서브셋의 버텍스 인덱스 버퍼를 IA에 바인딩할 것입니다. 그리고는 WVP(월드 뷰 프로젝션 행렬)와 상수 버퍼 것들을 쉐이더에 보내고 마지막으로 서브셋을 그립니다. 모든 것은 이전 강좌에 다 설명되어 있습니다. (이 테크닉은 OBJ 로더 강좌에서 분명히 설명되어 있습니다.)

    ///***Draw MD5 Model***///
    for(int i = 0; i < NewMD5Model.numSubsets; i ++)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( NewMD5Model.subsets[i].indexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &NewMD5Model.subsets[i].vertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = smilesWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(smilesWorld);    
        cbPerObj.hasTexture = true;        // We'll assume all md5 subsets have textures
        cbPerObj.hasNormMap = false;    // We'll also assume md5 models have no normal map (easy to change later though)
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[NewMD5Model.subsets[i].texArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        d3d11DevCon->DrawIndexed( NewMD5Model.subsets[i].indices.size(), 0, 0 );

    }

이게 이 강좌의 다 입니다. 이제 뼈대 구조체를 가지는 모델을 로딩할 수 있으며 우리의 모델을 움직일 준비를 마쳤습니다! (항상 그렇듯이) 어떻게든 이 강좌가 도움이 되었길 바랍니다!



Exercise:

1. material library를 생성하고 MD5 로더 함수를 material library를 사용하도록 바꾸세요. 2. 사원수와 친해지세요. 회전행렬 대신 회전에 대해 사원수를 사용하도록 해보세요. 3. 질문 남기세요! 4. 급나 좋은 날 되세요! 여기 최종 코드 입니다: main.cpp

//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
#pragma comment (lib, "D3D10_1.lib")
#pragma comment (lib, "DXGI.lib")
#pragma comment (lib, "D2D1.lib")
#pragma comment (lib, "dwrite.lib")
#pragma comment (lib, "dinput8.lib")
#pragma comment (lib, "dxguid.lib")

#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <xnamath.h>
#include <D3D10_1.h>
#include <DXGI.h>
#include <D2D1.h>
#include <sstream>
#include <dwrite.h>
#include <dinput.h>
#include <vector>
#include <fstream>
#include <istream>

//Global Declarations - Interfaces//
IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;
ID3D11DepthStencilView* depthStencilView;
ID3D11Texture2D* depthStencilBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D11PixelShader* D2D_PS;
ID3D10Blob* D2D_PS_Buffer;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;
ID3D11Buffer* cbPerObjectBuffer;
ID3D11BlendState* d2dTransparency;
ID3D11RasterizerState* CCWcullMode;
ID3D11RasterizerState* CWcullMode;
ID3D11SamplerState* CubesTexSamplerState;
ID3D11Buffer* cbPerFrameBuffer;

ID3D10Device1 *d3d101Device;    
IDXGIKeyedMutex *keyedMutex11;
IDXGIKeyedMutex *keyedMutex10;    
ID2D1RenderTarget *D2DRenderTarget;    
ID2D1SolidColorBrush *Brush;
ID3D11Texture2D *BackBuffer11;
ID3D11Texture2D *sharedTex11;    
ID3D11Buffer *d2dVertBuffer;
ID3D11Buffer *d2dIndexBuffer;
ID3D11ShaderResourceView *d2dTexture;
IDWriteFactory *DWriteFactory;
IDWriteTextFormat *TextFormat;

IDirectInputDevice8* DIKeyboard;
IDirectInputDevice8* DIMouse;

ID3D11Buffer* sphereIndexBuffer;
ID3D11Buffer* sphereVertBuffer;

ID3D11VertexShader* SKYMAP_VS;
ID3D11PixelShader* SKYMAP_PS;
ID3D10Blob* SKYMAP_VS_Buffer;
ID3D10Blob* SKYMAP_PS_Buffer;

ID3D11ShaderResourceView* smrv;

ID3D11DepthStencilState* DSLessEqual;
ID3D11RasterizerState* RSCullNone;

ID3D11BlendState* Transparency;
//Mesh variables. Each loaded mesh will need its own set of these
ID3D11Buffer* meshVertBuff;
ID3D11Buffer* meshIndexBuff;
XMMATRIX meshWorld;
int meshSubsets = 0;
std::vector<int> meshSubsetIndexStart;
std::vector<int> meshSubsetTexture;

//Textures and material variables, used for all mesh's loaded
std::vector<ID3D11ShaderResourceView*> meshSRV;
std::vector<std::wstring> textureNameArray;

std::wstring printText;

//Global Declarations - Others//
LPCTSTR WndClassName = L"firstwindow";
HWND hwnd = NULL;
HRESULT hr;

int Width  = 1920;
int Height = 1200;

DIMOUSESTATE mouseLastState;
LPDIRECTINPUT8 DirectInput;

float rotx = 0;
float rotz = 0;
float scaleX = 1.0f;
float scaleY = 1.0f;

XMMATRIX Rotationx;
XMMATRIX Rotationz;
XMMATRIX Rotationy;

XMMATRIX WVP;
XMMATRIX camView;
XMMATRIX camProjection;

XMMATRIX d2dWorld;

XMVECTOR camPosition;
XMVECTOR camTarget;
XMVECTOR camUp;
XMVECTOR DefaultForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR DefaultRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);
XMVECTOR camForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR camRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);

XMMATRIX camRotationMatrix;

float moveLeftRight = 0.0f;
float moveBackForward = 0.0f;

float camYaw = 0.0f;
float camPitch = 0.0f;

int NumSphereVertices;
int NumSphereFaces;

XMMATRIX sphereWorld;

XMMATRIX Rotation;
XMMATRIX Scale;
XMMATRIX Translation;

float rot = 0.01f;

double countsPerSecond = 0.0;
__int64 CounterStart = 0;

int frameCount = 0;
int fps = 0;

__int64 frameTimeOld = 0;
double frameTime;

//Function Prototypes//
bool InitializeDirect3d11App(HINSTANCE hInstance);
void CleanUp();
bool InitScene();
void DrawScene();
bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter);
void InitD2DScreenTexture();
void UpdateScene(double time);

void UpdateCamera();

void RenderText(std::wstring text, int inInt);

void StartTimer();
double GetTime();
double GetFrameTime();

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed);
int messageloop();

bool InitDirectInput(HINSTANCE hInstance);
void DetectInput(double time);

void CreateSphere(int LatLines, int LongLines);

LRESULT CALLBACK WndProc(HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam);

//Create effects constant buffer's structure//
struct cbPerObject
{
    XMMATRIX  WVP;
    XMMATRIX World;

    //These will be used for the pixel shader
    XMFLOAT4 difColor;
    BOOL hasTexture;
    //Because of HLSL structure packing, we will use windows BOOL
    //instead of bool because HLSL packs things into 4 bytes, and
    //bool is only one byte, where BOOL is 4 bytes
    BOOL hasNormMap;
};

cbPerObject cbPerObj;

//Create material structure
struct SurfaceMaterial
{
    std::wstring matName;
    XMFLOAT4 difColor;
    int texArrayIndex;
    int normMapTexArrayIndex;
    bool hasNormMap;
    bool hasTexture;
    bool transparent;
};

std::vector<SurfaceMaterial> material;

//Define LoadObjModel function after we create surfaceMaterial structure
bool LoadObjModel(std::wstring filename,            //.obj filename
    ID3D11Buffer** vertBuff,                    //mesh vertex buffer
    ID3D11Buffer** indexBuff,                    //mesh index buffer
    std::vector<int>& subsetIndexStart,            //start index of each subset
    std::vector<int>& subsetMaterialArray,        //index value of material for each subset
    std::vector<SurfaceMaterial>& material,        //vector of material structures
    int& subsetCount,                            //Number of subsets in mesh
    bool isRHCoordSys,                            //true if model was created in right hand coord system
    bool computeNormals);                        //true to compute the normals, false to use the files normals

struct Light
{
    Light()
    {
        ZeroMemory(this, sizeof(Light));
    }
    XMFLOAT3 pos;
    float range;
    XMFLOAT3 dir;
    float cone;
    XMFLOAT3 att;
    float pad2;
    XMFLOAT4 ambient;
    XMFLOAT4 diffuse;
};

Light light;

struct cbPerFrame
{
    Light  light;
};

cbPerFrame constbuffPerFrame;

struct Vertex    //Overloaded Vertex Structure
{
    Vertex(){}
    Vertex(float x, float y, float z,
        float u, float v,
        float nx, float ny, float nz,
        float tx, float ty, float tz)
        : pos(x,y,z), texCoord(u, v), normal(nx, ny, nz),
        tangent(tx, ty, tz){}

    XMFLOAT3 pos;
    XMFLOAT2 texCoord;
    XMFLOAT3 normal;
    XMFLOAT3 tangent;
    XMFLOAT3 biTangent;

///////////////**************new**************////////////////////
    // Will not be sent to shader
    int StartWeight;
    int WeightCount;
///////////////**************new**************////////////////////
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
    { "NORMAL",     0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
UINT numElements = ARRAYSIZE(layout);

///////////////**************new**************////////////////////
struct Joint
{
    std::wstring name;
    int parentID;

    XMFLOAT3 pos;
    XMFLOAT4 orientation;
};

struct Weight
{
    int jointID;
    float bias;
    XMFLOAT3 pos;
};

struct ModelSubset
{
    int texArrayIndex;
    int numTriangles;

    std::vector<Vertex> vertices;
    std::vector<DWORD> indices;
    std::vector<Weight> weights;

    std::vector<XMFLOAT3> positions;

    ID3D11Buffer* vertBuff; 
    ID3D11Buffer* indexBuff;
};

struct Model3D
{
    int numSubsets;
    int numJoints;

    std::vector<Joint> joints;
    std::vector<ModelSubset> subsets;
};

XMMATRIX smilesWorld;
Model3D NewMD5Model;

//LoadMD5Model() function prototype
bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray);
///////////////**************new**************////////////////////

int WINAPI WinMain(HINSTANCE hInstance,    //Main windows function
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine,
    int nShowCmd)
{

    if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true))
    {
        MessageBox(0, L"Window Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitializeDirect3d11App(hInstance))    //Initialize Direct3D
    {
        MessageBox(0, L"Direct3D Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }


    if(!InitScene())    //Initialize our scene
    {
        MessageBox(0, L"Scene Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitDirectInput(hInstance))
    {
        MessageBox(0, L"Direct Input Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    messageloop();

    CleanUp();    

    return 0;
}

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed)
{
    typedef struct _WNDCLASS {
        UINT cbSize;
        UINT style;
        WNDPROC lpfnWndProc;
        int cbClsExtra;
        int cbWndExtra;
        HANDLE hInstance;
        HICON hIcon;
        HCURSOR hCursor;
        HBRUSH hbrBackground;
        LPCTSTR lpszMenuName;
        LPCTSTR lpszClassName;
    } WNDCLASS;

    WNDCLASSEX wc;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = WndClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Error registering class",    
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    hwnd = CreateWindowEx(
        NULL,
        WndClassName,
        L"Lesson 4 - Begin Drawing",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        width, height,
        NULL,
        NULL,
        hInstance,
        NULL
        );

    if (!hwnd)
    {
        MessageBox(NULL, L"Error creating window",
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    ShowWindow(hwnd, ShowWnd);
    UpdateWindow(hwnd);

    return true;
}

bool InitializeDirect3d11App(HINSTANCE hInstance)
{    
    //Describe our SwapChain Buffer
    DXGI_MODE_DESC bufferDesc;

    ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));

    bufferDesc.Width = Width;
    bufferDesc.Height = Height;
    bufferDesc.RefreshRate.Numerator = 60;
    bufferDesc.RefreshRate.Denominator = 1;
    bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    //Describe our SwapChain
    DXGI_SWAP_CHAIN_DESC swapChainDesc; 

    ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

    swapChainDesc.BufferDesc = bufferDesc;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 1;
    swapChainDesc.OutputWindow = hwnd; 
    ///////////////**************new**************////////////////////
    swapChainDesc.Windowed = true; 
    ///////////////**************new**************////////////////////
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // Create DXGI factory to enumerate adapters///////////////////////////////////////////////////////////////////////////
    IDXGIFactory1 *DXGIFactory;

    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory);    

    // Use the first adapter    
    IDXGIAdapter1 *Adapter;

    hr = DXGIFactory->EnumAdapters1(0, &Adapter);

    DXGIFactory->Release();    

    //Create our Direct3D 11 Device and SwapChain//////////////////////////////////////////////////////////////////////////
    hr = D3D11CreateDeviceAndSwapChain(Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        NULL, NULL,    D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);

    //Initialize Direct2D, Direct3D 10.1, DirectWrite
    InitD2D_D3D101_DWrite(Adapter);

    //Release the Adapter interface
    Adapter->Release();

    //Create our BackBuffer and Render Target
    hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer11 );
    hr = d3d11Device->CreateRenderTargetView( BackBuffer11, NULL, &renderTargetView );

    //Describe our Depth/Stencil Buffer
    D3D11_TEXTURE2D_DESC depthStencilDesc;

    depthStencilDesc.Width     = Width;
    depthStencilDesc.Height    = Height;
    depthStencilDesc.MipLevels = 1;
    depthStencilDesc.ArraySize = 1;
    depthStencilDesc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilDesc.SampleDesc.Count   = 1;
    depthStencilDesc.SampleDesc.Quality = 0;
    depthStencilDesc.Usage          = D3D11_USAGE_DEFAULT;
    depthStencilDesc.BindFlags      = D3D11_BIND_DEPTH_STENCIL;
    depthStencilDesc.CPUAccessFlags = 0; 
    depthStencilDesc.MiscFlags      = 0;

    //Create the Depth/Stencil View
    d3d11Device->CreateTexture2D(&depthStencilDesc, NULL, &depthStencilBuffer);
    d3d11Device->CreateDepthStencilView(depthStencilBuffer, NULL, &depthStencilView);

    return true;
}

bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter)
{
    //Create our Direc3D 10.1 Device///////////////////////////////////////////////////////////////////////////////////////
    hr = D3D10CreateDevice1(Adapter, D3D10_DRIVER_TYPE_HARDWARE, NULL,D3D10_CREATE_DEVICE_BGRA_SUPPORT,
        D3D10_FEATURE_LEVEL_9_3, D3D10_1_SDK_VERSION, &d3d101Device    );    

    //Create Shared Texture that Direct3D 10.1 will render on//////////////////////////////////////////////////////////////
    D3D11_TEXTURE2D_DESC sharedTexDesc;    

    ZeroMemory(&sharedTexDesc, sizeof(sharedTexDesc));

    sharedTexDesc.Width = Width;
    sharedTexDesc.Height = Height;    
    sharedTexDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    sharedTexDesc.MipLevels = 1;    
    sharedTexDesc.ArraySize = 1;
    sharedTexDesc.SampleDesc.Count = 1;
    sharedTexDesc.Usage = D3D11_USAGE_DEFAULT;
    sharedTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;    
    sharedTexDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;    

    hr = d3d11Device->CreateTexture2D(&sharedTexDesc, NULL, &sharedTex11);    

    // Get the keyed mutex for the shared texture (for D3D11)///////////////////////////////////////////////////////////////
    hr = sharedTex11->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex11);    

    // Get the shared handle needed to open the shared texture in D3D10.1///////////////////////////////////////////////////
    IDXGIResource *sharedResource10;
    HANDLE sharedHandle10;    

    hr = sharedTex11->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedResource10);

    hr = sharedResource10->GetSharedHandle(&sharedHandle10);    

    sharedResource10->Release();

    // Open the surface for the shared texture in D3D10.1///////////////////////////////////////////////////////////////////
    IDXGISurface1 *sharedSurface10;    

    hr = d3d101Device->OpenSharedResource(sharedHandle10, __uuidof(IDXGISurface1), (void**)(&sharedSurface10));

    hr = sharedSurface10->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex10);    

    // Create D2D factory///////////////////////////////////////////////////////////////////////////////////////////////////
    ID2D1Factory *D2DFactory;    
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), (void**)&D2DFactory);    

    D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties;

    ZeroMemory(&renderTargetProperties, sizeof(renderTargetProperties));

    renderTargetProperties.type = D2D1_RENDER_TARGET_TYPE_HARDWARE;
    renderTargetProperties.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED);    

    hr = D2DFactory->CreateDxgiSurfaceRenderTarget(sharedSurface10, &renderTargetProperties, &D2DRenderTarget);

    sharedSurface10->Release();
    D2DFactory->Release();    

    // Create a solid color brush to draw something with        
    hr = D2DRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &Brush);

    //DirectWrite///////////////////////////////////////////////////////////////////////////////////////////////////////////
    hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&DWriteFactory));

    hr = DWriteFactory->CreateTextFormat(
        L"Script",
        NULL,
        DWRITE_FONT_WEIGHT_REGULAR,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        24.0f,
        L"en-us",
        &TextFormat
        );

    hr = TextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
    hr = TextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);

    d3d101Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);    
    return true;
}

bool InitDirectInput(HINSTANCE hInstance)
{
    hr = DirectInput8Create(hInstance,
        DIRECTINPUT_VERSION,
        IID_IDirectInput8,
        (void**)&DirectInput,
        NULL); 

    hr = DirectInput->CreateDevice(GUID_SysKeyboard,
        &DIKeyboard,
        NULL);

    hr = DirectInput->CreateDevice(GUID_SysMouse,
        &DIMouse,
        NULL);

    hr = DIKeyboard->SetDataFormat(&c_dfDIKeyboard);
    hr = DIKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

    hr = DIMouse->SetDataFormat(&c_dfDIMouse);
    hr = DIMouse->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_NOWINKEY | DISCL_FOREGROUND);

    return true;
}

void UpdateCamera()
{
    camRotationMatrix = XMMatrixRotationRollPitchYaw(camPitch, camYaw, 0);
    camTarget = XMVector3TransformCoord(DefaultForward, camRotationMatrix );
    camTarget = XMVector3Normalize(camTarget);

    XMMATRIX RotateYTempMatrix;
    RotateYTempMatrix = XMMatrixRotationY(camYaw);

    // Walk
    //camRight = XMVector3TransformCoord(DefaultRight, RotateYTempMatrix);
    //camUp = XMVector3TransformCoord(camUp, RotateYTempMatrix);
    //camForward = XMVector3TransformCoord(DefaultForward, RotateYTempMatrix);

    // Free Cam
    camRight = XMVector3TransformCoord(DefaultRight, camRotationMatrix);
    camForward = XMVector3TransformCoord(DefaultForward, camRotationMatrix);
    camUp = XMVector3Cross(camForward, camRight);

    camPosition += moveLeftRight*camRight;
    camPosition += moveBackForward*camForward;

    moveLeftRight = 0.0f;
    moveBackForward = 0.0f;

    camTarget = camPosition + camTarget;    

    camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );
}

void DetectInput(double time)
{
    DIMOUSESTATE mouseCurrState;

    BYTE keyboardState[256];

    DIKeyboard->Acquire();
    DIMouse->Acquire();

    DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState);

    DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState);

    if(keyboardState[DIK_ESCAPE] & 0x80)
        PostMessage(hwnd, WM_DESTROY, 0, 0);

    float speed = 10.0f * time;

    if(keyboardState[DIK_A] & 0x80)
    {
        moveLeftRight -= speed;
    }
    if(keyboardState[DIK_D] & 0x80)
    {
        moveLeftRight += speed;
    }
    if(keyboardState[DIK_W] & 0x80)
    {
        moveBackForward += speed;
    }
    if(keyboardState[DIK_S] & 0x80)
    {
        moveBackForward -= speed;
    }
    if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY))
    {
        camYaw += mouseLastState.lX * 0.001f;

        camPitch += mouseCurrState.lY * 0.001f;

        mouseLastState = mouseCurrState;
    }

    UpdateCamera();

    return;
}


void CleanUp()
{
    SwapChain->SetFullscreenState(false, NULL);
    PostMessage(hwnd, WM_DESTROY, 0, 0);

    //Release the COM Objects we created
    SwapChain->Release();
    d3d11Device->Release();
    d3d11DevCon->Release();
    renderTargetView->Release();
    VS->Release();
    PS->Release();
    VS_Buffer->Release();
    PS_Buffer->Release();
    vertLayout->Release();
    depthStencilView->Release();
    depthStencilBuffer->Release();
    cbPerObjectBuffer->Release();
    Transparency->Release();
    CCWcullMode->Release();
    CWcullMode->Release();

    d3d101Device->Release();
    keyedMutex11->Release();
    keyedMutex10->Release();
    D2DRenderTarget->Release();    
    Brush->Release();
    BackBuffer11->Release();
    sharedTex11->Release();
    DWriteFactory->Release();
    TextFormat->Release();
    d2dTexture->Release();

    cbPerFrameBuffer->Release();

    DIKeyboard->Unacquire();
    DIMouse->Unacquire();
    DirectInput->Release();

    sphereIndexBuffer->Release();
    sphereVertBuffer->Release();

    SKYMAP_VS->Release();
    SKYMAP_PS->Release();
    SKYMAP_VS_Buffer->Release();
    SKYMAP_PS_Buffer->Release();

    smrv->Release();

    DSLessEqual->Release();
    RSCullNone->Release();

    meshVertBuff->Release();
    meshIndexBuff->Release();

    ///////////////**************new**************////////////////////
    for(int i = 0; i < NewMD5Model.numSubsets; i++)
    {
        NewMD5Model.subsets[i].indexBuff->Release();
        NewMD5Model.subsets[i].vertBuff->Release();
    }
    ///////////////**************new**************////////////////////
}

///////////////**************new**************////////////////////
bool LoadMD5Model(std::wstring filename,
    Model3D& MD5Model,
    std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
    std::vector<std::wstring> texFileNameArray)
{
    std::wifstream fileIn (filename.c_str());        // Open file

    std::wstring checkString;                        // Stores the next string from our file

    if(fileIn)                                        // Check if the file was opened
    {
        while(fileIn)                                // Loop until the end of the file is reached
        {    
            fileIn >> checkString;                    // Get next string from file

            if(checkString == L"MD5Version")        // Get MD5 version (this function supports version 10)
            {
                /*fileIn >> checkString;
                MessageBox(0, checkString.c_str(),    //display message
                L"MD5Version", MB_OK);*/
            }
            else if ( checkString == L"commandline" )
            {
                std::getline(fileIn, checkString);    // Ignore the rest of this line
            }
            else if ( checkString == L"numJoints" )
            {
                fileIn >> MD5Model.numJoints;        // Store number of joints
            }
            else if ( checkString == L"numMeshes" )
            {
                fileIn >> MD5Model.numSubsets;        // Store number of meshes or subsets which we will call them
            }
            else if ( checkString == L"joints" )
            {
                Joint tempJoint;

                fileIn >> checkString;                // Skip the "{"

                for(int i = 0; i < MD5Model.numJoints; i++)
                {
                    fileIn >> tempJoint.name;        // Store joints name
                    // Sometimes the names might contain spaces. If that is the case, we need to continue
                    // to read the name until we get to the closing " (quotation marks)
                    if(tempJoint.name[tempJoint.name.size()-1] != '"')
                    {
                        wchar_t checkChar;
                        bool jointNameFound = false;
                        while(!jointNameFound)
                        {
                            checkChar = fileIn.get();

                            if(checkChar == '"')
                                jointNameFound = true;        

                            tempJoint.name += checkChar;                                                            
                        }
                    }

                    fileIn >> tempJoint.parentID;    // Store Parent joint's ID

                    fileIn >> checkString;            // Skip the "("

                    // Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
                    fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

                    fileIn >> checkString >> checkString;    // Skip the ")" and "("

                    // Store orientation of this joint
                    fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

                    // Remove the quotation marks from joints name
                    tempJoint.name.erase(0, 1);
                    tempJoint.name.erase(tempJoint.name.size()-1, 1);

                    // Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
                    // direction the bone is facing. However, we need to turn this into a quaternion, and the way
                    // quaternions work, is the xyz values describe the axis of rotation, while the w is a value
                    // between 0 and 1 which describes the angle of rotation)
                    float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
                        - ( tempJoint.orientation.y * tempJoint.orientation.y )
                        - ( tempJoint.orientation.z * tempJoint.orientation.z );
                    if ( t < 0.0f )
                    {
                        tempJoint.orientation.w = 0.0f;
                    }
                    else
                    {
                        tempJoint.orientation.w = -sqrtf(t);
                    }

                    std::getline(fileIn, checkString);        // Skip rest of this line

                    MD5Model.joints.push_back(tempJoint);    // Store the joint into this models joint vector
                }

                fileIn >> checkString;                    // Skip the "}"
            }
            else if ( checkString == L"mesh")
            {
                ModelSubset subset;
                int numVerts, numTris, numWeights;

                fileIn >> checkString;                    // Skip the "{"

                fileIn >> checkString;
                while ( checkString != L"}" )            // Read until '}'
                {
                    // In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
                    // Usually though, the name of a material (stored in a material library. Think back to the lesson on
                    // loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
                    // be an exercise to load the material from a material library such as obj's .mtl file, instead of
                    // just the texture like we will do here.
                    if(checkString == L"shader")        // Load the texture or material
                    {                        
                        std::wstring fileNamePath;
                        fileIn >> fileNamePath;            // Get texture's filename

                        // Take spaces into account if filename or material name has a space in it
                        if(fileNamePath[fileNamePath.size()-1] != '"')
                        {
                            wchar_t checkChar;
                            bool fileNameFound = false;
                            while(!fileNameFound)
                            {
                                checkChar = fileIn.get();

                                if(checkChar == '"')
                                    fileNameFound = true;

                                fileNamePath += checkChar;                                                                    
                            }
                        }

                        // Remove the quotation marks from texture path
                        fileNamePath.erase(0, 1);
                        fileNamePath.erase(fileNamePath.size()-1, 1);

                        //check if this texture has already been loaded
                        bool alreadyLoaded = false;
                        for(int i = 0; i < texFileNameArray.size(); ++i)
                        {
                            if(fileNamePath == texFileNameArray[i])
                            {
                                alreadyLoaded = true;
                                subset.texArrayIndex = i;
                            }
                        }

                        //if the texture is not already loaded, load it now
                        if(!alreadyLoaded)
                        {
                            ID3D11ShaderResourceView* tempMeshSRV;
                            hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                NULL, NULL, &tempMeshSRV, NULL );
                            if(SUCCEEDED(hr))
                            {
                                texFileNameArray.push_back(fileNamePath.c_str());
                                subset.texArrayIndex = shaderResourceViewArray.size();
                                shaderResourceViewArray.push_back(tempMeshSRV);
                            }
                            else
                            {
                                MessageBox(0, fileNamePath.c_str(),        //display message
                                    L"Could Not Open:", MB_OK);
                                return false;
                            }
                        }    

                        std::getline(fileIn, checkString);                // Skip rest of this line
                    }
                    else if ( checkString == L"numverts")
                    {
                        fileIn >> numVerts;                                // Store number of vertices

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numVerts; i++)
                        {
                            Vertex tempVert;

                            fileIn >> checkString                        // Skip "vert # ("
                                >> checkString
                                >> checkString;

                            fileIn >> tempVert.texCoord.x                // Store tex coords
                                >> tempVert.texCoord.y;    

                            fileIn >> checkString;                        // Skip ")"

                            fileIn >> tempVert.StartWeight;                // Index of first weight this vert will be weighted to

                            fileIn >> tempVert.WeightCount;                // Number of weights for this vertex

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.vertices.push_back(tempVert);        // Push back this vertex into subsets vertex vector
                        }
                    }
                    else if ( checkString == L"numtris")
                    {
                        fileIn >> numTris;
                        subset.numTriangles = numTris;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numTris; i++)                // Loop through each triangle
                        {
                            DWORD tempIndex;
                            fileIn >> checkString;                        // Skip "tri"
                            fileIn >> checkString;                        // Skip tri counter

                            for(int k = 0; k < 3; k++)                    // Store the 3 indices
                            {
                                fileIn >> tempIndex;
                                subset.indices.push_back(tempIndex);
                            }

                            std::getline(fileIn, checkString);            // Skip rest of this line
                        }
                    }
                    else if ( checkString == L"numweights")
                    {
                        fileIn >> numWeights;

                        std::getline(fileIn, checkString);                // Skip rest of this line

                        for(int i = 0; i < numWeights; i++)
                        {
                            Weight tempWeight;
                            fileIn >> checkString >> checkString;        // Skip "weight #"

                            fileIn >> tempWeight.jointID;                // Store weight's joint ID

                            fileIn >> tempWeight.bias;                    // Store weight's influence over a vertex

                            fileIn >> checkString;                        // Skip "("

                            fileIn >> tempWeight.pos.x                    // Store weight's pos in joint's local space
                                >> tempWeight.pos.z
                                >> tempWeight.pos.y;

                            std::getline(fileIn, checkString);            // Skip rest of this line

                            subset.weights.push_back(tempWeight);        // Push back tempWeight into subsets Weight array
                        }

                    }
                    else
                        std::getline(fileIn, checkString);                // Skip anything else

                    fileIn >> checkString;                                // Skip "}"
                }

                //*** find each vertex's position using the joints and weights ***//
                for ( int i = 0; i < subset.vertices.size(); ++i )
                {
                    Vertex tempVert = subset.vertices[i];
                    tempVert.pos = XMFLOAT3(0, 0, 0);    // Make sure the vertex's pos is cleared first

                    // Sum up the joints and weights information to get vertex's position
                    for ( int j = 0; j < tempVert.WeightCount; ++j )
                    {
                        Weight tempWeight = subset.weights[tempVert.StartWeight + j];
                        Joint tempJoint = MD5Model.joints[tempWeight.jointID];

                        // Convert joint orientation and weight pos to vectors for easier computation
                        // When converting a 3d vector to a quaternion, you should put 0 for "w", and
                        // When converting a quaternion to a 3d vector, you can just ignore the "w"
                        XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
                        XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

                        // We will need to use the conjugate of the joint orientation quaternion
                        // To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
                        XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

                        // Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
                        // We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
                        XMFLOAT3 rotatedPoint;
                        XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

                        // Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
                        // The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
                        tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
                        tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
                        tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

                        // Basically what has happened above, is we have taken the weights position relative to the joints position
                        // we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
                        // the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
                        // the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
                        // so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
                        // or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
                        // must add up to 1.
                    }

                    subset.positions.push_back(tempVert.pos);            // Store the vertices position in the position vector instead of straight into the vertex vector
                    // since we can use the positions vector for certain things like collision detection or picking
                    // without having to work with the entire vertex structure.
                }

                // Put the positions into the vertices for this subset
                for(int i = 0; i < subset.vertices.size(); i++)
                {
                    subset.vertices[i].pos = subset.positions[i];
                }

                //*** Calculate vertex normals using normal averaging ***///
                std::vector<XMFLOAT3> tempNormal;

                //normalized and unnormalized normals
                XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

                //Used to get vectors (sides) from the position of the verts
                float vecX, vecY, vecZ;

                //Two edges of our triangle
                XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

                //Compute face normals
                for(int i = 0; i < subset.numTriangles; ++i)
                {
                    //Get the vector describing one edge of our triangle (edge 0,2)
                    vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;        
                    edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

                    //Get the vector describing another edge of our triangle (edge 2,1)
                    vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
                    vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
                    vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;        
                    edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

                    //Cross multiply the two edge vectors to get the un-normalized face normal
                    XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

                    tempNormal.push_back(unnormalized);
                }

                //Compute vertex normals (normal Averaging)
                XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                int facesUsing = 0;
                float tX, tY, tZ;    //temp axis variables

                //Go through each vertex
                for(int i = 0; i < subset.vertices.size(); ++i)
                {
                    //Check which triangles use this vertex
                    for(int j = 0; j < subset.numTriangles; ++j)
                    {
                        if(subset.indices[j*3] == i ||
                            subset.indices[(j*3)+1] == i ||
                            subset.indices[(j*3)+2] == i)
                        {
                            tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                            tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                            tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                            normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                            facesUsing++;
                        }
                    }

                    //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
                    normalSum = normalSum / facesUsing;

                    //Normalize the normalSum vector
                    normalSum = XMVector3Normalize(normalSum);

                    //Store the normal and tangent in our current vertex
                    subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
                    subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
                    subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);

                    //Clear normalSum, facesUsing for next vertex
                    normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
                    facesUsing = 0;
                }

                // Create index buffer
                D3D11_BUFFER_DESC indexBufferDesc;
                ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

                indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
                indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
                indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
                indexBufferDesc.CPUAccessFlags = 0;
                indexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA iinitData;

                iinitData.pSysMem = &subset.indices[0];
                d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

                //Create Vertex Buffer
                D3D11_BUFFER_DESC vertexBufferDesc;
                ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

                vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;                            // We will be updating this buffer, so we must set as dynamic
                vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
                vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
                vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;                // Give CPU power to write to buffer
                vertexBufferDesc.MiscFlags = 0;

                D3D11_SUBRESOURCE_DATA vertexBufferData; 

                ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
                vertexBufferData.pSysMem = &subset.vertices[0];
                hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

                // Push back the temp subset into the models subset vector
                MD5Model.subsets.push_back(subset);
            }
        }
    }
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    // Make sure we are out of fullscreen

        // create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    // display message
            L"Error", MB_OK);

        return false;
    }

    return true;
}
///////////////**************new**************////////////////////

bool LoadObjModel(std::wstring filename, 
    ID3D11Buffer** vertBuff, 
    ID3D11Buffer** indexBuff,
    std::vector<int>& subsetIndexStart,
    std::vector<int>& subsetMaterialArray,
    std::vector<SurfaceMaterial>& material, 
    int& subsetCount,
    bool isRHCoordSys,
    bool computeNormals)
{
    HRESULT hr = 0;

    std::wifstream fileIn (filename.c_str());    //Open file
    std::wstring meshMatLib;                    //String to hold our obj material library filename

    //Arrays to store our model's information
    std::vector<DWORD> indices;
    std::vector<XMFLOAT3> vertPos;
    std::vector<XMFLOAT3> vertNorm;
    std::vector<XMFLOAT2> vertTexCoord;
    std::vector<std::wstring> meshMaterials;

    //Vertex definition indices
    std::vector<int> vertPosIndex;
    std::vector<int> vertNormIndex;
    std::vector<int> vertTCIndex;

    //Make sure we have a default if no tex coords or normals are defined
    bool hasTexCoord = false;
    bool hasNorm = false;

    //Temp variables to store into vectors
    std::wstring meshMaterialsTemp;
    int vertPosIndexTemp;
    int vertNormIndexTemp;
    int vertTCIndexTemp;

    wchar_t checkChar;        //The variable we will use to store one char from file at a time
    std::wstring face;        //Holds the string containing our face vertices
    int vIndex = 0;            //Keep track of our vertex index count
    int triangleCount = 0;    //Total Triangles
    int totalVerts = 0;
    int meshTriangles = 0;

    //Check to see if the file was opened
    if (fileIn)
    {
        while(fileIn)
        {            
            checkChar = fileIn.get();    //Get next char

            switch (checkChar)
            {        
            case '#':
                checkChar = fileIn.get();
                while(checkChar != '\n')
                    checkChar = fileIn.get();
                break;
            case 'v':    //Get Vertex Descriptions
                checkChar = fileIn.get();
                if(checkChar == ' ')    //v - vert position
                {
                    float vz, vy, vx;
                    fileIn >> vx >> vy >> vz;    //Store the next three types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f));    //Invert the Z axis
                    else
                        vertPos.push_back(XMFLOAT3( vx, vy, vz));
                }
                if(checkChar == 't')    //vt - vert tex coords
                {            
                    float vtcu, vtcv;
                    fileIn >> vtcu >> vtcv;        //Store next two types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv));    //Reverse the "v" axis
                    else
                        vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv));    

                    hasTexCoord = true;    //We know the model uses texture coords
                }
                //Since we compute the normals later, we don't need to check for normals
                //In the file, but i'll do it here anyway
                if(checkChar == 'n')    //vn - vert normal
                {
                    float vnx, vny, vnz;
                    fileIn >> vnx >> vny >> vnz;    //Store next three types

                    if(isRHCoordSys)    //If model is from an RH Coord System
                        vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f ));    //Invert the Z axis
                    else
                        vertNorm.push_back(XMFLOAT3( vnx, vny, vnz ));    

                    hasNorm = true;    //We know the model defines normals
                }
                break;

                //New group (Subset)
            case 'g':    //g - defines a group
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    subsetIndexStart.push_back(vIndex);        //Start index for this subset
                    subsetCount++;
                }
                break;

                //Get Face Index
            case 'f':    //f - defines the faces
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    face = L"";
                    std::wstring VertDef;    //Holds one vertex definition at a time
                    triangleCount = 0;

                    checkChar = fileIn.get();
                    while(checkChar != '\n')
                    {
                        face += checkChar;            //Add the char to our face string
                        checkChar = fileIn.get();    //Get the next Character
                        if(checkChar == ' ')        //If its a space...
                            triangleCount++;        //Increase our triangle count
                    }

                    //Check for space at the end of our face string
                    if(face[face.length()-1] == ' ')
                        triangleCount--;    //Each space adds to our triangle count

                    triangleCount -= 1;        //Ever vertex in the face AFTER the first two are new faces

                    std::wstringstream ss(face);

                    if(face.length() > 0)
                    {
                        int firstVIndex, lastVIndex;    //Holds the first and last vertice's index

                        for(int i = 0; i < 3; ++i)        //First three vertices (first triangle)
                        {
                            ss >> VertDef;    //Get vertex definition (vPos/vTexCoord/vNorm)

                            std::wstring vertPart;
                            int whichPart = 0;        //(vPos, vTexCoord, or vNorm)

                            //Parse this string
                            for(int j = 0; j < VertDef.length(); ++j)
                            {
                                if(VertDef[j] != '/')    //If there is no divider "/", add a char to our vertPart
                                    vertPart += VertDef[j];

                                //If the current char is a divider "/", or its the last character in the string
                                if(VertDef[j] == '/' || j ==  VertDef.length()-1)
                                {
                                    std::wistringstream wstringToInt(vertPart);    //Used to convert wstring to int

                                    if(whichPart == 0)    //If vPos
                                    {
                                        wstringToInt >> vertPosIndexTemp;
                                        vertPosIndexTemp -= 1;        //subtract one since c++ arrays start with 0, and obj start with 1

                                        //Check to see if the vert pos was the only thing specified
                                        if(j == VertDef.length()-1)
                                        {
                                            vertNormIndexTemp = 0;
                                            vertTCIndexTemp = 0;
                                        }
                                    }

                                    else if(whichPart == 1)    //If vTexCoord
                                    {
                                        if(vertPart != L"")    //Check to see if there even is a tex coord
                                        {
                                            wstringToInt >> vertTCIndexTemp;
                                            vertTCIndexTemp -= 1;    //subtract one since c++ arrays start with 0, and obj start with 1
                                        }
                                        else    //If there is no tex coord, make a default
                                            vertTCIndexTemp = 0;

                                        //If the cur. char is the second to last in the string, then
                                        //there must be no normal, so set a default normal
                                        if(j == VertDef.length()-1)
                                            vertNormIndexTemp = 0;

                                    }                                
                                    else if(whichPart == 2)    //If vNorm
                                    {
                                        std::wistringstream wstringToInt(vertPart);

                                        wstringToInt >> vertNormIndexTemp;
                                        vertNormIndexTemp -= 1;        //subtract one since c++ arrays start with 0, and obj start with 1
                                    }

                                    vertPart = L"";    //Get ready for next vertex part
                                    whichPart++;    //Move on to next vertex part                    
                                }
                            }

                            //Check to make sure there is at least one subset
                            if(subsetCount == 0)
                            {
                                subsetIndexStart.push_back(vIndex);        //Start index for this subset
                                subsetCount++;
                            }

                            //Avoid duplicate vertices
                            bool vertAlreadyExists = false;
                            if(totalVerts >= 3)    //Make sure we at least have one triangle to check
                            {
                                //Loop through all the vertices
                                for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
                                {
                                    //If the vertex position and texture coordinate in memory are the same
                                    //As the vertex position and texture coordinate we just now got out
                                    //of the obj file, we will set this faces vertex index to the vertex's
                                    //index value in memory. This makes sure we don't create duplicate vertices
                                    if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
                                    {
                                        if(vertTCIndexTemp == vertTCIndex[iCheck])
                                        {
                                            indices.push_back(iCheck);        //Set index for this vertex
                                            vertAlreadyExists = true;        //If we've made it here, the vertex already exists
                                        }
                                    }
                                }
                            }

                            //If this vertex is not already in our vertex arrays, put it there
                            if(!vertAlreadyExists)
                            {
                                vertPosIndex.push_back(vertPosIndexTemp);
                                vertTCIndex.push_back(vertTCIndexTemp);
                                vertNormIndex.push_back(vertNormIndexTemp);
                                totalVerts++;    //We created a new vertex
                                indices.push_back(totalVerts-1);    //Set index for this vertex
                            }                            

                            //If this is the very first vertex in the face, we need to
                            //make sure the rest of the triangles use this vertex
                            if(i == 0)
                            {
                                firstVIndex = indices[vIndex];    //The first vertex index of this FACE

                            }

                            //If this was the last vertex in the first triangle, we will make sure
                            //the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5))
                            if(i == 2)
                            {                                
                                lastVIndex = indices[vIndex];    //The last vertex index of this TRIANGLE
                            }
                            vIndex++;    //Increment index count
                        }

                        meshTriangles++;    //One triangle down

                        //If there are more than three vertices in the face definition, we need to make sure
                        //we convert the face to triangles. We created our first triangle above, now we will
                        //create a new triangle for every new vertex in the face, using the very first vertex
                        //of the face, and the last vertex from the triangle before the current triangle
                        for(int l = 0; l < triangleCount-1; ++l)    //Loop through the next vertices to create new triangles
                        {
                            //First vertex of this triangle (the very first vertex of the face too)
                            indices.push_back(firstVIndex);            //Set index for this vertex
                            vIndex++;

                            //Second Vertex of this triangle (the last vertex used in the tri before this one)
                            indices.push_back(lastVIndex);            //Set index for this vertex
                            vIndex++;

                            //Get the third vertex for this triangle
                            ss >> VertDef;

                            std::wstring vertPart;
                            int whichPart = 0;

                            //Parse this string (same as above)
                            for(int j = 0; j < VertDef.length(); ++j)
                            {
                                if(VertDef[j] != '/')
                                    vertPart += VertDef[j];
                                if(VertDef[j] == '/' || j ==  VertDef.length()-1)
                                {
                                    std::wistringstream wstringToInt(vertPart);

                                    if(whichPart == 0)
                                    {
                                        wstringToInt >> vertPosIndexTemp;
                                        vertPosIndexTemp -= 1;

                                        //Check to see if the vert pos was the only thing specified
                                        if(j == VertDef.length()-1)
                                        {
                                            vertTCIndexTemp = 0;
                                            vertNormIndexTemp = 0;
                                        }
                                    }
                                    else if(whichPart == 1)
                                    {
                                        if(vertPart != L"")
                                        {
                                            wstringToInt >> vertTCIndexTemp;
                                            vertTCIndexTemp -= 1;
                                        }
                                        else
                                            vertTCIndexTemp = 0;
                                        if(j == VertDef.length()-1)
                                            vertNormIndexTemp = 0;

                                    }                                
                                    else if(whichPart == 2)
                                    {
                                        std::wistringstream wstringToInt(vertPart);

                                        wstringToInt >> vertNormIndexTemp;
                                        vertNormIndexTemp -= 1;
                                    }

                                    vertPart = L"";
                                    whichPart++;                            
                                }
                            }                    

                            //Check for duplicate vertices
                            bool vertAlreadyExists = false;
                            if(totalVerts >= 3)    //Make sure we at least have one triangle to check
                            {
                                for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
                                {
                                    if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
                                    {
                                        if(vertTCIndexTemp == vertTCIndex[iCheck])
                                        {
                                            indices.push_back(iCheck);            //Set index for this vertex
                                            vertAlreadyExists = true;        //If we've made it here, the vertex already exists
                                        }
                                    }
                                }
                            }

                            if(!vertAlreadyExists)
                            {
                                vertPosIndex.push_back(vertPosIndexTemp);
                                vertTCIndex.push_back(vertTCIndexTemp);
                                vertNormIndex.push_back(vertNormIndexTemp);
                                totalVerts++;                    //New vertex created, add to total verts
                                indices.push_back(totalVerts-1);        //Set index for this vertex
                            }

                            //Set the second vertex for the next triangle to the last vertex we got        
                            lastVIndex = indices[vIndex];    //The last vertex index of this TRIANGLE

                            meshTriangles++;    //New triangle defined
                            vIndex++;        
                        }
                    }
                }
                break;

            case 'm':    //mtllib - material library filename
                checkChar = fileIn.get();
                if(checkChar == 't')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'l')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'l')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 'i')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'b')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        //Store the material libraries file name
                                        fileIn >> meshMatLib;
                                    }
                                }
                            }
                        }
                    }
                }

                break;

            case 'u':    //usemtl - which material to use
                checkChar = fileIn.get();
                if(checkChar == 's')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'e')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'm')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 't')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'l')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        meshMaterialsTemp = L"";    //Make sure this is cleared

                                        fileIn >> meshMaterialsTemp; //Get next type (string)

                                        meshMaterials.push_back(meshMaterialsTemp);
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            default:                
                break;
            }
        }
    }
    else    //If we could not open the file
    {
        SwapChain->SetFullscreenState(false, NULL);    //Make sure we are out of fullscreen

        //create message
        std::wstring message = L"Could not open: ";
        message += filename;

        MessageBox(0, message.c_str(),    //display message
            L"Error", MB_OK);

        return false;
    }

    subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here

    //sometimes "g" is defined at the very top of the file, then again before the first group of faces.
    //This makes sure the first subset does not conatain "0" indices.
    if(subsetIndexStart[1] == 0)
    {
        subsetIndexStart.erase(subsetIndexStart.begin()+1);
        meshSubsets--;
    }

    //Make sure we have a default for the tex coord and normal
    //if one or both are not specified
    if(!hasNorm)
        vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f));
    if(!hasTexCoord)
        vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f));

    //Close the obj file, and open the mtl file
    fileIn.close();
    fileIn.open(meshMatLib.c_str());

    std::wstring lastStringRead;
    int matCount = material.size();    //total materials

    //kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same)
    //If the diffuse color WAS set, then we don't need to set our diffuse color to ambient
    bool kdset = false;

    if (fileIn)
    {
        while(fileIn)
        {
            checkChar = fileIn.get();    //Get next char

            switch (checkChar)
            {
                //Check for comment
            case '#':
                checkChar = fileIn.get();
                while(checkChar != '\n')
                    checkChar = fileIn.get();
                break;

                //Set diffuse color
            case 'K':
                checkChar = fileIn.get();
                if(checkChar == 'd')    //Diffuse Color
                {
                    checkChar = fileIn.get();    //remove space

                    fileIn >> material[matCount-1].difColor.x;
                    fileIn >> material[matCount-1].difColor.y;
                    fileIn >> material[matCount-1].difColor.z;

                    kdset = true;
                }

                //Ambient Color (We'll store it in diffuse if there isn't a diffuse already)
                if(checkChar == 'a')    
                {                    
                    checkChar = fileIn.get();    //remove space
                    if(!kdset)
                    {
                        fileIn >> material[matCount-1].difColor.x;
                        fileIn >> material[matCount-1].difColor.y;
                        fileIn >> material[matCount-1].difColor.z;
                    }
                }
                break;

                //Check for transparency
            case 'T':
                checkChar = fileIn.get();
                if(checkChar == 'r')
                {
                    checkChar = fileIn.get();    //remove space
                    float Transparency;
                    fileIn >> Transparency;

                    material[matCount-1].difColor.w = Transparency;

                    if(Transparency > 0.0f)
                        material[matCount-1].transparent = true;
                }
                break;

                //Some obj files specify d for transparency
            case 'd':
                checkChar = fileIn.get();
                if(checkChar == ' ')
                {
                    float Transparency;
                    fileIn >> Transparency;

                    //'d' - 0 being most transparent, and 1 being opaque, opposite of Tr
                    Transparency = 1.0f - Transparency;

                    material[matCount-1].difColor.w = Transparency;

                    if(Transparency > 0.0f)
                        material[matCount-1].transparent = true;                    
                }
                break;

                //Get the diffuse map (texture)
            case 'm':
                checkChar = fileIn.get();
                if(checkChar == 'a')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'p')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == '_')
                        {
                            //map_Kd - Diffuse map
                            checkChar = fileIn.get();
                            if(checkChar == 'K')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'd')
                                {
                                    std::wstring fileNamePath;

                                    fileIn.get();    //Remove whitespace between map_Kd and file

                                    //Get the file path - We read the pathname char by char since
                                    //pathnames can sometimes contain spaces, so we will read until
                                    //we find the file extension
                                    bool texFilePathEnd = false;
                                    while(!texFilePathEnd)
                                    {
                                        checkChar = fileIn.get();

                                        fileNamePath += checkChar;

                                        if(checkChar == '.')
                                        {
                                            for(int i = 0; i < 3; ++i)
                                                fileNamePath += fileIn.get();

                                            texFilePathEnd = true;
                                        }                            
                                    }

                                    //check if this texture has already been loaded
                                    bool alreadyLoaded = false;
                                    for(int i = 0; i < textureNameArray.size(); ++i)
                                    {
                                        if(fileNamePath == textureNameArray[i])
                                        {
                                            alreadyLoaded = true;
                                            material[matCount-1].texArrayIndex = i;
                                            material[matCount-1].hasTexture = true;
                                        }
                                    }

                                    //if the texture is not already loaded, load it now
                                    if(!alreadyLoaded)
                                    {
                                        ID3D11ShaderResourceView* tempMeshSRV;
                                        hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                            NULL, NULL, &tempMeshSRV, NULL );
                                        if(SUCCEEDED(hr))
                                        {
                                            textureNameArray.push_back(fileNamePath.c_str());
                                            material[matCount-1].texArrayIndex = meshSRV.size();
                                            meshSRV.push_back(tempMeshSRV);
                                            material[matCount-1].hasTexture = true;
                                        }
                                    }    
                                }
                            }
                            //map_d - alpha map
                            else if(checkChar == 'd')
                            {
                                //Alpha maps are usually the same as the diffuse map
                                //So we will assume that for now by only enabling
                                //transparency for this material, as we will already
                                //be using the alpha channel in the diffuse map
                                material[matCount-1].transparent = true;
                            }

                            //map_bump - bump map (we're usinga normal map though)
                            else if(checkChar == 'b')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'u')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == 'm')
                                    {
                                        checkChar = fileIn.get();
                                        if(checkChar == 'p')
                                        {
                                            std::wstring fileNamePath;

                                            fileIn.get();    //Remove whitespace between map_bump and file

                                            //Get the file path - We read the pathname char by char since
                                            //pathnames can sometimes contain spaces, so we will read until
                                            //we find the file extension
                                            bool texFilePathEnd = false;
                                            while(!texFilePathEnd)
                                            {
                                                checkChar = fileIn.get();

                                                fileNamePath += checkChar;

                                                if(checkChar == '.')
                                                {
                                                    for(int i = 0; i < 3; ++i)
                                                        fileNamePath += fileIn.get();

                                                    texFilePathEnd = true;
                                                }                            
                                            }

                                            //check if this texture has already been loaded
                                            bool alreadyLoaded = false;
                                            for(int i = 0; i < textureNameArray.size(); ++i)
                                            {
                                                if(fileNamePath == textureNameArray[i])
                                                {
                                                    alreadyLoaded = true;
                                                    material[matCount-1].normMapTexArrayIndex = i;
                                                    material[matCount-1].hasNormMap = true;
                                                }
                                            }

                                            //if the texture is not already loaded, load it now
                                            if(!alreadyLoaded)
                                            {
                                                ID3D11ShaderResourceView* tempMeshSRV;
                                                hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
                                                    NULL, NULL, &tempMeshSRV, NULL );
                                                if(SUCCEEDED(hr))
                                                {
                                                    textureNameArray.push_back(fileNamePath.c_str());
                                                    material[matCount-1].normMapTexArrayIndex = meshSRV.size();
                                                    meshSRV.push_back(tempMeshSRV);
                                                    material[matCount-1].hasNormMap = true;
                                                }
                                            }    
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            case 'n':    //newmtl - Declare new material
                checkChar = fileIn.get();
                if(checkChar == 'e')
                {
                    checkChar = fileIn.get();
                    if(checkChar == 'w')
                    {
                        checkChar = fileIn.get();
                        if(checkChar == 'm')
                        {
                            checkChar = fileIn.get();
                            if(checkChar == 't')
                            {
                                checkChar = fileIn.get();
                                if(checkChar == 'l')
                                {
                                    checkChar = fileIn.get();
                                    if(checkChar == ' ')
                                    {
                                        //New material, set its defaults
                                        SurfaceMaterial tempMat;
                                        material.push_back(tempMat);
                                        fileIn >> material[matCount].matName;
                                        material[matCount].transparent = false;
                                        material[matCount].hasTexture = false;
                                        material[matCount].hasNormMap = false;
                                        material[matCount].normMapTexArrayIndex = 0;
                                        material[matCount].texArrayIndex = 0;
                                        matCount++;
                                        kdset = false;
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            default:
                break;
            }
        }
    }    
    else
    {
        SwapChain->SetFullscreenState(false, NULL);    //Make sure we are out of fullscreen

        std::wstring message = L"Could not open: ";
        message += meshMatLib;

        MessageBox(0, message.c_str(),
            L"Error", MB_OK);

        return false;
    }

    //Set the subsets material to the index value
    //of the its material in our material array
    for(int i = 0; i < meshSubsets; ++i)
    {
        bool hasMat = false;
        for(int j = 0; j < material.size(); ++j)
        {
            if(meshMaterials[i] == material[j].matName)
            {
                subsetMaterialArray.push_back(j);
                hasMat = true;
            }
        }
        if(!hasMat)
            subsetMaterialArray.push_back(0); //Use first material in array
    }

    std::vector<Vertex> vertices;
    Vertex tempVert;

    //Create our vertices using the information we got 
    //from the file and store them in a vector
    for(int j = 0 ; j < totalVerts; ++j)
    {
        tempVert.pos = vertPos[vertPosIndex[j]];
        tempVert.normal = vertNorm[vertNormIndex[j]];
        tempVert.texCoord = vertTexCoord[vertTCIndex[j]];

        vertices.push_back(tempVert);
    }

    //////////////////////Compute Normals///////////////////////////
    //If computeNormals was set to true then we will create our own
    //normals, if it was set to false we will use the obj files normals
    if(computeNormals)
    {
        std::vector<XMFLOAT3> tempNormal;

        //normalized and unnormalized normals
        XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

        //tangent stuff
        std::vector<XMFLOAT3> tempTangent;
        XMFLOAT3 tangent = XMFLOAT3(0.0f, 0.0f, 0.0f);
        float tcU1, tcV1, tcU2, tcV2;

        //Used to get vectors (sides) from the position of the verts
        float vecX, vecY, vecZ;

        //Two edges of our triangle
        XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

        //Compute face normals
        //And Tangents
        for(int i = 0; i < meshTriangles; ++i)
        {
            //Get the vector describing one edge of our triangle (edge 0,2)
            vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x;
            vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y;
            vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z;        
            edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our first edge

            //Get the vector describing another edge of our triangle (edge 2,1)
            vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x;
            vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y;
            vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z;        
            edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);    //Create our second edge

            //Cross multiply the two edge vectors to get the un-normalized face normal
            XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

            tempNormal.push_back(unnormalized);

            //Find first texture coordinate edge 2d vector
            tcU1 = vertices[indices[(i*3)]].texCoord.x - vertices[indices[(i*3)+2]].texCoord.x;
            tcV1 = vertices[indices[(i*3)]].texCoord.y - vertices[indices[(i*3)+2]].texCoord.y;

            //Find second texture coordinate edge 2d vector
            tcU2 = vertices[indices[(i*3)+2]].texCoord.x - vertices[indices[(i*3)+1]].texCoord.x;
            tcV2 = vertices[indices[(i*3)+2]].texCoord.y - vertices[indices[(i*3)+1]].texCoord.y;

            //Find tangent using both tex coord edges and position edges
            tangent.x = (tcV1 * XMVectorGetX(edge1) - tcV2 * XMVectorGetX(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
            tangent.y = (tcV1 * XMVectorGetY(edge1) - tcV2 * XMVectorGetY(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
            tangent.z = (tcV1 * XMVectorGetZ(edge1) - tcV2 * XMVectorGetZ(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));

            tempTangent.push_back(tangent);
        }

        //Compute vertex normals (normal Averaging)
        XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        XMVECTOR tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        int facesUsing = 0;
        float tX, tY, tZ;    //temp axis variables

        //Go through each vertex
        for(int i = 0; i < totalVerts; ++i)
        {
            //Check which triangles use this vertex
            for(int j = 0; j < meshTriangles; ++j)
            {
                if(indices[j*3] == i ||
                    indices[(j*3)+1] == i ||
                    indices[(j*3)+2] == i)
                {
                    tX = XMVectorGetX(normalSum) + tempNormal[j].x;
                    tY = XMVectorGetY(normalSum) + tempNormal[j].y;
                    tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

                    normalSum = XMVectorSet(tX, tY, tZ, 0.0f);    //If a face is using the vertex, add the unormalized face normal to the normalSum

                    //We can reuse tX, tY, tZ to sum up tangents
                    tX = XMVectorGetX(tangentSum) + tempTangent[j].x;
                    tY = XMVectorGetY(tangentSum) + tempTangent[j].y;
                    tZ = XMVectorGetZ(tangentSum) + tempTangent[j].z;

                    tangentSum = XMVectorSet(tX, tY, tZ, 0.0f); //sum up face tangents using this vertex

                    facesUsing++;
                }
            }

            //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
            normalSum = normalSum / facesUsing;
            tangentSum = tangentSum / facesUsing;

            //Normalize the normalSum vector and tangent
            normalSum = XMVector3Normalize(normalSum);
            tangentSum =  XMVector3Normalize(tangentSum);

            //Store the normal and tangent in our current vertex
            vertices[i].normal.x = XMVectorGetX(normalSum);
            vertices[i].normal.y = XMVectorGetY(normalSum);
            vertices[i].normal.z = XMVectorGetZ(normalSum);

            vertices[i].tangent.x = XMVectorGetX(tangentSum);
            vertices[i].tangent.y = XMVectorGetY(tangentSum);
            vertices[i].tangent.z = XMVectorGetZ(tangentSum);

            //Clear normalSum, tangentSum and facesUsing for next vertex
            normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
            tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
            facesUsing = 0;

        }
    }

    //Create index buffer
    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = &indices[0];
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff);

    //Create Vertex Buffer
    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = &vertices[0];
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff);

    return true;
}

void CreateSphere(int LatLines, int LongLines)
{
    NumSphereVertices = ((LatLines-2) * LongLines) + 2;
    NumSphereFaces  = ((LatLines-3)*(LongLines)*2) + (LongLines*2);

    float sphereYaw = 0.0f;
    float spherePitch = 0.0f;

    std::vector<Vertex> vertices(NumSphereVertices);

    XMVECTOR currVertPos = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);

    vertices[0].pos.x = 0.0f;
    vertices[0].pos.y = 0.0f;
    vertices[0].pos.z = 1.0f;

    for(DWORD i = 0; i < LatLines-2; ++i)
    {
        spherePitch = (i+1) * (3.14f/(LatLines-1));
        Rotationx = XMMatrixRotationX(spherePitch);
        for(DWORD j = 0; j < LongLines; ++j)
        {
            sphereYaw = j * (6.28f/(LongLines));
            Rotationy = XMMatrixRotationZ(sphereYaw);
            currVertPos = XMVector3TransformNormal( XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (Rotationx * Rotationy) );    
            currVertPos = XMVector3Normalize( currVertPos );
            vertices[i*LongLines+j+1].pos.x = XMVectorGetX(currVertPos);
            vertices[i*LongLines+j+1].pos.y = XMVectorGetY(currVertPos);
            vertices[i*LongLines+j+1].pos.z = XMVectorGetZ(currVertPos);
        }
    }

    vertices[NumSphereVertices-1].pos.x =  0.0f;
    vertices[NumSphereVertices-1].pos.y =  0.0f;
    vertices[NumSphereVertices-1].pos.z = -1.0f;


    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * NumSphereVertices;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = &vertices[0];
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &sphereVertBuffer);


    std::vector<DWORD> indices(NumSphereFaces * 3);

    int k = 0;
    for(DWORD l = 0; l < LongLines-1; ++l)
    {
        indices[k] = 0;
        indices[k+1] = l+1;
        indices[k+2] = l+2;
        k += 3;
    }

    indices[k] = 0;
    indices[k+1] = LongLines;
    indices[k+2] = 1;
    k += 3;

    for(DWORD i = 0; i < LatLines-3; ++i)
    {
        for(DWORD j = 0; j < LongLines-1; ++j)
        {
            indices[k]   = i*LongLines+j+1;
            indices[k+1] = i*LongLines+j+2;
            indices[k+2] = (i+1)*LongLines+j+1;

            indices[k+3] = (i+1)*LongLines+j+1;
            indices[k+4] = i*LongLines+j+2;
            indices[k+5] = (i+1)*LongLines+j+2;

            k += 6; // next quad
        }

        indices[k]   = (i*LongLines)+LongLines;
        indices[k+1] = (i*LongLines)+1;
        indices[k+2] = ((i+1)*LongLines)+LongLines;

        indices[k+3] = ((i+1)*LongLines)+LongLines;
        indices[k+4] = (i*LongLines)+1;
        indices[k+5] = ((i+1)*LongLines)+1;

        k += 6;
    }

    for(DWORD l = 0; l < LongLines-1; ++l)
    {
        indices[k] = NumSphereVertices-1;
        indices[k+1] = (NumSphereVertices-1)-(l+1);
        indices[k+2] = (NumSphereVertices-1)-(l+2);
        k += 3;
    }

    indices[k] = NumSphereVertices-1;
    indices[k+1] = (NumSphereVertices-1)-LongLines;
    indices[k+2] = NumSphereVertices-2;

    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * NumSphereFaces * 3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = &indices[0];
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &sphereIndexBuffer);

}

void InitD2DScreenTexture()
{
    //Create the vertex buffer
    Vertex v[] =
    {
        // Front Face
        Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f,-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex(-1.0f,  1.0f, -1.0f, 0.0f, 0.0f,-1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex( 1.0f,  1.0f, -1.0f, 1.0f, 0.0f, 1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
        Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
    };

    DWORD indices[] = {
        // Front Face
        0,  1,  2,
        0,  2,  3,
    };

    D3D11_BUFFER_DESC indexBufferDesc;
    ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA iinitData;

    iinitData.pSysMem = indices;
    d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &d2dIndexBuffer);


    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = v;
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &d2dVertBuffer);

    //Create A shader resource view from the texture D2D will render to,
    //So we can use it to texture a square which overlays our scene
    d3d11Device->CreateShaderResourceView(sharedTex11, NULL, &d2dTexture);
}

bool InitScene()
{
    InitD2DScreenTexture();

    CreateSphere(10, 10);

    if(!LoadObjModel(L"ground.obj", &meshVertBuff, &meshIndexBuff, meshSubsetIndexStart, meshSubsetTexture, material, meshSubsets, true, true))
        return false;

    ///////////////**************new**************////////////////////
    if(!LoadMD5Model(L"boy.md5mesh", NewMD5Model, meshSRV, textureNameArray))
        return false;
    ///////////////**************new**************////////////////////

    //Compile Shaders from shader file
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_0", 0, 0, 0, &PS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "D2D_PS", "ps_4_0", 0, 0, 0, &D2D_PS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_VS", "vs_4_0", 0, 0, 0, &SKYMAP_VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_PS", "ps_4_0", 0, 0, 0, &SKYMAP_PS_Buffer, 0, 0);

    //Create the Shader Objects
    hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
    hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);
    hr = d3d11Device->CreatePixelShader(D2D_PS_Buffer->GetBufferPointer(), D2D_PS_Buffer->GetBufferSize(), NULL, &D2D_PS);
    hr = d3d11Device->CreateVertexShader(SKYMAP_VS_Buffer->GetBufferPointer(), SKYMAP_VS_Buffer->GetBufferSize(), NULL, &SKYMAP_VS);
    hr = d3d11Device->CreatePixelShader(SKYMAP_PS_Buffer->GetBufferPointer(), SKYMAP_PS_Buffer->GetBufferSize(), NULL, &SKYMAP_PS);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    light.pos = XMFLOAT3(0.0f, 7.0f, 0.0f);
    light.dir = XMFLOAT3(-0.5f, 0.75f, -0.5f);
    light.range = 1000.0f;
    light.cone = 12.0f;
    light.att = XMFLOAT3(0.4f, 0.02f, 0.000f);
    light.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
    light.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);

    //Create the Input Layout
    hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
        VS_Buffer->GetBufferSize(), &vertLayout );

    //Set the Input Layout
    d3d11DevCon->IASetInputLayout( vertLayout );

    //Set Primitive Topology
    d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

    //Create the Viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = Width;
    viewport.Height = Height;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;

    //Set the Viewport
    d3d11DevCon->RSSetViewports(1, &viewport);

    //Create the buffer to send to the cbuffer in effect file
    D3D11_BUFFER_DESC cbbd;    
    ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));

    cbbd.Usage = D3D11_USAGE_DEFAULT;
    cbbd.ByteWidth = sizeof(cbPerObject);
    cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbbd.CPUAccessFlags = 0;
    cbbd.MiscFlags = 0;

    hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerObjectBuffer);

    //Create the buffer to send to the cbuffer per frame in effect file
    ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));

    cbbd.Usage = D3D11_USAGE_DEFAULT;
    cbbd.ByteWidth = sizeof(cbPerFrame);
    cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbbd.CPUAccessFlags = 0;
    cbbd.MiscFlags = 0;

    hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerFrameBuffer);

    //Camera information
    camPosition = XMVectorSet( 0.0f, 5.0f, -8.0f, 0.0f );
    camTarget = XMVectorSet( 0.0f, 0.5f, 0.0f, 0.0f );
    camUp = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );

    //Set the View matrix
    camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );

    //Set the Projection matrix
    camProjection = XMMatrixPerspectiveFovLH( 0.4f*3.14f, (float)Width/Height, 1.0f, 1000.0f);

    D3D11_BLEND_DESC blendDesc;
    ZeroMemory( &blendDesc, sizeof(blendDesc) );

    D3D11_RENDER_TARGET_BLEND_DESC rtbd;
    ZeroMemory( &rtbd, sizeof(rtbd) );

    rtbd.BlendEnable             = true;
    rtbd.SrcBlend                 = D3D11_BLEND_SRC_COLOR;
    rtbd.DestBlend                 = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
    rtbd.SrcBlendAlpha             = D3D11_BLEND_ONE;
    rtbd.DestBlendAlpha             = D3D11_BLEND_ZERO;
    rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
    rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.RenderTarget[0] = rtbd;

    d3d11Device->CreateBlendState(&blendDesc, &d2dTransparency);

    ZeroMemory( &rtbd, sizeof(rtbd) );

    rtbd.BlendEnable             = true;
    rtbd.SrcBlend                 = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.DestBlend                 = D3D11_BLEND_SRC_ALPHA;
    rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
    rtbd.SrcBlendAlpha             = D3D11_BLEND_INV_SRC_ALPHA;
    rtbd.DestBlendAlpha             = D3D11_BLEND_SRC_ALPHA;
    rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
    rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.RenderTarget[0] = rtbd;

    d3d11Device->CreateBlendState(&blendDesc, &Transparency);

    ///Load Skymap's cube texture///
    D3DX11_IMAGE_LOAD_INFO loadSMInfo;
    loadSMInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;

    ID3D11Texture2D* SMTexture = 0;
    hr = D3DX11CreateTextureFromFile(d3d11Device, L"skymap.dds", 
        &loadSMInfo, 0, (ID3D11Resource**)&SMTexture, 0);

    D3D11_TEXTURE2D_DESC SMTextureDesc;
    SMTexture->GetDesc(&SMTextureDesc);

    D3D11_SHADER_RESOURCE_VIEW_DESC SMViewDesc;
    SMViewDesc.Format = SMTextureDesc.Format;
    SMViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
    SMViewDesc.TextureCube.MipLevels = SMTextureDesc.MipLevels;
    SMViewDesc.TextureCube.MostDetailedMip = 0;

    hr = d3d11Device->CreateShaderResourceView(SMTexture, &SMViewDesc, &smrv);

    // Describe the Sample State
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory( &sampDesc, sizeof(sampDesc) );
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;    
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;

    //Create the Sample State
    hr = d3d11Device->CreateSamplerState( &sampDesc, &CubesTexSamplerState );

    D3D11_RASTERIZER_DESC cmdesc;

    ZeroMemory(&cmdesc, sizeof(D3D11_RASTERIZER_DESC));
    cmdesc.FillMode = D3D11_FILL_SOLID;
    cmdesc.CullMode = D3D11_CULL_BACK;
    cmdesc.FrontCounterClockwise = true;
    hr = d3d11Device->CreateRasterizerState(&cmdesc, &CCWcullMode);

    cmdesc.FrontCounterClockwise = false;

    hr = d3d11Device->CreateRasterizerState(&cmdesc, &CWcullMode);

    cmdesc.CullMode = D3D11_CULL_NONE;
    //cmdesc.FillMode = D3D11_FILL_WIREFRAME;
    hr = d3d11Device->CreateRasterizerState(&cmdesc, &RSCullNone);

    D3D11_DEPTH_STENCIL_DESC dssDesc;
    ZeroMemory(&dssDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
    dssDesc.DepthEnable = true;
    dssDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    dssDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;

    d3d11Device->CreateDepthStencilState(&dssDesc, &DSLessEqual);

    return true;
}

void StartTimer()
{
    LARGE_INTEGER frequencyCount;
    QueryPerformanceFrequency(&frequencyCount);

    countsPerSecond = double(frequencyCount.QuadPart);

    QueryPerformanceCounter(&frequencyCount);
    CounterStart = frequencyCount.QuadPart;
}

double GetTime()
{
    LARGE_INTEGER currentTime;
    QueryPerformanceCounter(&currentTime);
    return double(currentTime.QuadPart-CounterStart)/countsPerSecond;
}

double GetFrameTime()
{
    LARGE_INTEGER currentTime;
    __int64 tickCount;
    QueryPerformanceCounter(&currentTime);

    tickCount = currentTime.QuadPart-frameTimeOld;
    frameTimeOld = currentTime.QuadPart;

    if(tickCount < 0.0f)
        tickCount = 0.0f;

    return float(tickCount)/countsPerSecond;
}

void UpdateScene(double time)
{
    //Reset sphereWorld
    sphereWorld = XMMatrixIdentity();

    //Define sphereWorld's world space matrix
    Scale = XMMatrixScaling( 5.0f, 5.0f, 5.0f );
    //Make sure the sphere is always centered around camera
    Translation = XMMatrixTranslation( XMVectorGetX(camPosition), XMVectorGetY(camPosition), XMVectorGetZ(camPosition) );

    //Set sphereWorld's world space using the transformations
    sphereWorld = Scale * Translation;

    //the loaded models world space
    meshWorld = XMMatrixIdentity();

    Rotation = XMMatrixRotationY(3.14f);
    Scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f );
    Translation = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );

    meshWorld = Rotation * Scale * Translation;

    ///////////////**************new**************////////////////////
    Scale = XMMatrixScaling( 0.04f, 0.04f, 0.04f );            // The model is a bit too large for our scene, so make it smaller
    Translation = XMMatrixTranslation( 0.0f, 3.0f, 0.0f );
    smilesWorld = Scale * Translation;
    ///////////////**************new**************////////////////////
}

void RenderText(std::wstring text, int inInt)
{

    d3d11DevCon->PSSetShader(D2D_PS, 0, 0);

    //Release the D3D 11 Device
    keyedMutex11->ReleaseSync(0);

    //Use D3D10.1 device
    keyedMutex10->AcquireSync(0, 5);    

    //Draw D2D content        
    D2DRenderTarget->BeginDraw();    

    //Clear D2D Background
    D2DRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));

    //Create our string
    std::wostringstream printString; 
    printString << text << inInt;
    printText = printString.str();

    //Set the Font Color
    D2D1_COLOR_F FontColor = D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f);

    //Set the brush color D2D will use to draw with
    Brush->SetColor(FontColor);    

    //Create the D2D Render Area
    D2D1_RECT_F layoutRect = D2D1::RectF(0, 0, Width, Height);

    //Draw the Text
    D2DRenderTarget->DrawText(
        printText.c_str(),
        wcslen(printText.c_str()),
        TextFormat,
        layoutRect,
        Brush
        );

    D2DRenderTarget->EndDraw();    

    //Release the D3D10.1 Device
    keyedMutex10->ReleaseSync(1);

    //Use the D3D11 Device
    keyedMutex11->AcquireSync(1, 5);

    //Use the shader resource representing the direct2d render target
    //to texture a square which is rendered in screen space so it
    //overlays on top of our entire scene. We use alpha blending so
    //that the entire background of the D2D render target is "invisible",
    //And only the stuff we draw with D2D will be visible (the text)

    //Set the blend state for D2D render target texture objects
    d3d11DevCon->OMSetBlendState(d2dTransparency, NULL, 0xffffffff);

    //Set the d2d Index buffer
    d3d11DevCon->IASetIndexBuffer( d2dIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    //Set the d2d vertex buffer
    UINT stride = sizeof( Vertex );
    UINT offset = 0;
    d3d11DevCon->IASetVertexBuffers( 0, 1, &d2dVertBuffer, &stride, &offset );

    WVP =  XMMatrixIdentity();
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &d2dTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 6, 0, 0 );    
}

void DrawScene()
{
    //Clear our render target and depth/stencil view
    float bgColor[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);    
    d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

    constbuffPerFrame.light = light;
    d3d11DevCon->UpdateSubresource( cbPerFrameBuffer, 0, NULL, &constbuffPerFrame, 0, 0 );
    d3d11DevCon->PSSetConstantBuffers(0, 1, &cbPerFrameBuffer);    

    //Set our Render Target
    d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, depthStencilView );

    //Set the default blend state (no blending) for opaque objects
    d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    UINT stride = sizeof( Vertex );
    UINT offset = 0;

    ///////////////**************new**************////////////////////
    ///***Draw MD5 Model***///
    for(int i = 0; i < NewMD5Model.numSubsets; i ++)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( NewMD5Model.subsets[i].indexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &NewMD5Model.subsets[i].vertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = smilesWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(smilesWorld);    
        cbPerObj.hasTexture = true;        // We'll assume all md5 subsets have textures
        cbPerObj.hasNormMap = false;    // We'll also assume md5 models have no normal map (easy to change later though)
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[NewMD5Model.subsets[i].texArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        d3d11DevCon->DrawIndexed( NewMD5Model.subsets[i].indices.size(), 0, 0 );

    }
    ///////////////**************new**************////////////////////

    /////Draw our model's NON-transparent subsets/////
    for(int i = 0; i < meshSubsets; ++i)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = meshWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(meshWorld);    
        cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
        cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
        cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        if(material[meshSubsetTexture[i]].hasTexture)
            d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
        if(material[meshSubsetTexture[i]].hasNormMap)
            d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        int indexStart = meshSubsetIndexStart[i];
        int indexDrawAmount =  meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];
        if(!material[meshSubsetTexture[i]].transparent)
            d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
    }

    /////Draw the Sky's Sphere//////
    //Set the spheres index buffer
    d3d11DevCon->IASetIndexBuffer( sphereIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    //Set the spheres vertex buffer
    d3d11DevCon->IASetVertexBuffers( 0, 1, &sphereVertBuffer, &stride, &offset );

    //Set the WVP matrix and send it to the constant buffer in effect file
    WVP = sphereWorld * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    cbPerObj.World = XMMatrixTranspose(sphereWorld);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    //Send our skymap resource view to pixel shader
    d3d11DevCon->PSSetShaderResources( 0, 1, &smrv );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    //Set the new VS and PS shaders
    d3d11DevCon->VSSetShader(SKYMAP_VS, 0, 0);
    d3d11DevCon->PSSetShader(SKYMAP_PS, 0, 0);
    //Set the new depth/stencil and RS states
    d3d11DevCon->OMSetDepthStencilState(DSLessEqual, 0);
    d3d11DevCon->RSSetState(RSCullNone);
    d3d11DevCon->DrawIndexed( NumSphereFaces * 3, 0, 0 );    

    //Set the default VS, PS shaders and depth/stencil state
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);
    d3d11DevCon->OMSetDepthStencilState(NULL, 0);

    /////Draw our model's TRANSPARENT subsets now/////
    //Set our blend state
    d3d11DevCon->OMSetBlendState(Transparency, NULL, 0xffffffff);

    for(int i = 0; i < meshSubsets; ++i)
    {
        //Set the grounds index buffer
        d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
        //Set the grounds vertex buffer
        d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );

        //Set the WVP matrix and send it to the constant buffer in effect file
        WVP = meshWorld * camView * camProjection;
        cbPerObj.WVP = XMMatrixTranspose(WVP);    
        cbPerObj.World = XMMatrixTranspose(meshWorld);    
        cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
        cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
        cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
        d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
        d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
        d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
        if(material[meshSubsetTexture[i]].hasTexture)
            d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
        if(material[meshSubsetTexture[i]].hasNormMap)
            d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
        d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

        d3d11DevCon->RSSetState(RSCullNone);
        int indexStart = meshSubsetIndexStart[i];
        int indexDrawAmount =  meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];
        if(material[meshSubsetTexture[i]].transparent)
            d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
    }

    RenderText(L"FPS: ", fps);

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}

int messageloop(){
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    while(true)
    {
        BOOL PeekMessageL( 
            LPMSG lpMsg,
            HWND hWnd,
            UINT wMsgFilterMin,
            UINT wMsgFilterMax,
            UINT wRemoveMsg
            );

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;
            TranslateMessage(&msg);    
            DispatchMessage(&msg);
        }
        else{
            // run game code    
            frameCount++;
            if(GetTime() > 1.0f)
            {
                fps = frameCount;
                frameCount = 0;
                StartTimer();
            }    

            frameTime = GetFrameTime();

            DetectInput(frameTime);
            UpdateScene(frameTime);
            DrawScene();
        }
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch( msg )
    {
    case WM_KEYDOWN:
        if( wParam == VK_ESCAPE ){
            DestroyWindow(hwnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,
        msg,
        wParam,
        lParam);
}
Effects.fx
struct Light
{
    float3 pos;
    float  range;
    float3 dir;
    float cone;
    float3 att;
    float4 ambient;
    float4 diffuse;
};

cbuffer cbPerFrame
{
    Light light;
};

cbuffer cbPerObject
{
    float4x4 WVP;
    float4x4 World;

    float4 difColor;
    bool hasTexture;
    bool hasNormMap;
};

Texture2D ObjTexture;
Texture2D ObjNormMap;
SamplerState ObjSamplerState;
TextureCube SkyMap;

struct VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float4 worldPos : POSITION;
    float2 TexCoord : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
};

struct SKYMAP_VS_OUTPUT    //output structure for skymap vertex shader
{
    float4 Pos : SV_POSITION;
    float3 texCoord : TEXCOORD;
};

VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
    VS_OUTPUT output;
    
    output.Pos = mul(inPos, WVP);
    output.worldPos = mul(inPos, World);

    output.normal = mul(normal, World);

    output.tangent = mul(tangent, World);

    output.TexCoord = inTexCoord;

    return output;
}


SKYMAP_VS_OUTPUT SKYMAP_VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
    SKYMAP_VS_OUTPUT output = (SKYMAP_VS_OUTPUT)0;

    //Set Pos to xyww instead of xyzw, so that z will always be 1 (furthest from camera)
    output.Pos = mul(float4(inPos, 1.0f), WVP).xyww;

    output.texCoord = inPos;

    return output;
}

float4 PS(VS_OUTPUT input) : SV_TARGET
{
    input.normal = normalize(input.normal);    

    //Set diffuse color of material
    float4 diffuse = difColor;

    //If material has a diffuse texture map, set it now
    if(hasTexture == true)
        diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );

    //If material has a normal map, we can set it now
    if(hasNormMap == true)
    {
        //Load normal from normal map
        float4 normalMap = ObjNormMap.Sample( ObjSamplerState, input.TexCoord );

        //Change normal map range from [0, 1] to [-1, 1]
        normalMap = (2.0f*normalMap) - 1.0f;

        //Make sure tangent is completely orthogonal to normal
        input.tangent = normalize(input.tangent - dot(input.tangent, input.normal)*input.normal);

        //Create the biTangent
        float3 biTangent = cross(input.normal, input.tangent);

        //Create the "Texture Space"
        float3x3 texSpace = float3x3(input.tangent, biTangent, input.normal);

        //Convert normal from normal map to texture space and store in input.normal
        input.normal = normalize(mul(normalMap, texSpace));
    }

    float3 finalColor;

    finalColor = diffuse * light.ambient;
    finalColor += saturate(dot(light.dir, input.normal) * light.diffuse * diffuse);
    
    return float4(finalColor, diffuse.a);
}

float4 SKYMAP_PS(SKYMAP_VS_OUTPUT input) : SV_Target
{
    return SkyMap.Sample(ObjSamplerState, input.texCoord);
}

float4 D2D_PS(VS_OUTPUT input) : SV_TARGET
{
    float4 diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );
    
    return diffuse; 


728x90
728x90

원문 : http://www.rastertek.com/dx11tut10.html


Tutorial 10: Specular Lighting


이번 강좌는 DirectX 11 에서 HLSL로 specular 조명을 사용해볼 것입니다. 이번 강좌의 코드는 이전 강좌의 코드에서 이어집니다.


Specular 조명은 광원 위치에 대한 시각적 단서를 주기위해 "밝은 곳 강조"의 사용입니다. 예를들어 ambient와 diffuse 조명만 적용된 빨간 구가 다음과 같이 있습니다.



이제 백색 specular 조명을 추가하면 이런 결과를 얻습니다.



Specular 조명은 대부분 흔히 거울이나 아주 윤이나고 반사하는 금속면같은 것에 빛이 반사되는 효과를 주는데 사용됩니다. 또 수면 위 태양빛이 반사하는것과 같은 다른 물질에도 사용됩니다. 잘 사용하면 3D 씬에 사진같은 사실감을 더합니다.


다음은 specular 조명에 대한 식입니다.

	SpecularLighting = SpecularColor * (SpecularColorOfLight * ((NormalVector dot HalfWayVector) power SpecularReflectionPower) * Attentuation * Spotlight)


우리는 다음과 같이 기본 specular 조명을 주기 위해 식을 수정할 것입니다.


	SpecularLighting = SpecularLightColor * (ViewingDirection dot ReflectionVector) power SpecularReflectionPower


이 식에서 반사 벡터(ReflectionVector)는 광도(LightIntensity)의 두배에 법선 벡터를 곱하는 것으로 구해집니다. 빛의 방향(LightDirection)을 빼줌으로 광원과 보는 곳 사이 각을 구할 수 있습니다.


	ReflectionVector = 2 * LightIntensity * VertexNormal - LightDirection


식에서 보는 방향(ViewingDirection)은 카메라 위치(CameraPosition)에 정점 위치(VertexPosition)을 빼줌으로 구해집니다.


	ViewingDirection = CameraPosition - VertexPosition


이제 수정된 라이트 쉐이더가 어떻게 동작하는지 살펴보겠습니다.



Light.vs


////////////////////////////////////////////////////////////////////////////////
// Filename: light.vs
////////////////////////////////////////////////////////////////////////////////


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


카메라 정보를 가질 새 상수 버퍼를 추가하였습니다. 이번 쉐이더에서 specular 계산에서 해당 정점이 어디에서 보여지고 있는지를 결정하기 위해 카메라의 위치가 필요합니다.


cbuffer CameraBuffer
{
    float3 cameraPosition;
    float padding;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};


PixelInputType 구조체는 보는 방향을 버텍스 쉐이더에서 계산하여 specular 조명 계산을 위해 픽셀 쉐이더로 보내도록 수정되었습니다.


struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
    PixelInputType output;
    float4 worldPosition;


    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
	
    // Normalize the normal vector.
    output.normal = normalize(output.normal);


버텍스 쉐이더에서 보는 방향은 여기서 계산됩니다. 정점의 월드 위치를 계산하고 우리가 어디서 보고있는지를 결정하기 위해 카메라 위치에 월드 위치를 빼줍니다. 최종 값은 정규화되어 픽셀 쉐이더로 보내집니다.


    // Calculate the position of the vertex in the world.
    worldPosition = mul(input.position, worldMatrix);

    // Determine the viewing direction based on the position of the camera and the position of the vertex in the world.
    output.viewDirection = cameraPosition.xyz - worldPosition.xyz;
	
    // Normalize the viewing direction vector.
    output.viewDirection = normalize(output.viewDirection);

    return output;
}

Light.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: light.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


라이트 버퍼는 specular 조명 계산을 위한 specularColor와 specularPower값을 가지도록 바뀌었습니다.


cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float3 lightDirection;
    float specularPower;
    float4 specularColor;
};


//////////////
// TYPEDEFS //
//////////////


PixelInputType 구조체는 버텍스 쉐이더에서의 변화를 반영하도록 바뀌었습니다.


struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;
    float3 reflection;
    float4 specular;


    // Sample the pixel color from the texture using the sampler at this texture coordinate location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

    // Set the default output color to the ambient light value for all pixels.
    color = ambientColor;

    // Initialize the specular color.
    specular = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));

    if(lightIntensity > 0.0f)
    {
        // Determine the final diffuse color based on the diffuse color and the amount of light intensity.
        color += (diffuseColor * lightIntensity);

        // Saturate the ambient and diffuse color.
        color = saturate(color);


specular 조명 계산에 대한 반사 벡터는 여기 픽셀쉐이더에서 제공된 광도가 0보다 클때 계산된다. 강좌 초반에 나열된 식과 같습니다.


        // Calculate the reflection vector based on the light intensity, normal vector, and light direction.
        reflection = normalize(2 * lightIntensity * input.normal - lightDir); 


specular 빛의 양은 반사 벡터와 보는 방향 벡터를 이용하여 계산됩니다. 광원과 보는 방향 사이 각이 작을수록 specular 빛 반사는 강해집니다. 결과로 specularPower 값 만큼의 거듭제곱을 취합니다. specularPower 값이 작을수록 최종 효과는 강해집니다. (코사인 함수는 제곱하면 경사도가 커져서 하이라이트 효과범위가 좁아집니다. )


        // Determine the amount of specular light based on the reflection vector, viewing direction, and specular power.
        specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower);
    }

    // Multiply the texture pixel and the input color to get the textured result.
    color = color * textureColor;


마지막 전까지 specular 효과를 더하지 않습니다. 이 효과는 강조이기때문에 마지막 최종 결과값에 더해져야 하며 그렇지 않으면 적절히 보이지 않습니다.


    // Add the specular component last to the output color.
    color = saturate(color + specular);

    return color;
}

Lightshaderclass.h


LightShaderClass는 이전 강좌에서 specular 조명을 처리하도록 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: LightShaderClass
////////////////////////////////////////////////////////////////////////////////
class LightShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};


새로운 카메라 버퍼 구조체를 버텍스 쉐이더의 새 카메라 상수 버퍼와 맞추기 위해 추가하였습니다. CreateBuffer() 함수 실패를 예방하기 위해 16의 배수로 구조체 크기를 맞추도록 패딩값을 추가하는것을 잊지마세요.


	struct CameraBufferType
	{
		D3DXVECTOR3 cameraPosition;
		float padding;
	};


LightBufferType은 픽셀 쉐이더의 라이트 상수 버퍼와 맞추도록 specular color와 specular power를 추가하였습니다. 구조체 크기를 16바이트의 배수로 유지하기 위해 저번 강좌에서는 lightDirection 옆에 패딩값을 넣었는데 이번에는 대신에 specularPower를 위치시킨 것을 주의하시기 바랍니다. specularPower를 맨 아래에 위치시킬 수도 있지만(전체 크기는 맞으니까) lightDirection밑에 패딩이 사용되지 않으면 쉐이더가 올바르게 동작하지 않을 수 있습니다. 그 이유는 비록 구조체는 16바이트의 배수이지만 각 슬롯(구조체내 변수들) 들은 논리적으로 16바이트에 맞추어져 있지 않기때문입니다.


	struct LightBufferType
	{
		D3DXVECTOR4 ambientColor;
		D3DXVECTOR4 diffuseColor;
		D3DXVECTOR3 lightDirection;
		float specularPower;
		D3DXVECTOR4 specularColor;
	};

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

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4, 
		    D3DXVECTOR3, D3DXVECTOR4, float);

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

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4,
				 D3DXVECTOR3, D3DXVECTOR4, float);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11SamplerState* m_sampleState;
	ID3D11Buffer* m_matrixBuffer;


버텍스 쉐이더에 카메라 위치를 설정할 새로운 카메라 상수 버퍼 포인터를 추가하였습니다.


	ID3D11Buffer* m_cameraBuffer;
	ID3D11Buffer* m_lightBuffer;
};

#endif

Lightshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightshaderclass.h"


LightShaderClass::LightShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_sampleState = 0;
	m_matrixBuffer = 0;


생성자에서 카메라 상수 버퍼를 null로 초기화합니다.


	m_cameraBuffer = 0;
	m_lightBuffer = 0;
}


LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}


LightShaderClass::~LightShaderClass()
{
}


bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


void LightShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


Render() 함수는 이제 cameraPosition, specularColor, specularPower 값을 받고 렌더링전 라이트 쉐이더에 활성화 시키도록 SetShaderParameters() 함수로 넘깁니다.


bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
			      D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor,
			      D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, float specularPower)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor, 
				     cameraPosition, specularColor, specularPower);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* pixelShaderBuffer;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D11_SAMPLER_DESC samplerDesc;
	D3D11_BUFFER_DESC matrixBufferDesc;
	D3D11_BUFFER_DESC cameraBufferDesc;
	D3D11_BUFFER_DESC lightBufferDesc;


	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;

	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &pixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
		}
		// If there was nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the pixel shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	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 = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	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;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
	if(FAILED(result))
	{
		return false;
	}


새 카메라 버퍼의 description을 설정하고 그 description을 이용하여 버퍼를 생성합니다. 버텍스 쉐이더에 카메라 위치를 설정할 수 있습니다.


	// Setup the description of the camera dynamic constant buffer that is in the vertex shader.
	cameraBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	cameraBufferDesc.ByteWidth = sizeof(CameraBufferType);
	cameraBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cameraBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	cameraBufferDesc.MiscFlags = 0;
	cameraBufferDesc.StructureByteStride = 0;

	// Create the camera constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&cameraBufferDesc, NULL, &m_cameraBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the light dynamic constant buffer that is in the pixel shader.
	// Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail.
	lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	lightBufferDesc.ByteWidth = sizeof(LightBufferType);
	lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	lightBufferDesc.MiscFlags = 0;
	lightBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


void LightShaderClass::ShutdownShader()
{
	// Release the light constant buffer.
	if(m_lightBuffer)
	{
		m_lightBuffer->Release();
		m_lightBuffer = 0;
	}


ShutdownShader() 함수에서 카메라 상수 버퍼를 해제합니다.


	// Release the camera constant buffer.
	if(m_cameraBuffer)
	{
		m_cameraBuffer->Release();
		m_cameraBuffer = 0;
	}

	// Release the matrix constant buffer.
	if(m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pixel shader.
	if(m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = 0;
	}

	// Release the vertex shader.
	if(m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = 0;
	}

	return;
}


void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


SetShaderParameters() 함수는 입력으로 cameraPosition, specularColor, specularPower 값을 받도록 바뀌었습니다.


bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
					   D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, 
					   D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, 
					   float specularPower)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	unsigned int bufferNumber;
	MatrixBufferType* dataPtr;
	LightBufferType* dataPtr2;
	CameraBufferType* dataPtr3;


	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);


카메라 버퍼를 잠그고 카메라 위치 값을 설정합니다.


	// Lock the camera constant buffer so it can be written to.
	result = deviceContext->Map(m_cameraBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr3 = (CameraBufferType*)mappedResource.pData;

	// Copy the camera position into the constant buffer.
	dataPtr3->cameraPosition = cameraPosition;
	dataPtr3->padding = 0.0f;

	// Unlock the camera constant buffer.
	deviceContext->Unmap(m_cameraBuffer, 0);


카메라 상수 버퍼 설정 전에 bufferNumber를 0대신 1로 설정합니다. 버텍스 쉐이더의 두번째 버퍼이기 때문입니다. (첫번째(0번)는 행렬 버퍼)


	// Set the position of the camera constant buffer in the vertex shader.
	bufferNumber = 1;

	// Now set the camera constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_cameraBuffer);
	
	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);

	// Lock the light constant buffer so it can be written to.
	result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the light constant buffer.
	dataPtr2 = (LightBufferType*)mappedResource.pData;


쉐이더가 specular 조명 계산을 할 수 있도록 라이트 상수 버퍼는 이제 specularColor와 specularPower를 설정합니다.


	// Copy the lighting variables into the light constant buffer.
	dataPtr2->ambientColor = ambientColor;
	dataPtr2->diffuseColor = diffuseColor;
	dataPtr2->lightDirection = lightDirection;
	dataPtr2->specularColor = specularColor;
	dataPtr2->specularPower = specularPower;
	
	// Unlock the light constant buffer.
	deviceContext->Unmap(m_lightBuffer, 0);

	// Set the position of the light constant buffer in the pixel shader.
	bufferNumber = 0;

	// Finally set the light constant buffer in the pixel shader with the updated values.
	deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

	return true;
}


void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}

Lightclass.h


이번 강좌에서 LightClass는 specular 요소와 관련 함수를 포함하도록 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: LightClass
////////////////////////////////////////////////////////////////////////////////
class LightClass
{
public:
	LightClass();
	LightClass(const LightClass&);
	~LightClass();

	void SetAmbientColor(float, float, float, float);
	void SetDiffuseColor(float, float, float, float);
	void SetDirection(float, float, float);
	void SetSpecularColor(float, float, float, float);
	void SetSpecularPower(float);

	D3DXVECTOR4 GetAmbientColor();
	D3DXVECTOR4 GetDiffuseColor();
	D3DXVECTOR3 GetDirection();
	D3DXVECTOR4 GetSpecularColor();
	float GetSpecularPower();

private:
	D3DXVECTOR4 m_ambientColor;
	D3DXVECTOR4 m_diffuseColor;
	D3DXVECTOR3 m_direction;
	D3DXVECTOR4 m_specularColor;
	float m_specularPower;
};

#endif

Lightclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightclass.h"


LightClass::LightClass()
{
}


LightClass::LightClass(const LightClass& other)
{
}


LightClass::~LightClass()
{
}


void LightClass::SetAmbientColor(float red, float green, float blue, float alpha)
{
	m_ambientColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetDirection(float x, float y, float z)
{
	m_direction = D3DXVECTOR3(x, y, z);
	return;
}


void LightClass::SetSpecularColor(float red, float green, float blue, float alpha)
{
	m_specularColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetSpecularPower(float power)
{
	m_specularPower = power;
	return;
}


D3DXVECTOR4 LightClass::GetAmbientColor()
{
	return m_ambientColor;
}


D3DXVECTOR4 LightClass::GetDiffuseColor()
{
	return m_diffuseColor;
}


D3DXVECTOR3 LightClass::GetDirection()
{
	return m_direction;
}


D3DXVECTOR4 LightClass::GetSpecularColor()
{
	return m_specularColor;
}


float LightClass::GetSpecularPower()
{
	return m_specularPower;
}

Graphicsclass.h


GraphicsClass 헤더는 바뀐게 없습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render(float);

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;
	LightShaderClass* m_LightShader;
	LightClass* m_Light;
};

#endif

Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;


	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}

	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}


light 클래스 객체 초기화시 specular color와 specular power를 설정해줍니다. 이번 강좌에서 specular color는 흰색으로하고 specular power는 32로 설정합니다. specular power 값이 작아야 specular 효과 범위가 넓다는걸 기억하세요. (눈부셩)


	// Initialize the light object.
	m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light->SetDirection(0.0f, 0.0f, 1.0f);
	m_Light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light->SetSpecularPower(32.0f);

	return true;
}


void GraphicsClass::Shutdown()
{
	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;
	static float rotation = 0.0f;


	// Update the rotation variable each frame.
	rotation += (float)D3DX_PI * 0.005f;
	if(rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	
	// Render the graphics scene.
	result = Render(rotation);
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render(float rotation)
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Rotate the world matrix by the rotation value so that the triangle will spin.
	D3DXMatrixRotationY(&worldMatrix, rotation);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());


light 쉐이더의 render() 함수는 이제 카메라 위치, specular color, specular power를 받습니다.


	// Render the model using the light shader.
	result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
				       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), 
				       m_Camera->GetPosition(), m_Light->GetSpecularColor(), m_Light->GetSpecularPower());
	if(!result)
	{
		return false;
	}

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}

요약


specular 조명의 추가로 큐브 표면이 카메라가 보는 방향과 고르게 직면할 때마다 밝은 백색 플래쉬를 보실 수 있습니다.



연습하기


1. 재컴파일하고 실행해보고 카메라와 직면하면 밝은 specular 하이라이트가 반짝이며 회전하는 큐브가 보이는지 확인 해 보세요.


2. 광원이 다를때 효과를 보기 위해 빛의 방향을 m_Light->SetDirection(1.0f, 0.0f, 1.0f) 이런식으로 바꿔보세요.


3. 강좌 상단에 구 이미지를 재현하기 위해 폴리곤이 5000개 이상에 빨간 텍스쳐가 적용된 구 모델을 만들어보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut09.html


Tutorial 9: Ambient Lighting


이번 강좌는 HLSL로 DirectX 11에서 주변광 조명처리를 해볼 것입니다.


예를 이용하여 주변광을 설명할 것입니다. 여러분이 한 방에 있다고 상상해 보세요. 그리고 유일한 광원은 창문으로 들어오는 햇빛만 있습니다. 햇빛은 직접적으로 방안의 여러 표면들에 닿지않습니다만 방안 모든 물체는 광자들이 수없이 튕겨 일정량 비춰집니다. 햇빛이 직접 비추지 않는 표면위 조명 효과를 주변광이라고 부릅니다.


주변 조명을 시뮬레이션 하기 위해 간단한 식을 사용합니다. 픽셀 쉐이더의 시작에서 각 픽셀을 주변광의 값으로 설정합니다. 그 이후 모든 연산은 그 주변색에 더해지는 것 뿐입니다. 이 방식으로 주변 색상값을 이용하여 모든 물체가 최소한으로 보이게 합니다.


주변 조명은 3D 씬에 더욱더 현실감을 더합니다. 예를 들어 다음 그림은 X축 +방향으로 분산 조명만 적용되어있습니다.



위 이미지는 현실적으로 보이지 않습니다. 왜냐하면 주변광은 항상 빛이 아주 조금만 비춰도 물체에 적절한 형태를 어디선든 주기 때문입니다. 이제 15% 주변 흰색광을 똑같은 씬에 추가하였더니 다음과 같은 이미지를 얻었습니다.



이제 조금 더 현실적으로 보입니다.


이제 주변 조명을 적용한 코드 변화를 살펴보겠습니다. 이번 강좌는 저번 분산 조명을 사용한 강좌에 기반합니다. 이제 몇몇 변화와 주변광 요소를 추가 할 것입니다.



Light.vs


라이트 쉐이더는 이전 강좌 그대로 분산 조명 쉐이더입니다. 버텍스 쉐이더는 바뀐게 없고 픽셀 쉐이더만 수정되었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: light.vs
////////////////////////////////////////////////////////////////////////////////


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


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};


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

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
	
    // Normalize the normal vector.
    output.normal = normalize(output.normal);

    return output;
}


Light.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: light.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


라이트 상수 버퍼에 float4형 주변광 색상값 변수가 추가되었습니다. 주변광 색상을 외부 클래스에서 쉐이더로 설정할 수 있습니다.


cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float3 lightDirection;
    float padding;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;


    // Sample the pixel color from the texture using the sampler at this texture coordinate location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);


output 색상값을 기본적으로 ambient color(걍 앞으로 원어로 쓰겠습니다.)으로 설정하였습니다. 모든 픽셀은 최소한 ambient color 값으로 비춰집니다. (ambient color 값에따라 최소한으로 보입니다.)


    // Set the default output color to the ambient light value for all pixels.
    color = ambientColor;

    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));


빛 방향 벡터와 법선 벡터의 내적이 0보다 큰지 확인합니다. 크면 diffuse color를 ambient color에 더합니다. 0보다 작으면 diffuse color를 더할 필요가 없습니다. 그 이유는 0보다 작으면 diffuse color가 음수가 되어 ambient color 값을 빼버리기까지 하기 때문입니다.


    if(lightIntensity > 0.0f)
    {
        // Determine the final diffuse color based on the diffuse color and the amount of light intensity.
        color += (diffuseColor * lightIntensity);
    }


ambient과 diffuse의 합은 1보다 클 수 있기때문에 마지막 색깔의 범위를 확실하게 합니다. (saturate 함수는 0보다 작으면 0으로 1보다 크면 1로 합니다.)


    // Saturate the final light color.
    color = saturate(color);

    // Multiply the texture pixel and the final diffuse color to get the final pixel color result.
    color = color * textureColor;

    return color;
}


Lightshaderclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: LightShaderClass
////////////////////////////////////////////////////////////////////////////////
class LightShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};


LightBufferType에 ambient color 요소가 추가되었습니다.


	struct LightBufferType
	{
		D3DXVECTOR4 ambientColor;
		D3DXVECTOR4 diffuseColor;
		D3DXVECTOR3 lightDirection;
		float padding;
	};

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

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4);

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

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11SamplerState* m_sampleState;
	ID3D11Buffer* m_matrixBuffer;
	ID3D11Buffer* m_lightBuffer;
};

#endif


Lightshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightshaderclass.h"


LightShaderClass::LightShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_sampleState = 0;
	m_matrixBuffer = 0;
	m_lightBuffer = 0;
}


LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}


LightShaderClass::~LightShaderClass()
{
}


bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


void LightShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


Render() 함수는 이제 쉐이더에 설정할 ambient color 값도 받습니다.


bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
			      D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor,
			      D3DXVECTOR4 diffuseColor)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* pixelShaderBuffer;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D11_SAMPLER_DESC samplerDesc;
	D3D11_BUFFER_DESC matrixBufferDesc;
	D3D11_BUFFER_DESC lightBufferDesc;


	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;

	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &pixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
		}
		// If there was nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the pixel shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	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 = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	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;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the light dynamic constant buffer that is in the pixel shader.
	// Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail.
	lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	lightBufferDesc.ByteWidth = sizeof(LightBufferType);
	lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	lightBufferDesc.MiscFlags = 0;
	lightBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


void LightShaderClass::ShutdownShader()
{
	// Release the light constant buffer.
	if(m_lightBuffer)
	{
		m_lightBuffer->Release();
		m_lightBuffer = 0;
	}

	// Release the matrix constant buffer.
	if(m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pixel shader.
	if(m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = 0;
	}

	// Release the vertex shader.
	if(m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = 0;
	}

	return;
}


void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


SetShaderParameters() 함수도 이제 ambient color 값을 받습니다.


bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
					   D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, 
					   D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	unsigned int bufferNumber;
	MatrixBufferType* dataPtr;
	LightBufferType* dataPtr2;


	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);

	// Lock the light constant buffer so it can be written to.
	result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr2 = (LightBufferType*)mappedResource.pData;


ambient light color는 light buffer에 맵핑되고 픽셀 쉐이더에 상수로써 설정됩니다.


	// Copy the lighting variables into the constant buffer.
	dataPtr2->ambientColor = ambientColor;
	dataPtr2->diffuseColor = diffuseColor;
	dataPtr2->lightDirection = lightDirection;
	dataPtr2->padding = 0.0f;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_lightBuffer, 0);

	// Set the position of the light constant buffer in the pixel shader.
	bufferNumber = 0;

	// Finally set the light constant buffer in the pixel shader with the updated values.
	deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

	return true;
}


void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}


Lightclass.h


LightClass는 ambient 요소와 관련 함수들을 추가로 가집니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: LightClass
////////////////////////////////////////////////////////////////////////////////
class LightClass
{
public:
	LightClass();
	LightClass(const LightClass&);
	~LightClass();

	void SetAmbientColor(float, float, float, float);
	void SetDiffuseColor(float, float, float, float);
	void SetDirection(float, float, float);

	D3DXVECTOR4 GetAmbientColor();
	D3DXVECTOR4 GetDiffuseColor();
	D3DXVECTOR3 GetDirection();

private:
	D3DXVECTOR4 m_ambientColor;
	D3DXVECTOR4 m_diffuseColor;
	D3DXVECTOR3 m_direction;
};

#endif


Lightclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightclass.h"


LightClass::LightClass()
{
}


LightClass::LightClass(const LightClass& other)
{
}


LightClass::~LightClass()
{
}


void LightClass::SetAmbientColor(float red, float green, float blue, float alpha)
{
	m_ambientColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetDirection(float x, float y, float z)
{
	m_direction = D3DXVECTOR3(x, y, z);
	return;
}


D3DXVECTOR4 LightClass::GetAmbientColor()
{
	return m_ambientColor;
}


D3DXVECTOR4 LightClass::GetDiffuseColor()
{
	return m_diffuseColor;
}


D3DXVECTOR3 LightClass::GetDirection()
{
	return m_direction;
}


Graphicsclass.h


Graphicsclass 헤더는 바뀐게 없습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render(float);

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;
	LightShaderClass* m_LightShader;
	LightClass* m_Light;
};

#endif


Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;


	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}

	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}


ambient light의 강도를 15% 백색광으로 설정합니다. 또 빛 방향도 x축 +방향으로 바꾸어 큐브에 ambient 조명효과를 직접적으로 볼 수 있습니다.


	// Initialize the light object.
	m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light->SetDirection(1.0f, 0.0f, 0.0f);

	return true;
}


void GraphicsClass::Shutdown()
{
	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;
	static float rotation = 0.0f;


회전속도도 반으로 줄여 효과를 더 잘 볼 수 있습니다.


	// Update the rotation variable each frame.
	rotation += (float)D3DX_PI * 0.005f;
	if(rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	
	// Render the graphics scene.
	result = Render(rotation);
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render(float rotation)
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Rotate the world matrix by the rotation value so that the triangle will spin.
	D3DXMatrixRotationY(&worldMatrix, rotation);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());


light 쉐이더는 이제 빛의 ambient색상도 입력으로 받습니다.


	// Render the model using the light shader.
	result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
				       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor());
	if(!result)
	{
		return false;
	}

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}


Summary


더 사실적인 조명효과를 주기위해 ambient lighting의 추가로 모든 면이 최소한으로 비춰집니다.



연습하기


1. 재컴파일후 어두운면도 보이는 회전하는 큐브를 확인 해 보세요.


2. ambient light 값을 (0.0f, 0.0f, 0.0f, 1.0f)로 바꾸어 diffuse light만 다시 확인 해 보세요.


3. color = color * textureColor 픽셀쉐이더에서 이 부분을 주석처리하고 순수 조명효과만 확인 해 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut08.html


Tutorial 8: Loading Maya 2011 Models


이번 강좌는 마야 2011에서 정적 3D 모델을 어떻게 불러올지를 설명할 것입니다. 이번 강좌는 마야에 초점이 맞추어져 있지만 이를 이용하여 다른 많은 3D 모델링 소프트웨어 패키지에 적용할 수 있다는거 아시죠?


이전 강좌에서 우리의 독자 모델 포맷을 만들고 그 포맷을 이요하여 3D 모델을 렌더링해 보았습니다. 이제 다음 목표는 마야 2011 모델을 우리의 독자 포맷으로 변환하여 렌더링하는 것입니다. 이미 인터넷상에 많은 강좌들이 있어서 마야에서 3D 모델을 많드는 것은 설명하지 않습니다. 그래서 텍스쳐가 적용된 삼각형으로 이루어진 3D 모델을 가지고 있는 시점에서 시작할 것입니다.


우리가 이번에 사용할 마야 포맷은 .OBJ 포맷인데 초보자들이 시작하기에 좋고 읽기도 쉽습니다.


여러분의 모델을 .obj 포맷으로 내보내기위해 마야에서 OBJ 익스포터를 활성화해야합니다. "Window"에 "Settings/Preferences"에 "Plug-in Manager"를 누르세요. objExport.mll까지 스크롤을 내리고 "Loaded"와 "Auto load" 둘다 선택하세요. 그리고 이제 모델을 내보내기 위해 "File"에 "Export All"을 누르세요. "Files of type:"에서 밑으로 내리다 보면 "OBJexport"가 있으니 선택하세요. 파일 이름을 입력하고 "Export All"를 누르면 .obj라는 확장자의 텍스트파일이 생성됩니다. 파일을 보기위해 파일에 마우스 오른쪽 버튼을 눌러 "열기"를 누른 뒤 메모장, notepad++, 워드 패드등 아무 에디터로 여세요. 그럼 다음과 같은 텍스트를 보게 될 것입니다.



Cube.obj


# This file uses centimeters as units for non-parametric coordinates.

mtllib cube.mtl
g default
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.998008 0.998008
vt 0.001992 0.998008
vt 0.998008 0.001992
vt 0.001992 0.001992
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
s 1
g pCube1
usemtl file1SG
f 1/1/1 2/2/2 3/3/3
f 3/3/3 2/2/2 4/4/4
s 2
f 3/13/5 4/14/6 5/15/7
f 5/15/7 4/14/6 6/16/8
s 3
f 5/21/9 6/22/10 7/23/11
f 7/23/11 6/22/10 8/24/12
s 4
f 7/17/13 8/18/14 1/19/15
f 1/19/15 8/18/14 2/20/16
s 5
f 2/5/17 8/6/18 4/7/19
f 4/7/19 8/6/18 6/8/20
s 6
f 7/9/21 1/10/22 5/11/23
f 5/11/23 1/10/22 3/12/24


위 .OBJ파일은 3D 큐브를 표현합니다. 8개의 정점을 가지고 24개의 텍스쳐 좌표와 법선 벡터를 가지며 총 12개의 삼각형면으로 구성된 6개의 사각형면이 있습니다. 파일을 변환하면서 "V", "VT", "VN", "F"로 시작하는줄 외의 줄은 무시하여도 됩니다. 나머지 정보는 .obj파일을 우리의 포맷으로 변환하는데 필요없을 것입니다. 이제 각 라인이 의미를 살펴보겠습니다.


1. "V"줄은 정점에 대한 줄입니다. 큐브는 8개의 꼭지점 즉 8개의 정점으로 구성됩니다. 각줄은 X, Y, Z 좌표값이 들어갑니다.


2. "VT"줄은 텍스쳐 좌표에 대한 줄입니다. 큐브는 24개의 텍스쳐 좌표를 가지는데 큐브 모델의 모든 삼각형 정점에 대해 기록하였기 때문에 중복이 있습니다. 각줄은 TU, TV 좌표값이 들어갑니다.


3. "VN"줄은 법선 벡터에 대한 줄입니다. 큐브는 24개의 법선 벡터를 가지는데 텍셀좌표랑 동일하게 모든 삼각형 정점에 대해 기록하였기 때문에 중복이 있습니다. 각줄은 NX, NY, NZ 좌표값이 들어갑니다.


4. "F"줄은 큐브 모델의 각 삼각형(면)에 대한 줄입니다. 나열된 값은 정점, 텍스쳐 좌표, 법선 벡터의 인덱스입니다. 각 면의 포맷은 다음과 같습니다.


f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3


그래서 "f 3/13/5 4/14/6 5/15/7"라인을 해석하면 "Vertex3/Texture13/Normal5 Vertex4/Texture14/Normal6 Vertex5/Texture15/Normal7"입니다.


.obj파일에 나열된 데이터의 방식은 매우 중요합니다. 예를들어 파일의 첫번째 정점은 면 목록에서 Vertex1에 대응합니다. 텍스쳐 좌표와 법선 벡터도 동일합니다.


.obj파일에 면 목록을 보면 각 라인의 세 인덱스 그룹이 삼각형 하나를 이룬다는 것을 아실겁니다. 그리고 이번 큐브 모델의 경우 총 12 삼각형면이 면당 2개의 삼각형을 가진 6개의 사각형면을 이룹니다.



오른손 좌표에서 왼손 좌표로 변환


기본적으로 마야 2011은 오른손 좌표계이며 .obj파일도 오른손 좌표로 내보냅니다. 데이터를 DirectX 11이 기본으로 하는 왼손 좌표계로 변환하기위해 다음의 것들을 해야 합니다.


1. 정점의 Z부호를 반전합니다. 이런 코드를 보게 되실 것입니다 : vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;


2. 텍스쳐 좌표 TV를 반전합니다. 이런 코드를 보게 되실 것입니다 : texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;

(반전한다고 했지만 부호반전이 아닌 OpenGL에서 텍스쳐 좌표는 좌측하단이 (0,0)이라 이런 코드를 사용한 것 같다. ex) opengl (0.0f, 0.7f) == directx (0.0f, 0.3f) )


3. 법선 벡터의 NZ부호를 반전합니다. 이런 코드를 보게 되실 것입니다 : normals[normalIndex].z = normals[normalIndex].z * -1.0f;


4. 그리는 방식을 시계 반대방향에서 시계 방향으로 바꿉니다. 저는 코드에서 간단하게 재배열하는 대신 인덱스를 반대로 읽어들였습니다.


fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3;
fin >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2;
fin >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;


위 4개 작업을 완료하면 DirectX 11이 제대로 렌더링하도록 모델 데이터가 준비될 것입니다.



Main.cpp


마야 2011 .obj파일을 우리의 DirectX 11 포맷으로 변환하는 프로그램은 상당히 간단하고 main.cpp에 다 들어가 있습니다. 커맨드창을 열고 프로그램을 실행시켜 .obj 파일 이름을 입력하세요. 파일 이름을 입력하면 파일을 열고 데이터 개수를 읽고 데이터를 읽어드릴 구조체를 생성합니다. 그 후에 구조체로 데이터를 읽고 왼손 좌표계로 변환합니다. 모든 동작이 완료되면 데이터를 model.txt에 씁니다. 결과 파일의 이름을 변경할 수 있고 이전 강좌에서의 3D 모델 프로젝트를 이용하여 DirectX 11에서 렌더링하는데 사용할 수 있습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: main.cpp
////////////////////////////////////////////////////////////////////////////////


//////////////
// INCLUDES //
//////////////
#include <iostream>
#include <fstream>
using namespace std;


//////////////
// TYPEDEFS //
//////////////
typedef struct
{
	float x, y, z;
}VertexType;

typedef struct
{
	int vIndex1, vIndex2, vIndex3;
	int tIndex1, tIndex2, tIndex3;
	int nIndex1, nIndex2, nIndex3;
}FaceType;


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
void GetModelFilename(char*);
bool ReadFileCounts(char*, int&, int&, int&, int&);
bool LoadDataStructures(char*, int, int, int, int);


//////////////////
// MAIN PROGRAM //
//////////////////
int main()
{
	bool result;
	char filename[256];
	int vertexCount, textureCount, normalCount, faceCount;
	char garbage;


	// Read in the name of the model file.
	GetModelFilename(filename);

	// Read in the number of vertices, tex coords, normals, and faces so that the data structures can be initialized with the exact sizes needed.
	result = ReadFileCounts(filename, vertexCount, textureCount, normalCount, faceCount);
	if(!result)
	{
		return -1;
	}

	// Display the counts to the screen for information purposes.
	cout << endl;
	cout << "Vertices: " << vertexCount << endl;
	cout << "UVs:      " << textureCount << endl;
	cout << "Normals:  " << normalCount << endl;
	cout << "Faces:    " << faceCount << endl;

	// Now read the data from the file into the data structures and then output it in our model format.
	result = LoadDataStructures(filename, vertexCount, textureCount, normalCount, faceCount);
	if(!result)
	{
		return -1;
	}

	// Notify the user the model has been converted.
	cout << "\nFile has been converted." << endl;
	cout << "\nDo you wish to exit (y/n)? ";
	cin >> garbage;

	return 0;
}


void GetModelFilename(char* filename)
{
	bool done;
	ifstream fin;


	// Loop until we have a file name.
	done = false;
	while(!done)
	{
		// Ask the user for the filename.
		cout << "Enter model filename: ";

		// Read in the filename.
		cin >> filename;

		// Attempt to open the file.
		fin.open(filename);

		if(fin.good())
		{
			// If the file exists and there are no problems then exit since we have the file name.
			done = true;
		}
		else
		{
			// If the file does not exist or there was an issue opening it then notify the user and repeat the process.
			fin.clear();
			cout << endl;
			cout << "File " << filename << " could not be opened." << endl << endl;
		}
	}

	return;
}


bool ReadFileCounts(char* filename, int& vertexCount, int& textureCount, int& normalCount, int& faceCount)
{
	ifstream fin;
	char input;


	// Initialize the counts.
	vertexCount = 0;
	textureCount = 0;
	normalCount = 0;
	faceCount = 0;

	// Open the file.
	fin.open(filename);

	// Check if it was successful in opening the file.
	if(fin.fail() == true)
	{
		return false;
	}

	// Read from the file and continue to read until the end of the file is reached.
	fin.get(input);
	while(!fin.eof())
	{
		// If the line starts with 'v' then count either the vertex, the texture coordinates, or the normal vector.
		if(input == 'v')
		{
			fin.get(input);
			if(input == ' ') { vertexCount++; }
			if(input == 't') { textureCount++; }
			if(input == 'n') { normalCount++; }
		}

		// If the line starts with 'f' then increment the face count.
		if(input == 'f')
		{
			fin.get(input);
			if(input == ' ') { faceCount++; }
		}
		
		// Otherwise read in the remainder of the line.
		while(input != '\n')
		{
			fin.get(input);
		}

		// Start reading the beginning of the next line.
		fin.get(input);
	}

	// Close the file.
	fin.close();

	return true;
}


bool LoadDataStructures(char* filename, int vertexCount, int textureCount, int normalCount, int faceCount)
{
	VertexType *vertices, *texcoords, *normals;
	FaceType *faces;
	ifstream fin;
	int vertexIndex, texcoordIndex, normalIndex, faceIndex, vIndex, tIndex, nIndex;
	char input, input2;
	ofstream fout;


	// Initialize the four data structures.
	vertices = new VertexType[vertexCount];
	if(!vertices)
	{
		return false;
	}

	texcoords = new VertexType[textureCount];
	if(!texcoords)
	{
		return false;
	}

	normals = new VertexType[normalCount];
	if(!normals)
	{
		return false;
	}

	faces = new FaceType[faceCount];
	if(!faces)
	{
		return false;
	}

	// Initialize the indexes.
	vertexIndex = 0;
	texcoordIndex = 0;
	normalIndex = 0;
	faceIndex = 0;

	// Open the file.
	fin.open(filename);

	// Check if it was successful in opening the file.
	if(fin.fail() == true)
	{
		return false;
	}

	// Read in the vertices, texture coordinates, and normals into the data structures.
	// Important: Also convert to left hand coordinate system since Maya uses right hand coordinate system.
	fin.get(input);
	while(!fin.eof())
	{
		if(input == 'v')
		{
			fin.get(input);

			// Read in the vertices.
			if(input == ' ') 
			{ 
				fin >> vertices[vertexIndex].x >> vertices[vertexIndex].y >> vertices[vertexIndex].z;

				// Invert the Z vertex to change to left hand system.
				vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;
				vertexIndex++; 
			}

			// Read in the texture uv coordinates.
			if(input == 't') 
			{ 
				fin >> texcoords[texcoordIndex].x >> texcoords[texcoordIndex].y;

				// Invert the V texture coordinates to left hand system.
				texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;
				texcoordIndex++; 
			}

			// Read in the normals.
			if(input == 'n') 
			{ 
				fin >> normals[normalIndex].x >> normals[normalIndex].y >> normals[normalIndex].z;

				// Invert the Z normal to change to left hand system.
				normals[normalIndex].z = normals[normalIndex].z * -1.0f;
				normalIndex++; 
			}
		}

		// Read in the faces.
		if(input == 'f') 
		{
			fin.get(input);
			if(input == ' ')
			{
				// Read the face data in backwards to convert it to a left hand system from right hand system.
				fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3
				    >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2
				    >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;
				faceIndex++;
			}
		}

		// Read in the remainder of the line.
		while(input != '\n')
		{
			fin.get(input);
		}

		// Start reading the beginning of the next line.
		fin.get(input);
	}

	// Close the file.
	fin.close();

	// Open the output file.
	fout.open("model.txt");
	
	// Write out the file header that our model format uses.
	fout << "Vertex Count: " << (faceCount * 3) << endl;
	fout << endl;
	fout << "Data:" << endl;
	fout << endl;

	// Now loop through all the faces and output the three vertices for each face.
	for(int i=0; i<faceIndex; i++)
	{
		vIndex = faces[i].vIndex1 - 1;
		tIndex = faces[i].tIndex1 - 1;
		nIndex = faces[i].nIndex1 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

		vIndex = faces[i].vIndex2 - 1;
		tIndex = faces[i].tIndex2 - 1;
		nIndex = faces[i].nIndex2 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

		vIndex = faces[i].vIndex3 - 1;
		tIndex = faces[i].tIndex3 - 1;
		nIndex = faces[i].nIndex3 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;
	}

	// Close the output file.
	fout.close();

	// Release the four data structures.
	if(vertices)
	{
		delete [] vertices;
		vertices = 0;
	}
	if(texcoords)
	{
		delete [] texcoords;
		texcoords = 0;
	}
	if(normals)
	{
		delete [] normals;
		normals = 0;
	}
	if(faces)
	{
		delete [] faces;
		faces = 0;
	}

	return true;
}



요약


우리는 이제 마야 2011 .obj 파일을 우리의 독자 모델 포맷으로 변환할 수 있습니다!



연습하기


1. 프로그램을 컴파일하고 위 .obj 모델 파일을 변환해 보세요.


2. 마야 2011 모델을 만들어 보고(가지고 있는 거 있으시면 그거 쓰시면 됩니다.) .obj 포맷으로 내보낸 뒤 변환프로그램으로 변환해 보세요.


3. 코드를 변형해서 다른 모델 포맷을 변환해 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut07.html


Tutorial 7: 3D Model Rendering


이번 강좌는 HLSL을 이용하여 DirectX 11에서 어떻게 3D 모델을 렌더링하는지를 포함합니다.


이전 강좌에서 이미 3D 모델을 렌더링 해 보았지만 달랑 삼각형 하나라 별로 흥미롭지 못했습니다. 그래서 이번에는 더 복잡한 오브젝트를 렌더링하기 위해 한 발짝 더 움직여 볼 것입니다. 이번 오브젝트는 바로 큐브(정육면체)입니다. 복잡한 모델을 어떻게 렌더링할지 논의하기 전에 먼저 모델 포맷에 대해 알아볼 것입니다.


시중에 3D 모델을 만들수 있는 많은 툴들이 있습니다. 마야나 3D Stuio Max가 대표적인 3D 모델링 프로그램입니다. 또 기능은 저 두개에 비해 적지만 우리가 필요한 만큼의 기능을 갖춘 다른 툴들도 있습니다.


어느 툴을 사용하는지에 상관없이 대부분 만든 3D 모델을 많은 다른 포맷들로 변환하여 저장할 수 있습니다. 제가 제안하는 것은 독자적 모델 포맷을 만들고 다른 대표 포맷에서 독자적 포맷으로 변환시키는 "파서(parser)"를 작성하는 것입니다. 그 이유는 여러분이 사용하는 3D 모델링 패키지는 시간이 지나면 바뀌고 그 모델 포맷도 맞추어 바뀝니다. 그리고 여러분은 아마도 3D 모델링 패키지를 여러개 사용중이실 것이고 따라서 여러 다른 포맷들을 가지실 겁니다. 그래서 만약 여러분이 독자 포맷을 가지며 다른 포맷을 독자 포맷으로 변환하여 쓴다면 여러분의 코드는 바뀌지 않을 것입니다. 파서 프로그램만 수정해주면 됩니다. 뿐만 아니라 대부분의 3D 모델링 패키지들은 해당 프로그램에만 유용하고 여러분의 포맷에 필요치 않은 자질구레한 부분도 포맷에 들어가기 때문이기도 합니다.


독자 포맷을 만드는 것에 대한 중요한 점은 필요한 모든 것을 포함시킬 수 있고 사용하기 편리합니다. 또 애니메이션 데이터를 가지는 혹은 정적인 것을 가지는 등과 같은 다른 특성의 오브젝트들에 대해 다른 포맷을 만드는 것을 생각해 볼수도 있습니다.


제가 만들 모델 포맷은 매우 기초적입니다. 모델의 각 정점을 가지는 라인을 포함할 것입니다. 각 라인은 법선 벡터, 텍셀 좌표, 위치 벡터 등을 가진 코드에서 버텍스 포맷(쉐이더에서 VertexType)과 일치해야 합니다. 이 포맷은 맨 위에 정점 개수를 가지고 있어 첫 라인을 읽고 Data부분을 읽기전에 필요한 메모리를 확보할 수 있습니다. 또 이 포맷은 모든 3줄마다 삼각형(맨 위에서부터 3줄씩)을 이루어야 하며, 모델 포맷의 정점들은 시계방향으로 표현되야 합니다. 여기 우리가 렌더링할 큐브의 모델 파일이 있습니다.




Cube.txt


Vertex Count: 36

Data:

-1.0  1.0 -1.0 0.0 0.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
 1.0 -1.0 -1.0 1.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 0.0 0.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0  1.0 1.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 0.0 0.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
-1.0 -1.0  1.0 1.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 0.0 0.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0 -1.0 1.0 1.0 -1.0  0.0  0.0
-1.0  1.0  1.0 0.0 0.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
 1.0  1.0 -1.0 1.0 1.0  0.0  1.0  0.0
-1.0 -1.0 -1.0 0.0 0.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
 1.0 -1.0  1.0 1.0 1.0  0.0 -1.0  0.0


보다시피 36줄의 x, y, z, tu, tv, nx, ny, nz(위치 벡터, 텍셀 좌표, 법선 벡터) 데이터가 있습니다. 매 3줄이 삼각형을 구성하여 총 12개의 삼각형이 큐브를 형성합니다. 포맷은 매우 직관적이고 바로 버텍스 버퍼로 읽어들일 수 있으며 어떠한 수정없이 렌더링할 수 있습니다.


그런데 하나 조심할 점은 몇몇 3D 모델링 프로그램은 데이터를 다른방식으로 내보내는데 예컨데 왼손좌표계나 오른손좌표계가 있습니다. 기본적으로 DirectX 11은 왼손좌표계(보통 수학에서의 좌표와 Z축이 반대)이며 모델 데이터도 이와 맞추어야 합니다. 이 차이를 주의하여 여러분의 파싱 프로그램이 데이터를 올바른 포맷/방식으로 변환되는지 확실히 하시기 바랍니다.



Modelclass.h


이번 강좌를 위해 우리가 할 것은 ModelClass가 우리의 텍스트 모델 파일로부터 3D 모델을 렌더링하도록 조금 수정하는 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


모델 텍스트 파일에서 읽는 것을 처리하기 위해 fstream 라이브러리를 인클루드하였습니다.


#include <fstream>
using namespace std;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
		D3DXVECTOR3 normal;
	};


다음은 모델 포맷을 표현할 새 구조체의 추가입니다. ModelType이라 부르는 이 구조체는 모델 파일 포맷과 같이 위치, 텍스쳐, 법선 벡터를 포함합니다.


	struct ModelType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

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


Initialize() 함수는 로딩될 모델의 파일 이름 문자열을 입력으로 받습니다.


	bool Initialize(ID3D11Device*, char*, WCHAR*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);

	int GetIndexCount();
	ID3D11ShaderResourceView* GetTexture();


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

	bool LoadTexture(ID3D11Device*, WCHAR*);
	void ReleaseTexture();


또 텍스트 파일로부터 모델 데이터를 로딩하고 언로딩하는 함수 두개를 추가하였습니다.


	bool LoadModel(char*);
	void ReleaseModel();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	TextureClass* m_Texture;


마지막으로 ModelType 구조체의 배열이 될 m_model이라 하는 private 멤버가 추가되었습니다. 이 변수는 모델 데이터를 버텍스 버퍼로 읽어들이기 전에 먼저 읽고 유지하는데 사용될 것입니다.


	ModelType* m_model;
};

#endif



Modelclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_Texture = 0;


생성자에서 새 모델 구조체를 null로 설정합니다.


	m_model = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}


Initialize() 함수는 로딩될 모델의 파일 이름을 입력으로 받습니다.


bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename)
{
	bool result;


Initialize() 함수에서 이제 LoadModel() 함수를 가장먼저 호출합니다. 파일 이름에서 모델 데이터를 로딩하여 m_model 배열에 제공할 것입니다. 이 배열이 채워지면 배열을 이용하여 버텍스, 인덱스 버퍼를 생성할 수 있습니다. 이제 InitializeBuffers() 함수는 이 모델 데이터에 의존하기 때문에 함수가 올바르게 호출되었는지 확실히 해야합니다.


	// Load in the model data,
	result = LoadModel(modelFilename);
	if(!result)
	{
		return false;
	}

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if(!result)
	{
		return false;
	}

	// Load the texture for this model.
	result = LoadTexture(device, textureFilename);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{
	// Release the model texture.
	ReleaseTexture();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();


Shutdown() 함수에서는 다 쓴 m_model 배열 데이터를 지우기 위해 ReleaseModel() 호출을 추가합니다.


	// Release the model data.
	ReleaseModel();

	return;
}


void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(deviceContext);

	return;
}


int ModelClass::GetIndexCount()
{
	return m_indexCount;
}


ID3D11ShaderResourceView* ModelClass::GetTexture()
{
	return m_Texture->GetTexture();
}


bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


우리는 더 이상 손수 버텍스, 인덱스 카운트를 설정하지 않아도 됩니다. 대신에 ModelClass::LoadModel() 함수만 있으면 만사 OK.


	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[m_indexCount];
	if(!indices)
	{
		return false;
	}


버텍스, 인덱스 배열 로딩 부분은 조금 바뀌었습니다. 직접 값을 설정하는 대신 m_model 배열에서 모든 요소를 돌며 데이터를 정점 배열로 복사합니다. 인덱스 배열은 만들기 쉽습니다. 우리가 로딩한 정점의 배열 위치가 곧 인덱스 숫자와 같습니다.


	// Load the vertex array and index array with data.
	for(i=0; i<m_vertexCount; i++)
	{
		vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z);
		vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv);
		vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz);

		indices[i] = i;
	}

	// Set up the description of the static vertex buffer.
	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;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Set up the description of the static index buffer.
	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;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	return true;
}


void ModelClass::ShutdownBuffers()
{
	// Release the index buffer.
	if(m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if(m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}

	return;
}


void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType); 
	offset = 0;
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}


bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
	bool result;


	// Create the texture object.
	m_Texture = new TextureClass;
	if(!m_Texture)
	{
		return false;
	}

	// Initialize the texture object.
	result = m_Texture->Initialize(device, filename);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::ReleaseTexture()
{
	// Release the texture object.
	if(m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}


이것이 바로 텍스트 파일에서 m_model 배열 변수로 모델 데이터를 로딩하는 LoadModel() 함수입니다. 먼저 텍스트 파일을 열고 정점 개수를 읽습니다. 정점 개수를 읽은 후 ModelType 배열을 생성하고 각 라인을 배열로 읽어들입니다. 정점, 인덱스 수가 이 함수에서 설정됩니다.


bool ModelClass::LoadModel(char* filename)
{
	ifstream fin;
	char input;
	int i;


	// Open the model file.
	fin.open(filename);
	
	// If it could not open the file then exit.
	if(fin.fail())
	{
		return false;
	}

	// Read up to the value of vertex count.
	fin.get(input);
	while(input != ':')
	{
		fin.get(input);
	}

	// Read in the vertex count.
	fin >> m_vertexCount;

	// Set the number of indices to be the same as the vertex count.
	m_indexCount = m_vertexCount;

	// Create the model using the vertex count that was read in.
	m_model = new ModelType[m_vertexCount];
	if(!m_model)
	{
		return false;
	}

	// Read up to the beginning of the data.
	fin.get(input);
	while(input != ':')
	{
		fin.get(input);
	}
	fin.get(input);
	fin.get(input);

	// Read in the vertex data.
	for(i=0; i<m_vertexCount; i++)
	{
		fin >> m_model[i].x >> m_model[i].y >> m_model[i].z;
		fin >> m_model[i].tu >> m_model[i].tv;
		fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz;
	}

	// Close the model file.
	fin.close();

	return true;
}


ReleaseModel() 함수는 모델 데이터 배열 제거를 처리합니다.


void ModelClass::ReleaseModel()
{
	if(m_model)
	{
		delete [] m_model;
		m_model = 0;
	}

	return;
}



Graphicsclass.h


GraphicsClass의 헤더는 전 강좌에서 변한 게 없습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render(float);

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;
	LightShaderClass* m_LightShader;
	LightClass* m_Light;
};

#endif



Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;


	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}


모델 initialization() 함수는 로딩하는 모델 파일의 이름을 받습니다. 이번 강좌에서 우리는 cube.txt파일을 사용할 것입니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}


이번 강좌에는 분산광 색상을 흰색으로 바꾸었습니다.


	// Initialize the light object.
	m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light->SetDirection(0.0f, 0.0f, 1.0f);

	return true;
}


void GraphicsClass::Shutdown()
{
	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;
	static float rotation = 0.0f;


	// Update the rotation variable each frame.
	rotation += (float)D3DX_PI * 0.01f;
	if(rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	
	// Render the graphics scene.
	result = Render(rotation);
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render(float rotation)
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Rotate the world matrix by the rotation value so that the triangle will spin.
	D3DXMatrixRotationY(&worldMatrix, rotation);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());

	// Render the model using the light shader.
	result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
				       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
	if(!result)
	{
		return false;
	}

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}



요약


ModelClass에 변화로 이제 3D 모델을 로딩하고 렌더링할 수 있습니다. 여기서 사용된 포맷은 기본적인 움직이지 않는 오브젝트를 위함이지만 모델 포맷이 어떻게 동작하는지를 이해하는데에는 좋은 시작입니다.



연습하기


1. 코드를 재컴파일 하고 프로그램을 실행해 보세요. 여러분은 같은 텍스쳐에 회전하는 큐브르르 확인하실 수 있습니다. Esc키로 종료하세요.


2. 괜찮은 3D 모델링 패키지(왠만하면 공짜이길..)를 찾고 간단한 모델을 만든 뒤 파일로 내보내세요. 포맷을 한번 살펴보세요.


3. 다른 포맷의 모델을 이번 강좌에서 사용된 포맷으로 변환하는 간단한 파서 프로그램을 작성해 보세요. 그리고 여러분의 모델로 cube.txt를 대체하고 실행 해 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut06.html


Tutorial 6: Diffuse Lighting


이번 강좌에서는 분산 조명과 DirectX 11을 이용하여 3D 물체를 어떻게 비추는지를 설명할 것입니다. 저번 강좌의 코드에 이어서 고쳐나가보도록 하겠습니다.


우리가 도입할 "분산 조명"은 방향 조명이라고 부릅니다. 방향성이 있는 빛은 마치 태양이 우리 지구를 비추는것과 비슷하게 느껴집니다. 광원이 굉장히 멀리 있어서 방향에 근거하여 물체에 비추는 빛의 양을 알 수 있는 빛을 보내고 있습니다. (태양에서 빛이 온다는 것이 명확하다.) 그와 달리 "주변광"(곧 보게 될)은 물체의 표면을 비추지 않고 직접적으로 닿지도 않습니다.


저는 조명효과를 방향 조명으로 시작하기로 맘을 먹었습니다. 시각적으로 디버깅이 쉽기 때문입니다. 또 빛의 방향만 알면 되기 때문에 다른 타입의 주변광들 예컨데 스포트라이트나 포인트 라이트 보다 공식이 간단한 이유도 있습니다.


DirectX 11에서 분산 조명의 시행은 버텍스, 픽셀 쉐이더만 있으면 만사 오키무띠 입니다. 분산광은 우리가 비출 폴리곤에 대한 빛의 방향과 법선 벡터만 알면 됩니다. (미리 얘기하자면 법선 벡터와 빛의 방향 벡터의 내적을 이용한 코사인값을 얻어서 "램버트 코사인 법칙"에 따라 픽셀 색상 감쇠.) 방향은 단일 벡터이고 법선 벡터는 폴리곤을 구성하는 정점 3개를 이용하여 어느 폴리곤이든 계산할 수 있습니다. 이번 강좌에서는 조명 방정식에서 분산광의 색상도 넣어보겠습니다.



Framework


이번 강좌에는 씬에서 광원을 표현할 새로운 LightClass 클래스를 만들 것입니다. LightClass는 빛의 색상과 방향외 다른 어떤것도 하지 않을 것입니다. 또 TextureShaderClass(쉐이더좀 내버려 둬)를 지우고 모델에 빛을 입히는 작업을 처리할 LightShaderClass로 대체할 것입니다. 새로 추가된 클래스를 포함하여 프레임워크는 다음과 같이 될 것입니다.



먼저 HLSL 라이트 쉐이더부터 살펴보겠습니다. 라이트 쉐이더는 이전 강좌의 텍스쳐 쉐이더를 업데이트한 버전입니다.



Light.vs


////////////////////////////////////////////////////////////////////////////////
// Filename: light.vs
////////////////////////////////////////////////////////////////////////////////


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


두 구조체에 float3형 법선 벡터가 추가되었습니다. 법선 벡터는 법선 벡터는 빛의 방향 벡터 사이각을 이용하여 빛의 양을 계산하는데 사용됩니다.


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};


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

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;


정점에 대한 법선 벡터는 월드 좌표에서 계산되고 픽셀 쉐이더로 보내기 전에 정규화(해당 방향에대한 단위벡터로 만든다.) 합니다.


    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
	
    // Normalize the normal vector.
    output.normal = normalize(output.normal);

    return output;
}



Light.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: light.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


LightBuffer안에 빛의 분산 방향과 색상을 가지는 변수 두개가 있습니다. 이 두 변수는 LightClass 객체의 변수로부터 설정됩니다.


cbuffer LightBuffer
{
    float4 diffuseColor;
    float3 lightDirection;
    float padding;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;


    // Sample the pixel color from the texture using the sampler at this texture coordinate location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);


아까 얘기한 조명 방정식이 시행되는 곳입니다. 빛 세기 값이 삼각형의 법선 벡터와 빛의 방향 벡터의 내적으로 계산됩니다.


    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    // saturate() 함수는 입력값의 범위를 0~1로 한다.
    lightIntensity = saturate(dot(input.normal, lightDir));


마지막으로 결과 색상을 구하기 위해 빛의 분산값을 텍스쳐 픽셀 값이랑 결합합니다.


    // Determine the final amount of diffuse color based on the diffuse color combined with the light intensity.
    color = saturate(diffuseColor * lightIntensity);

    // Multiply the texture pixel and the final diffuse color to get the final pixel color result.
    color = color * textureColor;

    return color;
}



Lightshaderclass.h


새 LightShaderClass는 이전 강좌의 TextureShaderClass를 조명 부분을 포함하기 위해 아주 쬐끔 수정된 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: LightShaderClass
////////////////////////////////////////////////////////////////////////////////
class LightShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};


LightBufferType 구조체는 조명 정보를 가지는데 사용됩니다. 이 typedef은 픽셀 쉐이더의 LightBuffer와 같습니다. 버퍼를 16의 배수로 하기위해 padding 변수를 추가한 것을 주의하시기 바랍니다. 이 추가 float 변수가 없으면 구조체 크기가 28바이트인데 CreateBuffer()를 호출할때 버퍼사이즈가 16의 배수가 아니면 실패하기 때문입니다.


* 참조 : https://msdn.microsoft.com/en-us/library/windows/desktop/ff476501(v=vs.85).aspx


	struct LightBufferType
	{
		D3DXVECTOR4 diffuseColor;
		D3DXVECTOR3 lightDirection;
		float padding;  // Added extra padding so structure is a multiple of 16 for CreateBuffer function requirements.
	};

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

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);

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

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11SamplerState* m_sampleState;
	ID3D11Buffer* m_matrixBuffer;


여기 조명 정보(색상, 방향)를 위한 새 private 상수 버퍼가 생겼습니다. 라이트 버퍼는 이 클래스에서 전역 라이트 변수를 HLSL 픽셀 쉐이더로 설정하여 보내는데 사용될 것입니다.


	ID3D11Buffer* m_lightBuffer;
};

#endif



Lightshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightshaderclass.h"


LightShaderClass::LightShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_sampleState = 0;
	m_matrixBuffer = 0;


클래스 생성자에서 라이트 상수 버퍼를 null로 설정합니다.


	m_lightBuffer = 0;
}


LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}


LightShaderClass::~LightShaderClass()
{
}


bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


light.vs, lights.ps 쉐이더 파일은 쉐이더 초기화 함수의 입력으로 사용됩니다.


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


void LightShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


Render() 함수는 이제 lightDirection과 diffuseColor도 추가로 입력받습니다. 그리고 이 변수를 SetShaderParameters() 함수로 보내지고 결국 쉐이더 내부에 설정됩니다.


bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
			      D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* pixelShaderBuffer;


polygonLayout은 이제 요소개수가 2개 -> 3개로 바뀌었습니다. 법선 벡터가 추가되어서 그거에 맞추기 위함입니다.


	D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D11_SAMPLER_DESC samplerDesc;
	D3D11_BUFFER_DESC matrixBufferDesc;


또 라이트 상수 버퍼를 위한 새로운 description 변수를 추가하였습니다.


	D3D11_BUFFER_DESC lightBufferDesc;


	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;


새 라이트 버텍스 쉐이더를 로딩합니다.


	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}


새 라이트 픽셀 쉐이더를 로딩합니다.


	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
				       &pixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
		}
		// If there was nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the pixel shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	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 = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;


쉐이더 초기화에 가장 큰 변화중 하나는 바로 여기 polygonLayou에 있습니다. 조명 계산에 사용할 법선 벡터를 새번째 요소로 추가하였습니다. 시맨틱 이름은 NORMAL이고 포맷은 법선 벡터의 x, y, z 3개의 실수(float; 4바이트)를 처리하는 DXGI_FORMAT_R32G32B32_FLOAT 입니다. 이제 이 레이아웃은 HLSL 버텍스 쉐이더의 입력 타입과 일치할 것입니다.


	polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	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;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
	if(FAILED(result))
	{
		return false;
	}


여기 빛의 분산과 방향을 처리할 라이트 상수 버퍼 description를 설정합니다.  여기서 상수 버퍼 사이즈에 주의해야 하는데 사이즈가 16의 배수가 아니면 맞추도록 추가적인메모리공간을 넣어야 합니다. 안그럼 CreateBuffer() 함수가 실패합니다. 이변 강좌의 경우 상수 버퍼 크기가 28인데 4바이트 padding 변수를 합하여 32바이트를 만들었습니다.


	// Setup the description of the light dynamic constant buffer that is in the pixel shader.
	// Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail.
	lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	lightBufferDesc.ByteWidth = sizeof(LightBufferType);
	lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	lightBufferDesc.MiscFlags = 0;
	lightBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


void LightShaderClass::ShutdownShader()
{


새 라이트 상수 버퍼는 ShutdownShader() 함수에서 해제됩니다.


	// Release the light constant buffer.
	if(m_lightBuffer)
	{
		m_lightBuffer->Release();
		m_lightBuffer = 0;
	}

	// Release the matrix constant buffer.
	if(m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pixel shader.
	if(m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = 0;
	}

	// Release the vertex shader.
	if(m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = 0;
	}

	return;
}


void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


SetShaderParameters() 함수는 lgihtDirection과 diffuseColore를 입력받도록 바뀌었습니다.


bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
					   D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, 
					   D3DXVECTOR4 diffuseColor)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	unsigned int bufferNumber;
	MatrixBufferType* dataPtr;
	LightBufferType* dataPtr2;


	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);


라이트 상수 버퍼는 행렬 상수 버퍼와 동일한 방법으로 설정됩니다. 먼저 버퍼를 락하고 포인터를 얻습니다. 그리고 그 포인터를 이용하여 분산색상과 빛 방향을 설정합니다. 데이터가 설정되고 나면 버퍼의 락을 풀고 픽셀 쉐이더에 넣습니다. 여기서 주의할 점은 VSSetConstantBuffers() 함수가 아니라 PSSetConstantBuffers() 함수 입니다. 설정하는 곳이 픽셀 쉐이더이기 때문입니다.


	// Lock the light constant buffer so it can be written to.
	result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr2 = (LightBufferType*)mappedResource.pData;

	// Copy the lighting variables into the constant buffer.
	dataPtr2->diffuseColor = diffuseColor;
	dataPtr2->lightDirection = lightDirection;
	dataPtr2->padding = 0.0f;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_lightBuffer, 0);

	// Set the position of the light constant buffer in the pixel shader.
	bufferNumber = 0;

	// Finally set the light constant buffer in the pixel shader with the updated values.
	deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

	return true;
}


void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}



Modelclass.h


ModelClass는 조명 처리를 위해 조금 수정되었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:


VertexType 구조체에 법선 벡터 변수가 추가되었습니다.


	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
		D3DXVECTOR3 normal;
	};

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

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

	int GetIndexCount();
	ID3D11ShaderResourceView* GetTexture();


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

	bool LoadTexture(ID3D11Device*, WCHAR*);
	void ReleaseTexture();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	TextureClass* m_Texture;
};

#endif



Modelclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_Texture = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}


bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename)
{
	bool result;


	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if(!result)
	{
		return false;
	}

	// Load the texture for this model.
	result = LoadTexture(device, textureFilename);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{
	// Release the model texture.
	ReleaseTexture();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	return;
}


void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(deviceContext);

	return;
}


int ModelClass::GetIndexCount()
{
	return m_indexCount;
}


ID3D11ShaderResourceView* ModelClass::GetTexture()
{
	return m_Texture->GetTexture();
}


bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;


	// Set the number of vertices in the vertex array.
	m_vertexCount = 3;

	// Set the number of indices in the index array.
	m_indexCount = 3;

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[m_indexCount];
	if(!indices)
	{
		return false;
	}


InitializeBuffers() 함수에서 유일한 변화는 여기 정점 설정입니다. 각 정점은 조명 계산에 관련된 법선을 가집니다. 법선은 폴리곤의 면이 보는 정확한 방향이 계산될 수 있도록 폴리곤의 면에 직각을 이룹니다. 간단하게 각 정점의 z요소를 -1.0f로 즉 카메라를 향하도록 Z축을 따르게 설정합니다.


	// Load the vertex array with data.
	vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // Bottom left.
	vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
	vertices[0].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f);

	vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // Top middle.
	vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);
	vertices[1].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f);

	vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // Bottom right.
	vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
	vertices[2].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f);

	// Load the index array with data.
	indices[0] = 0;  // Bottom left.
	indices[1] = 1;  // Top middle.
	indices[2] = 2;  // Bottom right.

	// Set up the description of the static vertex buffer.
	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;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Set up the description of the static index buffer.
	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;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	return true;
}


void ModelClass::ShutdownBuffers()
{
	// Release the index buffer.
	if(m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if(m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}

	return;
}


void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType); 
	offset = 0;
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}


bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
	bool result;


	// Create the texture object.
	m_Texture = new TextureClass;
	if(!m_Texture)
	{
		return false;
	}

	// Initialize the texture object.
	result = m_Texture->Initialize(device, filename);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::ReleaseTexture()
{
	// Release the texture object.
	if(m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}



Lightclass.h


이제 아주 간단한 light 클래스를 살펴보죠. 이 클래스의 목적은 빛의 방향과 색상을 유지하는것이 다 입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: LightClass
////////////////////////////////////////////////////////////////////////////////
class LightClass
{
public:
	LightClass();
	LightClass(const LightClass&);
	~LightClass();

	void SetDiffuseColor(float, float, float, float);
	void SetDirection(float, float, float);

	D3DXVECTOR4 GetDiffuseColor();
	D3DXVECTOR3 GetDirection();

private:
	D3DXVECTOR4 m_diffuseColor;
	D3DXVECTOR3 m_direction;
};

#endif



Lightclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightclass.h"


LightClass::LightClass()
{
}


LightClass::LightClass(const LightClass& other)
{
}


LightClass::~LightClass()
{
}


void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
	return;
}


void LightClass::SetDirection(float x, float y, float z)
{
	m_direction = D3DXVECTOR3(x, y, z);
	return;
}


D3DXVECTOR4 LightClass::GetDiffuseColor()
{
	return m_diffuseColor;
}


D3DXVECTOR3 LightClass::GetDirection()
{
	return m_direction;
}



Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"


GraphicsClass는 새로운 클래스 LightShaderClass와 LightClass를 위한 헤더를 인클루드합니다.


#include "lightshaderclass.h"
#include "lightclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:


Render() 함수는 입력으로 float 하나를 받도록 바뀌었습니다.


	bool Render(float);

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;


라이트 쉐이더와 라이트 객체를 위한 새 private 속성 멤버 두개가 추가되었습니다.


	LightShaderClass* m_LightShader;
	LightClass* m_Light;
};

#endif



Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;


라이트 쉐이더와 라이트 객체는 생성자에서 null로 초기화 됩니다.


	m_LightShader = 0;
	m_Light = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;


	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}

	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}


라이트 쉐이더 객체는 여기서 생성되고 초기화 됩니다.


	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}


라이트 객체는 여기서 생성됩니다.


	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}


빛 색상은 보라색으로 설정하고 빛 방향은 Z축 +방향으로 설정합니다.


	// Initialize the light object.
	m_Light->SetDiffuseColor(1.0f, 0.0f, 1.0f, 1.0f);
	m_Light->SetDirection(0.0f, 0.0f, 1.0f);

	return true;
}


void GraphicsClass::Shutdown()
{


Shutdown() 함수는 라이트, 라이트 쉐이더 객체를 해제합니다.


	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;


매 프레임마다 Render() 함수로 넘길 회전값을 유지할 새로운 정적 변수를 추가합니다.


	static float rotation = 0.0f;


	// Update the rotation variable each frame.
	rotation += (float)D3DX_PI * 0.01f;
	if(rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	
	// Render the graphics scene.
	result = Render(rotation);
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render(float rotation)
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);


여기서 위에서 설정한 회전값으로 월드 행렬을 회전함으로 삼각하여 렌더링시 회전값에 따라 돌아가는 삼각형을 보실 수 있습니다.


	// Rotate the world matrix by the rotation value so that the triangle will spin.
	D3DXMatrixRotationY(&worldMatrix, rotation);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());


라이트 쉐이더는 삼각형 렌더링을 위해 여기서 호출됩니다. 라이트 객체는 라이트 쉐이더의 Render() 함수로 빛 색상과 방향을 보내어 쉐이더가 값을 접근할 수 있게 합니다.


	// Render the model using the light shader.
	result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
				       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
	if(!result)
	{
		return false;
	}

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}



요약


코드 수정은 별로 하지 않았지만 기본 방향 조명을 적용할 수 있게 되었습니다. (짝짝짝!) 법선 벡터가 어떻게 동작하고 조명 계산시 왜 중요한지 확실히 이해하시기 바랍니다. 돌아가는 삼각형은 뒷면은 D3DClass에서 back face culling(뒷면 제거)를 하여 비춰지지 않는 것을 기억하시기 바랍니다.




연습하기


1. 프로젝트를 재컴파일하고 보라색광에 텍스쳐가 적용된 회전하는 삼각형이 보이는지 확인하시고 Esc버튼으로 종료시키세요.


2. shaderTexture를 더이상 사용하지않고 텍스쳐 없이 조명효과를 보기위해 픽셀 쉐이더에 "color = color * textureColor;" 부분을 주석처리 하세요.


3. GraphicsClass의 m_Light->SetDiffuseColor 라인에서 빛 색상을 초록색으로 바꾸세요.


4. 빛 방향을 X축의 -방향, +방향으로 바꾸어보세요. 또 회전 속도도 올려보세요.



728x90
728x90

원문 : http://www.rastertek.com/dx11tut05.htmld


Tutorial 5: Texturing


이번 강좌는 DirectX 11에서 텍스쳐링을 어떻게 사용하지 설명할 것입니다. 텍스쳐링은 사진이나 이미지를 폴리곤의 면에 적용하여 씬에 실사적으로 묘사해 줍니다. 예를들어 이번 강좌에서는 다음 이미지를 사용할 것입니다.



그리고 저번 강좌의 폴리곤에 다음과 같이 적용할 것입니다.



우리가 사용할 텍스쳐의 포맷은 .dds 파일입니다. 이는 Direct Draw Surface 포맷으로 DirectX에서 사용합니다. .dds 파일을 생성하는데 사용하는 툴은 DIrectX SDK에 포함되어 있습니다. DirectX Utilities에 속하고 DirectX Texture Tool 이라고 부릅니다. 여러분은 어떤 사이즈나 포맷이든 텍스쳐를 만들 수 있으며 다른 텍스쳐를 잘라서 붙이고 .dds 파일로 저장할 수도 있습니다. 사용하기 매우 간단합니다.


코드를 살펴보기 전에 먼저 텍스쳐 맵핑이 어떻게 동작하는지에 대해 논의하여야 합니다. .dds 파일 이미지에서 폴리곤으로 픽셀을 맵핑하기 위해 '텍셀(texel = texture + pixel) 좌표계'라고 부르는 것을 사용합니다. 이 좌표계는 픽셀의 정수값을 0.0f ~ 1.0f사이의 실수값으로 변환합니다. 예를들어 텍스쳐의 너비가 256픽셀 일때 첫번째 픽셀은 0.0f 으로 맵핑되고 256번째 픽셀은 1.0f로 맵핑되고 128번째인 중간 픽셀은 0.5f로 맵핑됩니다.


텍셀 좌표계에서 너비값을 "U", 높이값을 "V"라고 부릅니다. 너비는 왼쪽부터 0.0, 오른쪽 끝이 1.0이고 높이는 위쪽부터 0.0, 맨 아래가 1.0입니다. 예를 들어 좌상단은 U 0.0, V 0.0 을 우하단은 U 1.0, V 1.0 을 나타냅니다. 아래 텍셀 좌표계를 설명하기 위해 그림을 만들었습니다.



이제 텍스쳐를 폴리곤에 어떻게 맵핑하는지 기본은 알았음으로 이번 강좌에 새로 업데이트된 프레임워크를 보겠습니다.



이전 강좌 이후 프레임워크의 새로운 변화는 ModelClass안에 TextureClass가 추가된 것과 새로운 TextureShaderClass가 ColorShaderClass를 대체한 것입니다. 먼저 HLSL 텍스쳐 쉐이더를 보는 것으로 코드탐방을 시작합니다.




Texture.vs


텍스쳐 버텍스 쉐이더는 텍스쳐링을 위한 부분을 제외하고 이전 color 쉐이더와 매우 비슷합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: texture.vs
////////////////////////////////////////////////////////////////////////////////


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


더이상 버텍스 타입에서 색상을 사용하지 않고 대신에 텍스쳐 좌표를 사용합니다. 텍스쳐 좌표는 실수인 U, V 두개를 가지기 때문에 float2형을 사용합니다. 버텍스, 픽셀 쉐이더를 위한 텍스쳐 좌표의 의미(예약어?)는 TEXCOORD0입니다. 0부터 어떤숫자든 필요한 텍스쳐 좌표만큼 만들 수 있습니다.

////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType TextureVertexShader(VertexInputType input) { PixelInputType output; // 행렬연산을 위해 위치벡터 4번째 요소에 1을 대입합니다. input.position.w = 1.0f; // 월드, 뷰, 투영 행렬에 대해 정점의 좌표를 계산합니다. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix);

이번 강좌의 color 버텍스 쉐이더와 비교하여 텍스쳐 버텍스 쉐이더의 다른점은 입력 버텍스로부터 색상의 복사를 취하는 대신 텍스쳐 좌표를 복사하여 픽셀 쉐이더로 넘깁니다.

// 픽셀 쉐이더를 위해 텍스쳐 좌표를 저장합니다. output.tex = input.tex; return output; }



Texture.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: texture.ps
////////////////////////////////////////////////////////////////////////////////


텍스쳐 픽셀 쉐이더는 2개의 전역 변수를 가집닏. 하나는 텍스쳐 리소스인 Texture2D shaderTexture 입니다. 이것은 모델에 텍스쳐를 렌더링할때 사용될 텍스쳐 리소스가 될 것입니다. 두번째는 SamplerState SampleType 입니다. sampler state는 픽셀이 폴리곤의 면에 어떻게 쓰여질지를 바꾸도록 해줍니다. 예를 들어 폴리곤이 엄청 멀리 있어서 화면상에 8픽셀로만 구성될 때 sample state을 사용하여 원래 텍스쳐의 어떤 픽셀 혹은 픽셀 결합이 실제로 그려질지를 알아냅니다. 본래 텍스쳐가 256x256 이라면 어떤 픽셀이 실제로 그려질지를 결정하는 것은 해당 텍스쳐가 멀어서 작게보일때 여전히 같은 물체로 보여야 하므로 매우 중요합니다. TextureShaderClass에 sampler state를 설정하고 리소스 포인터에 붙여 픽셀셀 쉐이더가 그릴 픽셀의 샘플을 결정하는데 사용하도록 할 것입니다.


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


텍스처 픽셀 쉐이더의 PixelInputType은 색상값 대신에 텍스쳐 좌표를 사용하는 것으로 바뀌었습니다.


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


픽셀 쉐이더는 HLSL sample 함수를 사용하는 것으로 바뀌었습니다. sample 함수는 우리가 위에서 정의한 sample state와 픽셀을 위한 텍스쳐 좌표를 사용합니다. 이 두 변수를 폴리곤 면의 UV 위치에 대한 픽셀값을 결정하고 반환하는데 사용합니다.

//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TexturePixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; // 텍스쳐 좌표 위치에서 sampler를 사용하여 텍스쳐로부터 픽셀 색상을 적용합니다. textureColor = shaderTexture.Sample(SampleType, input.tex); return textureColor; }



Textureclass.h


TextureClass는 텍스처 리소스에 대한 로딩, 언로딩, 접근을 캡슐화 합니다. 각 텍스쳐는 반드시 이 실체화된 클래스 객체를 필요로 합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: textureclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx11tex.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureClass
////////////////////////////////////////////////////////////////////////////////
class TextureClass
{
public:
	TextureClass();
	TextureClass(const TextureClass&);
	~TextureClass();


첫 2개의 함수는 넘겨 받은 파일이름의 파일로부터 텍스쳐를 로딩하고 더이상 필요 없을때 언로딩합니다.


	bool Initialize(ID3D11Device*, WCHAR*);
	void Shutdown();


GetTexture() 함수는 쉐이더가 렌더링하는데 사용할 수 있도록 텍스쳐 리소스에 대한 포인터를 반환합니다.


	ID3D11ShaderResourceView* GetTexture();

private:


이것은 private속성의 텍스쳐 리소스입니다.


	ID3D11ShaderResourceView* m_texture;
};

#endif




Textureclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: textureclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "textureclass.h"


클래스 생성자는 텍스쳐 리소스 포인터를 null로 초기화합니다.


TextureClass::TextureClass()
{
	m_texture = 0;
}


TextureClass::TextureClass(const TextureClass& other)
{
}


TextureClass::~TextureClass()
{
}


Initialize() 함수는 Direct3D 디바이스아 텍스쳐 파일이름을 가지고 m_texture라 부르는 쉐이더 리소스 변수에 텍스쳐 파일을 로딩합니다. 그럼 텍스쳐는 렌더링하는데 사용될 수 있습니다.

bool TextureClass::Initialize(ID3D11Device* device, WCHAR* filename) { HRESULT result; // 텍스쳐 로딩 result = D3DX11CreateShaderResourceViewFromFile(device, filename, NULL, NULL, &m_texture, NULL); if(FAILED(result)) { return false; } return true; }

Shutdown() 함수는 텍스쳐 리소스가 로딩되어있으면 해제하고 포인터를 null로 설정합니다.

void TextureClass::Shutdown() { // 텍스처 리소스 해제. if(m_texture) { m_texture->Release(); m_texture = 0; } return; }

GetTexture() 다른 객체에서 텍스쳐를 렌더링에 사용하기 위해 텍스쳐 쉐이더 리소스로 접근할때 사용하는 함수입니다.


ID3D11ShaderResourceView* TextureClass::GetTexture()
{
	return m_texture;
}




Modelclass.h


ModelClass는 이전 강좌 이후 텍스쳐링을 수용하기 위해 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


TextureClass 헤더는 ModelClass헤더에 인클루드 되었습니다.


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:


VertexType은 색상 요소를 텍스쳐 좌표 요소로 대체되었습니다. 텍스쳐 좌표는 이전 강좌에서 쓰인 초록색을 대신합니다.


	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

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

	int GetIndexCount();


ModelClass도 GetTexture() 함수를 가짐으로 텍스쳐 리소스를 모델을 그릴 쉐이더로 보낼 수 있습니다.


	ID3D11ShaderResourceView* GetTexture();

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


ModelClass는 모델을 렌더링하는데 쓰일 텍스쳐를 로딩하고 언로딩하는 private속성의 LoadTexture()와 ReleaseTexture()를 가집니다.


	bool LoadTexture(ID3D11Device*, WCHAR*);
	void ReleaseTexture();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;


m_Texture 변수는 모델을 위한 텍스쳐 리소스를 로딩하고 해제하고 접근하는데 사용됩니다.


	TextureClass* m_Texture;
};

#endif




Modelclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;


생성자는 새 텍스쳐 객체를 null로 초기화합니다.


	m_Texture = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}


Initialize() 함수는 입력으로 모델이 사용할 .dds 텍스쳐 파일 이름도 필요합니다.


bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename) { bool result; // 삼각형의 기하정보를 가지고 있는 버텍스, 인덱스 버퍼를 초기화합니다. result = InitializeBuffers(device); if(!result) { return false; }


이제 Initialize() 함수는 텍스쳐를 로딩할 새 private 함수를 호출합니다.

// 현재 모델을 위한 텍스쳐를 로딩합니다. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; } void ModelClass::Shutdown() {

이제 Shutdown() 함수는 초기화에서 로딩했던 텍스쳐 객체를 해제하기 위해 새 private 함수를 호출합니다.

// 모델 텍스쳐를 해제합니다. ReleaseTexture(); // 버텍스, 인덱스 버퍼를 해제합니다. ShutdownBuffers(); return; } void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // 그리기 위해 버텍스, 인덱스 버퍼를 준비하도록 그래픽 파이프라인에 넣습니다. RenderBuffers(deviceContext); return; } int ModelClass::GetIndexCount() { return m_indexCount; }

GetTexture() 함수는 모델 텍스쳐 리소스를 반환합니다. 텍스쳐 쉐이더는 모델을 렌더링하기 위해 이 텍스쳐에 접근해야 합니다.

ID3D11ShaderResourceView* ModelClass::GetTexture() { return m_Texture->GetTexture(); } bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; // 버텍스 배열에 정점 개수 설정. m_vertexCount = 3; // 인덱스 배열에 인덱스 개수 설정. m_indexCount = 3; // 버텍스 배열 생성. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // 인덱스 배열 설정. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }

버텍스 배열은 이제 색상 요소 대신 텍스쳐 요소를 가집니다. 텍스쳐 벡터는 항상 첫번쨰는 U이고 2번째는 V입니다. 예를 들어 텍스쳐 좌표가 삼각형의 왼쪽 아래이면 U 0.0, V 1.0에 상응합니다. 헷갈리시는 분들은 이 강좌 맨 위에 UV좌표 그림을 보시면 이해가 가실 겁니다.(U는 가장 왼쪽이 0.0, V는 가장 위쪽이 0.0) 텍스쳐의 어느 부분에서 폴리곤 면의 어느 부분으로 맵핑하기 위해 좌표계를 바꿀 수 있어야 하는 것을 기억하시기 바랍니다. 이번 강좌에서는 맵핑을 간단하게 직접 넣었습니다.


	// Load the vertex array with data.
	vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // Bottom left.
	vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);

	vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // Top middle.
	vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);

	vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // Bottom right.
	vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);

	// Load the index array with data.
	indices[0] = 0;  // Bottom left.
	indices[1] = 1;  // Top middle.
	indices[2] = 2;  // Bottom right.

	// Set up the description of the vertex buffer.
	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;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Set up the description of the static index buffer.
	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;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	return true;
}


void ModelClass::ShutdownBuffers()
{
	// Release the index buffer.
	if(m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if(m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}

	return;
}


void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType); 
	offset = 0;
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}


LoadTexture() 함수는 텍스쳐 객체를 생성하고 입력으로 들어온 파일 이름과 디바이스로 초기화하는 새 private 함수입니다. 이 함수는 초기화시 호출됩니다.

bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename) { bool result; // 텍스쳐 객체 생성. m_Texture = new TextureClass; if(!m_Texture) { return false; } // 텍스쳐 객체 초기화. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; }

ReleaseTexture() 함수는 LoadTexture() 함수에서 생성되고 로딩된 텍스쳐 객체를 해제할 것입니다.

void ModelClass::ReleaseTexture() { // 텍스쳐 객체 해제. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }



Textureshaderclass.h


TextureShaderClass는 이전 강좌의 ColorShaderClass의 업데이트된 버전입니다. 이 클래스는 버텍스, 픽셀 쉐이더를 이용하여 3D 모델을 그리는데 사용될 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: textureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class TextureShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};

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

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

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

D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer;


sampler state에 대한 새 private 포인터 변수가 있습니다. 이 포인터는 텍스쳐 쉐이더에 대한 인터페이스로 사용될 것입니다.


	ID3D11SamplerState* m_sampleState;
};

#endif



Textureshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: textureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "textureshaderclass.h"


TextureShaderClass::TextureShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_matrixBuffer = 0;


새 sampler 변수는 생성자에서 null로 설정됩니다.


	m_sampleState = 0;
}


TextureShaderClass::TextureShaderClass(const TextureShaderClass& other)
{
}


TextureShaderClass::~TextureShaderClass()
{
}


bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


새 texture.vs 와 texture.ps HLSL 파일은 이 쉐이더를 위해 로딩됩니다.

// 버텍스, 픽셀 쉐이더를 초기화합니다. result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps"); if(!result) { return false; } return true; }

Shutdown() 함수는 쉐이더를 해제하는 함수를 호출합니다.

void TextureShaderClass::Shutdown() { // 버텍스, 픽셀 쉐이더와 관련 객체들을 정리합니다. ShutdownShader(); return; }

Render() 함수는 텍스처 리소스에 대한 포인터인 texture라 부르는 파라미터가 늘었습니다. SetShaderParameters() 함수로 보내져서 쉐이더에서 설정되어 렌더링에 사용됩니다.

bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount,

D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,

D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)

{ bool result; // 렌더링에 사용할 쉐이더 파라미터를 설정합니다. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture); if(!result) { return false; } // 준비된 버퍼를 쉐이더로 렌더링합니다. RenderShader(deviceContext, indexCount); return true; }

InitializeShader() 함수는 텍스쳐 쉐이더를 설정합니다.


bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd,
                                                             WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* pixelShaderBuffer;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D11_BUFFER_DESC matrixBufferDesc;


이 함수에서 설정될 텍스쳐 sampler의 description을 가지는 변수가 새로 생겼습니다.

D3D11_SAMPLER_DESC samplerDesc; // 이 함수에서 사용할 포인터를 null로 초기화합니다. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0;

새 텍스쳐를 버텍스, 픽셀 쉐이더에 로딩합니다.


	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TextureVertexShader", "vs_5_0",
                                                         D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                         			       &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TexturePixelShader",

"ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(),

vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(),

pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; }


입력 레이아웃은 색상대신 텍스쳐 요소를 가짐으로 바뀌어졌습니다. 첫번째 포지션 부분은 바뀌지 않았지만 두번째 부분의 SemanticName과 Format은 TEXCOORD 과 DXGI_FORMAT_R32G32_FLOAT으로 바뀌었습니다. 이 두 변화는 ModelClass 정의와 쉐이더 파일에 있는 새 VertexType에 따라 조정되는 것입니다.


	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	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 = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),

vertexShaderBuffer->GetBufferSize(), &m_layout);

if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. 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; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; }


sampler state description은 여기서 설정하고 나중에 쉐이더로 보냅니다. 텍스쳐 sampler description의 가장 중요한 요소는 바로 Filter 입니다. Filter는 폴리곤 면 위 텍스쳐의 최종 결과를 만들기 위해 사용되거나 결합되는 픽셀을 어떻게 결정할지를 판단합니다. 예를 들어 여기서 저는 D3D11_FILTER_MIN_MAG_MIP_LINEAR를 사용했는데 계산비용은 비싸지만 최고의 결과물을 보여줍니다. 이 필터는 sampler에게 확대, 축소, 밉맵핑에 선형보간법을 사용한다고 알려줍니다.


AddressU 와 AddressV는 좌표를 0.0f ~ 1.0f로 고정시킵니다. 그 이상, 이하의 좌표들도 0.0f ~ 1.0f으로 맞춥니다. 다른 sampler state description 설정은 기본으로 합니다.


	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


ShutdownShader() 함수는 TextureShaderClass에서 사용된 모든 변수를 해제합니다. 이제 초기화에서 생성된 sampler state도 해제합니다.


void TextureShaderClass::ShutdownShader()
{

// Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }


OutputShaderErrorMessage() 함수는 HLSL 쉐이더가 로딩되지 않으면 텍스트 파일에 에러메시지를 씁니다.


void TextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, 
                                                                                                                       WCHAR* shaderFilename)

{ char* compileErrors; unsigned long bufferSize, i; ofstream fout; // Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }


SetShaderParameters() 함수는 텍스쳐 리소스의 포인터를 받아서 쉐이더에게 맡깁니다. 텍스쳐는 버퍼가 렌더링 되기전에 설정되어야 하는것을 주의하시기 바랍니다.


bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,

D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);


SetShaderParameters() 함수는 픽셀 쉐이더의 텍스쳐 설정 포함하도록 수정되었습니다.


	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);

	return true;
}


RenderShader() 함수는 폴리곤을 렌더링하기위해 쉐이더를 호출합니다.


void TextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);


RenderShader() 함수는 렌더링전에 픽셀 쉐이더에 sample state설정을 포함하도록 바뀌었습니다.


	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}



Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"


GraphicsClass는 ColorShaderClass가 지워지고 대신에 TextureShaderClass 헤더를 인클루드 합니다.


#include "textureshaderclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;


TextureShaderClass private 객체 변수가 추가되었습니다.


	TextureShaderClass* m_TextureShader;
};

#endif



Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


m_TextureShader 변수는 생성자에서 null로 초기화 합니다.


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_TextureShader = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN,
                                                                                                     SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}


ModelClass::Initialize() 함수는 이제 텍스처의 모델을 렌더링하는데 사용될 텍스쳐의 이름을 받습니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}


새로운 TextureShaderClass 객체를 생성하고 초기화하는 코드도 추가되었습니다.


	// Create the texture shader object.
	m_TextureShader = new TextureShaderClass;
	if(!m_TextureShader)
	{
		return false;
	}

	// Initialize the texture shader object.
	result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


TextureShaderClass 객체는 Shutdown() 함수에서 역시 해제됩니다.


	// Release the texture shader object.
	if(m_TextureShader)
	{
		m_TextureShader->Shutdown();
		delete m_TextureShader;
		m_TextureShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the Direct3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;


	// Render the graphics scene.
	result = Render();
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the view, projection, and world matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDevice());


텍스쳐 쉐이더는 이제 모델을 렌더링하기 위해 컬러 쉐이더 대신에 호출됩니다. 또 쉐이더에서 텍스쳐에 접근하기 위해 모델 객체에서 텍스쳐 리소스 포인터를 받는것을 기억하시기 바랍니다.


	// Render the model using the texture shader.
	result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(),

worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }




요약


여러분은 이제 텍스쳐를 로딩하고 폴리곤 면에 맵핑한 뒤 쉐이더로 렌더링을 하는 기본을 이해하셨을 것입니다.




연습하기


1. 재컴파일하고 텍스쳐가 맵핑된 삼각형이 잘 보이는지 확인하시기 바랍니다. 그리고 esc버튼으로 종료하시면 됩니다.


2. 여러분만의 .dds 텍스쳐를 생성하여 같은 디렉토리에 위치시키고 GraphicsClass::Initialize() 함수에 텍스쳐 이름을 변경하여 재컴파일후 확인해보세요.


3. 삼각형 두개로 사각형을 만들도록 코드를 바꾸세요. 이 사각형에 텍스쳐를 맵핑하여 잘 나오는지 확인해 보세요.


4. MIN_MAG_MIP_LINEAR 필터의 효과를 확인해 보기 위해 카메라 거리를 이동해 보세요.


5. 다른 필터를 적용해 보고 카메라 거리를 움직여 보고 다른 결과를 확인해 보세요.



소스 only


dx11src05.zip


728x90
728x90

원문 : http://www.rastertek.com/dx11tut04.html



Tutorial 4: Buffers, Shaders, and HLSL


이번 강좌는 DirectX 11에서 버텍스 쉐이더와 픽셀 쉐이더를 작성해 볼 것입니다. 또 DirectX 11에서 버텍스 버퍼와 인덱스 버퍼도 사용해 볼 것입니다. 방금 얘기한 것들은 여러분이 3D 그래픽을 렌더링하는 것을 이해하고 활용하는데 매우 기본적인  개념들 입니다.



Vertex Buffers


이해해야 할 첫번째 개념은 바로 버텍스 버퍼입니다. 이 개념을 설명하기 위해 구의 3D 모델을 예시로 살펴 보도록 하겠습니다.


이 3D 구는 실제로는 수백개의 삼각형으로 구성되어 있습니다.


이 구 모델의 각 삼각형들은 삼각형을 이루는 3개의 점을 가지며 우리는 이것을 "정점"이라고 부릅니다. 그래서 우리가 구 모델을 렌더링하기 위해 구를 이루는 모든 정점들을 "버텍스 버퍼"라고 부르는 특별한 배열에 넣어야 합니다. 구 모델의 모든 점들을 버텍스 버퍼에 넣으면 GPU에 버텍스 버퍼를 전달하여 모델을 렌더링 할 수 있습니다.



Index Buffers


인덱스 버퍼는 버텍스 버퍼와 연관이 있습니다. 인덱스 버퍼는 버텍스 버퍼에 있는 각 정점의 위치를 기록하기 위함입니다. 그러면 GPU는 버텍스 버퍼의 특정 정점들을 빠르게 찾는데에 인덱스 버퍼를 사용합니다. 인덱스 버퍼의 개념은 책에서 "색인"(혹은 찾아보기)을 사용하는 개념과 비슷해서 여러분이 찾고 있는 주제를 더 빠르게 찾도록 돕습니다. DirectX SDK 공식문서에서 말하기를 인덱스 버퍼를 사용하는 것은 비디오 메모리에서 정점 데이터의 캐싱의 가능성을 높인다고 

합니다. 그래서 인덱스 버퍼의 사용은 성능 향상의 측면에서도 매우 권고됩니다.


Vertex Shaders


버텍스 쉐이더는 주로 버텍스 버퍼의 정점들을 3D 공간으로 변형시키도록 작성된 작은 프로그램입니다. 각 정점에 대한 법선을 계산하는 것과 같은 다른 연산들도 들어갑니다. 처리해야할 각 정점에 대해 GPU가 버텍스 쉐이더를 호출 할 것입니다. 예를들어 5,000개의 폴리곤으로 이루어진 모델 하나를 그리기 위해 매 프레임마다 15,000번의 버텍스 쉐이더 프로그램이 실행 될 것입니다. 또 만약 여러분의 그래픽 프로그램을 60 fps로 고정한다면 5,000개의 삼각형을 그리기 위해 버텍스 쉐이더를 초당 900,000번 호출 할 것입니다. 여러분도 알듯이 효율적인 버텍스 쉐이더를 작성하는 것은 매우 중요합니다.



Pixel Shaders


픽셀 쉐이더는 우리가 그리는 폴리곤들의 색상을 입히는 것이 작성되는 작은 프로그램입니다. 화면에 그려질 모든 볼 수 있는 픽셀들에 대해 GPU에서 동작합니다. 색상 입히기, 텍스쳐 입히기, 빛 처리 그리고 대부분의 여러분의 폴리곤의 면에 보여 지게 될 모든 효과들은 픽셀 쉐이더 프로그램에 의해 처리됩니다. 픽셀 쉐이더는 GPU에 의해 호출되는 횟수 때문에 반드시 효율적으로 작성되어야 합니다.



HLSL


HLSL은 우리가 DirectX 11에서 버텍스 쉐이더, 픽셀 쉐이더를 작성하기위해 사용하는 언어입니다. 문법은 미리 정의된 타입들과 함께 C 언어와 거의 같습니다. HLSL 프로그램은 전역 변수, 타입 정의, 버텍스 쉐이더, 픽셀 쉐이더 그리고 지오메트리 쉐이더로 구성되어 있습니다. 이번 강좌는 HLSL은 처음이기 때문에 DirectX 11을 이용하여 매우 간단한 HLSL프로그램을 만들어 볼 것입니다.




Updated Framework



이번 강좌를 위해 프레임워크를 업데이트 하였습니다. GraphicsClass 밑에 CameraClass, ModelClass 그리고 ColorShaderClass 3개를 추가하였습니다. CameraClass는 전에 얘기했던 뷰 행렬을 다룰 것입니다. 월드 좌표상의 카메라 위치를 처리하고 그래픽을 그릴때 쉐이더에 넘겨서 우리가 씬을 어디서 보고있는지 알아 낼 것입니다. ModelClass는 우리의 3D 모델의 기하학적인 것을 다룰 것 인데 이번 강좌에서 3D 모델은 간단하게 삼각형 하나가 다 입니다. 그리고 마지막으로 ColorShaderClass는 HLSL을 호출하여 모델을 화면상에 렌더링하는데 사용 될 것입니다.



먼저 HLSL 쉐이더 프로그램을 보며 이번 강좌 코드 리뷰를 시작하겠습니다.





Color.vs


Color.vs는 우리의 첫 쉐이더 프로그램이 될 것입니다. 쉐ㅣ더는 모델들의 실제적인 렌더링을 하는 작은 프로그램입니다. 이 쉐이더는 HLSL로 작성되며 소스파일 color.vs와 color.ps에 저장됩니다. 저는 이 파일을 지금은 엔진의 .h와 .cpp과 함께 두었습니다. 이번 쉐이더의 목적은 이번 첫 HLSL 강좌에서 가능한 간단하게 하기 위해 원색의 삼각형을 그리는 것이 다 입니다. 먼저 버텍스 쉐이더 코드입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: color.vs
////////////////////////////////////////////////////////////////////////////////


먼저 전역 변수로 시작합니다. 이 전역변수들은 여러분의 C++코드에서 수정될 수 있습니다. 여러분은 int, float과 같은 많은 변수형을 사용할 수 있고 쉐이더 프로그램이 사용하도록 외부에서 설정할 수 있습니다. 일반적으로 cbuffer라 부르는 버퍼 오브젝트 형에 대부분의 전역변수들을 넣을 것이며 한개일 때도 마찬가지입니다. 논리적으로 이러한 버퍼들을 만드는 거슨 쉐이더의 효출적인 실행뿐만 아니라 그래픽 카드가 버퍼들을 어떻게 저장할지에 대해서도 중요합니다. 이번 예제에서는 같은 버퍼에 3개의 행렬 변수를 넣었습니다. 매 프레임마다 동시에 업데이트 되기 때문입니다.

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


C언어와 같이 우리가 원하는 자료형을 정의할 수 있습니다. 우리는 float4와 같이 쉐이더 작성을 쉽고 가독성이 좋게하고 HLSL에 사용가능한 다른 자료형들을 사용할 것입니다. 이번 예제에서는 x, y, z, w 값을 가지는 position 벡터와 빛의 삼원색(RGB)과 알파값을 가지는 color로 이루어진 자료형을 만들 것입니다. POSITION, COLOR 그리고 SV_POSITION 은 GPU에게 변수의 사용을 전달하는 의미입니다. 두 구조체가 동일하지만 버텍스, 픽셀 쉐이더에게 의미가 다르기 때문에 구조체를 두개로 만들어야 합니다. COLOR 는 두 쉐이더에 대해 동일하게 동작하지만 POSITION 은 버텍스 쉐이더에 대해 동작하고 SV_POSITION 은 픽셀 쉐이더에 대해 동작합니다. 만야 같은 자료형을 더 추가하고 싶으면 COLOR0, COLOR1 등등과 같이 뒤에 숫자를 추가하면 됩니다.

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

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


버텍스 쉐이더는 쉐이더 안으로 보내진 버텍스 버퍼의 데이터를 처리할 때 GPU에서 호출됩니다. ColorVertexShader 라고 이름 붙인 버텍스 쉐이더는 버텍스 버퍼안의 모든 정점 하나하나에 대해 호출 될 것입니다. 버텍스 쉐이더로의 입력은 반드시 버텍스 버퍼 내부 데이터의 형식과 쉐이더 소스 파일 이번 경우 VertexInputType 내에 자료형 정의와도 일치해야 합니다. 버텍스 쉐이더의 출력은 픽셀 쉐이더로 보내져 픽셀 쉐이더의 입력이 됩니다. 이번 경우에는 반환 형식은 위에서 정의된 PixelInputType 입니다.


방금 말했던 것처럼 버텍스 쉐이더가 PixelInputType 형 변수를 만드는 것이 보이실 것입니다. 그리곤 입력 정점의 위치를 취하고 월드, 뷰, 투영 행렬에 차례로 곱해집니다. 정점은 3D 공간에서 뷰에 따라 렌더링하여 2D 화면 위 맞는 위치로 바뀔 것입니다. output 변수가 input의 color의 카피를 취하고 픽셀 쉐이더에 input으로 사용 될 output을 반환합니다. 또 input.position의 W값을 1.0으로 설정한 것을 주의하세요. 그렇지 않으면 위치를 계산하느데 XYZ만 읽기 때문에 W값이 정의되지 않습니다.

//////////////////////////////////////////////////////////////////////////////// // 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; }





Color.ps


픽셀 쉐이더는 화면상에 렌더링 될 폴리곤의 각 픽셀들을 그립니다. 픽셀 쉐이더에서 PixelInputType을 입력으로 사용하고 최종 픽샐 색상으로 표현 될 출력으로 'float4' 변수 하나를 반환합니다. 이 픽셀 쉐이더 프로그램은 입력으로 들어온 색상 값을 그대로 반환합니다. 픽셀 쉐이더의 입력은 버텍스 쉐이더의 출력에서 얻는 것을 주시하시기 바랍니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: color.ps
////////////////////////////////////////////////////////////////////////////////


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


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




Modelclass.h


앞에서 언급했듯이 ModelClass은 3D 모델의 기하학적 부분을 캡슐화 해야 합니다. 이번 강좌에서는 직접 초록색 삼각형 하나의 데이터를 설정하도록 하겠습니다. 또 삼각형이 렌더링될 수 있도록 인덱스, 버텍스 버퍼를 만들도록 하겠습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:


ModelClass에서 버텍스 버퍼와 같이 사용 될 정점 타입의 정의가 있습니다. 여기서 typedef는 나중 강좌에서 살펴 볼 ColorShaderClass 안의 형식과 일치해야 합니다.

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR4 color;
	};

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


이 함수들은 모델의 버텍스, 인덱스 버퍼의 초기화와 정리를 처리합니다. Render() 함수는 쉐이더로 그리는 것을 준비하기 위해 비디오 카드에 모델 기하 정보를 넣습니다.

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

	int GetIndexCount();

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


ModelClass의 private 변수는 버텍스, 인덱스 버퍼와 각 버퍼 사이즈의 길이를 유지하는 두 정수 변수가 있습니다. 모든 DirectX 11 버퍼는 보통 ID3D11Buffer 형을 사용하며 만들어질때 버퍼 description으로 구별됩니다.

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
};

#endif



Modelclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


클래스 생성자는 버텍스, 인덱스 버퍼의 포인터를 null로 초기화합니다.

ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}


Initialize() 함수는 버텍스, 인덱스 버퍼를 초기화 하는 함수를 호출합니다.

bool ModelClass::Initialize(ID3D11Device* device) { bool result; // 삼각형의 기하 정보를 가지고 있는 버텍스, 인덱스 버퍼를 초기화 한다. result = InitializeBuffers(device); if(!result) { return false; } return true; }


Shutdown() 함수는 버텍스, 인덱스 버퍼를 정리하는 함수를 호출합니다.

void ModelClass::Shutdown() { // 버텍스, 인덱스 버퍼 해제. ShutdownBuffers(); return; }


Render() 함수는 GraphicsClass::Render() 함수에서 호출 됩니다. 이 함수가 버텍스, 인덱스 버퍼를 그래픽 파이프라인에 넣는 RenderBuffers() 함수를 호출하여 color 쉐이더가 렌더링 할 수 있도록 합니다.

void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // 버텍스, 인덱스 버퍼를 그려질 수 있도록 그래픽 파이프라인에 넣습니다. RenderBuffers(deviceContext); return; }


GetIndexCount() 함수는 모델의 인덱스의 수를 리턴합니다. color 쉐이더는 해당 모델을 그리는데 이 정보가 필요할 것입니다.

int ModelClass::GetIndexCount()
{
	return m_indexCount;
}


InitializeBuffers() 함수는 버텍스, 인덱스 버퍼를 생성하는 것을 처리하는 곳입니다. 주로 모델에서 읽어서 그 데이터 파일로 버퍼를 생성했을 것입니다. 그러나 이번 강좌에서는 삼각형 하나라서 직접 버텍스, 인덱스 버퍼에 점을 설정할 것입니다.

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;


먼저 나중에 최종버퍼에 들어갈 버텍스, 인덱스 데이터를 가지는 임시 배열 두개를 만듭니다.

// 버텍스 배열의 정점 갯수를 설정. m_vertexCount = 3; // 인덱스 배열의 인덱스 수를 설정. m_indexCount = 3; // 버텍스 배열 생성. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // 인덱스 배열 생성. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }


이제 버텍스, 인덱스 버퍼를 삼각형의 세 점과 그 각각의 점에 대한 인덱스로 채웁니다. 여기서 주의할 점은 삼각형의 점들을 시계방향으로 넣은 것입니다. 만약 시계 반대방향으로 넣을 경우 삼각형의 앞면이 원래 그리려던 반대 방향으로 보게 되어 back face culling(뒷면을 그리지 않음)때문에 그리지 않습니다. GPU에게 정점들을 보낼 때 이것을 기억하는게 매우 중요합니다. 이 부분도 버텍스 description의 일부이기 때문에 색상도 여기서 지정됩니다. 색상은 회색으로 설정하였습니다.

// 버텍스 배열에 데이터를 넣습니다. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // 왼쪽 아래 vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // 가운데 위 vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // 오른쪽 아래 vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); // 인덱스 배열에 데이터를 넣습니다. indices[0] = 0; // 왼쪽 아래 indices[1] = 1; // 가운데 위 indices[2] = 2; // 오른쪽 아래


버텍스, 인덱스 버퍼를 채움으로 이제 버텍스, 인덱스 버프를 생성하는데 사용할 수 있게 되었습니다. 같은 방식으로 두 버퍼를 만듭니다. 먼저 버퍼의 description을 채웁니다. description에서 ByteWidth (버퍼의 바이트 크기)와 BindFlags (버퍼의 타입)은 정확히 입력되었는지 확실히 해야하는 것들입니다. description을 다 채우고 전에 만든 버텍스, 인덱스 배열을 가리키는 subresource 포인터도 채워야 합니다. description과 subresource 포인터가 있으면 D3D 디바이스를 이용하여 CreateBuffer() 함수를 호출합니다. 그리고 여러분의 새 버퍼에 포인터를 리턴할 것입니다.

// 정적 정점 버퍼의 description을 작성합니다. 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 구조체에 넣습니다. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // 버텍스 버퍼를 생성합니다. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // 정적 인덱스 버퍼의 description을 작성합니다. 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; // 인덱스 데이터의 포인터를 subresource 구조체에 넣습니다. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // 인덱스 버퍼 생성. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; }


버텍스, 인덱스 버퍼를 생성된 이후 데이터가 버퍼로 복사되었으므로 더 이상 필요가 없어진 버텍스, 인덱스 배열을 지웁니다.

// 버텍스, 인덱스 버퍼가 생성되었으므로 배열을 해제합니다. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }


ShutdownBuffers() 함수는 InitializeBuffers() 함수에서 만들어진 버텍스, 인덱스 버퍼를 해제합니다.

void ModelClass::ShutdownBuffers() { // 인덱스 버퍼 해제. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // 버텍스 버퍼 해제. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }


RenderBuffers() 함수는 Render() 함수에서 호출됩니다. 이 함수의 목적은 버텍스, 인덱스 버퍼를 GPU의 어셈블러 입력에 활성화 시키는 것입니다. GPU가 활성화된 버텍스 버퍼를 가지면 쉐이더를 사용할 수 있습니다. 이 함수는 또 버퍼가 어떻게 그려질지 삼각형인지 선인지 부채꼴 등등인지를 정의합니다. 이번 강좌에서 어셈블러 입력에 버텍스, 인덱스 버퍼를 활성화시키고 IASetPrimitiveTopology() DirectX함수를 사용하여 GPU에게 해당 버퍼는 삼각형으로 그려질 거라고 알려줍니다.

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset; // 버텍스 버퍼 걸음(단위)와 오프셋을 설정합니다. stride = sizeof(VertexType); 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); return; }


Colorshaderclass.h


ColorShaderClass은 GPU위 3D 모델을 그리는 것에 대해 HLSL 쉐이더를 호출할 것입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: colorshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: ColorShaderClass
////////////////////////////////////////////////////////////////////////////////
class ColorShaderClass
{
private:


버텍스 쉐이더에서도 사용되는 cBuffer 타입의 정의입니다. 이 typedef는 올바른 렌더링을 위해 modelclass에서 VertexType이 일치하였던 것처럼 쉐이더의 cBuffer와 일치해야 합니다.

	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};

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


여기 함수들은 쉐이더의 초기화와 정리를 담당합니다. Render() 함수는 쉐이더의 매개변수를 설정하고 준비된 모델 정점들을 쉐이더를 이용하여 그립니다.

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

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

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

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

#endif



Colorshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: colorshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "colorshaderclass.h"


늘 그렇듯이 생성자는 private 포인터들을 null로 초기화 합니다.

ColorShaderClass::ColorShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_matrixBuffer = 0;
}


ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}


ColorShaderClass::~ColorShaderClass()
{
}


Initialize() 함수는 쉐이더를 위한 초기화 함수를 호출합니다. HLSL 쉐이더 파일의 이름을 넘기는데 이번 강좌에서 color.vs와 color.ps파일 입니다.

bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; // 버텍스, 픽셀 쉐이더를 초기화합니다. result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps"); if(!result) { return false; } return true; }


Shutdown() 함수는 쉐이더의 정리 함수를 호출합니다.

void ColorShaderClass::Shutdown() { // 버텍스, 픽셀 쉐이더와 관련 오브젝트들을 정리합니다. ShutdownShader(); return; }


Render() 함수는 먼저 SetShaderParameters() 함수를 이용하여 쉐이더 내부로 파라미터들을 설정합니다. 파라미터가 한번 설정되고 나면 RenderShader() 함수를 호출하여 HLSL 쉐이더를 이용하여 녹색 삼각형을 그립니다.

bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; // 렌더링하는데 사용할 쉐이더 파라미터들을 설정합니다. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // 이제 준비된 버퍼를 쉐이더로 렌더링합니다. RenderShader(deviceContext, indexCount); return true; }


이번 강좌에서 매우 중요한 함수중 하나인 InitializeShader() 함수를 살펴 볼 것입니다. 이 함수는 쉐이더 파일들이 실제 로로드되고 DirectX와 GPU가 사용할 수 있게끔 합니다. 레이아웃의 설정과 버텍스 버퍼 데이터가 어떻게 GPU안의 그래픽 파이프라인을 확인할지(사용될지?) 볼 것입니다. 레이아웃은 modelclass.h 파일 안의 VertexType과 color.vs 파일 안에 정의된 것과 일치를 필요로 합니다.

bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; // 이 함수에서 사용할 포인터를 null로 초기화 합니다. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0;


여기는 버퍼에 쉐이더 프로그램을 컴파일 하는 곳입니다. 쉐이더 파일 이름, 쉐이더의 이름, 쉐이더 버전(DirectX 11 에서는 5.0), 쉐이더가 컴파일될 버퍼를 파라미터로 넘깁니다. 만약 쉐이더 컴파일을 실패할 경우 다른 함수에서 출력하도록 보낼 errorMessage라는 문자열에 에러메시지를 담습니다. 여전히 실패에 에러메시지도 없을 경우 쉐이더 파일을 찾을 수 없다는 것을 의미하여 이 경우 메시지박스 하나를 띄워 "찾을 수 없다"고 출력해줍니다.

// 버텍스 쉐이더 코드를 컴파일 합니다. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0",

D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL);

if(FAILED(result)) { // 만약 쉐이더 컴파일을 실패할 경우 에러메시지에 뭔가가 쓰여져 있습니다. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // 에러메시지가 없을 경우 간단하게 직접 쉐이파일 찾을 수 없다고 메시지박스로 출력합니다. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // 필셀 쉐이더 코드를 컴파일 합니다. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0",

D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL);

if(FAILED(result)) { // 만약 쉐이더 컴파일을 실패할 경우 에러메시지에 뭔가가 쓰여져 있습니다. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // 에러메시지가 없을 경우 간단하게 직접 쉐이파일 찾을 수 없다고 메시지박스로 출력합니다. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; }


버텍스 쉐이더와 픽셀 쉐이더가 성공적으로 버퍼 안으로 컴파일 되면 해당 버퍼를 쉐이더 오브젝트를 만드는데 사용합니다. 이제부터 앞으로 이 포인터를 버텍스, 픽셀 쉐이더에 대한 인터페이스로 사용할 것입니다.

// 버퍼로부터 버텍스 쉐이더 생성. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(),

vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // 버퍼로부터 픽셀 쉐이더 생성.Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(),

pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; }


다음은 쉐이더가 처리할 정점 데이터의 레이아웃을 만들어야 합니다. 이 쉐이더가 위치, 색상 벡터를 사용하기 때문에 둘의 사이즈를 명시하는 레이아웃 2개를 만들어야 합니다. semantic name 은 레이아웃에서 첫번째로 채우는데 레이아웃 요소의 용법을 쉐이더가 결정하게 해줍니다. 두 개의 다른 요소를 가지는데 첫번째로 POSITION을 사용하고 두번째로 COLOR를 사용합니다. 다음으로 레이아웃의 중요한 부분은 Format입니다. 위치벡터를 위해 DXGI_FORMAT_R32G32B32_FLOAT을 사용하고 색상을 위해 DXGI_FORMAT_R32G32B32A32_FLOAT을 사용합니다. 마지막으로 주의해야 하는 것은 버퍼에 데이터가 어떻게 되는지를 나타내는 AlignedByteOffset 입니다. 레이아웃에에게 position은 12 바이트 이며 color는 16바이트 이라고 알려줘야하는데 AlignedByteOffset는 각 요소의 시작을 나타냅니다. AlignedByteOffset에 특정 값을 넣는 대신에 D3D11_APPEND_ALIGNED_ELEMENT을 사용하여 위치를 알 수 있습니다. 나머지 다른 설정은 현재 이번 강좌에서 필요치 않아 기본설저으로 하였습니다.

// 쉐이더로 전달할 데이터의 레이아웃을 설정합닌다. // 이 설정은 쉐이더와 ModelClass에 있는 VertexType 구조체의 일치가 필요합니다. 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;


한번 레이아웃 description이 설정되고 나면 사이즈를 얻을 수 있고 D3D 장치를 이용하여 입력 레이아웃을 생성할 수 있습니다. 레이아웃이 만들어지면 버텍스, 픽셀 쉐이더 버퍼는 더 이상 필요 없기 때문에 해제합니다.

// 레이아웃 요소 갯수를 얻습니다. (여기서는 2개) numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // 버텍스 입력 레이아웃 생성. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // 더 이상 필요 없는 버텍스, 픽셀 쉐이더 버퍼를 해제합니다. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0;


마지막으로 쉐이더를 사용하는데 필요한 설정은 상수 버퍼 입니다. 버텍스 쉐이더에서 봤듯이 우리는 상수버퍼를 하나만 가지며 여기서 하나만 설정하여 쉐이더에 대한 인터페이스로 사용합니다. 상수 버퍼는 매 프레임마다 업데이트 됨으로(행렬값이다.) 버퍼 사용법은 동적(dynamic)으로 설정합니다. bind flags 는 현재 이 버퍼가 상수 버퍼가 될 것이라는 것을 나타냅니다. cpu access flags 는 위 용법(usage)와 맞춰야하기 때문에 D3D11_CPU_ACCESS_WRITE로 설정합니다. description이 작성되면 상수버퍼 인터페이스를 만들 수 있고 SetShaderParameters() 함수를 이용하여 쉐이더 내부 변수에 접근 할 수 있습니다.

// 버텍스 쉐이더에 있는 동적 행렬 상수 버퍼의 description을 작성합니다. 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; // 상수 버퍼 포인터를 만듭니다. 이제 이 클래스에서 버텍스 쉐이더 상수 버퍼에 접근할 수 있습니다. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } return true; }


ShutdownShader() 함수는 InitializeShader() 함수에서 설정했던 4개의 인터페이스를 해제합니다.

void ColorShaderClass::ShutdownShader() { // 행렬 상수 버퍼 해제. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // 레이아웃 해제. if(m_layout) { m_layout->Release(); m_layout = 0; } // 픽셀 쉐이더 해제. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // 버텍스 쉐이더 해제. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }


OutputShaderErrorMessage() 함수는 버텍스 혹은 픽셀 쉐이더를 컴파일 할때 발생하는 에러메시지를 기록합니다.

void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout; // 에러 메시지 텍스트 버퍼의 포인터를 얻습니다. compileErrors = (char*)(errorMessage->GetBufferPointer()); // 메시지의 길이를 얻습니다. bufferSize = errorMessage->GetBufferSize(); // 에러메시지를 기록할 파일을 엽니다. fout.open("shader-error.txt"); // 에러 메시지를 기록합니다. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // 파일을 닫습니다. fout.close(); // 에러메시지 해제. errorMessage->Release(); errorMessage = 0; // 컴파일 에러 텍스트 파일을 유저가 확인하도록 메시지 팝업창을 띄웁니다. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }


SetShaderVariables() 함수는 쉐이더의 전역 변수에 더 쉽게 설정하기 위해 존재합니다. 이 함수에 사용된 행렬들은 GraphicsClass안에서 만들어 졌으며 이 함수가 종료된 이후 Render() 함수가 호출되는 동안 버텍스 쉐이더로 보내집니다.

bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, 
					   D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;


행렬들이 쉐이더로 보내지기 전 전치행렬로 바꾸는데 DirectX(행우선)와 쉐이더(열우선)가 행렬을 다루는 방식이 다르기 때문에 맞추어주기 위해 전체행렬을 취한다.

// 쉐이더가 사용할 수 있게 전치행렬로 바꿉니다. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);


m_matrixBuffer 잠그고 안에다가 새 행렬을 설정하고 다시 풉니다.

// 상수 버퍼를 잠급니다. 이제 수정될 수 있습니다. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // 상수버퍼 내부 데이터에 대한 포인터를 얻습니다. dataPtr = (MatrixBufferType*)mappedResource.pData; // 상수버퍼 안으로 행렬들을 복사합니다. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // 상수버퍼에 대한 잠금을 풉니다. deviceContext->Unmap(m_matrixBuffer, 0);


이제 HLSL 버텍스 쉐이더에 업데이트된 행렬 버퍼를 설정합니다.

// 버텍스 쉐이더의 상수 버퍼 위치 설정. bufferNumber = 0; // 마지막으로 업데이트된 값과 함께 버텍스 쉐이더의 상수 버퍼를 설정합니다. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); return true; }


RenderShader() 함수는 Render() 함수에서 두번째로 호출되는 함수입니다. SetShaderParameters() 함수는 쉐이더 파라미터가 제대로 설정되었지는 확인하기 전에 호출됩니다.


이 함수에서 처음으로 할 것은 우리의 입력 레이아웃을 입력 어셈블러에 활성화 시키는 것입니다. 이는 GPU가 버텍스 버퍼의 데이터의 포맷을 알게 합니다. 두번째는 이 버텍스 버퍼를 렌더링하는데 사용할 버텍스, 픽셀 쉐이더를 설정하는 것입니다. 쉐이더가 설정되면 D3D device context를 이용하여 DrawIndexed DirectX 11 함수를 호출하여 삼각형을 렌더링합니다. 이 함수가 호출되고 나면 초록 삼각형이 렌더링 될 것입니다.

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); return; }



Cameraclass.h


지금까지 우리는 어떻게 HLSL 쉐이더를 작성하며 버텍스, 인덱스 버퍼를 설정하고 ColorShaderClass를 이용하여 버퍼를 호출하기 위해 HLSL 쉐이더를 어떻게 호출하는지에 대해 살펴보았습니다. 그러나 우리가 놓치고 있는 하나는 그래픽이 그려질 뷰포인트 입니다. 이것을 위해 우리는 DirectX 11이 우리가 씬을 어디서 어떻게 보고있는지를 알도록 camera 클래스가 필요합니다. camera 클래스는 카메라 위치와 회전상태를 기록할 것입니다. 렌더링을 위해 HLSL 쉐이더에 넘길 뷰 행렬을 만드는데 필요한 위치, 회전 정보를 사용할 것입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: cameraclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: CameraClass
////////////////////////////////////////////////////////////////////////////////
class CameraClass
{
public:
	CameraClass();
	CameraClass(const CameraClass&);
	~CameraClass();

	void SetPosition(float, float, float);
	void SetRotation(float, float, float);

	D3DXVECTOR3 GetPosition();
	D3DXVECTOR3 GetRotation();

	void Render();
	void GetViewMatrix(D3DXMATRIX&);

private:
	float m_positionX, m_positionY, m_positionZ;
	float m_rotationX, m_rotationY, m_rotationZ;
	D3DXMATRIX m_viewMatrix;
};

#endif


CameraClass 헤더는 사용할 4개의 함수와 함께 상당히 간단합니다. SetPosition(), SetRotation() 함수는 카메라 오브젝트의 위치와 회전을 설정하는데 사용할 것입니다. Render() 함수는 카메라 위치와 회전을 기초로한 뷰 행렬을 생성하는데 사용할 것입니다. 그리고 마지막으로 GetViewMatrix() 함수는 쉐이더가 렌더링시 사용할 수 있도록 카메라 오브젝트로부터 뷰 행렬을 얻는데 사용할 것입니다.



Cameraclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: cameraclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "cameraclass.h"


클래스 생성자는 카메라가 원점에 위치하도록 위치와 회전을 초기화합니다.

CameraClass::CameraClass()
{
	m_positionX = 0.0f;
	m_positionY = 0.0f;
	m_positionZ = 0.0f;

	m_rotationX = 0.0f;
	m_rotationY = 0.0f;
	m_rotationZ = 0.0f;
}


CameraClass::CameraClass(const CameraClass& other)
{
}


CameraClass::~CameraClass()
{
}


SetPosition(), SetRotation() 는 카메라의 위치와 회전을 설정하는데 사용됩니다.

void CameraClass::SetPosition(float x, float y, float z)
{
	m_positionX = x;
	m_positionY = y;
	m_positionZ = z;
	return;
}


void CameraClass::SetRotation(float x, float y, float z)
{
	m_rotationX = x;
	m_rotationY = y;
	m_rotationZ = z;
	return;
}


GetPosition(), GetRotation() 은 호출한 함수에게 카메라의 위치와 회전값을 반환합니다.

D3DXVECTOR3 CameraClass::GetPosition()
{
	return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}


D3DXVECTOR3 CameraClass::GetRotation()
{
	return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}


Render() 함수는 뷰 행렬을 만들고 업데이트 하기위해 카메라의 위치와 회전값을 사용합니다. 먼저 up 벡터, 위치, 회전 등등의 변수를 설정합니다. 그리곤 씬의 원위치(원점에서 +z 방향. 참고로 DirectX 수학과 달리 z축 방향이 다르다.) 에서 카메라를 카메라의 x, y, z 회전 값에 따라 회전시킵니다. 회전후에 카메라의 위치를 3D 공간으로 옮깁니다. 맞게 설정된 position, lookAt, up 값으로 D3DXMatrixLookAtLH() 함수를 호출하여 카메라 회전과 변환을 표현하는 뷰 행렬을 만들 수 있습니다.

void CameraClass::Render() { D3DXVECTOR3 up, position, lookAt; float yaw, pitch, roll; D3DXMATRIX rotationMatrix; // up 벡터를 설정합니다. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; // 월드좌표에서 카메라 위치를 설정합니다. position.x = m_positionX; position.y = m_positionY; position.z = m_positionZ; // 카메라가 보는 곳을 기본으로 설정합니다. lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 1.0f; // yaw (Y축), pitch (X축), roll (Z축) 회전 값을 라디안값으로 설정합니다. pitch = m_rotationX * 0.0174532925f; yaw = m_rotationY * 0.0174532925f; roll = m_rotationZ * 0.0174532925f; // yaw, pitch, roll 값으로 회전 행렬을 만듭니다. D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll); // lookAt, up 벡터를 회전 행렬로 회전 변환합니다. 이제 뷰가 원점에서 회전되었습니다. D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix); D3DXVec3TransformCoord(&up, &up, &rotationMatrix); // 회전된 카메라 위치가 적용된 lookAt을 얻습니다. lookAt = position + lookAt; // 마지막으로 세개의 설정된 벡터로 뷰 행렬을 생성합니다. D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up); return; }


Render() 함수가 뷰 행렬을 생성하기 위해 호출 된 후 GetViewMatrix() 함수를 이용하여 호출하는 함수에게 업데이트된 뷰 행렬을 제공할 수 있습니다. 뷰 행렬은 HLSL 버텍스 쉐이더에서 세개의 주요 행렬중 하나가 될 것입니다.

void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
	viewMatrix = m_viewMatrix;
	return;
}



Graphicsclass.h


이제 GraphicsClass는 새로운 클래스 3개가 추가되었습니다. CameraClass, ModelClass, 그리고 ColorShaderClass 의 헤더파일이 여기에 인클루드되었고 private속성에 각 객체 포인터도 추가되었습니다. GraphicsClass 는 이 프로젝트를 위해 필요한 모든 클래스 객체를 호출하여 씬을 렌더링하는데 사용되는 메인 클래스인 것을 꼭 기억하시기 바랍니다.

////////////////////////////////////////////////////////////////////////////////

// Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "colorshaderclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; ColorShaderClass* m_ColorShader; }; #endif



Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass의 첫번째 변화는 클래스 생성자에서 camera, model, color shader 객체포인터를 null로 초기화하는 것입니다.

GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_ColorShader = 0;
}


Initialize() 함수는 새로운 객체 3개를 생성하고 초기화하는 코드가 업데이트 되었습니다.

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // Direct3D 객체 생성. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Direct3D 객체 초기화. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // 카메라 객체 생성. m_Camera = new CameraClass; if(!m_Camera) { return false; } // 카메라 기본 위치 설정. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // 모델 객체 생성. m_Model = new ModelClass; if(!m_Model) { return false; } // 모델 객체 초기화. result = m_Model->Initialize(m_D3D->GetDevice()); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // color shader 객체 생성. m_ColorShader = new ColorShaderClass; if(!m_ColorShader) { return false; } // color shader 객체 초기화. result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK); return false; } return true; }


Shutdown() 함수도 역시 새 객체 3개를 정리하고 해제하는 코드가 업데이트 되었습니다.

void GraphicsClass::Shutdown() { // color shader 객체 해제. if(m_ColorShader) { m_ColorShader->Shutdown(); delete m_ColorShader; m_ColorShader = 0; } // 모델 객체 해제. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // 카메라 객체 해제. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Direct3D 객체 해제. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }


Frame() 함수는 이전 강좌와 동일하게 그대로입니다.

bool GraphicsClass::Frame()
{
	bool result;


	// 그래픽 씬을 렌더링 한다.
	result = Render();
	if(!result)
	{
		return false;
	}

	return true;
}


여러분이 예상하다시피 Render() 함수는 매우 많이 바뀌었습니다. 그래도 여전히 씬을 검정색으로 지우는 것으로 시작합니다. 그 후에 Initialize() 함수에서 설정한 카메라 위치를 기반으로 한 뷰 행렬 생성하기 위해 카메라 객체의 Render() 함수를 호출합니다. 뷰 행렬이 생성된 이후 카메라 클래스로부터 뷰 행렬을 복사합니다. 또 D3DClass 객체로부터 월드 행렬과 투영 행렬도 복사합니다. 그래픽파이프라인에 초록색 삼각형 모델을 기하 정보를 넣기 위해 ModelClass::Render() 함수를 호출합니다. 준비된 정점들로 각 정점들을 맞게 위치시키는 행렬 3개와 모델 정보를 이용하여 정점들을 그리는 color 쉐이더를 호출합니다. 이제 초록색 삼각형이 후면 버퍼에 그려집니다. 이것으로 씬을 완료하고 EndScene() 함수를 호출하여 화면상에 출력합니다.

bool GraphicsClass::Render() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; bool result; // 씬을 시작하기 위해 버퍼를 비웁니다. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // 카메라 위치해 기반한 뷰 행렬을 생성합니다. m_Camera->Render(); // 월드, 뷰, 투영 행렬을 카메라, d3d 객체로부터 얻습니다. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // 모델 정점과 인덱스 버퍼를 그리기위해 그래픽 파이프라인에 넣습니다. m_Model->Render(m_D3D->GetDeviceContext()); // color 쉐이더를 이용하여 모델을 렌더링 합니다. result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // 렌더링된 씬을 화면으로 출력합니다. m_D3D->EndScene(); return true; }



요약


요약하면 여러분은 버텍스, 인덱스 버퍼가 어떻게 동작하는지에 대한 기본을 익히셨을 것입니다. 또 버텍스, 픽셀 쉐이더의 기본과 어떻게 HLSL을 이용하여 작성하지에 대해서도 익히셨을 것입니다. 그리고 마지막으로 화면상에 추력된 초록색 삼각형을 생성하기 위해 어떻게 새로운 개념들을 우리의 프레임워크에 녹여냈는지를 이해하셨을 것입니다. 제가 또 말하고 싶은 것은 단순히 삼각형 하나를 그리는데 비해 코드가 상당히 길고 아마도 main() 함수에 모든 코드를 다 넣어 버릴 수 있을 겁니다. 그러나 제가 이런 방식의 프레임워크를 취한 것은 앞으로의 강좌에서는 훨씬 더 복잡한 그래픽을 위한 코드변화가 크기 않기 때문에 이런 방식으로 코드를 작성하였습니다.



연습하기


1. 컴파일 후 실행해 보세요. 초록색 삼각형이 그려지는지 확인하고 esc키를 누러 종료시키세요.


2. 삼각형 색상을 빨간색으로 바꿔보세요.


3. 삼각형을 사각형으로 바꿔보세요.


4. 카메라를 뒤로 10칸정도 이동해 보세요.


5. 밝기가 반이 되도록 픽셀 쉐이더를 수정하세요.

    (결정적 힌트! ColorPixelShader의 뭔가에 0.5f를 곱하세요.)

728x90

+ Recent posts