Graphics Programming

[어도비] 프로시니엄(Proscenium) 프레임워크로 작업하기 본문

Season 1/플래시

[어도비] 프로시니엄(Proscenium) 프레임워크로 작업하기

minseoklee 2011. 10. 10. 01:32

어도비에서 직접 작성한 글입니다.
http://www.adobe.com/devnet/flashplayer/articles/working-with-proscenium.html



이 튜토리얼에서는 프로시니엄 프레임워크로 3D 견본 프로젝트를 제작하는 데 쓰일 여러 액션스크립트 클래스 파일을 살펴볼 것입니다. 프로시니엄 프레임워크는 어도비 플래시 플랫폼 Stage3D API에 기반하는 액션스크립트 3 코드 라이브러리로서 인터랙티브한 3D 내용을 빠르게 개발할 수 있습니다. 이번 빠른 소개의 목적은 여러분이 프로시니엄 프레임워크로 어도비 플래시 프로페셔널의 3D 요소를 제어하는 데 익숙해지게 하는 것입니다.

시작하기 전에 어도비 랩스에서 프로시니엄 프레임워크를 내려받고 데스크탑에 ZIP 파일의 압축을 풀어서 튜토리얼의 각 절을 따라할 때 그와 관련된 AS 파일을 열 수 있도록 하십시오. 코드를 분석하여 각 단계가 프로그래밍으로 어떻게 이뤄지는지 분석할 수 있습니다.

견본 프로젝트 클래스 파일들은 프로시니엄 프레임워크 ZIP 파일의 code\ProsceniumTutorials\src 폴더에 있습니다. 이 견본들은 프로시니엄 프로젝트를 구성하고 나면 컴파일하여 실행할 수 있으니 견본 파일들에 포함된 구성 지침을 읽어주시기 바랍니다.

다음의 절들에서는 3D 물체를 만들고 장면에 추가하여 표시하는 방법을 다룹니다. 여러분은 3D 물체의 재질 속성을 바꾸고 물체를 옮기고 그림자를 추가하는 방법도 배울 것입니다. 튜토리얼의 마지막 절에서는 Sprite 클래스를 확장하는 클래스를 사용하여 다른 절들에서 쓰는 BasicDemo 클래스 없이 프로시니엄 애플리케이션을 제작하는 방법을 봅니다. Sprite 클래스를 확장하면 3D 물체를 다루고 이벤트 수신자를 구성할 때 좀 더 많은 제어권을 얻을 수 있습니다.

월드 좌표계와 3D 카메라 이해하기
 
견본 프로젝트를 다루는 것에 대한 지침을 따르고 나면 월드 좌표계의 방향을 기억하십시오. y축은 항상 위를 가리킵니다. 기본으로 카메라는 -z 방향을 가리키며 이는 x축이 화면의 오른쪽을 가리킨다는 것을 뜻합니다(그림 1을 보세요).

그림 1. 월드 좌표계의 방향
 
간단한 3D 도형을 만들고 장면에 추가하기 

몇 가지 도형을 만들고 장면에 추가하는 것으로 시작하겠습니다. 어도비 랩스에서 프로시니엄 프레임워크를 내려받아 여러분의 데스크탑에 ZIP 파일의 압축을 풀었는지 확인하세요. code\ProsceniumTutorials\src 폴더에 있는 Tutorial01_SimpleShapes.as 파일을 플래시 프로페셔널에서 여세요. 이 첫 번째 절의 목표는 3D 장면에 보이는 물체를 만드는 것입니다(그림 2를 보세요).

그림 2. 액션스크립트로 도형을 제작하여 장면에 배치합니다.

1. 플래시 프로페셔널을 실행하고 code\ProsceniumTutorials\src 폴더에 있는Tutorial01_SimpleShapes.as 파일을 여세요.
2. 코드는 매우 직관적입니다. 평면, 정육면체, 구, 도넛(torus)을 생성하고 이들 3D 도형을 장면에 추가합니다.

