새로운 3D API 전쟁, OpenGL의 반격은 성공할 것인가?


월간 마소 2003년 2월 기사 중에서 OpenGL에 관련된 기사가 있어서 발췌했다. 이 글은 김성완([email protected]) 님이 작성하셨다.


특집 2-4 다이렉트X 9 그래픽의 세계

새로운 3D API 전쟁, OpenGL의 반격은 성공할 것인가?

필자가 다이렉트X를 호감어린 눈으로 바라보기 시작한 것이 바 로 7.0 버전부터이다. 다이렉트X의 3D API인 다이렉트3D는 사실 그 이전 버전까지는 PC 플랫폼에서 OpenGL보다 잘 지원된다 는 이유 외에는 딱히 반갑지 않은 API였다. 사용법도 초기 버전에 비 해서 어느 정도 개선이 이뤄졌지만 여전히 OpenGL에 비해서 많이 불편했고 지원되는 기능이나 참고할 수 있는 자료에 있어서는 OpenGL의 상대가 되지 않았다. 그럼에도 불구하고 윈도우 운영체 제로 PC 플랫폼을 완전히 장악하고 있는 마이크로소프트(이하 MS) 가 적극적으로 밀고 있는 3D API라서 무시할 수도 없는, 정말 심기 불편한 존재였다. 하지만 7.0 버전은 새롭게 만들어졌다고 볼 수 있 을 정도로 완전히 환골탈태한 모습을 보여줬고 속이 다 후련할 정도 였다.
그리고 이어서 나온 다이렉트X 8.0은 7.0의 연장선상에서 2D API인 다이렉트드로우(DirectDraw)와의 엉거주춤한 결합을 완전한 통합으로 전환했고, 엔비디아에서 새롭게 선보인 지포스3 칩의 획기적인 기능인 프로그래밍 가능한 렌더링 파이프라인을 처음으로 API의 기본 스펙으로 수용함으로써 명실상부한 가장 앞서가는 3D API가 되었다. 물론 사용의 편의성 면에서는 OpenGL이 여전히 조금 우위에 있긴 했지만, 이젠 누가 보더라도 OpenGL이 다이렉트X에 뒤지고 있는 게 확실했다.

#

다이렉트X에 왕관 뺏긴 OpenGL?

1992년 OpenGL이 처음으로 정식 발표된 이후로 PC의 그래픽 하드웨어는 눈부시게 발전을 거듭했고, 21세기에 접어든 시점에서는 파릇파릇한 하드웨어에 비해서 OpenGL은 여기저기 노쇠한 모습을 보이고 있었다. 결정적으로 엔비디아의 지포스3에 이어서 ATI도 재빠르게 좀더 개선된 프로그래밍 가능한 렌더링 파이프라인 기능을 선보이며 라데온 칩을 발표하자 이제 프로그래밍 가능한 렌더링 파이프라인 기능은 PC 플랫폼의 대세가 되고 있었고, 다이렉트X도 ATI 라데온 칩의 발표에 맞추어서 재빠르게 다이렉트X 8.1 버전을 발표했다. MS의 이처럼 발빠른 대응은 OpenGL을 완전히 낡은 3D API로 보이게 했다. 물론 새로운 하드웨어의 최신 기능들은 각 하드웨어 제조사 고유의 OpenGL 확장을 통해서 사용할 수는 있었지만, 하드웨어 제조사마다 상이한 확장들을 내놓았고, 느리게 움직이는 OpenGL의 표준 스펙으로는 빠르게 발전하는 하드웨어의 최신 기능들을 제대로 지원하지 못하고 있는 상태이다. 특히 가장 왕성하게 움직이는 엔비디아의 OpenGL 확장은 명세서만 500페이지가 넘을 정도로 어마어마한 규모가 되었고, 현재 최신의 OpenGL 1.4 버전의 표준 스펙 명세서 양이 300페이지를 조금 넘는 것과 비교한다면 배보다 배꼽이 더 큰 형국이 되었다.

