─━ IT ━─

3D 모델을 표시하는 자바 애플릿 작성

DKel 2021. 8. 16. 00:46
반응형
먼저 홈페이지에 입체적인 형상을 표시하고, 그것을 마우스로 빙글빙글 움직일 수 있으면 즐겁겠네요.본 기사에서는 3D 형상을 다루는 재미를 실현할 수 있는 자바 애플릿의 작성 방법을 소개합니다.브라우저 상에서 3D 모델을 보여주려고 하는 경우, X3D 나 Cult3D, YAPPA, Shockwave3D, XVL 등 훌륭한 어플리케이션들이 이미 많이 있는데, 이번에는 3D 모델을 보여주기 위한 어플렛을 아예 처음부터 직접 만들도록 하겠습니다.Java에는 Java3D라는 3D용 편리한 API가 있지만, 이 힘도 빌리지 않는다(!). 실제의 코드를 바탕으로 3DCG의 기초를 해설하므로, Java업렛 작성의 학습과 함께 3D의 기초적인 내용의 학습에도 도움 된다면 다행입니다.전체적으로는 다음과 같은 다섯 단계로 3D 모델을 표시하는 방법에 대한 설명을 합니다.Step1 3D 모델을 정의해서 일단 그려보기
Step2 와이어프레임 모델 빙글빙글 돌려보기
Step3 면이 있는 모델을 보여준다
Step4면의 밝기를 설정한다.
Step5 파일에서 모델의 데이터를 읽어들이는 최종적으로는 그림 1과 같이 3D 모델을 구루구루 움직일 수 있게 됩니다.완성판 애플릿을 보는 컴퓨터에서 3D 모델을 보여주는 프로그램을 만들 때 가장 중요한 점은 보이는 부분을 어떻게 표시하느냐, 즉 ′보이지 않는 부분을 어떻게 숨기느냐′입니다.보이지 않는 부분을 숨기는 것을 「음면 소거」라고 부르며, Z-버퍼법, 레이트 레이싱법, 스캔 라인법등의 여러가지 방법이 존재합니다.이번에는 「안쪽의 것부터 차례차례로 그린다」라고 하는 가장 단순하고 실장도 간단한 알고리즘의 「우선순위법(페인터즈 알고리즘)」을 소개합니다.대상 독자 3DCG의 기초를 배우고 싶은 사람이 대상입니다.필요한 환경 J2SE Development Kit(JDK) 1.4.x가 필요합니다.【스텝 1】3D모델을 정의하고 일단 그려 보고 먼저 애플릿에 표시할 3D모델의 형태를 결정합시다.처음이니까 가급적 간단한 모델이 좋습니다. 라고 해도 단순한 입방체로는 재미가 없기 때문에 우선은 그림2와 같은 형태를 생각해 보겠습니다.그런데 이 3D 모델을 표시하기 위해서는 어떤 정보가 있으면 좋을까요.우선 각 정점의 좌표값이 필요합니다.정점의 좌표에 관한 정보를 다음과 같이 double형의 2차원 배열로 정의해 보겠습니다.정점의 좌표값 정의
// 꼭지점 데이터
final double [ ][ ] VERTEX _ DATA = {
{-1, 0, 0}, {0, 1, 0}, {0, 0, -1},
{1, 0, 0}, {0, 0, 1}, {0, -1, 0}}; 내부 배열에 포함된 3가지 값이 각 정점의 x, y, z 좌표값을 나타냅니다.이 예에서는 0번째 정점(그림중 P0)의 좌표가 (-1, 0, 0)이고 1번째 정점(그림중 P1)의 좌표가 (0, 1, 0)이라는 식으로 정점의 좌표값을 순서대로 정의하고 있습니다.대상으로 하는 3D 모델에는 정점이 6개 있기 때문에 이러한 정점의 좌표값을 나타내는 배열이 6개 나열되어 있습니다.이어서 「3 D모델의 각 면이, 어느 정점으로 구성되는가」라고 하는 정보를 다음과 같은 int형의 2차원 배열로 정의합니다.정점의 좌표값 정의
// 면데이터
final int [ ][ ] FACE _ DATA = {
{1, 4, 2}, {1, 0, 4}, {1, 2, 0}, {3, 2, 4},
{0,5,4}, {4,5,3}, {3,5,2}, {2,5,0}; 내부에 포함된 3가지 값이 1가지 면(삼각형) 윤곽을 구성하는 정점의 인덱스(식별번호)를 나타냅니다.즉 {1,4,2}에서 정의되는 첫 번째 정점(P1), 네 번째 정점(P4), 두 번째 정점(P2)으로 구성되며 다음 면은 첫 번째 정점(P1), 0 번째 정점(P0), 네 번째 정점(P4)으로 구성됩니다라는 식으로 면을 구성합니다.대상으로 하는 3D 모델에는 면이 8개 있기 때문에 이러한 면을 구성하는 정점을 나타내는 배열이 8개 나열되어 있습니다.이상과 같이 ′정점의 좌표값 정보′와 ′면을 구성하는 정점의 인덱스 정보′가 있으면 이를 바탕으로 3D 모델을 표시할 수 있게 됩니다.그럼 정점을 나타내는 클래스와 면을 나타내는 클래스를 만들어 보겠습니다.각각 Vertex(정점)과 Face(면)이라는 이름의 클래스로 합니다.Vertex클래스는 3차원 공간상의 점을 나타내기 때문에 다음과 같이 구성원 변수로 (x, y, z)의 좌표값을 갖도록 정의합니다.정점 클래스의 정의
// 꼭지점 클래스
class Vertex {
public double x, y, z; // 모델의 정점 좌표

public Vertex ( double x , double y , double z ) {
this . x = x ;
this . y = y ;
this . z = z ;
}
}} 이번에는 3D 모델을 구성하는 면이 모두 삼각형이고 Face 클래스에는 면 윤곽을 구성하는 3개의 Vertex를 다음과 같이 배열로 갖게 하겠습니다.면 클래스의 정의
// 면 클래스
class Face {
public Vertex [ ] v = new Vertex[3]; // 면을 구성하는 3개의 정점

public Face ( Vertex v0 , Vertex v1 , Vertex v2 ) {
v [ 0 ] = v0 ;
v [ 1 ] = v1 ;
v [ 2 ] = v2 ;
}
}}자, 3D 모델의 형태를 표현하는데 필요한 데이터(정점 좌표의 배열 VERTEX_DATA와 정점 인덱스의 배열 FACE_DATA)와 클래스(Vertex 클래스와 Face 클래스 각각의 오브젝트를 생성하겠습니다.(뒤에 이 오브젝트를 바탕으로 3D 모델을 표시하게 됩니다.)꼭지점 및 면은 여러 개씩 관리를 해야 하기 때문에 각각을 vertices, faces라는 이름의 Array List에 저장하도록 하겠습니다.애플릿 코드의 필드에서, 다음과 같이 Array List 의 정의를 실시합니다.Array List vertices; // 꼭지점 열을 유지하는
ArrayList faces; //면(삼각형) 열을 유지하는 이어서 이 ArrayList에 Vertex클래스와 Face클래스 객체를 저장하기 위한 set ModelData라는 이름의 메서드를 다음과 같이 만듭니다.// 모델 데이터 설정
public void setModelData ( ) {
vertices = new Array List(); // 꼭지점 열을 초기화
faces = new Array List(); // 면열 초기화

// 꼭지점 작성
for ( int i = 0 ; i < VERTEX _ DATA . length ; i++ ) {
vertices . add ( new Vertex ( VERTEX _ DATA [ i ] [ 0 ] ,
VERTEX _ DATA [ i ] [ 1 ] ,
VERTEX _ DATA [ i ] [ 2 ] )) ;
}

// 면작성
for ( int i = 0 ; i < FACE _ DATA . length ; i++ ) {
faces . add ( new Face ( ( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 0 ] ) ,
( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 1 ] ) ,
( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 2 ] ))) ;
}
}}위 코드에서는 VERTEX_DATA에 저장된 데이터를 바탕으로 Vertex클래스 객체를 생성하고 그것을 vertices에 추가하고 있습니다.이어서 FACE_DATA에 저장된 정점의 인덱스 정보에서 Vertex 오브젝트를 vertices에서 취득하고 Face클래스의 오브젝트를 생성하고 그것을 faces에 추가하고 있습니다.그런 식으로 3D 모델의 정점과 면을 나타내는 오브젝트를 vertices와 faces라고 하는 이름의 Array List에 저장할 수 있습니다.이후에는 이를 바탕으로 3D 모델을 그리게 됩니다.이번에는 그리는 그림은 z좌표축의 정방향에서 원점을 향하는 방향을 향해서 모델을 본 경우의 형태라고 합니다(그림3). 또한 간단하기 위해서 면의 윤곽만을 그리기로 합니다(그 결과로 모델의 능선만 표시됩니다.이런 표시방법을 와이어프레임 표시라고 합니다. 그럼 실제로 3D모델을 그리는 메서드 draw Model을 생성을 해보겠습니다.다음의 코드에서는, 각면의 윤곽선을 3개의 선분으로 그립니다.// 모델 그리기
private void drawModel ( Graphics g ) {
// 묘화색을 검정색으로 설정
g . setColor ( Color . black ) ;

// 각 면 묘화
for ( int i = 0 ; i < faces . size ( ) ; i++ ) {
Face face = ( Face ) faces . get ( i ) ;

// 면 윤곽선 그리기
for ( int j = 0 ; j < 3 ; j++ ) {
int x0 = ( int ) ( center . x + face . v [ j ] . x * scale ) ;
int y0 = ( int ) ( center . y - face . v [ j ] . y * scale ) ;
int x1 = ( int ) ( center . x + face . v [ ( j + 1 ) % 3 ] . x * scale ) ;
int y1 = ( int ) ( center . y - face . v [ ( j + 1 ) % 3 ] . y * scale ) ;

g . drawLine ( x0 , y0 , x1 , y1 ) ;
}
}
}}faces에 저장된 각각의 Face 객체에 대해 윤곽선을 그리려면 정점의 좌표값을 애플릿 상에 그리기 위한 좌표 값으로 변환해야 합니다.이렇게3차원공간상의좌표치에서그리기화면의좌표값을구하는것을투영이라고합니다.이번에는, 간단하게 하기 위해서 가까이의 것이나 먼 곳의 것이나 같은 크기로 그리는 「평행 투영」을 실시합니다(먼 것의 것일수록 작게 그리는 방법을 「투시 투영」이라고 합니다).시선방향을 z축에 평행하게 정했기 때문에 정점의 z 좌표값은 무시하고 x 좌표값과 y 좌표값만으로 그리기용 좌표값을 구할 수 있습니다.이 투영은 다음의 계산으로 실시하고 있습니다.int x0 = ( int ) ( center . x + face . v [ j ] . x * scale ) ;
int y0 = (int)(center.y -face.v[j].y * scale); 우선, 애플의 중심이 원점이 되도록, 애플의 중심 좌표를 나타내는 center.x, center.y의 값을 추가하고 있습니다.다음으로 애플릿의 좌표계와 모델의 좌표계의 y축의 방향이 반대이므로(애플릿의 y축은 하향), 정점의 y 좌표치에 마이너스 부호를 붙이고 있습니다.또한 3D 모델의 크기는 한 변의 크기가 2의 입방체에 들어가는 정도이므로, 애플릿의 크기를 기준으로 한 scale 값을 곱함으로써 애플릿의 묘화 영역에 적합한 크기로 하고 있습니다.이상의 순서로 정점의 좌표치에서 애플릿으로 그릴 때의 좌표를 구할 수 있습니다.여기까지의 글에서 3D 모델을 보기 위한 개요를 설명해 드렸는데요.그럼 지금까지의 설명을 종합한 애플로3D_Step1 코드를 보겠습니다.이 코드로 z축방향에서 바라본 3D 모델의 와이어프레임이 표시됩니다.「 Hello 3D _ Step 1 . java 」
import java . applet . Applet ;
import java . awt . * ;
import java . util . * ;

