JavaでOpenGLのメモ(まだ追加するよ!)


、昔仕事でお世話になった人から依頼で、JavaでOpenGLを使った調査が必要になったので、メモ。

JavaでOpenGLを使う

ライブラリがいくつかあるらしいが、とりあえず今回はJOGLを使う。
ITPProの「OpenGLを使ってJavaでも3Dを楽しもう」を参考にしたが、記事が2006年と古いのでリンク切れがあったりして微妙。
まぁ、気を取り直して、JOGLのサイトに行って、ITProの記事を見ながらやってたけど、なんITか違う感じがしたので、Googlingで新しめの記事を探す。

とりあえず、JOGLことはじめ。を参考に環境を構築する。

  1. http://jogamp.org/deployment/jogamp-current/archive/からjogamp-all-platforms.7zをダウンロード
  2. ダウンロードしたファイルを解凍する。どこでもいいらしいけど、Javaなので、日本語や半角スペースを含むパスは避けた方がよいでしょう。
  3. Eclipseでプロジェクトを作って、クラスパス(ビルドパス)に解凍したフォルダの中から、gluegen.jarとjogl-all.jarを登録する。
  4. 解凍したフォルダの中のgluegen-rt-natives-xxxx.jarとjoal-natives-xxxx.jarを環境にあったものを選んで、その中のdllをEclipseで作ったプロジェクトにいれる。

あとは、ITProのソースを入力して、実行。
ちなみに、ITProのソースはJOGL2のソースではないので、若干の手直しが発生する。
手直ししたソースは以下の通り。

import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;

import com.jogamp.opengl.util.gl2.GLUT;

public class SimpleCube implements GLEventListener {
	private GL gl;
	private GL2 gl2;
    private GLUT glut;

    public SimpleCube() {
        Frame frame = new Frame("Simple Cube");

        // 3Dを描画するコンポーネント
        GLCanvas canvas = new GLCanvas();
        canvas.addGLEventListener(this);

        frame.add(canvas);
        frame.setSize(300, 300);
 
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
 
        frame.setVisible(true);
    }
 
    public void init(GLAutoDrawable drawable) {
        // 初期化処理
    	gl = drawable.getGL();
    	gl2 = gl.getGL2();
        glut = new GLUT();    	
    }
 
    public void reshape(GLAutoDrawable drawable,
                        int x, int y, 
                        int width, int height) {
        // 描画領域変更処理
        float ratio = (float)height / (float)width;
        
        gl2.glViewport(0, 0, width, height);
 
        gl2.glMatrixMode(GL2.GL_PROJECTION);
        gl2.glLoadIdentity();
        gl2.glFrustum(-1.0f, 1.0f, -ratio, ratio,
		             5.0f, 40.0f);
 
        gl2.glMatrixMode(GL2.GL_MODELVIEW);
        gl2.glLoadIdentity();
        gl2.glTranslatef(0.0f, 0.0f, -20.0f);
    }
 
    public void display(GLAutoDrawable drawable) {
        // 描画処理
        gl2.glClear(GL.GL_COLOR_BUFFER_BIT);
        
        // 大きさ 2 の線画の立方体を描画
        glut.glutWireCube(2.0f);
    }
 
    public void displayChanged(GLAutoDrawable drawable,
                               boolean modeChanged,
                               boolean deviceChanged) {
    }
 
    public static void main(String[] args) {
        new SimpleCube();
    }

	@Override
	public void dispose(GLAutoDrawable arg0) {
	}
}

手直しに参考にしたサイトは以下のところ
http://stackoverflow.com/questions/7606006/cant-use-glloadidentity-as-expected-after-a-translation-in-jogl

OpenGLで作った画像の保存

現在調査中だけど、以下のサイトを参考にしたらできそうな予感。

JOGLの情報ではないけど、OpenGLと同じメソッドが使えるという前提(でないと困る)
ちょこちょこっとやった感じではいけそうだけど、バッファの部分とかをどうしていいかわからない。
とりあえず、glReadPixels()当たりで取ってきたデータをつかってbmpやpng,jpgにできるのではないかと予想。
調べてみると、タイミングによっては真っ暗になるらしい。

で、やってみたのが以下のソース。
うまくかなかったりしたけど、とりあえず、保存できるようになった。が、色がおかしい・・・

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Calendar;

import javax.imageio.ImageIO;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;

import com.jogamp.opengl.util.gl2.GLUT;

public class SimpleCube implements GLEventListener, KeyListener {
	private GL gl;
	private GL2 gl2;
	private GLUT glut;
	private boolean isSaveGL = false;
	private GLCanvas canvas = null;
	private Frame frame = null;
	
	public SimpleCube() {
		frame = new Frame("Simple Cube");

		// 3Dを描画するコンポーネント
		canvas = new GLCanvas();
		canvas.addGLEventListener(this);
		canvas.addKeyListener(this);

		frame.add(canvas);
		frame.setSize(300, 300);

		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		frame.setVisible(true);
	}

	public void init(GLAutoDrawable drawable) {
		// 初期化処理
		gl = drawable.getGL();
		gl2 = gl.getGL2();
		glut = new GLUT();
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int width,
			int height) {
		// 描画領域変更処理
		float ratio = (float) height / (float) width;

		// 透視投影(遠近投影法)の行列を指定する。
		gl2.glViewport(0, 0, width, height);

		// 射影行列を設定
		gl2.glMatrixMode(GL2.GL_PROJECTION);

		// 現在選択されている行列に単位行列をロードする。
		gl2.glLoadIdentity();

		// ビューボリューム(視体積)を指定します。
		gl2.glFrustum(-1.0f, 1.0f, -ratio, ratio, 5.0f, 40.0f);

		// モデルビューの行列を用いて作業する事を指定する。
		gl2.glMatrixMode(GL2.GL_MODELVIEW);

		// 現在選択されている行列に単位行列をロードする。
		gl2.glLoadIdentity();

		// 物体を平行移動をさせる
		gl2.glTranslatef(0.0f, 0.0f, -20.0f);

		// 物体を回転させる場合。
		gl2.glRotatef(15, 1.0f, 0.0f, 0.0f);
	}

