Java

[Java3D] 3D 화면(scene)에 빛 효과 주기

_침묵_ 2007. 3. 9. 08:12

출처 :http://sdnkorea.com/blog/230

 

Java SE/중급2004/07/30 13:51

2003/10/14의 테크팁Introducing the Java3D API에서는 몇 개의 예시를 통해 Java 3D API을 소개했었다. 이 테크팁에는 박스나 3차원 텍스트 등을 회전시키는 예제가 포함되어 있었다. 텍스트를 보기 위해서는 빛이 필요하다. 이번 글에서는 Java3D 애플리케이션에서 사용할 수 있는 여러 종류의 조명에 대해 배워보자. 'ambient, directional, point, spot' 4가지 종류의 조명을 실행해볼 것이다.

지난 10월의 테크팁인Text3DWorld의 예시와 비슷한 예로 시작해보자. 이번에 소개할Framework3DsetLighting()abstract메소드로, 이것이Text3DWorld와의 가장 큰 차이점이다. 이제부터 소개하는 각 예시에서 이 메소드를 오버라이딩하게 될 것이다.

import com.sun.j3d.utils.universe.SimpleUniverse;   import javax.media.j3d.BranchGroup;   import javax.media.j3d.Transform3D;   import javax.media.j3d.TransformGroup;   import javax.media.j3d.Canvas3D;   import javax.media.j3d.Alpha;   import javax.media.j3d.RotationInterpolator;   import javax.media.j3d.BoundingSphere;   import javax.media.j3d.Appearance;   import javax.media.j3d.Material;   import javax.media.j3d.Font3D;   import javax.media.j3d.FontExtrusion;   import javax.media.j3d.Text3D;   import javax.media.j3d.Shape3D;   import javax.swing.JFrame;   import javax.vecmath.Vector3f;   import java.awt.BorderLayout;   import java.awt.GraphicsConfiguration;   import java.awt.Font;   public abstract class Framework3D extends JFrame {     private Transform3D rotate1 = new Transform3D();     private Transform3D rotate2 = new Transform3D();     public Framework3D() {       super("3 - D");       Canvas3D canvas3D = createCanvas3D();       BranchGroup scene = createSceneGraph();       connect(canvas3D, scene);     }     private Canvas3D createCanvas3D() {       setSize(300, 300);       getContentPane().setLayout(new BorderLayout());       GraphicsConfiguration config =         SimpleUniverse.getPreferredConfiguration();       Canvas3D canvas3D = new Canvas3D(config);       setSize(300, 300);       getContentPane().add(canvas3D);       return canvas3D;     }     public BranchGroup createSceneGraph() {       BranchGroup objRoot = new BranchGroup();       TransformGroup mover = moveTextBack();       TransformGroup spinner = createSpinner();       objRoot.addChild(mover);       mover.addChild(spinner);       spinner.addChild(createTextShape());       spinner.addChild(makeSpin(spinner));       setLighting(mover);       return objRoot;     }     private TransformGroup createSpinner() {       TransformGroup spinner = new TransformGroup();       spinner.setCapability(TransformGroup.         ALLOW_TRANSFORM_WRITE);       return spinner;     }     private TransformGroup moveTextBack() {       Transform3D transform3D = new Transform3D();       transform3D.setTranslation(                   new Vector3f(0.0f, 0.0f, -1.0f));       return new TransformGroup(transform3D);     }     private Shape3D createTextShape() {       Appearance textAppear = new Appearance();       textAppear.setMaterial(new Material());       Font3D font3D = new Font3D(                    new Font("Helvetica", Font.PLAIN, 1),                    new FontExtrusion());       Text3D textGeom = new Text3D(font3D,         new String("3 \t D"));       textGeom.setAlignment(Text3D.ALIGN_CENTER);       Shape3D textShape = new Shape3D();       textShape.setGeometry(textGeom);       textShape.setAppearance(textAppear);       return textShape;     }     private RotationInterpolator                       makeSpin(TransformGroup spinner) {       RotationInterpolator rotator =         new RotationInterpolator(new Alpha(-1, 3000),                                  spinner);       rotator.setTransformAxis(rotateCube());       BoundingSphere bounds =         new BoundingSphere();       rotator.setSchedulingBounds(bounds);       return rotator;     }     private Transform3D rotateCube() {       rotate1.rotX(Math.PI / 4.0d);       rotate2.rotY(Math.PI / 3.0d);       rotate1.mul(rotate2);       return rotate1;     }     private void connect(Canvas3D canvas3D,                       BranchGroup scene) {       SimpleUniverse simpleU =         new SimpleUniverse(canvas3D);       simpleU.getViewingPlatform().         setNominalViewingTransform();       simpleU.addBranchGraph(scene);     }      abstract void setLighting(TransformGroup objMove);   }

