androidのopenglesをつかってみる。三角形の描画

色々サンプルはあるけど、無駄なものがおおい。
必要な部分をしぼって描いてみた。

activity。

import android.app.Activity;
import android.os.Bundle;
import android.opengl.GLSurfaceView;

public class MainActivity extends Activity {
	private GLSurfaceView mGLView;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        mGLView = new GLSurfaceView(this);
        mGLView.setRenderer(new GLRenderer());
        this.setContentView(mGLView);
    }
    @Override
    protected void onPause(){
    	super.onPause();
    	mGLView.onPause();
    }
    @Override
    protected void onResume(){
    	super.onResume();
    	mGLView.onResume();
    }
}

レンダリング部分。

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.*;
import javax.microedition.khronos.opengles.*;

public class GLRenderer implements GLSurfaceView.Renderer{
	public GLRenderer(){
		mTriangle = new Triangle();
	}
	// Renderer implements
	//-------------------------------
	@Override
	public void onDrawFrame(GL10 gl){
		// Display Clear
		gl.glClearColor(1.0f,1.0f,0, 0);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		
		// model mode
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		// move view point
		gl.glTranslatef(0, 0, -1.5f);
		
		// draw triangle 
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		mTriangle.draw(gl);
	}
	
	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height){
		// set viewport
		gl.glViewport(0, 0, width, height);
		// frustum rendering area in perspective projection system
		float ratio = (float) width/ height;
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
	}
	
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config){
		
	}
	
	private Triangle mTriangle;
}

三角形。

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;
import javax.microedition.khronos.opengles.*;

public class Triangle{
	public Triangle(){
		// set vertices and colors
		int one = 0x10000;
		int vertices[] ={
				-one,0,0, // left
				one,0,0, // right
				0,one,0 // top
		};
		int colors[]={
				one,0,0,one, // red
				0,one,0,one, // green
				0,0,one,one // blue
		};
		byte indices[]={
				0,1,2
		};
		
		// prepare vertex
		ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length*4);
		vertexBuffer.order(ByteOrder.nativeOrder());
		mVertexBuffer = vertexBuffer.asIntBuffer();
		mVertexBuffer.put(vertices);
		mVertexBuffer.position(0);
		
		// prepare color
		ByteBuffer colorBuffer = ByteBuffer.allocateDirect(colors.length*4);
		colorBuffer.order(ByteOrder.nativeOrder());
		mColorBuffer = colorBuffer.asIntBuffer();
		mColorBuffer.put(colors);
		mColorBuffer.position(0);
		
		// prepare index
		mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
		mIndexBuffer.put(indices);
		mIndexBuffer.position(0);
	}
	
	public void draw(GL10 gl){
		// Culling
		gl.glFrontFace(GL10.GL_CW);
		// set Vertex
		gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
		// set Color
		gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
		// draw
		gl.glDrawElements(GL10.GL_TRIANGLES, 3, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
	}
	private IntBuffer mVertexBuffer;
	private IntBuffer mColorBuffer;
	private ByteBuffer mIndexBuffer;
}

こんな風に表示されるよ。

androidでopenglesをつかってみる。画面の表示

まずは、アクティビティの設定。

import android.app.Activity;
import android.os.Bundle;
import android.opengl.GLSurfaceView;

public class MainActivity extends Activity {
    private GLSurfaceView mGLView;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        mGLView = new GLSurfaceView(this);
        mGLView.setRenderer(new GLRenderer());
        this.setContentView(mGLView);
    }
    @Override
    protected void onPause(){
    	super.onPause();
    	mGLView.onPause();
    }
    @Override
    protected void onResume(){
    	super.onResume();
    	mGLView.onResume();
    }
}


次にレンダラー。

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.*;
import javax.microedition.khronos.opengles.*;

public class GLRenderer implements GLSurfaceView.Renderer{
	// Renderer
	//-------------------------------
	@Override
	public void onDrawFrame(GL10 gl){
		gl.glClearColor(1.0f, 1.0f, 0.0f, 1.0f);//背景色
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
	}
	
	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height){
		gl.glViewport(0, 0, width, height);
	}
	
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config){
		
	}
}

こんな風に表示されるよ
[,w300,h300]

androidのopenglesをつかってみる。立方体の描画

ひきつづき、Cubeの描画。

activityは変更なし。

public class MainActivity extends Activity {
	private GLSurfaceView mGLView;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        mGLView = new GLSurfaceView(this);
        mGLView.setRenderer(new GLRenderer());
        this.setContentView(mGLView);
    }
    @Override
    protected void onPause(){
    	super.onPause();
    	mGLView.onPause();
    }
    @Override
    protected void onResume(){
    	super.onResume();
    	mGLView.onResume();
    }
}