기본 도형을 만들기 위해 밑의 코드 예제들에서 볼 수 있듯이 ProceduralGeometry 클래스를 사용합니다.

MaterialStandard는 다양한 렌더링 모형을 구현하는 재질 클래스입니다. 이 튜토리얼에서는 녹색 재질을 만들어 평면의 재질로 사용했습니다.
SceneMesh는 메시를 포함하는 기본 장면 그래프 노드입니다. 다른 종류의 장면 그래프 노드로는 SceneLight, SceneCamera, SceneSkyBox 따위가 있습니다. 모든 노드 클래스의 이름은 Scene으로 시작합니다.
BasicDemo.scene은 장면 그래프의 루트 노드입니다. BasicDemo는 알아서 SceneLight 객체를 몇 개 생성하고 카메라 제어에 중요한 이벤트 수신자들을 포함합니다.

도형을 만들고 장면에 추가하는 코드는 아래에 있습니다.

package
{
   import com.adobe.scenegraph.*;
   
   import flash.display.*;
   import flash.display3D.*;
   import flash.geom.*;
   
   public class Tutorial01_SimpleShapes extends BasicDemo
   {
      public function Tutorial01_SimpleShapes()
      {
         super();
      }
      
      override protected function initModels():void
      {
         // 평면 재질을 만듭니다.
         var material:MaterialStandard = new MaterialStandard();
         material.diffuseColor.set( 0, .4, 0 );
         material.specularColor.set( .8, .8, .8 );
         material.ambientColor.set( .2, .2, .2 );

         // 평면을 만들어 장면에 추가합니다.
         var plane:SceneMesh = ProceduralGeometry.createPlane( 50, 50, 20, 20, null, "plane" );
         plane.appendTranslation( 0, -2, 0 );
         scene.addChild( plane );
         
         // 정육면체를 만들어 장면에 추가합니다.
         var cube:SceneMesh = ProceduralGeometry.createCube( 5 );
         cube.appendTranslation( 0, 6, 0 );
         scene.addChild( cube );
         
         // 도넛을 만들어 장면에 추가합니다.
         var torus:SceneMesh = ProceduralGeometry.createDonut( .25, 1.5, 50, 10, null, "torus" );
         torus.appendTranslation( 10, 2, 0 );
         var rotAxis:Vector3D = new Vector3D( 1,1,1 );
         rotAxis.normalize();
         torus.appendRotation( 45, rotAxis );
         scene.addChild( torus );
         
         // 구를 만들어 장면에 추가합니다.
         var sphere:SceneMesh = ProceduralGeometry.createSphere( 3, 50, 50, null, "sphere" );
         sphere.setPosition( -10, 2, 0 );
         scene.addChild( sphere );         
      }
   }
}
 
3D 물체를 애니메이팅하고 재질 속성을 바꾸기 

이 절에서는 정육면체 도형의 인스턴스를 생성하는 방법을 다시 봅니다. 또한 onAnimate() 함수를 사용하여 도형의 위치를 옮기고 위치에 기반해 도형의 재질을 변형하는 방법도 볼 것입니다(그림 3을 보세요).

그림 3. 장면 내 도형의 위치를 옮깁니다.
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial02_AnimationAndMaterial.as 파일을 여세요.
2. 코드를 검토하면 onAnimate() 메서드를 시간 t와 이전 호출로부터 경과한 시간 dt를 넘겨 호출하는 것을 볼 수 있습니다. onAnimate() 메서드에서 여러분은 물체의 위치를 SceneMesh.appendTranslation 메서드를 사용하여 옮길 수 있습니다.

이 예제에서 정육면체는 ProceduralGeometry.createCube 메서드를 사용하여 생성됩니다. 정육면체의 다른 인스턴스는 SceneMesh.instance() 메서드를 사용하여 생성됩니다. 이 전략으로 두 정육면체는 같은 정점 버퍼와 인덱스 버퍼를 공유합니다.

