パーティクルシステムでちらつきのあるテキストを描画します。

昨年の私の記事では 、Android用のOpenGLツールを使用してポストカードを作成することに専念し、「おめでとうテキストを後で追加します」というフレーズを残しました。 それで、時が来ました。



テキストは図のように見えますが、わずかにちらつき、各「アスタリスク」がスムーズに消えて表示されます(さらに、最終バージョンではテキスト自体が異なり、色が同じではなく、フォントと粒子サイズも同じです。 ) アニメーション化されたパーティクルシステムを使用して描画され、頂点配列では各ポイントの中心の座標とアニメーションの特定の「位相シフト」のみが設定されますが、アニメーション自体はシェーダーを介して行われます。



パーティクルは、このような場合に正確に作成されるポイントスプライトメカニズムを使用して描画されます。 その主な機能は、ポイントの中心の座標とサイズのみを指定し、OpenGL自体が空間座標とテクスチャ座標を含む4つのコーナー頂点と2つの三角形を生成して、同じセットをレンダリングすることです(同じ意味を持つという意味で)テクスチャ)正方形の写真。 それでは、ka [po] tの下を見てみましょう。



実際にテキスト



最初に行う必要があるのは、ポイントの座標を決定することです。 これを行うには、都合の良い場所に任意のテキストを表示するビットマップを作成します。その後、結果の画像で目的の色のポイントを見つけ、パーティクルをアタッチするポイントをランダムに選択します。



ビットマップを生成:



int width = screenWidth, height = screenHeight, fontSize = (int) (screenHeight / 8); // Create an empty, mutable bitmap Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); // get a canvas to paint over the bitmap Canvas canvas = new Canvas(bitmap); bitmap.eraseColor(Color.BLACK); // Draw the text Paint textPaint = new Paint(); textPaint.setTextSize(fontSize); textPaint.setAntiAlias(false); textPaint.setARGB(0xff, 0xff, 0xff, 0xff); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTypeface(Typeface.SANS_SERIF); // draw the text centered canvas.drawText(",", width / 2, height / 4, textPaint); canvas.drawText("!", width / 2, height / 2, textPaint); int[] pixels = new int[width * height]; bitmap.getPixels(pixels, 0, width, 0, 0, width, height); bitmap.recycle();
      
      





ここで、screenWidthとscreenHeightは、Rendererクラスの子孫のonSurfaceChanged関数の引数から派生しています。 このコードの最後から2番目の行は、パックされた整数の形式でピクセルの配列を受け取りました。 黒のドット(背景)は0xff000000で、白は0xffffffff(最上位バイトはアルファチャネル)です。



