I'm building an Android app that takes advantage of OpenGL. As it stands, the background for the GLSurfaceView is dynamically generated by my code and loaded in as a texture and drawn with glDrawTexfOES. Which is "ok", but I can simply display the image much more smoothly to its own surface (without OpenGL). Is there any way that I can make the background of a GLSurfaceView transparent? I've heard some rumors that this can be done with setEGLConfigChooser, but I haven't found any confirmation. Ultimately, I'd like to take a surface which I'm drawing to and put the GLSurfaceView over it to achieve a layered effect.
I know this is a tricky and is quite possibly infeasible, but any input is appreciated. Thanks in advance.
Just some simple changes that I did to get this to work.
On my GLSurfaceView.Renderer:
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_FASTEST);
gl.glClearColor(0,0,0,0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glEnable(GL10.GL_DEPTH_TEST);
}
On my GLSurfaceView:
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
Your GLSurfaceView also requires setZOrderOnTop(true);
I use my own GLSurfaceView class to display charts (transparent background / overlay).
My extended GLSurfaceView is embed via XML into a popover window.
<com.dsignmatters.iq_fitfunlite.GLPieChartView
android:id="#+id/gameprogress_chart"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
...
As part of the activity I added this code:
mGamesuccessPieChart = (GLSurfaceView) gameprogressView.findViewById(R.id.gameprogress_chart);
mGamesuccessPieChart.setZOrderOnTop(true);
Last but not least my GLSurfaceView looks like this:
public class GLPieChartView extends GLSurfaceView {
public GLPieChartView(Context context) {
super(context);
initGL();
}
public GLPieChartView(Context context, AttributeSet attrs) {
super(context, attrs);
initGL();
}
void initGL() {
setEGLContextClientVersion(2);
setEGLConfigChooser(8,8,8,8,16,0);
setRenderer(new GLPieChartRenderer());
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
}
My renderer class GLPieChartRenderer does not call glClearColor at all.
Code sample at the end is for enabling transparency with GLSurfaceView. But before using transparency with GLSurfaceView, consider the following negative points.
Enabling transparency requires that you use setZOrderOnTop on GLSurfaceView. That will prevent you from placing any other views ( e.g. TextView ) on top of your transparent GLSurfaceView. Even Navigation drawers cannot slide above the transparent GLSurfaceView( ignoring tricks ). Transparent or not, GLSurfaceView can only exist above or below other android views and not in between them.
Transparency requires that you use setEGLConfigChooser and setFormat as in below example. That means, you cannot get what would have been default format chosen by system which would have been the best for that particular device. More importantly, you will need to ensure that the device has the supported format and chose alternatives if expected format isn't present as in this gsoc example.
Other options to transparency.
Running Tracer for opengl in Android, will show that, background images for activities are drawn as opengl textures. So instead of making GLSurfaceView transparent, if possible, you can as well render your background as an opengl texture in GLSurfaceView.
Also, alpha channel or transparency on the surface is not pre requirement for alpha blending in opengl. Both are independent.
TextureView (trick) is good alternative if your opengl component is to be embedded between other views. This breakout game is a very good example for GLSurfaceView 2d drawing in Android.
Below sample with layout xml and code for transparent GLSurfaceView with background.
Layout xml file with green color background
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#00FFFF">
<android.opengl.GLSurfaceView
android:id="#+id/mySurfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
MainActivity.java file. Change ALPHA variable from 0.0 to 1.0 to see surface color red mixing with background activity color green
package com.example.transparentsurfaceview;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MainActivity extends Activity {
private GLSurfaceView mSurfaceView;
private static float ALPHA = 0.5f;
private static float RED = 1.0f;
private static float GREEN = 0.0f;
private static float BLUE = 0.0f;
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
mSurfaceView = (GLSurfaceView)findViewById( R.id.mySurfaceView );
mSurfaceView.setEGLContextClientVersion( 2 );
mSurfaceView.setZOrderOnTop( true );
mSurfaceView.setEGLConfigChooser( 8, 8, 8, 8, 16, 0 );
mSurfaceView.getHolder().setFormat( PixelFormat.RGBA_8888 );
mSurfaceView.setRenderer( new GLSurfaceView.Renderer() {
public void onSurfaceCreated( GL10 gl10, EGLConfig eglConfig ) {
GLES20.glClearColor( RED, GREEN, BLUE, ALPHA );
}
public void onSurfaceChanged( GL10 gl10, int i, int i2 ) {}
public void onDrawFrame( GL10 gl10 ) {
GLES20.glClear( GLES20.GL_COLOR_BUFFER_BIT );
}
});
}
}
you should make sure the activity background is transparent!
If the activity's background is opaque, say white by default, then even when the content view (the glsurfaceview) is translucent, it will display the activity's white background as its background.
You can set the activity background transparency in the Android.manifest where it is declared, see the official API demo TranslucentGLSurfaceViewActivity for help
Related
I am trying to draw a frame with two textures within Libgdx. My texture is just a white square that i scale and draw with SpriteBatch to the screen. The color is set with batch.setColor(). Now I want to draw a black rectangle and a smaller opaque rectangle in the middle, so it looks like a frame.
batch.begin();
batch.setColor(new Color(0,0,0,1)); //Black
batch.draw(rectangle, 0,0,100,100);
batch.setColor(new Color(0,0,0,0)); //Transparent
batch.draw(rectangle, 25,25,50,50);
batch.end();
I am using this blend function:
Gdx.gl.glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR);
the problem now is when i draw this it just shows the black rectangle cause the transparent rectangle is drawn on top of it. I want to draw it that I can see through the second rectangle the stuff I drawed before so it acts like a frame.
My question is: How can i accomplish this?
Example Background
How it should look
How it looks
First of all, you should not create new instances of Color in your render() method, since it's called every frame it will lead to a memory leak. Create colors in create() for example. And for black color you could use Color.BLACK instead of creating your own instance.
Back to your question.
You can simply just use TextureRegion class. Here is a sample code:
public class MyGdxGame extends ApplicationAdapter {
SpriteBatch batch;
Texture whiteRectangle;
TextureRegion backgroundRegion;
#Override
public void create() {
//initializing batch, whiteTexture, etc...
//background, the one with a turtle from you example
Texture backgroundTexture = ...;
backgroundRegion = new TextureRegion(backgroundTexture);
//to set a region (part of the texture you want to draw),
//you can use normalized coordinates (from 0 to 1), or absolute values in pixels
backgroundRegion.setRegion(0.25f, 0.25f, 0.75f, 0.75f);
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
//black background
batch.setColor(Color.BLACK);
batch.draw(whiteRectangle, 0, 0, 100, 100);
//on top of that background draw the backgroundRegion
batch.setColor(Color.WHITE);
batch.draw(backgroundRegion, 25,25,50,50);
batch.end();
}
}
I'm wondering if there are any good design pattern for initializing custom views (for example passing in a model/renderer). In particular I'm interested in patterns that allow for Android Studio layout preview to work (or something like mirror to work).
For example take a simple game structure:
layout.xml
<LinearLayout>
<com.package.GameSurface>
</LinearLayout>
GameSurface.java
public class GameSurface extends View {
int mComputedParam;
Renderer mRenderer;
GameState mGameState;
public PlaySurface(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PlaySurface(Context context) {
super(context);
}
// What are other ways of achieving this?
public void init(GameState gameState, Renderer renderer) {
mGameState = gameState;
mRenderer = renderer;
}
#Override
protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) {
// Game state is used to calculate some layout/size/render information on the fly
mComputedParam = (newWidth - getPaddingLeft() - getPaddingRight()) / mGameState.level();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Render can be swapped out based on level or user settings, etc
mRenderer.draw();
}
}
Something like this will crash the Android studio layout preview since onDraw and onSizeChanged both throw null pointer exceptions if called without the init().
Would there be some other approach that makes this possible? Some ideas off the top of my head:
Have a default renderer and model to fallback to in the layout preview. If this is a preferred solution, is there a way to detect that my code is being run in the layout preview?
Have an option to choose the renderer via some sort of XML attribute, e.g. custom:renderer="#values/default_renderer" and then specify one for layout preview view tools:renderer="#values/preview_renderer". It seems like there might not be good support for this (see Limitations section). This also might not work in some situations where instantiating the renderer couldn't easily be done in a static/preview context.
Perhaps there's a way to run some setup code in the layout preview that I've overlooked?
I'm building an Android app that takes advantage of OpenGL. As it stands, the background for the GLSurfaceView is dynamically generated by my code and loaded in as a texture and drawn with glDrawTexfOES. Which is "ok", but I can simply display the image much more smoothly to its own surface (without OpenGL). Is there any way that I can make the background of a GLSurfaceView transparent? I've heard some rumors that this can be done with setEGLConfigChooser, but I haven't found any confirmation. Ultimately, I'd like to take a surface which I'm drawing to and put the GLSurfaceView over it to achieve a layered effect.
I know this is a tricky and is quite possibly infeasible, but any input is appreciated. Thanks in advance.
Just some simple changes that I did to get this to work.
On my GLSurfaceView.Renderer:
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_FASTEST);
gl.glClearColor(0,0,0,0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glEnable(GL10.GL_DEPTH_TEST);
}
On my GLSurfaceView:
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
Your GLSurfaceView also requires setZOrderOnTop(true);
I use my own GLSurfaceView class to display charts (transparent background / overlay).
My extended GLSurfaceView is embed via XML into a popover window.
<com.dsignmatters.iq_fitfunlite.GLPieChartView
android:id="#+id/gameprogress_chart"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
...
As part of the activity I added this code:
mGamesuccessPieChart = (GLSurfaceView) gameprogressView.findViewById(R.id.gameprogress_chart);
mGamesuccessPieChart.setZOrderOnTop(true);
Last but not least my GLSurfaceView looks like this:
public class GLPieChartView extends GLSurfaceView {
public GLPieChartView(Context context) {
super(context);
initGL();
}
public GLPieChartView(Context context, AttributeSet attrs) {
super(context, attrs);
initGL();
}
void initGL() {
setEGLContextClientVersion(2);
setEGLConfigChooser(8,8,8,8,16,0);
setRenderer(new GLPieChartRenderer());
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
}
My renderer class GLPieChartRenderer does not call glClearColor at all.
Code sample at the end is for enabling transparency with GLSurfaceView. But before using transparency with GLSurfaceView, consider the following negative points.
Enabling transparency requires that you use setZOrderOnTop on GLSurfaceView. That will prevent you from placing any other views ( e.g. TextView ) on top of your transparent GLSurfaceView. Even Navigation drawers cannot slide above the transparent GLSurfaceView( ignoring tricks ). Transparent or not, GLSurfaceView can only exist above or below other android views and not in between them.
Transparency requires that you use setEGLConfigChooser and setFormat as in below example. That means, you cannot get what would have been default format chosen by system which would have been the best for that particular device. More importantly, you will need to ensure that the device has the supported format and chose alternatives if expected format isn't present as in this gsoc example.
Other options to transparency.
Running Tracer for opengl in Android, will show that, background images for activities are drawn as opengl textures. So instead of making GLSurfaceView transparent, if possible, you can as well render your background as an opengl texture in GLSurfaceView.
Also, alpha channel or transparency on the surface is not pre requirement for alpha blending in opengl. Both are independent.
TextureView (trick) is good alternative if your opengl component is to be embedded between other views. This breakout game is a very good example for GLSurfaceView 2d drawing in Android.
Below sample with layout xml and code for transparent GLSurfaceView with background.
Layout xml file with green color background
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#00FFFF">
<android.opengl.GLSurfaceView
android:id="#+id/mySurfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
MainActivity.java file. Change ALPHA variable from 0.0 to 1.0 to see surface color red mixing with background activity color green
package com.example.transparentsurfaceview;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MainActivity extends Activity {
private GLSurfaceView mSurfaceView;
private static float ALPHA = 0.5f;
private static float RED = 1.0f;
private static float GREEN = 0.0f;
private static float BLUE = 0.0f;
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
mSurfaceView = (GLSurfaceView)findViewById( R.id.mySurfaceView );
mSurfaceView.setEGLContextClientVersion( 2 );
mSurfaceView.setZOrderOnTop( true );
mSurfaceView.setEGLConfigChooser( 8, 8, 8, 8, 16, 0 );
mSurfaceView.getHolder().setFormat( PixelFormat.RGBA_8888 );
mSurfaceView.setRenderer( new GLSurfaceView.Renderer() {
public void onSurfaceCreated( GL10 gl10, EGLConfig eglConfig ) {
GLES20.glClearColor( RED, GREEN, BLUE, ALPHA );
}
public void onSurfaceChanged( GL10 gl10, int i, int i2 ) {}
public void onDrawFrame( GL10 gl10 ) {
GLES20.glClear( GLES20.GL_COLOR_BUFFER_BIT );
}
});
}
}
you should make sure the activity background is transparent!
If the activity's background is opaque, say white by default, then even when the content view (the glsurfaceview) is translucent, it will display the activity's white background as its background.
You can set the activity background transparency in the Android.manifest where it is declared, see the official API demo TranslucentGLSurfaceViewActivity for help
It is possible to render a view at a low resolution, and then scale it up to fit the actual size of your view using the setFixedSize() method of a SurfaceHolder. However, the scaling is done with some kind of interpolation, causing everything to blur.
Is there any method for changing the method of interpolation to nearest neighbour or just turning it off?
Here is an example of what I mean, Made with a 4x4 surface in a fullscreen-view:
Left image: This is how I want the result to look (here achieved by drawing a nonfiltered bitmap)
Right image: This is what happens when the 4x4 canvas is scaled to fullscreen.
Sample code for generating the right image if anyone's interested:
public class ScaleView extends SurfaceView implements SurfaceHolder.Callback {
private final static float[] points = {0,0, 2,0, 4,0, 1,1, 3,1, 0,2, 2,2, 4,2, 1,3, 3,3};
private Paint white;
public ScaleView(Context context) {
super(context);
white = new Paint();
white.setColor(0xFFFFFFFF);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
Canvas c = holder.lockCanvas();
try{
c.drawPoints(points, white);
}finally{
holder.unlockCanvasAndPost(c);
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
holder.setFixedSize(4, 4);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){}
}
Note: The square pattern above is just an example of the effect I want to avoid. I am looking for a general solution that can be applied to any view (with a surfaceholder) with any content.
Sorry, you can't control this. It is not defined what kind of scaling will be done on these surfaces -- it depends on the hardware and how it is configured. Also, this kind of extreme scaling really should be avoided, since in some cases hardware can't do it so you will end up in slower paths. (For example if surfaces are being put into overlays, many hardware overlay engines can't do that kind of extreme scaling.)
I've created 3 Java classes.
one that has a glsurfaceview object and this calls the renderer class.
this is the renderer class and this calls the cube class.
this is the cube class.
If I run the app then the screen shows a rotating cube (did rotation in the rendering class) which is fine. But I want to control the direction of rotation of the cube and for that I've set 2 buttons. This is where I need help because I don't know to to make the buttons control the movement of the cube. I'm new to Android so if you could leave some code for me to examine then that would be just great.
Your Activity class (or your class that extends Activity) should look like this:
public class stackoverflowTest extends Activity {
GLSurfaceView glSurface;
MyRenderer myRenderer;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myRenderer = new MyRenderer(this);
//Create an instance of the Renderer with this Activity
glSurface = (GLSurfaceView) findViewById(R.id.graphics_glsurfaceview1);
//Set our own Renderer and hand the renderer this Activity Context
glSurface.setEGLConfigChooser(true);
glSurface.setRenderer(myRenderer);
//Set the GLSurface as View to this Activity
}
/**
* this is the method the button click calls
*/
public void changeRotationDirection(View v){
myRenderer.changeRotationDirection();
}
}
Then in your renderer:
public class MyRenderer implements Renderer {
private float rotationDirection = 1.0f;
public MyRenderer(Context context){
this.context = context;
}
public void setRotationDirection(){
if(rotationDirection==1.0f){
rotationDirection=-1.0f;
} else {
rotationDirection=1.0f;
}
}
#Override
public void onDrawFrame(GL10 gl) {
// GL calls
gl.glRotatef(angle, rotateDirection, 0.0f, 0.0f);
// draw cube
gl.glDrawArrays( etc );
}
}
Basically, you use glRotatef to rotate the cube just before you draw it. Use -ve vales for either the angle parameter (the first) or the x,y,z amount parameters to rotate in the opposite direction. Use method calls to the Renderer to communicate with it and update the scene. Use this approach with caution as the Renderer thread and Main/UI thread (from where the button call is made) can have synchronisation issues
To make a button call the changeRotationDirection method, simply add android:onClick="changeRotationDirection" into the XML layout (of any view. Doesn't have to be just a button view). Any button methods declared in the XML layout have to be of the form public void [methodname](View [paramname]) and have to be in the Activity class from where the button is pressed
For more advanced touch control, check as Erik suggested and also check out OnTouchListeners
(Note: if you're using openGL-ES 2.0 or above (android 2.2+) then use GLES20.glRotatef())
You might want to check out the TouchRotateActivity example from the sdk. If I'm not mistaken its located in the samples/platform/api-demos folder.