기본적으로 인스턴스화된 큐브는 cubeMtrl이라는 같은 재질을 사용합니다. 이 재질 속성은 SceneMesh.materialBindings를 사용하여 다른 재질로 바꿀 수 있습니다. 여러분은 새 재질을 만들고 인스턴스화한 정육면체에 그 재질을 바인딩하는 것으로 시작합니다. 그 다음에는 인스턴스의 재질 속성을 원하는 대로 변경할 수 있습니다.

override protected function initModels():void
{
   …

   // 새로운 기본 재질을 생성합니다
   var material:MaterialStandard = new MaterialStandard( "cubeMtrl" );
   material.diffuseColor.set( 0, 1, 0 );

   var cube:SceneMesh = ProceduralGeometry.createCube( 5, material, "cube" );
   cube.appendTranslation( 0, 6, -10 );
   scene.addChild( cube );

   // 정육면체의 인스턴스를 생성합니다 (메시 데이터를 공유합니다)
   _cubeInstanced = cube.instance( "cube-instanced" );
   _cubeInstanced.appendTranslation( 0, 6, 0 );
   scene.addChild( _cubeInstanced );

   …
}

// onAnimate에서 애니메이션을 수행합니다
override protected function onAnimate( t:Number, dt:Number ):void
{
   _cubeInstanced.setPosition( Math.cos( t ) * 3, 6, Math.sin( t ) * 3 );
   // SceneMesh가 여러 재질의 복수 하위 메시를 가질 수 있고 하위 메시는
   // 여러 SceneMesh 인스턴스에서 공유할 수 있기 때문에
   // 재질에 직접 접근하는 방법은 제공하지 않습니다.
   // 재질을 변경하려면 새로운 재질을 생성하여 바인딩에 추가할 수 있습니다.
   // 재질 이름은 어떤 재질이 다시 씌워졌는지를 나타내는 데 쓰인다는 것을 숙지하세요. 
   if ( !_cubeInstanced.materialBindings[ "cubeMtrl" ] )
      _cubeInstanced.materialBindings[ "cubeMtrl" ] = new MaterialStandard( "cubeMtrl" );
         
   if ( _cubeInstanced.position.x < 0 )
      _cubeInstanced.materialBindings[ "cubeMtrl" ].diffuseColor.set( 1, 0, 0 );
   else
      _cubeInstanced.materialBindings[ "cubeMtrl" ].diffuseColor.set( 0, 1, 0 );
}
 
빛을 받아 그림자를 드리우는 물체를 정의하여 3D 장면에 그림자 추가하기
 
이 절에서는 빛을 받아 그림자를 드리우는 물체를 넣어봐서, 어떻게 3D 장면에 그림자를 추가할 지를 탐구합니다(그림 4를 보세요).

그림 4. 그림자는 3D 장면에 깊이를 더합니다.
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial03_Shadows.as 파일을 여세요.
2. 이 클래스의 코드를 검토하세요.
 
그림자를 활성화하는 과정은 쉽습니다. BasicDemo.shadowMapEnabled 속성이 설정되어있으면 BasicDemo 클래스의 두 기본 빛이 활성화되어 그림자를 표시합니다. 

다음 단계는 빛을 받아 그림자를 드리울 물체를 정의하는 것입니다. SceneLight.addToShadowMap라는 프로시니엄의 메서드를 사용하여 그렇게 할 수 있습니다. 한 가지 쉬운 방법은 이번 튜토리얼의 light[1]을 갱신하기 위해 BasicDemo.scene을 추가하여 모든 장면 그래프 노드가 이 빛에 그림자가 지도록 만드는 것입니다.

다른 방법은 각 빛마다 그림자를 드리울 특정 물체를 정의하는 것입니다. 예를 들어 이 튜토리얼의 아래 코드에서 lights[0]과 lights[1]을 어떻게 따로 제어하는지 확인하세요. 이것은 대개 좋은 발상인데 장면 내에서 땅 평면이 light[0]에 의해 그림자를 드리우지 않기 때문입니다.

