토성(Saturn)

 

Papervision3D나 Away3D 라이브러리를 이용하지 않고 Flash Player 10 3D API만을 이용해 태양계의 행성인 토성을 그려보았다. 토성을 그리기 위해 먼저 표면부분과 고리부분의 Texture를 구해야한다. 아래 사이트에서 다양한 행성의 Texture 그림을 구할 수 있다.

 

http://planetpixelemporium.com/saturn.html

 

토성의 고리 Texture

토성의 표면 Texture

 

Texture를 이용해 3D로 만들기 위한 아이디어는 아래 링크를 참고한다.

 

[Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기

[Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용

 

 

토성 그리기 : 표면과 고리가 겹치는 문제

 

지구와 같은 행성과 다르게 토성의 경우에는 고리가 있다. 토성의 표면은 그냥 구형태로 Mesh데이타를 만들어 처리하면 된다. 하지만 고리의 경우는 다른 형태로 Mesh데이타를 만들어야 한다. 설명은 생략하겠다.

 

아래는 아래 예시의 소스이다.

invalid-file

고리와 구가 겹치는 문제가 있는 소스

 

토성을 모양을 만들기 위해 토성의 표면과 고리에 대한 Mesh데이타를 만들어야 한다. 아래코드는 고리(Ring) 부분의 Mesh데이타를 만드는 부분이다. 표면부분과 다르게 고리 Texture를 반복해서 그리는 데이타를 만든다.

/**
 * 고리 Mesh 데이터 작성 
 * @param radius 고리 안쪽 반지름  
 * @param thickness 고리 두께
 * @param div 고리 조각수 
 * @return mesh 데이터 
 */
function createRingMesh( radius:Number, thickness:Number, div:uint ):GraphicsTrianglePath
{
    var vertices:Vector. = new Vector.( 0, false );
    var indices:Vector. = new Vector.( 0, false );
    var uvtData:Vector. = new Vector.( 0, false );
    var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.NONE );
    var s:Number = 0;
    var x1:Number = radius;
    var x2:Number = radius + thickness;
    var y1:Number = 0;
    var y2:Number = 0;
    var z:Number = 0;          
    var cos:Number;
    var sin:Number;    
    var n:Number = 0;
    
    for( var i:uint = 0; i < div; i++ )
    {
        //Vertices
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        s = Math.PI * 2 * (i + 1) / div;
        cos = Math.cos( s );
        sin = Math.sin( s );
        x1 = radius * cos;
        x2 = (radius + thickness) * cos;
        y1 = radius * sin;
        y2 = (radius + thickness) * sin;
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        //UVT
        mesh.uvtData.push( 
            0,1,1, 
            1,1,1, 
            0,0,1, 
            1,0,1 
        );
        
        //Indices
        n = i * 4;
        mesh.indices.push( n, n+1, n+2, n+2, n+1, n+3 );             
    }    
    
    return mesh;
}

 

Enterfame시에 렌더링을 실시하는데 Utils3D.projectVectors(), graphics.beginBitmapFill(), graphics.drawTriangles() 메소드를 2번씩 호출한다. 즉, 토성의 구와 고리를 그리기 위해 투영, 렌더링을 2번씩 하는 것이다.

private function onEnterFrame( event:Event ):void
{
    world.identity(); //단위행렬로 전환 
    world.appendRotation( getTimer() * 0.0027, Vector3D.Z_AXIS );
    world.appendRotation( xRotation, Vector3D.X_AXIS );
    world.appendRotation( yRotation, Vector3D.Y_AXIS );
    world.appendTranslation(0, 0, viewPortZAxis); //이동 
    world.append(projection.toMatrix3D()); //투영 변환 적용 
    
    // mesh 데이터를  투영하여  projected 생성 
    // uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
    Utils3D.projectVectors( world, saturnFaceMesh.vertices, saturnFaceProjected, saturnFaceMesh.uvtData );
    Utils3D.projectVectors( world, saturnRingMesh.vertices, saturnRingProjected, saturnRingMesh.uvtData );    
    
    viewport.graphics.clear();
    // Triangle 라인을 그림 
    if( visibleTriangle )
    {
        viewport.graphics.lineStyle( 1, 0xff0000, 0.1 );
    }
    
    //Texture 입힌다.
    viewport.graphics.beginBitmapFill( saturnFaceTexture, null, false, true );
    viewport.graphics.drawTriangles( saturnFaceProjected, getSortedIndices(saturnFaceMesh), saturnFaceMesh.uvtData, saturnFaceMesh.culling );                
    viewport.graphics.beginBitmapFill( saturnRingTexture, null, false, true );
    viewport.graphics.drawTriangles( saturnRingProjected, getSortedIndices(saturnRingMesh), saturnRingMesh.uvtData, saturnRingMesh.culling );                
}

 

언뜻보면 이 코드는 문제가 없어보이지만 실제로 실행해보면 바로 문제가 있다는 것을 확인할 수 있다.

아래는 이 프로그램을 실행한 것이다.

 

 

마우스로 돌려보고 화살표키로 확대/축소도 할 수 있다. 또한 Space키 누르면 삼각형 부분이 보인다.

 

실행한 결과를 보면 문제가 있다. 토성 표면 뒤에 고리가 보인다. 당연히 저렇게 그려지면 안된다. 이 문제는 위의 방법처럼 Texture와 Mesh데이타가 2개여서는 방법이 나오지 않는다. 왜냐하면 graphics.drawTriangles() 함수를 2번 그려주게 되면 먼저 그린 것은 항상 나중에 그린것에 의해 가려지기 때문이다.

 

 

토성 그리기 : 고리 겹치지 않게 만들기

 

invalid-file

표면과 고리가 겹치지 않는 소스

 

위처럼 고리가 토성표면에 겹치는 문제를 해결하기 위한 방법은 Texture를 하나로 통합하고 Mesh데이타도 하나로 만든다. 그리고 graphics.drawTriangles() 를 한번만 호출하도록 하면 된다.

 

아래 함수는 고리, 표면 Texture를 1개의 텍스쳐로 만들어준다.

/**
 * 텍스쳐 생성 
 * @param faceTexture 표면의 BitmapData
 * @param ringTexture 고리의 BitmapData 
 * @return TexturInfo의 객체 
 */ 
function createTexture( faceTexture:BitmapData, ringTexture:BitmapData ):TextureInfo
{
	var texture:BitmapData;

	texture = new BitmapData( 
			Math.max( faceTexture.width, ringTexture.width ), 
			faceTexture.height + ringTexture.height, 
			true,
			0xff000000 
	);

	faceTexture.lock();
	ringTexture.lock();

	var facePixels:ByteArray ;
	var ringPixels:ByteArray;
	facePixels = faceTexture.getPixels( new Rectangle( 0, 0, faceTexture.width, faceTexture.height ) );
	ringPixels = ringTexture.getPixels( new Rectangle( 0, 0, ringTexture.width, ringTexture.height ) );
	facePixels.position = 0;
	ringPixels.position = 0;

	texture.setPixels( 
		new Rectangle( 0, 0, faceTexture.width, faceTexture.height ), 
		facePixels 
	);
	texture.setPixels( 
		new Rectangle( 0, faceTexture.height, ringTexture.width, ringTexture.height ), 
		ringPixels 
	);
	
	var faceRect:Rectangle = new Rectangle( 0, 0, faceTexture.width, faceTexture.height );
	var ringRect:Rectangle = new Rectangle( 0, faceTexture.height+1, ringTexture.width, ringTexture.height );
	
	faceTexture.unlock();
	ringTexture.unlock();
	
	return new TextureInfo( texture, faceRect, ringRect ); 
}

 

결국 아래 그림처럼 Texture BitmapData가 만들어진다.

 

위 함수에 TextureInfo는 실제 texture Bitmap정보와 Texture상의 표면과 고리 부분에 대한 영역정보를 가지고 있다. 이 영역정보를 가지고 Mesh 데이타를 만들도록 한다.

 

/**
 * 토성모양의 Mesh 데이타 
 * @param textureInfo 텍스쳐 정보 
 * @return Mesh 데이타 
 */ 
function createMesh( textureInfo:TextureInfo ):GraphicsTrianglePath
{
	var width:Number = textureInfo.texture.width;
	var height:Number =textureInfo.texture.height;
	var faceRect:Rectangle = textureInfo.faceRect;
	var ringRect:Rectangle = textureInfo.ringRect;

	var minU:Number;
	var maxU:Number;
	var minV:Number;
	var maxV:Number;
	
	//표면 Mesh 데이타 만들기 
	minU = faceRect.x / width;
	maxU = (faceRect.x + faceRect.width) / width;
	minV = faceRect.y / height;
	maxV = (faceRect.y + faceRect.height) / height;
	var faceMesh:GraphicsTrianglePath = createSphereMesh( 50, 24, 24, minU, maxU, minV, maxV );
	
	//고리 Mesh 데이타 만들기 
	minU = ringRect.x / width;
	maxU = (ringRect.x + ringRect.width) / width;
	minV = ringRect.y / height;
	maxV = (ringRect.y + ringRect.height) / height;
	var ringMesh:GraphicsTrianglePath = createRingMesh( 70, 20, 50, minU, maxU, minV, maxV );

	//고리 Mesh 데이타에서 Index 부분 조정 
	var deltaIndex:uint = faceMesh.vertices.length/3; //Vertex는 x,y,z 3개씩 묶이므로... ^^
	var length:uint = ringMesh.indices.length;
	for( var i:int = 0; i < length; i++ )
	{
		//아래와 같이 2개의 mesh가 합쳐지면 뒤에 붙는  ring부분의 index값이 변경되야한다.
		ringMesh.indices[i] += deltaIndex; 
	}
	
	//최종 Mesh 데이타 완성 
	var mesh:GraphicsTrianglePath = new GraphicsTrianglePath();
	mesh.vertices = faceMesh.vertices.concat( ringMesh.vertices );
	mesh.uvtData = faceMesh.uvtData.concat( ringMesh.uvtData );
	mesh.indices = faceMesh.indices.concat( ringMesh.indices );
	mesh.culling = TriangleCulling.NONE;
	return mesh;	
}

 

위 함수는 2개의 Mesh데이터를 이용해 1개의 Mesh데이타로 통합해준다. 이로써 이제는 투영, 렌더링을 한번만 하도록 한다.

 

private function onEnterFrame( event:Event ):void
{
	world.identity(); //단위행렬로 전환 
	world.appendRotation( getTimer() * 0.0027, Vector3D.Z_AXIS );
	world.appendRotation( xRotation, Vector3D.X_AXIS );
	world.appendRotation( yRotation, Vector3D.Y_AXIS );
	world.appendTranslation(0, 0, viewPortZAxis); //이동 
	world.append(projection.toMatrix3D()); //투영 변환 적용 
	
	// mesh 데이터를  투영하여  projected 생성 
	// uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
	Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData );
	
	viewport.graphics.clear();

	// Triangle 라인을 그림 
	if( visibleTriangle )
	{
		viewport.graphics.lineStyle( 1, 0xff0000, 0.1 );
	}
	
	//Texture 입힌다.
	viewport.graphics.beginBitmapFill( textureInfo.texture, null, false, true );
	viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling );            	

}

 

