📜 ⬆️ ⬇️

Effect of Glow Gaussian Blur on Android

Introduction


Result This epic work appeared thanks to several events.

Firstly, Android video acceleration support appeared in the Android emulator, which allows working at full speed not only with the interface, but also with testing programs using OpenGL ES 2.0.

Secondly, the birthday of his beloved spouse is nearing, and the best addition to the new smartphone or tablet will be the hand-written card program for him.
')
It is said - done: we create the outline of the project on the android Tutorial , we get the old Direct3D projects off the shelf using .3ds file upload, render-in-texture and shader packs, rewrite in Java and OpenGL ES 2.0, we get what we see in the picture . Greeting text and the like will be added later.

All the information on using OpenGL ES 2.0 on Android turned out to be very fragmented, knowledge was collected bit by bit ... I hope this post will help those who will face the same difficulties in the future as I am.

And now more.

Training


The first thing to do is enable hardware acceleration in the emulator. This is done either through the AVD Manager (see screenshot; do not forget to set the value to “yes”), or by adding the line “hw.gpu.enabled = yes to the file“ .android / avd / <your_ emulator_name> .avd / config.ini ”. ". There is one subtlety: hardware acceleration is incompatible with snapshots. Accordingly, we remove this checkbox (or write in the .ini file “snapshot.present = false”).



Next, follow the above-mentioned Tutorial to create everything you need, in particular the heir to the Renderer class.

Model


By the word “model” I mean here this very rosette. In essence, you can use any object or the whole scene, it does not matter.

This implies that all further code is located in the same class inherited from the Renderer class.

Loading model

I will not give the code for downloading the .3ds file here: long, and the post is not about that (in principle, it is worthy of a separate post), but I will give the model drawing code, because, firstly, I raked a lot along the way, secondly, it consists almost entirely of gl * calls, and thirdly, some functions will be needed below. However, if only the implementation of the effect is interesting, you can skip this section. So, as a result, all these models fit into such structures:

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; } 


Here we deliberately removed such subtleties as specular and directional light sources: the scene will be hard enough to draw anyway. The array of object vertices contains 6 * (the number of vertices) of real numbers: the coordinates of the vertices and the normals recorded in a row.

Drawing from float / short arrays was not fast, but from buffers it was quite tolerable (depending on the driver and the video core, this data can be immediately located in the video memory). We convert from one to another, separately vertex, separately index. Do not forget after filling the buffer to finish working with it, specifying 0 as the active buffer.

  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); } 


The vertex buffer generation is moved to a separate function in order to use it when creating a quad.

Shaders for a model

Create shaders for scene rendering:

  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]; 


Compile them, determine the location of the attributes:

  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)); } 


I will not dwell on the work of these shaders: fragmentary and so trivial, and the vertex implements the usual work with omnidirectional light sources.

Model rendering

It is assumed that we already have the Model, View and Projection transformation matrices ready (for me, for example, the rosette turns smoothly). From the work Model-View we select only a turn, it is necessary for working with normals.

Drawing is simple and pleasant: we take the previously created buffers, assign attributes, draw. It is worth noting that the coordinates of the light sources are transmitted in the eye-space, for this they are multiplied by the View matrix.

  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); } 


Scene without effect
As a result, we got the function of rendering the scene without textures, but with materials and eight light sources. Actually, this particular scene, and even on such a small screen, has nothing to do with the texture, and it looks good. But I still want a glow!

Now - the most interesting: let's render this scene for texture.

Quad


Quad we will need, first, to apply a Gaussian blur to a pre-rendered scene, and second, to apply the final “glow” to the scene.

Create Quad

We prepare vertices and texture coordinates, create a buffer, compile the shader - everything is the same as for the model, except that the shaders have become even simpler:

  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"); 


Texture Buffer Preparation

Create two 256x256 buffers at once, this should be done in the onSurfaceChanged function.

  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]; } 


There are two variables associated with each texture buffer: the actual texture and frame buffer (I apologize for the tautology).

Drawing a quad from texture / texture

Literally a couple of functions: one sets the current source and target of the drawing (0 is our screen, the rest is the previously created frame buffers), the second draws a quad.

  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); } 


Scene in texture Since the effect will overlap in several passes, for performance reasons, the texture is low resolution, so the result of its rendering on the quad will be similar to something like the one shown in the picture.

Phew! Just a little bit left: a couple more shaders and a few blocks of code!

Gaussian blur


One-dimensional blur is implemented here as follows: for each point of our frame, 44 neighboring pixels are taken, and their colors are added with different weights, in accordance with the Gaussian distribution. We will do this in 11 passes, in each of which we will add 4 pixels of the original scene to the resulting image. Appropriately, the shaders are designed to impose four textures in a single pass, with different offsets. Of course, you can make a smaller number of passes, here you have to look to your taste and according to how much you can draw out the iron on which you plan to launch the program with acceptable performance.

The effect is applied in two stages: horizontally and vertically. We will need to calculate something in advance.

Gauss Supporting Data

 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; 


The constants calculated at the end are needed to correct the aspect ratio: the screen, unlike the texture buffer, is not square, so without such an adjustment the glow will be somewhat flattened.

Gauss Shaders

  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)); } 


Gauss drawing

This function will blur on one axis: either horizontally or vertically, and in several passes. That is why two textures were needed!

  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); } 


This is what the result will look like after horizontal and vertical blur, respectively:

After horizontal blurAfter vertical blur

Putting it all together


The last step: the procedure of drawing the entire frame, with all the effects. We must once again render the model, and then impose a glow on it.

  @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); } 


Total:
1. Draw a scene in the first texture;
2. For the first texture, we make a horizontal blur, save the result to the second;
3. We erase the second texture vertically, save it to the first;
4. Draw a scene in the usual mode;
5. We put the first texture on top of the scene with a quad;
6. Done!

In fact, the scene can be drawn once, only on the texture, immediately copying it at the very beginning into the screen buffer using quad. However, to ensure a decent picture quality, this will require a texture of the same resolution as the screen itself, and this will significantly slow down its blurring.

By the way, the resulting program serves as a good benchmark, although it rests mainly in the fillrate. The only "but": it does not work well on Qualcomm processors (some kind of problem with the shader of the original scene), and I could not figure out the reason, because I do not have a single device from HTC to debug to the end, but everything is perfectly drawn on the PowerVR 540 (on the old Galaxy S), Mali 400 (S2, Tab 7.7, Note) and in the emulator.

Update: Since the publication, several errors have been found, so the article has been slightly updated. The model's shader code has changed (vertexShaderCode, several lines have been removed), the DrawScene function code (the transformation of the coordinates of the light sources to eye-space has been added) and the final rendering (onDrawFrame), the screenshots have been updated (the redraw has disappeared). The rest remains the same.

Update 2: Downloading post .3ds is ready.

Update 3: The problem of drawing on the squares is solved: it turned out that this line is in the shader
 for (i = 0; i < uLights; i++) 

worked wrong. Who would have thought?..
Shaders and everything else is updated, now this problem will not be.

Source: https://habr.com/ru/post/144831/


All Articles