	public void display(GLAutoDrawable drawable) {
		// 描画処理
		gl2.glClear(GL.GL_COLOR_BUFFER_BIT);

		// 大きさ 2 の線画の立方体を描画
		// glut.glutWireCube(2.0f);
		glut.glutWireTeapot(2.0f);

		if (isSaveGL == true) {
			try {
				Calendar cal = Calendar.getInstance();
				long mil = cal.getTimeInMillis();
				Dimension dim = frame.getSize();
				Insets insets = frame.getInsets();
				int imgWidth = dim.width - (insets.left + insets.right);
				int imgHeight = dim.height - (insets.top + insets.bottom);
				saveGL("./test1_" + Long.toString(mil) + ".jpg", 300, 300);
				saveGL("./test2_" + Long.toString(mil) + ".jpg", imgWidth, imgHeight);
				System.out.println("OK");
			} catch (Exception e) {
				System.out.println("faild");
				e.printStackTrace();
			} finally {
				isSaveGL = false;
			}
		}
	}

	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
			boolean deviceChanged) {
	}

	public static void main(String[] args) {
		new SimpleCube();
	}

	@Override
	public void dispose(GLAutoDrawable arg0) {
	}

	/**
	 * http://d.hatena.ne.jp/npal_shared/20121107/1352284053 を参考に保存処理を作成 保存は
	 * http://www.geocities.jp/inu_poti/java/meida/image05.html
	 * http://www.metareal
	 * .org/2007/04/02/convert-awt-image-to-buffered-image-or-byte-array/ を参考
	 * 
	 * @param filename
	 */
	public void saveGL(String filename, int imageWidth, int imageHeight) {
		// RGBなら3, RGBAなら4
		int channelNum = 4;
		int allocSize = imageWidth * imageHeight * channelNum;
		ByteBuffer byteBuffer = ByteBuffer.allocate(allocSize);
		//gl2.glFlush();
		// 読み取るOpneGLのバッファを指定 GL_FRONT:フロントバッファ GL_BACK:バックバッファ
		gl2.glReadBuffer( GL2.GL_BACK );

		// OpenGLで画面に描画されている内容をバッファに格納
		gl2.glReadPixels(0,				// 読み取る領域の左下隅のx座標
				0,						// 読み取る領域の左下隅のy座標
				imageWidth,				// 読み取る領域の幅
				imageHeight,			// 読み取る領域の高さ
				GL2.GL_BGRA,			// 取得したい色情報の形式
				GL2.GL_UNSIGNED_BYTE,	// 読み取ったデータを保存する配列の型
				(Buffer) byteBuffer		// ビットマップのピクセルデータ(実際にはバイト配列)へのポインタ
		);
		printGLError();

		// glReadBufferで取得したデータ(ByteBuffer)をDataBufferに変換する
		byte[] buff = byteBuffer.array();

		// データがBGRAをABGR
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		byte[] abgrBuff = new byte[4];
		try{
			for(int y = imageHeight-1; y >= 0; y--){
				for(int x = 0; x < imageWidth; x++){
					// ここがおかしいので色もおかしい
					//      0 1 2 3
					// from B G R A
					// to   A B G R
					int offset = imageWidth * y * channelNum;
					// A
					abgrBuff[0] = buff[x * channelNum + offset + 3];
					// B
					abgrBuff[1] = buff[x * channelNum + offset + 0];
					// G
					abgrBuff[2] = buff[x * channelNum + offset + 1];
					// R
					abgrBuff[3] = buff[x * channelNum + offset + 2];
					baos.write(abgrBuff);
				}
			}
		}catch(IOException ie){
			return;
		}
		buff = baos.toByteArray();
		DataBufferByte dataBuffer = new DataBufferByte(buff, buff.length);

		// DtaaBuffereをつかって、BufferedImageを作成
		BufferedImage imageBuffer = new BufferedImage(
				imageWidth, 
				imageHeight,
				BufferedImage.TYPE_4BYTE_ABGR);

		// BufferedImageのSampleModelを取得
		SampleModel sm = imageBuffer.getSampleModel();

		// DataBuffereとSampleModelをつかってRasterを作成
		Raster raster = Raster.createRaster(sm, dataBuffer, null);

		// BufferedImageにRasterを設定
		imageBuffer.setData(raster);

		try {
			// ImageIOクラスを使って、画像を保存
			String[] fnSplit = filename.split("\\.");
			String ext = fnSplit[fnSplit.length - 1].toLowerCase();
			String fileType = "bmp";
			if(ext.equals("jpg") || ext.equals("jpeg")){
				fileType = "jpg";
			}
			else if(ext.equals("gif")){
				fileType = "gif";
			}
			else if(ext.equals("bmp")){
				fileType = "bmp";
			}
			ImageIO.write(imageBuffer, fileType, new File(filename));
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	private int printGLError(){
		int err = gl2.glGetError();
		System.out.println(err);
		return err;
	}
	
	@Override
	public void keyPressed(KeyEvent ke) {
	}

	@Override
	public void keyReleased(KeyEvent ke) {
		switch (ke.getKeyCode()) {
		case KeyEvent.VK_S:
			isSaveGL = true;
			// 再描画要求
			canvas.display();
			break;
		}

	}

	@Override
	public void keyTyped(KeyEvent arg0) {

	}
}

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

*

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)