パーティクル効果を演出してみる ( Android OpenGL フレームワーク “Rajawali” と戯れる #05 )
Android OpenGL フレームワーク "Rajawali" と戯れるシリーズ
第 05 回目は、パーティクル効果を演出します。
パーティクル用オブジェクトおよびマテリアル
Rajawali には、パーティクル専用のオブジェクトとマテリアルのクラス ( Particle と ParticleMaterial ) が用意されています。Particle は、第 02 回目の記事で紹介した Cube や Sphere などと同じく BaseObject3D のサブクラスです。
アーキテクチャは、ParticleMaterial を保持した Particle サブクラスのオブジェクトを RajawaliRenderer 上に addChild() するだけ…といったシンプルな内容なのですが、Particle のサブクラス構築がパーティクルをアレンジするための重要なポイントとなります。
Particle のサブクラスで定義する情報
Particle のサブクラスでは、主に以下のような情報を定義します。
- パーティクル数
- パーティクルの頂点
- パーティクルの速度
- パーティクルの法線
- テクスチャ
- ブレンド情報
実装例
Lesson04Renderer.java
package jp.classmethod.sample.renderer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; import jp.classmethod.sample.R; import rajawali.Camera; import rajawali.Geometry3D; import rajawali.materials.ParticleMaterial; import rajawali.materials.TextureInfo; import rajawali.math.Number3D; import rajawali.primitives.Particle; import rajawali.renderer.RajawaliRenderer; import rajawali.util.BufferUtil; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLES20; import android.util.FloatMath; /** * RajawaliRenderer のサブクラス */ public class Lesson04Renderer extends RajawaliRenderer { /** 総フレーム数 */ private final int MAX_FRAMES = 300; /** フレームカウンタ */ private int mFrameCount; /** カウンタ移動量 */ private int mDisplacement = 1; /** パーティクルのオブジェクト */ private SampleParticle mParticle; /** コンストラクタ */ public Lesson04Renderer(Context context) { super(context); setFrameRate(60); } @Override /** シーン初期化 */ protected void initScene() { mCamera.setPosition(0, 0, -10); mParticle = new SampleParticle(); addChild(mParticle); } @Override /** フレーム描画 */ public void onDrawFrame(GL10 glUnused) { super.onDrawFrame(glUnused); mParticle.setTime((float) mFrameCount * .2f); mFrameCount += mDisplacement; if(mFrameCount >= MAX_FRAMES) { mDisplacement = -1; mFrameCount = MAX_FRAMES; } else if(mFrameCount <= 0) { mDisplacement = 1; mFrameCount = 0; } } /** パーティクルのサンプルクラス */ private class SampleParticle extends Particle { /** 摩擦係数 */ protected Number3D mFriction; /** 速度バッファ */ protected FloatBuffer mVelocityBuffer; /** 頂点データへのポインタ ( オフセット ) */ protected FloatBuffer mAnimOffsetBuffer; /** 速度バッファのハンドル */ protected int mVelocityBufferHandle; /** 時間 ( アニメーションのフレーム ) */ public void setTime(float time) { mTime = time; } /** @private */ private float mTime; @Override /** リロード */ public void reload() { super.reload(); initVelocityBuffer(); } /** コンストラクタ */ public SampleParticle() { super(); } @Override /** 初期化処理 */ protected void init() { setDrawingMode(GLES20.GL_POINTS); setTransparent(true); final int numParticles = 5000; //パーティクル数 float[] vertices = new float[numParticles * 3]; //頂点 float[] velocity = new float[numParticles * 3]; //速度 float[] normals = new float[numParticles * 3]; //法線 float[] animOffsets = new float[numParticles]; //オフセット int[] indices = new int[numParticles]; //インデックス int i, index = 0; for(i = 0; i < numParticles; ++i) { index = i * 3; vertices[index] = 0; vertices[index + 1] = 0; vertices[index + 2] = 0; velocity[index] = -.2f + ((float)Math.random() * .4f); velocity[index + 1] = -.2f + ((float)Math.random() * .4f); velocity[index + 2] = -.2f + ((float)Math.random() * .4f); normals[index] = 0; normals[index + 1] = 0; normals[index + 2] = 1; indices[i] = i; animOffsets[i] = FloatMath.floor((float)Math.random() * 64); } mVelocityBuffer = ByteBuffer .allocateDirect(velocity.length * Geometry3D.FLOAT_SIZE_BYTES) .order(ByteOrder.nativeOrder()).asFloatBuffer(); BufferUtil.copy(velocity, mVelocityBuffer, velocity.length, 0); mVelocityBuffer.position(0); mAnimOffsetBuffer = ByteBuffer .allocateDirect(animOffsets.length * Geometry3D.FLOAT_SIZE_BYTES) .order(ByteOrder.nativeOrder()).asFloatBuffer(); BufferUtil.copy(animOffsets, mAnimOffsetBuffer, animOffsets.length, 0); mAnimOffsetBuffer.position(0); mFriction = new Number3D(.95f, .95f, .95f); initVelocityBuffer(); setBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); //ブレンド setData(vertices, normals, null, null, indices); Resources r = mContext.getResources(); Bitmap particleBitmap = BitmapFactory.decodeResource(r, R.drawable.taiga); TextureInfo particleTexture = mTextureManager.addTexture(particleBitmap); setMaterial(new ParticleMaterial(), true); addTexture(particleTexture); setPointSize(200); } @Override /** シェーダのパラメータ設定 */ protected void setShaderParams(Camera camera) { super.setShaderParams(camera); ParticleMaterial particleShader = (ParticleMaterial) mParticleShader; particleShader.setAnimOffsets(mAnimOffsetBuffer); particleShader.setFriction(mFriction); particleShader.setVelocity(mVelocityBufferHandle); particleShader.setMultiParticlesEnabled(true); particleShader.setTime(mTime); camera.setRotZ(camera.getRotZ() + .75f); particleShader.setCameraPosition(camera.getPosition()); } /** 速度バッファの初期化 ( 1 つ目のバッファオブジェクトに速度データ配列を転送 ) */ protected void initVelocityBuffer() { int buff[] = new int[1]; GLES20.glGenBuffers(1, buff, 0); mVelocityBufferHandle = buff[0]; GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVelocityBufferHandle); GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVelocityBuffer.limit() * Geometry3D.FLOAT_SIZE_BYTES, mVelocityBuffer, GLES20.GL_DYNAMIC_DRAW); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); } } } [/java]
出力結果
次回
パーティクルを使用したブレンド効果について解説します。