override protected function initModels():void
{
   …

   if ( lights )
   {
      lights[0].setPosition( 10, 20, 10);
      if ( lights[0].shadowMapEnabled )
         lights[0].addToShadowMap( _cubeInstanced, cube, torus, sphere );   // 캐스터를 정의합니다
      if ( lights[1].shadowMapEnabled )
         lights[1].addToShadowMap( scene );   // 모든 장면 그래프 객체를 캐스터로 설정합니다
   }
}

외부 파일로부터 3D 모델 불러오기

이 절에서는 3D 그래픽스 프로그램으로 제작한 3D 모델을 가져오는 방법을 살펴봅니다. 프로시니엄 프레임워크를 써서 외부 파일로부터 3D 모델을 불러올 수 있습니다. 이 예제는 스카이박스 텍스쳐와 3D 모델을 불러오는 방법을 보여줍니다(그림 5를 보세요).

그림 5. 외부 모델 파일을 사용하여 플래시 프로젝트 안으로 3D 모델을 불러올 수 있습니다.
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial04_LoadingModels.as 파일을 여세요.
2. 코드에서 장면 안으로 모델을 불러오는 방법을 검토하세요.

SceneSkyBox라는 객체는 스카이박스를 위해 특별히 제공되는 장면 그래프 노드 객체입니다. 안개(fogging) 옵션을 더 자세히 알려면 TestFog를 보세요.

6면 스카이 박스 표면을 위한 텍스쳐 이미지 파일 이름들은 이렇게 정의되어 있습니다.

protected static const SKYBOX_FILENAMES:Vector.<String> = new Vector.<String>(6,true);
SKYBOX_FILENAMES[ 0 ] = "../res/textures/skybox/px.png";
SKYBOX_FILENAMES[ 1 ] = "../res/textures/skybox/nx.png";
SKYBOX_FILENAMES[ 2 ] = "../res/textures/skybox/py.png";
SKYBOX_FILENAMES[ 3 ] = "../res/textures/skybox/ny.png";
SKYBOX_FILENAMES[ 4 ] = "../res/textures/skybox/pz.png";
SKYBOX_FILENAMES[ 5 ] = "../res/textures/skybox/nz.png";

추가적으로 이 예제는 야자수 모델을 불러옵니다. 스카이박스 텍스쳐와 야자수 모델을 불러오기 위해 코드는 각각에 대해 로더를 실행합니다. 불러오기가 끝나면 각 로더는 이벤트를 보냅니다. 이 견본 프로젝트에서 두 불러오기 작업은 다음 단계를 거쳐 관리됩니다.

1. 스카이 텍스쳐 로더를 실행합니다.
2. 불러오기가 끝나면 imageLoadComplete 수신자를 호출합니다.
3. 이 수신자는 SceneSkyBox 객체를 생성하고 장면에 추가합니다.
4. 야자수 모델 로더를 실행합니다.
5. 야자수 모델 불러오기가 끝나면 loadComplete 수신자를 호출합니다.
6. 이 수신자는 야자수를 장면에 추가하는 코드를 포함합니다. 
 
예제에서 외부 파일을 불러오는 코드를 아래에서 검토하세요. 

override protected function initModels():void
{
   var plane:SceneMesh = ProceduralGeometry.createPlane(100,100,20,20,null, "plane" );
   plane.transform.appendTranslation( 0, -2, 0 );
   scene.addChild( plane );

   LoadTracker.loadImages( SKYBOX_FILENAMES, imageLoadComplete );
}

