Androidのガウスのグローぼかし効果

はじめに



結果 この壮大な作品は、いくつかの出来事のために登場しました。



まず、ビデオアクセラレーションのサポートがAndroidエミュレーターに登場しました。これにより、インターフェイスをフルスピードで動作させるだけでなく、OpenGL ES 2.0を使用してプログラムをテストすることもできます。



第二に、最愛の妻の誕生日が近づいており、新しいスマートフォンやタブレットを補完する最高のものは、彼のための手書きのはがきプログラムです。



つまり、Android チュートリアルでプロジェクトのアウトラインを作成し、.3dsファイルのダウンロード、テクスチャへのレンダリング、シェーダーのパックを使用してシェルフから古いDirect3Dプロジェクトを取り出し、JavaとOpenGL ES 2.0で書き直します。 。 おめでとうなどのテキストは後で追加されます。



AndroidでのOpenGL ES 2.0の使用に関するすべての情報は非常に断片的であることが判明し、知識は少しずつ収集されました。



そして今、より詳細に。



準備する



最初に行うことは、エミュレータでハードウェアアクセラレーションを有効にすることです。 これは、AVDマネージャー(スクリーンショットを参照してください;値を「yes」に設定することを忘れないでください)か、ファイル.android / avd / <your emulator_name> .avd / config.iniに行「hw.gpu.enabled = yes」を追加することによって行われます。 「。 ここには微妙な点が1つあります。ハードウェアアクセラレーションはスナップショットと互換性がありません。 したがって、このdawを削除します(または.iniファイルに「snapshot.present = false」と書き込みます)。







次に、上記のチュートリアルに従って、必要なものすべて、特にRendererクラスの子孫を作成します。



モデル



ここで「モデル」という言葉は、これが非常に上昇したことを意味します。 実際には、任意のオブジェクトまたはシーン全体を使用できますが、それは重要ではありません。



それ以降のすべてのコードは、Rendererクラスから継承された同じクラスにあると理解されています。



モデルをダウンロード


ここでは.3dsファイルのダウンロードコードを提供しません。それは長く、投稿はそれについてではありません(原則として、これは別の投稿の価値があります)が、最初に、多くのレーキを収集したため、モデルレンダリングコードを提供します途中で、第二に、それはほとんど完全にgl *呼び出しで構成され、第三に、いくつかの関数が以下で必要になります。 ただし、効果のみが興味深い場合は、このセクションをスキップできます。 そのため、最終的に、これらのすべてのモデルは次の構造に適合します。



class Light3D { public float[] pos; public float[] color; } class Material3D { public float[] ambient; public float[] diffuse; } class FaceMat { public Material3D material; public int faces; public short[] indexBuffer; public int bufOffset; } class Object3D { public ArrayList<FaceMat> faceMats; public int vertCount; public int indCount; public int glVertices; public int glIndices; public float[] vertexBuffer; } public class Scene3D { public ArrayList<Material3D> materials; public ArrayList<Object3D> objects; public ArrayList<Light3D> lights; public float[] ambient; }
      
      







ここでは、鏡面光源や指向性光源などの微妙な点が意図的に削除されています。シーンはすでにレンダリングに十分なほど重くなっています。 オブジェクトの頂点の配列には、6 *(頂点の数)の実数が含まれます。これは、行に記録された頂点と法線の座標です。



float / short配列からの描画は遅いことが判明しましたが、バッファーからはかなり許容できました(ドライバーとビデオコアによっては、このデータはすぐにビデオメモリに配置できます)。 ある頂点から別の頂点に、個別にインデックスを付けて変換します。 バッファを埋めた後、アクティブバッファとして0を指定して、バッファでの作業を終了することを忘れないでください。



  int[] genbuf = new int[1]; private int createBuffer(float[] buffer) { FloatBuffer floatBuf = ByteBuffer.allocateDirect(buffer.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); floatBuf.put(buffer); floatBuf.position(0); GLES20.glGenBuffers(1, genbuf, 0); int glBuf = genbuf[0]; GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, glBuf); GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.length * 4, floatBuf, GLES20.GL_STATIC_DRAW); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); return glBuf; } ... int i, num = scene.objects.size(); for (i = 0; i < num; i++) { Object3D obj = scene.objects.get(i); obj.glVertices = createBuffer(obj.vertexBuffer); GLES20.glGenBuffers(1, genbuf, 0); obj.glIndices = genbuf[0]; GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, obj.glIndices); GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, obj.indCount * 2, null, GLES20.GL_STATIC_DRAW); int k, mats = obj.faceMats.size(); for (k = 0; k < mats; k++) { FaceMat mat = obj.faceMats.get(k); ShortBuffer indBuf = ByteBuffer.allocateDirect(mat.indexBuffer.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer(); indBuf.put(mat.indexBuffer); indBuf.position(0); GLES20.glBufferSubData(GLES20.GL_ELEMENT_ARRAY_BUFFER, mat.bufOffset * 2, mat.indexBuffer.length * 2, indBuf); } GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); }
      
      







