Androidアプリ開発 OpenGL アルファブレンド 物体のソート

アルファブレンドで正しくドロイド君を表示させるため、視点からの距離でソートするように修正していく。ついでなので、四角形にしてちゃんと表示できるようにも変更しよう。
まず、Modelの頂点データを修正して、三角形をふたつ作る。また、物体の中心位置をフィールドx,y,zで覚えておくようにする。これでソートする。

public class Model {
private FloatBuffer buffer;			// 頂点用バッファ
private FloatBuffer normalBuffer;	// 法線用バッファ
private FloatBuffer textureBuffer;	// テクスチャ用バッファ
private float x, y, z;
public Model(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
float vertex[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f,  1.0f, 0.0f,
1.0f,  1.0f, 0.0f,
-1.0f,  1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
};
ByteBuffer vb = ByteBuffer.allocateDirect(vertex.length * 4);
vb.order(ByteOrder.nativeOrder());
buffer = vb.asFloatBuffer();
buffer.put(vertex);
buffer.position(0);
float normal[] = {
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
};
ByteBuffer nb = ByteBuffer.allocateDirect(normal.length * 4);
nb.order(ByteOrder.nativeOrder());
normalBuffer = nb.asFloatBuffer();
normalBuffer.put(normal);
normalBuffer.position(0);
float texture[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};
ByteBuffer tb = ByteBuffer.allocateDirect(texture.length * 4);
tb.order(ByteOrder.nativeOrder());
textureBuffer = tb.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
}

次、視点との距離を計算するメソッドを作成する。

public float distance(float ex, float ey, float ez){
double dx = this.x - ex;
double dy = this.y - ey;
double dz = this.z - ez;
return (float)Math.sqrt(dx * dx + dy * dy + dz * dz);
}

次、drawでマトリックスのプッシュ、移動、ポップを行うようにする。移動は、フィールドの座標値で行う。頂点が増えたので、glDrawArraysの頂点数を6に増加させる。

public void draw(GL10 gl) {
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(x, y, z);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 6);
gl.glPopMatrix();		// マトリックスを戻す
}

次、モデルのリストを作成する。

public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
private ArrayList<Model> models = new ArrayList<Model>();
private float angle = 0.0f;
private float eyepos[] = new float[3];
private Context context;

次、onSurfaceCreatedで、モデルを4つ作成してリストに入れる。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
eyepos[0] = 0;
eyepos[1] = 0;
eyepos[2] = 3;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
// テクスチャを生成
int textures[] = new int[1];
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
models.add(new Model(1, 0, 0));
models.add(new Model(-1, 0, 0));
models.add(new Model(1, 0, -2));
models.add(new Model(-1, 0, -2));
}

次、ソート時の比較用クラスを作成する。

class ModelComparator implements Comparator<Model> {
public int compare(Model s, Model t) {
float ds = s.distance(eyepos[0], eyepos[1], eyepos[2]);
float dt = t.distance(eyepos[0], eyepos[1], eyepos[2]);
if ( ds == dt ) return 0;
else if ( ds < dt ) return 1;
else return -1;
}
}

そして最後、ソートして描画する。

@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// ライティングをON
gl.glEnable(GL10.GL_LIGHTING);
// 光源を有効にして位置を設定
gl.glEnable(GL10.GL_LIGHT0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, white, 0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
// カメラ位置を設定
GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
/*
// デプステスト
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glDepthMask(true);
*/
// アルファブレンド
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
/*
// アルファテスト
gl.glEnable(GL10.GL_ALPHA_TEST);
gl.glAlphaFunc(GL10.GL_GEQUAL, 0.1f);
*/
// 陰面消去
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);
// スムースシェーディング
gl.glShadeModel(GL10.GL_SMOOTH);
// マテリアル
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
// テクスチャ
gl.glEnable(GL10.GL_TEXTURE_2D);
// ソートして描画
Collections.sort(models, new ModelComparator());
for ( Model m : models ){
m.draw(gl);
}
}

ほえー、けっこう大変だったが、我ながら手際よくできたと思っている。

結果はごらんのとおり。
変更部分が結構多かったので、念のためソース全体を載せておく。
SampleGLSurfaceView.java