レンダリング
変わったのは gl.glEnable(GL10.GL_CULL_FACE) の部分で、カリングをonにして立方体の見えない部分を描画しないようにしている。

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GLRenderer implements GLSurfaceView.Renderer{
	public GLRenderer(){
		mCube = new Cube();
		mAngle = 0;
	}
	// Renderer implements
	//-------------------------------
	@Override
	public void onDrawFrame(GL10 gl){
		// Display Clear
		gl.glClearColor(1.0f,1.0f,0, 0);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		
		// model mode
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		// move view point
		gl.glTranslatef(0, 0, -5.0f);
		gl.glRotatef( mAngle, 1.0f, 1.0f, 0);
		
		// draw cube
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glEnable(GL10.GL_CULL_FACE); //enable background culling
		mCube.draw(gl);
		
		mAngle+=1.2f;
	}
	
	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height){
		// set viewport
		gl.glViewport(0, 0, width, height);
		// frustum rendering area in perspective projection system
		float ratio = (float) width/ height;
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
		
	}
	
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config){
		
	}
	
	private Cube mCube;
	private float mAngle;
}

Cube。

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ByteOrder;
import javax.microedition.khronos.opengles.GL10;

public class Cube{
	public Cube(){
		int one = 0x10000;
		int vertices[] = {
				-one, -one, -one,
				one, -one, -one,
				one, one, -one,
				
				-one, one, -one,
				-one, -one, one,
				one, -one, one,
				
				one, one, one,
				-one, one, one
		};
		int colors[] = {
				0, 0, 0, one,
				one, 0, 0, one,
				one, one, 0, one,
				0, one, 0, one,
				0, 0, one, one,
				one, 0, one, one,
				one, one, one, one,
				0, one, one, one
		};
		byte indices[] = {
				0,4,5, 0,5,1,
				1,5,6, 1,6,2,
				2,6,7, 2,7,3,
				3,7,4, 3,4,0,
				4,7,6, 4,6,5,
				3,0,1, 3,1,2
		};
		
		mVertexBuffer = setupIntBuffer(vertices);
		mColorBuffer = setupIntBuffer(colors);
		mIndexBuffer = setupByteBuffer(indices);
	}
	
	// draw
	//-------------------------
	public void draw(GL10 gl){
		gl.glFrontFace(GL10.GL_CW);
		gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
		gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
		gl.glDrawElements(GL10.GL_TRIANGLES, mIndexBuffer.capacity(), GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
	}
	
	// util
	//-------------------------
	private IntBuffer setupIntBuffer( int[] arrays ){
		// prepare vertex
		ByteBuffer arrayBuffer = ByteBuffer.allocateDirect(arrays.length*4);
		arrayBuffer.order(ByteOrder.nativeOrder());
		IntBuffer intBuffer = arrayBuffer.asIntBuffer();
		intBuffer.put(arrays);
		intBuffer.position(0);
		return intBuffer;
	}
	private ByteBuffer setupByteBuffer( byte []arrays ){
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(arrays.length);
		byteBuffer.put(arrays);
		byteBuffer.position(0);
		return byteBuffer;
	}
	private IntBuffer mColorBuffer;
	private IntBuffer mVertexBuffer;
	private ByteBuffer mIndexBuffer;
}

こんな感じだよ。

android 2.2 (froyo) と1.6 (donut) でカメラプレビュー

HTC desire HD (2.2) でカメラのpreviewサイズを設定すると「予期せぬエラーで終了しました」がでる。
ぐぐってみるとどうやら2.1-1 以降でこの問題が起きるらしい。

http://code.google.com/p/android/issues/detail?id=7909


試してはないけど、sdk付属のカメラサンプルですら起動失敗する様子。
ここでの議論を要約すると、

  1. 2.1-1でsdkのカメラサンプルすら動かんorz
  2. setPreviewSizeをコメントアウトすればとりあえず動くよ。
  3. あと写真サイズの変更は問題なくできるよ。
  4. でもそれってなんかやだよね。
  5. このコード使ってちょ
    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.05;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        Camera.Parameters parameters = mCamera.getParameters();

        List<Size> sizes = parameters.getSupportedPreviewSizes();
        Size optimalSize = getOptimalPreviewSize(sizes, w, h);
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);

        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }

という感じだった。
これだけだとgetSupportedPreviewSizes()がapi level 5以降でないと対応してないから、HT-03AとかXperiaだとエラー出る。
なので汎用的に1.6でも2.2でも動くようにするにはオーバーラップ関数を自分で作らなくちゃならない。

同じ問題に直面してリフレクションで実装してる例があったで参考にした。
http://labs.techfirm.co.jp/android/cho/1647