protected function imageLoadComplete( bitmaps:Dictionary ):void
{
   var bitmapDatas:Vector.<BitmapData> = new Vector.<BitmapData>( 6, true );
   var bitmap:Bitmap;
   for ( var i:uint = 0; i < 6; i++ )
      bitmapDatas[ i ] = bitmaps[ SKYBOX_FILENAMES[ i ] ].bitmapData;
         
   // 하늘
   _sky = new SceneSkyBox( bitmapDatas, false );
   scene.addChild( _sky );   // 스카이박스는 장면 루트의 직접적인 자식이여야 합니다
   _sky.name = "Sky"
         
   _treeLoader = new OBJLoader( "../res/models/PalmTrees/PalmTrees.obj" );
   _treeLoader.addEventListener( Event.COMPLETE, loadComplete);
}

protected function loadComplete( event:Event ):void
{
   var tree:SceneNode = new SceneNode( "PalmTrees" );
   var manifest:ModelManifest = _treeLoader.model.addTo( tree );
   scene.addChild( tree );
}
 
Sprite 클래스를 확장하여 프로시니엄 애플리케이션 제작하기 

이 튜토리얼에서 설명한 이전 예제들은 모두 BasicDemo를 사용하여 이벤트 수신자와 다른 제어 작업을 했습니다. 튜토리얼의 마지막 절인 이번에는 BasicDemo 없이 Sprite 클래스를 곧바로 확장하여 프로시니엄 애플리케이션을 제작하는 방법을 봅니다.

public class Tutorial_SpriteBased  extends Sprite
 
Sprite 클래스를 확장하여 스프라이트를 Stage 객체에 추가할 때 더 많은 제어권을 얻습니다. 또한 Stage3D 컨텍스트를 생성하고 이벤트 수신자를 설정, 구현하는 시기를 더 자유롭게 선택할 수 있습니다(그림 6을 보세요).

그림 6. Sprite 클래스를 확장하여 프로시니엄 애플리케이션을 제작할 수 있습니다.
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial05_SpriteBased.as 파일을 여세요.
2. 클래스에서 Sprite 클래스를 확장하는 방법을 검토하세요.

Instance3D로 프로시니엄 클래스 제작하기

Instance3D는 기본적인 프로시니엄 클래스로서 3D 몰힐 컨텍스트를 저장합니다. 프로시니엄 프레임워크를 사용하고자 한다면 항상 Instance3D 클래스를 쓰게 될 것입니다. 이 예제에서는 다음 코드가 새로운 Instance3D 인스턴스를 생성합니다.

instance = new Instance3D( stage3D.context3D );

주의: BasicDemo 클래스를 쓰는 앞선 예제들에서는 BasicDemo.instance라는 Instance3D 객체를 자동으로 생성하였습니다.

장면 그래프 루트 설정하기

앞선 튜토리얼들에서는 루트의 이름이 scene이였으며 BasicDemo.scene과 함께 설정되었습니다. 하지만 이 예제는 BasicDemo 클래스를 사용하지 않기 때문에 루트는 그저 Instance3D.scene이며 이 튜토리얼의 경우 instance.scene입니다.

주의: Instance3D는 클래스 이름이고 instance는 변수입니다.

애플리케이션 완성하기

장면 그래프 루트를 설정하고 나면 3D 모델을 제작하고 장면에 추가하는 과정은 그 전과 똑같은 단계를 따릅니다. 이번 예제에서는 빛을 만들고 캐스터를 사용하여 그림자를 활성화합니다. 예제에는 사용자 상호작용을 위한 키와 마우스 이벤트 수신자도 있습니다. 이 함수는 안개 효과를 켜기 위한 코드도 포함합니다.
-> 뭔 함수..

물리로 물체들을 떨어지고 부딪히고 튕기게 만들기

이 절에서는 물체를 떨어지고 부딪히고 튕기게 만드는 방법을 살펴봅니다.

그림 7. 물리로 물체들을 떨어지고 부딪히고 튕기게 만듭니다.

1. code\ProsceniumTutorials\src 폴더의 Tutorial06_SimplePhysics.as 파일을 여세요.
2. 이 클래스의 함수들을 검토하세요.
 
