728x90

HCE (host-based card emulation)을 이용하면 nfc 태그가 없더라도 nfc통신이 가능하다.



리더기는 acr 1251제품을 사용하였으며 리더기 제어에 관한 코드는 여기에 있다.


hce를 사용하기 위해서는 기본적으로 HostApduService를 상속받아야 하는데 이름에서 알 수 있듯이 Service이기 때문에

매니페스트 파일에 추가해 주어야 한다.


AndroidMainifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hyunseo.hce_sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".CardService"
            android:exported="true"
            android:permission="android.permission.BIND_NFC_SERVICE">
            <!-- Intent filter indicating that we support card emulation. -->
            <intent-filter>
                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <!-- Required XML configuration file, listing the AIDs that we are emulating cards
                 for. This defines what protocols our card emulation service supports. -->
            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
                android:resource="@xml/aid_list"/>
        </service>
    </application>

    <uses-permission android:name="android.permission.NFC"/>

</manifest>


AID란 것이 존재하는데 여러 어플리케이션에서 어떤 어플리케이션과 nfc통신을 해야 하는지를 구분하기 위한 값으로 임의로 설정 가능하다.

매니페스트 파일에 aid값이 있는 xml파일을 명시해 주어야 한다. 또 android.permission.BIND_NFC_SERVICE권한을 꼭 추가해 주어야 한다.


MainActivity.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import org.w3c.dom.Text; public class MainActivity extends AppCompatActivity { private TextView _tv; private Intent _cardService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _tv = (TextView) findViewById(R.id._textView); _cardService = new Intent(this, CardService.class); startService(_cardService); } @Override protected void onDestroy() { stopService(_cardService); } public void onClickUpdate(View v) { _tv.setText("Count : " + Counter.GetCurrentCout()); } }


메인 액티비티에서 하는 일은 간단한데 사용자가 버튼을 누르면 Count클래스에 있는 cnt값을 화면에 표시한다.



CardService.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import java.util.Arrays; public class CardService extends HostApduService { private static final String TAG = "CardService"; // AID for our loyalty card service. private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222"; // ISO-DEP command HEADER for selecting an AID. // Format: [Class | Instruction | Parameter 1 | Parameter 2] private static final String SELECT_APDU_HEADER = "00A40400"; // "OK" status word sent in response to SELECT AID command (0x9000) private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000"); // "UNKNOWN" status word sent in response to invalid APDU command (0x0000) private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000"); private static final byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID); //////////////////////////////////////////////////////////////////////////////////// private Messenger _handler; @Override public int onStartCommand(Intent intent, int flags, int startId) { // UI thread 핸들 얻음 Bundle extras = intent.getExtras(); // 서비스를 앱 종료시까지 계속 실행상태로 return START_STICKY; } // 외부로부터의 APDU명령을 받았을때 호출 @Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { if (Arrays.equals(SELECT_APDU, commandApdu)) { Log.i(TAG, "Application selected"); Counter.AddOne(); return SELECT_OK_SW; } else { return UNKNOWN_CMD_SW; } } // 연결을 잃었을때 호출됨 @Override public void onDeactivated(int reason) { Log.i(TAG, "Deactivated: " + reason); } public static byte[] BuildSelectApdu(String aid) { // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA] return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid); } public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException { int len = s.length(); if (len % 2 == 1) { throw new IllegalArgumentException("Hex string must have even number of characters"); } byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters for (int i = 0; i < len; i += 2) { // Convert each character into a integer (base-16), then bit-shift into place data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } }


이 클래스가 nfc통신을 담당할 클래스이다. 외부로부터 이 어플리케이션에 apdu 명령을 할 경우 processCommandApdu() 메소드가 호출되며


카운터 클래스의 Counter.AddOne() 메소드를 호출한다.


기본적으로 Select AID란 작업이 완료되면 리더기에서 핸드폰을 떼기 전까지는 이 어플리케이션에만 apdu명령이 들어온다.


현재 aid는 f2222222로 설정하였고 Select AID의 apdu헤더는 00A40400이며 aid의 값은 5바이트(바이트로 변환하기 때문에 16진수로 끊어서)이기 때문에


여기서의 Select AID의 apdu명령은 "00A40400 05 f2222222"의 바이트열로 구성된다. 외부 카드리더기로부터 저 명령이 왔을때부터 핸드폰을 리더기에서


뗄때까지 이 어플리케이션과 통신이 이루어진다. 위 코드에는 select aid외 작업을 하지 않지만 원하면 다른 바이트를 주고 받을 수 있다.


select aid가 완료된 이후에는 따로 헤더가 필요치 않는다. processCommandApdu()의 리턴값은 리더기에 보낼 바이트열 값이다.



Counter.java

package com.example.hyunseo.hce_sample; public class Counter { private static int _cnt = 0; public static void AddOne() {_cnt++;} public static int GetCurrentCout() {return _cnt;} }


카운터 클래스는 간단하게 UI thread와 Service 두곳에서 접근 가능하도록 static으로 구성하였다. 카드서비스에서 _cnt의 값을 올리면


메인액티비티에서 증가된 값을 표시한다.



aid_list.xml

<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/service_name"
    android:requireDeviceUnlock="false">

    <aid-group android:description="@string/card_title" android:category="other">
        <aid-filter android:name="F222222222"/>
    </aid-group>
</host-apdu-service>


aid가 적혀있다. 외부로부터 이 값을 이용하여 들어온다.



실행결과 :

리더기에 찍고 UPDATE버튼을 누르면 카운트값이 변경이 된다.


자세한 설명은 구글 개발자 페이지를 확인하시면 됩니다.

https://developer.android.com/samples/CardEmulation/index.html

728x90
728x90

 원문 : http://www.rastertek.com/dx11tut17.html

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


Tutorial 17: Multitexturing and Texture Arrays


이번 강좌는 DirectX 11 에서 멀티텍스쳐링을 하는 방법과 텍스쳐 배열을 사용하는지에 대해 설명할 것입니다. 멀티텍스쳐링은 다른 두 텍스쳐를 섞어서 최종 텍스쳐를 얻는 과정입니다. 여러분이 텍스쳐를 혼합하는데 사용하는 방정식은 여러분이 성취하고자 하는바에 따라 다를 수 있습니다. 이번 강좌에서는 두 텍스쳐 색상의 평균값을 결합하여 균등하게 혼합된 최종 텍스쳐를 얻는 것을 볼 것입니다.


텍스쳐 배열은 DirectX 10부터 적용된 것으로 여러분이 gpu에서 한번에 여러 텍스쳐를 활성화를 가능하게 해줍니다. 이전 gpu에서 오직 단일 텍스쳐만 활성화되었을때 방법들은 항상 텍스쳐를 로드하고 언로드하는데 많은 추가 과정들이 초래했습니다. 대부분의 사람들은 이러한 문제를 텍스쳐 별칭을 사용하고 (하나의 큰 텍스쳐로 텍스쳐의 묶음을 로딩) UV 좌표를 다르게 사용하여 해결하였습니다. 그러나 이 새로운 특성으로 인해 텍스쳐 별칭 더이상 필요치 않습니다.


첫번째로 이 강좌에서 사용된 텍스쳐이며 우리는 기본 텍스쳐로 부를 것이고 다음과 같습니다.



두번째 텍스쳐는 첫번째것과 결합하는데 사용될 것이고 컬러 텍스쳐라 불릴 겁니다. 다음과 같습니다.



이 두 텍스쳐는 픽셀 쉐이더에서 픽셀대 픽셀로 결합될 것입니다. 우리가 사용할 블렌딩 방정식은 다음과 같습니다.


	blendColor = basePixel * colorPixel * gammaCorrection;


저 방정식과 위 두 텍스쳐를 사용하여 다음과 같은 결과를 얻을 것입니다.



여러분들은 아마도 의아해 하실 겁니다. 왜 그냥 두 텍스쳐 픽셀의 평균값을 다음처럼 더하지 않는지..


	blendColor = (basePixel * 0.5) + (colorPixel * 0.5);


그 이유는 우리에게 표현되는 픽셀 색상은 모니터의 감마값에 달려있기 때문입니다. 이는 픽셀 값을 0.0에서 1.0으로 곡선을 따르게 합니다. 따라서 비선형 컬러 값을 다루기 위해 픽셀 쉐이더에서 감마 보정이 필요합니다. 감마 보정을 하지 않고 단순히 평균을 더하면 다음과 같이 뭔가 젖은 듯한 결과를 얻게 됩니다.



또 중요한게 대부분의 장치들은 감마값이 달라 룩업테이블(미리 계산된 값이 나열된 테이블)이나 감마 슬라이더(보통의 모니터 화면 조정에 있는 것)를 통해 사용자가 장치에 대한 감마 설정을 할 수 있게 해줍니다. 이번 예제에서는 강좌를 쉽게 하기 위해 감마값을 2.0으로 하겠습니다.


먼저 텍스쳐 쉐이더 파일을 조금 변형한 멀티텍스쳐 쉐이더를 살펴봄으로 코드 섹션을 시작하겠습니다.


Multitexture.vs


버텍스 쉐이더는 이름만 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.vs
////////////////////////////////////////////////////////////////////////////////


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


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType MultiTextureVertexShader(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;
    
    return output;
}


Multitexture.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////


서로 혼합될 두 텍스쳐에 대한 텍스쳐 배열 리소스를 추가하였습니다. 텍스쳐 배열은 그래픽 카드상 성능면에서 단일 텍스쳐 리소스를 사용하는 것보다 더 효율적입니다. 텍스쳐 스위칭은 이전 DirectX 버전에서 비용이 큰 작업이어서 대부분의 엔진들을 텍스쳐와 재질 스위치 중심으로 작성되어야 했습니다. 텍스쳐 배열은 그런 수행 비용을 줄이는데 도움이 됩니다.


Texture2D shaderTextures[2];
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


픽셀 쉐이더가 이번 강좌의 모든 작업이 수행되는 곳입니다. 현재 두 텍스쳐의 좌표에서 픽셀의 샘플을 취합니다. 그 후에 픽셀 컬러를 곱하여 결합하는데 감마 보정때문에 비선형이기 때문입니다. 또 감마값을 곱하는데 대부분의 모니터의 감마값에 가까운 2.0를 사용하였습니다. 혼합된 픽셀을 얻은 뒤 0~1사이로 만들고(saturate) 최종 결과로 반환합니다. 또 텍스쳐 배열의 두 텍스쳐에 접근하는데 인덱싱을 사용한 것을 기억하시기 바랍니다.


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 MultiTexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 color1;
    float4 color2;
    float4 blendColor;


    // Get the pixel color from the first texture.
    color1 = shaderTextures[0].Sample(SampleType, input.tex);

    // Get the pixel color from the second texture.
    color2 = shaderTextures[1].Sample(SampleType, input.tex);

    // Blend the two pixels together and multiply by the gamma value.
    blendColor = color1 * color2 * 2.0;
    
    // Saturate the final color.
    blendColor = saturate(blendColor);

    return blendColor;
}


Multitextureshaderclass.h


multitexture shader 코드는 TextureShaderClass에서 약간 수정하였습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MULTITEXTURESHADERCLASS_H_
#define _MULTITEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: MultiTextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class MultiTextureShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};

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

	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;
	ID3D11SamplerState* m_sampleState;
};

#endif


Multitextureshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "multitextureshaderclass.h"


MultiTextureShaderClass::MultiTextureShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_matrixBuffer = 0;
	m_sampleState = 0;
}


MultiTextureShaderClass::MultiTextureShaderClass(const MultiTextureShaderClass& other)
{
}


MultiTextureShaderClass::~MultiTextureShaderClass()
{
}


bool MultiTextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


multitexture HLSL 쉐이더 파일은 여기서 로드됩니다.


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/multitexture.vs", L"../Engine/multitexture.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


Shutdown은 ShutdownShader 함수를 호출하여 쉐이더 관련 인터페이스들을 해제합니다.


void MultiTextureShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


이제 Render 함수는 텍스쳐 배열의 포인터를 받습니다. 이는 쉐이더에 혼할 계산을 위한 두 텍스쳐의 접근을 하게 해줄 것입니다.


bool MultiTextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, 
				     D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


InitializeShader 함수는 정점 쉐이더와 픽셀 쉐이더 뿐만아니라 레이아웃, 매트릭스 버퍼와 샘플 state도 로드한다.


bool MultiTextureShaderClass::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;
	D3D11_SAMPLER_DESC samplerDesc;


	// 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, "MultiTextureVertexShader", "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, "MultiTexturePixelShader", "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 vertex 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;

	// 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 matrix dynamic 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 matrix 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;
	}

	// 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는 InitializeShader 함수에서 셋업된 모든 인터페이스들을 해제합니다.


void MultiTextureShaderClass::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 MultiTextureShaderClass::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 MultiTextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, 
						  D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
						  ID3D11ShaderResourceView** textureArray)
{
	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 matrix 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 matrix constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the matrix constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the matrix constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);


여기가 렌더링전에 텍스쳐 배열이 설정되는 곳입니다. PSSetShaderResources 함수는 텍스쳐 배열을 설정하는데 사용됩니다. 첫번째 파라미터는 배열에서 시작하는 곳(인덱스)입니다. 두번째 파라미터는 넘겨지는 배열에 든 텍스쳐의 개수 입니다. 그리고 세번째 파라미터는 텍스쳐 배열에 대한 포인터입니다.


	// Set shader texture array resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 2, textureArray);

	return true;
}


RenderShader 함수는 레이아웃, 쉐이더, 샘플러를 설정합니다. 그리고는 쉐이더를 이용하여 모델을 그립니다.


void MultiTextureShaderClass::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 triangles.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}


Texturearrayclass.h


TextureArrayClass는 전에 사용된 TextureClass를 대체합니다. 이제 단순 단일 텍스쳐를 가지지 않고 다수의 텍스쳐를 가질 수 있고 필요로 하는 오브젝트들에게 접근할 수 있게 됩니다. 이번 강좌는 그저 텍스쳐 두개만 다루지만 쉽게 확장될 수 있습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTUREARRAYCLASS_H_
#define _TEXTUREARRAYCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx11tex.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureArrayClass
////////////////////////////////////////////////////////////////////////////////
class TextureArrayClass
{
public:
	TextureArrayClass();
	TextureArrayClass(const TextureArrayClass&);
	~TextureArrayClass();

	bool Initialize(ID3D11Device*, WCHAR*, WCHAR*);
	void Shutdown();

	ID3D11ShaderResourceView** GetTextureArray();

private:


여기 원소 2개짜리 텍스쳐 배열이 This is the two element texture array private variable.


	ID3D11ShaderResourceView* m_textures[2];
};

#endif


Texturearrayclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "texturearrayclass.h"


클래스 생성자는 텍스쳐 배열 원소들을 null로 초기화합니다.


TextureArrayClass::TextureArrayClass()
{
	m_textures[0] = 0;
	m_textures[1] = 0;
}


TextureArrayClass::TextureArrayClass(const TextureArrayClass& other)
{
}


TextureArrayClass::~TextureArrayClass()
{
}


Initialize 함수는 두 텍스쳐 파일 이름을 받아서 해당 파일로부터 텍스쳐 배열안에 두 텍스쳐 자원을 생성합니다.


bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	HRESULT result;


	// Load the first texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Load the second texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


Shutdown 함수는 텍스쳐 배열의 각 원소를 해제합니다.


void TextureArrayClass::Shutdown()
{
	// Release the texture resources.
	if(m_textures[0])
	{
		m_textures[0]->Release();
		m_textures[0] = 0;
	}

	if(m_textures[1])
	{
		m_textures[1]->Release();
		m_textures[1] = 0;
	}

	return;
}


GetTextureArray 함수는 텍스쳐 배열의 포인터를 반환하여 함수를 호출하는 객체는 텍스쳐 배열내 텍스쳐들에 접근할 수 있습니다.


ID3D11ShaderResourceView** TextureArrayClass::GetTextureArray()
{
	return m_textures;
}


Modelclass.h


ModelClass는 텍스쳐 배열을 사용하도록 수정되었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;


TextureArrayClass 헤더파일이 이전의 TextureClass 헤더를 대신하여 포함되었습니다.


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "texturearrayclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

	struct ModelType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

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

	bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);

	int GetIndexCount();
	ID3D11ShaderResourceView** GetTextureArray();

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

	bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*);
	void ReleaseTextures();

	bool LoadModel(char*);
	void ReleaseModel();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	ModelType* m_model;


이제 TextureClass 변수 대신 TextureArrayClass 변수를 가집니다.


	TextureArrayClass* m_TextureArray;
};

#endif


Modelclass.cpp


이전 강좌이후 바뀐 함수들만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_model = 0;


생성자에서 새 TextureArray변수를 초기화합니다.


	m_TextureArray = 0;
}


bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2)
{
	bool result;


	// 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;
	}


이번 모델에서 텍스쳐배열에 로드되고 혼합된 결과로 렌더링 될 텍스쳐에 대한 여러 파일 이름을 받는 LoadTextures 함수를 호출합니다.


	// Load the textures for this model.
	result = LoadTextures(device, textureFilename1, textureFilename2);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{


Shutdown 함수에서 ReleaseTextures 함수를 호출하여 텍스쳐 배열을 해제합니다.


	// Release the model textures.
	ReleaseTextures();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	// Release the model data.
	ReleaseModel();

	return;
}


호출하는 객체에게 모델 렌더링에 사용될 텍스쳐 배열로의 접근을 가능케 해주는 GetTextureArray 함수를 새로 가집니다.


ID3D11ShaderResourceView** ModelClass::GetTextureArray()
{
	return m_TextureArray->GetTextureArray();
}


LoadTextures 함수는 함수에 매개변수로 들어온 두 텍스쳐를 로딩을 하기위해 TextureArrayClass 객체를 생성하고 초기화하도록 바뀌었습니다.


bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	bool result;


	// Create the texture array object.
	m_TextureArray = new TextureArrayClass;
	if(!m_TextureArray)
	{
		return false;
	}

	// Initialize the texture array object.
	result = m_TextureArray->Initialize(device, filename1, filename2);
	if(!result)
	{
		return false;
	}

	return true;
}


ReleaseTextures 함수는 TextureArrayClass 객체를 해제합니다.


void ModelClass::ReleaseTextures()
{
	// Release the texture array object.
	if(m_TextureArray)
	{
		m_TextureArray->Shutdown();
		delete m_TextureArray;
		m_TextureArray = 0;
	}

	return;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"


GraphicsClass에서 MultiTextureShaderClass에 대한 헤더를 포함합니다.


#include "multitextureshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;


여기서 새로운 MultiTextureShaderClass 객체를 생성하였습니다.


	MultiTextureShaderClass* m_MultiTextureShader;
};

#endif


Graphicsclass.cpp


이전 강좌에서 바뀐 함수만을 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;


생성자에서 새 mulltitexture shader 객체를 null로 초기화합니다.


	m_MultiTextureShader = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// 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;
	}

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}


이제 ModelClass 객체는 좀 다르게 초기화됩니다. 이번 강좌에는 간단히 사각형 평면 위에서 잘 보여지는지 확인만을 목적으로 square.txt 모델을 로드합니다. 또 이전 강좌에서처럼 단일 텍스쳐가 아닌 텍스쳐 배열을 위한 두 텍스쳐를 로드합니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", 
				     L"../Engine/data/dirt01.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}


여기 새 multitexture shader 객체를 생성하고 초기화합니다.


	// Create the multitexture shader object.
	m_MultiTextureShader = new MultiTextureShaderClass;
	if(!m_MultiTextureShader)
	{
		return false;
	}

	// Initialize the multitexture shader object.
	result = m_MultiTextureShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the multitexture shader object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


Shutdown 함수에서 multitexture shader를 해제합니다.


	// Release the multitexture shader object.
	if(m_MultiTextureShader)
	{
		m_MultiTextureShader->Shutdown();
		delete m_MultiTextureShader;
		m_MultiTextureShader = 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()
{


블렌딩 효과를 좀더 쉽게 보기 위해 카메라의 위치를 약간 더 가깝게 설정하였습니다.


	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -5.0f);

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;


	// 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, projection, and ortho matrices from the camera and D3D objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());


multitexture shader를 모델 렌더링에 사용합니다. 주의할 점은 텍스쳐 배열을 ModelClass에서 입력으로 쉐이더에 보내야 합니다.


	// Render the model using the multitexture shader.
	m_MultiTextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
				     m_Model->GetTextureArray());

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}


Summary


이제 두 텍스쳐를 고르게 결합하고 감마 보정을 적용시킬 쉐이더를 갖게 되었습니다. 또 향상된 그래픽스 퍼포먼스를 위해 텍스쳐 배열을 어떻게 사용하는지에 대해서도 배웠습니다.




연습하기


1. 코드를 재컴파일하고 프로그램을 실행하여 결과 이미지 확인하기. 종료하기 위해 esc버튼을 누르세요.


2. 다른 텍스쳐들로 바꿔서 결과 보기.

728x90
728x90

윤성우씨의 "열혈강의 TCP/IP 소켓 프로그래밍" 책 끝부분의 IOCP 예제를 c++형식으로 바꾸었다.


main.cpp

#include "IOCPServer.h"

int main()
{
    IOCPServer *s = new IOCPServer();

    s->Init();

    s->Run();

    delete s;
    return 0;
}


별 내용 없다.


IOCPServer.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <winsock2.h>

#define PORT        9088
#define BUFSIZE     1024
#define READ        3
#define WRITE       5