/*

< / applet >
*/

public class Hello 3D _ Step 1 extends Applet {
// 꼭지점 데이터
final double [ ][ ] VERTEX _ DATA = {
{-1, 0, 0}, {0, 1, 0}, {0, 0, -1},
{1, 0, 0}, {0, 0, 1}, {0, -1, 0}};
// 면데이터
final int [ ][ ] FACE _ DATA = {
{1, 4, 2}, {1, 0, 4}, {1, 2, 0}, {3, 2, 4},
{0, 5, 4}, {4, 5, 3}, {3, 5, 2}, {2, 5, 0}};
Array List vertices; // 꼭지점 열을 유지하는
Array List faces; // 면(삼각형) 열을 유지하는
Point center; // 애플릿의 중심 좌표
double scale; // 모델 묘화시 스케일
Dimension appletSize; // 애플릿 사이즈

public void init ( ) {
// 애플릿 사이즈 취득
appletSize = getSize ( ) ;

// 애플릿의 중심 좌표 획득
center =
new Point ( appletSize . width / 2 , appletSize . height / 2 ) ;

// 그리기 스케일 설정
scale = appletSize . width * 0.8 / 2;

// 모델 데이터 설정
setModelData ( ) ;
}

public void paint ( Graphics g ) {
drawModel ( g ) ;
}

// 모델 데이터 설정
public void setModelData ( ) {
vertices = new Array List(); // 꼭지점 열을 초기화
faces = new Array List(); // 면열 초기화

// 꼭지점 작성
for ( int i = 0 ; i < VERTEX _ DATA . length ; i++ ) {
vertices . add ( new Vertex ( VERTEX _ DATA [ i ] [ 0 ] ,
VERTEX _ DATA [ i ] [ 1 ] ,
VERTEX _ DATA [ i ] [ 2 ] )) ;
}

// 면작성
for ( int i = 0 ; i < FACE _ DATA . length ; i++ ) {
faces . add ( new Face ( ( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 0 ] ) ,
( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 1 ] ) ,
( Vertex ) vertices . get ( FACE _ DATA [ i ] [ 2 ] ))) ;
}
}

