I'm trying to texture an ArrayList of rectangle "bricks" for my game. I have code that works perfectly when there's only one rectangle to texture, but anytime I add more, the fragment shader only applies the texture to the last rectangle drawn, and the others are rendered invisible. I don't need to use different textures, just the exact same texture for each rectangle. What do I need to modify with my fragment shaders to make this happen?
Here's the code for my Graphics class:
private ArrayList<FloatBuffer> bricks;
private FloatBuffer textureBuffer;
private ShortBuffer drawListBuffer;
static final int COORDS_PER_VERTEX = 3;
private short drawOrder[] = {0, 1, 2, 0, 2, 3}; //Order to draw vertices
private float[] textureCoords =
{ 1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f };
private final int mProgram;
//New variables for textures
private int[] texBufferID = new int[1];
private int texCoordID;
private int texID;
//Use this guy to access and set the view transformation
private int mMVPMatrixHandle;
private final String vertexShaderCode =
//These are the coordinates used to pass texCoord over to the fragmentShader
"attribute vec2 s_vTexCoord;" +
"varying vec2 texCoord;" +
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
//The line that does the aforementioned pass
" texCoord = s_vTexCoord;" +
//the matrix has to modify glPosition
//Note: uMVPMatrix has to be first here. Cuz reasons
" gl_Position = uMVPMatrix * vPosition;" +
"}";
private final String fragmentShaderCode =
"varying vec2 texCoord;" +
"precision mediump float;" +
"uniform sampler2D texture;" +
"void main() {" +
" gl_FragColor = texture2D(texture, texCoord);" + //How is texture defined?
"}";
public Graphics(Context context) {
//Load our texture coordinates into a buffer
ByteBuffer b = ByteBuffer.allocateDirect(textureCoords.length * 4);
b.order(ByteOrder.nativeOrder());
//create a floating point buffer from bb
textureBuffer = b.asFloatBuffer();
//Add our coordinates to the float buffer
textureBuffer.put(textureCoords);
//Set the buffer to read the first coordinate
textureBuffer.position(0);
//initialize buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2); //2 bytes per short
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
//Add the shaders
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
//Create OpenGL program executables
GLES20.glLinkProgram(mProgram);
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
//Pass in our texture coordinates to the vertex shader
texCoordID = GLES20.glGetAttribLocation(mProgram, "s_vTexCoord");
GLES20.glEnableVertexAttribArray(texCoordID);
GLES20.glVertexAttribPointer(texCoordID, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
//Read in the bmp from file to bmp
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // No pre-scaling
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), R.drawable.brick, options);
//Read bmp into OpenGL as texture 0
GLES20.glGenTextures(1, texBufferID, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texBufferID[0]);
//This is basically OpenGL texture preferences
//GL_NEAREST means it grabs the color value of the nearest pixel for scaling. Fast and dirty.
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
bmp.recycle();
if (texBufferID[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
//Tell our vertex shader that texture 0 is the active texture
texID = GLES20.glGetUniformLocation(mProgram, "texture");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(texID, 0);
}
public void newBricks(ArrayList<float[]> brickList){
bricks = new ArrayList<>();
for (int i = 0; i < brickList.size(); i++) {
float[] brick = brickList.get(i);
// initialize a buffer to hold the vertices
ByteBuffer bb = ByteBuffer.allocateDirect(
//4 bytes per float
brick.length * 4);
//Tells bb to use the devices native byte order
bb.order(ByteOrder.nativeOrder());
//create a floating point buffer from bb
FloatBuffer vertexBuffer = bb.asFloatBuffer();
//Add our coordinates to the float buffer
vertexBuffer.put(brick);
//Set the buffer to read the first coordinate
vertexBuffer.position(0);
bricks.add(vertexBuffer);
}
}
public void remove(int brick){
bricks.remove(brick);
}
private int mPositionHandle;
private final int vertexCount = 12 / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; //4 bytes per vertex
//We now pass draw the calculated camera and projection view matrix
public void draw(float[] mvpMatrix) {
//Add program to the OpenGL ES environment
GLES20.glUseProgram(mProgram);
//Pass in our texture coordinates to the vertex shader
texCoordID = GLES20.glGetAttribLocation(mProgram, "s_vTexCoord");
GLES20.glEnableVertexAttribArray(texCoordID);
GLES20.glVertexAttribPointer(texCoordID, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
//get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//enable a handle to the brick vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
//get handle to the transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
//pass the transformation matrix to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
for (int i = bricks.size() - 1; i >= 0; i--) {
//Prepare the brick coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, bricks.get(0));
//Draw the brick
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, vertexCount);
}
//Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
You wrote bricks.get(0) instead of bricks.get(i) in for loop of public void draw(float[] mvpMatrix) function.
And also you can remove this line GLES20.glEnable(GLES20.GL_TEXTURE_2D); because it is from fixed function pipeline OpenGL, but you use Shaders from OpenGL ES 2.0
Related
I am new to android opengl development.I want to fill polygon with image as shown below using opengl-es 2.0 in android. I am able to draw square,triangle but not able to fill it with repeating image. Anyone know please help me.
Square.java
public class Square {
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec2 a_TextureCoordinates;" +
"varying vec2 v_TextureCoordinates;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
" v_TextureCoordinates = a_TextureCoordinates;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform sampler2D u_TextureUnit;"+
"varying vec2 v_TextureCoordinates;" +
"void main() {" +
" gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);" +
"}";
private final FloatBuffer vertexBuffer;
private final FloatBuffer textureBuffer;
private final ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
static final int COORDS_PER_VERTEX = 3;
static float squareCoords[] = {
-1.0f, 1.0f, 0.0f, // top left
1.0f, 1.0f, 0.0f, // top right
1.0f, -1.0f, 0.0f, // bottom right
-1.0f, -1.0f, 0.0f // bottom left
};
final float[] previewTextureCoordinateData =
{
0.0f,0.0f, // top left
1.0f,0.0f, // Top-right
1.0f,1.0f, // Bottom-right
0.0f,1.0f, // Bottom-left
};
private int textureDataHandle;
private int textureUniformHandle;
private int textureCoordinateHandle;
private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 };
float color[] = { 0.2f, 0.709803922f, 0.898039216f, 1.0f };
private 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]);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
// 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();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];}
public Square(Context c) {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
ByteBuffer texCoordinates = ByteBuffer.allocateDirect(previewTextureCoordinateData.length * 4);
texCoordinates.order(ByteOrder.nativeOrder());
textureBuffer = texCoordinates.asFloatBuffer();
textureBuffer.put(previewTextureCoordinateData);
textureBuffer.position(0);
// prepare shaders and OpenGL program
int vertexShader = MyGLRenderer.loadShader(
GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(
GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
textureDataHandle = loadTexture(c, R.drawable.air_hockey_surface);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // create OpenGL program executables
}
public void draw(float[] mvpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
textureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TextureCoordinates");
GLES20.glVertexAttribPointer(textureCoordinateHandle, 2, GLES20.GL_FLOAT, false,
0, textureBuffer);
GLES20.glEnableVertexAttribArray(textureCoordinateHandle);
textureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_TextureUnit");
MyGLRenderer.checkGlError("glGetUniformLocation");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureDataHandle);
GLES20.glUniform1i(textureUniformHandle, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
MyGLRenderer.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
MyGLRenderer.checkGlError("glUniformMatrix4fv");
// Draw the square
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}}
If you want the texture to repeat you need to set such texture coordinates. A texture coordinate is a relative pixel position in the texture where (0.0, 0,0) is the top-left position on the image and (1.0, 1.0) is the bottom right. Now no matter the vertex coordinates the texture part shown will always be the same. In your case then
final float[] previewTextureCoordinateData =
{
0.0f,0.0f, // top left
1.0f,0.0f, // Top-right
1.0f,1.0f, // Bottom-right
0.0f,1.0f, // Bottom-left
};
means you will be drawing a whole texture on the object. To make it repeat for instance 3 times in each of dimensions you need to multiply these:
final float[] previewTextureCoordinateData =
{
0.0f,0.0f, // top left
1.0f*3.0,0.0f, // Top-right
1.0f*3.0,1.0f*3.0, // Bottom-right
0.0f,1.0f*3.0, // Bottom-left
};
The repeating is already enabled on the texture so that should be it.
I'm trying to capture video and display it to the screen by setting an Open GL ES texture to an android surfaceTexture. I can't use a TextureView and implement SurfaceTextureListener as per this tutorial since I am using Google Cardboard.
I have followed the Android documentation on how to initialise Open GL ES 2.0 and use it, and also this tutorial on texturing.
Putting the 2 together I get a blank screen and occasionally get <core_glBindTexture:572>: GL_INVALID_OPERATION in the console window.
Overwhelmed by so many new concepts that I don't know, I'm not able to debug or just understand if the two approach can be used like this. Here is my drawing code, it is initialised in the onSurfaceCreated() of the MainActivity class, and drawn from onEyeDraw() which is Cardboard's draw function.
package com.example.rich.test3;
import android.hardware.Camera;
import android.opengl.GLES20;
import android.view.TextureView;
import java.nio.ShortBuffer;
import java.nio.FloatBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Created by rich on 03/05/2015.
*/
public class Square {
private java.nio.FloatBuffer vertexBuffer;
private java.nio.ShortBuffer drawListBuffer;
private final java.nio.FloatBuffer mCubeTextureCoordinates;
float color[] = { 1.f, 1.f, 1.f, 1.0f };
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
" gl_Position = vPosition;" +
" v_TexCoordinate = a_TexCoordinate;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"uniform sampler2D u_Texture;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
"gl_FragColor = (texture2D(u_Texture, v_TexCoordinate));" +
"}";
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float squareCoords[] = {
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, 0.5f, 0.0f, // top left
0.5f, 0.5f, 0.0f}; // top right
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
private int mProgram;
private int mPositionHandle;
private int mColorHandle;
private int mTextureUniformHandle;
private int mTextureCoordinateHandle;
private final int mTextureCoordinateDataSize = 2;
private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private int mTextureDataHandle;
float textureCoordinates[] =
{0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f };
Camera _camera;
TextureView _textureView;
int[] textures;
android.graphics.SurfaceTexture _surface;
public Square()
{
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
mCubeTextureCoordinates = ByteBuffer.allocateDirect(textureCoordinates.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubeTextureCoordinates.put(textureCoordinates).position(0);
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
_surface = new android.graphics.SurfaceTexture(textures[0]);
_camera = Camera.open();
Camera.Size previewSize = _camera.getParameters().getPreviewSize();
try
{
_camera.setPreviewTexture(_surface);
}
catch (java.io.IOException ex)
{
// Console.writeLine (ex.Message);
}
final int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
GLES20.glShaderSource(vertexShaderHandle, vertexShaderCode);
GLES20.glCompileShader(vertexShaderHandle);
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0)
{
//do check here
}
final int fragmentShaderHandle = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fragmentShaderHandle, fragmentShaderCode);
GLES20.glCompileShader(fragmentShaderHandle);
GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0)
{
//do check here
}
GLES20.glAttachShader(mProgram, vertexShaderHandle);
GLES20.glAttachShader(mProgram, fragmentShaderHandle);
GLES20.glBindAttribLocation(mProgram, 0, "a_Position");
GLES20.glBindAttribLocation(mProgram, 0, "a_TexCoordinate");
GLES20.glLinkProgram(mProgram);
final int[] linkStatus = new int[1];
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0)
{
//do check here
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
mTextureDataHandle = textures[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);
}
public void draw()
{
_surface.updateTexImage();
GLES20.glUseProgram(mProgram);
mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "a_Position");
mColorHandle = GLES20.glGetAttribLocation(mProgram, "a_Color");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
0, mCubeTextureCoordinates);
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glUniform1i(mTextureUniformHandle, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
When rendering a SurfaceTexture texture object, you need to use the GL_TEXTURE_EXTERNAL_OES texture target:
The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target, which is defined by the GL_OES_EGL_image_external OpenGL ES extension. This limits how the texture may be used. Each time the texture is bound it must be bound to the GL_TEXTURE_EXTERNAL_OES target rather than the GL_TEXTURE_2D target. Additionally, any OpenGL ES 2.0 shader that samples from the texture must declare its use of this extension using, for example, an "#extension GL_OES_EGL_image_external : require" directive. Such shaders must also access the texture using the samplerExternalOES GLSL sampler type.
So you need to change your fragment shader like this, adding the #extension declaration and declaring your texture uniform as samplerExternalOES:
private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;" +
"uniform vec4 vColor;" +
"uniform samplerExternalOES u_Texture;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
"gl_FragColor = (texture2D(u_Texture, v_TexCoordinate));" +
"}";
Also in your draw() function, bind the texture like this:
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureDataHandle);
You can't use normal texture to render camera or video preview, you have to use GL_TEXTURE_EXTERNAL_OES extension. I had same problem and i found a complete working solution on github. The name of the project is android_instacam .
Here you will find source code to study. If you want to see it in action directly on your device just go on play store here.
I started from the google's android tutorial on open gl and then used this tutorial:
to add textures. Since the class structures differ a bit, I had to do some code-moving-renaming. What i ended up is:
Here is the texture file i used.
As one can see, the texture is somehow stretched along (1, 1), although the underlying object is a square.
Here is my quad's code, any help is appreciated.
public class GLSquare implements IGLObject {
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" + "attribute vec2 a_TexCoordinate;"
+ "varying vec2 v_TexCoordinate;" + "attribute vec4 vPosition;"
+ "void main() {"
+
// the matrix must be included as a modifier of gl_Position
" gl_Position = uMVPMatrix * vPosition;"
+ "v_TexCoordinate = a_TexCoordinate;" + "}";
private final String fragmentShaderCode = "precision mediump float;"
+ "uniform sampler2D u_Texture;" + "varying vec2 v_TexCoordinate;"
+ "void main() {"
+ " gl_FragColor = texture2D(u_Texture, v_TexCoordinate);" + "}";
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private final int mProgramHandle;
private int mPositionHandle;
private int mMVPMatrixHandle;
private int mTextureDataHandle;
/** The texture pointer */
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float squareCoords[] = { -10f, 10f, 0.0f, // top left
-10f, -10f, 0.0f, // bottom left
10f, -10f, 0.0f, // bottom right
10f, 10f, 0.0f }; // top right
private FloatBuffer textureBuffer; // buffer holding the texture coordinates
private float texture[] = {
// Mapping coordinates for the vertices
0.0f, 1.0f, // top left (V2)
0.0f, 0.0f, // bottom left (V1)
1.0f, 1.0f, // top right (V4)
1.0f, 0.0f // bottom right (V3)
};
static float[] mTranslate = new float[16];
static float[] translatedMVP = new float[16];
private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 };
public GLSquare(float x, float y, float z, Context context) {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
bb = ByteBuffer.allocateDirect(texture.length * 4);
bb.order(ByteOrder.nativeOrder());
textureBuffer = bb.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
Matrix.setIdentityM(mTranslate, 0);
Matrix.translateM(mTranslate, 0, x, y, z);
// prepare shaders and OpenGL program
final int vertexShaderHandle = GLTools.compileShader(
GLES20.GL_VERTEX_SHADER, vertexShaderCode);
final int fragmentShaderHandle = GLTools.compileShader(
GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgramHandle = GLTools.createAndLinkProgram(vertexShaderHandle,
fragmentShaderHandle, new String[] { "a_Position", "a_Color",
"a_TexCoordinate" });
// Load the texture
mTextureDataHandle = GLTools.loadGLTexture(context, R.raw.stars1024);
}
public void draw(float[] vpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgramHandle);
// Pass in the position information
vertexBuffer.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT,
false, 0, vertexBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);
int mTextureCoordinateHandle = GLES20.glGetAttribLocation(
mProgramHandle, "a_TexCoordinate");
// Pass in the texture coordinate information
textureBuffer.position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, 2,
GLES20.GL_FLOAT, false, 0, textureBuffer);
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle,
"uMVPMatrix");
WideOpenRenderer.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
Matrix.multiplyMM(translatedMVP, 0, vpMatrix, 0, mTranslate, 0);
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, translatedMVP, 0);
// Draw the cube.
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
}
}
here is the methode loading texture:
public static int loadGLTexture(Context context, final int resourceId) {
Log.d("GLTools", "Loading texture...");
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]);
Log.d("GLTools", "Binding texture, setting parameter" + resourceId);
// 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();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
Ok, i am now able to answer my own question.
The texture was just scaled relative to its origin = left bottom point (u, v) = (0, 0), which has irritated me, since I am new to opengl, I was expecting that it would be scaled relative to at least world origin, or object's centroid.
I have tried to add a fairly simple extension on top of Android's example OpenGL 2.0 project in order to add texturing to basic shapes. This seems pretty straightforward, but on certain devices (Samsung Nexus S, LG Optimus 3D, Samsung Galaxy S) the texture just does not render.
This is actually a problem that I am having on a much larger project, but I was able to reproduce the issue with the simple project below in the hope that someone here has an idea of where my code presents issues, or how to specifically architect GL textures for these devices (maybe there are issues with the devices).
To give an idea of how this object is used: In the GLSurfaceView.Renderer's onSurfaceCreated method I am instantiating a Square() object and in the onDrawFrame method I am calling Square's draw() method. However, all of the relevant code to dealing with textures should appear in this Square class which is almost exactly identical to Google's own example.
Many thanks in advance to anyone who takes a crack at this.
class Square {
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
" gl_Position = vPosition * uMVPMatrix;" +
" v_TexCoordinate = a_TexCoordinate;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform sampler2D u_Texture;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
" gl_FragColor = texture2D(u_Texture, v_TexCoordinate);" +
"}";
private final FloatBuffer vertexBuffer;
private final FloatBuffer textureBuffer;
private final ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float squareCoords[] = { -0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f }; // top right
final float[] previewTextureCoordinateData =
{
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
private int textureDataHandle;
private int textureUniformHandle;
private int textureCoordinateHandle;
private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.2f, 0.709803922f, 0.898039216f, 1.0f };
private 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();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
public Square(Context context) {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
ByteBuffer texCoordinates = ByteBuffer.allocateDirect(previewTextureCoordinateData.length * 4);
texCoordinates.order(ByteOrder.nativeOrder());
textureBuffer = texCoordinates.asFloatBuffer();
textureBuffer.put(previewTextureCoordinateData);
textureBuffer.position(0);
// prepare shaders and OpenGL program
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
textureDataHandle = loadTexture(context, R.drawable.color_texture);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // create OpenGL program executables
}
public void draw(float[] mvpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
textureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");
GLES20.glVertexAttribPointer(textureCoordinateHandle, 2, GLES20.GL_FLOAT, false,
0, textureBuffer);
GLES20.glEnableVertexAttribArray(textureCoordinateHandle);
textureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
MyGLRenderer.checkGlError("glGetUniformLocation");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureDataHandle);
GLES20.glUniform1i(textureUniformHandle, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
MyGLRenderer.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
MyGLRenderer.checkGlError("glUniformMatrix4fv");
// Draw the square
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
I'll guess that this is a Power-of-two problem.
By default, the GL_TEXTURE_WRAP setting of glTexParameter is set to GL_REPEAT, and textures that use GL_REPEAT must be power-of-two sized:
Similarly, if the width or height of a texture image are not powers of two and either the
GL_TEXTURE_MIN_FILTER is set to one of the functions that requires mipmaps
or the GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T is not
set to GL_CLAMP_TO_EDGE, then the texture image unit will return
(R, G, B, A) = (0, 0, 0, 1).
You may start with a power-of-two texture, but when you use a BitmapFactory.decodeResource to generate a bitmap, it helpfully(?) scales this based on the density of a device. So for example if you load a 512*512 source texture from drawable folder on a HDPI device, I believe it scales it by 1.5x, so you're left with something that is not Po2.
This gives you the result that your textures don't work on a ton of devices, because those devices are all of a density that causes you to generate illegal texture sizes.
The solution in this case would be to place your (power of 2) source texture into the resource folder drawable-nodpi, which will prevent any density-based scaling. Either that or use CLAMP_TO_EDGE, which doesn't care about Po2.
I'm trying to load a texture (480x720px) as a background in my app, but it comes out as black. I'm a bit of a noob with GLES20 so I have followed a few examples on the internet to build my code, but it's giving me no luck. How fix?
Here's my shaders code:
public class Shaders {
private int mProgram;
private int maPositionHandle;
private int texCoords;
FloatBuffer texBuffer;
int iBaseMap;
private final String vertexShaderCode =
"attribute vec4 a_position;" +
"attribute vec2 a_texCoords;" +
"varying vec2 v_texCoords;" +
"void main()" +
"{" +
"gl_Position = a_position;" +
"v_texCoords = a_texCoords;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"varying vec2 v_texCoords;" +
"uniform sampler2D u_baseMap;" +
"void main()" +
"{" +
"gl_FragColor = texture2D(u_baseMap, v_texCoords);" +
"}";
private int loadShader(int type, String shaderCode){
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
public void initShaders() {
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
// get handle to the vertex shader's vPosition member
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "a_position");
iBaseMap = GLES20.glGetUniformLocation(mProgram, "u_baseMap");
texCoords = GLES20.glGetAttribLocation(mProgram, "a_texCoords");
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
GLES20.glViewport(0, 0, Settings.width, Settings.height);
}
public int getMPH() {
return maPositionHandle;
}
public int getMProg() {
return mProgram;
}
public int getIBaseMap() {
return iBaseMap;
}
public int getTexCoords() {
return texCoords;
}
public int loadTexture(GLSurfaceView view, int imgResID){
Bitmap img = null;
int textures[] = new int[1];
img = BitmapFactory.decodeResource(view.getResources(), imgResID);
System.out.println(imgResID);
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, img, 0);
img.recycle();
return textures[0];
}
}
and the class where I draw the background:
public class Background {
Shaders shaders;
int texId;
private GLSurfaceView mGLView;
private float BackgroundCoords[] = {
// X, Y, Z
-1f, -1f, 0, 0,0,
-1f, 1f, 0, 0, 1,
1f, 1f, 0, 1, 0,
1f, -1f, 0, 1, 1
};
private FloatBuffer backBuffer;
public void setAct(Shaders s, GLSurfaceView v) {
shaders = s;
mGLView = v;
texId = shaders.loadTexture(mGLView, R.raw.ground_tex);
System.out.println(texId);
}
public Background() {
// a float has 4 bytes so we allocate for each coordinate 4 bytes
ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(BackgroundCoords.length * 4);
vertexByteBuffer.order(ByteOrder.nativeOrder());
// allocates the memory from the byte buffer
backBuffer = vertexByteBuffer.asFloatBuffer();
// fill the vertexBuffer with the vertices
backBuffer.put(BackgroundCoords);
// set the cursor position to the beginning of the buffer
backBuffer.position(0);
}
/** The draw method for the square with the GL context */
public void draw() {
// Add program to OpenGL environment
GLES20.glUseProgram(shaders.getMProg());
// Point to our vertex buffer
backBuffer.position(0);
GLES20.glVertexAttribPointer(shaders.getMPH(), 3, GLES20.GL_FLOAT, false, 5 * 4, backBuffer);
GLES20.glEnableVertexAttribArray(shaders.getMPH());
backBuffer.position(3);
GLES20.glVertexAttribPointer(shaders.getTexCoords(), 2, GLES20.GL_FLOAT, false, 5 * 4, backBuffer);
GLES20.glEnableVertexAttribArray(shaders.getTexCoords());
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
GLES20.glUniform1i(shaders.getIBaseMap(), 0);
// Draw the vertices as triangle strip
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 4);
}
}
this code works if I only try with a simple shader, though only one of the two triangles that fill the background gets colored.
There's an error "E/libEGL(8971): call to OpenGL ES API with no current context (logged once per thread)" originating from the loadTexture function on every call to GLES"=.
I really hope someone can help me.
"color = texture2D(u_baseMap, gl_PointCoord);"
Why did you decide to use gl_PointCoord here? You're drawing triangles, and according to the documentation:
If the current primitive is not a point, then values read
from gl_PointCoord are undefined.
If you want to use gl_PointCoord, than draw GL_POINTS, not GL_TRIANGLES. Otherwise generate and send texture coordinates.