///////////////////////////////////////////////
// Structure Definition
typedef struct          // 소켓 정보를 구조체화
{
    SOCKET      hClntSock;
    SOCKADDR_IN clntAddr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct          // 소켓의 버퍼 정보를 구조체화
{
    OVERLAPPED overlapped;
    CHAR       buffer[BUFSIZE];
    WSABUF     wsaBuf;
    int        rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;


////////////////////////////////////////////////
// Class definition
class IOCPServer {
public:
    IOCPServer();
    ~IOCPServer();

    bool Run();
    bool Init();
    void Cleanup();

    bool setSocket();

    static unsigned int __stdcall _CompletionThread(void *p_this);
    UINT WINAPI CompletionThread();

private:
    HANDLE  m_hCompletionPort;

    SOCKET m_hServSock;
    SOCKADDR_IN m_servAddr;
};

//////////////////////////////////////////
// Function forward declaration
void ErrorHandling(LPCSTR pszMessage);


중간에 _CompletionThread() 멤버함수가 static인 이유는 _beginthreadex() 함수의 인자때문으로


_CompletionThread()가 호출되면 바로 CompletionThread() 멤버함수를 호출한다.


IOCPServer.cpp

#include "IOCPServer.h" IOCPServer::IOCPServer() { } IOCPServer::~IOCPServer() { closesocket(m_hServSock); WSACleanup(); } bool IOCPServer::Init() { WSAData wsaData; SYSTEM_INFO systemInfo; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // Load Winsock 2.2 DLL ErrorHandling("WSAStartup() error!"); } // Completion Port 생성 m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); GetSystemInfo(&systemInfo); // 쓰레드를 CPU 개수*2 만큼 생성 for (int i = 0; i<systemInfo.dwNumberOfProcessors*2; i++) { _beginthreadex(NULL, 0, _CompletionThread, (LPVOID)this, 0, NULL); } setSocket(); return true; } void IOCPServer::Cleanup() { } bool IOCPServer::setSocket() { // IOCP를 사용하기 위한 소켓 생성 (Overlapped I/O 소켓, 윈도우용) m_hServSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (m_hServSock == INVALID_SOCKET) { ErrorHandling("WSASocket() error!"); } // 바인딩할 소켓 주소정보 memset(&m_servAddr, 0, sizeof(m_servAddr)); m_servAddr.sin_family = AF_INET; m_servAddr.sin_addr.s_addr = htonl(INADDR_ANY); m_servAddr.sin_port = htons(PORT); if (bind(m_hServSock, (SOCKADDR *)&m_servAddr, sizeof(m_servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!"); } if (listen(m_hServSock, 10) == SOCKET_ERROR) { ErrorHandling("listen() error!"); } return true; } bool IOCPServer::Run() { LPPER_IO_DATA perIoData; LPPER_HANDLE_DATA perHandleData; DWORD dwRecvBytes; DWORD i, dwFlags; while (TRUE) { SOCKET hClntSock; SOCKADDR_IN clntAddr; int nAddrLen = sizeof(clntAddr); hClntSock = accept(m_hServSock, (SOCKADDR *)&clntAddr, &nAddrLen); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!"); } // 연결된 클라이언트의 소켓 핸들 정보와 주소 정보를 설정 perHandleData = new PER_HANDLE_DATA; perHandleData->hClntSock = hClntSock; memcpy(&(perHandleData->clntAddr), &clntAddr, nAddrLen); // 3. Overlapped 소켓과 Completion Port 의 연결 CreateIoCompletionPort((HANDLE)hClntSock, m_hCompletionPort, (DWORD)perHandleData, 0); // 클라이언트를 위한 버퍼를 설정, OVERLAPPED 변수 초기화 perIoData = new PER_IO_DATA; memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = BUFSIZE; perIoData->wsaBuf.buf = perIoData->buffer; perIoData->rwMode = READ; dwFlags = 0; // 4. 중첩된 데이타 입력 WSARecv(perHandleData->hClntSock, // 데이타 입력 소켓 &(perIoData->wsaBuf), // 데이타 입력 버퍼 포인터 1, // 데이타 입력 버퍼의 수 &dwRecvBytes, &dwFlags, &(perIoData->overlapped), // OVERLAPPED 변수 포인터 NULL ); } return true; } unsigned int __stdcall IOCPServer::_CompletionThread(void *p_this) { IOCPServer* p_IOCPServer = static_cast<IOCPServer*>(p_this); p_IOCPServer->CompletionThread(); // Non-static member function! return 0; } UINT WINAPI IOCPServer::CompletionThread() { HANDLE hCompletionPort = (HANDLE)m_hCompletionPort; SOCKET hClntSock; DWORD dwBytesTransferred; LPPER_HANDLE_DATA perHandleData; LPPER_IO_DATA perIoData; DWORD dwFlags; while (TRUE) { // 5. 입.출력이 완료된 소켓의 정보 얻음 GetQueuedCompletionStatus(hCompletionPort, // Completion Port &dwBytesTransferred, // 전송된 바이트 수 (LPDWORD)&perHandleData, (LPOVERLAPPED *)&perIoData, INFINITE); //printf("GQCS error code : %d (TID : %d)\n", GetLastError(), GetCurrentThreadId()); if (perIoData->rwMode == READ) { puts("message received!"); if (dwBytesTransferred == 0) // 전송된 바이트가 0일때 종료 (EOF 전송 시에도) { puts("socket will be closed!"); closesocket(perHandleData->hClntSock); delete perHandleData; delete perIoData; continue; } // 6. 메세지. 클라이언트로 에코 memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = dwBytesTransferred; perIoData->rwMode = WRITE; WSASend(perHandleData->hClntSock, &(perIoData->wsaBuf), 1, NULL, 0, &(perIoData->overlapped), NULL); // RECEIVE AGAIN perIoData = new PER_IO_DATA(); memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = BUFSIZE; perIoData->wsaBuf.buf = perIoData->buffer; perIoData->rwMode = READ; dwFlags = 0; WSARecv(perHandleData->hClntSock, &(perIoData->wsaBuf), 1, NULL, &dwFlags, &(perIoData->overlapped), NULL); } else // WRITE Mode { puts("message sent!"); delete perIoData; } } return 0; } void ErrorHandling(LPCSTR pszMessage) { fputs(pszMessage, stderr); fputc('\n', stderr); exit(1); }


CreateIoCompletionPort() 함수는 특이하게도 이름처럼 completion port(이하 cp)를 생성하는데도 쓰지만


cp에 소켓을 등록하는데에도 쓰인다.


Run() 멤버 함수는 호출되면 클라이언트의 연결을 받아들이고 cp에 등록 후 WSARecv()를 호출하는 과정을 반복한다.


그리고 CompletionThread() 에서 각 스레드는 GetQueuedCompletionStatus() 를 호출하고 입출력이 완료된 소켓이


큐에 올라올때까지 대기한다. 입출력이 완료된(입력이 완료된) 소켓이 올라오면 받은 데이터를 그대로 WSASend()를 이용하여 보내주고


다시 WSARecv()호출한다.


실행한 모습

728x90

'프로그래밍 > C++' 카테고리의 다른 글

[C++] Reference Counting  (2) 2020.11.04
힙(Heap)과 완전 이진 트리(Complete binary tree)  (1) 2019.10.31
[c] SHA-1 구조 및 코드  (2) 2017.12.09
[c] AES 구조 및 코드  (2) 2017.11.15
[c] DES 구조 및 코드  (21) 2017.10.26
728x90

나중에 쓸 것 같아서 (이러고 쓰지 않는다.) 대충 서버와 메시지를 주고 받을 수 있게 만들었다.


기본 화면 구성이다.



IP와 Port를 받아서 소켓을 생성하고 Input datas에 아무 문자열을 입력하면 보내면


에코서버에서 똑같은 문자열을 보내며 그 결과를 토스트객체를 통해 화면에 띄운다. 이게 다다. 매우 초라하다.


예외처리를 하지 않았기 때문에 소켓 생성 없이 SEND 버튼을 누르면 앱이 뻗는다. 꼭 미리 누르자.


AndroidManifest.xml 파일에 네트워킹 사용을 위해


=> <uses-permission android:name="android.permission.INTERNET"/>


이 퍼미션을 넣는 것을 잊으면 안된다.

참고 : 에코서버 코드



MainActivity.java

package com.example.hyunseo.testingapp;

import android.app.ProgressDialog;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.*;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button m_bCreateSocket;
    private Button m_bSend;
    private EditText m_etInputIP;
    private EditText m_etInputPort;
    private EditText m_etSendData;
    private ProgressDialog m_pdIsLoading;

    private SocketManager m_hSocketManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        m_etInputIP = (EditText) findViewById(R.id.editText_inputIP);
        m_etInputPort = (EditText) findViewById(R.id.editText_inputPort);
        m_etSendData = (EditText) findViewById(R.id.editText_sendData);
        m_pdIsLoading = new ProgressDialog(this);

        // 클릭 이벤트 리스너 등록
        m_bCreateSocket.setOnClickListener(this);
        m_bSend.setOnClickListener(this);
    }

    private Handler m_Handler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case 0: // 소켓 생성 완료
                    // 로딩화면 제거
                    m_pdIsLoading.dismiss();
                    break;
                case 1: // 데이터 수신 완료
                    // 수신 데이터 토스트로 띄움.
                    Toast.makeText(MainActivity.this, "server responded : " + msg.obj, Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_CreateSocket:
                String IP = m_etInputIP.getText().toString();
                int Port = Integer.parseInt(m_etInputPort.getText().toString());

                m_pdIsLoading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                m_pdIsLoading.setMessage("Loading..");
                m_pdIsLoading.show();

                m_hSocketManager = new SocketManager(IP, Port, m_Handler);
                break;

            case R.id.button_Send:
                m_hSocketManager.sendData(m_etSendData.getText().toString());
                break;
        }
    }
}


안드로이드에서는 아이스크림 샌드위치 버전부터(아마 맞음) 소켓 생성같은 네트워킹 작업들을 메인 UI 스레드에서는


못하도록 해놓았다. 그래서 SocketManager에서 별도의 스레드를 생성하여 네트워킹을 관리하며 메인 스레드에서는 핸들러를 통한


화면 갱신만을 하고 있다. 여기서 ProgressDialog는 흔히 "로딩중"을 표시하는 객체로



요 화면이다. 소켓 생성이 끝나면 핸들러를 통해 메시지를 받아 ProgressDialog를 제거한다.


SocketManager.java

package com.example.hyunseo.testingapp; import android.os.Handler; import android.os.Message; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Iterator; public class SocketManager { private String IP; private int Port; private SocketChannel m_hSocketChannel; private Selector m_hSelector; private readDataThread m_readData; private sendDataThread m_sendData; private Handler m_handler; public SocketManager(String ip, int port, Handler h) { this.IP = ip; this.Port = port; this.m_handler = h; // thread objects의 작업 할당 및 초기화 m_readData = new readDataThread(); m_readData.start(); } // m_createSocket thread 안에서 실행 private void setSocket(String ip, int port) throws IOException { // selector 생성 m_hSelector = Selector.open(); // 채널 생성 m_hSocketChannel = SocketChannel.open(new InetSocketAddress(ip, port)); // 논블로킹 모드 설정 m_hSocketChannel.configureBlocking(false); // 소켓 채널을 selector에 등록 m_hSocketChannel.register(m_hSelector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE); } public void sendData(String data) { m_sendData = new sendDataThread(m_hSocketChannel, data); m_sendData.start(); } private void read(SelectionKey key) throws Exception { // SelectionKey로부터 소켓채널을 얻어온다. SocketChannel sc = (SocketChannel) key.channel(); // ByteBuffer를 생성한다. ByteBuffer buffer = ByteBuffer.allocate(1024); int read = 0; // 요청한 클라이언트의 소켓채널로부터 데이터를 읽어들인다. read = sc.read(buffer); buffer.flip(); String data = new String(); CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); data = decoder.decode(buffer).toString(); // 메시지 얻어오기 Message msg = m_handler.obtainMessage(); // 메시지 ID 설정 msg.what = 1; // 메시지 정보 설정3 (Object 형식) msg.obj = data; m_handler.sendMessage(msg); // 버퍼 메모리를 해제한다. clearBuffer(buffer); } private void clearBuffer(ByteBuffer buffer) { if (buffer != null) { buffer.clear(); buffer = null; } } /*********** inner thread classes **************/ public class sendDataThread extends Thread { private SocketChannel sdt_hSocketChannel; private String data; public sendDataThread(SocketChannel sc, String d) { sdt_hSocketChannel = sc; data = d; } public void run() { try { // 데이터 전송. sdt_hSocketChannel.write(ByteBuffer.wrap(data.getBytes())); } catch (Exception e1) { } } } public class readDataThread extends Thread { public readDataThread() { } public void run() { try { setSocket(IP, Port); } catch (IOException e) { e.printStackTrace(); } // 소켓 생성 완료를 메인UI스레드에 알림. m_handler.obtainMessage(); m_handler.sendEmptyMessage(0); // 데이터 읽기 시작. try { while(true) { // 셀렉터의 select() 메소드로 준비된 이벤트가 있는지 확인한다. m_hSelector.select(); // 셀렉터의 SelectoedSet에 저장된 준비된 이벤트들(SelectionKey)을 하나씩 처리한다. Iterator it = m_hSelector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isReadable()) { // 이미 연결된 클라이언트가 메시지를 보낸경우... try { read(key); } catch (Exception e) { } } // 이미 처리한 이벤트므로 반드시 삭제해준다. it.remove(); } } } catch (Exception e) { } } } }


아주 중구난방이다.


기본 소켓은 블럭킹 방식이기 때문에  SocketChannel을 이용하여 논블럭킹 방식으로 소켓을 생성하였다.



크게 중요한 스레드 객체가 두개있다. 소켓 생성 후 소켓들이 등록된(클라이언트라 하나밖에 음슴) Selector를 돌며


읽을 수 있는 상태의 소켓이 있으면 데이터를 읽는 readDataThread와 단순히 서버로 데이터를 보내는 sendDataThread이다.


readDataThread에서 수신 가능한 소켓이 있으면 read() 메소드를 호출하여 핸들러를 통해 UI 스레드로 보내고


UI 스레드에서 Toast를 이용하여 화면에 띄운다.





728x90
728x90

원문 : http://www.rastertek.com/dx11tut16.html



Tutorial 16: Frustum Culling


모든 것이 그려지는 화면상의 3차원 뷰잉 영역은 "뷰잉 프러스텀(절두체)"이라고 부른다. 프러스텀안에 있는 모든 것들은 비디오카드에 의해 화면에 렌더링될 것입니다. 프러스텀 밖에 있는 모든 것들은 비디오 카드가 조사하고 렌더링 처리시 폐기할 것입니다.


그러나 골라내기 위해 비디오 카드에 의존한 처리는 우리가 큰 씬을 가지고 있다면 비쌀 수 있습니다. 예를 들어 각각 5000개의 폴리곤으로 이루어진 모델이 2000개 이상 있지만 주어진 시간에 10~20개만 볼 수 있는 씬을 가지고 있습니다. 비디오 카드는 씬에서 1990개의 모델들을 지우기 위해 모든 2000개의 모델들의 모든 삼각형들을 조사해야 하며 그래야 모델 10개를 그릴 수 있습니다. 보시다시피 이건 매우 비효율적입니다.


프러스텀 컬링이 우리의 문제를 해결하는 방법은 렌더링 전에 모델이 프러스텀 안에 있는지 아닌지를 대신에 알 수 있습니다. 이는 모든 삼각형을 비디오 카드에 보내는 것을 막아주며 그려질 삼각형만을 보내게 해줍니다. 이것을 하는 방법은 각 모델 주위로 큐브, 직육면체나 구를 놓고 큐브, 직육면체나 구가 볼 수 있는지를 계산하는 것입니다. 이것에 필요한 수학은 코드 몇줄로 되어 있으며 몇천개의 삼각형의 계산을 없애줍니다.


어떻게 동작하는지 설명하기 위해 먼저 랜덤하게 배치된 구 25개가 있는 씬을 만들 것입니다. 우리의 뷰 밖의 구들을 컬링하기 위해 손수 방향키를 왼쪽 오른쪽 누르며 카메라를 회전시킬 것입니다. 또 카운터를 사용하고 그려지고 있는 구들과 컬링 되지 않은 구들의 개수를 나타낼 것입니다. 씬을 만들기 위해 이전 몇몇 강좌들로 부터의 코드를 사용할 것입니다.



Framework


프레임워크는 거의 이전 몇몇 강좌들의 클래스들을 가집니다. 새로운 새 클래스 3개는 FrustumClass, PositionClass, ModelListClass입니다. FrustumClass은 이번 강좌가 포커스를 맞춘 프러스텀 컬링 기술을 캡슐화할 것입니다. ModelListClass는 우리가 프로그램을 실행할 때마다 랜덤하게 생성될 25개 구의 위치와 색상 정보의 리스트를 포함할 것입니다. PositionClass은 사용자가 누르고 있는 방향키에 따라 카메라의 회전을 처리할 것입니다.




Frustumclass.h


FrustumClass의 헤더 파일은 상당히 간단합니다. 클래스는 어떤 초기화나 정리가 필요하지 않습니다. 매 프레임마다 카메라가 렌더링되고 난 뒤 ConstructFrustum 함수가 호출됩니다. ConstructFrustum 함수는 갱신된 보는 위치에 따른 뷰 프러스텀의 6면을 계산하고 저장하기 위해 private m_planes를 사용합니다. 그로부터 점, 큐브, 구, 직육면체가 뷰잉 프러스텀 안에 있는지 아닌지 보기 위해 4가지 체크 함수들 아무거나 호출할 수 있습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: frustumclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FRUSTUMCLASS_H_
#define _FRUSTUMCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: FrustumClass
////////////////////////////////////////////////////////////////////////////////
class FrustumClass
{
public:
	FrustumClass();
	FrustumClass(const FrustumClass&);
	~FrustumClass();

	void ConstructFrustum(float, D3DXMATRIX, D3DXMATRIX);

	bool CheckPoint(float, float, float);
	bool CheckCube(float, float, float, float);
	bool CheckSphere(float, float, float, float);
	bool CheckRectangle(float, float, float, float, float, float);

private:
	D3DXPLANE m_planes[6];
};

#endif


Frustumclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: frustumclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "frustumclass.h"


FrustumClass::FrustumClass()
{
}


FrustumClass::FrustumClass(const FrustumClass& other)
{
}


FrustumClass::~FrustumClass()
{
}


ConstructFrustum은 매 프레임마다 GraphicsClass에 의해 호출됩니다. 화면의 깊이와 투영 행렬 그리고 뷰 행렬을 취합니다. 이 파라미터 변수들을 해당 프레임에서 뷰 프러스텀의 행렬을 계산하기 위해 사용합니다. 새 프러스텀 행렬과 함께 뷰 프러스텀을 형성하는 여섯 면을 계산합니다.


void FrustumClass::ConstructFrustum(float screenDepth, D3DXMATRIX projectionMatrix, D3DXMATRIX viewMatrix)
{
	float zMinimum, r;
	D3DXMATRIX matrix;

	
	// Calculate the minimum Z distance in the frustum.
	zMinimum = -projectionMatrix._43 / projectionMatrix._33;
	r = screenDepth / (screenDepth - zMinimum);
	projectionMatrix._33 = r;
	projectionMatrix._43 = -r * zMinimum;

	// Create the frustum matrix from the view matrix and updated projection matrix.
	D3DXMatrixMultiply(&matrix, &viewMatrix, &projectionMatrix);

	// Calculate near plane of frustum.
	m_planes[0].a = matrix._14 + matrix._13;
	m_planes[0].b = matrix._24 + matrix._23;
	m_planes[0].c = matrix._34 + matrix._33;
	m_planes[0].d = matrix._44 + matrix._43;
	D3DXPlaneNormalize(&m_planes[0], &m_planes[0]);

	// Calculate far plane of frustum.
	m_planes[1].a = matrix._14 - matrix._13; 
	m_planes[1].b = matrix._24 - matrix._23;
	m_planes[1].c = matrix._34 - matrix._33;
	m_planes[1].d = matrix._44 - matrix._43;
	D3DXPlaneNormalize(&m_planes[1], &m_planes[1]);

	// Calculate left plane of frustum.
	m_planes[2].a = matrix._14 + matrix._11; 
	m_planes[2].b = matrix._24 + matrix._21;
	m_planes[2].c = matrix._34 + matrix._31;
	m_planes[2].d = matrix._44 + matrix._41;
	D3DXPlaneNormalize(&m_planes[2], &m_planes[2]);

	// Calculate right plane of frustum.
	m_planes[3].a = matrix._14 - matrix._11; 
	m_planes[3].b = matrix._24 - matrix._21;
	m_planes[3].c = matrix._34 - matrix._31;
	m_planes[3].d = matrix._44 - matrix._41;
	D3DXPlaneNormalize(&m_planes[3], &m_planes[3]);

	// Calculate top plane of frustum.
	m_planes[4].a = matrix._14 - matrix._12; 
	m_planes[4].b = matrix._24 - matrix._22;
	m_planes[4].c = matrix._34 - matrix._32;
	m_planes[4].d = matrix._44 - matrix._42;
	D3DXPlaneNormalize(&m_planes[4], &m_planes[4]);

	// Calculate bottom plane of frustum.
	m_planes[5].a = matrix._14 + matrix._12;
	m_planes[5].b = matrix._24 + matrix._22;
	m_planes[5].c = matrix._34 + matrix._32;
	m_planes[5].d = matrix._44 + matrix._42;
	D3DXPlaneNormalize(&m_planes[5], &m_planes[5]);

	return;
}


CheckPoint는 한 점이 뷰잉 프러스텀안에 있는지를 확인합니다. 4개의 확인 알고리즘중 가장 일반적이지만 특정 상황에서 잘 쓰면 매우 효율적입니다. 한 점을 받아서 모든 여섯 면안에 있는지를 확인합니다. 안에 있다면 true를 반환하고 그렇지 않으면 false를 반환합니다.


bool FrustumClass::CheckPoint(float x, float y, float z)
{
	int i;


	// Check if the point is inside all six planes of the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x, y, z)) < 0.0f)
		{
			return false;
		}
	}

	return true;
}


CheckCube는 큐브의 여덟 꼭지점이 뷰잉 프러스텀에 있는지를 확인합니다. 파라미터로 큐브의 중앙점과 반경을 요구하며 큐브의 여덟 꼭지점을 계산하는데 사용합니다. 그러고는 뷰잉 프러스텀의 모든 6면안에 한 꼭지점이라도 있는지를 확인합니다. 찾으면 true를 그렇지 않으면 false를 반환합니다.


bool FrustumClass::CheckCube(float xCenter, float yCenter, float zCenter, float radius)
{
	int i;


	// Check if any one point of the cube is in the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}
		
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}
		
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		return false;
	}

	return true;
}


CheckSphere는 중심점에서 구의 반경이 뷰잉 프러스텀의 모든 여섯 면안에 있는지를 확인합니다. 밖에 있다면 구는 보여질 수 없고 함수는 false를 반환할 것입니다. 안에 있다면 함수는 구가 보여질 수 있는 true를 반환합니다.


bool FrustumClass::CheckSphere(float xCenter, float yCenter, float zCenter, float radius)
{
	int i;


	// Check if the radius of the sphere is inside the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(xCenter, yCenter, zCenter)) < -radius)
		{
			return false;
		}
	}

	return true;
}


CheckRectangle은 큐브의 단일 반경 대신 직육면체의 x반경, y반경, z반경을 파라미터로 취하는 것을 제외하고는 CheckCube와 똑같이 동작합니다. 직육면체의 여덟 꼭지점을 계산하여 CheckCube 함수와 비슷하게 프러스텀 확인을 합니다.


bool FrustumClass::CheckRectangle(float xCenter, float yCenter, float zCenter, float xSize, float ySize, float zSize)
{
	int i;


	// Check if any of the 6 planes of the rectangle are inside the view frustum.
	for(i=0; i<6; i++)
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		return false;
	}

	return true;
}


Modellistclass.h


ModelListClass은 씬에서 모든 모델들에 관한 정보를 유지하기 위한 새 클래스입니다. 이번 강좌에는 한가지 타입의 모델만 있어서 구 모델들의 위치와 색상을 유지합니다. 이 클래스는 다른 유형의 모델들을 유지하고 모델들의 ModelClass를 색인하기 위해 확장될 수 있으나 지금은 강좌를 간단하게 유지합니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: modellistclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _MODELLISTCLASS_H_
#define _MODELLISTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>
#include <stdlib.h>
#include <time.h>


///////////////////////////////////////////////////////////////////////////////
// Class name: ModelListClass
///////////////////////////////////////////////////////////////////////////////
class ModelListClass
{
private:
	struct ModelInfoType
	{
		D3DXVECTOR4 color;
		float positionX, positionY, positionZ;
	};

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

	bool Initialize(int);
	void Shutdown();

	int GetModelCount();
	void GetData(int, float&, float&, float&, D3DXVECTOR4&);

private:
	int m_modelCount;
	ModelInfoType* m_ModelInfoList;
};

#endif


Modellistclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: modellistclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "modellistclass.h"


생성자는 모델 정보 리스트를 null로 초기화합니다.


ModelListClass::ModelListClass()
{
	m_ModelInfoList = 0;
}


ModelListClass::ModelListClass(const ModelListClass& other)
{
}


ModelListClass::~ModelListClass()
{
}


bool ModelListClass::Initialize(int numModels)
{
	int i;
	float red, green, blue;


먼저 사용될 모델들의 개수를 저장하고 ModelInfoType 구조체를 사용하여 모델들의 리스트 배열을 생성합니다.


	// Store the number of models.
	m_modelCount = numModels;

	// Create a list array of the model information.
	m_ModelInfoList = new ModelInfoType[m_modelCount];
	if(!m_ModelInfoList)
	{
		return false;
	}


난수 발생기의 Seed값을 현재 시간으로 하고 모델들의 색상과 위치를 무작위로 생성하고 리스트 배열에 저장합니다.


	// Seed the random generator with the current time.
	srand((unsigned int)time(NULL));

	// Go through all the models and randomly generate the model color and position.
	for(i=0; i<m_modelCount; i++)
	{
		// Generate a random color for the model.
		red = (float)rand() / RAND_MAX;
		green = (float)rand() / RAND_MAX;
		blue = (float)rand() / RAND_MAX;

		m_ModelInfoList[i].color = D3DXVECTOR4(red, green, blue, 1.0f);

		// Generate a random position in front of the viewer for the mode.
		m_ModelInfoList[i].positionX = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f;
		m_ModelInfoList[i].positionY = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f;
		m_ModelInfoList[i].positionZ = ((((float)rand()-(float)rand())/RAND_MAX) * 10.0f) + 5.0f;
	}

	return true;
}


Shutdown 함수는 모델 정보 리스트 배열을 해제합니다.


void ModelListClass::Shutdown()
{
	// Release the model information list.
	if(m_ModelInfoList)
	{
		delete [] m_ModelInfoList;
		m_ModelInfoList = 0;
	}

	return;
}


GetModelCount은 이 클래스가 정보를 가지는 모델들의 개수를 반환합니다.


int ModelListClass::GetModelCount()
{
	return m_modelCount;
}


GetData 함수는 주어진 인덱스 위치의 구의 위치와 색상을 추출합니다.


void ModelListClass::GetData(int index, float& positionX, float& positionY, float& positionZ, D3DXVECTOR4& color)
{
	positionX = m_ModelInfoList[index].positionX;
	positionY = m_ModelInfoList[index].positionY;
	positionZ = m_ModelInfoList[index].positionZ;

	color = m_ModelInfoList[index].color;

	return;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


이번 강좌의 GraphicsClass는 이전 강좌들에서 사용했던 많은 클래스들을 인클루드합니다. 새로운 frustumclass.h와 modellistclass.h도 인쿨루드합니다.


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"
#include "modellistclass.h"
#include "frustumclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame(float);
	bool Render();

private:


새로운 두 private 클래스 객체는 m_Frustum와 m_ModelList입니다.


	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
	ModelClass* m_Model;
	LightShaderClass* m_LightShader;
	LightClass* m_Light;
	ModelListClass* m_ModelList;
	FrustumClass* m_Frustum;
};