package cx.fam.asai.TestOpenGL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
public class SampleGLSurfaceView extends GLSurfaceView implements OnGestureListener {
private ArrayList<Model> models = new ArrayList<Model>();
private float angle = 0.0f;
private float eyepos[] = new float[3];
private Context context;
// レンダラークラス
class OpenGLRenderer implements Renderer {
float lightpos[] = {0.0f, 0.0f, 4.0f, 0.0f};
float red[] = {1.0f, 0.0f, 0.0f, 1.0f};
float green[] = {0.0f, 1.0f, 0.0f, 1.0f};
float blue[] = {0.0f, 0.0f, 1.0f, 1.0f};
float white[] = {1.0f, 1.0f, 1.0f, 1.0f};
float gray[] = {0.5f, 0.5f, 0.5f, 1.0f};
float yellow[] = {1.0f, 1.0f, 0.0f, 1.0f};
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// ライティングをON
gl.glEnable(GL10.GL_LIGHTING);
// 光源を有効にして位置を設定
gl.glEnable(GL10.GL_LIGHT0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, white, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, white, 0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
// カメラ位置を設定
GLU.gluLookAt(gl, eyepos[0], eyepos[1], eyepos[2], 0, 0, 0, 0, 1, 0);
/*
// デプステスト
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glDepthMask(true);
*/
// アルファブレンド
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
/*
// アルファテスト
gl.glEnable(GL10.GL_ALPHA_TEST);
gl.glAlphaFunc(GL10.GL_GEQUAL, 0.1f);
*/
// 陰面消去
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);
// スムースシェーディング
gl.glShadeModel(GL10.GL_SMOOTH);
// マテリアル
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0);
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f);
// テクスチャ
gl.glEnable(GL10.GL_TEXTURE_2D);
// ソートして描画
Collections.sort(models, new ModelComparator());
for ( Model m : models ){
m.draw(gl);
}
}
class ModelComparator implements Comparator<Model> {
public int compare(Model s, Model t) {
float ds = s.distance(eyepos[0], eyepos[1], eyepos[2]);
float dt = t.distance(eyepos[0], eyepos[1], eyepos[2]);
if ( ds == dt ) return 0;
else if ( ds < dt ) return 1;
else return -1;
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 50f, (float)width / height, 0.01f, 100f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
eyepos[0] = 0;
eyepos[1] = 0;
eyepos[2] = 3;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
// テクスチャを生成
int textures[] = new int[1];
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
models.add(new Model(1, 0, 0));
models.add(new Model(-1, 0, 0));
models.add(new Model(1, 0, -2));
models.add(new Model(-1, 0, -2));
}
}
private OpenGLRenderer renderer;
// サーフェースビューのコンストラクタ
public SampleGLSurfaceView(Context context) {
super(context);
this.context = context;
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
renderer = new OpenGLRenderer();
setRenderer(renderer);
}
@Override
public boolean onDown(MotionEvent arg0) {
return false;
}
@Override
public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
float arg3) {
return false;
}
@Override
public void onLongPress(MotionEvent arg0) {
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distx, float disty) {
eyepos[0] += distx * 0.01;
eyepos[1] += disty * 0.01;
return true;
}
@Override
public void onShowPress(MotionEvent arg0) {
}
@Override
public boolean onSingleTapUp(MotionEvent arg0) {
return false;
}
}

Model.java

package cx.fam.asai.TestOpenGL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Model {
private FloatBuffer buffer;			// 頂点用バッファ
private FloatBuffer normalBuffer;	// 法線用バッファ
private FloatBuffer textureBuffer;	// テクスチャ用バッファ
private float x, y, z;
public Model(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
float vertex[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f,  1.0f, 0.0f,
1.0f,  1.0f, 0.0f,
-1.0f,  1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
};
ByteBuffer vb = ByteBuffer.allocateDirect(vertex.length * 4);
vb.order(ByteOrder.nativeOrder());
buffer = vb.asFloatBuffer();
buffer.put(vertex);
buffer.position(0);
float normal[] = {
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
};
ByteBuffer nb = ByteBuffer.allocateDirect(normal.length * 4);
nb.order(ByteOrder.nativeOrder());
normalBuffer = nb.asFloatBuffer();
normalBuffer.put(normal);
normalBuffer.position(0);
float texture[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};
ByteBuffer tb = ByteBuffer.allocateDirect(texture.length * 4);
tb.order(ByteOrder.nativeOrder());
textureBuffer = tb.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
}
public float distance(float ex, float ey, float ez){
double dx = this.x - ex;
double dy = this.y - ey;
double dz = this.z - ez;
return (float)Math.sqrt(dx * dx + dy * dy + dz * dz);
}
public void draw(GL10 gl) {
gl.glPushMatrix();		// マトリックス記憶
gl.glTranslatef(x, y, z);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 6);
gl.glPopMatrix();		// マトリックスを戻す
}
}

投稿者プロフィール

asai
asai
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript