3D 환경맵핑(Environment Mapping)의 한 기법으로 Cube Maps가 있다. 말그대로 직육면체의 각면에 대한 텍스쳐 이미지로 3D 환경을 조성하는 방법인데 아래 이미지를 보면 바로 이해할 수 있다.
출처 : developer.com
Cube Maps는 환경 맵핑 방법중 가장 빠르고 매우 사실적으로 묘사할 수 있는 방법으로 내가 알기로는 게임등에 자주 사용하는 것으로 알고 있다. 단점은 모서리 부분에서 약간 티가 난다는 점이다.
Flash Player 10부터 3D API를 제공한다. 이 API들을 적절히 활용하면 Papervision3D나 Away3D와 같은 라이브러리를 이용하지 않고도 어려움 없이 Cube Maps을 구현할 수 있다.
아래 프로그램은 Flash Player 10에서 제공하는 3D API만 이용해서 Cube Map을 구현한 것이다. 마우스 드래그로 회전이 가능하다.
마우스로 Drag하면 회전이 됩니다.
(Mac에서 화면이 작게 나오는데 아직 이유를 모르겠네요 ^^;)
위와 같은 프로그램을 만들기 위해 6면을 가지는 텍스쳐 이미지가 필요하다. 인터넷에 많이 있는데 아래처럼 Layout이 여러종류가 있다.
Cube Map Layout 출처 : cgtextures.com
나는 가장 일반적이고 자주 쓰이는 Horizontal Cross방식 대신에 Horizontal Strip(NVidia DDS Exporter Layout)을 이용했다. 이 방식은 아래같이 구성된다.
Cube Map Layout 출처 : cgtextures.com
이러한 이미지가 어떻게 Cube Map을 만들 수 있는지 아래 화면을 보면 쉽게 알 수 있다.
마우스로 Drag하면 회전이 됩니다.
아래는 위 프로그램에 대한 소스이다.
package { import flash.display.*; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.*; import flash.utils.getTimer; [SWF(frameRate=60, backgroundColor=0x000000)] /** * Cube Map 예제 * @author Yongho, Ji (http://blog.jidolstar.com/574) * @since 2009.08.05 */ public class CubeSky extends Sprite { [Embed(source="sky.jpg")] private const SKY:Class; //투영된 Vectex 정보 private var projected:Vector.<Number>; //World 변환 행렬 private var world:Matrix3D; //투영을 위한 변환행렬 private var projection:Matrix3D; //Mesh 데이터 private var mesh:GraphicsTrianglePath //Texture private var texture:BitmapData = (new SKY as Bitmap).bitmapData; //Viewport (3D 렌더링 대상) private var viewport:Shape; //Viewport의 Z축 위치 private var viewPortZAxis:Number = 0; public function CubeSky() { //Viewport viewport = new Shape; addChild( viewport ); //투영 변환 행렬 var p:PerspectiveProjection = new PerspectiveProjection() p.fieldOfView = 30; projection = p.toMatrix3D(); //World 변환 행렬 world = new Matrix3D(); var check3D:Vector3D = Utils3D.projectVector( projection, new Vector3D(1.0,0,1.0) ); //Mesh 데이타 mesh = createCubeMesh( check3D.x ); projected = new Vector.<Number>(0,false); viewPortZAxis = check3D.x;// * 4; //이벤트 핸들러 등록 stage.addEventListener( Event.RESIZE, resize ); stage.addEventListener( Event.ENTER_FRAME, render ); //stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel ); stage.addEventListener( MouseEvent.MOUSE_DOWN, onMouseEvent ); resize(); } private function resize( event:Event = null ):void { viewport.x = stage.stageWidth/2; viewport.y = stage.stageHeight/2; } private function render( event:Event = null ):void { world.identity(); //단위행렬로 전환 world.appendRotation( zRotation, Vector3D.Z_AXIS ); world.appendRotation( 90+xRotation, Vector3D.X_AXIS ); world.appendTranslation(0, 0, viewPortZAxis); //이동 world.append(projection); //투영 변환 적용 // mesh 데이터를 투영하여 projected 생성 // uvtData도 갱신된다. 갱신되는 데이터는 T값이다. Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData ); // texture를 이용해 렌더링 viewport.graphics.clear(); //viewport.graphics.lineStyle( 1, 0xff0000 ); viewport.graphics.beginBitmapFill( texture, null, false, true ); viewport.graphics.drawTriangles( projected, mesh.indices, mesh.uvtData, mesh.culling ); //Cube는 z축을 굳이 정렬할 필요 없다. //viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling ); } /** * 마우스 휠 처리 */ private function onMouseWheel( event:MouseEvent ):void { viewPortZAxis += event.delta * 10; } private var xRotation:Number = 0; private var zRotation:Number = 0; private var prevX:Number; private var prevY:Number; /** * 마우스 이벤트 - x,y축 회전 */ private function onMouseEvent( event:MouseEvent ):void { switch( event.type ) { case MouseEvent.MOUSE_DOWN: stage.addEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent ); stage.addEventListener( MouseEvent.MOUSE_UP, onMouseEvent ); prevX = mouseX; prevY = mouseY; break; case MouseEvent.MOUSE_MOVE: if( event.buttonDown == false ) { stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent ); stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent ); break; } var dx:Number = mouseX - prevX; var dy:Number = mouseY - prevY; zRotation += dx; xRotation += dy; if( xRotation > 90 ) xRotation = 90; if( xRotation < -90 ) xRotation = -90; prevX = mouseX; prevY = mouseY; break; case MouseEvent.MOUSE_UP: stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent ); stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent ); break; } } } } import flash.display.GraphicsTrianglePath; import flash.display.TriangleCulling; /** * Cube Mesh 데이타 작성 * @param size 한변의 사이즈 * @return mesh 데이터 */ function createCubeMesh( size:Number = 512 ):GraphicsTrianglePath { var vertices:Vector.<Number> = new Vector.<Number>( 0, false ); var indices:Vector.<int> = new Vector.<int>( 0, false ); var uvtData:Vector.<Number> = new Vector.<Number>( 0, false ); var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.POSITIVE ); var s:Number = size/2; var w:Number = size * 6; var u00:Number = 0; var u01:Number = (size-1)/w; var u10:Number = size/w; var u11:Number = (size*2-1)/w; var u20:Number = (size*2)/w; var u21:Number = (size*3-1)/w; var u30:Number = (size*3)/w; var u31:Number = (size*4-1)/w; var u40:Number = (size*4)/w; var u41:Number = (size*5-1)/w; var u50:Number = (size*5)/w; var u51:Number = (size*6-1)/w; mesh.vertices.push( //Right s,-s,-s, //0 s,-s,s, //1 s,s,s, //2 s,s,-s, //3 //Left -s,s,-s, //4 -s,s,s, //5 -s,-s,s, //6 -s,-s,-s, //7 //Top -s,-s,s, //8 -s,s,s, //9 s,s,s, //10 s,-s,s, //11 //Bottom -s,s,-s, //12 -s,-s,-s, //13 s,-s,-s, //14 s,s,-s, //15 //Front -s,-s,-s, //16 -s,-s,s, //17 s,-s,s, //18 s,-s,-s, //19 //Back s,s,-s, //20 s,s,s, //21 -s,s,s, //22 -s,s,-s //23 ); mesh.indices.push( 0,1,2, 0,2,3, //Right 4,5,6, 4,6,7, //Left 8,9,10, 8,10,11, //Top 12,13,14, 12,14,15, //Bottom 16,17,18, 16,18,19, //Front 20,21,22, 20,22,23 //Back ); mesh.uvtData.push( //Right u00, 1, 1, u00, 0, 1, u01, 0, 1, u01, 1, 1, //Left u10, 1, 1, u10, 0, 1, u11, 0, 1, u11, 1, 1, //Top u20, 1, 1, u20, 0, 1, u21, 0, 1, u21, 1, 1, //Bottom u30, 1, 1, u30, 0, 1, u31, 0, 1, u31, 1, 1, //Front u40, 1, 1, u40, 0, 1, u41, 0, 1, u41, 1, 1, //Back u50, 1, 1, u50, 0, 1, u51, 0, 1, u51, 1, 1 ); return mesh; } /** * GraphicsTrianglePath를 기반으로, Z축으로 sort된 인덱스를 돌려준다. * 이 작업을 해주어야 z축 깊이에 따라 Triangle이 제대로 그려진다. * @param mesh 정보 * @return sort된 index 데이터 */ function getSortedIndices( mesh:GraphicsTrianglePath ):Vector.<int> { var triangles:Array = []; var length:uint = mesh.indices.length; //z축 sort를 위한 기반 제작 for ( var i:uint=0; i < length; i += 3 ) { var i1:uint = mesh.indices[ i+0 ]; var i2:uint = mesh.indices[ i+1 ]; var i3:uint = mesh.indices[ i+2 ]; var z:Number = Math.min( mesh.uvtData[i1 * 3 + 2], mesh.uvtData[i2 * 3 + 2], mesh.uvtData[i3 * 3 + 2] ); if (z > 0) { triangles.push({i1:i1, i2:i2, i3:i3, z:z}); } } //z축으로 sort triangles = triangles.sortOn("z", Array.NUMERIC); //sort된 값을 이용해 Vector값 만듬 var sortedIndices:Vector.<int> = new Vector.<int>(0, false); for each (var triangle:Object in triangles) { sortedIndices.push(triangle.i1, triangle.i2, triangle.i3); } return sortedIndices; }
아래 이미지는 위 프로그램에서 사용한 이미지이다.
위 프로그램에서는 Graphics.drawTriangle() 메소드를 이용했다. 하지만 이 함수를 사용하지 않고도 만들수 있다. 왜냐하면 Flash Player 10부터는 DisplayObject에 대해 x,y,z축 회전 및 이동 API가 추가되었기 때문이다. 그러면 위 프로그램 소스처럼 힘들게 vertex, index, uvt 데이타를 만들지 않아도 될 것이다.
내 생각에 Flash에서 3D 개발을 위해 Graphics.drawTriangle()와 DisplayObject의 3D API, Matrix3D 등을 서로 섞어가면서 만드는 것이 좋을 것 같다.
개발환경 : Flash Builder 4 Beta 1 (Flash CS4 에서도 개발할 수 있음.)
참고내용
- 3차원(3D)에서 작업
- Cubical Environments Maps Texture
- Environment Mapping Techniques
- Papervison3D로 만든 Cube Map
- 모모님의 Skybox 강의
- [Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용
- [Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기
- [Flash 3D]토성 그리기
글쓴이 : 지돌스타(http://blog.jidolstar.com/574)
'비공개 > Adobe Flash 3D' 카테고리의 다른 글
[오픈캐스트]Flash 3D 세계 (2) | 2009.08.10 |
---|---|
[Flash 3D]최고의 3D 엔진. Alternativa3D (10) | 2009.08.07 |
[Flash 3D]토성 그리기 (20) | 2009.07.07 |
[Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기 (5) | 2009.07.02 |
[Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용 (24) | 2009.06.30 |