제17장 버텍스 셰이더의 소개
버텍스 셰이더 버텍스 셰이더는 그래픽 카드의 GPU에서 실행되는 프로그램이며 고정 기능 파이프라인의 변환과 조명 단계를 대체하는 역할을 담당함 하드웨어에서 버텍스 셰이더를 지원하지 못하는 경우에는 Direct3D 런타임에서 소프트웨어로 애뮬레이트 하므로 100퍼센트 정확한 정의는 아님 버텍스 셰이더 모델 스페이스 동종 클립 스페이스 조명 모델과 뷰 스페이스 변환 투영 변환 프로그래머블 버텍스 경로 고정 기능 (그림1) 버텍스 셰이더는 고정 기능 파이프라인의 조명과 변환 단계를 대체 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더로 입력되는 버텍스가 지역 좌표를 가지며, 버텍스 셰이더는 조명(컬러)을 입힌 버텍스를 동종 클립 스페이스로 출력 투영 행렬이 버텍스를 변환하는 공간을 동종 클립 스페이스라 부름 포인트 기본형의 경우에는 버텍스 셰이더를 이용해 각 버텍스의 크기를 조정할 수도 있음 버텍스 셰이더는 우리가 직접 작성하는 커스텀 프로그램이기 때문에 구현할 수 있는 그래픽 효과의 범위가 엄청나게 향상될 수 있음. 2010-1학기 컴퓨터게임(DirectX)
의상 시뮬레이션이나 파티클 시스템을 위한 포인트 크기의 제어, 버텍스 블렌딩/모핑 등의 다양한 효과를 가능하게 해줌. 예를 들어, 어떤 조명 알고리즘도 이용할 수 있음. 버텍스의 위치를 조정할 수 있는 능력 의상 시뮬레이션이나 파티클 시스템을 위한 포인트 크기의 제어, 버텍스 블렌딩/모핑 등의 다양한 효과를 가능하게 해줌. 또한 고정 기능 파이프라인에 비하면 버텍스 데이터 구조체에 훨씬 많은 데이터를 저장할 수 있음 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더는 아직까지는 비교적 새로운 기능 중 하나이며 DirectX9에서 추가된 기능들은 많은 그래픽 카드들이 지원하지 못함 다음의 코드는 지원되는 버텍스 셰이더 버전을 확인하는 예 //만약 장치에서 지원하는 버전이 2.0보다 작다면 If(cap.VertexShaderVersion < D3DVS_VERSION(2,0)) // 현재 장치에서는 버텍스 셰이더 버전 2.0이 //지원되지 않는 것임 D3DVS_VERSION 매크로는 메이저와 마이너 버전 번호를 인자로 받는다는 것을 알 수 있음. D3DXCompileShaderFromFile 함수는 버텍스 셰이더 1.1과 2.0 버전을 지원 2010-1학기 컴퓨터게임(DirectX)
이 장의 내용 프로그래머블 파이프라인 내 버텍스 구조체의 요소를 정의하는 방법 버텍스 요소의 여러가지 다른 이용법 버텍스 셰이더를 만들고 지정하고 제거하는 방법 버텍스 셰이더를 이용해 카툰 렌더링을 구현하는 방법 2010-1학기 컴퓨터게임(DirectX)
버텍스 선언 FVF 로 표현하는 것보다 훨씬 많은 데이터를 포함할 수 있다. 버텍스 선언 기술하기 버텍스 선언을 기술하는 데는 D3DVERTEXELEMENT9 구조체의 배열이 이용되며, D3DVERTEXELEMENT9 배열의 각 요소는 버텍스의 한 요소를 기술. 따라서 버텍스 구조체가 세 개의 요소를(예:위치, 법선, 컬러) 가지고 있다면 대응하는 버텍스 선언은 세 개의 D3DVERTEXELEMENT9 구조체 배열을 이용해 기술 D3DVERTEXELEMENT9 구조체는 다음과 같이 정의 2010-1학기 컴퓨터게임(DirectX)
D3DVERTEXELEMENT9 구조체 typedef struct _D3DVERTEXELEMENT9{ BYTE Stream; BYTE Offset; BYTE Type; BYTE Method; BYTE Usage; BYTE UsageIndex; } D3DVERTEXELEMENT9; Stream – 버텍스 요소가 연결될 스트림을 지정 Offset – 버텍스 구조체 내의 버텍스 요소로의 시작을 가리키는 바이트 단위의 오프셋. 2010-1학기 컴퓨터게임(DirectX)
Type – 데이터 형을 지정. D3DDECLTYPE 열거형의 멤버 중 어떤 것이나 될 수 있음 D3DDECLTYPE_FLOAT1 – 부동 소수점 스칼라 D3DDECLTYPE_FLOAT2 – 2D 부동 소수점 스칼라 D3DDECLTYPE_FLOAT3 – 3D 부동 소수점 스칼라 D3DDECLTYPE_FLOAT4 – 4D 부동 소수점 스칼라 D3DDECLTYPE_D3DCOLOR – 각 성분이 [0,1] 범위로 정규화되어 RGBA 부동 소수 컬러 벡터로 확장되는 D3DCOLOR형 Method – 세분화 방식을 지정 Usage = 버텍스 요소의 계획된 용도를 지정. 예를 들어, 위치 벡터나 법선 벡터, 텍스처 좌표 등으로 이용될 수 있음. D3DDECLUSAGE 열거형의 멤버들을 식별자로 지정할 수 있음 2010-1학기 컴퓨터게임(DirectX)
typedef enum_D3DDECLUSAGE{ D3DDECLUSAGE_POSITION = 0, // 위치 D3DDECLUSAGE_BLENDWEIGHTS = 1, // 블렌딩 가중치 D3DDECLUSAGE_BLENDINDICES = 2, // 블렌딩 인덱스 D3DDECLUSAGE_NORMAL = 3, // 법선 벡터 D3DDECLUSAGE_PSIZE = 4, // 버텍스 포인트 크기 D3DDECLUSAGE_TEXCOORD = 5, // 텍스처 좌표 D3DDECLUSAGE_TANGENT = 6, // 접선 벡터 D3DDECLUSAGE_BINORMAL = 7, // 이중법선 벡터 2010-1학기 컴퓨터게임(DirectX)
D3DDECLUSAGE_TESSFACTOR = 8, // 세분화 인수 D3DDECLUSAGE_POSITIONT = 9, // 변환된 위치 D3DDECLUSAGE_COLOR = 10, // 컬러 D3DDECLUSAGE_FOG = 11, // 안개 블렌드 값 D3DDECLUSAGE_DEPTH = 12, // 깊이 값 D3DDECLUSAGE_SAMPLE = 13 // 샘플러 데이터 } D3DDECLUSAGE; D3DDECLUSAGE_PSIZE 형은 버텍스 포인트의 크기를 지정하며, 포인트 스프라이트를 구현할 때 각 버텍스의 크기를 지정하는 데 이용 UsageIndex – 동일한 용도 내에서 다수의 버텍스 요소를 식별하는데 이용됨. 용도 인덱스는 [0, 15] 범위 내의 정수임 2010-1학기 컴퓨터게임(DirectX)
버텍스 선언 기술의 예: 하나의 위치 벡터와 세 개의 법선 벡터로 구성된 버텍스 포맷을 기술하고자 한다면 다음과 같이 버텍스 선언을 작성 D3DVERTXELEMENT9 decl[]= { {0, 0, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, 12, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, {0, 24, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1}, {0, 36, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 2}, D3DDECL_END() }; 2010-1학기 컴퓨터게임(DirectX)
버텍스 선언 만들기 D3DVERTEXELEMENT9 배열로 버텍스 선언을 기술한 뒤에는 다음의 함수를 이용해 IDirect3DVertexDeclaration9 인터페이스로의 포인터를 얻을 수 있음 HRESULT IDirect3DDevice9::CreateVertexDeclaration( CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl }; pVertexElements – 우리가 만들려고 하는 버텍스 선언을 기술하는 D3DVERTEXELEMENT9 구조체의 배열 ppDecl – 만들어진 IDirect3DVertexDeclaration9 인터페이스로의 포인터를 리턴 2010-1학기 컴퓨터게임(DirectX)
함수 호출 예, decl은 D3DVERTEXELEMENT9 배열 IDirect#3DVertexDeclaration9* _decl = 0; hr = _device->CreateVertexDeclaration(decl, &_decl); 2010-1학기 컴퓨터게임(DirectX)
버텍스 선언 활성화하기 FVF는 매우 유용한 기능이며 내부적으로 버텍스 선언으로 변환됨. 즉, 직접 버텍스 선언을 이용할 때는 다음과 같이 호출하는 대신, Device->SetFVF(fvf); 다음과 같이 호출을 이용 Device->SetVertexDeclaration(_decl); 여기에서 _decl은 IDirect3DVertexDeclaration9 인터페이스로의 포인터임 2010-1학기 컴퓨터게임(DirectX)
버텍스 데이터 이용 다음과 같이 버텍스 선언이 있다고 가정 D3DVERTXELEMENT9 decl[]= { {0, 0, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, 12, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, {0, 24, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1}, {0, 36, D3DDECLTYPE_FLOAT3, D3DDELMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 2}, D3DDECL_END() }; 2010-1학기 컴퓨터게임(DirectX)
버텍스 선언의 요소에서 버텍스 셰이더 입력 구조체의 데이터 멤버를 연결하는 맵을 정의하는 방법이 필요. 이 맵은 입력 구조체 내에 지정된 각 데이터 멤버의 의미(용도-타입[용도-인덱스])로 정의됨 의미는 용도의 타입과 인덱스로 버텍스 선언 내의 요소를 식별해내며, 데이터 멤버의 의미로 식별된 버텍스 요소가 데이터 멤버로 매핑되는 요소가 됨 예를 들어 앞서의 버텍스 선언을 위한 입력 구조체는 다음과 같음 struct VS_INPUT { vector position : POSITION; vector normal : NORMAL0; vector faceNormal1 : NORMAL1; vector faceNormal2 : NORMAL2; }; 2010-1학기 컴퓨터게임(DirectX)
요소 0은 decl이고 용도 POSITION과 용도 인덱스 0으로 식별되며 position으로 매핑됨. 요소 1은 decl이고 용도 NORMAL과 용도 인덱스 0으로 식별되며 normal로 매핑됨. 요소 2는 decl이고 용도 NORMAL과 용도 인덱스 1로 식별되는 faceNormal1로 매핑됨. 요소 3은 decl이고 용도 NORMAL과 용도 인덱스 2로 식별되며 faceNormal2로 매핑 2010-1학기 컴퓨터게임(DirectX)
지원되는 버텍스 셰이더 입력 용도에는 다음과 같은 것들이 있음 POSITION[n] – 위치 BLENDWEIGHTS[n] – 블렌드 가중치 BLENDINDICES[n] – 블렌드 인덱스 NORMAL[n] – 법선 벡터 PSIZE[n] – 버텍스 포인트 크기 DIFFUSE[n] – 난반사 컬러 SPECULAR[n] – 정반사 컬러 TEXCOORD[n] – 텍스처 좌표 TANGENT[n] – 접선 벡터 BINORMAL[n] – 이중법선 벡터 TESSFACTOR[n] – 세분화 인수 n은 선택적인 정수이며 [0, 15] 사이의 값을 가짐 2010-1학기 컴퓨터게임(DirectX)
출력 구조체에서는 각 멤버가 어떻게 이용될 것인지를 지정해야 함 예를 들어, 데이터 멤버가 위치 벡터로 이용되는지, 혹은 컬러인지, 혹은 텍스트 좌표로 이용되는지를 밝혀야 함. 그래픽 카드로서는 이를 알아낼 방법이 없음. 이 과정 역시 의미 구문을 통해 처리 struct VS_OUTPUT { vector position : POSITION; vector diffuse : COLOR0; vector specular : COLOR1; }; 지원되는 버텍스 셰이더 출력 용도 POSITION – 위치 PSIZE – 버텍스 포인트 크기 FOG – 안개 블렌드 값 2010-1학기 컴퓨터게임(DirectX)
COLOR[n] – 버텍스 컬러. 다수의 버텍스 컬러를 출력할 수 있다는데 주의 TEXCOORD[n] – 버텍스 텍스처 좌표 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더를 이용하기 위한 단계 1) 버텍스 셰이더를 작성하고 컴파일 2) 컴파일된 셰이더 코드를 기반으로 버텍스 셰이더를 나타내는 IDirect3DVertexShader9 인터페이스를 만듦 3) IDirect3DDevice9::SetVertexShader 메서드를 이용해 버텍스 셰이더를 활성화함 4) 이용이 끝난 버텍스 셰이더는 제거 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더의 작성과 컴파일 먼저 버텍스 셰이더 프로그램을 작성 셰이더 코드를 작성한 다음에는 D3DXCompileShaderFromFile 함수를 이용해 셰이더를 컴파일하며, 이 함수는 컴파일된 셰이더 코드를 포함하는 ID3DXBuffer로의 포인터를 리턴 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더 만들기 셰이더 코드를 컴파일한 뒤에는 다음의 메서드를 이용해 버텍스 셰이더를 나타내는 IDirect3DVertexShader9 인터페이스로의 포인터를 얻을 수 있음 HRESULT IDirect3DDevice9::CreateVertexShader( constDWORD *pFunction, IDirect3DVertexShader9** ppShader ); pFunction – 컴파일된 셰이더 코드로의 포인터 ppShader – IDirect3DVertexShader9 인터페이스로의 포인터를 리턴 2010-1학기 컴퓨터게임(DirectX)
IDirect3DVertexShader9* ToonShader = 0; 예를 들어 컴파일된 셰이더를 포함하는 ID3DXBuffer를 가지고 있다고 할 때, IDirect3DVertexShader9 인터페이스를 얻기 위해서는 다음과 같은 코드를 이용 IDirect3DVertexShader9* ToonShader = 0; hr = Device->CreateVertexShader( (DWORD*)shader->GetBufferPointer(), &ToonShader); 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더의 셋팅 버텍스 셰이더를 나타내는 IDirect3DVertexShader9 인터페이스로의 포인터를 얻은 뒤에는 다음의 메서드를 이용해 셰이더를 활성화할 수 있음 HRESULT IDirect3DDevice9::SetVertexShader( IDirect3DVertexShader9* pShader ); 이 메서드는 활성화하려는 버텍스 셰이더의 포인터를 하나의 인자로 받음. 17.3.2 섹션에서 만든 셰이더를 활성화하려면 다음과 같은 코드를 이용 Device->SetVertexShader(ToonShader); 2010-1학기 컴퓨터게임(DirectX)
버텍스 셰이더 파괴하기 다른 Direct3D 인터페이스들과 마찬가지로 이용이 끝난 뒤에는 Release 메서드를 이용해 셰이더를 제거해야 함. 17.3.2 섹션에서 만든 버텍스 셰이더를 제거하고자 한다면 다음과 같은 코드를 이용 d3d::Release<IDirect3DVertexShader9*>(ToonShader); 2010-1학기 컴퓨터게임(DirectX)
예제 애플리케이션: 난반사광 방향(평행) 광원을 이용하여 각 버텍스에 표준 난반사 조명을 수행하는 버텍스 셰이더를 작성 난반사 조명은 버텍스 법선과 조명 벡터(광원의 방향을 가리키는) 간의 각도에 따라 버텍스가 받을 빛의 양을 계산해 냄 각도가 작을수록 버텍스가 받는 빛의 양은 늘어나며 각도가 커질수록 버텍스가 받는 빛의 양이 줄어든다. 만약 각도가 90도 보다 크거나 같으면 버텍스는 전혀 빛을 받지 못함 2010-1학기 컴퓨터게임(DirectX)
2010-1학기 컴퓨터게임(DirectX)
// Author: Frank Luna (C) All Rights Reserved 다음은 버텍스 셰이더 코드임 //////////////////////////////////////////////////////////////////////////// // // File: diffuse.txt // Author: Frank Luna (C) All Rights Reserved // System: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, MSVC++ 7.0 // Desc: Vertex shader that does diffuse lighting. 2010-1학기 컴퓨터게임(DirectX)
// Global variables we use to hold the view matrix, projection matrix, // ambient material, diffuse material, and the light vector that describes // the direction to the light source. These variables are initialized from // the application. matrix ViewMatrix; matrix ViewProjMatrix; vector AmbientMtrl; vector DiffuseMtrl; vector LightDirection; 2010-1학기 컴퓨터게임(DirectX)
// Global variables used to hold the ambient light intensity (ambient // light the light source emits) and the diffuse light intensity (diffuse // light the light source emits). These variables are initialized here // in the shader. vector DiffuseLightIntensity = {0.0f, 0.0f, 1.0f, 1.0f}; vector AmbientLightIntensity = {0.0f, 0.0f, 0.2f, 1.0f}; // Input and Output structures. 2010-1학기 컴퓨터게임(DirectX)
vector position : POSITION; vector normal : NORMAL; }; struct VS_INPUT { vector position : POSITION; vector normal : NORMAL; }; struct VS_OUTPUT vector diffuse : COLOR; // // Main 2010-1학기 컴퓨터게임(DirectX)
VS_OUTPUT Main(VS_INPUT input) { // zero out all members of the output instance. VS_OUTPUT output = (VS_OUTPUT)0; // // Transform position to homogeneous clip space // and store in the output.position member. output.position = mul(input.position, ViewProjMatrix); // Transform lights and normals to view space. Set w // componentes to zero since we're transforming vectors 2010-1학기 컴퓨터게임(DirectX)
LightDirection = mul(LightDirection, ViewMatrix); // here and not points. // LightDirection.w = 0.0f; input.normal.w = 0.0f; LightDirection = mul(LightDirection, ViewMatrix); input.normal = mul(input.normal, ViewMatrix); // Compute cosine of the angle between light and normal. float s = dot(LightDirection, input.normal); // Recall that if the angle between the surface and light 2010-1학기 컴퓨터게임(DirectX)
// is greater than 90 degrees the surface recieves no light. // Thus, if the angle is greater than 90 degrees we set // s to zero so that the surface will not be lit. // if( s < 0.0f ) s = 0.0f; // Ambient light reflected is computed by performing a // component wise multiplication with the ambient material // vector and the ambient light intensity vector. // Diffuse light reflected is computed by performing a // component wise multiplication with the diffuse material 2010-1학기 컴퓨터게임(DirectX)
// vector and the diffuse light intensity vector. Further // we scale each component by the shading scalar s, which // shades the color based on how much light the vertex received // from the light source. // // The sum of both the ambient and diffuse components gives // us our final vertex color. output.diffuse = (AmbientMtrl * AmbientLightIntensity) + (s * (DiffuseLightIntensity * DiffuseMtrl)); return output; } 2010-1학기 컴퓨터게임(DirectX)
애플리케이션 코드에서 전역 변수는 다음과 같이 선언 Direct3DVertexShader9* DiffuseShader = 0; ID3DXConstantTable* DiffuseConstTable = 0; ID3DXMesh* Teapot = 0; D3DXHANDLE ViewMatrixHandle = 0; D3DXHANDLE ViewProjMatrixHandle = 0; D3DXHANDLE AmbientMtrlHandle = 0; D3DXHANDLE DiffuseMtrlHandle = 0; D3DXHANDLE LightDirHandle = 0; D3DXMATRIX Proj; 2010-1학기 컴퓨터게임(DirectX)
여기에는 버텍스 셰이더를 의미하는 변수와 셰이더의 상수 테이블에 대응되는 변수들이 포함되어 있음. 주전자 메쉬를 위한 변수가 있으며, 참조하는 변수들의 이름을 가지는 여러 개의 D3DXHANDLE이 정의되어 있음 Setup 함수는 다음과 같은 작업을 수행 주전자 메쉬를 만든다 버텍스 셰이더를 컴파일함 컴파일된 코드를 이용해 버텍스 셰이더를 만든다 상수 테이블을 통해 셰이더 프로그램 내의 몇 개의 변수로 통하는 핸들을 얻음 상수 테이블을 통해 몇 개의 셰이더 변수들을 초기화 2010-1학기 컴퓨터게임(DirectX)
D3DXCreateTeapot(Device, &Teapot, 0); bool Setup() { HRESULT hr = 0; // // Create geometry: D3DXCreateTeapot(Device, &Teapot, 0); // Compile shader ID3DXBuffer* shader = 0; ID3DXBuffer* errorBuffer = 0; 2010-1학기 컴퓨터게임(DirectX)
hr = D3DXCompileShaderFromFile( "diffuse.txt", 0, "Main", // entry point function name "vs_1_1", D3DXSHADER_DEBUG, &shader, &errorBuffer, &DiffuseConstTable); // output any error messages if( errorBuffer ) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); d3d::Release<ID3DXBuffer*>(errorBuffer); } 2010-1학기 컴퓨터게임(DirectX)
::MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0); if(FAILED(hr)) { ::MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0); return false; } // Create shader hr = Device->CreateVertexShader( (DWORD*)shader->GetBufferPointer(), &DiffuseShader); 2010-1학기 컴퓨터게임(DirectX)
::MessageBox(0, "CreateVertexShader - FAILED", 0, 0); return false; } d3d::Release<ID3DXBuffer*>(shader); // // Get Handles ViewMatrixHandle = DiffuseConstTable->GetConstantByName(0, "ViewMatrix"); ViewProjMatrixHandle= DiffuseConstTable->GetConstantByName(0, "ViewProjMatrix"); 2010-1학기 컴퓨터게임(DirectX)
// Set shader constants: // Light direction: AmbientMtrlHandle = DiffuseConstTable->GetConstantByName(0, "AmbientMtrl"); DiffuseMtrlHandle = DiffuseConstTable->GetConstantByName(0, "DiffuseMtrl"); LightDirHandle = DiffuseConstTable->GetConstantByName(0, "LightDirection"); // Set shader constants: // Light direction: D3DXVECTOR4 directionToLight(-0.57f, 0.57f, -0.57f, 0.0f); DiffuseConstTable->SetVector(Device, LightDirHandle, &directionToLight); // Materials: D3DXVECTOR4 ambientMtrl(0.0f, 0.0f, 1.0f, 1.0f); D3DXVECTOR4 diffuseMtrl(0.0f, 0.0f, 1.0f, 1.0f); 2010-1학기 컴퓨터게임(DirectX)
DiffuseConstTable->SetDefaults(Device); DiffuseConstTable->SetVector(Device,AmbientMtrlHandle,&ambientMtrl); DiffuseConstTable->SetVector(Device,DiffuseMtrlHandle,&diffuseMtrl); DiffuseConstTable->SetDefaults(Device); // Compute projection matrix. D3DXMatrixPerspectiveFovLH( &Proj, D3DX_PI * 0.25f, (float)Width / (float)Height, 1.0f, 1000.0f); return true; } 2010-1학기 컴퓨터게임(DirectX)
Display 함수는 사용자의 입력을 테스트하고 이에 따라 뷰행렬을 적절히 갱신하는 역할을 함 뷰 행렬 변환은 셰이더 내에서 이루어지므로 뷰 행렬 변수의 갱신 역시 셰이더 내에서 처리되어야 함. 이 과정에서는 상수 테이블 이용 2010-1학기 컴퓨터게임(DirectX)
bool Display(float timeDelta) { if( Device ) // // Update the scene: Allow user to rotate around scene. static float angle = (3.0f * D3DX_PI) / 2.0f; static float height = 3.0f; if( ::GetAsyncKeyState(VK_LEFT) & 0x8000f ) angle -= 0.5f * timeDelta; if( ::GetAsyncKeyState(VK_RIGHT) & 0x8000f ) angle += 0.5f * timeDelta; 2010-1학기 컴퓨터게임(DirectX)
if( ::GetAsyncKeyState(VK_UP) & 0x8000f ) height += 5.0f * timeDelta; if( ::GetAsyncKeyState(VK_DOWN) & 0x8000f ) height -= 5.0f * timeDelta; D3DXVECTOR3 position( cosf(angle) * 7.0f, height, sinf(angle) * 7.0f ); D3DXVECTOR3 target(0.0f, 0.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &target, &up); DiffuseConstTable->SetMatrix(Device, ViewMatrixHandle,&V); D3DXMATRIX ViewProj = V * Proj; DiffuseConstTable->SetMatrix(Device, ViewProjMatrixHandle, &ViewProj); 2010-1학기 컴퓨터게임(DirectX)
Device->BeginScene(); Device->SetVertexShader(DiffuseShader); // // Render Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene(); Device->SetVertexShader(DiffuseShader); Teapot->DrawSubset(0); Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; 2010-1학기 컴퓨터게임(DirectX)
DrawSubset을 호출하기 직전에 버텍스 셰이더를 활성화했음을 확인 정리 단계에서는 할당된 인터페이스를 해제 void Cleanup() { d3d::Release<ID3DXMesh*>(Teapot); d3d::Release<IDirect3DVertexShader9*>(DiffuseShader); d3d::Release<ID3DXConstantTable*>(DiffuseConstTable); } 2010-1학기 컴퓨터게임(DirectX)
예제 애플리케이션: 카툰 렌더링 만화 스타일의 드로잉 방식으로 메쉬를 그려냄 카툰 렌더링은 비실사적인 렌더링 방식의 하나이며 스타일리스틱 렌더링이라고도 불림 카툰 렌더링은 모든 스타일의 게임에 적용할 수 있는 것은 아니지만 만화의 느낌을 원하는 경우에 훌륭한 도구가 될 수 있고, 카툰 렌더링은 다음과 같은 두 단계로 분리 카툰 드로잉은 일반적으로 하나의 음영에서 다른 음영으로의 변환이 매우 가파르며 음영 강도의 단계 역시 비교적 단순 카툰 드로잉은 실루엣 외곽선을 가지고 있음 2010-1학기 컴퓨터게임(DirectX)
카툰 셰이딩 카툰 셰이딩의 기본적인 원리에 따르면, 카툰 셰이딩을 위해서는 일단 우리가 원하는 음영 강도를 포함하는 그레이 스케일 명도 텍스처가 필요 (그림 1)셰이드 텍스처는 우리가 이용할 음영 강도를 포함 음영간의 전환이 급격함 2010-1학기 컴퓨터게임(DirectX)
만약 s<0라면, s=0이 되도록 해야하므로 s∈[0, 1] 버텍스 셰이더에서는 표준 난반사 계산 내적을 수행하여 버텍스 법선 N과 광원벡터 L 간의 각도의 코사인을 알아내고, 이를 이용해 버텍스가 받는 빛의 양을 결정 s = L·N 만약 s<0라면, 광원 벡터와 버텍스 법선 간의 각도가 90도 보다 크다는 의미이며, 이 경우에는 표면이 빛을 받지 않음. 만약 s<0라면, s=0이 되도록 해야하므로 s∈[0, 1] 일반적인 난반사 광원 모델에서는 버텍스가 받는 빛의 양에 따라 음영이 결정되도록 s를 이용해 컬러 벡터의 배율을 조정 diffuseColor=s(r, g, b, a) 이 과정을 그대로 거치면 어두운 음영에서 밝은 음영으로의 부드러운 전환이 만들어지며, 이는 우리가 원하는 카툰 셰이딩과 상반되는 것임. 우리가 원하는 것은 적은 수의 음영을 이용한 급격한 음영 변환을 원함 2010-1학기 컴퓨터게임(DirectX)
(그림2) 텍스처 좌표의 범위에 따라 이용되는 음영이 결정 따라서 s는 컬러 벡터의 크기 변경이 아니라 (그림1)에서 보았던 명도 텍스처의 텍스처 좌표 u로 이용하게 됨 이와 같은 원리로 부드러운 음영 변환 대신 급격한 음영 변환의 예로 명도 텍스처를 세 가지의 음영으로 나눌 수 있음 s∈[0, 0.33] 값에는 음영 0이 이용되며, s ∈(0.33, 0.66] 값에는 음영 1이 이용되고, s(0.66, 1] 값에는 음영 2가 이용됨 한 음영에서 다른 음영으로의 변화는 매우 급격하므로 우리가 원하는 효과가 만들어짐 셰이드 0 셰이드 1 셰이드 2 .66 .33 0.0 1.0 (그림2) 텍스처 좌표의 범위에 따라 이용되는 음영이 결정 2010-1학기 컴퓨터게임(DirectX)
카툰 셰이딩 버텍스 셰이더 코드 버텍스 셰이더를 작성하는데 가장 중요한 작업은 s =L·N을 기반으로 텍스처 좌표를 계산하고 지정하는 것 출력 구조체 내에 계산된 텍스처 좌표를 보관하기 위한 데이터 멤버가 추가되었다는 것과, 여전히 버텍스의 컬러를 출력한다는 것을 눈여겨 봐야 함. 컬러는 명도 텍스처와 조합될 때 음영으로 나타남 2010-1학기 컴퓨터게임(DirectX)
// Author: Frank Luna (C) All Rights Reserved //////////////////////////////////////////////////////////////////////////// // // File: toon.txt // Author: Frank Luna (C) All Rights Reserved // System: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, MSVC++ 7.0 // Desc: Vertex shader that lights geometry such it appears to be // drawn in a cartoon style. 2010-1학기 컴퓨터게임(DirectX)
extern matrix WorldViewMatrix; extern matrix WorldViewProjMatrix; // // Globals extern matrix WorldViewMatrix; extern matrix WorldViewProjMatrix; extern vector Color; extern vector LightDirection; // Structures 2010-1학기 컴퓨터게임(DirectX)
vector position : POSITION; vector normal : NORMAL; }; struct VS_INPUT { vector position : POSITION; vector normal : NORMAL; }; struct VS_OUTPUT float2 uvCoords : TEXCOORD; vector diffuse : COLOR; // Main 2010-1학기 컴퓨터게임(DirectX)
VS_OUTPUT Main(VS_INPUT input) { // zero out each member in output VS_OUTPUT output = (VS_OUTPUT)0; // transform vertex position to homogenous clip space output.position = mul(input.position, WorldViewProjMatrix); // // Transform lights and normals to view space. Set w // components to zero since we're transforming vectors. // Assume there are no scalings in the world // matrix as well. 2010-1학기 컴퓨터게임(DirectX)
LightDirection = mul(LightDirection, WorldViewMatrix); LightDirection.w = 0.0f; input.normal.w = 0.0f; LightDirection = mul(LightDirection, WorldViewMatrix); input.normal = mul(input.normal, WorldViewMatrix); // // Compute the 1D texture coordinate for toon rendering. float u = dot(LightDirection, input.normal); // Clamp to zero if u is negative because u // negative implies the angle between the light // and normal is greater than 90 degrees. And 2010-1학기 컴퓨터게임(DirectX)
// if that is true then the surface receives // no light. // if( u < 0.0f ) u = 0.0f; // Set other tex coord to middle. float v = 0.5f; output.uvCoords.x = u; output.uvCoords.y = v; 2010-1학기 컴퓨터게임(DirectX)
output.diffuse = Color; return output; } 몇 가지 주의 사항: // save color output.diffuse = Color; return output; } 몇 가지 주의 사항: 여기에서는 월드 행렬이 크기 변형을 하지 않는다고 가정. 만약 그렇다면 이를 곱하는 벡터의 길이와 방향에 혼란이 생길 수 있음 항상 v 텍스처 좌표를 텍스처의 중앙으로 지정. 이것은 텍스처 내의 단일 수평 라인만을 이용하겠다는 것이며 2D가 아닌 1D 명도 텍스처를 이용할 수도 있음을 의미. 1D와 2D 텍스처는 모두 작동에 문제가 없으며, 예제에서는 1D 텍스처 대신 2D 텍스처를 이용 2010-1학기 컴퓨터게임(DirectX)
2010-1학기 컴퓨터게임(DirectX)
실루엣 외곽선 카툰 효과를 완성하기 위해서는 실루엣 외곽선을 그려주어야 함. 이 과정은 카툰 셰이딩에 비해 다소 복잡 2010-1학기 컴퓨터게임(DirectX)
경계선 표현 메쉬의 외곽선은 하나의 사각형으로(두 개의 삼각형으로 구성된) 나타낼 수 있음 사각형을 선택한 데는 우선 사각형의 넓이를 조정하여 손쉽게 외곽선의 두께를 변할 수 있다는 점. 실루엣 외곽선이 아닌 외곽선을 감추기 위한 퇴축 사각형(degenerate quad)을 렌더링 할 수 있다는 장점 Direct3D에서 사각형은 두 개의 삼각형으로 만들어지며, 퇴축 사각형은 두 개의 퇴축 삼각형으로 만들어진 사각형을 의미 퇴축 삼각형은 0의 넓이를 가진 삼각형으로, 다른 말로 하면 동일한 라인에 위치한 세 개의 버텍스로 정의된 삼각형 퇴축 삼각형을 렌더링 파이프라인으로 보내면 해당 삼각형에 대해서는 아무것도 보여지지 않음. 이는 삼각형 리스트(버텍스 버퍼)에서 삼각형을 제거하지 않고도 특정한 삼각형을 퇴축시켜 감출 수 있게 해주므로 상당히 유용함. 2010-1학기 컴퓨터게임(DirectX)
퇴축 사각형은 두 삼각형에서 공유하는 외곽선을 나타냄 처음 경계선을 만들 때는 외곽선의 네 버텍스를 지정하여 퇴축되도록 함. 이는 외곽선이 감추어질 것임을 의미(렌더링시 보여지지 않음) v0과 v1의 두 버텍스의 버텍스 법선 벡터를 영 벡터로 지정. 이 외곽선 벡터를 셰이더에 버텍스 셰이더에 전달하면 셰이더는 버텍스가 실루엣 외곽선인지를 확인하며, 만약 그렇다면 버텍스 법선 방향으로 버텍스 위치를 일정한 스칼라만큼 이동. 이때 영법선 벡터를 가진 버텍스는 영향 받지 않는다는 데 주의 v0=v2 v1=v3 퇴축 사각형은 두 삼각형에서 공유하는 외곽선을 나타냄 2010-1학기 컴퓨터게임(DirectX)
- 실루엣 외곽선의 버텍스 v2와 v3이 자신의 법선 방향 n2와 n3으로 오프셋 이동 한다. 비-퇴축 사각형이 실루엣 경계선으로 나타남 v1 v3 v0 v2 kn3 kn2 - 실루엣 외곽선의 버텍스 v2와 v3이 자신의 법선 방향 n2와 n3으로 오프셋 이동 한다. - 버텍스 v0과 v1의 버텍스 법선은 영 벡터와 같으므로 이 두 버텍스의 위치는 영향 받지 않는다. 2010-1학기 컴퓨터게임(DirectX)
실루엣 경계 테스트 외곽선의 두 개의 면 face0과 face1이 관찰 방향에서 서로 다른 면을 공유할 경우에 이 외곽선을 실루엣 외곽선이라 할 수 있음 만약 하나의 면이 전면을 바라보고 다른 면이 후면을 바라볼 경우 이 외곽선은 실루엣 외곽선임. 2010-1학기 컴퓨터게임(DirectX)
다음은 실루엣 외곽선과 비-실루엣 외곽선의 예를 보여주고 있음 v0 v1 n0 n1 v 관찰방향 (b) (a) (a) 버텍스 v0과 v1로 정의되는 외곽선을 공유하는 하나의 면은 전면을 바라보고 다른 면을 후면을 바라본다. 따라서 이 외곽선은 실루엣 외곽선이다 (b) v0과 v1로 정의되는 외곽선을 공유하는 면이 모두 전면을 바라본다 따라서 이 외곽선은 실루엣 외곽선이 아니다. 2010-1학기 컴퓨터게임(DirectX)
vector position : POSITION; vector normal : NORMAL0; 버텍스가 실루엣 외곽선에 있는지를 테스트하기 위해서는 먼저 face0과 face1의 법선 벡터를 알아야 함. struct VS_INPUT { vector position : POSITION; vector normal : NORMAL0; vector faceNormal1 : NORMAL1; vector faceNormal2 : NORMAL2; }; 처음 두 개의 요소는 어떤 것인지 쉽게 알 수 있을 것이고 나머지 두 개의 요소인 faceNormal1과 faceNormal2는 외곽선을 공유하는 면의 면 법선을 나타냄 2010-1학기 컴퓨터게임(DirectX)
버텍스가 실루엣 외곽선에 있는지를 확인하는 공식은 다음과 같음 먼저 뷰 스페이스 내에 있다고 가정하고 원점과 테스트하려는 버텍스 간의 벡터를 v라 함 이제 face0의 면 법선이 n0이고 face1의 면 법선이 n1이라고 가정할 때 다음과 같은 부등식이 성립할 경우 버텍스는 실루엣 외곽선에 있는 것 (v·n0)(v·n1) < 0 두 내적의 부호가 다를 경우 위의 부등식이 성립. 두 내적의 부호가 다른 경우 하나의 면이 전면을 향하고 다른 면이 후면을 향하는 내적의 특성을 기억할 것 2010-1학기 컴퓨터게임(DirectX)
외곽선이 하나의 삼각형만을 가지는 상황에서는 외곽선의 법선은 faceNormal1에 보관. 이와 같은 외곽선은 항상 실루엣 외곽선인 것으로 정의해야 하므로, 버텍스 셰이더가 이와 같은 외곽선을 실루엣 외곽선으로 취급하도록 faceNormal2 = -faceNormal1로 함. 즉, 면 법선이 반대 방향이 되도록 하여 위의 부등식이 성립하도록 함 2010-1학기 컴퓨터게임(DirectX)
경계 생성 메쉬 외곽선을 생성하는 것은 메쉬 내의 각 면을 대상으로 반복하여 면의 각 외곽선에 대한 사각형을 계산하면 됨 각 외곽선의 버텍스에서 외곽선을 공유하는 두 개의 면에 대해서도 알아야 함. 면 중 하나는 삼각형의 외곽선이 있는 곳으로, 예를 들어, i번째 면의 외곽선을 계산한다면, i번째 면이 외곽선을 공유. 외곽선을 공유하는 다른 면은 메쉬의 인접 정보를 이용해 찾을 수 있음 2010-1학기 컴퓨터게임(DirectX)
실루엣 외곽선 버텍스 셰이더 코드 이 셰이더의 가장 중요한 작업은 전달된 버텍스가 실루엣 외곽선인지를 판단하는 것이며, 버텍스의 위치를 지정된 스칼라만큼 버텍스 법선 방향으로 오프셋 이동해야 함 2010-1학기 컴퓨터게임(DirectX)
// Author: Frank Luna (C) All Rights Reserved //////////////////////////////////////////////////////////////////////////// // File: outline.txt // // Author: Frank Luna (C) All Rights Reserved // System: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, MSVC++ 7.0 // Desc: Vertex shader that draws the silhouette edges of a mesh. // Globals extern matrix WorldViewMatrix; extern matrix ProjMatrix; 2010-1학기 컴퓨터게임(DirectX)
static vector Black = {0.0f, 0.0f, 0.0f, 0.0f}; // // Structures struct VS_INPUT { vector position : POSITION; vector normal : NORMAL0; vector faceNormal1 : NORMAL1; vector faceNormal2 : NORMAL2; }; struct VS_OUTPUT 2010-1학기 컴퓨터게임(DirectX)
vector position : POSITION; vector diffuse : COLOR; }; // Main VS_OUTPUT Main(VS_INPUT input) { // zero out each member in output VS_OUTPUT output = (VS_OUTPUT)0; // transform position to view space input.position = mul(input.position, WorldViewMatrix); // Compute a vector in the direction of the vertex // from the eye. Recall the eye is at the origin // in view space - eye is just camera position. vector eyeToVertex = input.position; 2010-1학기 컴퓨터게임(DirectX)
// transform normals to view space. Set w // components to zero since we're transforming vectors. // Assume there are no scalings in the world // matrix as well. input.normal.w = 0.0f; input.faceNormal1.w = 0.0f; input.faceNormal2.w = 0.0f; input.normal = mul(input.normal, WorldViewMatrix); input.faceNormal1 = mul(input.faceNormal1, WorldViewMatrix); input.faceNormal2 = mul(input.faceNormal2, WorldViewMatrix); // compute the cosine of the angles between // the eyeToVertex vector and the face normals. float dot0 = dot(eyeToVertex, input.faceNormal1); float dot1 = dot(eyeToVertex, input.faceNormal2); 2010-1학기 컴퓨터게임(DirectX)
// if cosines are different signs (positive/negative) // than we are on a silhouette edge. Do the signs // differ? if( (dot0 * dot1) < 0.0f ) { // yes, then this vertex is on a silhouette edge, // offset the vertex position by some scalar in the // direction of the vertex normal. input.position += 0.1f * input.normal; } // transform to homogeneous clip space output.position = mul(input.position, ProjMatrix); // set outline color output.diffuse = Black; return output; 2010-1학기 컴퓨터게임(DirectX)
2010-1학기 컴퓨터게임(DirectX)