頂点バッファー生成は、クワッドを作成するときに使用するための別個の関数です。



モデルのシェーダー


シェーダーを作成してシーンをレンダリングします。



  private final String vertexShaderCode = "precision mediump float;\n" + "uniform mat4 uMVPMatrix;\n" + "uniform mat4 uMVMatrix;\n" + "uniform mat3 uNMatrix;\n" + "uniform vec4 uAmbient;\n" + "uniform vec4 uDiffuse;\n" + "const int MaxLights = 8;\n" + "struct LightSourceParameters {\n" + " bool enabled;\n" + " vec4 color;\n" + " vec3 position;\n" + "};\n" + "uniform LightSourceParameters uLight[MaxLights];\n" + "attribute vec4 vPosition;\n" + "attribute vec3 vNormal;\n" + "varying vec4 FrontColor;\n" + "vec4 light_point_view_local(vec3 epos, vec3 normal, int idx);\n" + "void main() {\n" + " gl_Position = uMVPMatrix * vPosition;\n" + " vec4 epos = uMVMatrix * vPosition;\n" + " vec3 normal =uNMatrix * vNormal;\n" + " vec4 vcolor = uAmbient;\n" + " int i;\n" + " for (i = 0; i < MaxLights; i++) {\n" + " if (uLight[i].enabled) {\n" + " vcolor += light_point_view_local(epos.xyz, normal, i);\n" + " }\n" + " }\n" + " FrontColor = clamp(vcolor, 0.0, 1.0);\n" + "}\n" + "vec4 light_point_view_local(vec3 epos, vec3 normal, int idx) {\n" + " vec3 vert2light = uLight[idx].position - epos;\n" + " vec3 ldir = normalize(vert2light);\n" + " float NdotL = dot(normal, ldir);\n" + " vec4 outCol = vec4(0.0, 0.0, 0.0, 1.0);\n" + " if (NdotL > 0.0) {\n" + " outCol = uLight[idx].color * uDiffuse * NdotL;\n" + " }\n" + " return outCol;\n" + "}\n"; private final String fragmentShaderCode = "precision mediump float;\n" + "varying vec4 FrontColor;\n" + "void main() {\n" + " gl_FragColor = FrontColor;\n" + "}\n"; private int mProgram; private int maPosition; private int maNormal; private int muMVPMatrix; private int muMVMatrix; private int muNMatrix; private int muAmbient; private int muDiffuse; private int[] muLightOn = new int[8]; private int[] muLightPos = new int[8]; private int[] muLightCol = new int[8];
      
      







それらをコンパイルし、属性の場所を決定します。



  private int loadShader(int type, String shaderCode) { int shader = GLES20.glCreateShader(type); GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); Log.i("Shader", GLES20.glGetShaderInfoLog(shader)); return shader; } private int Compile(String vs, String fs) { int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vs); int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fs); int prog = GLES20.glCreateProgram(); // create empty OpenGL Program GLES20.glAttachShader(prog, vertexShader); // add the vertex shader to program GLES20.glAttachShader(prog, fragmentShader); // add the fragment shader to program GLES20.glLinkProgram(prog); // creates OpenGL program executables return prog; } ... mProgram = Compile(vertexShaderCode, fragmentShaderCode); // get handle to the vertex shader's vPosition member maPosition = GLES20.glGetAttribLocation(mProgram, "vPosition"); maNormal = GLES20.glGetAttribLocation(mProgram, "vNormal"); muMVPMatrix = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); muMVMatrix = GLES20.glGetUniformLocation(mProgram, "uMVMatrix"); muNMatrix = GLES20.glGetUniformLocation(mProgram, "uNMatrix"); muAmbient = GLES20.glGetUniformLocation(mProgram, "uAmbient"); muDiffuse = GLES20.glGetUniformLocation(mProgram, "uDiffuse"); int i; for (i = 0; i < 8; i++) { muLightOn[i] = GLES20.glGetUniformLocation(mProgram, String.format("uLight[%d].enabled", i)); muLightPos[i] = GLES20.glGetUniformLocation(mProgram, String.format("uLight[%d].position", i)); muLightCol[i] = GLES20.glGetUniformLocation(mProgram, String.format("uLight[%d].color", i)); }
      
      