#

OpenGL 작전 회의 돌입

다이렉트X의 바람직한 변신은 반가운 일이지만, 상황이 이렇게 되자 기존에 OpenGL을 사용하던 개발자들도 다이렉트X로 옮기려는 움직임을 보이기 시작하는 등 오랜 왕자의 자리를 빼앗긴 OpenGL은 안타까운 상황에 처해 있었다. 그런데 예상치 못한 반가운 소식이 들려왔다. OpenGL ARB 회원사인 3D랩에서 OpenGL의 현 위기상황을 타개할 목적으로 OpenGL 2.0에 대한 제안을 들고 나온 것이다.
OpenGL 커뮤니티에서는 당장에 긍정적인 반응들이 나타났고 OpenGL의 공식 홈페이지에서 실시된 온라인 여론 조사에서도 2.0에 대한 열렬한 지지를 확인할 수 있었다. 3D랩은 2001년 9월에 열린 ARB회의에서 처음으로 2.0에 대해 공식적으로 제안을 했고, 11월에는 OpenGL 공식 홈페이지에 공개적으로 2.0 제안에 대한 소식
이 게시되면서 본격적인 논의가 진행되었다. 원래 제안된 일정에 의하면 2002년 8월 SIGGRAPH에서 2.0 버전의 공개 초안의 발표를 목표로 했지만 ARB에서의 논의가 늑장을 부리고 있는 사이에 2002년 12월에 다이렉트X 9.0의 발표가 먼저 이루어졌고, 현재 2.0에 대한 논의는 2002년을 넘기고 어느새 2003년에 접어들고 말았다.
특히 ARB에서 OpenGL 2.0에 대한 논의가 진행되는 중에 역시 ARB의 회원사인 MS가 버텍스 셰이더와 픽셀 셰이더의 기능에 대해서 자사의 특허권을 주장하고 나와서 찬물을 끼얹었다. 물론 MS의 의도는 힘들게 확보한 다이렉트X의 우위를 지키자는 것이지만, OpenGL의 반격 작전 회의에 적이 함께 끼어서 딴지를 걸고 있는 상
황이니 OpenGL의 반격 작전이 순탄하지 않을 것으로 예상된다.

#

HLSL이 필요한 이유

OpenGL 2.0 제안은 하드웨어의 프로그래밍 가능한 기능들을 표준스펙으로 수용하고, 최신의 하드웨어의 성능을 효율적으로 사용할 수 있도록 하는 등 여러 가지 개선 목표를 가지고 있지만 가장 핵심적인
내용은 바로 하드웨어 독립적인 고수준 셰이딩 언어(HLSL, High Level Shading Language)의 지원이다.
엔비디아의 지포스3를 통해서 프로그래밍 가능한 렌더링 파이프라인 기능이 처음으로 지원되긴 했지만 프로그래밍은 저수준의 어셈블리 언어로만 가능했고, 이 기능의 원활한 보급에 주된 난관이기도 했다. 프로그래밍 가능한 렌더링 파이프라인 기능의 지원은 하드웨어 제조사의 고유한 OpenGL 확장이나 다이렉트X 8.x를 통해서 지원되고 있었지만 곧 고수준 셰이딩 언어의 필요성이 대두되었다. 다음은 다이렉트X 8.0을 기준으로 작성된 간단한 버텍스 셰이더 프로그램이다.

<DIV class=geshi>

<LI class=li1>

<DIV class=de1>; Transforms a vertex to homogenoeous clip space</DIV>

<LI class=li1>

<DIV class=de1>; and lights it with a single directional light in</DIV>

<LI class=li1>

<DIV class=de1>world space, then outputs a texture coordinate.</DIV>

<LI class=li1>

<DIV class=de1>; – <SPAN class=nu0>7</SPAN> instructions</DIV>

<LI class=li1>

<DIV class=de1>; Note that negative output colors will be clamped</DIV>

<LI class=li1>

<DIV class=de1>; by the implicit <SPAN class=nu0>0</SPAN><SPAN class=nu0>-1</SPAN> clamp after the shader</DIV>

<LI class=li1>

<DIV class=de1>vs<SPAN class=nu0>.1</SPAN><SPAN class=nu0>.0</SPAN></DIV>

<LI class=li1>

<DIV class=de1>; Transform to clip space</DIV>

<LI class=li1>

<DIV class=de1>dp4 oPos.<SPAN class=me1>x</SPAN>, v0, c0</DIV>

<LI class=li1>

<DIV class=de1>dp4 oPos.<SPAN class=me1>y</SPAN>, v0, c1</DIV>

<LI class=li1>

<DIV class=de1>dp4 oPos.<SPAN class=me1>z</SPAN>, v0, c2</DIV>

<LI class=li1>

<DIV class=de1>dp4 oPos.<SPAN class=me1>w</SPAN>, v0, c3</DIV>

<LI class=li1>

<DIV class=de1>; Dot normal with light direction in world space</DIV>

<LI class=li1>

<DIV class=de1>dp3 r0, v1, c4</DIV>

<LI class=li1>

<DIV class=de1>; Calculate color intensity</DIV>

<LI class=li1>

<DIV class=de1>mul oD0, r0.<SPAN class=me1>x</SPAN>, c5</DIV>

<LI class=li1>

<DIV class=de1>; Output texture coordinates</DIV>

<LI class=li1>

<DIV class=de1>mov oT0, v2</DIV>

<LI class=li1>

<DIV class=de1> </DIV>

<LI class=li1>

<DIV class=de1> </DIV></LI></DIV>

지면 관계상 아주 간단한 셰이더 프로그램을 예로 들어서 언뜻 쉽게 보일지 모르지만, 이처럼 저수준의 어셈블리어로 작성해야 하기 때문에 어셈블리 언어에 경험이 없는 개발자들에게는 매우 부담되는 일이다. 다이렉트X의 경우 8.0에서 처음으로 셰이더 프로그램을 지원하기 시작했지만, 특정 하드웨어에 의존적인 어셈블리어로 작성해야 하는 방식이었기 때문에 곧이어 ATI도 이 기능을 지원하는 새로운 하드웨어를 발표하자 ATI의 하드웨어에 맞추기 위해서 8.1 버전을 또 발표해야 했었다. 다양한 하드웨어를 표준적인 방식으로 인터페이스해 줘야 할 API가 개별 하드웨어에 맞추느라 춤을 춘 격이 된 셈이다. 결국 이런 문제는 하드웨어 독립적인 고수준 셰이딩 언어의 필요성을 여실히 보여준 것이다. 그래서 OpenGL 2.0 제안에는 C 언어와 유사한 문법의 고수준 셰이딩 언어에 대한 제안이 가장 중요한 이슈였고, OpenGL 공식 홈페이지를 통해서 HLSL에 대한 온라인 여론 조사가 이루어지기도 했다.

#

엔비디아는 Cg 선보여 이러한 문제에 대한 실질적인 해결책을 가장 먼저 제시한 곳은 바로 프로그래밍 가능한 그래픽 하드웨어를 가장 먼저 시장에 선보였던 엔비디아였다. 엔비디아는 Cg(C for graphic)라는 C와 유사한 문법의 하드웨어 독립적인 고수준 셰이딩 언어를 발표하면서 이 분야에서도 앞서가기 시작했다. Cg는 대상 플랫폼의 프로파일만 선택해주면 동일한 Cg 소스를 컴파일러를 통해서 다이렉트X는 물론 자사 고유의 OpenGL 확장과 ARB 확장에 호환되는 셰이더 프로그램으로 번역해줘서 개발자들의 부담을 덜어주게 된다. 다음은 Cg로 작성된 간단한 셰이더 프로그램이다.