위 함수처럼 1번만 투영/렌더링 처리하면 이제 토성의 표면과 고리는 아래처럼 자연스럽게 나오게 된다.

 

 

하지만 완벽하지 않다. 이유는 렌더링시 Culling을 None으로 지정했기 때문이다. 이는 보이지 않는 표면까지 그린다는 의미이다. 고리의 경우 양쪽면이 다보여야한다. 그러므로 고리의 Mesh데이타는 None으로 지정한다. 하지만 토성 표면의 경우는 화면 앞쪽을 향하는 부분만 보이면 되므로 Culling을 Positive로 지정하면 된다. 첫번째 예제에서는 어짜피 Mesh 데이터가 2개이므로 상관없었지만 두번째의 경우에는 양면 다보여야하는 고리때문에 토성의 표면도 None처리 했다. 이는 보이지 않는 부분까지 렌더링하므로 그만큼 속도저하가 일어난다. 이를 해결하기 위해 Positive로 지정하되 고리를 앞뒤면으로 겹쳐서 그리면 된다. 하지만 이 방법밖에 없는지 의문이다.

 

 

정리하기

 

이런 작업은 신나는 일이다. ^^

 

이 토성에 Light효과를 주어 Shading 예제도 만들어봐야 겠다.

 

참고로 모든 작업은 Flash Builder 4에서 했으며 Flex 4 SDK를 이용했다. 결과물은 Flash Player 10이상 설치된 브라우져에서만 확인할 수 있다.

 