これらのシェーダーの動作については説明しません。フラグメントの1つは既に些細なものであり、頂点の1つは全方向光源で通常の動作を実装します。



モデルレンダリング


変換行列のモデル、ビュー、および投影の準備がすでに整っていると仮定します(たとえば、私のロゼットはスムーズに回転します)。 Model-Viewの作業から、回転のみを選択します。これは法線を操作するために必要です。



レンダリングはシンプルで楽しいです。先ほど作成したバッファを使用して、属性を割り当て、描画します。 光源の座標が視空間に送信されることは特に注目に値します。これは、ビューマトリックスによって乗算されるためです。



  private void DrawScene() { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); GLES20.glUseProgram(mProgram); GLES20.glEnable(GLES20.GL_CULL_FACE); GLES20.glEnable(GLES20.GL_DEPTH_TEST); Matrix.multiplyMM(mMVMatrix, 0, mVMatrix, 0, mMMatrix, 0); Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVMatrix, 0); // Apply a ModelView Projection transformation GLES20.glUniformMatrix4fv(muMVPMatrix, 1, false, mMVPMatrix, 0); GLES20.glUniformMatrix4fv(muMVMatrix, 1, false, mMVMatrix, 0); int i, j, num; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) mNMatrix[i*3 + j] = mMVMatrix[i*4 + j]; GLES20.glUniformMatrix3fv(muNMatrix, 1, false, mNMatrix, 0); num = min(scene.lights.size(), 8); float[] eyepos = new float[3]; for (i = 0; i < num; i++) { Light3D light = scene.lights.get(i); for (j = 0; j < 3; j++) { eyepos[j] = mVMatrix[4*3 + j]; for (k = 0; k < 3; k++) eyepos[j] += light.pos[k] * mVMatrix[k*4 + j]; } GLES20.glUniform1i(muLightOn[i], 1); GLES20.glUniform3fv(muLightPos[i], 1, eyepos, 0); GLES20.glUniform4fv(muLightCol[i], 1, light.color, 0); } for (i = num; i < 8; i++) GLES20.glUniform1i(muLightOn[i], 0); // Prepare the triangle data GLES20.glEnableVertexAttribArray(maPosition); GLES20.glEnableVertexAttribArray(maNormal); num = scene.objects.size(); for (i = 0; i < num; i++) { Object3D obj = scene.objects.get(i); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, obj.glVertices); GLES20.glVertexAttribPointer(maPosition, 3, GLES20.GL_FLOAT, false, 24, 0); GLES20.glVertexAttribPointer(maNormal, 3, GLES20.GL_FLOAT, false, 24, 12); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, obj.glIndices); int mats = obj.faceMats.size(); for (j = 0; j < mats; j++) { FaceMat mat = obj.faceMats.get(j); for (int k = 0; k < 3; k++) mAmbient[k] = mat.material.ambient[k] * scene.ambient[k]; GLES20.glUniform4fv(muAmbient, 1, mAmbient, 0); GLES20.glUniform4fv(muDiffuse, 1, mat.material.diffuse, 0); GLES20.glDrawElements(GLES20.GL_TRIANGLES, mat.indexBuffer.length, GLES20.GL_UNSIGNED_SHORT, mat.bufOffset * 2); } GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); } GLES20.glDisableVertexAttribArray(maPosition); GLES20.glDisableVertexAttribArray(maNormal); }
      
      







エフェクトシーンなし

その結果、テクスチャなしで、マテリアルと8つの光源を使用してシーンをレンダリングする機能が得られました。 実際、特にこのシーンに、そしてそのような小さな画面上でさえ、テクスチャはもはや必要ではなく、とてもきれいに見えます。 しかし、私はまだ輝きが欲しいです!



次に、楽しい部分として、このシーンをテクスチャにレンダリングします。



クワッド



まず、事前にレンダリングされたシーンにガウスぼかしを適用するために、次にシーンに最終的な「グロー」を適用するために、クワッドが必要になります。