#endif


Graphicsclass.cpp


이전 강좌들 이후 달라진 함수들만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


생성자는 private 멤버 변수들을 null로 초기화합니다.


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Text = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 0;
	m_ModelList = 0;
	m_Frustum = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// 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;
	}

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

	// Create the text object.
	m_Text = new TextClass;
	if(!m_Text)
	{
		return false;
	}

	// Initialize the text object.
	result = m_Text->Initialize(m_D3D->GetDevice(), m_D3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
		return false;
	}

	// 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", "../Engine/data/sphere.txt");
	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->SetDirection(0.0f, 0.0f, 1.0f);


여기서 새로운 ModelListClass를 만들고 25개의 무작위로 위치하고 칠해진 구 모델들을 생성시킵니다.


	// Create the model list object.
	m_ModelList = new ModelListClass;
	if(!m_ModelList)
	{
		return false;
	}

	// Initialize the model list object.
	result = m_ModelList->Initialize(25);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model list object.", L"Error", MB_OK);
		return false;
	}


여기서 새로운 FrustumClass객체를 만듭니다. ConstructFrustum 함수를 이용하여 매 프레임마다 수행되게 때문에 초기화할 필요는 없습니다.


	// Create the frustum object.
	m_Frustum = new FrustumClass;
	if(!m_Frustum)
	{
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


Shutdown 함수의 여기서 새로운 FrustumClass와 ModelListClass 객체를 해제합니다.


	// Release the frustum object.
	if(m_Frustum)
	{
		delete m_Frustum;
		m_Frustum = 0;
	}

	// Release the model list object.
	if(m_ModelList)
	{
		m_ModelList->Shutdown();
		delete m_ModelList;
		m_ModelList = 0;
	}

	// 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 text object.
	if(m_Text)
	{
		m_Text->Shutdown();
		delete m_Text;
		m_Text = 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;
}


Frame 함수는 호출하는 SystemClass로 부터 카메라 회전값을 받습니다. 그리고는 카메라의 위치와 회전이 설정되며 Render 함수에서 뷰 행렬을 적절히 갱신할 수 있습니다.


bool GraphicsClass::Frame(float rotationY)
{
	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	// Set the rotation of the camera.
	m_Camera->SetRotation(0.0f, rotationY, 0.0f);

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
	int modelCount, renderCount, index;
	float positionX, positionY, positionZ, radius;
	D3DXVECTOR4 color;
	bool renderModel, 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, projection, and ortho matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);


Render 함수의 주요한 변화는 매 프레임마다 갱신된 뷰 행렬에 따라 뷰잉 프러스텀을 만드는 것입니다. 이 작업은 뷰 행렬이 바뀌거나 프러스텀 컬링이 바르지 못할때마다 발생해야 합니다.


	// Construct the frustum.
	m_Frustum->ConstructFrustum(SCREEN_DEPTH, projectionMatrix, viewMatrix);

	// Get the number of models that will be rendered.
	modelCount = m_ModelList->GetModelCount();

	// Initialize the count of models that have been rendered.
	renderCount = 0;


ModelListClass 객체의 모든 모델에 대한 루프를 돕니다.


	// Go through all the models and render them only if they can be seen by the camera view.
	for(index=0; index<modelCount; index++)
	{
		// Get the position and color of the sphere model at this index.
		m_ModelList->GetData(index, positionX, positionY, positionZ, color);

		// Set the radius of the sphere to 1.0 since this is already known.
		radius = 1.0f;


이곳은 새로운 FrustumClass 객체를 사용하는 곳입니다. 뷰잉 프러스텀에서 구를 볼 수 있는지 확인합니다. 볼 수 있다면 렌더하고 안보인다면 넘어가고 다음 거를 확인합니다. 프러스텀 컬링을 사용함으로 빠른속도를 얻을 곳입니다.


		// Check if the sphere model is in the view frustum.
		renderModel = m_Frustum->CheckSphere(positionX, positionY, positionZ, radius);

		// If it can be seen then render it, if not skip this model and check the next sphere.
		if(renderModel)
		{
			// Move the model to the location it should be rendered at.
			D3DXMatrixTranslation(&worldMatrix, positionX, positionY, positionZ); 

			// 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.
			m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
					      m_Model->GetTexture(), m_Light->GetDirection(), color);

			// Reset to the original world matrix.
			m_D3D->GetWorldMatrix(worldMatrix);

			// Since this model was rendered then increase the count for this frame.
			renderCount++;
		}
	}


실제로 렌더되는 구가 몇개인지 나타내도록 살짝 바뀐 TextClass를 사용합니다. 또 렌더되지 않은 대신에 FrustumClass 객체를 이용하여 제거된 구들의 숫자를 추정할 수 있습니다.


	// Set the number of models that was actually rendered this frame.
	result = m_Text->SetRenderCount(renderCount, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Turn off the Z buffer to begin all 2D rendering.
	m_D3D->TurnZBufferOff();

	// Turn on the alpha blending before rendering the text.
	m_D3D->TurnOnAlphaBlending();

	// Render the text string of the render count.
	m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix);
	if(!result)
	{
		return false;
	}

	// Turn off alpha blending after rendering the text.
	m_D3D->TurnOffAlphaBlending();

	// Turn the Z buffer back on now that all 2D rendering has completed.
	m_D3D->TurnZBufferOn();

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}


Positionclass.h


이번 강좌에서 왼쪽 오른쪽 방향키를 이용하여 카메라 움직임을 하기 위해 뷰어의 위치를 계산하고 유지하는 새로운 클래스를 만듭니다. 이 클래스는 지금은 왼쪽 오른쪽으로 도는것만 다룰 것이지만 다른 모든 움직임을 하도록 확장될 수 있습니다. 그 움직임은 또한 부드러운 카메라 효과를 만들기 위해 가속, 감속을 포함합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: positionclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _POSITIONCLASS_H_
#define _POSITIONCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: PositionClass
////////////////////////////////////////////////////////////////////////////////
class PositionClass
{
public:
	PositionClass();
	PositionClass(const PositionClass&);
	~PositionClass();

	void SetFrameTime(float);
	void GetRotation(float&);

	void TurnLeft(bool);
	void TurnRight(bool);

private:
	float m_frameTime;
	float m_rotationY;
	float m_leftTurnSpeed, m_rightTurnSpeed;
};

#endif


Positionclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: positionclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "positionclass.h"


생성자는 private 멤버 변수들을 0으로 초기화합니다.


PositionClass::PositionClass()
{
	m_frameTime = 0.0f;
	m_rotationY = 0.0f;
	m_leftTurnSpeed  = 0.0f;
	m_rightTurnSpeed = 0.0f;
}


PositionClass::PositionClass(const PositionClass& other)
{
}


PositionClass::~PositionClass()
{
}


SetFrameTime 함수는 이 클래스에서 프레임 속도를 설정하는데 사용됩니다. PositionClass은 프레임 시간 스피드를 카메라가 얼마나 빠르게 움직이고 회전하는지를 계산하기 위해 사용할 것입니다. 이 함수는 항상 이 클래스를 이용하여 카메라 위치를 움직이기 전에 각 프레임의 시작부분에서 호출되어야 합니다.


void PositionClass::SetFrameTime(float time)
{
	m_frameTime = time;
	return;
}


GetRotation는 카메라의 Y축 회전을 반환합니다. 이 함수는 카메라의 위치에 관한 더 많은 정보를 얻도록 확장될 수 있습니다.


void PositionClass::GetRotation(float& y)
{
	y = m_rotationY;
	return;
}


이동 함수는 둘다 동일하게 동작합니다. 각 keydown 파라미터 변수는 사용자가 왼쪽 혹은 오른쪽 키를 누르고 있는지를 나타냅니다. 사용자들이 키를 누르고 있다면 각 프레임마다 스피드가 최대치가 될때까지 가속합니다. 엄청 민감하고 부드러운 효과를 제공하는 탈 것(차량 같은)에서 가속하는 것처럼 카메라가 빨라집니다. 비슷하게 사용자가 키를 떼며 keydown변수가 false가 되면 각 프레임마다 스피드가 0이 될때까지 부드럽게 느려질 것입니다. 프레임 비율에 상관없이 스피드를 일정하게 하기 위해 스피드를 스페임 시간에 대해 계산합니다. 각 함수는 카메라의 새 위치를 계산하기 위해 약간의 기본적인 수학을 사용합니다.


void PositionClass::TurnLeft(bool keydown)
{
	// If the key is pressed increase the speed at which the camera turns left.  If not slow down the turn speed.
	if(keydown)
	{
		m_leftTurnSpeed += m_frameTime * 0.01f;

		if(m_leftTurnSpeed > (m_frameTime * 0.15f))
		{
			m_leftTurnSpeed = m_frameTime * 0.15f;
		}
	}
	else
	{
		m_leftTurnSpeed -= m_frameTime* 0.005f;

		if(m_leftTurnSpeed < 0.0f)
		{
			m_leftTurnSpeed = 0.0f;
		}
	}

	// Update the rotation using the turning speed.
	m_rotationY -= m_leftTurnSpeed;
	if(m_rotationY < 0.0f)
	{
		m_rotationY += 360.0f;
	}

	return;
}


void PositionClass::TurnRight(bool keydown)
{
	// If the key is pressed increase the speed at which the camera turns right.  If not slow down the turn speed.
	if(keydown)
	{
		m_rightTurnSpeed += m_frameTime * 0.01f;

		if(m_rightTurnSpeed > (m_frameTime * 0.15f))
		{
			m_rightTurnSpeed = m_frameTime * 0.15f;
		}
	}
	else
	{
		m_rightTurnSpeed -= m_frameTime* 0.005f;

		if(m_rightTurnSpeed < 0.0f)
		{
			m_rightTurnSpeed = 0.0f;
		}
	}

	// Update the rotation using the turning speed.
	m_rotationY += m_rightTurnSpeed;
	if(m_rotationY > 360.0f)
	{
		m_rotationY -= 360.0f;
	}

	return;
}


Systemclass.h


SystemClass는 새로운 PositionClass를 사용하기 위해 수정되었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN


//////////////
// INCLUDES //
//////////////
#include <windows.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"
#include "timerclass.h"
#include "positionclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();

	bool Initialize();
	void Shutdown();
	void Run();

	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
	bool Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();

private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;

	InputClass* m_Input;
	GraphicsClass* m_Graphics;
	TimerClass* m_Timer;
	PositionClass* m_Position;
};


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;


#endif


Systemclass.cpp


이전 강좌들에서 달라진 함수들만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;
	m_Timer = 0;


생성자에서 새로운 PositionClass객체가 null로 초기화됩니다.


	m_Position = 0;
}


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}
	
	// Create the timer object.
	m_Timer = new TimerClass;
	if(!m_Timer)
	{
		return false;
	}

	// Initialize the timer object.
	result = m_Timer->Initialize();
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
		return false;
	}


새로운 PositionClass 객체를 여기서 생성합니다. 초기화할 필요는 없습니다.


	// Create the position object.
	m_Position = new PositionClass;
	if(!m_Position)
	{
		return false;
	}

	return true;
}


void SystemClass::Shutdown()
{


Shutdown 함수의 이곳에서 PositionClass 객체가 해제됩니다.


	// Release the position object.
	if(m_Position)
	{
		delete m_Position;
		m_Position = 0;
	}

	// Release the timer object.
	if(m_Timer)
	{
		delete m_Timer;
		m_Timer = 0;
	}

	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


bool SystemClass::Frame()
{
	bool keyDown, result;
	float rotationY;


	// Update the system stats.
	m_Timer->Frame();

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}


각 프레임동안 PositionClass 객체가 프레임 시간을 통하여 갱신됩니다.


	// Set the frame time for calculating the updated position.
	m_Position->SetFrameTime(m_Timer->GetTime());


그 후 현재 키보드 상태에 따라 이동 함수가 갱신됩니다. 이동 함수는 카메라의 위치를 이번 프레임에 대한 새로운 위치로 갱신할 것입니다.


	// Check if the left or right arrow key has been pressed, if so rotate the camera accordingly.
	keyDown = m_Input->IsLeftArrowPressed();
	m_Position->TurnLeft(keyDown);

	keyDown = m_Input->IsRightArrowPressed();
	m_Position->TurnRight(keyDown);


새로운 카메라의 회전은 검색되어지고 Graphics::Frame 함수로 카메라 위치를 갱신시키기 위해 보내집니다.


	// Get the current view point rotation.
	m_Position->GetRotation(rotationY);

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(rotationY);
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}


요약


이제 여러분은 오브젝트 고르기를 어떻게 하는지 보셨습니다. 서로 다른 오브젝트들을 컬링하기 위한 유일한 트릭은 물체를 감싸는 큐브, 직육면체, 구를 결정하거나 점을 잘 사용하는 것입니다.




연습하기


1. 코드 컴파일 후 프로그램을 실행해 보세요. 왼쪽 오른쪽 방향키를 사용하여 카메라 움직임과 왼쪽 상단에 렌더 카운트가 바뀌는지 확인해 보세요.


2. 큐브 모델로 로드하고 CheckCube 함수로 바꿔보세요.


3. 다른 모델들을 생성하고 그 모델을에 대해 어떤 컬링 확인이 가장 좋은지 테스트해 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut15.html


Tutorial 15: FPS, CPU Usage, and Timers


이번 강좌는 fps, cpu 사용량, 매우 정확한 시간의 기능을 캡슐화하는 새 클래스 3개를 다루어 볼 것입니다. 이번 강좌의 코드는 폰트 엔진 강좌에 기초합니다.


첫번째 새 클래스는 FpsClass입니다. FpsClass는 어플리케이션이 돌아가는 초당 프레임을 기록하는 것을 처리할 것입니다. 매초에 프레임이 몇번 렌더링되는지 아는 것은 우리의 어플리케이션의 성능을 측정하는데 좋은 기준이 됩니다. 이는 받아들일 수 있는 그래픽 렌더링 속도를 결정하는데 사용되는 업계 표준 측정 기준중 하나입니다. 또 새로운 것을 시험해볼때 프레임 스피드에 얼마나 영향을 미칠지 보기위해서도 유용합니다. 새로 추가한 것이 프레임 스피드를 반이나 깍아먹으면 큰 문제가 있다는 것을 이 간단한 카운터를 사용함으로 즉각 알 수 있습니다. 현재 컴퓨터에 대한 표준(보통 기본적인) fps 스피드는 60 fps인 것을 명심하시기 바랍니다. 60 fps밑으로는 퍼포먼스가 좋지 못하다고 여기며 30 밑으로는 사람 눈으로도 알아차릴 수 있습니다. 코딩 시 일반적으로 fps를 최대로 하며 적절히 시행된 새로운 특성이 속도에 심각한 영향을 미치면 문제를 해명해야 하며 최소한 집중할 필요는 있습니다.


두번째 새 클래스는 CpuClass입니다. 이 클래스는 cpu 사용량 기록을 처리할 것이며 그래서 현재 cpu 사용률을 화면에 나타낼 수 있습니다. cpu 사용량을 아는 것은 fps가 사용되는 방법과 비슷하게 새 코드 변화를 디버깅하는데 유용합니다. 현재 시행된 코드나 알고리즘을 확인하는데 간단하고 즉각적인 측정 기준을 제공합니다.


마지막 새 클래스는 TimerClass입니다. 정확성이 아주 좋은 타이머이며 타이밍 이벤트나 우리의 어플리케이션과 다양한 구성요소들을 공통된 시간 프레임으로 동기화하는데 사용할 수 있습니다.


Framework


이번 강좌의 새 클래스 3개와 함께 프레임워크는 다음과 같습니다.



이 새 클래스 3개를 설명하는 것으로 강좌를 시작할 것입니다.



Fpsclass.h


FpsClass는 관련 타이머를 간단하게 재는 것입니다. 1초동안 프레임이 몇번 발생했는지를 세며 지속적으로 갱신합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: fpsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FPSCLASS_H_
#define _FPSCLASS_H_


/////////////
// LINKING //
/////////////
#pragma comment(lib, "winmm.lib")


//////////////
// INCLUDES //
//////////////
#include <windows.h>
#include <mmsystem.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: FpsClass
////////////////////////////////////////////////////////////////////////////////
class FpsClass
{
public:
	FpsClass();
	FpsClass(const FpsClass&);
	~FpsClass();

	void Initialize();
	void Frame();
	int GetFps();

private:
	int m_fps, m_count;
	unsigned long m_startTime;
};

#endif


Fpsclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: fpsclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "fpsclass.h"


FpsClass::FpsClass()
{
}


FpsClass::FpsClass(const FpsClass& other)
{
}


FpsClass::~FpsClass()
{
}


Initialize 함수는 모든 카운터를 0으로 설정하고 타이머를 시작합니다.


void FpsClass::Initialize()
{
	m_fps = 0;
	m_count = 0;
	m_startTime = timeGetTime();
	return;
}


Frame 함수는 반드시 매 프레임마다 호출되어야 하며 호출 시 프레임 카운트를 1 증가시킵니다. 1초가 흘렀다면 m_fps 변수에 프레임 카운트를 저장할 것입니다. 그리고는 카운트를 리셋하고 타이머를 재시작합니다.


void FpsClass::Frame()
{
	m_count++;

	if(timeGetTime() >= (m_startTime + 1000))
	{
		m_fps = m_count;
		m_count = 0;
		
		m_startTime = timeGetTime();
	}
}


GetFps은 지난 마지막 초에 대한 초당 프레임 속도를 반환합니다. 이 함수는 지속적으로 요청되어 화면에 fps가 표시됩니다.


int FpsClass::GetFps()
{
	return m_fps;
}


Cpuclass.h


CpuClass은 매 초에 발생중인 총 cpu 사용의 사용률을 결정하는데 사용됩니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: cpuclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _CPUCLASS_H_
#define _CPUCLASS_H_


cpu 사용량을 요청하기 위해 pdh 라이브러리를 사용합니다.


/////////////
// LINKING //
/////////////
#pragma comment(lib, "pdh.lib")


//////////////
// INCLUDES //
//////////////
#include <pdh.h>


///////////////////////////////////////////////////////////////////////////////
// Class name: CpuClass
///////////////////////////////////////////////////////////////////////////////
class CpuClass
{
public:
	CpuClass();
	CpuClass(const CpuClass&);
	~CpuClass();

	void Initialize();
	void Shutdown();
	void Frame();
	int GetCpuPercentage();

private:
	bool m_canReadCpu;
	HQUERY m_queryHandle;
	HCOUNTER m_counterHandle;
	unsigned long m_lastSampleTime;
	long m_cpuUsage;
};

#endif


Cpuclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: cpuclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "cpuclass.h"


CpuClass::CpuClass()
{
}


CpuClass::CpuClass(const CpuClass& other)
{
}


CpuClass::~CpuClass()
{
}


Initialize 함수는 cpu 사용량 요청 핸들을 설정할 것입니다. 그 쿼리는 각각의 cpu 사용량 대신 시스템의 모든 cpu 사용량을 결합하여 알려줄 것입니다. 만약 쿼리 핸들을 얻을 수 없거나 무슨 이유로서인지 cpu 사용량을 받지 못하면 m_canReadCpu 플래그를 false로 설정하고 cpu 사용량을 0퍼센트로 유지할 것입니다. 일부 cpu와 운영체제 특권 레벨은(ring0 ?) 이 과정을 실패할 수 있습니다. 또 1초에 한번 cpu 사용량을 얻도록 타이머를 시작합니다.


void CpuClass::Initialize()
{
	PDH_STATUS status;


	// Initialize the flag indicating whether this object can read the system cpu usage or not.
	m_canReadCpu = true;

	// Create a query object to poll cpu usage.
	status = PdhOpenQuery(NULL, 0, &m_queryHandle);
	if(status != ERROR_SUCCESS)
	{
		m_canReadCpu = false;
	}

	// Set query object to poll all cpus in the system.
	status = PdhAddCounter(m_queryHandle, TEXT("\\Processor(_Total)\\% processor time"), 0, &m_counterHandle);
	if(status != ERROR_SUCCESS)
	{
		m_canReadCpu = false;
	}

	m_lastSampleTime = GetTickCount(); 

	m_cpuUsage = 0;

	return;
}


Shutdown 함수는 cpu 사용량을 요청하기 위해 사용했던 핸들을 해제합니다.


void CpuClass::Shutdown()
{
	if(m_canReadCpu)
	{
		PdhCloseQuery(m_queryHandle);
	}

	return;
}


FpsClass와 같이 Frame 함수를 매 프레임마다 호출해야 합니다. 요청 횟수를 줄이기 위해 m_lastSampleTime 변수를 사용하여 1초에 한번 얻도록 합니다. 그래서 매초마다 cpu에 사용량을 묻고 m_cpuUsage 변수에 저장합니다. 이 이상은 불필요합니다.


void CpuClass::Frame()
{
	PDH_FMT_COUNTERVALUE value; 

	if(m_canReadCpu)
	{
		if((m_lastSampleTime + 1000) < GetTickCount())
		{
			m_lastSampleTime = GetTickCount(); 

			PdhCollectQueryData(m_queryHandle);
        
			PdhGetFormattedCounterValue(m_counterHandle, PDH_FMT_LONG, NULL, &value);

			m_cpuUsage = value.longValue;
		}
	}

	return;
}


GetCpuPercentage 함수는 현재 cpu 사용량을 호출하는 함수에게 반환합니다. 다시 한번 어떤 이유든지 cpu를 읽을 수 없었을 경우 usage를 0으로 설정합니다.


int CpuClass::GetCpuPercentage()
{
	int usage;

	if(m_canReadCpu)
	{
		usage = (int)m_cpuUsage;
	}
	else
	{
		usage = 0;
	}

	return usage;
}


Timerclass.h


TimerClass은 프레임들의 사이 정확한 시간을 측정하는 정밀도가 높은 타이머입니다. 원래 사용법은 이동에 대해 표준 시간 프레임을 필요로하는 오브젝트들의 동기화를 위함입니다. 이번 강좌에서는 그렇게 사용하지는 않을거지만 코드는 있으니 어떻게 여러분의 프로젝트에 적용할 수 있을지는 볼 수 있을 겁니다. 대부분의 TimerClass의 공통 사용법은 프레임 시간을 사용하여 현재 프레임에 넘겨진 초의 '비율'을 알아내고 그 비율에 따라 오브젝트를 움직입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: timerclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TIMERCLASS_H_
#define _TIMERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <windows.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TimerClass
////////////////////////////////////////////////////////////////////////////////
class TimerClass
{
public:
	TimerClass();
	TimerClass(const TimerClass&);
	~TimerClass();

	bool Initialize();
	void Frame();

	float GetTime();

private:
	INT64 m_frequency;
	float m_ticksPerMs;
	INT64 m_startTime;
	float m_frameTime;
};

#endif


Timerclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: timerclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "timerclass.h"


TimerClass::TimerClass()
{
}


TimerClass::TimerClass(const TimerClass& other)
{
}


TimerClass::~TimerClass()
{
}


Initialize 함수는 먼저 시스템에게 고빈도 타이머를 지원하는지 알기 위해 요청을 합니다. 빈도를 반환하면 그 값을 사용하여 밀리초마다 째깍거림이 얼마나 발생할 지를 알아냅니다. 그러면 프레임 시간을 계산하기 위해 각 프레임 마다 이 값을 사용할 수 있습니다. Initialize 함수의 마지막에서 타이밍을 시작하기 위해 프레임의 시작 시간을 요청합니다.


bool TimerClass::Initialize()
{
	// Check to see if this system supports high performance timers.
	QueryPerformanceFrequency((LARGE_INTEGER*)&m_frequency);
	if(m_frequency == 0)
	{
		return false;
	}

	// Find out how many times the frequency counter ticks every millisecond.
	m_ticksPerMs = (float)(m_frequency / 1000);

	QueryPerformanceCounter((LARGE_INTEGER*)&m_startTime);

	return true;
}


Frame 함수는 메인 프로그램 루프마다 호출됩니다. 루프 사이 시간 차이를 계산할 수 있고 이 프레임을 실행하는 시간도 알 수 있습니다. 이 프레임에 대해 시간을 요청하고 계산하고 m_frameTime에 저장하여 동기화를 위해 호출하는 모든 오브젝트에 의해 사용될 수 있습니다. 그리하여 현재 시간을 다음 프레임의 시작으로 저장합니다.


void TimerClass::Frame()
{
	INT64 currentTime;
	float timeDifference;


	QueryPerformanceCounter((LARGE_INTEGER*)& currentTime);

	timeDifference = (float)(currentTime - m_startTime);

	m_frameTime = timeDifference / m_ticksPerMs;

	m_startTime = currentTime;

	return;
}


GetTime는 가장 최근에 계산된 프레임 시간을 반환합니다.


