Android で OpenGL ES 2.0 事始め 〜JNI 編〜
数回にわたり Android で OpenGL ES 2.0 を使用した超絶簡単なサンプルを紹介して参りましたが、OpenGL を使用しているのだから、やはりネイティブな C/C++ でもっと速度を向上させたいと思うのが実践的です。 前回の記事では Android NDK での開発環境の構築手順について紹介しました。そこで今回は Beginning サンプルを JNI 化したいと思います。
今回のソースは、
- AndroidManifest.xml
- Activity
- GLSurfaceView.Renderer
- JNI ブリッジクラス
- Android.mk
- Application.mk
- C/C++ ソースファイル
です。
それではご覧ください。
AndroidManifest.xml
Beginning サンプルのままです。変更はありません。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.orangesignal.android.example.gles20" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> <!-- OpenGL ES 2.0 を使用するので API レベル 8 以上とします。 --> <uses-sdk android:minSdkVersion="8" /> <!-- Google プレイ (旧 Android マーケット) からダウンロード可能な端末を OpenGL ES 2.0 を使用可能な端末に制限します。 --> <uses-feature android:glEsVersion="0x00020000" /> <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <activity android:name=".ui.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Activity
このクラスも Beginning サンプルから変更はありません。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ package com.orangesignal.android.example.gles20.ui; import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; import com.orangesignal.android.example.gles20.gl.SimpleRenderer; /** * このアプリケーションの主たるアクティビティを提供します。 * * @author 杉澤 浩二 */ public final class MainActivity extends Activity { private GLSurfaceView mGLSurfaceView; ////////////////////////////////////////////////////////////////////////// // ライフサイクル @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); mGLSurfaceView = new GLSurfaceView(this); mGLSurfaceView.setEGLContextClientVersion(2); // OpenGL ES 2.0 を使用するように構成します。 mGLSurfaceView.setRenderer(new SimpleRenderer(getApplicationContext())); setContentView(mGLSurfaceView); } @Override public void onResume() { super.onResume(); mGLSurfaceView.onResume(); } @Override public void onPause() { super.onPause(); mGLSurfaceView.onPause(); } }
GLSurfaceView.Renderer
今回は OpenGL の処理を JNI で行うため、onSurfaceCreated、onSurfaceChanged、onDrawFrame それぞれで JNI のネイティブメソッドを呼び出しているだけとなります。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ package com.orangesignal.android.example.gles20.gl; 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 com.orangesignal.android.example.gles20.R; /** * ビットマップをテクスチャとして読み込んで {@link GLSurfaceView} の描画領域いっぱいに描画するだけのシンプルな {@link GLSurfaceView.Renderer} を提供します。 * * @author 杉澤 浩二 */ public final class SimpleRenderer implements GLSurfaceView.Renderer { /** * コンテキストを保持します。 */ private final Context mContext; ////////////////////////////////////////////////////////////////////////// // コンストラクタ /** * コンストラクタです。 * * @param context コンテキスト */ public JniBridge(final Context context) { mContext = context; } ////////////////////////////////////////////////////////////////////////// // オーバーライド メソッド @Override public void onSurfaceCreated(final GL10 gl, final EGLConfig config) { // テクスチャで使用するビットマップを読み込んでピクセルデータを取得します。 final Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.sample); final int width = bitmap.getWidth(); final int height = bitmap.getHeight(); final int[] pixels = new int[width * height]; bitmap.getPixels(pixels, 0, width, 0, 0, width, height); bitmap.recycle(); JniBridge.nativeOnSurfaceCreated(pixels, width, height); } @Override public void onSurfaceChanged(final GL10 gl, final int width, final int height) { JniBridge.nativeOnSurfaceChanged(width, height); } @Override public void onDrawFrame(final GL10 gl) { JniBridge.nativeOnDrawFrame(); } }
JNI ブリッジクラス
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ package com.orangesignal.android.example.gles20.gl; /** * JNI への橋渡しをするブリッジクラスを提供します。 * * @author 杉澤 浩二 */ public final class JniBridge { /** * JNI のライブラリ名です。 */ private static final String LIBRARY_NAME = "example"; static { // JNI のライブラリ (モジュール) をロードします。 System.loadLibrary(LIBRARY_NAME); } public static native void nativeOnSurfaceCreated(int[] pixels, int width, int height); public static native void nativeOnSurfaceChanged(int width, int height); public static native void nativeOnDrawFrame(); }
Android.mk
いわゆる make ファイルです。モジュール名やソースファイル群の指定、使用するライブラリの指定などを行います。LOCAL_MODULE で指定した名前と Java 側の System.loadLibrary で指定している名前が一致していることを確認して下さい。
# # Copyright (c) 2012 OrangeSignal.com All Rights Reserved. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := example LOCAL_SRC_FILES := main.cpp # see ${ANDROID_NDK_HOME}/docs/STABLE-APIS.html LOCAL_LDLIBS := -llog # ログ機能を使用するので Android-specific Log Support を追加します。 LOCAL_LDLIBS += -lGLESv2 # OpenGL ES 2.0 を使用するので OpenGL ES 2.0 ライブラリを追加します。 include $(BUILD_SHARED_LIBRARY)
Application.mk
Application.mk はマストではないですが、対応プラットフォームや対応アーキテクチャなどを指定するため、実際の開発時には実質的にマストとなってくるかと思います。このサンプルでは APP_ABI にとりあえず all を指定しています。実際のアプリ開発では、対象端末に合わせて APP_ABI や APP_PLATFORM などを最適化することになります。
#APP_STL := gnustl_static #APP_CPPFLAGS := -frtti -fexceptions APP_ABI := all #APP_ABI := armeabi armeabi-v7a #APP_PLATFORM := android-8
main.cpp
今回の肝となる C/C++ のソースファイルです。OpenGL ES 2.0 の処理を C 側へ持ってきているだけなので Java の時と大筋違いはないですがエラーチェックを若干盛っています。テクスチャの読み込みのところでは Android の Bitmap が ARGB なのに対して OpenGL が扱える RGBA へ変換しています。また glVertexAttribPointer では Java の場合と異なり GLfloat により直接パラメータの指定を行っています。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <android/log.h> ////////////////////////////////////////////////////////////////////////////// #define LOG_TAG "example-jni" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) ////////////////////////////////////////////////////////////////////////////// #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceCreated(JNIEnv* env, jobject thiz, jintArray pixels, jint width, jint height); JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceChanged(JNIEnv* env, jobject thiz, jint width, jint height); JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnDrawFrame(JNIEnv* env, jobject thiz); #ifdef __cplusplus } #endif ////////////////////////////////////////////////////////////////////////////// static void printGLString(const char *name, GLenum s) { const char *v = (const char *) glGetString(s); LOGI("GL %s = %s\n", name, v); } /** * 指定された直前の OpenGL API 操作についてエラーが発生しているかどうか検証します。 * * @param op 検証する直前に操作した OpenGL API 名 */ static void checkGlError(const char* op) { for (GLint error = glGetError(); error; error = glGetError()) { LOGE("after %s() glError (0x%x)\n", op, error); } } /** * 指定されたシェーダのソースコードをコンパイルします。 * * @param shaderType シェーダの種類 * @param shaderCode シェーダのソースコード * @return シェーダハンドルまたは <code>0</code> */ static GLuint loadShader(GLenum shaderType, const char *shaderCode){ GLuint shader = glCreateShader(shaderType); if (shader != 0) { glShaderSource(shader, 1, &shaderCode, NULL); glCompileShader(shader); GLint compiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen) { char* buf = (char*) malloc(infoLen); if (buf) { glGetShaderInfoLog(shader, infoLen, NULL, buf); LOGE("Could not compile shader %d:\n%s\n",shaderType, buf); free(buf); } } glDeleteShader(shader); } } return shader; } static GLuint createProgram(const char* vertexCode, const char* fragmentCode) { // バーテックスシェーダをコンパイルします。 GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexCode); if (!vertexShader) { LOGE("vertex missed!"); return 0; } // フラグメントシェーダをコンパイルします。 GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, fragmentCode); if (!pixelShader) { LOGE("fragment missed!"); return 0; } // プログラムを生成して、プログラムへバーテックスシェーダとフラグメントシェーダを関連付けます。 GLuint program = glCreateProgram(); if (program) { // プログラムへバーテックスシェーダを関連付けます。 glAttachShader(program, vertexShader); checkGlError("glAttachShader"); // プログラムへフラグメントシェーダを関連付けます。 glAttachShader(program, pixelShader); checkGlError("glAttachShader"); glLinkProgram(program); GLint linkStatus = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { GLint bufLength = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); if (bufLength) { char* buf = (char*) malloc(bufLength); if (buf) { glGetProgramInfoLog(program, bufLength, NULL, buf); LOGE("Could not link program:\n%s\n", buf); free(buf); } } LOGE("program missed!"); glDeleteProgram(program); program = 0; } } return program; } ////////////////////////////////////////////////////////////////////////////// /** * ポリゴン描画用のバーテックスシェーダ (頂点シェーダ) のソースコード */ static const char VERTEX_SHADER[] = "attribute vec4 position;" "attribute vec2 texcoord;" "varying vec2 texcoordVarying;" "void main() {" "gl_Position = position;" "texcoordVarying = texcoord;" "}"; /** * 色描画用のピクセル/フラグメントシェーダのソースコード */ static const char FRAGMENT_SHADER[] = "precision mediump float;" "varying vec2 texcoordVarying;" "uniform sampler2D texture;" "void main() {" "gl_FragColor = texture2D(texture, texcoordVarying);" "}"; static GLuint program; static GLuint position; static GLuint texcoord; static GLuint textures[1]; JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceCreated(JNIEnv* env, jobject thiz, jintArray pixels, jint width, jint height) { LOGI("nativeOnSurfaceCreated"); printGLString("Version", GL_VERSION); printGLString("Vendor", GL_VENDOR); printGLString("Renderer", GL_RENDERER); printGLString("Extensions", GL_EXTENSIONS); // プログラムを生成して使用可能にします。 program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (!program) { LOGE("Could not create program."); } glUseProgram(program); checkGlError("glUseProgram"); // シェーダで使用する変数のハンドルを取得し使用可能にします。 position = glGetAttribLocation(program, "position"); checkGlError("glGetAttribLocation position"); glEnableVertexAttribArray(position); texcoord = glGetAttribLocation(program, "texcoord"); checkGlError("glGetAttribLocation texcoord"); glEnableVertexAttribArray(texcoord); textures[0] = glGetUniformLocation(program, "texture"); checkGlError("glGetUniformLocation texture"); // テクスチャを作成します。(サーフェスが作成される度にこれを行う必要があります) unsigned int* _pixels = (unsigned int*) env->GetPrimitiveArrayCritical(pixels, 0); // ARGB ⇒ RGBA へ変換します。 const int size = width * height; for (int i = 0; i < size; i++) { unsigned int px = _pixels[i]; _pixels[i] = ( ((px ) & 0xFF000000) | // A ((px << 16) & 0x00FF0000) | // R ((px ) & 0x0000FF00) | // G ((px >> 16) & 0x000000FF) // B ); } // テクスチャオブジェクトを作成して画像を与えます。 glGenTextures(1, textures); glBindTexture(GL_TEXTURE_2D, textures[0]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, _pixels); // テクスチャを拡大/縮小する方法を設定します。 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // 縮小するときピクセルの中心に最も近いテクスチャ要素で補完 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 拡大するときピクセルの中心付近の線形で補完 env->ReleasePrimitiveArrayCritical(pixels, _pixels, JNI_ABORT); } JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceChanged(JNIEnv* env, jobject thiz, jint width, jint height) { // ビューポートを設定します。 glViewport(0, 0, width, height); checkGlError("glViewport"); } /** * 頂点データです。 */ static const GLfloat VERTEXS[] = { -1.0f, 1.0f, 0.0f, // 左上 -1.0f, -1.0f, 0.0f, // 左下 1.0f, 1.0f, 0.0f, // 右上 1.0f, -1.0f, 0.0f // 右下 }; /** * テクスチャ (UV マッピング) データです。 */ static const GLfloat TEXCOORDS[] = { 0.0f, 0.0f, // 左上 0.0f, 1.0f, // 左下 1.0f, 0.0f, // 右上 1.0f, 1.0f // 右下 }; JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnDrawFrame(JNIEnv* env, jobject thiz) { // XXX - このサンプルではテクスチャの簡単な描画だけなので深さ関連の有効/無効や指定は一切していません。 // 背景色を指定して背景を描画します。 glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 背景とのブレンド方法を設定します。 glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 単純なアルファブレンド // テクスチャの指定 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[0]); glUniform1i(textures[0], 0); glVertexAttribPointer(texcoord, 2, GL_FLOAT, false, 0, TEXCOORDS); glVertexAttribPointer(position, 3, GL_FLOAT, false, 0, VERTEXS); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); }
作成して思ったのですが、NDK の hello-gl2 とあまり変わらないかもです。
Android で OpenGL ES 2.0 事始め JNI 編 〜android/bitmap.h を使ってみる〜
先程のサンプルにちょっと手お加えて今度は、ビットマップを JNI 側から読み込むように変更してみます。(この方式は Java 側からピクセルデータを渡す方式と比べて JNI 側で画像形式のハンドリングができる特徴があります)
今回は前回のサンプルからの差分のみを紹介します。変更が発生するソースは
- SimpleRenderer
- JniBridge
- Android.mk
- Application.mk
- main.cpp
です。
SimpleRenderer
onSurfaceCreated を変更しています。今回は JNI でビットマップを読み込むので onSurfaceCreated でビットマップからピクセルデータを読み込まず Bitmap オブジェクトを渡すように変更しています。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ package com.orangesignal.android.example.gles20.gl; 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 com.orangesignal.android.example.gles20.R; /** * ビットマップをテクスチャとして読み込んで {@link GLSurfaceView} の描画領域いっぱいに描画するだけのシンプルな {@link GLSurfaceView.Renderer} を提供します。 * * @author 杉澤 浩二 */ public final class SimpleRenderer implements GLSurfaceView.Renderer { : 略 : @Override public void onSurfaceCreated(final GL10 gl, final EGLConfig config) { final Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.sample); JniBridge.nativeOnSurfaceCreated(bitmap); bitmap.recycle(); } : 略 : }
JniBridge
nativeOnSurfaceCreated を変更しています。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ package com.orangesignal.android.example.gles20.gl; import android.graphics.Bitmap; /** * JNI への橋渡しをするブリッジクラスを提供します。 * * @author 杉澤 浩二 */ public final class JniBridge { : 略 : public static native void nativeOnSurfaceCreated(Bitmap bitmap); : 略 : }
Android.mk
JNI で Bitmap から読み込むので、jnigraphics ライブラリを追加しています。
# # Copyright (c) 2012 OrangeSignal.com All Rights Reserved. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := example LOCAL_SRC_FILES := main.cpp # see ${ANDROID_NDK_HOME}/docs/STABLE-APIS.html LOCAL_LDLIBS := -llog # ログ機能を使用するので Android-specific Log Support を追加します。 LOCAL_LDLIBS += -lGLESv2 # OpenGL ES 2.0 を使用するので OpenGL ES 2.0 ライブラリを追加します。 LOCAL_LDLIBS += -ljnigraphics # Java ビットマップオブジェクトへアクセスするので jnigraphics ライブラリを追加します。(ソースコード上で使用するには <android/bitmap.h> ヘッダファイルをインクルードする必要があります) include $(BUILD_SHARED_LIBRARY)
Application.mk
jnigraphics ライブラリは API レベル 8 から使用可能なので、APP_PLATFORM で明確化しています。
#APP_STL := gnustl_static #APP_CPPFLAGS := -frtti -fexceptions APP_ABI := all #APP_ABI := armeabi armeabi-v7a APP_PLATFORM := android-8
main.cpp
JNI で Bitmap から読み込むので、
- インクルードに android/bitmap.h を追加しています。
- nativeOnSurfaceCreated のパラメータと実装を変更しています。
- nativeOnDrawFrame での背景ブレンド方法を GL_ONE, GL_ONE_MINUS_SRC_ALPHA へ変更しています。
尚、ピクセルデータ使用時と異なり ARGB ⇒ RGBA 変換をしていないことにも注意して下さい。
/* * Copyright (c) 2012 OrangeSignal.com All Rights Reserved. */ #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <android/bitmap.h> #include <android/log.h> ////////////////////////////////////////////////////////////////////////////// : 略 : ////////////////////////////////////////////////////////////////////////////// #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceCreated(JNIEnv* env, jobject thiz, jobject bitmap); JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceChanged(JNIEnv* env, jobject thiz, jint width, jint height); JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnDrawFrame(JNIEnv* env, jobject thiz); #ifdef __cplusplus } #endif ////////////////////////////////////////////////////////////////////////////// : 略 : JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnSurfaceCreated(JNIEnv* env, jobject thiz, jobject bitmap) { LOGI("nativeOnSurfaceCreated"); printGLString("Version", GL_VERSION); printGLString("Vendor", GL_VENDOR); printGLString("Renderer", GL_RENDERER); printGLString("Extensions", GL_EXTENSIONS); // プログラムを生成して使用可能にします。 program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (!program) { LOGE("Could not create program."); } glUseProgram(program); checkGlError("glUseProgram"); // シェーダで使用する変数のハンドルを取得し使用可能にします。 position = glGetAttribLocation(program, "position"); checkGlError("glGetAttribLocation position"); glEnableVertexAttribArray(position); texcoord = glGetAttribLocation(program, "texcoord"); checkGlError("glGetAttribLocation texcoord"); glEnableVertexAttribArray(texcoord); textures[0] = glGetUniformLocation(program, "texture"); checkGlError("glGetUniformLocation texture"); // テクスチャを作成します。(サーフェスが作成される度にこれを行う必要があります) int ret; AndroidBitmapInfo info; // ビットマップオブジェクトに関する情報を取得します。 if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return; } // ビットマップの画像データをロックして画像データを操作できるようにします。 void* pixels; if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); } // テクスチャオブジェクトを作成して画像を与えます。 glGenTextures(1, textures); glBindTexture(GL_TEXTURE_2D, textures[0]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // ビットマップの画像データをアンロックします。 AndroidBitmap_unlockPixels(env, bitmap); // テクスチャを拡大/縮小する方法を設定します。 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // 縮小するときピクセルの中心に最も近いテクスチャ要素で補完 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 拡大するときピクセルの中心付近の線形で補完 } : 略 : JNIEXPORT void JNICALL Java_com_orangesignal_android_example_gles20_gl_JniBridge_nativeOnDrawFrame(JNIEnv* env, jobject thiz) { : 略 : // 背景とのブレンド方法を設定します。 glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // see http://ga29.blog.fc2.com/blog-entry-37.html : 略 : } : 略 :