クワッド作成


頂点とテクスチャ座標を準備し、バッファーを作成し、シェーダーをコンパイルします-シェーダーがさらに単純であることを除いて、すべてはモデルと同じです:



  private final String quadVS = "precision mediump float;\n" + "attribute vec4 vPosition;\n" + "attribute vec4 vTexCoord0;\n" + "varying vec4 TexCoord0;\n" + "void main() {\n" + " gl_Position = vPosition;\n" + " TexCoord0 = vTexCoord0;\n" + "}\n"; private final String quadFS = "precision mediump float;\n" + "uniform sampler2D uTexture0;\n" + "varying vec4 TexCoord0;\n" + "void main() {\n" + " gl_FragColor = texture2D(uTexture0, TexCoord0.xy);\n" + "}\n"; private int mQProgram; private int maQPosition; private int maQTexCoord; private int muQTexture; private int glQuadVB; ... final float quadv[] = { -1, 1, 0, 0, 1, -1, -1, 0, 0, 0, 1, 1, 0, 1, 1, 1, -1, 0, 1, 0 }; glQuadVB = createBuffer(quadv); mQProgram = Compile(quadVS, quadFS); maQPosition = GLES20.glGetAttribLocation(mQProgram, "vPosition"); maQTexCoord = GLES20.glGetAttribLocation(mQProgram, "vTexCoord0"); muQTexture = GLES20.glGetUniformLocation(mQProgram, "uTexture0");
      
      







テクスチャバッファの準備


2つの256x256バッファを一度に作成します。これはonSurfaceChanged関数で行う必要があります。



  private int filterBuf1; private int filterBuf2; private int renderTex1; private int renderTex2; public int scrWidth; public int scrHeight; public int texWidth; public int texHeight; ... private int makeRenderTarget(int width, int height, int[] handles) { GLES20.glGenTextures(1, genbuf, 0); int renderTex = genbuf[0]; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); IntBuffer texBuffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder()).asIntBuffer(); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, texBuffer); GLES20.glGenRenderbuffers(1, genbuf, 0); int depthBuf = genbuf[0]; GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthBuf); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height); GLES20.glGenFramebuffers(1, genbuf, 0); int frameBuf = genbuf[0]; GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuf); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, renderTex, 0); GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthBuf); int res = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); handles[0] = frameBuf; handles[1] = renderTex; return res; } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { ratio = (float) width / height; int[] handles = new int[2]; scrWidth = width; scrHeight = height; texWidth = 256; texHeight = 256; makeRenderTarget(texWidth, texHeight, handles); filterBuf1 = handles[0]; renderTex1 = handles[1]; makeRenderTarget(texWidth, texHeight, handles); filterBuf2 = handles[0]; renderTex2 = handles[1]; }
      
      







各テクスチャバッファには、テクスチャ自体とフレームバッファの2つの変数が関連付けられています(トートロジーについて謝罪します)。



テクスチャから/へのクワッドのレンダリング


ほんの2つの機能:1つは現在のレンダリングのソースとターゲットを設定し(0は画面、残りは以前に作成されたフレームバッファー)、2つ目はクワッドを描画します。



  private void setRenderTexture(int frameBuf, int texture) { if (frameBuf == 0) GLES20.glViewport(0, 0, scrWidth, scrHeight); else GLES20.glViewport(0, 0, texWidth, texHeight); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuf); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); } private void DrawQuad() { GLES20.glUseProgram(mQProgram); GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, glQuadVB); GLES20.glEnableVertexAttribArray(maQPosition); GLES20.glVertexAttribPointer(maQPosition, 3, GLES20.GL_FLOAT, false, 20, 0); GLES20.glEnableVertexAttribArray(maQTexCoord); GLES20.glVertexAttribPointer(maQTexCoord, 2, GLES20.GL_FLOAT, false, 20, 12); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); GLES20.glUniform1i(muQTexture, 0); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glDisableVertexAttribArray(maQPosition); GLES20.glDisableVertexAttribArray(maQTexCoord); }
      
      







テクスチャのシーン エフェクトは複数のパスで重ね合わされるため、パフォーマンス上の理由から、低解像度のテクスチャを使用しているため、クワッドでレンダリングした結果は、図に示すようなものになります。



ふう! 少し残っています:シェーダーとコードブロックがさらに2つあります!



ガウスぼかし