<DIV class=geshi>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Define inputs from application.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>struct</SPAN> appin</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 Position : POSITION;</DIV>

<LI class=li1>

<DIV class=de1>float4 Normal : NORMAL;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Define outputs from vertex shader.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>struct</SPAN> vertout</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 HPosition : POSITION;</DIV>

<LI class=li1>

<DIV class=de1>float4 Color : COLOR;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>vertout main<SPAN class=br0>(</SPAN>appin IN,</DIV>

<LI class=li1>

<DIV class=de1>uniform float4x4 ModelViewProj,</DIV>

<LI class=li1>

<DIV class=de1>uniform float4x4 ModelViewIT,</DIV>

<LI class=li1>

<DIV class=de1>uniform float4 LightVec<SPAN class=br0>)</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1>vertout OUT;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform vertex position into homogenous clip-space.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>OUT.<SPAN class=me1>HPosition</SPAN> = mul<SPAN class=br0>(</SPAN>ModelViewProj, IN.<SPAN class=me1>Position</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform normal from model-space to view-space.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 normalVec = normalize<SPAN class=br0>(</SPAN>mul<SPAN class=br0>(</SPAN>ModelViewIT,IN.<SPAN class=me1>Normal</SPAN><SPAN class=br0>)</SPAN>.<SPAN class=me1>xyz</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Store normalized light vector.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 lightVec = normalize<SPAN class=br0>(</SPAN>LightVec.<SPAN class=me1>xyz</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Calculate half angle vector.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 eyeVec = float3<SPAN class=br0>(</SPAN><SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>1.0</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>float3 halfVec = normalize<SPAN class=br0>(</SPAN>lightVec + eyeVec<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Calculate diffuse component.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>float</SPAN> diffuse = dot<SPAN class=br0>(</SPAN>normalVec, lightVec<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Calculate specular component.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>float</SPAN> specular = dot<SPAN class=br0>(</SPAN>normalVec, halfVec<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Use the lit function to compute lighting vector from</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// diffuse and specular values.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 lighting = lit<SPAN class=br0>(</SPAN>diffuse, specular, <SPAN class=nu0>32</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Blue diffuse material</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 diffuseMaterial = float3<SPAN class=br0>(</SPAN><SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>1.0</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// White specular material</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 specularMaterial = float3<SPAN class=br0>(</SPAN><SPAN class=nu0>1.0</SPAN>, <SPAN class=nu0>1.0</SPAN>, <SPAN class=nu0>1.0</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Combine diffuse and specular contributions and</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// output final vertex color.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>OUT.<SPAN class=me1>Color</SPAN>.<SPAN class=me1>rgb</SPAN> = lighting.<SPAN class=me1>y</SPAN> * diffuseMaterial + lighting.<SPAN class=me1>z</SPAN> * specularMaterial;</DIV>

<LI class=li1>

<DIV class=de1>OUT.<SPAN class=me1>Color</SPAN>.<SPAN class=me1>a</SPAN> = <SPAN class=nu0>1.0</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw1>return</SPAN> OUT;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN></DIV>

<LI class=li1>

<DIV class=de1> </DIV></LI></DIV>

보다시피 C 언어와 매우 유사한 형태로 프로그래밍이 가능하다. 어셈블리 셰이더 프로그램에 비해서 작성도 쉽고, 디버깅에도 유리하다. 현재 엔비디아의 Cg를 비롯하여 몇 가지 C 문법과 유사한 고수준 셰이딩 언어가 있는데, 다이렉트X 9.0의 발표와 함께 여기에 하나가 더 추가되었다. 이렇게 해서 PC 플랫폼에는 실질적으로 세 가지 HLSL이 경합을 벌이게 되었다. 엔비디아의 Cg, OpenGL 2.0 HLSL, 다이렉트X 9.0 HLSL들은 사실 각 언어의 문법만으로는 어떤 게 더 우수하다고 쉽게 단언하기 힘들다. 실질적인 경쟁은 지원되는 툴에 의해서 판가름이 날 것으로 예상된다. 1차적으로 각 컴파일러의 최적화 성능이 중요한 이슈가 될 것이고, 디버거 등의 주변 개발툴들도 우열을 가리는 데 중요한 역할을 할 것이다. 이번에 발표된 다이렉트X 9.0의 경우 SDK에는 비주얼 스튜디오 닷넷 환경에서 돌아가는 셰이더 디버거가 포함되어 있다. MS가 개발 툴의 중요성을 잘 인식하고 있는 것이다. 다음은 다이렉트X 9.0의 HLSL로 작성된 간단한 셰이더 프로그램이다.

<DIV class=geshi>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Declare the required matrices with the appropriate semantics</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// so that the viewer can supply the necessary matrix information.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4x4 mProjection : PROJECTION;</DIV>

<LI class=li1>

<DIV class=de1>float4x3 mWorldView : WORLDVIEW;</DIV>

<LI class=li1>

<DIV class=de1>float4x4 mViewProjection : VIEWPROJECTION;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Declare data used by the shaders that the application can modify.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 vLightDirection = <SPAN class=br0>{</SPAN><SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>-1.0</SPAN> <SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>float</SPAN> vDisplace = <SPAN class=nu0>0.015</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>float4 vGlowColor = <SPAN class=br0>{</SPAN> <SPAN class=nu0>0.5</SPAN>, <SPAN class=nu0>0.2</SPAN>, <SPAN class=nu0>0.2</SPAN>, <SPAN class=nu0>1.0</SPAN> <SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>float4 vGlowAmbient = <SPAN class=br0>{</SPAN> <SPAN class=nu0>0.2</SPAN>, <SPAN class=nu0>0.2</SPAN>, <SPAN class=nu0>0.0</SPAN>, <SPAN class=nu0>0.0</SPAN> <SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Set up an output structure defining the output to the pixel shader.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>struct</SPAN> VS_OUTPUT_TEXCOORD0</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 Position : POSITION;</DIV>

<LI class=li1>

<DIV class=de1>float4 Diffuse : COLOR;</DIV>

<LI class=li1>

<DIV class=de1>float2 Texture0 : TEXCOORD0;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Helper function to transform position/normal into view space</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>void</SPAN> TransformUnskinned</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>(</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 vPos,</DIV>

<LI class=li1>

<DIV class=de1>float3 vNormal,</DIV>

<LI class=li1>

<DIV class=de1>float3 vTransformedPosition,</DIV>

<LI class=li1>

<DIV class=de1>float3 vTransformedNormal</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>)</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform the position</SPAN></DIV>

<LI class=li1>

<DIV class=de1>into view space</DIV>

<LI class=li1>

<DIV class=de1>vTransformedPosition = mul<SPAN class=br0>(</SPAN>vPos, mWorldView<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform the normal into view space (just use the upper 3×3 of WorldView)</SPAN></DIV>

<LI class=li1>

<DIV class=de1>vTransformedNormal = mul<SPAN class=br0>(</SPAN>vNormal, <SPAN class=br0>(</SPAN>float3x3<SPAN class=br0>)</SPAN>mWorldView<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Draws unskinned object with one texture and one directional light.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>VS_OUTPUT_TEXCOORD0 Unskinned</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>(</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float4 vPos : POSITION,</DIV>

<LI class=li1>

<DIV class=de1>float3 vNormal : NORMAL,</DIV>

<LI class=li1>

<DIV class=de1>float2 vTexCoord0 : TEXCOORD0</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>)</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1>float3 vTransformedPosition = <SPAN class=br0>{</SPAN><SPAN class=nu0>0</SPAN>,<SPAN class=nu0>0</SPAN>,<SPAN class=nu0>0</SPAN><SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>float3 vTransformedNormal = <SPAN class=br0>{</SPAN><SPAN class=nu0>0</SPAN>,<SPAN class=nu0>0</SPAN>,<SPAN class=nu0>0</SPAN><SPAN class=br0>}</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>VS_OUTPUT_TEXCOORD0 Output;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>float</SPAN> fDot;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform the position/normal into view space.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>TransformUnskinned<SPAN class=br0>(</SPAN>vPos, vNormal, vTransformedPosition, vTransformedNormal<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Calculate amount of light from the one light direction.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>fDot = dot<SPAN class=br0>(</SPAN>vTransformedNormal, vLightDirection<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform view space position into screen space.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>Output.<SPAN class=me1>Position</SPAN> = mul<SPAN class=br0>(</SPAN>float4<SPAN class=br0>(</SPAN>vTransformedPosition, <SPAN class=nu0>1.0</SPAN><SPAN class=br0>)</SPAN>, mProjection<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Multiple amount of light times light color.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Note: Color could be negative with this equation, but will be</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// clamped to zero before pixel shader.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>Output.<SPAN class=me1>Diffuse</SPAN> = float4<SPAN class=br0>(</SPAN><SPAN class=nu0>1</SPAN>.0f, <SPAN class=nu0>1</SPAN>.0f, <SPAN class=nu0>1</SPAN>.0f, <SPAN class=nu0>1</SPAN>.0f<SPAN class=br0>)</SPAN> * fDot;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Just copy the texture coordinate through.</SPAN></DIV>

<LI class=li1>

<DIV class=de1>Output.<SPAN class=me1>Texture0</SPAN> = vTexCoord0;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw1>return</SPAN> Output;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN></DIV></LI></DIV>

보면 알 수 있지만 엔비디아의 Cg와 매우 흡사하다는 걸 알 수 있다. 그리고 Cg의 매뉴얼이나 OpenGL 2.0의 매뉴얼과 굳이 비교하지 않더라도 다이렉트X 9.0의 SDK 매뉴얼에 있는 HLSL에 대한 내용은 매우 부실한데, 예제도 달랑 두 개에다 간단한 언어 문법 설명이 전부이다. 일정에 쫓겨서 그렇게 된 것 같지는 않고, Cg가 사실은 엔비디아와 MS의 공동 개발품이라는 데서 그 이유를 짐작할 수 있을듯 하다. 아마도 MS는 Cg에 더 비중을 두고 다이렉트X 9.0에 포함된 HLSL은 단지 구색 갖추기 정도로 둘 모양이다. Cg, OpenGL 2.0 HLSL, 다이렉트X 9.0 HLSL의 3파전 현재 OpenGL 2.0의 HLSL은 아직 정식 표준으로 정해진 상태도 아니고 ARB에서 계속 논의 중인 상황이라 유동적이긴 하지만 3D랩의 제안에서 약간의 세부적인 사항을 변경하는 것 외에 큰 변화는 없을것으로 예상된다. 이미 3D랩은 컴파일러의 소스를 공개한 상태이고, 이드 소프트웨어의 존 카멕이 둠 III의 개발에 OpenGL 2.0의 셰이딩 언어 지원을 공개적으로 천명했기 때문에 큰 힘이 될 것이다. 과거 OpenGL이 게임 개발자들 사이에서 널리 사용되는데도 존 카멕의 OpenGL 지원 결정이 지대한 영향을 미쳤듯이 이번에도 OpenGL 2.0의 미래에 대해서 주저하는 이들에게 긍정적인 영향을 끼칠 것이 다. OpenGL 2.0의 HLSL도 역시 C와 유사한 문법으로 되어 있다. 다음은 그 간단한 프로그램 예이다.

<DIV class=geshi>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Functions defined in next example.</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>void</SPAN> main <SPAN class=br0>(</SPAN><SPAN class=kw4>void</SPAN><SPAN class=br0>)</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>{</SPAN></DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw4>float</SPAN> normalDotVP, normalDotHalfVector, powerFactor;</DIV>

<LI class=li1>

<DIV class=de1>vec3 color, normal, diffuseLight, specularLight;</DIV>

<LI class=li1>

<DIV class=de1>vec4 position;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=co1>// Transform vertex to clip space</SPAN></DIV>

<LI class=li1>

<DIV class=de1>gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;</DIV>

<LI class=li1>

<DIV class=de1>normal = gl_NormalMatrix * gl_Normal;</DIV>

<LI class=li1>

<DIV class=de1>normalDotVP = min <SPAN class=br0>(</SPAN><SPAN class=nu0>0</SPAN>, dot <SPAN class=br0>(</SPAN>normal, gl_Light0<SPAN class=br0>[</SPAN>gl_kPosition<SPAN class=br0>]</SPAN><SPAN class=br0>)</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>normalDotHalfVector = min <SPAN class=br0>(</SPAN><SPAN class=nu0>0</SPAN>, dot <SPAN class=br0>(</SPAN>normal, gl_Light0<SPAN class=br0>[</SPAN>gl_kHalfVector<SPAN class=br0>]</SPAN><SPAN class=br0>)</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw1>if</SPAN> <SPAN class=br0>(</SPAN>normalDotVP == <SPAN class=nu0>0.0</SPAN><SPAN class=br0>)</SPAN></DIV>

<LI class=li1>

<DIV class=de1>powerFactor = <SPAN class=nu0>0.0</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=kw1>else</SPAN></DIV>

<LI class=li1>

<DIV class=de1>powerFactor = pow <SPAN class=br0>(</SPAN>normalDotHalfVector,</DIV>

<LI class=li1>

<DIV class=de1>gl_FrontMaterial<SPAN class=br0>[</SPAN>gl_kSpecularExponent<SPAN class=br0>]</SPAN>.<SPAN class=me1>y</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>diffuseLight = gl_Light0<SPAN class=br0>[</SPAN>gl_kDiffuseIntensity<SPAN class=br0>]</SPAN> * vec3 <SPAN class=br0>(</SPAN>normalDotVP<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>specularLight = gl_Light0<SPAN class=br0>[</SPAN>gl_kSpecularIntensity<SPAN class=br0>]</SPAN> * vec3 <SPAN class=br0>(</SPAN>powerFactor<SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>color = gl_Light0<SPAN class=br0>[</SPAN>gl_kAmbientIntensity<SPAN class=br0>]</SPAN>*gl_FrontMaterial<SPAN class=br0>[</SPAN>gl_kAmbientIntensity<SPAN class=br0>]</SPAN> +</DIV>

<LI class=li1>

<DIV class=de1>diffuseLight * gl_FrontMaterial<SPAN class=br0>[</SPAN>gl_kDiffuseIntensity<SPAN class=br0>]</SPAN> +</DIV>

<LI class=li1>

<DIV class=de1>specularLight * gl_FrontMaterial<SPAN class=br0>[</SPAN>gl_kSpecularIntensity<SPAN class=br0>]</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1>gl_FrontColor = clamp<SPAN class=br0>(</SPAN>vec4 <SPAN class=br0>(</SPAN>color, gl_FrontMaterial<SPAN class=br0>[</SPAN>gl_kDiffuseAlpha<SPAN class=br0>]</SPAN>.<SPAN class=me1>x</SPAN><SPAN class=br0>)</SPAN>, <SPAN class=nu0>0</SPAN>, <SPAN class=nu0>1</SPAN><SPAN class=br0>)</SPAN>;</DIV>

<LI class=li1>

<DIV class=de1><SPAN class=br0>}</SPAN></DIV></LI></DIV>

세 가지 HLSL로 작성된 셰이더 프로그램들을 살펴보았는데, 모두 C 언어와 유사한 문법을 채용했기 때문에 근본적인 차이점은 찾을수 없다. 앞으로의 경쟁은 언어의 스펙보다는 지원되는 각종 개발 툴들의 성능과 편의성이 좌우할 것이다.

#

3D 프로그래밍의 신나는 도전요즘 이렇게 대두되고 있는 고수준 셰이딩 언어의 원조 격은‘토이스토리’로 유명한 픽사의 렌더맨이다. 렌더맨은 렌더링 파이프라인


전 과정을 완전히 프로그래밍할 수 있는 강력한 고수준 셰이딩 언어로서 프로그래밍 가능한 하드웨어 개발의 모티브가 되었고 최근에 등장한 HLSL들의 이상적인 모델이기도 하다. 하지만 오래 전에 개발된 것으로 실시간 렌더링을 목표로 하는 요즘의 최신 하드웨어에 적용하기는 좀 곤란하다. 현재 PC 플랫폼의 3D 가속 칩들의 프로그래밍 기능은 렌더맨처럼 모든 렌더링 과정을 대상으로 하지는 않는다. 변경의 여지가 없는 정형화된 기능들은 고정된 방식을 그대로 사용하고, 버텍스의 좌표를 변환하고 조명을 계산하는 소위 T&L 기능과 여러 장의 텍스처 맵으로부터 가져온 픽셀 데이터를 다양한 방법의 연산으로 처리하는 기능에 프로그램 기능을 집중하고 있다. <그림 1>은 다이렉트3D의 렌더링 파이프라인이다. 보다시피 버텍스 셰이더와 픽셀 셰이더는 전체 파이프라인의 아주 일부인 것처럼 보이지만 최종적인 결과 영상에 결정적인 영향을 끼치는 가장 중요한 부분이다. 그래서 앞으로 이러한 프로그래밍 기능들은 HLSL의 활용으로 더욱 보편화될 것이고, 많은 3D 프로그래머들에게 신나는 도전의 영역이 될 것이다. 결국 이번 다이렉트X 9.0의 발표는 실질적으로 프로그래밍 가능한 렌더링 파이프라인 기능을 최신 하드웨어에 맞추어서 업그레이드하고, HLSL을 도입한 것이 주된 변화이고 나머지 변화들은 사실 사소한 것들이다. 물론 OpenGL 2.0의 경우도 실질적으로 동일한 기능들이 프로그래밍 가능한 기능들로 채워지는 것이다.

#

멀티미디어 영역에서도 경합마지막으로 OpenGL 2.0의 빼놓을 수 없는 한 가지 중요한 변화는 그동안 3D API로만 머물러 있던 OpenGL이 OpenML과의 결합을 통해 3D 그래픽은 물론 동영상과 사운드를 비롯한 멀티미디어의 전반적인 기능을 지원하는 API로 발전하려 한다는 것이다. 현재 OpenML은 3D랩, ATI, 디스크리트, 에반스 앤 서더랜드, 인텔, 엔비디아, SGI, 썬 마이크로시스템즈 등이 크로노스 그룹이라는 이름으로 연합해서 멀티미디어 데이터 처리를 위한 개방형 표준을 제정한 상태이다. 기존에 PC 플랫폼에서 게임을 개발할 경우 3D 그래픽 처리는 OpenGL을 사용한다 하더라도 나머지 동영상이나 사운드 처리는 대개 다이렉트X에 의존했는데, OpenGL 2.0이 3D랩의 제안대로 OpenML과도 긴밀히 결합하게 되면 다이렉트X에는 더욱 큰 위협이 되는 것이다. 사실 이번에 다이렉트쇼(DirectShow)에 추가된 새로운 기능도 3D 영상과 동영상을 결합할 수 있는 VMR9 기능이다. 이제 OpenGL과 다이렉트X 간의 3D API 전쟁은 전장을 넓혀서 멀티미디어쪽으로도 확장될 태세이다. 앞으로의 3D API 전쟁이 어떻게 전개될 것인지는 두고 볼 일이지만 개발자들이 좀더 편리하고 개방된 개발환경을 누릴 수 있는 방향으로 발전하기를 바랄 따름이다.