float TimerClass::GetTime()
{
	return m_frameTime;
}


Systemclass.h


이제 새 클래스 3개를 보았으니 프레임에 어떻게 맞출지를 설명할 수 있습니다. 이 클래스들은 SystemClass밑에 위치될 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN


//////////////
// INCLUDES //
//////////////
#include <windows.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"


새 클래스 3개를 여기서 인클루드합니다.


#include "fpsclass.h"
#include "cpuclass.h"
#include "timerclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();

	bool Initialize();
	void Shutdown();
	void Run();

	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
	bool Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();

private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;

	InputClass* m_Input;
	GraphicsClass* m_Graphics;


각 새 클래스에 대해 객체를 만듭니다.


	FpsClass* m_Fps;
	CpuClass* m_Cpu;
	TimerClass* m_Timer;
};


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;


#endif


Systemclass.cpp


폰트 강좌 이후 이 클래스에 변화된 부분만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;


생성자에서 새 객체 3개를 null로 초기화합니다.


	m_Fps = 0;
	m_Cpu = 0;
	m_Timer = 0;
}


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}


FpsClass를 생성하고 초기화합니다.


	// Create the fps object.
	m_Fps = new FpsClass;
	if(!m_Fps)
	{
		return false;
	}

	// Initialize the fps object.
	m_Fps->Initialize();


CpuClass를 생성하고 초기화합니다.


	// Create the cpu object.
	m_Cpu = new CpuClass;
	if(!m_Cpu)
	{
		return false;
	}

	// Initialize the cpu object.
	m_Cpu->Initialize();


TimerClass를 생성하고 초기화합니다.


	// Create the timer object.
	m_Timer = new TimerClass;
	if(!m_Timer)
	{
		return false;
	}

	// Initialize the timer object.
	result = m_Timer->Initialize();
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void SystemClass::Shutdown()
{


Shutdown 함수내 여기서 새 클래스 객체들을 해제합니다.


	// Release the timer object.
	if(m_Timer)
	{
		delete m_Timer;
		m_Timer = 0;
	}

	// Release the cpu object.
	if(m_Cpu)
	{
		m_Cpu->Shutdown();
		delete m_Cpu;
		m_Cpu = 0;
	}

	// Release the fps object.
	if(m_Fps)
	{
		delete m_Fps;
		m_Fps = 0;
	}

	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


마지막 변화는 Frame 함수입니다. 각 새 클래스들은 어플리케이션이 거치는 각 프레임에 대해 클래스마다 있는 Frame 함수를 호출해야 합니다. 각 Frame이 호출되고 나면 각각의 업데이트된 데이터를 요청할 수 있고 사용하도록 GraphicsClass에 보낼 수 있습니다.


bool SystemClass::Frame()
{
	bool result;


	// Update the system stats.
	m_Timer->Frame();
	m_Fps->Frame();
	m_Cpu->Frame();

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(m_Fps->GetFps(), m_Cpu->GetCpuPercentage(), m_Timer->GetTime());
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;


이번 강좌에는 vsync를 껐으며 그래서 어플리케이션이 최대빠르기로 실행될 것입니다.


const bool VSYNC_ENABLED = false;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame(int, int, float);
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
};

#endif


Graphicsclass.cpp


폰트 강좌이후 달라진 함수만 살펴볼 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


Frame 함수는 이제 fps, cpu, 타이머 카운트들을 취합니다. fps와 cpu 카운트는 TextClass에 설정되어 화면에 렌더링 됩니다.


bool GraphicsClass::Frame(int fps, int cpu, float frameTime)
{
	bool result;


	// Set the frames per second.
	result = m_Text->SetFps(fps, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the cpu usage.
	result = m_Text->SetCpu(cpu, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	return true;
}


Textclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

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


fps 카운트와 cpu 사용량을 설정하는 것에 대한 새로운 함수 2개가 추가되었습니다.


	bool SetFps(int, ID3D11DeviceContext*);
	bool SetCpu(int, ID3D11DeviceContext*);

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;
	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif


Textclass.cpp


폰트 강좌 이후 달라진 함수만 다룰 것입니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"


SetFps 함수는 fps 정수 값을 취하여 문자열로 변환합니다. fps 카운트가 문자열이 되면 또 다른 fps 스피드가는 것을 나타내는 접두사를 가진 관련 문자열을 얻습니다. 그 후에 렌더링을 위해 문장 구조체에 저장합니다. SetFps 함수는 60 fps 이상이면 fps 문자열을 초록색으로 60 밑으로는 노란색, 30밑으로는 빨간색으로 설정합니다.


bool TextClass::SetFps(int fps, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char fpsString[16];
	float red, green, blue;
	bool result;


	// Truncate the fps to below 10,000.
	if(fps > 9999)
	{
		fps = 9999;
	}

	// Convert the fps integer to string format.
	_itoa_s(fps, tempString, 10);

	// Setup the fps string.
	strcpy_s(fpsString, "Fps: ");
	strcat_s(fpsString, tempString);

	// If fps is 60 or above set the fps color to green.
	if(fps >= 60)
	{
		red = 0.0f;
		green = 1.0f;
		blue = 0.0f;
	}

	// If fps is below 60 set the fps color to yellow.
	if(fps < 60)
	{
		red = 1.0f;
		green = 1.0f;
		blue = 0.0f;
	}

	// If fps is below 30 set the fps color to red.
	if(fps < 30)
	{
		red = 1.0f;
		green = 0.0f;
		blue = 0.0f;
	}

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, fpsString, 20, 20, red, green, blue, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}


SetCpu 함수는 SetFps 함수와 비슷합니다. cpu값을 위하여 문장 구조체에 저장될 문자열로 변환하고 렌더링됩니다.


bool TextClass::SetCpu(int cpu, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char cpuString[16];
	bool result;


	// Convert the cpu integer to string format.
	_itoa_s(cpu, tempString, 10);

	// Setup the cpu string.
	strcpy_s(cpuString, "Cpu: ");
	strcat_s(cpuString, tempString);
	strcat_s(cpuString, "%");

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, cpuString, 20, 40, 0.0f, 1.0f, 0.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}


요약



우리의 씬을 렌더링하는 동안 FPS와 CPU 사용량을 볼 수 있습니다. 우리는 이제 정확한 타이머를 가지게 되어 어플리케이션이 실행되는 속도와 상관없이 오브젝트들의 이동과 회전이 일정하게 하기 위해 타이머를 사용할 수 있습니다.



연습하기


1. 코드를 컴파일하고 fps와 cpu 사용량이 보이는지 확인해 보세요. esc키로 종료하세요.


2. graphicsclass.h 파일의 vsync를 켜서 여러분의 비디오 카드/모니터가 어플리케이션을 잠그는 재생률이 몇인지를 확인해 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut14.html


Tutorial 14: Direct Sound


이번 강좌는 DirectX 11에서 Direct 사운드 사용의 기초와 .wav 오디오 파일을 로드하는 방법에 대해 살펴볼 것입니다. 이번 강좌는 저번 DirectX 11 강좌에 기초합니다. 이번 강좌의 코드부분을 보기 앞서 DirectX 11에서 Direct 사운드에 관한 여러 기본들과 사운드 포맷에 대한 비트를 살펴보겠습니다.


여러분이 알아야 할 첫번쨰는 DirectX 11에서 Direct 사운드 API는 여전히 DirectX 8의 것과 같다는 것입니다. 유일한 큰 차이는 최신 Windows OS에서는 하드웨어 사운드 혼합이 일반적으로 사용할 수 없다는 것입니다. 그 이유는 보안과 이제 모든 하드웨어 호출은 보안 레이어를 거쳐가야하는 OS의 일관성 때문입니다. 예전 사운드 카드들은 매우 빠른 DMA(direct memory access)를 지원하였지만 새 Windows 보안 모델에서는 동작하지 않습니다. 그래서 이제 모든 사운드 혼합은 소프트웨어 레벨에서 수행되며 그러한 이유로 하드웨어 가속이 아닌 것은 이 API로 직접 이용할 수 있습니다.


Direct 사운드의 장점은 여러분이 원하는 어떤 오디오 포맷이든 간에 재생할 수 있다는 것입니다. 이번 강좌에서 .wav 오디오 포맷을 사용하지만 .wav 코드를 .mp3나 원하는 다른 것들으로 교체할 수 있습니다. 심지어 여러분이 만든 오디오 포맷도 사용할 수 있습니다. Direct 사운드는 사용하기 매우 쉬워서 원하는 플레이백 포맷(.wav)의 사운드 버퍼를 만들고 오디오 포맷을 버퍼의 포맷에 복사하면 재생할 준비가 됩니다. 보다시피 많은 어플리케이션이 Direct 사운드를 사용하는 이유는 이런 간단함 때문입니다.


기억하실 것은 Direct 사운드는 두 종류의 버퍼 primary와 secondary 버퍼를 사용합니다. primary 버퍼는 여러분의 기본 사운드 카드, USB 헤드셋 등등의 메인 사운드 메모리 버퍼입니다. Secondary 버퍼들은 메모리에서 여러분이 만드는 버퍼이며 여러분의 사운드가 로드되는 곳입니다. secondary 버퍼를 재생할때 Direct 사운드 API는 사운드를 primary 버퍼로 혼합을 처리하고 사운드를 재생합니다. 여러 secondary 버퍼들을 동시에 재생한다면 한꺼번에 혼합하여 primary 버퍼에서 재생합니다. 또한 모든 버퍼들은 순환적이어서 반복재생하도록 설정할 수 있습니다.


강좌를 시작하기 위해 먼저 갱신된 프레임워크를 볼 것입니다. 유일한 새로운 클래스는 모든 DirectSound와 .wav 기능을 가지는 SoundClass입니다. 이번 강좌를 간단히 하기 위해 다른 클래스들은 지웠습니다.



Soundclass.h


SoundClass는 DirectSound기능과 .wav 오디오를 로딩하고 재생하는 기능을 캡슐화 합니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: soundclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _SOUNDCLASS_H_
#define _SOUNDCLASS_H_


다음 라이브러리들과 헤더들은 컴파일하는데 필요합니다.


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")
 
 
//////////////
// INCLUDES //
//////////////
#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>
#include <stdio.h>
 
 
///////////////////////////////////////////////////////////////////////////////
// Class name: SoundClass
///////////////////////////////////////////////////////////////////////////////
class SoundClass
{
private:


여기에 사용된 WaveHeaderType 구조체는 .wav 파일 포맷에 대한 것입니다. .wav파일을 로딩할때 .wav 오디오 데이터로딩에 필요한 정보를 알기 위해 먼저 이 헤더를 읽습니다. 여러분이 다른 포맷을 사용중이라면 여러분의 오디오 포맷에 필요대로 이 헤더를 교체하셔도 됩니다.


	struct WaveHeaderType
	{
		char chunkId[4];
		unsigned long chunkSize;
		char format[4];
		char subChunkId[4];
		unsigned long subChunkSize;
		unsigned short audioFormat;
		unsigned short numChannels;
		unsigned long sampleRate;
		unsigned long bytesPerSecond;
		unsigned short blockAlign;
		unsigned short bitsPerSample;
		char dataChunkId[4];
		unsigned long dataSize;
	};
 
public:
	SoundClass();
	SoundClass(const SoundClass&);
	~SoundClass();


Initialize와 Shutdown 이번 강좌에 필요한 모든 것을 처리합니다. Initialize 함수는 DirectSound를 초기화하고 .wav 오디오 파일을 로드할 것이며 그리고는 한번 재생합니다. Shutdown은 .wav 파일을 해제하고 DirectSound를 정리할 것입니다.


	bool Initialize(HWND);
	void Shutdown();
 
private:
	bool InitializeDirectSound(HWND);
	void ShutdownDirectSound();
 
	bool LoadWaveFile(char*, IDirectSoundBuffer8**);
	void ShutdownWaveFile(IDirectSoundBuffer8**);
 
	bool PlayWaveFile();
 
private:
	IDirectSound8* m_DirectSound;
	IDirectSoundBuffer* m_primaryBuffer;


이번 강좌는 사운드를 1개만 로드하기 때문에 secondary 버퍼를 하나만 가집니다.


	IDirectSoundBuffer8* m_secondaryBuffer1;
};
 
#endif


Soundclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: soundclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "soundclass.h"


생성자에서 사운드 클래스 내부에서 사용되는 private 멤버 변수를 초기화합니다.


SoundClass::SoundClass()
{
	m_DirectSound = 0;
	m_primaryBuffer = 0;
	m_secondaryBuffer1 = 0;
}
 
 
SoundClass::SoundClass(const SoundClass& other)
{
}
 
 
SoundClass::~SoundClass()
{
}
 
 
bool SoundClass::Initialize(HWND hwnd)
{
	bool result;


먼저 DirectSound API와 primary 버퍼를 초기화합니다. 초기화하고 나면 .wav 오디오 파일을 로드하고 오디오 파일 정보로 secondary를 초기화하는 LoadWaveFile 함수를 호출합니다. 로딩이 끝나면 .wav파일을 한번 재생하는 PlayWaveFile을 호출합니다.


	// Initialize direct sound and the primary sound buffer.
	result = InitializeDirectSound(hwnd);
	if(!result)
	{
		return false;
	}
 
	// Load a wave audio file onto a secondary buffer.
	result = LoadWaveFile("../Engine/data/sound01.wav", &m_secondaryBuffer1);
	if(!result)
	{
		return false;
	}
 
	// Play the wave file now that it has been loaded.
	result = PlayWaveFile();
	if(!result)
	{
		return false;
	}
 
	return true;
}


Shutdown 함수는 먼저 .wav 파일 오디오 데이터를 가지고 있는 secondary 버퍼를 ShutdownWaveFile 함수를 사용하여 해제합니다. 그리고는 ShutdownDirectSound를 호출하여 primary와 DirectSound 인터페이스를 해제합니다.


void SoundClass::Shutdown()
{
	// Release the secondary buffer.
	ShutdownWaveFile(&m_secondaryBuffer1);

	// Shutdown the Direct Sound API.
	ShutdownDirectSound();
 
	return;
}


InitializeDirectSound는 DirectSound와 기본 primary 사운드 버퍼에 대한 인터페이스 포인터를 얻는 것을 처리합니다. 시스템에 모든 사운드 장치에 대해 요청하여 특정 장치에 대한 primary 사운드 버퍼의 포인터를 얻을 수 있지만 이번 강좌는 간단함을 유지하기 위해  기본 사운드 장치에 대한 primary 버퍼의 포인터만 구했습니다.


bool SoundClass::InitializeDirectSound(HWND hwnd)
{
	HRESULT result;
	DSBUFFERDESC bufferDesc;
	WAVEFORMATEX waveFormat;
 
 
	// Initialize the direct sound interface pointer for the default sound device.
	result = DirectSoundCreate8(NULL, &m_DirectSound, NULL);
	if(FAILED(result))
	{
		return false;
	}
 
	// Set the cooperative level to priority so the format of the primary sound buffer can be modified.
	result = m_DirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
	if(FAILED(result))
	{
		return false;
	}


primary 버퍼에 원하는 접근 방법의 description을 설정해야 합니다. dwFlags는 이 구조체의 중요한 부분입니다. 이번 강좌의 경우 primary 버퍼 description의 dwFlags을 볼륨 조절 기능으로 설정할 것입니다. 다른 기능들도 설정 가능하지만 지금은 간단함을 위해 그냥 두겠습니다.


	// Setup the primary buffer description.
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = 0;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = NULL;
	bufferDesc.guid3DAlgorithm = GUID_NULL;
 
	// Get control of the primary sound buffer on the default sound device.
	result = m_DirectSound->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL);
	if(FAILED(result))
	{
		return false;
	}


이제 기본 사운드 장치의 primary 버퍼의 제어권을 얻었으니 포맷을 원했던 오디오 파일 포맷으로 변경해봅시다. 저는 여기서 고음질의 사운드를 원하니 압축되지 않은 CD 오디오 음질로 설정할 것입니다.


	// Setup the format of the primary sound bufffer.
	// In this case it is a .WAV file recorded at 44,100 samples per second in 16-bit stereo (cd audio format).
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = 44100;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nChannels = 2;
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;
 
	// Set the primary buffer to be the wave format specified.
	result = m_primaryBuffer->SetFormat(&waveFormat);
	if(FAILED(result))
	{
		return false;
	}
 
	return true;
}


ShutdownDirectSound 함수는 primary 버퍼와 DirectSound 인터페이스 해제를 처리합니다.


void SoundClass::ShutdownDirectSound()
{
	// Release the primary sound buffer pointer.
	if(m_primaryBuffer)
	{
		m_primaryBuffer->Release();
		m_primaryBuffer = 0;
	}
 
	// Release the direct sound interface pointer.
	if(m_DirectSound)
	{
		m_DirectSound->Release();
		m_DirectSound = 0;
	}
 
	return;
}


LoadWaveFile 함수는 .wav 오디오 파일을 로딩하고 데이터를 새 secondary 버퍼에 복사하는 것을 처리하는 함수입니다. 여러분이 다른 포맷을 원하면 이 함수를 고치거나 비슷한 함수를 작성하시면 됩니다.


bool SoundClass::LoadWaveFile(char* filename, IDirectSoundBuffer8** secondaryBuffer)
{
	int error;
	FILE* filePtr;
	unsigned int count;
	WaveHeaderType waveFileHeader;
	WAVEFORMATEX waveFormat;
	DSBUFFERDESC bufferDesc;
	HRESULT result;
	IDirectSoundBuffer* tempBuffer;
	unsigned char* waveData;
	unsigned char *bufferPtr;
	unsigned long bufferSize;


먼저 .wav파일을 열고 파일의 헤더를 읽습니다. 헤더는 오디오 파일의 모든 정보를 포함할 것이며 그래서 오디오 데이터를 수용하기 위한 secondary 버퍼를 생성하는데 그 정보를 사용할 수 있습니다. 또 오디오 파일은 데이터가 어디서 시작하는지 얼마나 큰지를 알려 줍니다. 제가 오디오 파일이 변질되지는 않았는지 적절한 wave파일 포맷으로 RIFF, WAVE, fmt, data, WAVE_FORMAT_PCM 태그들을 포함하는지 확실히 하기위해 필요한 모든 태그들을 체크하는 것을 아시게 될 것입니다. 또  44.1KHz 스테레오 16비트 오디오 파일인지 확인하기 위해 다른 것들도 체크합니다. 만약 모노에 22.1KHz, 8비트 또는 다른 포맷이면 우라기 원하는 정확한 포맷만을 로딩중인지 확인작업을 실패할 것입니다.


	// Open the wave file in binary.
	error = fopen_s(&filePtr, filename, "rb");
	if(error != 0)
	{
		return false;
	}
 
	// Read in the wave file header.
	count = fread(&waveFileHeader, sizeof(waveFileHeader), 1, filePtr);
	if(count != 1)
	{
		return false;
	}
 
	// Check that the chunk ID is the RIFF format.
	if((waveFileHeader.chunkId[0] != 'R') || (waveFileHeader.chunkId[1] != 'I') || 
	   (waveFileHeader.chunkId[2] != 'F') || (waveFileHeader.chunkId[3] != 'F'))
	{
		return false;
	}
 
	// Check that the file format is the WAVE format.
	if((waveFileHeader.format[0] != 'W') || (waveFileHeader.format[1] != 'A') ||
	   (waveFileHeader.format[2] != 'V') || (waveFileHeader.format[3] != 'E'))
	{
		return false;
	}
 
	// Check that the sub chunk ID is the fmt format.
	if((waveFileHeader.subChunkId[0] != 'f') || (waveFileHeader.subChunkId[1] != 'm') ||
	   (waveFileHeader.subChunkId[2] != 't') || (waveFileHeader.subChunkId[3] != ' '))
	{
		return false;
	}
 
	// Check that the audio format is WAVE_FORMAT_PCM.
	if(waveFileHeader.audioFormat != WAVE_FORMAT_PCM)
	{
		return false;
	}
 
	// Check that the wave file was recorded in stereo format.
	if(waveFileHeader.numChannels != 2)
	{
		return false;
	}
 
	// Check that the wave file was recorded at a sample rate of 44.1 KHz.
	if(waveFileHeader.sampleRate != 44100)
	{
		return false;
	}
 
	// Ensure that the wave file was recorded in 16 bit format.
	if(waveFileHeader.bitsPerSample != 16)
	{
		return false;
	}
 
	// Check for the data chunk header.
	if((waveFileHeader.dataChunkId[0] != 'd') || (waveFileHeader.dataChunkId[1] != 'a') ||
	   (waveFileHeader.dataChunkId[2] != 't') || (waveFileHeader.dataChunkId[3] != 'a'))
	{
		return false;
	}


wave 헤더가 확인되었으니 오디오 데이터를 로드되는 secondary 버퍼를 설정할 수 있습니다. 먼저 primary 버퍼에 대해 했던 것처럼 wave 포맷과 secondary 버퍼의 description을 설정해야 합니다. 그래도 primary 버퍼가 아닌 secondary니까 dwFlags와 dwBufferBytes에서 차이가 조금 있습니다.


	// Set the wave format of secondary buffer that this wave file will be loaded onto.
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = 44100;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nChannels = 2;
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;
 
	// Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = waveFileHeader.dataSize;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = &waveFormat;
	bufferDesc.guid3DAlgorithm = GUID_NULL;


secondary 버퍼 생성 방법은 상당히 이상합니다. 첫번째로 secondary버퍼에 대해 설정한 사운드 버퍼 description으로 임시 IDirectSoundBuffer를 만듭니다. 성공하면 그 임시 버퍼를 사용하여 IID_IDirectSoundBuffer8 파라미터로 QueryInterface를 호출하여 IDirectSoundBuffer8 secondary 버퍼를 만들 수 있습니다. 성공하면 임시 버퍼를 해제하며 secondary 버퍼의 사용준비가 완료됩니다.


	// Create a temporary sound buffer with the specific buffer settings.
	result = m_DirectSound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL);
	if(FAILED(result))
	{
		return false;
	}
 
	// Test the buffer format against the direct sound 8 interface and create the secondary buffer.
	result = tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&*secondaryBuffer);
	if(FAILED(result))
	{
		return false;
	}
 
	// Release the temporary buffer.
	tempBuffer->Release();
	tempBuffer = 0;


secondary 버퍼가 준비되었으니 오디오 파일로부터 wave 데이터를 로드할 수 있습니다. 먼저 메모리에 로드하므로 필요하면 데이터를 체크하거나 수정할 수 있습니다. 데이터가 메모리로 읽고나면 secondary 버퍼를 잠그고 데이터를 memcpy를 이용하여 복사하고 잠금을 풉니다. 이 secondary 버퍼는 사용할 준비가 되었습니다. 주의하실게 secondary 버퍼를 잠글때 실제 포인터 두개와 위치 두개를 받는다는 것입니다. 그 이유는 버퍼가 순환적이기 때문인데 만약 버퍼 중간에서 쓰기 시작하면 버퍼 범위 밖을 쓰지 않기 때문에 그 점에서의 버퍼 사이즈가 필요할 것입니다. 이런 점은 오디오 재생같은 것에 유용합니다. 이번 강좌에서 버퍼를 오디오 파일과 같은 사이즈로 만들며 간단히 하기 위해 버퍼의 시작에서 부터 씁니다.


	// Move to the beginning of the wave data which starts at the end of the data chunk header.
	fseek(filePtr, sizeof(WaveHeaderType), SEEK_SET);
 
	// Create a temporary buffer to hold the wave file data.
	waveData = new unsigned char[waveFileHeader.dataSize];
	if(!waveData)
	{
		return false;
	}
 
	// Read in the wave file data into the newly created buffer.
	count = fread(waveData, 1, waveFileHeader.dataSize, filePtr);
	if(count != waveFileHeader.dataSize)
	{
		return false;
	}
 
	// Close the file once done reading.
	error = fclose(filePtr);
	if(error != 0)
	{
		return false;
	}
 
	// Lock the secondary buffer to write wave data into it.
	result = (*secondaryBuffer)->Lock(0, waveFileHeader.dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0);
	if(FAILED(result))
	{
		return false;
	}
 
	// Copy the wave data into the buffer.
	memcpy(bufferPtr, waveData, waveFileHeader.dataSize);
 
	// Unlock the secondary buffer after the data has been written to it.
	result = (*secondaryBuffer)->Unlock((void*)bufferPtr, bufferSize, NULL, 0);
	if(FAILED(result))
	{
		return false;
	}
	
	// Release the wave data since it was copied into the secondary buffer.
	delete [] waveData;
	waveData = 0;
 
	return true;
}


ShutdownWaveFile은 그냥 secondary 버퍼의 해제를 합니다.


void SoundClass::ShutdownWaveFile(IDirectSoundBuffer8** secondaryBuffer)
{
	// Release the secondary sound buffer.
	if(*secondaryBuffer)
	{
		(*secondaryBuffer)->Release();
		*secondaryBuffer = 0;
	}

	return;
}