ここで、一次元ぼかしは次のように実装されます。フレームの各ポイントについて、44個の隣接ピクセルが取得され、ガウス分布に従って異なる色で重みが加算されます。 これを11パスで行います。各パスで、元のシーンの4ピクセルが結果の画像に追加されます。 したがって、シェーダーは、1つのパスで4つのテクスチャを異なるオフセットでオーバーラップするように設計されています。 もちろん、パスの数を減らすことはできますが、実際に自分の好みと、許容できるパフォーマンスでプログラムを実行する予定の鉄をどれだけ引き出せるかに注意する必要があります。



効果は、水平と垂直の2段階で適用されます。 事前に何かを計算する必要があります。



ガウスのサポートデータ


 class FilterKernelElement { public float du; public float dv; public float coef; } ... float mOffsets[] = new float[4]; private float[] pix_mult = new float[4]; private FilterKernelElement[] mvGaussian1D = new FilterKernelElement[44]; private float mfPerTexelWidth; private float mfPerTexelHeight; ... float cent = (mvGaussian1D.length - 1.0f) / 2.0f, radi; for (int u = 0; u < mvGaussian1D.length; u++) { FilterKernelElement el = mvGaussian1D[u] = new FilterKernelElement(); el.du = ((float)u) - cent - 0.1f; el.dv = 0.0f; radi = (el.du * el.du) / (cent * cent); el.coef = (float)((0.24/Math.exp(radi*0.18)) + 0.41/Math.exp(radi*4.5)); } float rr = texWidth / (float) texHeight; float rs = rr / ratio; mfPerTexelWidth = rs / texWidth; mfPerTexelHeight = 1.0f / texHeight;
      
      







最後に計算される定数は、アスペクト比を調整するために必要です。テクスチャバッファとは対照的に、画面は正方形ではないため、このような調整を行わないと、グローは多少平坦になります。



ガウスシェーダー


  private final String gaussVS = "precision mediump float;\n" + "attribute vec4 vPosition;\n" + "attribute vec4 vTexCoord0;\n" + "uniform vec4 uTexOffset0;\n" + "uniform vec4 uTexOffset1;\n" + "uniform vec4 uTexOffset2;\n" + "uniform vec4 uTexOffset3;\n" + "varying vec4 TexCoord0;\n" + "varying vec4 TexCoord1;\n" + "varying vec4 TexCoord2;\n" + "varying vec4 TexCoord3;\n" + "void main() {\n" + " gl_Position = vPosition;\n" + " TexCoord0 = vTexCoord0 + uTexOffset0;\n" + " TexCoord1 = vTexCoord0 + uTexOffset1;\n" + " TexCoord2 = vTexCoord0 + uTexOffset2;\n" + " TexCoord3 = vTexCoord0 + uTexOffset3;\n" + "}\n"; private final String gaussFS = "precision mediump float;\n" + "uniform sampler2D uTexture0;\n" + "uniform vec4 uTexCoef0;\n" + "uniform vec4 uTexCoef1;\n" + "uniform vec4 uTexCoef2;\n" + "uniform vec4 uTexCoef3;\n" + "varying vec4 TexCoord0;\n" + "varying vec4 TexCoord1;\n" + "varying vec4 TexCoord2;\n" + "varying vec4 TexCoord3;\n" + "void main() {\n" + " vec4 c0 = texture2D(uTexture0, TexCoord0.xy);\n" + " vec4 c1 = texture2D(uTexture0, TexCoord1.xy);\n" + " vec4 c2 = texture2D(uTexture0, TexCoord2.xy);\n" + " vec4 c3 = texture2D(uTexture0, TexCoord3.xy);\n" + " gl_FragColor = uTexCoef0 * c0 + uTexCoef1 * c1 + uTexCoef2 * c2 + uTexCoef3 * c3;\n" + "}\n"; private int mGProgram; private int maGPosition; private int maGTexCoord; private int muGTexture; private int[] muGTexCoef = new int[4]; private int[] muGTexOffset = new int[4]; ... mGProgram = Compile(gaussVS, gaussFS); maGPosition = GLES20.glGetAttribLocation(mGProgram, "vPosition"); maGTexCoord = GLES20.glGetAttribLocation(mGProgram, "vTexCoord0"); muGTexture = GLES20.glGetUniformLocation(mGProgram, "uTexture0"); for (i = 0; i < 4; i++) { muGTexOffset[i] = GLES20.glGetUniformLocation(mGProgram, String.format("uTexOffset%d", i)); muGTexCoef[i] = GLES20.glGetUniformLocation(mGProgram, String.format("uTexCoef%d", i)); }
      
      