ちなみに、このテキストをテクスチャ上に引き伸ばしたい場合は、ビットマップ.recycle()を呼び出す前に 、たとえば次のような行を追加することでこれを行います。



  GLES20.glGenTextures(1, textures, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
      
      





ただし、まったく異なるものが必要です。 黒い背景に白いドットを見つけ、そこからランダムなドットを選択し、粒子の配列を作成して、すぐに頂点バッファーに変換します(前の投稿の変換関数を参照)。



  private final int mParticles = 1200; private int glParticleVB; ... int colored = 0; float[] cx = new float[width * height]; float[] cy = new float[width * height]; for (int y = 0, idx = 0; y < height; y++) for (int x = 0; x < width; x++) if ((pixels[idx++] & 0xffffff) != 0) { cx[colored] = x / (float)width; cy[colored] = y / (float)height; colored++; } float[] particleBuf = new float[3 * mParticles]; for (int i = 0, idx = 0; i < mParticles; i++, idx += 3) { int n = (int) (Math.random() * colored); particleBuf[idx + 0] = cx[n] * 2 - 1; particleBuf[idx + 1] = 1 - cy[n] * 2; particleBuf[idx + 2] = (float) Math.random(); } glParticleVB = createBuffer(particleBuf);
      
      





粒子の数は目で選択されます。 前述のように、各パーティクルには、アニメーションのペアと「位相シフト」のペアのみが含まれています。 OpenGLでは画面の下部の座標が「-1」で上部が「+1」であり、ビットマップでは画像の上部が「0」で下部が「高さ」であるため、Y座標が反転していることに注意。



粒子のテクスチャ



パーティクルテクスチャをロードします。 私はそのような写真を利用しました(私はそれを個別に生成しました)が、最終的な結果に合う場合にのみ、他の写真を使用できます。 プロジェクトのres / drawableフォルダーに画像(たとえばparticle.pngと呼ばれる)を含むファイルを配置し、その後、リソースからテクスチャをロードするためのコードを記述します。



  private int particleTex; ... public static int loadTexture(final Context context, final int resourceId) { final int[] textureHandle = new int[1]; GLES20.glGenTextures(1, textureHandle, 0); if (textureHandle[0] != 0) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; // No pre-scaling // Read in the resource final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options); // Bind to the texture in OpenGL GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]); // Set filtering GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); // Load the bitmap into the bound texture. GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); // Recycle the bitmap, since its data has been loaded into OpenGL. bitmap.recycle(); } return textureHandle[0]; } ... particleTex = loadTexture(mContext, R.drawable.particle);
      
      





ここでは、Rendererクラスインスタンスの作成時にmContextを保存したと想定しています。



シェーダー



次の効果を作成したかった:各粒子は「脈動」する、つまり 周期的に増減し、それぞれ独立して移動する必要があります。 結果を見て、別の改善点を追加しました。粒子サイズが最大値の3/4に達すると、色が白くなり始めます(そして、最大点でそうなります)。



  private final String particleVS = "precision mediump float;\n" + "attribute vec4 vPosition;\n" + "attribute float vSizeShift;\n" + "uniform float uPointSize;\n" + "uniform float uTime;\n" + "uniform vec4 uColor;\n" + "varying vec4 Color;\n" + "void main() {\n" + " float Phase = abs(fract(uTime + vSizeShift) * 2.0 - 1.0);\n" + " vec4 pColor = uColor;\n" + " if (Phase > 0.75) {\n" + " pColor.y = (Phase - 0.75) * 4.0;\n" + " };\n" + " Color = pColor;\n" + " gl_PointSize = uPointSize * Phase;\n" + " gl_Position = vPosition;\n" + "}\n"; private final String particleFS = "precision mediump float;\n" + "uniform sampler2D uTexture0;\n" + "varying vec4 Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = texture2D(uTexture0, gl_PointCoord) * Color;\n" + "}\n";
      
      





ここでの頂点シェーダーは、パーティクルサイズと色のアニメーション化を担当し、フラグメントシェーダーはgl_PointCoordシステム変数を使用してテクスチャを適用するだけであることが簡単にわかります。 このvSizeShift属性の範囲は0〜1で、uTimeに追加して小数部分を強調表示すると、各パーティクルのアニメーションフェーズ値が取得されます。 ちなみに、初期の色は紫に設定されるため、白への移行は緑の成分のためにのみ行われます。 位置をコピーして、粒子の色とサイズを決定します-これで完了です。



シェーダーをロードするためだけに残ります(再び、元の投稿のコンパイル機能を参照してください):



  private int mPProgram; private int maPPosition; private int maPSizeShift; private int muPPointSize; private int muPTime; private int muPTexture; private int muPColor; ... mPProgram = Compile(particleVS, particleFS); maPPosition = GLES20.glGetAttribLocation(mPProgram, "vPosition"); maPSizeShift = GLES20.glGetAttribLocation(mPProgram, "vSizeShift"); muPPointSize = GLES20.glGetUniformLocation(mPProgram, "uPointSize"); muPTime = GLES20.glGetUniformLocation(mPProgram, "uTime"); muPTexture = GLES20.glGetUniformLocation(mPProgram, "uTexture0"); muPColor = GLES20.glGetUniformLocation(mPProgram, "uColor");
      
      





そしてすべてを描きます。



レンダリング



すべての準備が整いました。定数を設定して描画関数を呼び出すだけです。



  private void DrawText() { GLES20.glUseProgram(mPProgram); GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, particleTex); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, glParticleVB); GLES20.glEnableVertexAttribArray(maPPosition); GLES20.glVertexAttribPointer(maPPosition, 2, GLES20.GL_FLOAT, false, 12, 0); GLES20.glEnableVertexAttribArray(maPSizeShift); GLES20.glVertexAttribPointer(maPSizeShift, 1, GLES20.GL_FLOAT, false, 12, 8); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); GLES20.glUniform1f(muPPointSize, 12); GLES20.glUniform4f(muPColor, 1, 0, 1, 1); GLES20.glUniform1i(muPTexture, 0); GLES20.glUniform1f(muPTime, (SystemClock.uptimeMillis() % 1000) / 1000.0f); GLES20.glDrawArrays(GLES20.GL_POINTS, 0, mParticles); GLES20.glDisableVertexAttribArray(maPPosition); GLES20.glDisableVertexAttribArray(maPSizeShift); }
      
      





結果







おわりに



これで、AndroidでのOpenGL ES 2.0チュートリアルのシリーズが終了し、1週間の休憩を発表しました。その後、デバイスで結果を評価し、パフォーマンスを比較できるようにする.apkファイルを投稿できます。 ただし、この間に、カードに他の特殊効果を追加したい場合、新しい記事が表示される可能性があります。



All Articles