PlayWaveFile 함수는 secondary 버퍼에 저장된 오디오 파일을 재생할 것입니다. Play 함수를 사용하는 순간 자동적으로 오디오를 primary 버퍼로 혼합하고 이미 되어있으면 재생을 시작할 것입니다. 또 재생 시작 위치를 secondary 사운드 버퍼의 시작으로 설정하며 그 외에는 마지막에 재생을 멈춘 곳에서부터 재생될 것입니다. 그리고 버퍼의 설정을 사운드를 조절할 수 있게 했기때문에 여기서 볼륨을 최대로 설정합니다.


bool SoundClass::PlayWaveFile()
{
	HRESULT result;
 
 
	// Set position at the beginning of the sound buffer.
	result = m_secondaryBuffer1->SetCurrentPosition(0);
	if(FAILED(result))
	{
		return false;
	}
 
	// Set volume of the buffer to 100%.
	result = m_secondaryBuffer1->SetVolume(DSBVOLUME_MAX);
	if(FAILED(result))
	{
		return false;
	}
 
	// Play the contents of the secondary sound buffer.
	result = m_secondaryBuffer1->Play(0, 0, 0);
	if(FAILED(result))
	{
		return false;
	}
 
	return true;
}


Systemclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
 
 
///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN
 
 
//////////////
// INCLUDES //
//////////////
#include <windows.h>
 
 
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"


여기 새 SoundClass 헤더를 인클루드 합니다.


#include "soundclass.h"
 
 
////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();
 
	bool Initialize();
	void Shutdown();
	void Run();
 
	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
 
private:
	void Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();
 
private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;
 
	InputClass* m_Input;
	GraphicsClass* m_Graphics;


SoundClass 객체를 위한 새 private 변수를 만듭니다.


	SoundClass* m_Sound;
};
 
 
/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 
 
/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;
 
 
#endif


Systemclass.cpp


이전 강좌 이후 달라진 함수만 살펴보겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"
 
 
SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;


생성자에서 새 SoundClass 객체를 null로 초기화합니다.


	m_Sound = 0;
}
 
 
bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}


여기는 SoundClass를 생성하고 사용할 수 있게 초기화하는 곳입니다. 이번 강좌에서는 초기화에서 wave 파일 재생을 할 것입니다.


	// Create the sound object.
	m_Sound = new SoundClass;
	if(!m_Sound)
	{
		return false;
	}
 
	// Initialize the sound object.
	result = m_Sound->Initialize(m_hwnd);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize Direct Sound.", L"Error", MB_OK);
		return false;
	}
 
	return true;
}
 
 
void SystemClass::Shutdown()
{


SystemClass::Shutdown에서 SoundClass 객체를 정리하고 해제합니다.


	// Release the sound object.
	if(m_Sound)
	{
		m_Sound->Shutdown();
		delete m_Sound;
		m_Sound = 0;
	}
 
	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


요약


이제 엔진은 Direct 사운드의 기초를 지원합니다. 프로그램을 시작하면 그저 단일 wave 파일을 재생합니다.



연습하기


1. 코드를 컴파일 해보고 스테레오 사운드로 wave 파일을 재생하는지 확인해 보세요. esc키로 윈도우를 종료하세요.


2. sound01.wav파일을 여러분만의 44.1KHz 16bit 2channel 오디오 wave 파일로 교체하고 프로그램을 다시 실행해 보세요.


3. wave파일 두개를 로드하도록 프로그램을 자시 작성하고 동시에 재생해보세요.


4. 한번만 재생하지말고 계속 반복하도록 바꿔보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut13.html


Tutorial 13: Direct Input


이번 강좌는 DirectX 11에서 Direct 입력 사용하는 법을 설명합니다. 이번 강좌의 코드는 저번 폰트 강좌 코드에 기초합니다.


Direct 입력은 DirectX API가 제공하는 입력 장치와의 초 엄청 빠른 인터페이싱 방법입니다. DirectX 11에서 Direct 입력 API 부분은 이전 버전 그대로여서 여전히 8버전입니다. 그러나 Direct 입력은 애초에 많이 시행되어서 (direct 사운드와 비슷하게) 업데이트될 필요가 없었습니다. Direct 입력은 일반적인 윈도우 입력 시스템을 뛰어넘는 믿을 수 없는 스피드를 제공합니다. 입력 장치에 빠르게 반응하는 것을 요구하는 왠만한 고성능 어플리케이션들은 Direct 입력을 사용할 것입니다.


이번 강좌는 키보드와 마우스 장치에 Direct 입력을 어떻게 사용하는지에 초점을 맞출 것입니다. 또 마우스 포인터의 현재 위치를 나타내기 위해 TextClass를 사용할 것입니다. 이전 강좌들이 이미 InputClass를 가지고 있기 때문에 이번에는 그냥 이전에 사용된 Windows 메소드 대신에 Direct 입력을 사용하도록 재작성할 것입니다.



Inputclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_


헤더에 여러분이 사용하는 Direct 입력 버전을 정의해야 합니다. 안그럼 컴파일러가 8버전을 기본으로 하겠다는 짜증스러운 메시지를 발생할 것입니다.


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define DIRECTINPUT_VERSION 0x0800


다음 두 라입러리는 Direct 입력이 동작하도록 링크해야 합니다.


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")


이게 Direct 입력을 위해 요구되는 헤더입니다.


//////////////
// INCLUDES //
//////////////
#include <dinput.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: InputClass
////////////////////////////////////////////////////////////////////////////////
class InputClass
{
public:
	InputClass();
	InputClass(const InputClass&);
	~InputClass();

	bool Initialize(HINSTANCE, HWND, int, int);
	void Shutdown();
	bool Frame();

	bool IsEscapePressed();
	void GetMouseLocation(int&, int&);

private:
	bool ReadKeyboard();
	bool ReadMouse();
	void ProcessInput();

private:


처음의 private 멤버 변수 3개는 Direct 입력과, 키보드 장치, 마우스 장치에 대한 인터페이스 입니다.


	IDirectInput8* m_directInput;
	IDirectInputDevice8* m_keyboard;
	IDirectInputDevice8* m_mouse;


고 다음 private 멤버 변수 2개는 키보드와 마우스 장치의 현재 상태를 기록하기 위해 사용됩니다.


	unsigned char m_keyboardState[256];
	DIMOUSESTATE m_mouseState;

	int m_screenWidth, m_screenHeight;
	int m_mouseX, m_mouseY;
};

#endif


Inputclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "inputclass.h"


클래스 생성자는 Direct 입력 인터페이스 변수를 null로 초기화합니다.


InputClass::InputClass()
{
	m_directInput = 0;
	m_keyboard = 0;
	m_mouse = 0;
}


InputClass::InputClass(const InputClass& other)
{
}


InputClass::~InputClass()
{
}


bool InputClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight)
{
	HRESULT result;


	// Store the screen size which will be used for positioning the mouse cursor.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Initialize the location of the mouse on the screen.
	m_mouseX = 0;
	m_mouseY = 0;


이 함수 호출은 Direct 입력의 인터페이스를 초기화할 것입니다. Direct 입력 객체를 얻기만 하면 다른 입력 장치들을 초기화할 수 있습니다.


	// Initialize the main direct input interface.
	result = DirectInput8Create(hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, NULL);
	if(FAILED(result))
	{
		return false;
	}


먼저 초기화할 입력 장치는 키보드가 되겠습니다.


	// Initialize the direct input interface for the keyboard.
	result = m_directInput->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format.  In this case since it is a keyboard we can use the predefined data format.
	result = m_keyboard->SetDataFormat(&c_dfDIKeyboard);
	if(FAILED(result))
	{
		return false;
	}


키보드의 협력 레벨을 설정하는 것은 장치가 뭘 하는지 장치를 어떻게 사용할 것인지의 이유로 중요합니다. 여기서는 다름 프로그램과 공유하지 않도록 설정할 것입니다. (DISCL_EXCLUSIVE) 이 방법은 여러분이 키를 누르면 오직 여러분의 어플리케이션만 입력을 볼 수 있고 다른 어플리케이션은 그 입력에 접근할 수 없을 것입니다. 그러나 여러분의 프로그램이 실행중인 동안 키보드 입력에 다른 어플리케이션이 접근할 수 있기를 원한다면 non-exclusive로 설정할 수 있습니다. (DISCL_NONEXCLUSIVE) 다시 print screen key가 동작하고 다른 실행중인 어플리케이션이 키보드에 의해 조작될 수 있는 등등이 가능해집니다. 기억하실게 non-exclusive이고 창 모드로 실행 중이면 장치가 언제 focus(윈도우에서 창 활성화를 말한다.)를 잃었고 언제 다시 얻었는지를 확인해야 하며 그럼 프로그램이 다시 장치를 얻어서 사용할 수 있습니다. 이 focus를 잃는 것은 일반적으로 다른 어플리케이션 창이 여러분의 창위로 메인 focus가 되거나 여러분의 창이 '최소화'될때 발생합니다.


	// Set the cooperative level of the keyboard to not share with other programs.
	result = m_keyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}


키보드가 설정되면 마지막으로 사용할 수 있도록 키보드에 접근을 얻기 위해 Acquire를 호출합니다.


	// Now acquire the keyboard.
	result = m_keyboard->Acquire();
	if(FAILED(result))
	{
		return false;
	}


다음으로 설정할 입력 장치는 마우스입니다.


	// Initialize the direct input interface for the mouse.
	result = m_directInput->CreateDevice(GUID_SysMouse, &m_mouse, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format for the mouse using the pre-defined mouse data format.
	result = m_mouse->SetDataFormat(&c_dfDIMouse);
	if(FAILED(result))
	{
		return false;
	}


우리는 마우스에 대해 비배타적 협력 설정을 사용합니다. focus 안인지 밖인지 확인해야하며 매번 다시 얻어야 합니다.


	// Set the cooperative level of the mouse to share with other programs.
	result = m_mouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}


마우스가 설정되면 마우스 사용을 시작할 수 있도록 Acquire를 호출합니다.


	// Acquire the mouse.
	result = m_mouse->Acquire();
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


Shutdown 함수는 두 장치와 Direct 입력의 인터페이스를 해제합니다. 주의하실 것은 장치들은 항상 먼저 un-acuired되고 해제됩니다.


void InputClass::Shutdown()
{
	// Release the mouse.
	if(m_mouse)
	{
		m_mouse->Unacquire();
		m_mouse->Release();
		m_mouse = 0;
	}

	// Release the keyboard.
	if(m_keyboard)
	{
		m_keyboard->Unacquire();
		m_keyboard->Release();
		m_keyboard = 0;
	}

	// Release the main interface to direct input.
	if(m_directInput)
	{
		m_directInput->Release();
		m_directInput = 0;
	}

	return;
}


InputClass의 Frame 함수는 우리가 설정하는 상태버퍼로 장치의 상태를 읽어들입니다. 각 장치의 상태가 읽어진 후에 변화를 처리합니다.


bool InputClass::Frame()
{
	bool result;


	// Read the current state of the keyboard.
	result = ReadKeyboard();
	if(!result)
	{
		return false;
	}

	// Read the current state of the mouse.
	result = ReadMouse();
	if(!result)
	{
		return false;
	}

	// Process the changes in the mouse and keyboard.
	ProcessInput();

	return true;
}


ReadKeyboard은 키보드의 상태를 m_keyboardState 변수로 읽어들일 것입니다. 그 상태는 어떤 키가 현재 눌렸는지 안눌렸는지를 보여줄 것입니다. 만약 키보드 읽기를 실패한다면 다섯가지 이유중 하나일 것입니다. 여기서 제가 고칠 2개는 focus를 잃었거나 acquired가 되지 않은 것입니다. 이 경우라면 컨트롤을 다시 되찾아 올때까지 매 프레임마다 Acquire를 호출합니다. 창이 최소화되어 있다면 그 경우 Acquire는 실패할 것이지만 창이 다시 포그라운드로 돌아오면 Acquire은 성공할 것이고 키보드 상태를 읽을 수 있게 될 것입니다. 다른 3개의 에러 타입은 이번 강좌에서는 고치고 싶지 않습니다.


bool InputClass::ReadKeyboard()
{
	HRESULT result;


	// Read the keyboard device.
	result = m_keyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);
	if(FAILED(result))
	{
		// If the keyboard lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_keyboard->Acquire();
		}
		else
		{
			return false;
		}
	}
		
	return true;
}


ReadMouse은 ReadKeyboard가 키보드 상태를 읽은것 처럼 마우스의 상태를 읽을 것입니다. 그러나 마우스 상태는 단지 마지막 프레임에서 마우스 위치에서의 변화입니다. 그래서 예를 들어 변화는 "마우스가 오른쪽으로 5칸 이동했다" 같을 것인데 마우스의 실제 화면상 위치는 얻지 못할 것입니다. 이 델타 정보는 다른 목적들에 매우 유용하며 마우스의 화면상 위치는 우리가 직접 유지할 수 있습니다.


bool InputClass::ReadMouse()
{
	HRESULT result;


	// Read the mouse device.
	result = m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);
	if(FAILED(result))
	{
		// If the mouse lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_mouse->Acquire();
		}
		else
		{
			return false;
		}
	}

	return true;
}


ProcessInput 함수는 마지막 프레임 이후 입력 장치에서 일어난 변화를 처리하는 곳입니다. 이번 강좌에서는 Windows가 마우스 커서 위치를 유지하는 법과 비슷한 그저 간단한 마우스 위치 갱신을 할 것입니다. 이것을 위해 0으로 초기화 되어있는 m_mouseX와 m_mouseY 변수들을 사용하며 간단하게 마우스 위치의 변화를 이 두 변수에 더합니다. 이는 마우스의 위치를 사용자의 마우스 움직임에 따라 유지할 것입니다.


주의할 것은 마우스 위치(우리가 유지하는)가 화면에서 벗어나지 않도록 확인해야 합니다. 만약 사용자가 마우스를 계속해서 왼쪽으로 움직이면 다시 오른쪽으로 움직이기 시작할때까지 계속 커서를 0 위치에 유지해야 합니다.


void InputClass::ProcessInput()
{
	// Update the location of the mouse cursor based on the change of the mouse location during the frame.
	m_mouseX += m_mouseState.lX;
	m_mouseY += m_mouseState.lY;

	// Ensure the mouse location doesn't exceed the screen width or height.
	if(m_mouseX < 0)  { m_mouseX = 0; }
	if(m_mouseY < 0)  { m_mouseY = 0; }
	
	if(m_mouseX > m_screenWidth)  { m_mouseX = m_screenWidth; }
	if(m_mouseY > m_screenHeight) { m_mouseY = m_screenHeight; }
	
	return;
}


InputClass에 IsEscapePressed라는 함수를 추가하였습니다. 이 함수는 현재 특정 키가 눌려져 있는지 확인하기 위해 키보드를 활용하는 방법을 보여줍니다. 여러분의 어플리케이션에 중요한 다른 키들에 대해 체크하는 다른 함수를 작성할 수 있습니다.


bool InputClass::IsEscapePressed()
{
	// Do a bitwise and on the keyboard state to check if the escape key is currently being pressed.
	if(m_keyboardState[DIK_ESCAPE] & 0x80)
	{
		return true;
	}

	return false;
}


GetMouseLocation은 마우스의 위치를 리턴하는 도우미 함수입니다. GraphicsClass는 이 정보를 얻어서 TextClass를 사용하여 마우스의 x, y 위치를 화면에 렌더링할 수 있습니다.


void InputClass::GetMouseLocation(int& mouseX, int& mouseY)
{
	mouseX = m_mouseX;
	mouseY = m_mouseY;
	return;
}


Systemclass.cpp


그저 Windows 입력 시스템의 제거와 DirectX 입력 시스템 추가로 변화된 함수들만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}


입력 객체의 초기화는 이제 윈도우, 인스턴스의 핸들들과 화면 크기 변수가 필요하기 때문에 다릅니다. 또 Direct 입력 시작에서 성공했는지 여부를 나타내기 위해 boolean형 값을 반환합니다.


	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}
	
	return true;
}


void SystemClass::Shutdown()
{
	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}


입력 개체 해제는 객체를 delete하기 전에 Shutdown 호출이 필요합니다.


	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


void SystemClass::Run()
{
	MSG msg;
	bool done, result;


	// Initialize the message structure.
	ZeroMemory(&msg, sizeof(MSG));
	
	// Loop until there is a quit message from the window or the user.
	done = false;
	while(!done)
	{
		// Handle the windows messages.
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// If windows signals to end the application then exit out.
		if(msg.message == WM_QUIT)
		{
			done = true;
		}
		else
		{
			// Otherwise do the frame processing.  If frame processing fails then exit.
			result = Frame();
			if(!result)
			{
				MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK);
				done = true;
			}
		}


Run함수에서 esc키 확인은 약간 다르게 InputClass의 도우미 함수의 반환값을 확인하는 것으로 수행됩니다.


		// Check if the user pressed escape and wants to quit.
		if(m_Input->IsEscapePressed() == true)
		{
			done = true;
		}
	}

	return;
}


bool SystemClass::Frame()
{
	bool result;
	int mouseX, mouseY;


Frame 함수 수행 중 키보드와 마우스의 상태를 갱신하기 위해 입력 객체가 가진 Frame 함수를 호출합니다. 이 호출이 실패할 수 있으니 반환값을 확인해야 합니다.


	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}


입력 장치 업데이트를 읽은 후 마우스의 위치로 GraphicsClass를 갱신하여 화면상 텍스트에 그 위치를 렌더링할 수 있습니다.


	// Get the location of the mouse from the input object,
	m_Input->GetMouseLocation(mouseX, mouseY);

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(mouseX, mouseY);
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}


MessageHandler에서 Windows 키보드 읽는 것은 지웠습니다. Direct 입력가 이제 우리를 위한 이 모든 것을 처리합니다.


LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	return DefWindowProc(hwnd, umsg, wparam, lparam);
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();


Frame 함수는 이제 매 프레임마다 마우스 위치 갱신을 위해 정수형 두개를 받습니다.


	bool Frame(int, int);
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
};

#endif


Graphicsclass.cpp


이전 강좌에서 바뀐 함수만을 살펴볼 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


Frame 함수는 이제 마우스 x, y 위치를 받아서 TextClass 객체가 화면에 위치를 쓸 텍스트 문자열을 갱신하게 합니다.