위 코드는 아래 링크에서도 볼 수 있다.

 

Saturn 3D, 토성, 고리 중첩 해결못한 것

Saturn 3D, 토성, 고리 중첩 해결한 것

 

참고사이트

[Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기

[Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/547)

 

지난번에 “100시간 천문학(100 Hours of Astronomy)” 행사에 대해 글을 적은적이 있다. 행사 마지막날에는 아마추어 천문가들을 중심으로 하는 “거리의 별 축제”가 있었고 천문노트(http://astronote.org)도 이 행사에 참가했다. 천문노트는 서울천문동호회와 함께 서울역에 자리를 잡았다. 이 행사는 전국 각지역에 열렸다.

 

길을 다니다가 천체망원경을 보고 신기하다듯이 오는 사람이 대부분이였다. 일본사람, 태국사람, 국적 모를 외국인도 흥미를 가지고 다녀갔다. 나는 12인치 돕소니안 천체망원경으로 사람들에게 달과 토성을 보여주었다. 대부분 사람들이 달의 크레이터를 보고 놀라움을 금치 못했다. 이 날 달이 상현달이라 오후 5시에도 보였기 때문에 어렵지 않게 달을 보여줄 수 있었다.

 

어두워 지면서 서서히 토성이 보이기 시작했다. 사자자리에 위치한 토성은 밝기 때문에 서울시내에서도 충분히 관측이 가능하다. 망원경의 아이피스 안에 아기자기한 토성을 본 사람들은 토성의 고리를 보고 신기해 했다.  정말 토성이 있고 고리가 있구나 하는 모습이였다.

 

이날 KBS World 라디오 방송에서 이번 행사를 취재해갔다. 방송은 들어보지 않았지만 아마도 사람들에게 별자리를 가르치는 것과 달을 보여주며 설명해주는 내 육성이 공개되지 않았을까 생각한다. ㅎㅎ

 

그날 교통사고까지 당해서 스트레스성 위산 과다분비로 인해 속이 쓰려 매우 컨디션이 좋지 않았지만 많은 사람들에게 별을 보여줬다는 좋은 추억을 만들 수 있어서 나름 기분은 좋았다.

 

 

 

아이한테 별을 보여주는 모습. 옆에 이어폰 낀 여자분은 KBS World 라디오 기자분이다.

 

 

12인치 돕소니안이다. 지금은 레이저 콜리메이터를 이용해 광축을 맞추고 있는 모습

 

 

 

달과 토성을 본 사람들에게 저렇게 봤던 대상을 붙이고 자신의 이름이나 메시지를 적는 행사도 곁들였다.

 

 

 

쌍안경을 이용해 달을 보는 아이. 많은 사람들이 달을 보려면 큰 망원경이어야 한다고 생각하지만 저렇게 쌍안경으로도 충분히 행성 및 성단, 달을 관측할 수 있다.(태양은 금물! 실명될 수 있음)

 

 

 

굴절망원경을 통해 달을 보는 모습

 

 

 

이날 초 대박 작품인 별지도. 여고 학생들이 저렇게 만들었다 야광별을 일일히 붙이고 별자리 선까지 그려주고 메시에 목록(성운,성단,은하 목록) 사진도 붙인 작품을 가지고 나왔다. 학생들의 열정에 박수를 보낸다.

 

 

 

행사를 준비하는 중

 

 

 

서울천문동호회 분이 가지고 계신 망원경. 적도의 EM-11 뽀대가 제대로다. 시상도 매우 깔끔했다.

 

 

서울역에서 별을 보여주는 모습. 오른쪽 하단에 보이는 큰 망원경은 16인치 돕소니안이다. 열심히 달을 맞추고 계신다. ^^;;

 

 

 

토성(Saturn)은 태양으로 부터 목성 다음으로 먼 행성이다. 지구에서 바라볼 때, 토성의 고리는 14~15년 주기로 기울기가 커졌다 작아졌다 한다. 토성의 고리는 아마추어 천체 관측가 뿐만아니라, 일반인들에게도 매우 흥미로운 모습을 보여준다. 작은 천체망원경만 가져도 토성의 고리를 확인할 수 있다. 별자리를 공부한 다음에 행성의 위치를 알게되는데 그때 망원경으로 관측하는 대상이 금성,목성 다음으로 토성이 아닐까 싶다.

토성의 밝고 아름다운 고리는 기울기가 감소함에 따라 대구경 천체망원경 조차도 관측하기 어려울 정도가 된다. 아쉽게도 현시점에서 토성의 고리는 점차 기울여지고 있으며 그 아름다운 고리는 보기 어려워지게 될 것이다.

 

하지만 너무 아쉬워할 필요는 없다. 토성의 고리를 못보는 대신 토성의 고리로 인해 안보이던 토성 위성의 토성면을 통과하는 매우 흥미로운 모습을 관측할 수 있을 것이니 말이다. 위 사진은 바로 그 모습을 보여주고 있다. 위 사진은 지난 2월 24일에 허블우주망원경(HST)이 찍은 사진이다. 위 사진에서 토성의 위성중 4개의 위성을 볼 수 있다. 가장 큰 위성은 토성에서 가장 큰 위성인 타이탄(Titan)이다. 가장 좌측에는 엔셀라두스(Enceladus)와 그의 그림자, 그 다음 좌측에는 디오네(Dione)과 그의 그림자이다. 우측 토성의 디스크 표면에 작은 점은 미마스(Mimas)이다. 타이탄과 미마스의 그림자는 토성표면 밖에 있어서 사진상에는 나오지 않았다.

 

토성의 적도 지름은 약 120,000 Km로 지구의 약 9배 이상이다. 부피도 지구의 760배에 달하지만 질량은 95배 밖에 안되기 때문에 토성의 평균밀도는 0.7g/cm3으로 토성을 담을 충분한 물이 있다면 물에 뜰 정도이다.

토성의 아름다운 고리는 얼마동안 보지 못할 것이지만 위성의 통과 모습을 망원경을 통해 볼 수 있지 않을까 기대감을 가지게 된다.

 

사진 및 내용의 출처 : http://antwrp.gsfc.nasa.gov/apod/ap090319.html

 

 

 

+ Recent posts