ガウスの描画


この関数は、1つの軸でぼかします:水平または垂直のいずれか、および複数のパス。 そのため、2つのテクスチャが必要でした!



  private void DrawGauss(boolean invert) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glUseProgram(mGProgram); GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, glQuadVB); GLES20.glEnableVertexAttribArray(maGPosition); GLES20.glVertexAttribPointer(maGPosition, 3, GLES20.GL_FLOAT, false, 20, 0); GLES20.glEnableVertexAttribArray(maGTexCoord); GLES20.glVertexAttribPointer(maGTexCoord, 2, GLES20.GL_FLOAT, false, 20, 12); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); GLES20.glUniform1i(muGTexture, 0); int i, n, k; for (i = 0; i < mvGaussian1D.length; i += 4) { for (n = 0; n < 4; n++) { FilterKernelElement pE = mvGaussian1D[i + n]; for (k = 0; k < 4; k++) pix_mult[k] = pE.coef * 0.10f; GLES20.glUniform4fv(muGTexCoef[n], 1, pix_mult, 0); mOffsets[0] = mfPerTexelWidth * (invert ? pE.dv : pE.du); mOffsets[1] = mfPerTexelHeight * (invert ? pE.du : pE.dv); GLES20.glUniform4fv(muGTexOffset[n], 1, mOffsets, 0); } GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } GLES20.glDisableVertexAttribArray(maGPosition); GLES20.glDisableVertexAttribArray(maGTexCoord); }
      
      







これは、それぞれ水平方向と垂直方向のぼかし後の結果のようになります。



水平方向のぼかしの後垂直ぼかしの後



すべてをまとめる



最後のステップ:すべての効果を使用して、フレーム全体をレンダリングする手順。 モデルを再度レンダリングしてから、光沢を付ける必要があります。



  @Override public void onDrawFrame(GL10 arg0) { setRenderTexture(filterBuf1, 0); DrawScene(); GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); setRenderTexture(filterBuf2, renderTex1); DrawGauss(false); setRenderTexture(filterBuf1, renderTex2); DrawGauss(true); GLES20.glDisable(GLES20.GL_BLEND); setRenderTexture(0, 0); DrawScene(); GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); setRenderTexture(0, renderTex1); DrawQuad(); GLES20.glDisable(GLES20.GL_BLEND); }
      
      







合計:

1.最初のテクスチャでシーンを描きます。

2.最初のテクスチャに対して、水平方向のぼかしを行い、結果を2番目に保存します。

3. 2番目のテクスチャを垂直にぼかし、最初のテクスチャを保存します。

4.通常モードでシーンを描画します。

5.クワッドを使用して、シーンの上に最初のテクスチャを適用します。

6.完了!



実際、シーンはテクスチャ上で一度だけ描画でき、クワッドを使用してスクリーンバッファにすぐにコピーできます。 ただし、まともな画質を確保するには、画面自体と同じ解像度のテクスチャが必要になり、ぼかしが大幅に遅くなります。



ちなみに、結果のプログラムは優れたベンチマークとして機能しますが、主にフィルレートに依存します。 唯一の「しかし」:Qualcommプロセッサではあまりうまく機能せず(ソースシーンのシェーダーに関する何らかの問題)、それでも理由を見つけることができませんでした。 最後までデバッグする単一のHTCデバイスはありませんが、すべてがPowerVR 540(古いGalaxy S)、Mali 400(S2、Tab 7.7、注)およびエミュレーターで完全にレンダリングされます。



更新:公開以来、いくつかのエラーが発生しているため、記事は少し更新されています。 モデルシェーダーコード(vertexShaderCode、いくつかの行が削除されました)、DrawScene関数のコード(光源の座標の目の空間への変換)、および最終レンダリング(onDrawFrame)が変更され、スクリーンショットが更新されました(露出オーバーが消えました)。 残りは同じままです。



更新2: .3dsのダウンロード に関する 投稿の準備できました。



更新3:クアルコムでのレンダリングの問題が解決されました。シェーダーのこの行が判明しました

 for (i = 0; i < uLights; i++)
      
      





正しく機能しませんでした。 誰が考えたでしょうか?..

シェーダーおよびその他すべてが更新されましたが、この問題は解決されます。



All Articles