물리는 PelletManager 객체를 생성하여 활성화됩니다. 이 객체는 물리가 활성하된 SceneMesh 객체를 생성하는 데 쓰는 메서드를 제공합니다. 예를 들어 땅 평면은 createStaticInfinitePlane을 호출하여 만들 수 있고 정육면체는 createBox, 구는 createSphere로 만들 수 있습니다.

// 평면을 만들어 장면에 추가합니다
var plane:SceneMesh = mSim.createStaticInfinitePlane( 1000, 1000, 2, 2, material, "plane" );
plane.appendTranslation( 0, -2, 0 );
scene.addChild( plane );

// 정육면체를 만들어 장면에 추가합니다
var cube0:SceneMesh = mSim.createBox( 5, 5, 5 );
cube0.appendRotation( 40, Vector3D.X_AXIS );
cube0.appendTranslation( 0, 6, 0 );
scene.addChild( cube0 );

// 구를 만들어 장면에 추가합니다
var sphere:SceneMesh = mSim.createSphere( 3, 32, 16 );
sphere.setPosition( -10, 2, 0 );
scene.addChild( sphere );
 
사변형(quadrilateral)에 텍스쳐 할당하기
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial07_Texture.as 파일을 여세요.
2. 이 클래스의 함수들을 검토하세요.

텍스쳐 맵은 다음과 같이 포함됩니다. 

[Embed( source="/../res/content/foliage022.jpg" )]
protected static const BITMAP:Class;

이 자료를 사용하여 텍스쳐 맵을 만들고 다음 코드에 보이는 바와 같이 diffuse map에 쓸 수 있습니다.

var textureMap:TextureMap = new TextureMap( new BITMAP().bitmapData );
material.diffuseMap = textureMap; 
 
COLLADA 애니메이션 파일 불러오기
 
이 절에서는 COLLADA 애니메이션 파일을 불러오는 코드를 살펴봅니다(그림 8을 보세요). 

그림 8. COLLADA 애니메이션 파일 불러오기
 
1. code\ProsceniumTutorials\src 폴더의 Tutorial08_LoadedAnimation.as 파일을 여세요.
2. 클래스의 함수들을 검토하세요.
 
COLLADA 파일을 불러오려면 이 코드를 참고하세요.

public var loader:ColladaLoader;
override protected function initModels():void {
   loader = new ColladaLoader( "../res/content/AnimatedBones.dae" );
   loader.addEventListener( Event.COMPLETE, onLoad );
}
 
불러오기가 끝나면 모델을 장면에 추가하고 애니메이션을 장면에 바인드합니다. 

public var animations:Vector.<AnimationController>, initialized:Boolean;
public function onLoad( event:Event ):void {
   var manifest:ModelManifest = loader.model.addTo( scene );
   animations = loader.model.animations;
      for each ( var anim:AnimationController in animations ) {
anim.bind( scene );
}
initialized = true;
}
 
애니메이션은 시간을 진행시켜 끝냅니다. 

override protected function onAnimate( t:Number, dt:Number ):void {
   if ( !initialized ) return;
for each ( var anim:AnimationController in animations )
{
 anim.time = ( t % anim.length ) + anim.start;
}
}

procedural geometry를 사용하여 맨바닥에서 메시 만들어내기

1. code\ProsceniumTutorials\src 폴더의 Tutorial_09_ProceduralGeometry.as 파일을 여세요.
2. 클래스의 함수들을 검토하세요.

이제 어디로 가야 하나

이 튜토리얼로 프로시니엄 프레임워크로 플래시 프로젝트를 위한 3D 요소를 제작하는 것에 대한 개요를 여러분이 알게 되었기를 바랍니다. 플래시 프로페셔널에서 3D 애니메이션과 게임을 제작하는 것을 더 배우려면 다음 온라인 자원들을 확인해보세요.
Comments