パーティクルで学ぶ OpenGL ブレンディング ( Android OpenGL フレームワーク “Rajawali” と戯れる #06 )

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Android OpenGL フレームワーク "Rajawali" と戯れるシリーズ
第 06 回目は、前回解説したパーティクルを用いた OpenGL ブレンディングについて解説します。

BaseObject3D#setBlendFunc()

Rajawali では、setBlendFunc(sFactor, dFactor) を用いてブレンディングを実現することが可能ですが、結果的には下記コードが Rajawali 内部で実行されます。

GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(sFactor, dFactor);
// sFactor は RGBA ソースのブレンディング係数
// dFactor は RGBA デスティネーションのブレンディング係数

GLES20#glBlendFunc() および引数 ( ブレンディング係数 ) については下記を参照してください。
https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBlendFunc.xml

実現可能なブレンド種別 ( 合成種別 )

Flash 制作者には馴染みのあるブレンドモード (DisplayObject.blendMode) と同じようなことが GLES20#glBlendFunc() では実現可能です。( Flash ほど多様ではありませんが ) 組み合わせ例は以下のとおりです。

  • デフォルト
    GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO);
  • アルファ
    GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
  • 加算
    GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE);
  • 乗算
    GLES20.glBlendFunc(GLES20.GL_ZERO, GLES20.GL_SRC_COLOR);
  • スクリーン
    GLES20.glBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ONE);
  • XOR
    GLES20.glBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
  • 反転
    GLES20.glBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ZERO);

減算は?

上記リストを見て気づかれた方も居ると思いますが、減算合成は

GLES20.glBlendEquation(GLES20.GL_FUNC_REVERSE_SUBTRACT);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE);
GLES20.glBlendEquation(GLES20.GLES20.GL_FUNC_ADD);

といった組み合わせで実現可能らしいのですが、端末によって glBlendEquation() メソッドが非サポートである場合があるようで、使用は避けた方が良さそうです。

参考
http://d.hatena.ne.jp/o_healer/20111112/1321068474
http://d.hatena.ne.jp/o_healer/20111005/1317791670

実装例

同時に複数のブレンド種別を確認できるサンプルを用意しました。

ソース画像

ソースコード

Lesson04_02Renderer.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 Lesson04_02Renderer extends RajawaliRenderer { /** 総フレーム数 */ private final int MAX_FRAMES = 300; /** フレームカウンタ */ private int mFrameCount; /** カウンタ移動量 */ private int mDisplacement = 1; /** @private */ private SampleParticle mParticle_01; /** @private */ private SampleParticle mParticle_02; /** @private */ private SampleParticle mParticle_03; /** @private */ private SampleParticle mParticle_04; /** @private */ private SampleParticle mParticle_05; /** @private */ private SampleParticle mParticle_06; /** @private */ private SampleParticle mParticle_07; /** コンストラクタ */ public Lesson04_02Renderer(Context context) { super(context); setFrameRate(60); setBackgroundColor(0x999999); } @Override /** シーン初期化 */ protected void initScene() { mCamera.setPosition(0, 0, -5);

float xPos = 0.5f; float yPos = 1.5f; float hYPos = 0.5f;

mParticle_01 = new SampleParticle(); mParticle_01.setBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO); //デフォルト mParticle_01.setPosition(-xPos, yPos, 0); addChild(mParticle_01);

mParticle_02 = new SampleParticle(); mParticle_02.setBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); //アルファ mParticle_02.setPosition(xPos, yPos, 0); addChild(mParticle_02);

mParticle_03 = new SampleParticle(); mParticle_03.setBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); //加算 mParticle_03.setPosition(-xPos, hYPos, 0); addChild(mParticle_03);

mParticle_04 = new SampleParticle(); mParticle_04.setBlendFunc(GLES20.GL_ZERO, GLES20.GL_SRC_COLOR); //乗算 mParticle_04.setPosition(xPos, hYPos, 0); addChild(mParticle_04);

mParticle_05 = new SampleParticle(); mParticle_05.setBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ONE); //スクリーン mParticle_05.setPosition(-xPos, -hYPos, 0); addChild(mParticle_05);

mParticle_06 = new SampleParticle(); mParticle_06.setBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR); //XOR mParticle_06.setPosition(xPos, -hYPos, 0); addChild(mParticle_06);

mParticle_07 = new SampleParticle(); mParticle_07.setBlendFunc(GLES20.GL_ONE_MINUS_DST_COLOR, GLES20.GL_ZERO); //反転 mParticle_07.setPosition(-xPos, -yPos, 0); addChild(mParticle_07);

} @Override /** フレーム描画 */ public void onDrawFrame(GL10 glUnused) { super.onDrawFrame(glUnused); float frame = (float) mFrameCount * .04f; mParticle_01.setTime(frame); mParticle_02.setTime(frame); mParticle_03.setTime(frame); mParticle_04.setTime(frame); mParticle_05.setTime(frame); mParticle_06.setTime(frame); mParticle_07.setTime(frame); 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 = 50; //パーティクル数 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; float pos = 0.2f; float hpos = pos * 0.5f; for(i = 0; i < numParticles; ++i) { index = i * 3; vertices[index] = 0; vertices[index + 1] = 0; vertices[index + 2] = 0; velocity[index] = -hpos + ((float)Math.random() * pos); velocity[index + 1] = -hpos + ((float)Math.random() * pos); velocity[index + 2] = -hpos + ((float)Math.random() * pos); 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(); setData(vertices, normals, null, null, indices); Resources r = mContext.getResources(); Bitmap particleBitmap = BitmapFactory.decodeResource(r, R.drawable.taiga_alpha); 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); 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]

出力結果

ソース画像やブレンド種類にアレンジを加えることで、色々なエフェクト表現を実現できます。

次回

3D オブジェクトのインポートとテクスチャについて解説します。