/** * */ package rajawali.renderer.plugins; import java.nio.ByteBuffer; import java.util.Stack; import android.graphics.Bitmap.Config; import android.opengl.GLES20; import rajawali.Camera; import rajawali.materials.TextureInfo; import rajawali.materials.TextureManager.FilterType; import rajawali.materials.TextureManager.TextureType; import rajawali.materials.TextureManager.WrapType; import rajawali.math.Number3D; import rajawali.math.Vector2D; import rajawali.renderer.RajawaliRenderer; import rajawali.util.RajLog; import rajawali.extras.LensFlare; import rajawali.extras.LensFlare.FlareInfo; /** * @author Andrew Jo * */ @SuppressWarnings("unused") public final class LensFlarePlugin extends Plugin { private static final String mVShaderVertexTexture = "precision highp float;\n" + "\n" + "uniform lowp int uRenderType;\n" + "uniform vec3 uScreenPosition;\n" + "uniform vec2 uScale;\n" + "uniform float uRotation;\n" + "uniform sampler2D uOcclusionMap;\n" + "\n" + "attribute vec2 aPosition;\n" + "attribute vec2 aTextureCoord;\n" + "\n" + "varying vec2 vTextureCoord;\n" + "varying float vVisibility;\n" + "\n" + "void main() {\n" + " vTextureCoord = aTextureCoord;\n" + "\n" + " vec2 pos = aPosition;\n" + "\n" + " if (uRenderType == 3) {\n" + " vec4 visibility = texture2D(uOcclusionMap, vec2(0.1, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.9)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.9)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.9));\n" + "\n" + " vVisibility = (visibility.r / 9.0) * (1.0 - visibility.g / 9.0) *\n" + " (visibility.b / 9.0) * (visibility.a / 9.0);\n" + "\n" + " pos.x = cos(uRotation) * aPosition.x - sin(uRotation) * aPosition.y;\n" + " pos.y = sin(uRotation) * aPosition.x + cos(uRotation) * aPosition.y;\n" + " }\n" + "\n" + " gl_Position = vec4((pos * uScale + uScreenPosition.xy).xy, uScreenPosition.z, 1.0);\n" + "}"; private static final String mFShaderVertexTexture = "precision highp float;\n" + "\n" + "uniform lowp int uRenderType;\n" + "uniform sampler2D uMap;\n" + "uniform float uOpacity;\n" + "uniform vec3 uColor;\n" + "uniform lowp int uDebugMode;\n" + "\n" + "varying vec2 vTextureCoord;\n" + "varying float vVisibility;\n" + "\n" + "void main() {\n" + " if (uRenderType == 1) {\n" + " gl_FragColor = vec4(1.0, 0.0, 1.0, 0.0);\n" + " } else if (uRenderType == 2) {\n" + " gl_FragColor = texture2D(uMap, vTextureCoord);\n" + " } else {\n" + " vec4 texture = texture2D(uMap, vTextureCoord);\n" + " if (uDebugMode == 1) {\n" + " texture.a = 1.0;\n" + " } else {\n" + " texture.a *= uOpacity * vVisibility;\n" + " }\n" + " gl_FragColor = texture;\n" + " gl_FragColor.rgb *= uColor;\n" + " }\n" + "}"; private static final String mVShaderNoVertexTexture = "precision highp float;\n" + "\n" + "uniform mediump int uRenderType;\n" + "uniform vec3 uScreenPosition;\n" + "uniform float uRotation;\n" + "uniform vec2 uScale;\n" + "\n" + "attribute vec2 aPosition;\n" + "attribute vec2 aTextureCoord;\n" + "\n" + "varying vec2 vTextureCoord;\n" + "\n" + "void main() {\n" + " vTextureCoord = aTextureCoord;\n" + " vec2 pos = aPosition;\n" + " if (uRenderType == 3) {\n" + " pos.x = cos(uRotation) * aPosition.x - sin(uRotation) * aPosition.y;\n" + " pos.y = sin(uRotation) * aPosition.x + cos(uRotation) * aPosition.y;\n" + " }\n" + " gl_Position = vec4((pos * uScale + uScreenPosition.xy).xy, uScreenPosition.z, 1.0);\n" + "}"; private static final String mFShaderNoVertexTexture = "precision mediump float;\n" + "\n" + "uniform mediump int uRenderType;\n" + "uniform lowp int uDebugMode;\n" + "uniform mediump sampler2D uMap;\n" + "uniform mediump sampler2D uOcclusionMap;\n" + "uniform mediump sampler2D uFlareTexture;\n" + "uniform float uOpacity;\n" + "uniform vec3 uColor;\n" + "\n" + "varying vec2 vTextureCoord;\n" + "\n" + "void main() {\n" + " if (uRenderType == 1) {\n" + " gl_FragColor = vec4(texture2D(uMap, vTextureCoord).rgb, 0.0);\n" + " } else if (uRenderType == 2) {\n" + " gl_FragColor = texture2D(uMap, vTextureCoord);\n" + " } else {\n" + /*" vec4 visibility = texture2D(uOcclusionMap, vec2(0.1, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.1)) +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.5)) +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.9)) +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.9)) +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.9));\n" + " float finalVisibility = (visibility.r / 9.0) * (1.0 - visibility.g / 9.0) *\n" + " (visibility.b / 9.0) * (1.0 - visibility.a / 9.0);\n" + //" finalVisibility = 0.5;\n" +*/ " float finalVisibility = texture2D(uOcclusionMap, vec2(0.1, 0.1)).a +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.1)).a +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.1)).a +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.5)).a +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.5)).a +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.5)).a +\n" + " texture2D(uOcclusionMap, vec2(0.1, 0.9)).a +\n" + " texture2D(uOcclusionMap, vec2(0.5, 0.9)).a +\n" + " texture2D(uOcclusionMap, vec2(0.9, 0.9)).a;\n" + " finalVisibility = (1.0 - finalVisibility / 4.0);\n" + "\n" + " vec4 texture = texture2D(uMap, vTextureCoord);\n" + " if (uDebugMode == 1) {\n" + " texture.a = 1.0;\n" + " } else {\n" + " texture.a *= uOpacity * finalVisibility;\n" + " }" + " gl_FragColor = texture;\n" + " gl_FragColor.rgb *= uColor;\n" + " }\n" + "}"; private Stack<LensFlare> mLensFlares; /* * Determines whether texture sampling is supported in the vertex shader. */ private boolean mVertexTextureSupported; /* * Shader Attribute Handles */ private int maPositionHandle; private int maTextureCoordHandle; /* * Shader Uniform Handles */ private int muRenderTypeHandle; private int muRotationHandle; private int muScreenPositionHandle; private int muOpacityHandle; private int muScaleHandle; private int muColorHandle; private int muMapTextureHandle; private int muOcclusionMapTextureHandle; private int muDebugModeHandle; // UNCOMMENT TO USE DEBUG MODE private TextureInfo mMapTexture; private TextureInfo mOcclusionMapTexture; public LensFlarePlugin(RajawaliRenderer renderer) { super(renderer); } @Override protected void init() { mLensFlares = new Stack<LensFlare>(); int[] maxVertexTextureImageUnits = new int[1]; GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, maxVertexTextureImageUnits, 0); mVertexTextureSupported = maxVertexTextureImageUnits[0] != 0; int i = 0, j = 0; int numVertices = 8; float[] vertices = new float[numVertices]; float[] textureCoords = new float[numVertices]; float[] normals = new float[numVertices * 3]; float[] colors = new float[numVertices * 4]; int[] indices = new int[6]; int vertexCount = 0; int texCoordCount = 0; // Create a quad with vertices at (-1, -1), (1, -1), (1, 1), (-1, 1). vertices[vertexCount++] = -1; vertices[vertexCount++] = -1; vertices[vertexCount++] = 1; vertices[vertexCount++] = -1; vertices[vertexCount++] = 1; vertices[vertexCount++] = 1; vertices[vertexCount++] = -1; vertices[vertexCount++] = 1; // Each vertex normals are facing upwards on the Y-axis. for (j = 0; j < numVertices; j+=3) { normals[j] = 0; normals[j+1] = 1; normals[j+2] = 0; } // Texture (UV) coordinates are at (0, 0), (1, 0), (1, 1), (0, 1). textureCoords[texCoordCount++] = 0; textureCoords[texCoordCount++] = 0; textureCoords[texCoordCount++] = 1; textureCoords[texCoordCount++] = 0; textureCoords[texCoordCount++] = 1; textureCoords[texCoordCount++] = 1; textureCoords[texCoordCount++] = 0; textureCoords[texCoordCount++] = 1; // Draw the vertices in the index order of 0 -> 1 -> 2 -> 0 -> 2 -> 3. indices[i++] = 0; indices[i++] = 1; indices[i++] = 2; indices[i++] = 0; indices[i++] = 2; indices[i++] = 3; // Just default to yellow. for (i = 0; i < colors.length; i+=4) { colors[i] = 1.0f; colors[i+1] = 1.0f; colors[i+2] = 0.0f; colors[i+3] = 1.0f; } // Set geometry data. setData(vertices, normals, textureCoords, colors, indices); // Set up lookup textures. mMapTexture = mRenderer.getTextureManager().addTexture(new ByteBuffer[0], null, 16, 16, TextureType.LOOKUP, Config.RGB_565, false, false, WrapType.CLAMP, FilterType.NEAREST); mOcclusionMapTexture = mRenderer.getTextureManager().addTexture(new ByteBuffer[0], null, 16, 16, TextureType.LOOKUP, Config.ARGB_8888, false, false, WrapType.CLAMP, FilterType.NEAREST); // Set up shader program. // Currently vertex texture shader causes problems on Adreno 320 GPUs. //if (mVertexTextureSupported) { // setShaders(mVShaderVertexTexture, mFShaderVertexTexture); //} else { setShaders(mVShaderNoVertexTexture, mFShaderNoVertexTexture); //} } public void addLensFlare(LensFlare lensFlare) { mLensFlares.add(lensFlare); } public int getLensFlareCount() { return mLensFlares.size(); } public boolean removeLensFlare(LensFlare lensFlare) { return mLensFlares.remove(lensFlare); } @Override public void render() { int f, i, numLensFlares = mLensFlares.size(); // Calculate world space position to normalized screen space. float viewportWidth = mRenderer.getViewportWidth(), viewportHeight = mRenderer.getViewportHeight(); float invAspect = viewportHeight / viewportWidth; float size; Vector2D scale = new Vector2D(); float halfViewportWidth = viewportWidth / 2; float halfViewportHeight = viewportHeight / 2; Number3D screenPosition = new Number3D(); float screenPositionPixels_x, screenPositionPixels_y; Camera camera = mRenderer.getCamera(); float[] viewMatrix = camera.getViewMatrix().clone(), projMatrix = camera.getProjectionMatrix().clone(); useProgram(mProgram); // Push the VBOs to the GPU. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGeometry.getVertexBufferInfo().bufferHandle); GLES20.glEnableVertexAttribArray(maPositionHandle); GLES20.glVertexAttribPointer(maPositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); // Push texture coordinates to the GPU. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGeometry.getTexCoordBufferInfo().bufferHandle); GLES20.glEnableVertexAttribArray(maTextureCoordHandle); GLES20.glVertexAttribPointer(maTextureCoordHandle, 2, GLES20.GL_FLOAT, false, 0, 0); // Push vertex element indices to the GPU. GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mGeometry.getIndexBufferInfo().bufferHandle); // Set up texture locations. GLES20.glUniform1i(muOcclusionMapTextureHandle, 0); GLES20.glUniform1i(muMapTextureHandle, 1); GLES20.glDisable(GLES20.GL_CULL_FACE); GLES20.glDepthMask(false); // Calculate camera direction vector. Number3D cameraPosition = camera.getPosition().clone(); Number3D cameraLookAt = camera.getLookAt() != null ? camera.getLookAt().clone() : new Number3D(0, 0, 1); Number3D cameraDirection = cameraLookAt.clone().subtract(cameraPosition); cameraDirection.normalize(); synchronized (mLensFlares) { for (i = 0; i < numLensFlares; i++) { size = 16 / viewportHeight; scale.x = size * invAspect; scale.y = size; LensFlare lensFlare = mLensFlares.get(i); // Calculate normalized device coordinates. screenPosition.setAllFrom(lensFlare.getPosition().clone()); screenPosition.multiply(viewMatrix); screenPosition.project(projMatrix); // Calculate actual device coordinates. screenPositionPixels_x = screenPosition.x * halfViewportWidth + halfViewportWidth; screenPositionPixels_y = screenPosition.y * halfViewportHeight + halfViewportHeight; // Calculate the angle between the camera and the light vector. Number3D lightToCamDirection = lensFlare.getPosition().clone().subtract(cameraPosition); lightToCamDirection.normalize(); float angleLightCamera = lightToCamDirection.dot(cameraDirection); // Camera needs to be facing towards the light and the light should come within the // viewing frustum. if (mVertexTextureSupported || (angleLightCamera > 0 && screenPositionPixels_x > -64 && screenPositionPixels_x < viewportWidth + 64 && screenPositionPixels_y > -64 && screenPositionPixels_y < viewportHeight + 64)) { // Bind current framebuffer to texture. GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mMapTexture.getTextureId()); GLES20.glCopyTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, (int)screenPositionPixels_x - 8, (int)screenPositionPixels_y - 8, 16, 16, 0); // First render pass. GLES20.glUniform1i(muRenderTypeHandle, 1); GLES20.glUniform2fv(muScaleHandle, 1, new float[] { scale.x, scale.y }, 0); GLES20.glUniform3fv(muScreenPositionHandle, 1, new float[] { screenPosition.x, screenPosition.y, screenPosition.z }, 0); GLES20.glDisable(GLES20.GL_BLEND); GLES20.glEnable(GLES20.GL_DEPTH_TEST); GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, mGeometry.areOnlyShortBuffersSupported() ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT, 0); // Copy result to occlusion map. GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOcclusionMapTexture.getTextureId()); GLES20.glCopyTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int)screenPositionPixels_x - 8, (int)screenPositionPixels_y - 8, 16, 16, 0); // Second render pass. GLES20.glUniform1i(muRenderTypeHandle, 2); GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mMapTexture.getTextureId()); GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, mGeometry.areOnlyShortBuffersSupported() ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT, 0); // Update the flare's screen positions. lensFlare.setPositionScreen(screenPosition); lensFlare.updateLensFlares(); // Third render pass. GLES20.glUniform1i(muRenderTypeHandle, 3); GLES20.glEnable(GLES20.GL_BLEND); // DEBUG - Shows the current uMap and uOcclusionMap textures on screen. // NOTE: UNCOMMENT IF THE LENS FLARE DOES NOT GET OCCLUDED. // IF THE OCCLUSION TEXTURE IS EMPTY, YOU ARE USING RGB_565 IN YOUR EGL CONFIG. // SWITCH TO RGBA_8888. /* GLES20.glUniform3fv(muScreenPositionHandle, 1, new float[] { -0.75f, -0.35f, 0 }, 0); GLES20.glUniform2fv(muScaleHandle, 1, new float[] { (200 / viewportHeight) * invAspect, 200 / viewportHeight }, 0); GLES20.glUniform1f(muRotationHandle, 0); GLES20.glUniform1i(muDebugModeHandle, 1); GLES20.glUniform1f(muOpacityHandle, 1); GLES20.glUniform3fv(muColorHandle, 1, new float[] { 1, 1, 1 }, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mMapTexture.getTextureId()); fix.android.opengl.GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, mGeometry.areOnlyShortBuffersSupported() ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); GLES20.glUniform3fv(muScreenPositionHandle, 1, new float[] { -0.3f, -0.35f, 0 }, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOcclusionMapTexture.getTextureId()); fix.android.opengl.GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, mGeometry.areOnlyShortBuffersSupported() ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT, 0); GLES20.glUniform1i(muDebugModeHandle, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); */ // END DEBUG for (f = 0; f < lensFlare.getLensFlares().size(); f++) { FlareInfo sprite = lensFlare.getLensFlares().get(f); // Don't bother rendering if the sprite's too transparent or too small. if (sprite.getOpacity() > 0.001f && sprite.getScale() > 0.001f) { screenPosition.setAllFrom(sprite.getScreenPosition()); // Calculate pixel size to normalized size size = sprite.getSize() * sprite.getScale() / viewportHeight; scale.x = size * invAspect; scale.y = size; GLES20.glUniform3fv(muScreenPositionHandle, 1, new float[] { screenPosition.x, screenPosition.y, screenPosition.z }, 0); GLES20.glUniform2fv(muScaleHandle, 1, new float[] { scale.x, scale.y }, 0); GLES20.glUniform1f(muRotationHandle, sprite.getRotation()); GLES20.glUniform1f(muOpacityHandle, sprite.getOpacity()); GLES20.glUniform3fv(muColorHandle, 1, new float[] { sprite.getColor().x, sprite.getColor().y, sprite.getColor().z }, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, sprite.getTexture().getTextureId()); //GLES20.glBlendEquation(GLES20.GL_FUNC_ADD); GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); // Draw the elements. GLES20.glDrawElements(GLES20.GL_TRIANGLES, mGeometry.getNumIndices(), mGeometry.areOnlyShortBuffersSupported() ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT, 0); // Unbind texture. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); } } } } } // Unbind element array. GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); GLES20.glEnable(GLES20.GL_CULL_FACE); GLES20.glEnable(GLES20.GL_DEPTH_TEST); GLES20.glDepthMask(true); } @Override public void setShaders(String vertexShader, String fragmentShader) { super.setShaders(vertexShader, fragmentShader); maPositionHandle = getAttribLocation("aPosition"); maTextureCoordHandle = getAttribLocation("aTextureCoord"); muRenderTypeHandle = getUniformLocation("uRenderType"); muScreenPositionHandle = getUniformLocation("uScreenPosition"); muRotationHandle = getUniformLocation("uRotation"); muScaleHandle = getUniformLocation("uScale"); muOpacityHandle = getUniformLocation("uOpacity"); muColorHandle = getUniformLocation("uColor"); muMapTextureHandle = getUniformLocation("uMap"); muOcclusionMapTextureHandle = getUniformLocation("uOcclusionMap"); muDebugModeHandle = GLES20.glGetUniformLocation(mProgram, "uDebugMode"); // UNCOMMENT TO USE DEBUG MODE } }