이제javax.media.j3d.AmbientLight을 이용하여 화면에 조명을 비춰보자. 앞에서 말했듯이 이것은 야외 조명을 시뮬레이션하는 데 사용하는 소스이다.AmbientLight객체에서는 조명의 색상, 범위, on/off 외에 설정할 수 있는 것은 거의 없다. 이 조명의 색은 이후의 보여주는 예시의 색과 같으므로 다양한 효과를 비교해볼 수 있을 것이다. 다음에 제시하는AmbientEx를 컴파일하고 실행해보자. 그러면 회전하고 있는 텍스트에 연한 자주색이 일률적으로 적용되는 것을 볼 수 있을 것이다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.BoundingSphere;   import javax.media.j3d.AmbientLight;   import javax.vecmath.Color3f;   public class AmbientEx extends Framework3D {     void setLighting(TransformGroup objMove) {       AmbientLight light = new AmbientLight();       light.setInfluencingBounds(new BoundingSphere());       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));       objMove.addChild(light);     }     public static void main(String[] args) {       new AmbientEx().setVisible(true);     }   }

사용자 삽입 이미지

javax.media.j3d.DirectionalLight를 사용하여 발생된 조명과 효과를 대조해보자. 방향성을 갖고있는 조명이 텍스트의 입체성을 뚜렷하게 한다. 이 조명은 아주 먼 곳의 소스에서 발생되어 마치 모든 광선들이 평행이면서 같은 방향으로 움직이 것처럼 보인다. 뒤에서 보여줄 예시들과 달리 소스의 위치를 지정하지는 않지만,setDirection()메소드를 이용하여 조명 광선의 진로를 지정할 수 있다. 다음에 제시된DirectionalEx프로그램을 컴파일하고 실행해보자. 그림자와 함께 더 생생한 색을 볼 수 있을 것이다. 이 색깔과 그림자의 결합이 형태의 윤곽을 분명하게 하는 데 도움을 줄 것이다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.DirectionalLight;   import javax.media.j3d.BoundingSphere;   import javax.vecmath.Vector3f;   import javax.vecmath.Color3f;   public class DirectionalEx extends Framework3D {     void setLighting(TransformGroup objMove) {       DirectionalLight light =         new DirectionalLight();       light.setInfluencingBounds(new BoundingSphere());       light.setDirection(new Vector3f(0.6f, 1.0f, -1.0f));       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));       objMove.addChild(light);     }     public static void main(String[] args) {       new DirectionalEx().setVisible(true);     }   }

사용자 삽입 이미지

한번에 한 개 이상의DirectionLight를 사용할 수 있다. 다음에 제시되는DirectionalEx2프로그램에서는 보라색 조명으로 만들어진 그림자가 녹색으로 채워질 것이다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.DirectionalLight;   import javax.media.j3d.BoundingSphere;   import javax.vecmath.Vector3f;   import javax.vecmath.Color3f;   public class DirectionalEx2 extends Framework3D {     void setLighting(TransformGroup objMove) {       DirectionalLight light =         new DirectionalLight();       light.setInfluencingBounds(new BoundingSphere());       light.setDirection(new Vector3f(0.6f, 1.0f, -1.0f));       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));       objMove.addChild(light);       DirectionalLight light2 =         new DirectionalLight();       light2.setInfluencingBounds(new BoundingSphere());       light2.setDirection(new Vector3f(-0.6f, -1.0f, -1.0f));       light2.setColor(new Color3f(0.0f, 1.0f, 0.0f));       objMove.addChild(light2       );     }     public static void main(String[] args) {       new DirectionalEx2().setVisible(true);     }   }

사용자 삽입 이미지

또한, 두 개의 다른 조명을 겹치게 할 수도 있다. 이런 경우에는AmbientLight를 사용하면DirectionalLight의 그림자를 부드럽게 만들 수 있다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.AmbientLight;   import javax.media.j3d.BoundingSphere;   import javax.media.j3d.DirectionalLight;   import javax.vecmath.Color3f;   import javax.vecmath.Vector3f;   public class AmbientEx2 extends Framework3D {     void setLighting(TransformGroup objMove) {       AmbientLight light = new AmbientLight();       light.setInfluencingBounds(new BoundingSphere());       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));       objMove.addChild(light);       DirectionalLight light2 = new DirectionalLight();       light2.setInfluencingBounds(new BoundingSphere());       light2.setDirection(new Vector3f(-0.6f, -1.0f, -1.0f));       light2.setColor(new Color3f(1.0f, 0.0f, 0.5f));       objMove.addChild(light2);     }     public static void main(String[] args) {       new AmbientEx2().setVisible(true);     }   }