bool GraphicsClass::Frame(int mouseX, int mouseY)
{
	bool result;


	// Set the location of the mouse.
	result = m_Text->SetMousePosition(mouseX, mouseY, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	return true;
}


Textclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

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


TextClass는 이제 마우스 위치 설정을 위한 새 함수를 가집니다.


	bool SetMousePosition(int, int, ID3D11DeviceContext*);

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;
	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif


Textclass.cpp


이전 버전 이후 달라진 함수들만을 다룰 것입니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"


이제 TextClass에서 새 함수를 가지는데 마우스 x, y위치를 두 문자열로 변환하고 마우스 위치가 화면에 렌더링 될 수 있도록 두 문장을 갱신합니다.


bool TextClass::SetMousePosition(int mouseX, int mouseY, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char mouseString[16];
	bool result;


	// Convert the mouseX integer to string format.
	_itoa_s(mouseX, tempString, 10);

	// Setup the mouseX string.
	strcpy_s(mouseString, "Mouse X: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, mouseString, 20, 20, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	// Convert the mouseY integer to string format.
	_itoa_s(mouseY, tempString, 10);

	// Setup the mouseY string.
	strcpy_s(mouseString, "Mouse Y: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, mouseString, 20, 40, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}


Summary


보시다시피 DirectX 11에서 Direct 입력을 설정하는 것은 매우 간단하며 입력 장치에서의 정보를 엄청 빠르게 접근할 수 있게 해줍니다.



연습하기


1. 컴파일하고 프로그램을 실행해 보세요. 화면 주위 마우스를 움직여 보고 위치 텍스트가 바뀌는지 확인하세요.


2. 2D 렌더링 강좌에서의 정보와 이번 강좌의 것과 조합하여 마우스 움직임에 따라 움직이는 여러분만의 마우스 커서를 만드세요.


3. 키보드 버퍼를 읽는 함수를 시행해 보고 여러분이 타이핑한 것을 화면에 나타내 보세요.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut12.html


Tutorial 12: Font Engine


화면에 텍스트를 쓰는 것은 어느 프로그램이나 상당히 중요한 기능입니다. DirectX 11에서 텍스트 렌더링은 먼저 2D 이미지를 어떻게 렌더링하는지를 아는 것부터 요구됩니다. 이 주제는 이미 이전 강좌에서 다뤘기 때문에 이번 강좌는 그걸 전제로 시작할 것입니다.


가장 필요 할 것은 전용 폰트 이미지입니다. 제가 원하는 문자들을 1024x16 DDS 텍스쳐에 넣은 정말 간단한 것 하나를 만들었습니다.



보시다시피 필요한 기본 문자들로 구성되며 텍스쳐 파일 하나에 다 들어가 있습니다. 이제 필요할때 화면에 각 문자를 그리기 위해 텍스쳐의 인덱스를 사용하는 간단한 폰트 엔진을 만들 수 있습니다. DirectX 11에서 하는 방법은 두 삼감형으로 구성된 사각형을 만들고 문자 하나를 텍스쳐로부터 사각형 위로 렌더링합니다. 그래서 만약 문장이 있으면 필요한 문자들을 알아내고 각각의 사각형을 만들고 문자들을 해당 사각형에 렌더링합니다. 그 후에 모든 사각형을 화면의 문장을 형성하는 위치에 렌더링합니다. 이는 이전 강좌에서 2D 이미지를 화면에 렌더링하기 위해 사용했던 방법과 같습니다.


이제 텍스쳐 인덱스를 위해 텍스쳐에서 각 문자의 크기와 위치를 알려주는 텍스트 파일이 필요합니다. 이 텍스트 파일은 폰트 엔진이 텍스쳐 밖에서 렌더링 할 수 있는 문자를 구성하는데 필요한 픽셀을 빠르게 구하게 해줍니다.


파일의 포맷 : [문자의 아스키 값] [해당 문자] [왼쪽 U 텍스쳐 좌표] [오른쪽 U 텍스쳐 좌표] [문자의 픽셀 너비]


32   0.0        0.0         0
33 ! 0.0        0.000976563 1
34 " 0.00195313 0.00488281  3
35 # 0.00585938 0.0136719   8
36 $ 0.0146484  0.0195313   5
37 % 0.0205078  0.0302734   10
38 & 0.03125    0.0390625   8
39 ' 0.0400391  0.0410156   1
40 ( 0.0419922  0.0449219   3
41 ) 0.0458984  0.0488281   3
42 * 0.0498047  0.0546875   5
43 + 0.0556641  0.0625      7
44 , 0.0634766  0.0644531   1
45 - 0.0654297  0.0683594   3
46 . 0.0693359  0.0703125   1
47 / 0.0712891  0.0751953   4
48 0 0.0761719  0.0820313   6
49 1 0.0830078  0.0859375   3
50 2 0.0869141  0.0927734   6
51 3 0.09375    0.0996094   6
52 4 0.100586   0.106445    6
53 5 0.107422   0.113281    6
54 6 0.114258   0.120117    6
55 7 0.121094   0.126953    6
56 8 0.12793    0.133789    6
57 9 0.134766   0.140625    6
58 : 0.141602   0.142578    1
59 ; 0.143555   0.144531    1
60 < 0.145508   0.151367    6
61 = 0.152344   0.15918     7
62 > 0.160156   0.166016    6
63 ? 0.166992   0.171875    5
64 @ 0.172852   0.18457     12
65 A 0.185547   0.194336    9
66 B 0.195313   0.202148    7
67 C 0.203125   0.209961    7
68 D 0.210938   0.217773    7
69 E 0.21875    0.225586    7
70 F 0.226563   0.232422    6
71 G 0.233398   0.241211    8
72 H 0.242188   0.249023    7
73 I 0.25       0.250977    1
74 J 0.251953   0.256836    5
75 K 0.257813   0.265625    8
76 L 0.266602   0.272461    6
77 M 0.273438   0.282227    9
78 N 0.283203   0.290039    7
79 O 0.291016   0.298828    8
80 P 0.299805   0.306641    7
81 Q 0.307617   0.31543     8
82 R 0.316406   0.323242    7
83 S 0.324219   0.331055    7
84 T 0.332031   0.338867    7
85 U 0.339844   0.34668     7
86 V 0.347656   0.356445    9
87 W 0.357422   0.370117    13
88 X 0.371094   0.37793     7
89 Y 0.378906   0.385742    7
90 Z 0.386719   0.393555    7
91 [ 0.394531   0.396484    2
92 \ 0.397461   0.401367    4
93 ] 0.402344   0.404297    2
94 ^ 0.405273   0.410156    5
95 _ 0.411133   0.417969    7
96 ` 0.418945   0.420898    2
97 a 0.421875   0.426758    5
98 b 0.427734   0.432617    5
99 c 0.433594   0.438477    5
100 d 0.439453  0.444336    5
101 e 0.445313  0.450195    5
102 f 0.451172  0.455078    4
103 g 0.456055  0.460938    5
104 h 0.461914  0.466797    5
105 i 0.467773  0.46875     1
106 j 0.469727  0.472656    3
107 k 0.473633  0.478516    5
108 l 0.479492  0.480469    1
109 m 0.481445  0.490234    9
110 n 0.491211  0.496094    5
111 o 0.49707   0.501953    5
112 p 0.50293   0.507813    5
113 q 0.508789  0.513672    5
114 r 0.514648  0.517578    3
115 s 0.518555  0.523438    5
116 t 0.524414  0.527344    3
117 u 0.52832   0.533203    5
118 v 0.53418   0.539063    5
119 w 0.540039  0.548828    9
120 x 0.549805  0.554688    5
121 y 0.555664  0.560547    5
122 z 0.561523  0.566406    5
123 { 0.567383  0.570313    3
124 | 0.571289  0.572266    1
125 } 0.573242  0.576172    3
126 ~ 0.577148  0.583984    7


인덱스 파일과 텍스쳐 파일로 폰트 엔진을 만드는데 필요한 것을 갖추었습니다. 여러분만의 인덱스 파일을 만들고 싶다면 각 문자는 빈칸으로만 분리되어 있는지 확실히 해야 하며 빈칸이 없는 곳에서 TU와 TV를 생성하는 비트맵 파서를 작성할 수 있어야 합니다.


여러 사용자들이 여러 해상도에서 여러분의 응용프로그램을 실행 시킨다는 것을 기억하세요. 한 크기의 폰트는 모든 해상도에서 깔끔하게 읽지 못할 것입니다. 이러한 문제를 해결하기 위해 3-4개의 다른 폰트 사이즈를 만들고 특정 해상도에 특정 크기를 사용하게 될 것입니다.



Framework


폰트 기능을 클래스 셋에 캡슐화 하기 원하므로 몇몇 새 클래스를 우리의 프레임워크에 추가할 것입니다. 갱신될 프레임워크는 다음과 같습니다.



이번 강좌에서 TextClass, FontClass, FontShaderClass 라는 새로운 클래스 3개가 추가되었습니다. FontShaderClass는 폰트 렌더링을 위한 쉐이더로 이전 강좌에서 비트맵 이미지를 렌더링하는데 TextureShaderClass가 사용된 방법과 유사합니다. FontClass는 폰트 데이터를 가지며 문자열을 렌더링하는데 필요한 정점 버퍼를 만듭니다. TextClass는 화면에 렌더링되어야 하는 각 텍스트 문자열에 대한 정점 버퍼와 인덱스 버퍼를 가지는데 문자열에 대한 정점 버퍼를 생성하기 위해 FontClass를 사용하고 그 버퍼들을 렌더링하기 위해 FontShaderClass를 사용합니다.



Fontclass.h


먼저 FontClass를 보도록 하겠습니다. 이 클래스는 폰트 대한 텍스쳐, 텍스트 파일의 폰트 데이터, 폰트 데이터의 정점 버퍼를 만드는 함수를 다룰 것입니다. 각 문장의 폰트 데이터를 가지는 정점 버퍼들은 이 클래스가 아닌 TextClass에 둘 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTCLASS_H_
#define _FONTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: FontClass
////////////////////////////////////////////////////////////////////////////////
class FontClass
{
private:


FontType 구조체는 폰트 인덱스 파일에서 읽은 인덱싱하는 데이터를 가지는데 사용됩니다. left와 right는 TU 텍스쳐 좌표입니다. size는 픽셀에서 문자의 너비입니다.


	struct FontType
	{
		float left, right;
		int size;
	};


VertexType 구조체는 텍스트 문자가 렌더링될 사각형을 생성하기 위해 사용되는 실제 정점 데이터를 위함입니다. 각 문자는 사각형을 만드는데 2개의 삼각형이 필요할 것입니다. 이 삼각형들은 위치와 텍스쳐 데이터만을 가질 것입니다.


	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

	bool Initialize(ID3D11Device*, char*, WCHAR*);
	void Shutdown();

	ID3D11ShaderResourceView* GetTexture();


BuildVertexArray는 이 함수에 파라미터로 넘겨진 char* 문장을 렌더링할 삼각형들의 정점 배열을 생성하고 반환하는 것을 처리할 것입니다. 이 함수는 렌더링할 모든 문장들의 정점 배열을 생성하도록 새 TextClass에 의해 호출됩니다.


	void BuildVertexArray(void*, char*, float, float);

private:
	bool LoadFontData(char*);
	void ReleaseFontData();
	bool LoadTexture(ID3D11Device*, WCHAR*);
	void ReleaseTexture();

private:
	FontType* m_Font;
	TextureClass* m_Texture;
};

#endif


Fontclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "fontclass.h"


클래스 생성자는 FontClass의 모든 private 멤버 변수를 null로 초기화합니다.


FontClass::FontClass()
{
	m_Font = 0;
	m_Texture = 0;
}


FontClass::FontClass(const FontClass& other)
{
}


FontClass::~FontClass()
{
}


Initialize는 폰트 텍스쳐와 폰트 데이터를 로드할 것입니다.


bool FontClass::Initialize(ID3D11Device* device, char* fontFilename, WCHAR* textureFilename)
{
	bool result;


	// Load in the text file containing the font data.
	result = LoadFontData(fontFilename);
	if(!result)
	{
		return false;
	}

	// Load the texture that has the font characters on it.
	result = LoadTexture(device, textureFilename);
	if(!result)
	{
		return false;
	}

	return true;
}


Shutdown은 폰트 텍스쳐와 폰트 데이터를 해제할 것입니다.


void FontClass::Shutdown()
{
	// Release the font texture.
	ReleaseTexture();

	// Release the font data.
	ReleaseFontData();

	return;
}


LoadFontData 함수는 텍스쳐의 인덱싱 정보를 가지는 fontdata.txt 파일을 로드하는 곳입니다.


bool FontClass::LoadFontData(char* filename)
{
	ifstream fin;
	int i;
	char temp;


먼저 FontType 구조체의 배열을 생성합니다. 배열의 사이즈는 95로 설정했는데 텍스쳐의 문자들의 개수이며 fontdata.txt 파일의 인덱스 개수이기 때문입니다.


	// Create the font spacing buffer.
	m_Font = new FontType[95];
	if(!m_Font)
	{
		return false;
	}


이제 파일을 열고 각 라인을 m_Font 배열로 읽어들입니다. 우리는 TU 왼쪽, 오른쪽 좌표랑 문자의 픽셀 사이즈만 읽습니다.


	// Read in the font size and spacing between chars.
	fin.open(filename);
	if(fin.fail())
	{
		return false;
	}

	// Read in the 95 used ascii characters for text.
	for(i=0; i<95; i++)
	{
		fin.get(temp);
		while(temp != ' ')
		{
			fin.get(temp);
		}
		fin.get(temp);
		while(temp != ' ')
		{
			fin.get(temp);
		}

		fin >> m_Font[i].left;
		fin >> m_Font[i].right;
		fin >> m_Font[i].size;
	}

	// Close the file.
	fin.close();

	return true;
}


ReleaseFontData 함수는 텍스쳐 인덱싱 데이터를 가지고 있는 배열을 해제합니다.


void FontClass::ReleaseFontData()
{
	// Release the font data array.
	if(m_Font)
	{
		delete [] m_Font;
		m_Font = 0;
	}

	return;
}


LoadTexture 함수는 font.dds 파일을 텍스쳐 쉐이더 리소스로 읽어들입니다. 이 파일은 우리가 문자를 얻고 렌더링시 사각형 폴리곤에 쓸 텍스쳐가 될 것입니다.


bool FontClass::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;
}


ReleaseTexture 함수는 font에 사용된 텍스쳐를 해제합니다.


void FontClass::ReleaseTexture()
{
	// Release the texture object.
	if(m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}


GetTexture가 폰트 텍스쳐 인터페이스를 반환하여 폰트 그래픽이 렌더링 될 수 있습니다.


ID3D11ShaderResourceView* FontClass::GetTexture()
{
	return m_Texture->GetTexture();
}


BuildVertexArray는 파라미터로 넘겨진 텍스트 문자으로부터 정점 버퍼를 생성하기 위하여 TextClass에 의해 호출될 것입니다. TextClass에서 그려질 각 문장은 생성된 이후 쉽게 렌더링 될 수 있는 전용 정점 버퍼를 가집니다. vertices는 정점 배열의 포인터로 생성되면 TextClass로 반환될 것입니다. sentence는 정점 배열을 만드는데 사용될 텍스트 문장입니다. drawX와 drawY는 문장을 그릴 곳의 화면 좌표입니다.


void FontClass::BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY)
{
	VertexType* vertexPtr;
	int numLetters, index, i, letter;


	// Coerce the input vertices into a VertexType structure.
	vertexPtr = (VertexType*)vertices;

	// Get the number of letters in the sentence.
	numLetters = (int)strlen(sentence);

	// Initialize the index to the vertex array.
	index = 0;


다음 루프는 정점, 인덱스 배열을 생성할 것입니다. 문장에서 각 문자를 취하고 문자의 두 삼각형을 생성합니다. 폰트 텍스쳐로부터 해당 문자를 픽셀 사이즈와 TU 텍스쳐 좌표가 있는 m_Font 배열을 이용하여 두 삼각형에 매핑합니다. 문자를 위한 폴리곤이 생성되고 나면 다음 문자를 그리기 위해 화면의 X 좌표를 갱신합니다.


	// Draw each letter onto a quad.
	for(i=0; i<numLetters; i++)
	{
		letter = ((int)sentence[i]) - 32;

		// If the letter is a space then just move over three pixels.
		if(letter == 0)
		{
			drawX = drawX + 3.0f;
		}
		else
		{
			// First triangle in quad.
			vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f);  // Top left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f);  // Bottom right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3(drawX, (drawY - 16), 0.0f);  // Bottom left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 1.0f);
			index++;

			// Second triangle in quad.
			vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f);  // Top left.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3(drawX + m_Font[letter].size, drawY, 0.0f);  // Top right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 0.0f);
			index++;

			vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f);  // Bottom right.
			vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
			index++;

			// Update the x location for drawing by the size of the letter and one pixel.
			drawX = drawX + m_Font[letter].size + 1.0f;
		}
	}

	return;
}


Font.vs


폰트 정점 쉐이더는 이전 강좌에서 2D 이미지를 렌더링하는데 사용된 텍스쳐 정점 쉐이더의 수정된 버전입니다. 유일한 차이는 정점 쉐이더 이름입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: font.vs
////////////////////////////////////////////////////////////////////////////////


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


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType FontVertexShader(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;
    
    return output;
}


Font.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: font.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


pixelColor라는 값을 가지는 새로운 상수 버퍼가 생겼습니다. 폰트 텍스트를 그리는데 쓰일 픽셀 색상을 조절하기 위해 사용합니다.


cbuffer PixelBuffer
{
    float4 pixelColor;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


FontPixelShader는 먼저 폰트 텍스쳐를 조사하여 해당 픽셀을 얻습니다. 검정색인 픽셀을 얻었다면 텍스트 픽셀이 아닌 그냥 배경 부분입니다. 이 경우 픽셀의 알파값을 0으로 설정하여 블렌딩을 계산할때 이 픽셀이 투명이라고 결정할 것입니다. 입력 픽셀 색상이 검정색이 아니면 텍스트 픽셀 입니다. 이 경우 원하는 색이 칠해진 픽셀을 얻기 위해 color에 pixelColor를 곱하고 화면에 그립니다.


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FontPixelShader(PixelInputType input) : SV_TARGET
{
    float4 color;
	
	
    // Sample the texture pixel at this location.
    color = shaderTexture.Sample(SampleType, input.tex);
	
    // If the color is black on the texture then treat this pixel as transparent.
    if(color.r == 0.0f)
    {
        color.a = 0.0f;
    }
	
    // If the color is other than black on the texture then this is a pixel in the font so draw it using the font pixel color.
    else
    {
        color.a = 1.0f;
        color = color * pixelColor;
    }

    return color;
}



Fontshaderclass.h


FontShaderClass은 이전 강좌의 TextureShaderClass에 폰트 렌더링을 위한 약간의 코드 수정과 이름만 바꾼 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTSHADERCLASS_H_
#define _FONTSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: FontShaderClass
////////////////////////////////////////////////////////////////////////////////
class FontShaderClass
{
private:
	struct ConstantBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};


픽셀 쉐이더의 PixelBuffer와 매치되는 새 구조체가 생겼습니다. 렌더링 될 텍스트의 픽셀 색상만을 가집니다.


	struct PixelBufferType
	{
		D3DXVECTOR4 pixelColor;
	};

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

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4);

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

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11Buffer* m_constantBuffer;
	ID3D11SamplerState* m_sampleState;


FontShaderClass은 텍스트 폰트 렌더링에 쓰이는 픽셀의 색상을 위한 상수 버퍼를 가집니다.


	ID3D11Buffer* m_pixelBuffer;
};

#endif


Fontshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "fontshaderclass.h"


FontShaderClass::FontShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_constantBuffer = 0;
	m_sampleState = 0;


생성자에서 픽셀 색상 상수 버퍼를 null로 초기화합니다.


	m_pixelBuffer = 0;
}


FontShaderClass::FontShaderClass(const FontShaderClass& other)
{
}


FontShaderClass::~FontShaderClass()
{
}


bool FontShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


Initialize은 새 폰트 정점 쉐이더와 픽셀 쉐이더의 HLSL 파일을 로드합니다.


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/font.vs", L"../Engine/font.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


Shutdown은 폰트 쉐이더와 관련된 포인터, 데이터를 해제하는 ShutdownShader를 호출합니다.


void FontShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


Render는 쉐이더 파라미터를 설정하며 폰트 쉐이더를 이용하여 버퍼를 그릴 것입니다. 이 과정은 새 pixelColor 파라미터를 제외하고 TextureShaderClass와 같습니다.


bool FontShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
			     D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, pixelColor);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


InitializeShader 함수는 새 HLSL 폰트 정점, 픽셀 쉐이더들과 쉐이더의 인터페이스인 포인터를 설정합니다.


bool FontShaderClass::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 constantBufferDesc;
	D3D11_SAMPLER_DESC samplerDesc;
	D3D11_BUFFER_DESC pixelBufferDesc;


	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;


정점 쉐이더의 이름은 FontVertexShader로 변경되었습니다.


	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FontVertexShader", "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;
	}


픽셀 쉐이더의 이름은 FontPixelShader로 변경 되었습니다.


// Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FontPixelShader", "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; // 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 constant buffer that is in the vertex shader. constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC; constantBufferDesc.ByteWidth = sizeof(ConstantBufferType); constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; constantBufferDesc.MiscFlags = 0; constantBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&constantBufferDesc, NULL, &m_constantBuffer); if(FAILED(result)) { return false; } // 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 pixel constant buffer that is in the pixel shader.
	pixelBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	pixelBufferDesc.ByteWidth = sizeof(PixelBufferType);
	pixelBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	pixelBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	pixelBufferDesc.MiscFlags = 0;
	pixelBufferDesc.StructureByteStride = 0;

	// Create the pixel constant buffer pointer so we can access the pixel shader constant buffer from within this class.
	result = device->CreateBuffer(&pixelBufferDesc, NULL, &m_pixelBuffer);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


ShutdownShader 함수는 모든 쉐이더 관련 데이터를 해제합니다.


void FontShaderClass::ShutdownShader()
{


새 픽셀 색상 상수 버퍼는 여기서 해제됩니다.


	// Release the pixel constant buffer.
	if(m_pixelBuffer)
	{
		m_pixelBuffer->Release();
		m_pixelBuffer = 0;
	}

	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the constant buffer.
	if(m_constantBuffer)
	{
		m_constantBuffer->Release();
		m_constantBuffer = 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는 쉐이더 컴파일 에러를 텍스트 파일에 컴파일 실패 이벤트를 확인할 수 있도록 기록합니다.


void FontShaderClass::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 FontShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
					  D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	ConstantBufferType* dataPtr;
	unsigned int bufferNumber;
	PixelBufferType* dataPtr2;


	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(m_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (ConstantBufferType*)mappedResource.pData;

	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the constant buffer.
	deviceContext->Unmap(m_constantBuffer, 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_constantBuffer);

	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);


여기는 렌더링 전 픽셀 색상을 설정하는 곳입니다. 픽셀 상수 버퍼를 잠그고 픽셀 색상을 버퍼 안데 설정하고 다시 버퍼를 풉니다. 픽셀 쉐이더에서 상수 버퍼의 위치(순번)를 설정하면 사용 준비가 되었습니다.


	// Lock the pixel constant buffer so it can be written to.
	result = deviceContext->Map(m_pixelBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the pixel constant buffer.
	dataPtr2 = (PixelBufferType*)mappedResource.pData;

	// Copy the pixel color into the pixel constant buffer.
	dataPtr2->pixelColor = pixelColor;

	// Unlock the pixel constant buffer.
	deviceContext->Unmap(m_pixelBuffer, 0);

	// Set the position of the pixel constant buffer in the pixel shader.
	bufferNumber = 0;

	// Now set the pixel constant buffer in the pixel shader with the updated value.
	deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_pixelBuffer);

	return true;
}


RenderShader는 폰트 쉐이더를 이용하여 준비된 폰트 정점/인덱스 버퍼를 그립니다.


void FontShaderClass::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 the triangles.
	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 triangles.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}


Textclass.h


TextClass는 프로그램이 필요할 모든 2D 텍스트를 처리합니다. 2D 텍스트를 화면에 렌더링 하며 이것을 돕기 위해 FontClass와 FontShaderClass를 사용합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:


SentenceType은 각 텍스트 문장의 렌더링 정보를 가지는 구조체 입니다.


	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};


VertexType은 FontClass의 것과 일치해야 합니다.


	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

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

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;


이번 강좌에서는 2개의 문장을 사용할 것입니다.


	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif


Textclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"


클래스 생성자는 private 멤버 변수를 null로 초기화합니다.


TextClass::TextClass()
{
	m_Font = 0;
	m_FontShader = 0;

	m_sentence1 = 0;
	m_sentence2 = 0;
}


TextClass::TextClass(const TextClass& other)
{
}


TextClass::~TextClass()
{
}


bool TextClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth, int screenHeight, 
						   D3DXMATRIX baseViewMatrix)
{
	bool result;


화면 크기와 기본 뷰 행렬을 저장합니다. 이 정보는 2D 텍스트 렌더링에 사용될 것입니다.


	// Store the screen width and height.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Store the base view matrix.
	m_baseViewMatrix = baseViewMatrix;


폰트 객체를 생성하고 초기화합니다.


	// Create the font object.
	m_Font = new FontClass;
	if(!m_Font)
	{
		return false;
	}

	// Initialize the font object.
	result = m_Font->Initialize(device, "../Engine/data/fontdata.txt", L"../Engine/data/font.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the font object.", L"Error", MB_OK);
		return false;
	}


폰트 쉐이더 객체를 생성하고 초기화합니다.


	// Create the font shader object.
	m_FontShader = new FontShaderClass;
	if(!m_FontShader)
	{
		return false;
	}

	// Initialize the font shader object.
	result = m_FontShader->Initialize(device, hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK);
		return false;
	}


이번 강좌에 사용될 두 문자열을 생성하고 초기화합니다. 문자열 하나는 100, 100에 흰색으로 "Hello"이며 다른 하나는 100, 200에 노란색으로 "Goodbye"입니다. UpdateSentence 함수는 언제든 문자열의 색상, 내용, 위치를 변경하기 위해 호출될 수 있습니다.


	// Initialize the first sentence.
	result = InitializeSentence(&m_sentence1, 16, device);
	if(!result)
	{
		return false;
	}

	// Now update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, "Hello", 100, 100, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	// Initialize the first sentence.
	result = InitializeSentence(&m_sentence2, 16, device);
	if(!result)
	{
		return false;
	}

	// Now update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, "Goodbye", 100, 200, 1.0f, 1.0f, 0.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}


Shutdown 함수는 두 문자열과 폰트 객체와 폰트 쉐이더 객체를 해제할 것입니다. 


void TextClass::Shutdown()
{
	// Release the first sentence.
	ReleaseSentence(&m_sentence1);

	// Release the second sentence.
	ReleaseSentence(&m_sentence2);

	// Release the font shader object.
	if(m_FontShader)
	{
		m_FontShader->Shutdown();
		delete m_FontShader;
		m_FontShader = 0;
	}

	// Release the font object.
	if(m_Font)
	{
		m_Font->Shutdown();
		delete m_Font;
		m_Font = 0;
	}

	return;
}


Render는 화면에 문장 두개를 그릴 것입니다.


bool TextClass::Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX orthoMatrix)
{
	bool result;


	// Draw the first sentence.
	result = RenderSentence(deviceContext, m_sentence1, worldMatrix, orthoMatrix);
	if(!result)
	{
		return false;
	}

	// Draw the second sentence.
	result = RenderSentence(deviceContext, m_sentence2, worldMatrix, orthoMatrix);
	if(!result)
	{
		return false;
	}

	return true;
}


InitializeSentence 함수는 문장을 저장하고 렌더링하는데 사용될 빈 정점 버퍼가 든 SentenceType을 생성합니다. maxLength 파라미터는 정점 버퍼의 최대 크기를 결정합니다.


bool TextClass::InitializeSentence(SentenceType** sentence, int maxLength, ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


	// Create a new sentence object.
	*sentence = new SentenceType;
	if(!*sentence)
	{
		return false;
	}

	// Initialize the sentence buffers to null.
	(*sentence)->vertexBuffer = 0;
	(*sentence)->indexBuffer = 0;

	// Set the maximum length of the sentence.
	(*sentence)->maxLength = maxLength;

	// Set the number of vertices in the vertex array.
	(*sentence)->vertexCount = 6 * maxLength;

	// Set the number of indexes in the index array.
	(*sentence)->indexCount = (*sentence)->vertexCount;

	// Create the vertex array.
	vertices = new VertexType[(*sentence)->vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[(*sentence)->indexCount];
	if(!indices)
	{
		return false;
	}

	// Initialize vertex array to zeros at first.
	memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));

	// Initialize the index array.
	for(i=0; i<(*sentence)->indexCount; i++)
	{
		indices[i] = i;
	}


문장을 위한 정점 버퍼 description의 생성중 Usage 형식을 동적으로 설정합니다. 언제든지 문장의 내용을 변경하고 싶을 수 있기때문입니다.


	// Set up the description of the dynamic vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * (*sentence)->vertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	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;

	// Create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->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) * (*sentence)->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, &(*sentence)->indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex array as it is no longer needed.
	delete [] vertices;
	vertices = 0;

	// Release the index array as it is no longer needed.
	delete [] indices;
	indices = 0;

	return true;
}


UpdateSentence 파라미터 sentence에 대한 정점 버퍼의 내용을 수정합니다. 정점 버퍼의 내용을 갱신하기 위해 Map과 Unmap 함수와 덧붙여 memcpy를 사용합니다.


bool TextClass::UpdateSentence(SentenceType* sentence, char* text, int positionX, int positionY, float red, float green, float blue,
			       ID3D11DeviceContext* deviceContext)
{
	int numLetters;
	VertexType* vertices;
	float drawX, drawY;
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	VertexType* verticesPtr;


문장의 크기와 색상을 설정합니다.


	// Store the color of the sentence.
	sentence->red = red;
	sentence->green = green;
	sentence->blue = blue;

	// Get the number of letters in the sentence.
	numLetters = (int)strlen(text);

	// Check for possible buffer overflow.
	if(numLetters > sentence->maxLength)
	{
		return false;
	}

	// Create the vertex array.
	vertices = new VertexType[sentence->vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Initialize vertex array to zeros at first.
	memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));


화면에 문장을 그리는 시작 위치를 계산합니다.


	// Calculate the X and Y pixel position on the screen to start drawing to.
	drawX = (float)(((m_screenWidth / 2) * -1) + positionX);
	drawY = (float)((m_screenHeight / 2) - positionY);


FontClass와 문장 정보를 이용하여 정점 배열을 생성합니다.


	// Use the font class to build the vertex array from the sentence text and sentence draw location.
	m_Font->BuildVertexArray((void*)vertices, text, drawX, drawY);


정점 배열 정보를 sentence 정점 버퍼에 복사합니다.


	// Lock the vertex buffer so it can be written to.
	result = deviceContext->Map(sentence->vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the vertex buffer.
	verticesPtr = (VertexType*)mappedResource.pData;

	// Copy the data into the vertex buffer.
	memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));

	// Unlock the vertex buffer.
	deviceContext->Unmap(sentence->vertexBuffer, 0);

	// Release the vertex array as it is no longer needed.
	delete [] vertices;
	vertices = 0;

	return true;
}


ReleaseSentence는 sentence 정점, 인덱스 버퍼뿐 아니라 sentence도 해제합니다.


void TextClass::ReleaseSentence(SentenceType** sentence)
{
	if(*sentence)
	{
		// Release the sentence vertex buffer.
		if((*sentence)->vertexBuffer)
		{
			(*sentence)->vertexBuffer->Release();
			(*sentence)->vertexBuffer = 0;
		}

		// Release the sentence index buffer.
		if((*sentence)->indexBuffer)
		{
			(*sentence)->indexBuffer->Release();
			(*sentence)->indexBuffer = 0;
		}

		// Release the sentence.
		delete *sentence;
		*sentence = 0;
	}

	return;
}


RenderSentence 함수는 sentence 정점, 인덱스 버퍼를 입력 어셈블러에 넣고 FontShaderClass 객체를 호출하여 넘겨받은 sentence를 그립니다. 현재 뷰 행렬 대신 m_baseViewMatrix를 사용하니 주의하세요. 이로인해 현재 뷰가 어느 곳인지 상관없이 각 프레임마다 화면의 같은 위치에 텍스트를 그릴 수 있게 됩니다. 마찬가지로 일반 투영 행렬 대신에 2D 좌표를 이용하여 그려야하기 때문에 orthoMatrix를 사용합니다.


bool TextClass::RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, D3DXMATRIX worldMatrix, 
			       D3DXMATRIX orthoMatrix)
{
	unsigned int stride, offset;
	D3DXVECTOR4 pixelColor;
	bool result;


	// 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, &sentence->vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(sentence->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);

	// Create a pixel color vector with the input sentence color.
	pixelColor = D3DXVECTOR4(sentence->red, sentence->green, sentence->blue, 1.0f);

	// Render the text using the font shader.
	result = m_FontShader->Render(deviceContext, sentence->indexCount, worldMatrix, m_baseViewMatrix, orthoMatrix, m_Font->GetTexture(), 
				      pixelColor);
	if(!result)
	{
		false;
	}

	return true;
}


D3dclass.h


이번 강좌에서 D3DClass도 블렌딩 상태를 포함하기 위해 수정하였습니다. 블렌딩은 폰트가 배경의 3D 객체와 혼합될게 해줍니다. 블렌딩을 켜지 않으면 텍스트뒤 검정 삼각형들을 볼 것입니다. 그러나 블렌딩을 켜면 텍스트에 대한 픽셀들만 화면에 보여지고 그외 삼각형의 나머지(텍스쳐에서 검정 부분)는 완전히 투명이 됩니다. 여기서 블렌딩에 대해 엄청 자세하게 파지는 않을 거지만 간단한 블렌딩은 이번 강좌에 대해 올바르게 동작하기 위해 필요됩니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")


//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
	D3DClass();
	D3DClass(const D3DClass&);
	~D3DClass();

	bool Initialize(int, int, bool, HWND, bool, float, float);
	void Shutdown();
	
	void BeginScene(float, float, float, float);
	void EndScene();

	ID3D11Device* GetDevice();
	ID3D11DeviceContext* GetDeviceContext();

	void GetProjectionMatrix(D3DXMATRIX&);
	void GetWorldMatrix(D3DXMATRIX&);
	void GetOrthoMatrix(D3DXMATRIX&);

	void TurnZBufferOn();
	void TurnZBufferOff();


알파 블렌딩을 키고 끄는 두 함수가 추가되었습니다.


	void TurnOnAlphaBlending();
	void TurnOffAlphaBlending();

private:
	bool m_vsync_enabled;
	
	IDXGISwapChain* m_swapChain;
	ID3D11Device* m_device;
	ID3D11DeviceContext* m_deviceContext;
	ID3D11RenderTargetView* m_renderTargetView;
	ID3D11Texture2D* m_depthStencilBuffer;
	ID3D11DepthStencilState* m_depthStencilState;
	ID3D11DepthStencilView* m_depthStencilView;
	ID3D11RasterizerState* m_rasterState;

	D3DXMATRIX m_projectionMatrix;
	D3DXMATRIX m_worldMatrix;
	D3DXMATRIX m_orthoMatrix;

	ID3D11DepthStencilState* m_depthDisabledStencilState;


두 블렌딩 상태도 생겼습니다. m_alphaEnableBlendingState은 알파 블렌딩을 켠 것, m_alphaDisableBlendingState은 끈 것에 대한 것입니다.


	ID3D11BlendState* m_alphaEnableBlendingState;
	ID3D11BlendState* m_alphaDisableBlendingState;
};

#endif



D3dclass.cpp


이전 강좌 이후 클래스에서 변화된 함수만 살펴보겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"


D3DClass::D3DClass()
{
	m_swapChain = 0;
	m_device = 0;
	m_deviceContext = 0;
	m_renderTargetView = 0;
	m_depthStencilBuffer = 0;
	m_depthStencilState = 0;
	m_depthStencilView = 0;
	m_rasterState = 0;
	m_depthDisabledStencilState = 0;


새로운 두 블렌딩 상태를 null로 설정합니다.


	m_alphaEnableBlendingState = 0;
	m_alphaDisableBlendingState = 0;
}


bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, 
			  float screenDepth, float screenNear)
{
	HRESULT result;
	IDXGIFactory* factory;
	IDXGIAdapter* adapter;
	IDXGIOutput* adapterOutput;
	unsigned int numModes, i, numerator, denominator;
	DXGI_MODE_DESC* displayModeList;
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	D3D_FEATURE_LEVEL featureLevel;
	ID3D11Texture2D* backBufferPtr;
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	D3D11_RASTERIZER_DESC rasterDesc;
	D3D11_VIEWPORT viewport;
	float fieldOfView, screenAspect;
	D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc;


두 블렌딩 상태를 설정하기 위한 새 description 변수를 만듭니다.


	D3D11_BLEND_DESC blendStateDescription;


	// Store the vsync setting.
	m_vsync_enabled = vsync;

	// Create a DirectX graphics interface factory.
	result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
	if(FAILED(result))
	{
		return false;
	}

	// Use the factory to create an adapter for the primary graphics interface (video card).
	result = factory->EnumAdapters(0, &adapter);
	if(FAILED(result))
	{
		return false;
	}

	// Enumerate the primary adapter output (monitor).
	result = adapter->EnumOutputs(0, &adapterOutput);
	if(FAILED(result))
	{
		return false;
	}

	// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Create a list to hold all the possible display modes for this monitor/video card combination.
	displayModeList = new DXGI_MODE_DESC[numModes];
	if(!displayModeList)
	{
		return false;
	}

	// Now fill the display mode list structures.
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
	if(FAILED(result))
	{
		return false;
	}

	// Now go through all the display modes and find the one that matches the screen width and height.
	// When a match is found store the numerator and denominator of the refresh rate for that monitor.
	for(i=0; i<numModes; i++)
	{
		if(displayModeList[i].Width == (unsigned int)screenWidth)
		{
			if(displayModeList[i].Height == (unsigned int)screenHeight)
			{
				numerator = displayModeList[i].RefreshRate.Numerator;
				denominator = displayModeList[i].RefreshRate.Denominator;
			}
		}
	}

	// Release the display mode list.
	delete [] displayModeList;
	displayModeList = 0;

	// Release the adapter output.
	adapterOutput->Release();
	adapterOutput = 0;

	// Release the adapter.
	adapter->Release();
	adapter = 0;

	// Release the factory.
	factory->Release();
	factory = 0;

	// Initialize the swap chain description.
	ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

	// Set to a single back buffer.
	swapChainDesc.BufferCount = 1;

	// Set the width and height of the back buffer.
	swapChainDesc.BufferDesc.Width = screenWidth;
	swapChainDesc.BufferDesc.Height = screenHeight;

	// Set regular 32-bit surface for the back buffer.
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

	// Set the refresh rate of the back buffer.
	if(m_vsync_enabled)
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
	}
	else
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	}

	// Set the usage of the back buffer.
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

	// Set the handle for the window to render to.
	swapChainDesc.OutputWindow = hwnd;

	// Turn multisampling off.
	swapChainDesc.SampleDesc.Count = 1;
	swapChainDesc.SampleDesc.Quality = 0;

	// Set to full screen or windowed mode.
	if(fullscreen)
	{
		swapChainDesc.Windowed = false;
	}
	else
	{
		swapChainDesc.Windowed = true;
	}

	// Set the scan line ordering and scaling to unspecified.
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// Discard the back buffer contents after presenting.
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

	// Don't set the advanced flags.
	swapChainDesc.Flags = 0;

	// Set the feature level to DirectX 11.
	featureLevel = D3D_FEATURE_LEVEL_11_0;

	// Create the swap chain, Direct3D device, and Direct3D device context.
	result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, 
					       D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
	if(FAILED(result))
	{
		return false;
	}

	// Get the pointer to the back buffer.
	result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
	if(FAILED(result))
	{
		return false;
	}

	// Create the render target view with the back buffer pointer.
	result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
	if(FAILED(result))
	{
		return false;
	}

	// Release pointer to the back buffer as we no longer need it.
	backBufferPtr->Release();
	backBufferPtr = 0;

	// Initialize the description of the depth buffer.
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

	// Set up the description of the depth buffer.
	depthBufferDesc.Width = screenWidth;
	depthBufferDesc.Height = screenHeight;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc.Count = 1;
	depthBufferDesc.SampleDesc.Quality = 0;
	depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;

	// Create the texture for the depth buffer using the filled out description.
	result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Initialize the description of the stencil state.
	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

	// Set up the description of the stencil state.
	depthStencilDesc.DepthEnable = true;
	depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;

	// Stencil operations if pixel is front-facing.
	depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Stencil operations if pixel is back-facing.
	depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Create the depth stencil state.
	result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
	if(FAILED(result))
	{
		return false;
	}

	// Set the depth stencil state.
	m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

	// Initialize the depth stencil view.
	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

	// Set up the depth stencil view description.
	depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;

	// Create the depth stencil view.
	result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
	if(FAILED(result))
	{
		return false;
	}

	// Bind the render target view and depth stencil buffer to the output render pipeline.
	m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

	// Setup the raster description which will determine how and what polygons will be drawn.
	rasterDesc.AntialiasedLineEnable = false;
	rasterDesc.CullMode = D3D11_CULL_BACK;
	rasterDesc.DepthBias = 0;
	rasterDesc.DepthBiasClamp = 0.0f;
	rasterDesc.DepthClipEnable = true;
	rasterDesc.FillMode = D3D11_FILL_SOLID;
	rasterDesc.FrontCounterClockwise = false;
	rasterDesc.MultisampleEnable = false;
	rasterDesc.ScissorEnable = false;
	rasterDesc.SlopeScaledDepthBias = 0.0f;

	// Create the rasterizer state from the description we just filled out.
	result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
	if(FAILED(result))
	{
		return false;
	}

	// Now set the rasterizer state.
	m_deviceContext->RSSetState(m_rasterState);
	
	// Setup the viewport for rendering.
	viewport.Width = (float)screenWidth;
	viewport.Height = (float)screenHeight;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;

	// Create the viewport.
	m_deviceContext->RSSetViewports(1, &viewport);

	// Setup the projection matrix.
	fieldOfView = (float)D3DX_PI / 4.0f;
	screenAspect = (float)screenWidth / (float)screenHeight;

	// Create the projection matrix for 3D rendering.
	D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);

	// Initialize the world matrix to the identity matrix.
	D3DXMatrixIdentity(&m_worldMatrix);

	// Create an orthographic projection matrix for 2D rendering.
	D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);

	// Clear the second depth stencil state before setting the parameters.
	ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc));

	// Now create a second depth stencil state which turns off the Z buffer for 2D rendering.  The only difference is 
	// that DepthEnable is set to false, all other parameters are the same as the other depth stencil state.
	depthDisabledStencilDesc.DepthEnable = false;
	depthDisabledStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthDisabledStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
	depthDisabledStencilDesc.StencilEnable = true;
	depthDisabledStencilDesc.StencilReadMask = 0xFF;
	depthDisabledStencilDesc.StencilWriteMask = 0xFF;
	depthDisabledStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthDisabledStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
	depthDisabledStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthDisabledStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Create the state using the device.
	result = m_device->CreateDepthStencilState(&depthDisabledStencilDesc, &m_depthDisabledStencilState);
	if(FAILED(result))
	{
		return false;
	}


먼저 블렌드 상태 description을 초기화합니다.


	// Clear the blend state description.
	ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));


알파 블렌딩 사용 상태 description을 만들기 위해 BlendEnable을 TRUE로 DestBlend은 D3D33_BLEND_INV_SRC_ALPHA로 바꿉니다. 다른 것들은 Windows DirectX 그래픽스 문서에서 보여지는 기본 값으로 설정합니다.


	// Create an alpha enabled blend state description.
	blendStateDescription.RenderTarget[0].BlendEnable = TRUE;
	blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
	blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	blendStateDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	blendStateDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	blendStateDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
	blendStateDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	blendStateDescription.RenderTarget[0].RenderTargetWriteMask = 0x0f;


우리가 설정한 이 description을 사용하여 알파 블렌딩 사용 상태를 만듭니다.


	// Create the blend state using the description.
	result = m_device->CreateBlendState(&blendStateDescription, &m_alphaEnableBlendingState);
	if(FAILED(result))
	{
		return false;
	}


이제 알파 블렌딩 비사용 상태를 만들기 위해 아까 만든 description의 BlendEnable을 FALSE로 바꿉니다. 나머지 설정은 있는 그대로 둡니다.


	// Modify the description to create an alpha disabled blend state description.
	blendStateDescription.RenderTarget[0].BlendEnable = FALSE;


그리고는 수정된 description을 사용하여 알파 블렌딩 비사용 상태를 만듭니다. 이제 두 블렌딩 상태를 가졌으니 알파 블렌딩 on/off를 전환할 수 있습니다.


	// Create the blend state using the description.
	result = m_device->CreateBlendState(&blendStateDescription, &m_alphaDisableBlendingState);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


void D3DClass::Shutdown()
{
	// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
	if(m_swapChain)
	{
		m_swapChain->SetFullscreenState(false, NULL);
	}


두 새로운 블렌딩 상태를 해제합니다.


	if(m_alphaEnableBlendingState)
	{
		m_alphaEnableBlendingState->Release();
		m_alphaEnableBlendingState = 0;
	}

	if(m_alphaDisableBlendingState)
	{
		m_alphaDisableBlendingState->Release();
		m_alphaDisableBlendingState = 0;
	}

	if(m_rasterState)
	{
		m_rasterState->Release();
		m_rasterState = 0;
	}

	if(m_depthStencilView)
	{
		m_depthStencilView->Release();
		m_depthStencilView = 0;
	}

	if(m_depthDisabledStencilState)
	{
		m_depthDisabledStencilState->Release();
		m_depthDisabledStencilState = 0;
	}

	if(m_depthStencilState)
	{
		m_depthStencilState->Release();
		m_depthStencilState = 0;
	}

	if(m_depthStencilBuffer)
	{
		m_depthStencilBuffer->Release();
		m_depthStencilBuffer = 0;
	}

	if(m_renderTargetView)
	{
		m_renderTargetView->Release();
		m_renderTargetView = 0;
	}

	if(m_deviceContext)
	{
		m_deviceContext->Release();
		m_deviceContext = 0;
	}

	if(m_device)
	{
		m_device->Release();
		m_device = 0;
	}

	if(m_swapChain)
	{
		m_swapChain->Release();
		m_swapChain = 0;
	}

	return;
}


첫번째 새 함수 TurnOnAlphaBlending은 m_alphaEnableBlendingState의 OMSetBlendState를 사용하여 알파 블렌딩을 켜도록 해줍니다.


void D3DClass::TurnOnAlphaBlending()
{
	float blendFactor[4];
	

	// Setup the blend factor.
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	
	// Turn on the alpha blending.
	m_deviceContext->OMSetBlendState(m_alphaEnableBlendingState, blendFactor, 0xffffffff);

	return;
}


두번째 새 함수인 TurnOffAlphaBlending은 m_alphaDisableBlendingState의 OMSetBlendState 함수를 이용하여 알파 블렌딩을 끄도록 해줍니다.


void D3DClass::TurnOffAlphaBlending()
{
	float blendFactor[4];
	

	// Setup the blend factor.
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	
	// Turn off the alpha blending.
	m_deviceContext->OMSetBlendState(m_alphaDisableBlendingState, blendFactor, 0xffffffff);

	return;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"


새로운 TextClass 헤더를 추가하였습니다.


#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	void Frame();
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;


TextClass 객체인 새로운 private 변수가 생겼습니다.


	TextClass* m_Text;
};

#endif


Graphicsclass.cpp


이전 강좌에서 변화된 함수만 살펴볼 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;


생성자에서 새 TextClass 객체를 null로 초기화합니다.


	m_Text = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// 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;
	}


camera 객체로부터 TextClass가 사용할 새 뷰 행렬을 만듭니다. 텍스트는 화면상에 항상 같은 위치에 그려지기 때문에 항상 이 뷰 행렬을 사용할 것입니다.


	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);


여기서 새 TextClass 객체를 생성하고 초기화합니다.


	// Create the text object.
	m_Text = new TextClass;
	if(!m_Text)
	{
		return false;
	}

	// Initialize the text object.
	result = m_Text->Initialize(m_D3D->GetDevice(), m_D3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


여기서 TextClass 객체를 해제합니다.


	// Release the text object.
	if(m_Text)
	{
		m_Text->Shutdown();
		delete m_Text;
		m_Text = 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::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
	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->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);

	// Turn off the Z buffer to begin all 2D rendering.
	m_D3D->TurnZBufferOff();


여기서 알파 블렌딩을 켭니다. 그래서 텍스트가 배경과 함께 혼합될 것입니다.


	// Turn on the alpha blending before rendering the text.
	m_D3D->TurnOnAlphaBlending();


여기서 모든 문장을 그리도록 텍스트 객체의 render를 호출합니다. 2D 이미지와 같이 그리기 전에 Z 버퍼를 끄고 모든 2D가 그려진 후에 다시 켭니다.


	// Render the text strings.
	result = m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix);
	if(!result)
	{
		return false;
	}


여기서 알파 블렌딩을 끄며 화면에 그려지는 어떤 것이든 뒤 오브젝트들과 알파 블렌드를 하지 않을 것입니다.


	// Turn off alpha blending after rendering the text.
	m_D3D->TurnOffAlphaBlending();

	// Turn the Z buffer back on now that all 2D rendering has completed.
	m_D3D->TurnZBufferOn();

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}



요약


이제 화면의 어느 위치든 색깔있는 텍스트를 렌더링 할 수 있습니다.



연습하기


1. 코드를 컴파일하고 흰색 "Hello"와 그 아래 노란색 "Goodbye"가 화면의 100X100에 나타나는지 확인하세요


2. 문장의 내용, 위치, 색상을 바꿔보세요.


3. 세번째 문장을 만들고 이 문장도 렌더링 해보세요.


4. GraphicsClass::Render 함수에서 블렌딩 호출을 주석처리하고 m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f);으로 설정해보세요. 그럼 왜 블렌딩이 필요한지 보게될 것입니다.


5. GraphicsClass::Render에서 blending 호출을 돌려 놓고 m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f);는 그대로 두어 차이를 확인합니다.

728x90
728x90

원문 : http://www.rastertek.com/dx11tut11.html


Tutorial 11: 2D Rendering

2D 이미지를 화면에 렌더링 할 수 있는 것은 배우 유용합니다. 예를들어 대부분의 UI, 스프라이트 시스템, 텍스트 엔진들은 2D 이미지로 구성됩니다. DirectX 11은 이러한 것들을 폴리곤에 맵핑하고 직교 투영 행렬을 이용하여 렌더링하는 것으로 2D 이미지를 렌더링할 수 있게 해줍니다.



2D Screen Coordinates


2D 이미지를 화면에 렌더링하기 위해 화면의 X와 Y좌표를 계산해야 합니다. DirectX에서 화면의 정중앙은 0,0 입니다. 거기서 화면의 왼쪽과 아래쪽은 음의 방향이 됩니다. 화면의 오른쪽과 위쪽은 양의 방향이 됩니다. 예로 1024x768 해상도의 화면이 있다면 화면의 테두리의 좌표는 다음과 같을 것입니다.



여러분의 모든 2D 렌더링은 화면 좌표 계산하는 것이 필요하고 또 2D 이미지의 올바은 위치 지정을 위해 사용자의 윈도우/화면 크기가 필요하다는 것을 기억하시기 바랍니다.



Disabling Z buffer in DirectX 11


2D로 그리기 위해 Z 버퍼를 사용하지 말아야 합니다. Z 버퍼를 끌때 어느 픽셀에 있든지 가장 위(사용자 방향)에 2D 데이터를 덮어 씌울 것입니다. 여러분이 예상한 렌더링 출력을 확실히 얻도록 화가 알고리즘(Z버퍼를 꺼서 깊이 테스트를 쓰지 않으므로) 사용과 뒤에서부터 앞으로 그리는 것을 확실하게 해야합니다. 2D 그래픽 그리기를 마치면 Z 버퍼를 다시 사용으로 하여 3D 객체들을 적절히 렌더링할 수 있게 합니다.


Z 버퍼를 켜고 끄고를 하기 위해 3D 깊이 스텐실과 DepthEnable 가 false로 된것을 제외하고 같은 2번째 깊이 스텐실 상태를 만들어야 합니다. 그러고는 Z 버퍼를 켜고 끄는 두 상태를 스위치하기 위해 OMSetDepthStencilState를 사용합니다.



Dynamic Vertex Buffers


소개할 또다른 새로운 개념은 동적 정점 버퍼입니다. 우리는 이전 강좌들에서 오랫동안 정적 정점 버퍼를 사용해 왔습니다. 정적 정점 버퍼에 대한 이슈는 버퍼 내부 데이터를 변경할 수 없다는 것입니다. 반면에 동적 정점 버퍼는 원한다면 각 프레임마다 정점 버퍼내 정보를 조정할 수 있습니다. 이 버퍼는 정적 정점 버퍼보다 느리지만 추가 기능이 있어 등가교환입니다. (강철의 연금술사? md5 로딩부분에 아주 쪼금 더 기술하였습니다.)


2D 렌더링에 동적 정점 버퍼를 사용하는 이유는 종종 2D 이미지를 화면에 다른 위치로 옮기고 싶기 때문입니다. 좋은 예로 마우스 포인터가 있는데 자주 움직여서 화면상 위치를 나타내는 정점 데이터가 자주 바뀌어야 합니다.


주의할 점이 2개가 있는데 동적 정점 버퍼는 정적 버퍼보다 상당히 느려서 완전히 호출되기 전에는 사용하면 안됩니다. 두번째로 매 프레임마다 정적 정점 버퍼를 절대로 지우고 다시만들면 안되는데 비디오 카드에 완전히 락이 걸릴 수 있으며 (Nvidia에서는 아니지만 ATI에서 보았습니다.) 정적 정점 버퍼와 비교할때 종합적으로 성능이 떨어집니다.



Orthographic Projection in DirectX 11


2D 에서 렌더링 하는데 요구되는 마지막 새 개념은 보통의 3D 투영 행렬(원근 투영) 대신 직교 투영 행렬을 사용하는 것입니다. 2D 화면 좌표로 렌더링 할 수 있게 해줍니다. 이 행렬은 Direct3D 초기화 코드에서 이미 만들었던 것을 기억해보세요


	// Create an orthographic projection matrix for 2D rendering.
	D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);


Framework


이번 강좌의 코드는 저번 강좌에 기반합니다. 이번 강좌의 주요한 차이는 ModelClass가 BitmapClass로 교체된것과 LightShaderClass대신 TextureShaderClass를 다시 사용하는 것입니다. 프레임워크는 다음과 같습니다.





Bitmapclass.h


BitmapClass는 화면에 렌더링될 각 2D 이미지를 나타내는데 사용될 것입니다. 여러분이 가진 모든 2D 이미지에 대해 각각 새로운 BitmapClass가 필요할 것입니다. 기억하실것은 이 클래스는 단지 ModelClass를 3D 객체 대신 2D 이미지를 다루도록 다시 쓴 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: bitmapclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _BITMAPCLASS_H_
#define _BITMAPCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: BitmapClass
////////////////////////////////////////////////////////////////////////////////
class BitmapClass
{
private:


각 비트맵 이미지는 여전히 3D 객체처럼 렌더링될 폴리곤 객체입니다. 2D 이미지에 대해 위치 벡터와 텍스쳐 좌표만 필요합니다.


	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

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

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

	int GetIndexCount();
	ID3D11ShaderResourceView* GetTexture();

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

	bool LoadTexture(ID3D11Device*, WCHAR*);
	void ReleaseTexture();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	TextureClass* m_Texture;


BitmapClass는 3D 모델은 하지 않을 화면 크기, 비트맵 사이즈, 렌더링된 마지막 위치 같은 몇몇 추가적인 정보를 유지해야 할 것입니다. 이 추가 정보를 가지는 private 변수를 추가하였습니다.


	int m_screenWidth, m_screenHeight;
	int m_bitmapWidth, m_bitmapHeight;
	int m_previousPosX, m_previousPosY;
};

#endif


Bitmapclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: bitmapclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "bitmapclass.h"


클래스 생성자는 클래스내 모든 private 포인터를 초기화합니다.


BitmapClass::BitmapClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_Texture = 0;
}


BitmapClass::BitmapClass(const BitmapClass& other)
{
}


BitmapClass::~BitmapClass()
{
}


bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, WCHAR* textureFilename, int bitmapWidth, int bitmapHeight)
{
	bool result;


Initialize 함수에서 화면 크기와 이미지 크기가 저장됩니다. 이 값들은 렌더링동안 정확한 정점 위치를 알기 위해 필요할 것입니다. 이미지의 픽셀은 정확히 사용된 텍스쳐와 같을 필요는 없으며 여러분이 원하는 사이즈의 이미지와 텍스쳐를 사용할 수 있습니다.


	// Store the screen size.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Store the size in pixels that this bitmap should be rendered at.
	m_bitmapWidth = bitmapWidth;
	m_bitmapHeight = bitmapHeight;


이전 렌더링 위치는 먼저 -1로 초기화됩니다. 마지막으로 이미지를 그린 곳을 가리킬 중요한 변수가 될 것입니다. 마지막 프레임 이후 이미지 위치가 바뀌지 않았다면 동적 정점 버퍼를 수정하지 않을 것이며 그로인해 몇몇 처리 사이클을 아낄 것입니다.


	// Initialize the previous rendering position to negative one.
	m_previousPosX = -1;
	m_previousPosY = -1;


다음으로 버퍼가 만들어지고 비트맵 이미지의 텍스쳐 또한 로드됩니다.


	// 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;
}


Shutdown 함수는 정점, 인덱스 버퍼와 또 비트맵 이미지에 사용된 텍스쳐까지 해제할 것입니다.


void BitmapClass::Shutdown()
{
	// Release the model texture.
	ReleaseTexture();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	return;
}


Render 함수는 2D 이미지의 버퍼를 비디오 카드에 넣습니다. 파라미터로 화면에 이미지를 렌더링할 위치를 받습니다. UpdateBuffers 함수는 위치 파라미터와 함께 호출됩니다. 만약 마지막 프레임 이후 위치가 바뀌었다면 동적 정점 버퍼에 정점의 위치를 새 위치로 갱신합니다. 그렇지 않으면 UpdateBuffers 함수를 넘깁니다. 그 후에 RenderBuffers 함수는 렌더링을 위해 최종 정점/인덱스를 준비할 것입니다.


bool BitmapClass::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
	bool result;


	// Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen.
	result = UpdateBuffers(deviceContext, positionX, positionY);
	if(!result)
	{
		return false;
	}

	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(deviceContext);

	return true;
}


GetIndexCount 함수는 2D 이미지의 인덱스 개수를 반환합니다. 거의 항상 6일 것입니다.


int BitmapClass::GetIndexCount()
{
	return m_indexCount;
}


GetTexture 함수는 2D 이미지의 텍스쳐 자원에 대한 포인터를 반환합니다. 쉐이더는 버퍼를 그릴때 이 함수를 호출하여 이미지에 접근할 것입니다.


ID3D11ShaderResourceView* BitmapClass::GetTexture()
{
	return m_Texture->GetTexture();
}


InitializeBuffers는 2D 이미지를 그리는데 사용될 정점, 인덱스 버퍼를 생성하는데 사용되는 함수입니다.


bool BitmapClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


정점을 두 삼각형으로 구성된 사각형을 만들기 때문에 6점이 필요하여 6개로 설정하였습니다. 인덱스도 똑같습니다.


	// Set the number of vertices in the vertex array.
	m_vertexCount = 6;

	// Set the number of indices in the index array.
	m_indexCount = m_vertexCount;

	// 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;
	}

	// Initialize vertex array to zeros at first.
	memset(vertices, 0, (sizeof(VertexType) * m_vertexCount));

	// Load the index array with data.
	for(i=0; i<m_indexCount; i++)
	{
		indices[i] = i;
	}


여기에 ModelClass와 비교하여 가장 큰 변화가 있습니다. 이제 동적 정점 버퍼를 만들 것이며 그래서 필요한 각 프레임마다 정점 버퍼 내부 데이터를 수정할 수 있습니다. 동적으로 만들기 위해 description 설정에서 Usage를 D3D11_USAGE_DYNAMIC으로 그리고 CPUAccessFlags를 D3D11_CPU_ACCESS_WRITE로 설정합니다.


	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	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;
}


ShutdownBuffers 는 정점, 인덱스 버퍼를 해제합니다.


void BitmapClass::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;
}


UpdateBuffers 함수는 2D 비트맵 이미지를 원하는 화면위에 재위치시키도록 동적 정점 버퍼의 내용을 갱신하기 위해 매 프레임마다 호출됩니다.


bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
	float left, right, top, bottom;
	VertexType* vertices;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	VertexType* verticesPtr;
	HRESULT result;


이미지를 렌더링하는 위치가 바뀌었는지 확인합니다. 바뀌지 않았다면 정점 버퍼는 이 프레임에서 바뀔 필요가 없기 때문에 함수를 종료합니다. 이 확인은 많은 처리를 절약하도록 해줍니다.


	// If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it
	// currently has the correct parameters.
	if((positionX == m_previousPosX) && (positionY == m_previousPosY))
	{
		return true;
	}


만약 위치가 바뀌었다면 다음번에 이 함수에 올때를 위해 새로운 위치를 기록합니다.


	// If it has changed then update the position it is being rendered to.
	m_previousPosX = positionX;
	m_previousPosY = positionY;


이미지의 네 모서리를 계산해야 합니다. 완벽한 설명을 위해 강좌의 상단에 다이어그램을 참고하시기 바랍니다.


	// Calculate the screen coordinates of the left side of the bitmap.
	left = (float)((m_screenWidth / 2) * -1) + (float)positionX;

	// Calculate the screen coordinates of the right side of the bitmap.
	right = left + (float)m_bitmapWidth;

	// Calculate the screen coordinates of the top of the bitmap.
	top = (float)(m_screenHeight / 2) - (float)positionY;

	// Calculate the screen coordinates of the bottom of the bitmap.
	bottom = top - (float)m_bitmapHeight;


좌표가 계산되었으므로 임시 정점 배열을 만들고 새로운 여섯 정점을 채웁니다.


	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Load the vertex array with data.
	// First triangle.
	vertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
	vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

	vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
	vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f);

	vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
	vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f);

	// Second triangle.
	vertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
	vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

	vertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
	vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

	vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
	vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);


이제 Map함수와 memcpy함수를 이용하여 정점 배열의 내용을 정점 버퍼로 카피합니다.


	// Lock the vertex buffer so it can be written to.
	result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the vertex buffer.
	verticesPtr = (VertexType*)mappedResource.pData;

	// Copy the data into the vertex buffer.
	memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount));

	// Unlock the vertex buffer.
	deviceContext->Unmap(m_vertexBuffer, 0);

	// Release the vertex array as it is no longer needed.
	delete [] vertices;
	vertices = 0;

	return true;
}


RenderBuffers 함수는 gpu에서 쉐이더가 그릴수 있도록 정점, 인덱스 버퍼를 설정합니다.


void BitmapClass::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;
}


다음 함수는 2D 이미지를 그리는데 사용될 텍스쳐를 로드합니다.


bool BitmapClass::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;
}


ReleaseTexture 함수는 로드되었던 텍스쳐를 해제합니다.


void BitmapClass::ReleaseTexture()
{
	// Release the texture object.
	if(m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}


D3dclass.h


D3DClass를 Z 버퍼 사용을 키고 끌 수 있도록 바꾸었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")


//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
	D3DClass();
	D3DClass(const D3DClass&);
	~D3DClass();

	bool Initialize(int, int, bool, HWND, bool, float, float);
	void Shutdown();
	
	void BeginScene(float, float, float, float);
	void EndScene();

	ID3D11Device* GetDevice();
	ID3D11DeviceContext* GetDeviceContext();

	void GetProjectionMatrix(D3DXMATRIX&);
	void GetWorldMatrix(D3DXMATRIX&);
	void GetOrthoMatrix(D3DXMATRIX&);

	void GetVideoCardInfo(char*, int&);


D3DClass에 2D 이미지를 렌더링할때 Z 버퍼를 키고 끌 새로운 두 함수가 생겼습니다.


	void TurnZBufferOn();
	void TurnZBufferOff();

private:
	bool m_vsync_enabled;
	int m_videoCardMemory;
	char m_videoCardDescription[128];
	IDXGISwapChain* m_swapChain;
	ID3D11Device* m_device;
	ID3D11DeviceContext* m_deviceContext;
	ID3D11RenderTargetView* m_renderTargetView;
	ID3D11Texture2D* m_depthStencilBuffer;
	ID3D11DepthStencilState* m_depthStencilState;
	ID3D11DepthStencilView* m_depthStencilView;
	ID3D11RasterizerState* m_rasterState;
	D3DXMATRIX m_projectionMatrix;
	D3DXMATRIX m_worldMatrix;
	D3DXMATRIX m_orthoMatrix;


2D 그리기를 위한 새 깊이 스텐실 상태도 생겼습니다.


	ID3D11DepthStencilState* m_depthDisabledStencilState;
};

#endif


D3dclass.cpp


텍스쳐링 강좌 이후 이 클래스에서 변화된 함수만 설명할 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"


D3DClass::D3DClass()
{
	m_swapChain = 0;
	m_device = 0;
	m_deviceContext = 0;
	m_renderTargetView = 0;
	m_depthStencilBuffer = 0;
	m_depthStencilState = 0;
	m_depthStencilView = 0;
	m_rasterState = 0;


생성자에서 새 깊이 스텐실 상태를 NULL로 초기화합니다.


	m_depthDisabledStencilState = 0;
}


D3DClass::D3DClass(const D3DClass& other)
{
}


D3DClass::~D3DClass()
{
}


bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, float screenDepth, float screenNear)
{
	HRESULT result;
	IDXGIFactory* factory;
	IDXGIAdapter* adapter;
	IDXGIOutput* adapterOutput;
	unsigned int numModes, i, numerator, denominator, stringLength;
	DXGI_MODE_DESC* displayModeList;
	DXGI_ADAPTER_DESC adapterDesc;
	int error;
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	D3D_FEATURE_LEVEL featureLevel;
	ID3D11Texture2D* backBufferPtr;
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	D3D11_RASTERIZER_DESC rasterDesc;
	D3D11_VIEWPORT viewport;
	float fieldOfView, screenAspect;


새로운 깊이 스텐실 설정에 대한 깊이 스텐실 description 변수가 생겼습니다.


D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc; // Store the vsync setting. m_vsync_enabled = vsync; // Create a DirectX graphics interface factory. result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); if(FAILED(result)) { return false; } // Use the factory to create an adapter for the primary graphics interface (video card). result = factory->EnumAdapters(0, &adapter); if(FAILED(result)) { return false; } // Enumerate the primary adapter output (monitor). result = adapter->EnumOutputs(0, &adapterOutput); if(FAILED(result)) { return false; } // Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor). result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL); if(FAILED(result)) { return false; } // Create a list to hold all the possible display modes for this monitor/video card combination. displayModeList = new DXGI_MODE_DESC[numModes]; if(!displayModeList) { return false; } // Now fill the display mode list structures. result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList); if(FAILED(result)) { return false; } // Now go through all the display modes and find the one that matches the screen width and height. // When a match is found store the numerator and denominator of the refresh rate for that monitor. for(i=0; i<numModes; i++) { if(displayModeList[i].Width == (unsigned int)screenWidth) { if(displayModeList[i].Height == (unsigned int)screenHeight) { numerator = displayModeList[i].RefreshRate.Numerator; denominator = displayModeList[i].RefreshRate.Denominator; } } } // Get the adapter (video card) description. result = adapter->GetDesc(&adapterDesc); if(FAILED(result)) { return false; } // Store the dedicated video card memory in megabytes. m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024); // Convert the name of the video card to a character array and store it. error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128); if(error != 0) { return false; } // Release the display mode list. delete [] displayModeList; displayModeList = 0; // Release the adapter output. adapterOutput->Release(); adapterOutput = 0; // Release the adapter. adapter->Release(); adapter = 0; // Release the factory. factory->Release(); factory = 0; // Initialize the swap chain description. ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); // Set to a single back buffer. swapChainDesc.BufferCount = 1; // Set the width and height of the back buffer. swapChainDesc.BufferDesc.Width = screenWidth; swapChainDesc.BufferDesc.Height = screenHeight; // Set regular 32-bit surface for the back buffer. swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Set the refresh rate of the back buffer. if(m_vsync_enabled) { swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator; swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator; } else { swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; } // Set the usage of the back buffer. swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Set the handle for the window to render to. swapChainDesc.OutputWindow = hwnd; // Turn multisampling off. swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; // Set to full screen or windowed mode. if(fullscreen) { swapChainDesc.Windowed = false; } else { swapChainDesc.Windowed = true; } // Set the scan line ordering and scaling to unspecified. swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // Discard the back buffer contents after presenting. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // Don't set the advanced flags. swapChainDesc.Flags = 0; // Set the feature level to DirectX 11. featureLevel = D3D_FEATURE_LEVEL_11_0; // Create the swap chain, Direct3D device, and Direct3D device context. result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, D3D11_SDK_VERSION, &swapChainDesc,

&m_swapChain, &m_device, NULL, &m_deviceContext); if(FAILED(result)) { return false; } // Get the pointer to the back buffer. result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr); if(FAILED(result)) { return false; } // Create the render target view with the back buffer pointer. result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView); if(FAILED(result)) { return false; } // Release pointer to the back buffer as we no longer need it. backBufferPtr->Release(); backBufferPtr = 0; // Initialize the description of the depth buffer. ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc)); // Set up the description of the depth buffer. depthBufferDesc.Width = screenWidth; depthBufferDesc.Height = screenHeight; depthBufferDesc.MipLevels = 1; depthBufferDesc.ArraySize = 1; depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthBufferDesc.SampleDesc.Count = 1; depthBufferDesc.SampleDesc.Quality = 0; depthBufferDesc.Usage = D3D11_USAGE_DEFAULT; depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthBufferDesc.CPUAccessFlags = 0; depthBufferDesc.MiscFlags = 0; // Create the texture for the depth buffer using the filled out description. result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer); if(FAILED(result)) { return false; } // Initialize the description of the stencil state. ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc)); // Set up the description of the stencil state. depthStencilDesc.DepthEnable = true; depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthStencilDesc.StencilEnable = true; depthStencilDesc.StencilReadMask = 0xFF; depthStencilDesc.StencilWriteMask = 0xFF; // Stencil operations if pixel is front-facing. depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Stencil operations if pixel is back-facing. depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Create the depth stencil state. result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); if(FAILED(result)) { return false; } // Set the depth stencil state. m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1); // Initialize the depth stencil view. ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc)); // Set up the depth stencil view description. depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; depthStencilViewDesc.Texture2D.MipSlice = 0; // Create the depth stencil view. result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView); if(FAILED(result)) { return false; } // Bind the render target view and depth stencil buffer to the output render pipeline. m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView); // Setup the raster description which will determine how and what polygons will be drawn. rasterDesc.AntialiasedLineEnable = false; rasterDesc.CullMode = D3D11_CULL_BACK; rasterDesc.DepthBias = 0; rasterDesc.DepthBiasClamp = 0.0f; rasterDesc.DepthClipEnable = true; rasterDesc.FillMode = D3D11_FILL_SOLID; rasterDesc.FrontCounterClockwise = false; rasterDesc.MultisampleEnable = false; rasterDesc.ScissorEnable = false; rasterDesc.SlopeScaledDepthBias = 0.0f; // Create the rasterizer state from the description we just filled out. result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState); if(FAILED(result)) { return false; } // Now set the rasterizer state. m_deviceContext->RSSetState(m_rasterState); // Setup the viewport for rendering. viewport.Width = (float)screenWidth; viewport.Height = (float)screenHeight; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; // Create the viewport. m_deviceContext->RSSetViewports(1, &viewport); // Setup the projection matrix. fieldOfView = (float)D3DX_PI / 4.0f; screenAspect = (float)screenWidth / (float)screenHeight; // Create the projection matrix for 3D rendering. D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth); // Initialize the world matrix to the identity matrix. D3DXMatrixIdentity(&m_worldMatrix); // Create an orthographic projection matrix for 2D rendering. D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);


여기서 깊이 스텐실의 description을 설정합니다. 새 깊이 스텐실과 이전 스텐실의 유일한 차이는 DepthEnable을 2D 그리기를 위해 false로 설정한 것 뿐임을 기억하시기 바랍니다.


	// Clear the second depth stencil state before setting the parameters.
	ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc));

	// Now create a second depth stencil state which turns off the Z buffer for 2D rendering.  The only difference is 
	// that DepthEnable is set to false, all other parameters are the same as the other depth stencil state.
	depthDisabledStencilDesc.DepthEnable = false;
	depthDisabledStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthDisabledStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
	depthDisabledStencilDesc.StencilEnable = true;
	depthDisabledStencilDesc.StencilReadMask = 0xFF;
	depthDisabledStencilDesc.StencilWriteMask = 0xFF;
	depthDisabledStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthDisabledStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
	depthDisabledStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthDisabledStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthDisabledStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;


이제 새 깊이 스텐실을 생성합니다.


	// Create the state using the device.
	result = m_device->CreateDepthStencilState(&depthDisabledStencilDesc, &m_depthDisabledStencilState);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


void D3DClass::Shutdown()
{
	// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
	if(m_swapChain)
	{
		m_swapChain->SetFullscreenState(false, NULL);
	}


Shutdown 함수에서 새 깊이 스텐실을 해제합니다. 


	if(m_depthDisabledStencilState)
	{
		m_depthDisabledStencilState->Release();
		m_depthDisabledStencilState = 0;
	}

	if(m_rasterState)
	{
		m_rasterState->Release();
		m_rasterState = 0;
	}

	if(m_depthStencilView)
	{
		m_depthStencilView->Release();
		m_depthStencilView = 0;
	}

	if(m_depthStencilState)
	{
		m_depthStencilState->Release();
		m_depthStencilState = 0;
	}

	if(m_depthStencilBuffer)
	{
		m_depthStencilBuffer->Release();
		m_depthStencilBuffer = 0;
	}

	if(m_renderTargetView)
	{
		m_renderTargetView->Release();
		m_renderTargetView = 0;
	}

	if(m_deviceContext)
	{
		m_deviceContext->Release();
		m_deviceContext = 0;
	}

	if(m_device)
	{
		m_device->Release();
		m_device = 0;
	}

	if(m_swapChain)
	{
		m_swapChain->Release();
		m_swapChain = 0;
	}

	return;
}


void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
	float color[4];


	// Setup the color to clear the buffer to.
	color[0] = red;
	color[1] = green;
	color[2] = blue;
	color[3] = alpha;

	// Clear the back buffer.
	m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);
    
	// Clear the depth buffer.
	m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

	return;
}


void D3DClass::EndScene()
{
	// Present the back buffer to the screen since rendering is complete.
	if(m_vsync_enabled)
	{
		// Lock to screen refresh rate.
		m_swapChain->Present(1, 0);
	}
	else
	{
		// Present as fast as possible.
		m_swapChain->Present(0, 0);
	}

	return;
}


ID3D11Device* D3DClass::GetDevice()
{
	return m_device;
}


ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
	return m_deviceContext;
}


void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
	projectionMatrix = m_projectionMatrix;
	return;
}


void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
	worldMatrix = m_worldMatrix;
	return;
}


void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
	orthoMatrix = m_orthoMatrix;
	return;
}


void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
	strcpy_s(cardName, 128, m_videoCardDescription);
	memory = m_videoCardMemory;
	return;
}


이것들은 Z 버퍼를 사용, 비사용에 관한 새 함수들입니다. Z 버퍼링을 켜기 위해 원래의 깊이 스텐실을 설정하고 Z 버퍼링을 끄기 위해 depthEnable을 false로 설정한 새 깊이 스텐실을 설정합니다. 일반적으로 이 함수들을 사용하는 가장 좋은 방법은 먼저 모든 3D 렌더링을 한 뒤 Z 버퍼를 끄고 2D 렌더링을 한 뒤 다시 Z 버퍼를 켜는 것입니다.


void D3DClass::TurnZBufferOn()
{
	m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);
	return;
}


void D3DClass::TurnZBufferOff()
{
	m_deviceContext->OMSetDepthStencilState(m_depthDisabledStencilState, 1);
	return;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textureshaderclass.h"


새 BitmapClass 헤더 파일을 인클루드 합니다.


#include "bitmapclass.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;
	TextureShaderClass* m_TextureShader;


새 private BitmapClass 객체를 생성합니다.


	BitmapClass* m_Bitmap;
};

#endif


Graphicsclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_TextureShader = 0;


생성자에서 새 비트맵 객체를 NULL로 초기화합니다.


	m_Bitmap = 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 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;
	}


이곳은 새 BitmapClass 객체를 생성하고 초기화하는 곳입니다. 텍스쳐로 seafloor.dds를 사용하고 크기를 256x256으로 설정하였습니다. 텍스쳐의 정확한 크기를 반영할 필요는 없기때문에 여러분이 원하는 어느 사이즈이든 변경할 수 있습니다.


	// Create the bitmap object.
	m_Bitmap = new BitmapClass;
	if(!m_Bitmap)
	{
		return false;
	}

	// Initialize the bitmap object.
	result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, L"../Engine/data/seafloor.dds", 256, 256);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


BitmapClass 객체는 Shutdown 함수에서 해제됩니다.


	// Release the bitmap object.
	if(m_Bitmap)
	{
		m_Bitmap->Shutdown();
		delete m_Bitmap;
		m_Bitmap = 0;
	}

	// Release the texture shader object.
	if(m_TextureShader)
	{
		m_TextureShader->Shutdown();
		delete m_TextureShader;
		m_TextureShader = 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, orthoMatrix;
	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, projection, and ortho matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);


또 D3DClass로부터 2D 렌더링을 위한 ortho(직교)행렬을 얻습니다. 투영 행렬대신 이 행렬을 넘길 것입니다.


	m_D3D->GetOrthoMatrix(orthoMatrix);


2D 렌더링을 하기 전에 Z 버퍼가 꺼집니다.


	// Turn off the Z buffer to begin all 2D rendering.
	m_D3D->TurnZBufferOff();


그 다음 비트맵을 화면의 100, 100 위치에 렌더링합니다. 위치는 자유롭게 수정하실 수 있습니다.


	// Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing.
	result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 100, 100);
	if(!result)
	{
		return false;
	}


정점/인덱스 버퍼가 준비되면 텍스쳐 쉐이더를 이용해 그립니다. 2D 렌더링을 위해 투영 행렬 대신 ortho 행렬을 보내는 것을 주의하시기 바랍니다. 또 뷰 행렬은 바뀔 수 있어 2D 렌더링을 위한 기본 행렬을 만들어야 할 것이며 보통의 뷰 행렬 대신 이걸 사용해야 함을 주의하시기 바랍니다. 이번 강좌에서 카메라가 고정이라 보통의 뷰 행렬을 사용해도 괜찮습니다.


	// Render the bitmap with the texture shader.
	result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_Bitmap->GetTexture());
	if(!result)
	{
		return false;
	}


2D 렌더링이 다 끝나면 다음 3D 렌더링을 위해 Z 버퍼를 되돌립니다.


	// Turn the Z buffer back on now that all 2D rendering has completed.
	m_D3D->TurnZBufferOn();

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}


요약


새 개념들로 인해 이제 화면에 2D 이미지를 렌더링할 수 있습니다. 이번 강좌는 폰트 시스템과 UI 렌더링에 대한 입문입니다.



연습하기


1. 코드를 컴파일하고 여러분 화면의 100, 100 위치에 2D 이미지가 그려지는지 확인해보세요.


2. 화면상 이미지가 그려지는 위치를 변경해보세요.


3. GraphicsClass의 m_Bitmap->Initialize 함수 호출에서 이미지 사이즈를 변경해보세요.


4. 2D 이미지에 사용되는 텍스쳐를 변경해보세요.

728x90

+ Recent posts