// 모델 그리기
private void drawModel ( Graphics g ) {
// 묘화색을 검정색으로 설정
g . setColor ( Color . black ) ;

// 각 면 묘화
for ( int i = 0 ; i < faces . size ( ) ; i++ ) {
Face face = ( Face ) faces . get ( i ) ;

// 면 윤곽선 그리기
for ( int j = 0 ; j < 3 ; j++ ) {
int x0 = ( int ) ( center . x + face . v [ j ] . x * scale ) ;
int y0 = ( int ) ( center . y - face . v [ j ] . y * scale ) ;
int x1 =
( int ) ( center . x + face . v [ ( j + 1 ) % 3 ] . x * scale ) ;
int y1 =
( int ) ( center . y - face . v [ ( j + 1 ) % 3 ] . y * scale ) ;

g . drawLine ( x0 , y0 , x1 , y1 ) ;
}
}
}
}

// 면 클래스
class Face {
public Vertex [ ] v = new Vertex[3]; // 면을 구성하는 3개의 정점

public Face ( Vertex v0 , Vertex v1 , Vertex v2 ) {
v [ 0 ] = v0 ;
v [ 1 ] = v1 ;
v [ 2 ] = v2 ;
}
}

// 꼭지점 클래스
class Vertex {
public double x, y, z; // 모델의 정점 좌표

public Vertex ( double x , double y , double z ) {
this . x = x ;
this . y = y ;
this . z = z ;
}
}}위의 ′Hello3D_Step1.java′ 실제로 컴파일하여 실행해 보면 다음과 같은 결과를 얻을 수 있습니다.Hello 3D_Step1 애플릿을 보는 확실히 목적의 형태를 z축방향에서 바라본 형태로 되어있네요. 하지만 다른 각도에서 볼 수 없기 때문에 실제로 어떤 입체형상을 하고있는지 파악할 수 없습니다.다음 단계에서는 이 모델을 마우스로 빙글빙글 회전할 수 있도록 하겠습니다.
반응형