사용자 삽입 이미지

다음에 살펴볼 조명의 종류는javax.media.j3d.PointLight이다. 이 조명은 백열전구와 흡사하다. 사방으로 방사하는 포인트 소스를 상상해보라.setPosition()을 이용하면 소스의 위치를 정할 수 있을 뿐 아니라 이전과 같이 컬러를 설정할 수도 있다. 사용 가능한 또 다른 설정은 빛의 감소이다. 이것은 소스로부터 멀어지면 얼마나 빨리 빛이 감소하는 지에 대한 것이다. 빛의 감소는 constant 컴포넌트 a, linear 컴포넌트 b, quadratic 컴포넌트 c 이렇게 세가지로 구성되어 있다.PointLight는, x가 조명과 조명을 비추는 지점 사이의 거리일 때, 1/(a + b x + c x2) 의 속도로 빛이 감소한다. 여기서 디폴트 값은 1이다. 다음에 제시되는PointEx프로그램을 실행하면 조명 소스에 가까운 텍스트의 부분이 멀리 있는 부분보다 밝게 비춰진다는 사실을 알게될 것이다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.BoundingSphere;   import javax.media.j3d.PointLight;   import javax.vecmath.Color3f;   public class PointEx extends Framework3D {     void setLighting(TransformGroup objMove) {       PointLight light = new PointLight();       light.setInfluencingBounds(new BoundingSphere());       light.setPosition(-0.5f, 0.5f, 0.8f);       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));       light.setAttenuation(0f, 0f, 2f);       objMove.addChild(light);     }     public static void main(String[] args) {       new PointEx().setVisible(true);     }   }

사용자 삽입 이미지

javax.media.j3d.SpotLightPointLight의 서브 클래스이다. spot조명을 이용하면PointLight으로 설정할 수 있는 원뿔형(cone) 앵글의 조명 위치, 상태와 함께 방향도 설정할 수 있다. 콘 앵글의 반인 스프레드 앵글의 설정도 가능하다. spot조명의 또 다른 수단으로는 빛을 감소할 수 있다. 조명 축으로부터 광선이 멀어지면서 빛의 밀도가 감소할 수 있다.SpotLight객체들을 형성하기 위해 다음의SpotEx에서는setPosition(),setDirection(),setSpreadAngle()를 사용한다. 이를 실행하면 회전하고있는 텍스트 중 빛이 비춰지는 부분만 보일 것이다.

import javax.media.j3d.TransformGroup;   import javax.media.j3d.SpotLight;   import javax.media.j3d.BoundingSphere;   import javax.vecmath.Color3f;   public class SpotEx extends Framework3D {     void setLighting(TransformGroup objMove) {       SpotLight light = new SpotLight();       light.setInfluencingBounds(new BoundingSphere());       light.setPosition(0.0f, 0.0f, 2.0f);       light.setDirection(0.0f, 0.0f, -1.0f);       light.setSpreadAngle(.3f);       light.setColor(new Color3f(1.0f, 0.0f, 0.5f));      light.setAttenuation(0f, 0f, 1f);       objMove.addChild(light);     }     public static void main(String[] args) {       new SpotEx().setVisible(true);    }   }

사용자 삽입 이미지

이번 테크팁을 통해 Java3D 어플리케이션에서 사용되는 여러 타입의 조명 소스들을 알아보았다. 예제를 통해, 여러 종류의 조명들을 적당하게 조합했을 때 원하는 결과가 나온다는 사실을 알았을 것이다. 포토그래퍼가 밝은 날에도 플래쉬를 사용하는 것과 마찬가지로 여러 조명을 혼합함으로써 사용자가 만드는 화면이 좀 더 실제적으로 보이게 할 수 있을 것이다.

덧붙여, 빛은 비추는 화면의 모습과 함께 그 퍼포먼스도 고려해야 한다. 사실, 사용하는 하드웨어에 따라 퍼포먼스가 많이 제약 받기는 하지만, 최상의 퍼포먼스를 위한 일반적인 룰은 가장 간단한 조명을 사용하는 것이다. 쉽게 말해, 가능하다면 조명을 적게 사용하는 것이 좋다. 또한 국부적인(local) 조명이 넓은(infinite) 조명보다 비경제적이다. 국부적인 조명을 사용해야한다면, spot조명보다 positional 조명이 낫다.

3D화면에 조명을 비추는 것에 대해 좀 더 자세히 알고 싶으면, Java 3D API Tutorial의 6장Lights를 참고하기 바란다.

'Java' 카테고리의 다른 글

JOGL(Java APIs for OpenGL) 소개  (0) 2007.03.09
Java3D  (0) 2007.03.09
Singletons and lazy loading  (0) 2007.02.02