これでparameters.getSupportedPreviewSizes()の部分をReflect.getSupportedParameters(params)に変更してあげればダイジョブなはず。

実際に書いたコード

import android.hardware.Camera;
import android.hardware.Camera.*;
import android.view.*;
import android.content.*;
import android.util.*;
import java.io.*;
import java.util.*;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback{
	private final static String LOGTAG="CameraPreview";
	protected SurfaceHolder holder;
	protected Camera camera;
	public CameraPreview(Context context){
		super(context);
		holder=getHolder();
		holder.addCallback(this);
		holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
	}
	
	public void surfaceCreated(SurfaceHolder holder){
		camera=camera.open();
		try{
			camera.setPreviewDisplay(holder);
		}catch(IOException e){
			Log.d(LOGTAG,"setPreviewDisplay exception");
		}
	}
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
		camera.stopPreview();
		Camera.Parameters params=camera.getParameters();
		// params.setPreviewFormat(PixelFormat.JPEG); //if clear this comment, it cause setParameter Exception in api-level 5 emulator.
		List<Size> sizes=Reflect.getSupportedPreviewSizes(params);
		if(null != sizes){
			//for(int i=0;i<sizes.size();i++){
			//	Size s=sizes.get(i);
			//	Log.d(LOGTAG,"["+i+"]:"+s.width+","+s.height);
			//}
			Size optimalSize = getOptimalPreviewSize(sizes,width,height);
			//Log.d(LOGTAG,"optimalsize:"+optimalSize.width+","+optimalSize.height);
			//Log.d(LOGTAG,"defaultsize:"+width+","+height);
			params.setPreviewSize(optimalSize.width, optimalSize.height);
		}else{
			//Log.d(LOGTAG,"set previewsize normally:"+width+","+height);
			params.setPreviewSize(width, height);
		}
		camera.setParameters(params);
		camera.startPreview();
	}
	public void surfaceDestroyed(SurfaceHolder holder){
		camera.stopPreview();
		camera.release();
	}
	
	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
		final double ASPECT_TOLERANCE = 0.05;
		double targetRatio = (double) w / h;
		if (sizes == null) return null;
		
		Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;
		
		int targetHeight = h;
		
		// Try to find an size match aspect ratio and size
		for (Size size : sizes) {
		    double ratio = (double) size.width / size.height;
		    if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
		    if (Math.abs(size.height - targetHeight) < minDiff) {
		        optimalSize = size;
		        minDiff = Math.abs(size.height - targetHeight);
		    }
		}
		
		// Cannot find the one match the aspect ratio, ignore the requirement
		if (optimalSize == null) {
		    minDiff = Double.MAX_VALUE;
		    for (Size size : sizes) {
		        if (Math.abs(size.height - targetHeight) < minDiff) {
		            optimalSize = size;
		            minDiff = Math.abs(size.height - targetHeight);
		        }
		    }
		}
		return optimalSize;
	}
}

Reflect.javaはもらってきたものから特に変更なし

import android.hardware.Camera;
import android.hardware.Camera.*;
import android.util.Log;
import java.lang.reflect.*;
import java.util.*;

public class Reflect{
	private final static String LOGTAG="Reflect";
	private static Method Parameters_getSupportedPreviewSizes;
	static{
		initCompatibility();
	}
	private static void initCompatibility(){
		try{
			Parameters_getSupportedPreviewSizes = Camera.Parameters.class
				.getMethod("getSupportedPreviewSizes",new Class[]{});
		}catch(NoSuchMethodException e){
			Log.d(LOGTAG,"no such method exception");
		}
	}
	@SuppressWarnings("unchecked")
	public static List<Size> getSupportedPreviewSizes(Camera.Parameters p){
		try{
			if(Parameters_getSupportedPreviewSizes != null){
				return (List<Size>)Parameters_getSupportedPreviewSizes.invoke(p);
			}else{
				return null;
			}
		}catch(InvocationTargetException e){
			Log.e(LOGTAG,"InvocationTargetException");
			Throwable cause=e.getCause();
			if(cause instanceof RuntimeException){
				throw (RuntimeException)cause;
			}else if(cause instanceof Error){
				throw (Error)cause;
			}else{
				throw new RuntimeException(e);
			}
		}catch(IllegalAccessException e){
			Log.e(LOGTAG,"IllegalAccessException");
			return null;
		}
	}
}

パーミッションの設定は

SDKのバージョンは4でテスト。

あとAndroidManifest.xmlのActivityに横向き、全表示追加。
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"


HT-03A,HTC desire HDで実機動作の確認しました。
emulatorは1.6と2.2でちゃんと動きます。