I’ve been developing a cube program that provides a number of cubes with desired qualities. However, whenever I try to light a textured cube, my cube becomes very dark. The lighting works well with a non-textured cube so I’m led to believe it’s done properly just as a simple textured cube without lighting works. There doesn’t seem to be significant documentation on how to solve this in OpenGL 2.0+ but there are a few things pertaining to older versions.
The following link offers information as to why my cube is behaving as it is, but I’m having trouble translating the solution to a newer version, especially within my shader code where I’m unsure if further changes should occur. I am using Android Studio 2.1.3 if that and its contained emulators would pose issues to the desired effect. If anyone could offer any advice, I’d greatly appreciate it. I have a separate (large) renderer that calls for the Cube to be drawn, let me know if that code would be beneficial as well in addition to my Cube. Below is my Cube:
public class TexturedLightCube {
/** Cube vertices */
private static final float VERTICES[] = {
-0.3f, -0.3f, -0.3f, //top front right
0.3f, -0.3f, -0.3f, //bottom front right
0.3f, 0.3f, -0.3f, //bottom front left
-0.3f, 0.3f, -0.3f, //top front left
-0.3f, -0.3f, 0.3f, //top back right
0.3f, -0.3f, 0.3f, //bottom back right
0.3f, 0.3f, 0.3f, //bottom back left
-0.3f, 0.3f, 0.3f // top back left
};
/** Vertex colors. */
private static final float COLORS[] = {
0.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f, 1.0f,
};
/** Order to draw vertices as triangles. */
private static final byte INDICES[] = {
0, 1, 3, 3, 1, 2, // Front face.
0, 1, 4, 4, 5, 1, // Bottom face.
1, 2, 5, 5, 6, 2, // Right face.
2, 3, 6, 6, 7, 3, // Top face.
3, 7, 4, 4, 3, 0, // Left face.
4, 5, 7, 7, 6, 5, // Rear face.
};
private static final float TEXTURECOORDS[] =
{
0.0f, 1.0f, //left-bottom
0.0f, 0.0f, //right bottom
1.0f, 0.0f, //left top
1.0f, 1.0f, //right top
0.0f, 1.0f, //left-bottom
0.0f, 0.0f, //right bottom
1.0f, 0.0f, //left top
1.0f, 1.0f, //right top
};
private static final float NORMALS[] = {
//set all normals to all light for testing
1.0f, 1.0f, 1.0f, //top front right
1.0f, 0.0f, 1.0f, //bottom front right
0.0f, 0.0f, 1.0f, //bottom front left
0.0f, 1.0f, 1.0f, //top front left
1.0f, 1.0f, 0.0f, //top back right
1.0f, 0.0f, 0.0f, //bottom back right
0.0f, 0.0f, 0.0f, //bottom back left
0.0f, 1.0f, 0.0f //top back left
};
static final int COORDS_PER_VERTEX = 3;
private static final int VALUES_PER_COLOR = 4;
/** Vertex size in bytes. */
final int VERTEX_STRIDE = COORDS_PER_VERTEX * 4;
/** Color size in bytes. */
private final int COLOR_STRIDE = VALUES_PER_COLOR * 4;
/** Shader code for the vertex. */
private static final String VERTEX_SHADER_CODE =
"uniform mat4 uMVPMatrix;" +
"uniform mat4 uMVMatrix;" +
"uniform vec3 u_LightPos;" +
"attribute vec4 vPosition;" +
"attribute vec4 a_Color;" +
"attribute vec3 a_Normal;" +
"varying vec4 v_Color;" +
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
"vec3 modelViewVertex = vec3(uMVMatrix * vPosition);"+
"vec3 modelViewNormal = vec3(uMVMatrix * vec4(a_Normal, 0.0));" +
"float distance = length(u_LightPos - modelViewVertex);" +
"vec3 lightVector = normalize(u_LightPos - modelViewVertex);" +
"float diffuse = max(dot(modelViewNormal, lightVector), 0.1);" +
"diffuse = diffuse * (1.0/(1.0 + (0.00000000000002 * distance * distance)));" + //attenuation factor
"v_Color = a_Color * a_Color * diffuse;" +
"gl_Position = uMVPMatrix * vPosition;" +
"v_TexCoordinate = a_TexCoordinate;" +
"}";
/** Shader code for the fragment. */
private static final String FRAGMENT_SHADER_CODE =
"precision mediump float;" +
"varying vec4 v_Color;" +
"uniform sampler2D u_Texture;"+ //The input texture
"varying vec2 v_TexCoordinate;" +
"void main() {" +
" gl_FragColor = v_Color * texture2D(u_Texture, v_TexCoordinate) ;" + //still works with just color
"}";
private int mTextureUniformHandle; //Pass in texture.
private int mTextureCoordinateHandle; //Pass in model texture coordinate information.
private final int mTextureCoordinateDataSize = 2; //Size of texture coordinate data in elements
public static int mTextureDataHandle; //Handle to texturedata;
private final FloatBuffer mTextureBuffer; //Store model data in float buffer.
private final FloatBuffer mVertexBuffer;
private final FloatBuffer mColorBuffer;
private final FloatBuffer mNormalBuffer;
private final ByteBuffer mIndexBuffer;
private final int mProgram;
private final int mPositionHandle;
private final int mColorHandle;
private final int mMVPMatrixHandle;
private final int mNormalHandle;
public static int mLightPosHandle;
public final int mMVMatrixHandle;
public static int loadTexture(final Context context, final int resourceId) {
//Get the texture from the Android resource directory
final int[] textureHandle = new int[1];
InputStream is = context.getResources().openRawResource(+ R.drawable.teneighty);
Bitmap bitmap = null;
try {
//BitmapFactory is an Android graphics utility for images
bitmap = BitmapFactory.decodeStream(is);
} finally {
//Always clear and close
try {
is.close();
is = null;
} catch (IOException e) {
}
}
//Generate one texture pointer...
GLES20.glGenTextures(1, textureHandle, 0);
//and bind it to our array.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
//Create Nearest Filtered Texture.
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_LINEAR);
//Accounting for different texture parameters.
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//Use the Android GLUtils to specify a two-dimensional texture image from our bitmap.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
//Clean up
bitmap.recycle();
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture");
}
return textureHandle[0];
}
public TexturedLightCube() {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(VERTICES.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mVertexBuffer = byteBuffer.asFloatBuffer();
mVertexBuffer.put(VERTICES);
mVertexBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(COLORS.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mColorBuffer = byteBuffer.asFloatBuffer();
mColorBuffer.put(COLORS);
mColorBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(NORMALS.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mNormalBuffer = byteBuffer.asFloatBuffer();
mNormalBuffer.put(NORMALS);
mNormalBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(TEXTURECOORDS.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mTextureBuffer = byteBuffer.asFloatBuffer();
mTextureBuffer.put(TEXTURECOORDS);
mTextureBuffer.position(0);
mIndexBuffer = ByteBuffer.allocateDirect(INDICES.length);
mIndexBuffer.put(INDICES);
mIndexBuffer.position(0);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE));
GLES20.glAttachShader(mProgram, loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE));
GLES20.glLinkProgram(mProgram);
mTextureDataHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");
mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
mMVMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVMatrix");
mLightPosHandle = GLES20.glGetUniformLocation(mProgram, "u_LightPos");
mNormalHandle = GLES20.glGetAttribLocation(mProgram, "a_Normal");
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
mColorHandle = GLES20.glGetAttribLocation(mProgram, "a_Color");
}
/**
* Encapsulates the OpenGL ES instructions for drawing this shape.
*
* #param mvpMatrix The Model View Project matrix in which to draw this shape
*/
public void draw(float[] mvpMatrix) {
// Add program to OpenGL environment.
GLES20.glUseProgram(mProgram);
//set active texture unit to texture unit 0.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
// Prepare the cube coordinate data.
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);
// Prepare the cube color data.
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glVertexAttribPointer(mColorHandle, 4, GLES20.GL_FLOAT, false, COLOR_STRIDE, mColorBuffer);
//Will have the same size as Vertex as we are implementing per vertex lighting
GLES20.glEnableVertexAttribArray(mNormalHandle);
GLES20.glVertexAttribPointer(mNormalHandle, 3, GLES20.GL_FLOAT, false, VERTEX_STRIDE, mNormalBuffer);
// Prepare the cube texture data.
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
//Pass texture coordinate information.
GLES20.glVertexAttribPointer(mTextureCoordinateHandle,4, GLES20.GL_FLOAT, false, mTextureCoordinateDataSize, mTextureBuffer);
// Apply the projection and view transformation.
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
GLES20.glUniform3f(LightCube.mLightPosHandle, MyGLRenderer.mLightPosInEyeSpace[0], MyGLRenderer.mLightPosInEyeSpace[1], MyGLRenderer.mLightPosInEyeSpace[2]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(mTextureUniformHandle, 0);
// Draw the cube.
GLES20.glDrawElements(GLES20.GL_TRIANGLES, INDICES.length, GLES20.GL_UNSIGNED_BYTE, mIndexBuffer); //-removed indices-
// Disable vertex arrays.
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordinateHandle);
GLES20.glDisableVertexAttribArray(mColorHandle);
GLES20.glDisableVertexAttribArray(mNormalHandle);
}
/** Loads the provided shader in the program. */
private static int loadShader(int type, String shaderCode){
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
You're missing an ambient component to your lighting, which emulates second order (and higher) reflections you would get in real life, but can't get directly in a rasterizer.
Not sure why you are squaring a_Color in your fragment shader. This will definitely make things darker because all values are between 0 and 1; e.g. 0.1^2 == 0.01.
Remember that your dot product might be negative, so you want to clamp out negative diffuse components (e.g. no light intensity on surfaces which are facing away from the light).
Related
I want to render a simple cube in opengl es 3.0 on my android device but it doesn't render. This is my SurfaceView setup:
public GLContextView(Context context){
super(context);
setEGLContextClientVersion(3);
setRenderer(renderer);
}
Render Code:
public void onSurfaceCreated(javax.microedition.khronos.opengles.GL10 unused, javax.microedition.khronos.egl.EGLConfig p2){
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
GLES30.glEnable(GLES30.GL_BLEND);
GLES30.glEnable(GLES30.GL_CULL_FACE);
GLES30.glDepthFunc(GLES30.GL_LEQUAL);
GLES30.glCullFace(GLES30.GL_BACK);
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
cubeProgram = RenderHelper.createShaderProgram(Shader.CubeVertexShader, Shader.CubeFragmentShader);
int[] array = new int[2];
GLES30.glGenVertexArrays(1, array, 0);
vaId = array[0];
GLES30.glBindVertexArray(vaId);
GLES30.glGenBuffers(2, array, 0);
vbId = array[0];
ibId = array[1];
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbId);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, RenderCube.vertices.length * 4, RenderCube.vBuffer, GLES30.GL_STATIC_DRAW);
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, 0);
GLES30.glEnableVertexAttribArray(positionHandle);
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ibId);
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, RenderCube.indices.length * 4, RenderCube.iBuffer, GLES30.GL_STATIC_DRAW);
colorHandle = GLES30.glGetUniformLocation(cubeProgram, "in_color");
GLES30.glBindVertexArray(0);
}
public void onDrawFrame(GL10 p1){
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
GLES30.glUseProgram(cubeProgram);
GLES30.glBindVertexArray(vaId);
GLES30.glUniform4f(colorHandle, 1.0f, 1.0f, 1.0f, 1.0f);
GLES30.glDrawElements(GLES30.GL_TRIANGLES, RenderCube.indices.length, GLES30.GL_UNSIGNED_INT, 0);
}
public void onSurfaceChanged(GL10 p1, int p2, int p3){
GLES30.glViewport(0, 0, p2, p3);
}
RenderCube class with vertex and index data:
class RenderCube{
public static float[] vertices = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
};
public static int[] indices = {
0, 3, 1, 0, 2, 3, //Bottom
4, 7, 5, 4, 6, 7, //Top
0, 5, 1, 0, 4, 5, //Back
2, 7, 3, 2, 6, 7, //Front
0, 6, 2, 0, 4, 6, //Left
1, 7, 3, 1, 5, 7 //Right
};
public static FloatBuffer vBuffer = RenderHelper.createFloatBuffer(vertices.length * 4, vertices);
public static IntBuffer iBuffer = RenderHelper.createIntBuffer(indices.length * 4, indices);
};
Shaders:
final class Shader{
public static final String CubeVertexShader =
"#version 300 es\n" +
"layout (location = 0) in vec3 pos;" +
"void main(){" +
" gl_Position = vec4(pos, 1.0f);" +
"}";
public static final String CubeFragmentShader =
"#version 300 es\n" +
"precision mediump float;" +
"uniform vec4 in_color;" +
"out vec4 color;" +
"void main(){" +
" color = in_color;" +
"}";
}
It compiles fine and no opengl errors are printed. What am I doing wrong?
I think you simply have no visible triangles.
The triangles of the bottom, top, left, and right faces are invisible because they're orthogonal to the viewing plane. So you're looking at them edge on, and they end up as degenerate triangles (i.e. triangles with zero area).
The way you defined them, the triangles of the back and front face all have clockwise winding order. Expanding the indices of those 4 triangles, and showing just the x and y coordinates of the corresponding vertices:
0, 5, 1 -> (0.0f, 0.0f), (1.0f, 1.0f), (1.0f, 0.0f)
0, 4, 5 -> (0.0f, 0.0f), (0.0f, 1.0f), (1.0f, 1.0f)
2, 7, 3 -> (0.0f, 0.0f), (1.0f, 1.0f), (1.0f, 0.0f)
2, 6, 7 -> (0.0f, 0.0f), (0.0f, 1.0f), (1.0f, 1.0f)
As you can see, these triangles are all clockwise. Since you chose to enable culling of back faces:
GLES30.glEnable(GLES30.GL_CULL_FACE);
GLES30.glCullFace(GLES30.GL_BACK);
and the default winding order for front faces is counter-clockwise, meaning that the winding of back faces is clockwise, all these triangles will be culled.
In addition, since the front face is at z = 1.0, it's also exactly on the front clip plane. The way I read the spec, geometry that is exactly on a clipping plane should still be visible. But it's probably safer to place it clearly inside the clip volume.
I drew a cube in openGL es 2.0.
Right now it has just two faces, for testing purposes(the front and the back). So basically there are two planes in space, both with the same color.
Now I want to apply a different color to each face. I tought that expanding the color array was sufficient, but the colors are not changing (there's just the original color).
Do I have to change the shader? Or pass a specific function to the draw method?
The class should explain better
public class Cube {
private FloatBuffer mVer;
private FloatBuffer colMem;
private ShortBuffer ordVer;
private float vertici[] = {
-0.2f, 0.2f, 0.2f, //p1 upper left front plane (0)
-0.2f, -0.2f, 0.2f, //p2 lower left front plane (1)
0.2f, -0.2f, 0.2f, //p3 lower right front plane (2)
0.2f, 0.2f, 0.2f, //p4 upper right front plane (3)
-0.2f, 0.2f, -0.2f, //p1 upper left front plane (4)
-0.2f, -0.2f, -0.2f, //p2 lower left front plane (5)
0.2f, -0.2f, -0.2f, //p3 lower right front plane (6)
0.2f, 0.2f, -0.2f, //p4 upper right front plane (7)
};
private short order[] = {
0, 1, 2, 0, 2, 3, //front face
7, 6, 5, 7, 5, 4, //back face
//3, 2, 6, 3, 6, 7, //right face
// 0, 1, 5, 0, 5, 4 //left face
};
private float color [] = {
0.8f, 0.8f, 0.1f, 1.0f,//color1
0.8f, 0.8f, 0.1f, 1.0f,
0.8f, 0.8f, 0.1f, 1.0f,
0.8f, 0.8f, 0.1f, 1.0f,
0.1f, 0.2f, 0.5f, 1.0f,//color2
0.1f, 0.2f, 0.5f, 1.0f,
0.1f, 0.2f, 0.5f, 1.0f,
0.1f, 0.2f, 0.5f, 1.0f
};
private final String vertCode =
"uniform mat4 uMVPMatrix;"+
"attribute vec4 vPosition;"+
"void main() {"+
"gl_Position = uMVPMatrix * vPosition;"+
"}";
private final String fragCode =
"precision mediump float;"+
"uniform vec4 vColor;"+
"void main() {"+
"gl_FragColor = vColor;"+
"}";
private int prog;
private int pos;
private int col;
private int mHandle;
public Cube () {
mVer = ByteBuffer.allocateDirect(vertici.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mVer.put(vertici).position(0);
ordVer = ByteBuffer.allocateDirect(order.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
ordVer.put(order).position(0);
colMem = ByteBuffer.allocateDirect(color.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
colMem.put(color).position(0);
int vertexShader = Render.loadShader (GLES20.GL_VERTEX_SHADER, vertCode);
int fragmentShader = Render.loadShader (GLES20.GL_FRAGMENT_SHADER, fragCode);
prog = GLES20.glCreateProgram();
GLES20.glAttachShader(prog, vertexShader);
GLES20.glAttachShader(prog, fragmentShader);
GLES20.glLinkProgram(prog);
}
public void draw (float[] mVMatrix) {
GLES20.glUseProgram(prog);
pos = GLES20.glGetAttribLocation(prog, "vPosition");
GLES20.glEnableVertexAttribArray(pos);
GLES20.glVertexAttribPointer(pos, 3, GLES20.GL_FLOAT, false, 12, mVer);
col = GLES20.glGetUniformLocation(prog, "vColor");
GLES20.glUniform4fv(col, 1, color, 0);
mHandle = GLES20.glGetUniformLocation(prog, "uMVPMatrix");
GLES20.glUniformMatrix4fv(mHandle, 1, false, mVMatrix, 0);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, order.length, GLES20.GL_UNSIGNED_SHORT, ordVer);
GLES20.glDisableVertexAttribArray(pos);
}
}
You have a few options:
If you want to pass in the color as a uniform, which seems to be where you were headed, you need to draw each face with a separate draw call. You can't just pass in an array of colors for the uniform, and expect the colors to be applied to the triangles in order. You would call glUniform4v with the first color, call glDrawElements with just the indices f the first face, and then repeat these two calls for each face. This is fairly inefficient.
You make the colors an attribute instead of a uniform, very similar to what you do for the vertex positions. You have to be careful when using this approach because you need an OpenGL vertex for each combination of position and color. For a cube, you typically end up with 24 vertices. You should be able to find details if you search for older questions about similar topics.
There's another method called "instanced rendering" that could be applied, but that is only available in ES 3.0.
I've spent days searching, trying tutorials, and not actually getting results in this, so here I am.
I'm trying, simply put, to animate a collection of objects (Android Studio) on the screen, in a 2D format, with each independent movements and rotations. However, when I try this, I'm either not getting the object rendered, or its rendering skewed (as if rotated through the vertical Y-axis)
I know the importance of the order in which objects are drawn too (to give correct Z-ordering appearance) however, I'm at a bit of a loss with the matrix manipulation.
Here is what I have so far:
Main Activity - standard stuff
private GLSurfaceView mGLSurfaceView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLSurfaceView = new GLSurfaceView(this);
//check if device supports ES 2.0
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
if (supportsEs2) {
//Get the ES2 compatible context
mGLSurfaceView.setEGLContextClientVersion(2);
//set renderer to my renderer below
mGLSurfaceView.setRenderer(new MyGL20Renderer(this));
} else {
//no support
return;
}
//setContentView(R.layout.activity_main);
setContentView(mGLSurfaceView);
}
GL20Renderer class - Notice I'm now just manually adding 2 objects to my collection to render
public class MyGL20Renderer implements GLSurfaceView.Renderer
{
private final Context mActivityContext;
//Matrix Initializations
private final float[] mMVPMatrix = new float[16];
private final float[] mProjMatrix = new float[16];
private final float[] mVMatrix = new float[16];
private float[] mRotationMatrix = new float[16];
private final float[] mRotateMatrix = new float[16];
private final float[] mMoveMatrix = new float[16];
private final float[] mTempMatrix = new float[16];
private final float[] mModelMatrix = new float[16];
private int numObjects = 2;
private ArrayList<Sprite> spriteList = new ArrayList<Sprite>();
//Declare as volatile because we are updating it from another thread
public volatile float mAngle;
//private Triangle triangle;
//private Sprite sprite;
public MyGL20Renderer(final Context activityContext)
{
mActivityContext = activityContext;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config)
{
//Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//Set the camera position (View Matrix) //mtx, offset, eyex,y,z, centrex,y,z, upx,y,z
Matrix.setLookAtM(mVMatrix, 0,
0, 0, -1.5f, //Eye XYZ - position eye behind the origin
0f, 0f, -5.0f, //Look XYZ - We are looking toward the distance
0f, 1.0f, 0.0f); //Up XYZ - Up vector - where head would be pointing if holding the camera
//Initialize Shapes
//triangle = new Triangle();
//sprite = new Sprite(mActivityContext);
//Sprite newSprite;
float xMax = 2.0f;
float yMax = 2.0f;
//rand = 0->1
float newX = (new Random().nextFloat() * xMax * 2) - xMax; //2.0f; //-2 -> +2
float newY = (new Random().nextFloat() * yMax * 2) - yMax; //-3 -> +3
float newZ = 0f;
//for (int i=0; i<numObjects; i++) {
//newSprite = new Sprite(mActivityContext);
//spriteList.add(new Sprite(mActivityContext, newX, newY, newZ));
//}
spriteList.add(new Sprite(mActivityContext, -0.0f, -0.0f, 0.0f));
spriteList.add(new Sprite(mActivityContext, +0.5f, -0.5f, 0.0f));
//spriteList.add(new Sprite(mActivityContext, -1.0f, +1.0f, 0.0f));
//spriteList.add(new Sprite(mActivityContext, +1.0f, +1.0f, 0.0f));
}
public void onDrawFrame(GL10 unused)
{
//init
Sprite currSprite;
//Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//timing
float jFactor = 0.1f;
long time = SystemClock.uptimeMillis() % 10000L;
float angleInDegrees = (360.0f / 1000.0f) * ((int) time) * jFactor;
/*
//number 1
//Matrix.setIdentityM(mModelMatrix, 0);
//currSprite = spriteList.get(0);
//Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
//currSprite.Draw(mModelMatrix);
//number 2
Matrix.setIdentityM(mModelMatrix, 0);
currSprite = spriteList.get(1);
Matrix.translateM(mModelMatrix, 0, 0.0f, -0.1f, 0.0f);
//Matrix.rotateM(mModelMatrix, 0, 90.0f, 1.0f, 0.0f, 0.0f);
Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
currSprite.Draw(mModelMatrix);
//Matrix.translateM(mModelMatrix, 0, 0, 0, 4.0f);
*/
//Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
//zoom out a bit?
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.0f);
//number 1
//currSprite = spriteList.get(0);
//Matrix.setIdentityM(mMVPMatrix, 0);
//Matrix.rotateM(mMVPMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
//Matrix.translateM(mMVPMatrix, 0, currSprite.coordX, 0.0f, 0.0f);
//currSprite.coordX += 0.01f;
//currSprite.Draw(mMVPMatrix);
//number 2
currSprite = spriteList.get(0);
Matrix.setIdentityM(mMVPMatrix, 0);
Matrix.translateM(mMVPMatrix, 0, 0.0f, 0.0f, 0.0f);
Matrix.rotateM(mMVPMatrix, 0, angleInDegrees, 0.0f, 0.0f, +1.0f);
//float[] mTempMatrix = new float[16];
//mTempMatrix = mModelMatrix.clone();
//Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, mRotateMatrix, 0);
//mTempMatrix = mMVPMatrix.clone();
//Matrix.multiplyMM(mMVPMatrix, 0, mTempMatrix, 0, mModelMatrix, 0);
//Matrix.setIdentityM(mMVPMatrix, 0);
currSprite.Draw(mMVPMatrix);
/*
//Set the camera position (View Matrix) //mtx, offset, eyex,y,z, centrex,y,z, upx,y,z
Matrix.setLookAtM(mVMatrix, 0,
0, 0, -10,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
//Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
//zoom out a bit?
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.0f);
for (int i=0; i<numObjects; i++) {
//Create a rotation transformation for the triangle
//Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);
Matrix.setRotateM(mRotationMatrix, 0, 0, 0, 0, -1.0f); //-1.0 = Z, for some reason need this. Grr
//Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0);
//Draw Shape
//triangle.Draw(mMVPMatrix);
//sprite.Draw(mMVPMatrix);
currSprite = spriteList.get(i);
//Move the object to the passed initial coordinates?
//Matrix.translateM(mMVPMatrix, 0, currSprite.coordX, currSprite.coordY, currSprite.coordZ);
currSprite.Draw(mMVPMatrix);
}
*/
}
public void onSurfaceChanged(GL10 unused, int width, int height)
{
GLES20.glViewport(0, 0, width, height);
if (height == 0) {
height = 1; //incase of div 0 errors
}
float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0f;
final float top = 1.0f;
final float near = 1.0f;
final float far = 10.f;
//This Projection Matrix is applied to object coordinates in the onDrawFrame() method
//Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
}
public static int loadShader(int type, String shaderCode)
{
//Create a Vertex Shader Type Or a Fragment Shader Type (GLES20.GL_VERTEX_SHADER OR GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
//Add The Source Code and Compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
Please excuse the commented code in OnDrawFrame() where I've been experimenting, and failing.
Sprite Class
public class Sprite
{
//Reference to Activity Context
private final Context mActivityContext;
//Added for Textures
private final FloatBuffer mCubeTextureCoordinates;
private int mTextureUniformHandle;
private int mTextureCoordinateHandle;
private final int mTextureCoordinateDataSize = 2;
private int mTextureDataHandle;
private final String vertexShaderCode =
//Test
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
//End Test
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition * uMVPMatrix;" +
//Test
"v_TexCoordinate = a_TexCoordinate;" +
//End Test
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
//Test
"uniform sampler2D u_Texture;" +
"varying vec2 v_TexCoordinate;" +
//End Test
"void main() {" +
//"gl_FragColor = vColor;" +
"gl_FragColor = (vColor * texture2D(u_Texture, v_TexCoordinate));" +
"}";
private final int shaderProgram;
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
public float coordX;
public float coordY;
//public float coordZ;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 2;
static float spriteCoords[] = { -0.5f, 0.5f, // top left
-0.5f, -0.5f, // bottom left
0.5f, -0.5f, // bottom right
0.5f, 0.5f }; //top right
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //Order to draw vertices
private final int vertexStride = COORDS_PER_VERTEX * 4; //Bytes per vertex
// Set color with red, green, blue and alpha (opacity) values
//float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
float color[] = { 1f, 1f, 1f, 1.0f };
public Sprite(final Context activityContext, float initX, float initY, float initZ)
{
mActivityContext = activityContext;
this.coordX = initX;
this.coordY = initY;
//this.coordZ = initZ;
//ergh - will do manually for now. Paxo n00b
//just a 2D array, no need for Z nonsense
for (int i=0; i<spriteCoords.length; i++) {
spriteCoords[i] -= (i%2==0) ? coordX : coordY; //- works better than +
}
//float newPosMatrix[] = { initX, initY, 0f };
//adjust the vector coords accordingly
//Matrix.multiplyMV(spriteCoords, 0, newPosMatrix, 0, spriteCoords, 0);
//Initialize Vertex Byte Buffer for Shape Coordinates / # of coordinate values * 4 bytes per float
ByteBuffer bb = ByteBuffer.allocateDirect(spriteCoords.length * 4);
//Use the Device's Native Byte Order
bb.order(ByteOrder.nativeOrder());
//Create a floating point buffer from the ByteBuffer
vertexBuffer = bb.asFloatBuffer();
//Add the coordinates to the FloatBuffer
vertexBuffer.put(spriteCoords);
//Set the Buffer to Read the first coordinate
vertexBuffer.position(0);
// S, T (or X, Y)
// Texture coordinate data.
// Because images have a Y axis pointing downward (values increase as you move down the image) while
// OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.
// What's more is that the texture coordinates are the same for every face.
final float[] cubeTextureCoordinateData =
{
//Front face
/*0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f*/
/*-0.5f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f*/
0f, 1f,
0f, 0f,
1f, 0f,
1f, 1f
};
mCubeTextureCoordinates = ByteBuffer.allocateDirect(cubeTextureCoordinateData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);
//Initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(spriteCoords.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
int vertexShader = MyGL20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGL20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
shaderProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(shaderProgram, vertexShader);
GLES20.glAttachShader(shaderProgram, fragmentShader);
//Texture Code
GLES20.glBindAttribLocation(shaderProgram, 0, "a_TexCoordinate");
GLES20.glLinkProgram(shaderProgram);
//Load the texture
mTextureDataHandle = loadTexture(mActivityContext, R.drawable.cube);
}
public void Draw(float[] mvpMatrix)
{
//Add program to OpenGL ES Environment
GLES20.glUseProgram(shaderProgram);
//Get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(shaderProgram, "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);
//Get Handle to Fragment Shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");
//Set the Color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
//Set Texture Handles and bind Texture
mTextureUniformHandle = GLES20.glGetAttribLocation(shaderProgram, "u_Texture");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoordinate");
//Set the active texture unit to texture unit 0.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//Bind the texture to this unit.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
//Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
GLES20.glUniform1i(mTextureUniformHandle, 0);
//Pass in the texture coordinate information
mCubeTextureCoordinates.position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mCubeTextureCoordinates);
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
//Get Handle to Shape's Transformation Matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");
//Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
//glTranslatef(0f, 0f, 0f);
//Draw the triangle
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
//Disable Vertex Array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
public static 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];
}
}
Now, I don't know if I'm going about this the right way at all, but I simply want to just animate the collection of Sprite objects in spriteList.
More specifically, have a collection of 3 objects and then respond to screen touch, and animate the objects to that location (but that will come later)
Initially, I just want to be able to correctly render these objects (with initial locations) and then rotate them on the centre point (about the Z axis).
For some reason, TranslateM is warping the texture (as if about the Y axis) and not actually moving an object along the X/Y planes
Many thanks for any help you can offer. As you can see I'm fairly new to OpenGL and have had little luck with the limited tutorials out there that support Android Studio and GLES2.0.
Kind regards,
James
I think the problem is that you have not multiplied the translation matrices into your rotation matrices. A matrix multiply is required to combine those.
I've been making an Android OpenGLES2.0 2D game engine for the past week or so, and after a few bumps in the road, I've largely been successful. I've got the ModelMatrix, ProjectionMatrix, ViewMatrix, LightMatrix, shaders, 2D planes, and textures implemented. However, although my data is seemingly passing through this jungle of pipeline just fine, my textures do not appear, and are instead a solid black.
Most, if not all of my code was derived from this source, and it is ultimately the same, except that I created my own shader class, bounding box class, room class, and game object class to simplify the process of instantiating objects in-game. Renderer takes Room, Room takes GameObject(s) (SpaceShip extends game object), and GameObject takes BoundingBox, then Renderer renders the room's objects in a for loop. To do this, I moved the exact code from the example around so that certain handles are elements of some of the classes I created, instead of being elements of the renderer. This hasn't caused any problems with matrix multiplication or my data reaching the end of the pipeline, so I doubt moving the handles is the problem, but I felt it was important to know.
Things I've tried:
Changing the bitmap
Changed it to a bitmap with no alpha channel, both were 32x32 (2^5) and were .png.
Changing the order of operations
I moved glBindTexture in my implementation, so I moved it back, then back again.
Changing the texture parameters
I tried several combinations, none with mip-mapping
Changing the way I load the image
Went from BitmapFactory.decodeResource to BitmapFactory.decodeStream
Moved the texture to all drawable folders
Also tried it in the raw folder
Tried it on another device
My friend's DROID (Froyo 2.2), My rooted NextBook (Gingerbread 2.3). Both support OpenGLES2.0.
Thigs I haven't tried (That I'm aware of):
Changing the texture coordinates
They came directly from the example. I just took one face of the cube.
Changing my shader
It also came directly from the example (aside from it being it's own class now).
Restructuring my program to be just two (3, 4... x) classes
Dude...
I've been testing on the emulator (Eclipse Indigo, AVD, Intel Atom x86, ICS 4.2.2, API level 17) for some time now, and right about the time I got all the matrixes working, the emulator failed to render anything. It used to render just fine (when the projection was all screwy), now it just shows up black with a titlebar. This has made debugging incredibly difficult. I'm not sure if this is something related to what I've done (probably is) or if it is related to the emulator sucking at OpenGL.
Sorry to be so long winded and include so much code, but I don't know how to use a show/hide button.
Any ideas?
Edit: I was using the wrong shader from the example. The naming was very misleading. I wasn't passing in the color info. I still don't have texture, but the emulator works again. :)
OpenGLES20_2DRenderer
package mycompany.OpenGLES20_2DEngine;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
public class OpenGLES20_2DRenderer implements GLSurfaceView.Renderer {
/** Used for debug logs. */
private static final String TAG = "Renderer";
//Matrix Declarations*************************
/**
* Store the model matrix. This matrix is used to move models from object space (where each model can be thought
* of being located at the center of the universe) to world space.
*/
private float[] mModelMatrix = new float[16];
/**
* Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
* it positions things relative to our eye.
*/
private float[] mViewMatrix = new float[16];
/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = new float[16];
/** Allocate storage for the final combined matrix. This will be passed into the shader program. */
private float[] mMVPMatrix = new float[16];
/**
* Stores a copy of the model matrix specifically for the light position.
*/
private float[] mLightModelMatrix = new float[16];
//********************************************
//Global Variable Declarations****************
//Shader
Shader shader;
//PointShader
PointShader pointShader;
//Application Context
Context context;
//A room to add objects to
Room room;
//********************************************
public OpenGLES20_2DRenderer(Context ctx) {
context = ctx;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
//Initialize GLES20***************************
// Set the background frame color
GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
// Use culling to remove back faces.
GLES20.glEnable(GLES20.GL_CULL_FACE);
// Enable depth testing
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
// Position the eye in front of the origin.
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = -0.5f;
// We are looking toward the distance
final float lookX = 0.0f;
final float lookY = 0.0f;
final float lookZ = -5.0f;
// Set our up vector. This is where our head would be pointing were we holding the camera.
final float upX = 0.0f;
final float upY = 1.0f;
final float upZ = 0.0f;
// Set the view matrix. This matrix can be said to represent the camera position.
// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
//********************************************
//Initialize Shaders**************************
shader = new Shader();
pointShader = new PointShader();
//********************************************
//Load The Level******************************
//Create a new room
room = new Room(800,600, 0);
//Load game objects
SpaceShip user = new SpaceShip();
//Load sprites
for(int i=0;i<room.numberOfGameObjects;i++) {
room.gameObjects[i].spriteGLIndex = room.gameObjects[i].loadSprite(context, room.gameObjects[i].spriteResId);
}
//Add them to the room
room.addGameObject(user);
//********************************************
}
public void onDrawFrame(GL10 unused) {
//Caclulate MVPMatrix*************************
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Set our per-vertex lighting program.
GLES20.glUseProgram(shader.mProgram);
// Set program handles for object drawing.
shader.mMVPMatrixHandle = GLES20.glGetUniformLocation(shader.mProgram, "u_MVPMatrix");
shader.mMVMatrixHandle = GLES20.glGetUniformLocation(shader.mProgram, "u_MVMatrix");
shader.mLightPosHandle = GLES20.glGetUniformLocation(shader.mProgram, "u_LightPos");
shader.mTextureUniformHandle = GLES20.glGetUniformLocation(shader.mProgram, "u_Texture");
shader.mPositionHandle = GLES20.glGetAttribLocation(shader.mProgram, "a_Position");
shader.mColorHandle = GLES20.glGetAttribLocation(shader.mProgram, "a_Color");
shader.mNormalHandle = GLES20.glGetAttribLocation(shader.mProgram, "a_Normal");
shader.mTextureCoordinateHandle = GLES20.glGetAttribLocation(shader.mProgram, "a_TexCoordinate");
// Calculate position of the light. Rotate and then push into the distance.
Matrix.setIdentityM(mLightModelMatrix, 0);
Matrix.translateM(mLightModelMatrix, 0, 0.0f, 0.0f, -5.0f);
Matrix.rotateM(mLightModelMatrix, 0, 0, 0.0f, 1.0f, 0.0f);
Matrix.translateM(mLightModelMatrix, 0, 0.0f, 0.0f, 2.0f);
Matrix.multiplyMV(shader.mLightPosInWorldSpace, 0, mLightModelMatrix, 0, shader.mLightPosInModelSpace, 0);
Matrix.multiplyMV(shader.mLightPosInEyeSpace, 0, mViewMatrix, 0, shader.mLightPosInWorldSpace, 0);
//********************************************
//Draw****************************************
//Draw the background
//room.drawBackground(mMVPMatrix);
// Draw game objects
for(int i=0;i<room.numberOfGameObjects;i++) {
// Set the active texture unit to texture unit 0.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// Bind the texture to this unit.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, room.gameObjects[i].spriteGLIndex);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
GLES20.glUniform1i(shader.mTextureUniformHandle, 0);
//Set up the model matrix
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, 4.0f, 0.0f, -7.0f);
Matrix.rotateM(mModelMatrix, 0, room.gameObjects[i].rotation, 1.0f, 0.0f, 0.0f);
//Draw the object
room.gameObjects[i].draw(mModelMatrix, mViewMatrix, mProjectionMatrix, mMVPMatrix, shader);
}
//********************************************
// Draw a point to indicate the light.********
drawLight();
//********************************************
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
//Initialize Projection Matrix****************
// Set the OpenGL viewport to the same size as the surface.
GLES20.glViewport(0, 0, width, height);
// Create a new perspective projection matrix. The height will stay the same
// while the width will vary as per aspect ratio.
final float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0f;
final float top = 1.0f;
final float near = 1.0f;
final float far = 10.0f;
Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
//********************************************
}
// Draws a point representing the position of the light.
private void drawLight()
{
GLES20.glUseProgram(pointShader.mProgram);
final int pointMVPMatrixHandle = GLES20.glGetUniformLocation(pointShader.mProgram, "u_MVPMatrix");
final int pointPositionHandle = GLES20.glGetAttribLocation(pointShader.mProgram, "a_Position");
// Pass in the position.
GLES20.glVertexAttrib3f(pointPositionHandle, shader.mLightPosInModelSpace[0], shader.mLightPosInModelSpace[1], shader.mLightPosInModelSpace[2]);
// Since we are not using a buffer object, disable vertex arrays for this attribute.
GLES20.glDisableVertexAttribArray(pointPositionHandle);
// Pass in the transformation matrix.
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mLightModelMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(pointMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// Draw the point.
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 1);
}
}
Shader
package mycompany.OpenGLES20_2DEngine;
import android.opengl.GLES20;
import android.util.Log;
public class Shader {
/** Used for debug logs. */
private static final String TAG = "Shader";
//Shaders*************************************
public int vertexShader;
public int fragmentShader;
//********************************************
//Handles*************************************
/** This will be used to pass in model position information. */
public int mPositionHandle;
/** This will be used to pass in model color information. */
public int mColorHandle;
/** This will be used to pass in model normal information. */
public int mNormalHandle;
/** This will be used to pass in model texture coordinate information. */
public int mTextureCoordinateHandle;
/** This will be used to pass in the transformation matrix. */
public int mMVPMatrixHandle;
/** This will be used to pass in the modelview matrix. */
public int mMVMatrixHandle;
/** This will be used to pass in the light position. */
public int mLightPosHandle;
/** This will be used to pass in the texture. */
public int mTextureUniformHandle;
/** Used to hold a light centered on the origin in model space. We need a 4th coordinate so we can get translations to work when
* we multiply this by our transformation matrices. */
public final float[] mLightPosInModelSpace = new float[] {0.0f, 0.0f, 0.0f, 1.0f};
/** Used to hold the current position of the light in world space (after transformation via model matrix). */
public final float[] mLightPosInWorldSpace = new float[4];
/** Used to hold the transformed position of the light in eye space (after transformation via modelview matrix) */
public final float[] mLightPosInEyeSpace = new float[4];
//********************************************
//GL Code For Shaders*************************
public final String vertexShaderCode =
// A constant representing the combined model/view/projection matrix.
"uniform mat4 u_MVPMatrix;" + "\n" +
// A constant representing the combined model/view matrix.
"uniform mat4 u_MVMatrix;" + "\n" +
// Per-vertex position information we will pass in.
"attribute vec4 a_Position;" + "\n" +
// Per-vertex normal information we will pass in.
"attribute vec3 a_Normal;" + "\n" +
// Per-vertex texture coordinate information we will pass in.
"attribute vec2 a_TexCoordinate;" + "\n" +
// This will be passed into the fragment shader.
"varying vec3 v_Position;" + "\n" +
// This will be passed into the fragment shader.
"varying vec3 v_Normal;" + "\n" +
// This will be passed into the fragment shader.
"varying vec2 v_TexCoordinate;" + "\n" +
// The entry point for our vertex shader.
"void main()" + "\n" +
"{" + "\n" +
// Transform the vertex into eye space.
"v_Position = vec3(u_MVMatrix * a_Position);" + "\n" +
// Pass through the texture coordinate.
"v_TexCoordinate = a_TexCoordinate;" + "\n" +
// Transform the normal's orientation into eye space.
"v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));" + "\n" +
// gl_Position is a special variable used to store the final position.
// Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
"gl_Position = u_MVPMatrix * a_Position;" + "\n" +
"}";
public final String fragmentShaderCode =
"precision mediump float;" + "\n" + // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
"uniform vec3 u_LightPos;" + "\n" + // The position of the light in eye space.
"uniform sampler2D u_Texture;" + "\n" + // The input texture.
"varying vec3 v_Position;" + "\n" + // Interpolated position for this fragment.
"varying vec3 v_Normal;" + "\n" + // Interpolated normal for this fragment.
"varying vec2 v_TexCoordinate;" + "\n" + // Interpolated texture coordinate per fragment.
// The entry point for our fragment shader.
"void main()" + "\n" +
"{" + "\n" +
// Will be used for attenuation.
"float distance = length(u_LightPos - v_Position);" + "\n" +
// Get a lighting direction vector from the light to the vertex.
"vec3 lightVector = normalize(u_LightPos - v_Position);" + "\n" +
// Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
// pointing in the same direction then it will get max illumination.
"float diffuse = max(dot(v_Normal, lightVector), 0.0);" + "\n" +
// Add attenuation.
"diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance)));" + "\n" +
// Add ambient lighting
"diffuse = diffuse + 0.7;" + "\n" +
// Multiply the color by the diffuse illumination level and texture value to get final output color.
"gl_FragColor = (diffuse * texture2D(u_Texture, v_TexCoordinate));" + "\n" +
"}";
//********************************************
//GL Program Handle***************************
public int mProgram;
//********************************************
public Shader() {
//Load Shaders********************************
vertexShader = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
fragmentShader = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//********************************************
//Create GL Program***************************
mProgram = createAndLinkProgram(vertexShader, fragmentShader, new String[] {"a_Position", "a_Color", "a_Normal", "a_TexCoordinate"});
//********************************************
}
/**
* Helper function to compile a shader.
*
* #param shaderType The shader type.
* #param shaderSource The shader source code.
* #return An OpenGL handle to the shader.
*/
public static int compileShader(final int shaderType, final String shaderSource)
{
int shaderHandle = GLES20.glCreateShader(shaderType);
if (shaderHandle != 0)
{
// Pass in the shader source.
GLES20.glShaderSource(shaderHandle, shaderSource);
// Compile the shader.
GLES20.glCompileShader(shaderHandle);
// Get the compilation status.
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
// If the compilation failed, delete the shader.
if (compileStatus[0] == 0)
{
Log.e(TAG, "Error compiling shader " /*+ GLES20.glGetShaderInfoLog(shaderHandle)*/);
GLES20.glDeleteShader(shaderHandle);
shaderHandle = 0;
}
}
if (shaderHandle == 0)
{
throw new RuntimeException("Error creating shader.");
}
return shaderHandle;
}
/**
* Helper function to compile and link a program.
*
* #param vertexShaderHandle An OpenGL handle to an already-compiled vertex shader.
* #param fragmentShaderHandle An OpenGL handle to an already-compiled fragment shader.
* #param attributes Attributes that need to be bound to the program.
* #return An OpenGL handle to the program.
*/
public static int createAndLinkProgram(final int vertexShaderHandle, final int fragmentShaderHandle, final String[] attributes)
{
int programHandle = GLES20.glCreateProgram();
if (programHandle != 0)
{
// Bind the vertex shader to the program.
GLES20.glAttachShader(programHandle, vertexShaderHandle);
// Bind the fragment shader to the program.
GLES20.glAttachShader(programHandle, fragmentShaderHandle);
// Bind attributes
if (attributes != null)
{
final int size = attributes.length;
for (int i = 0; i < size; i++)
{
GLES20.glBindAttribLocation(programHandle, i, attributes[i]);
}
}
// Link the two shaders together into a program.
GLES20.glLinkProgram(programHandle);
// Get the link status.
final int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);
// If the link failed, delete the program.
if (linkStatus[0] == 0)
{
Log.e(TAG, "Error compiling program " /*+ GLES20.glGetProgramInfoLog(programHandle)*/);
GLES20.glDeleteProgram(programHandle);
programHandle = 0;
}
}
if (programHandle == 0)
{
throw new RuntimeException("Error creating program.");
}
return programHandle;
}
}
GameObject
package mycompany.OpenGLES20_2DEngine;
import java.io.IOException;
import java.io.InputStream;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;
public class GameObject {
/** Used for debug logs. */
private static final String TAG = "GameObject";
//Declare Variables****************************
//Position
public int x;
public int y;
public int z;
//Size
public int width;
public int height;
//Movement
double thrustX;
double thrustY;
//Rotation
public int rotation;
public int rotationSpeed;
//Unique Identifier
public int UID;
//Sprite Resource ID
int spriteResId;
//GL Texture Reference
int spriteGLIndex;
//Bounding Box
BoundingBox boundingBox;
//********************************************
GameObject() {
}
public int loadSprite(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
InputStream is = context.getResources()
.openRawResource(resourceId);
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch(IOException e) {
Log.e(TAG, "Could not load the texture");
}
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
//TODO: Offending Line - Makes textures black because of parameters
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);
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);
// 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 void setUID(int uid) {
UID = uid;
}
public int getUID() {
return UID;
}
public void draw(float[] mModelMatrix, float[] mViewMatrix, float[] mProjectionMatrix, float[] mMVPMatrix, Shader shader) {
{
// Pass in the position information
boundingBox.mPositions.position(0);
GLES20.glVertexAttribPointer(shader.mPositionHandle, boundingBox.mPositionDataSize, GLES20.GL_FLOAT, false,
0, boundingBox.mPositions);
GLES20.glEnableVertexAttribArray(shader.mPositionHandle);
// Pass in the color information
boundingBox.mColors.position(0);
GLES20.glVertexAttribPointer(shader.mColorHandle, boundingBox.mColorDataSize, GLES20.GL_FLOAT, false,
0, boundingBox.mColors);
GLES20.glEnableVertexAttribArray(shader.mColorHandle);
// Pass in the normal information
boundingBox.mNormals.position(0);
GLES20.glVertexAttribPointer(shader.mNormalHandle, boundingBox.mNormalDataSize, GLES20.GL_FLOAT, false,
0, boundingBox.mNormals);
GLES20.glEnableVertexAttribArray(shader.mNormalHandle);
// Pass in the texture coordinate information
boundingBox.mTextureCoordinates.position(0);
GLES20.glVertexAttribPointer(shader.mTextureCoordinateHandle, boundingBox.mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
0, boundingBox.mTextureCoordinates);
GLES20.glEnableVertexAttribArray(shader.mTextureCoordinateHandle);
// This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
// (which currently contains model * view).
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
// Pass in the modelview matrix.
GLES20.glUniformMatrix4fv(shader.mMVMatrixHandle, 1, false, mMVPMatrix, 0);
// This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
// (which now contains model * view * projection).
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
// Pass in the combined matrix.
GLES20.glUniformMatrix4fv(shader.mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// Pass in the light position in eye space.
GLES20.glUniform3f(shader.mLightPosHandle, shader.mLightPosInEyeSpace[0], shader.mLightPosInEyeSpace[1], shader.mLightPosInEyeSpace[2]);
// Draw the object
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
}
}
}
BoundingBox
package mycompany.OpenGLES20_2DEngine;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
//TODO: make this dynamic, both the constructor and the coordinates.
class BoundingBox {
//Variable Declarations***********************
/** How many bytes per float. */
private final int mBytesPerFloat = 4;
/** Store our model data in a float buffer. */
public final FloatBuffer mPositions;
public final FloatBuffer mColors;
public final FloatBuffer mNormals;
public final FloatBuffer mTextureCoordinates;
//Number of coordinates per vertex in this array
final int COORDS_PER_VERTEX = 3;
//Coordinates
float[] positionData;
//Texture Coordinates
float[] textureCoordinateData;
//Vertex Color
float[] colorData;
float[] normalData;
//Vertex Stride
final int vertexStride = COORDS_PER_VERTEX * 4;
/** Size of the position data in elements. */
public final int mPositionDataSize = 3;
/** Size of the color data in elements. */
public final int mColorDataSize = 4;
/** Size of the normal data in elements. */
public final int mNormalDataSize = 3;
/** Size of the texture coordinate data in elements. */
public final int mTextureCoordinateDataSize = 2;
//********************************************
public BoundingBox(float[] coords) {
//TODO: Normalize values
//Set Coordinates and Texture Coordinates*****
if(coords==null) {
float[] newPositionData = {
// Front face
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f
};
positionData = newPositionData;
float[] newColorData = {
// Front face (red)
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f
};
colorData = newColorData;
float[] newTextureCoordinateData =
{
// Front face
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
textureCoordinateData = newTextureCoordinateData;
float[] newNormalData = {
// Front face
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f
};
normalData = newNormalData;
}
else {
positionData = coords;
//TODO:Reverse coords HERE
textureCoordinateData = coords;
}
//********************************************
//Initialize Buffers**************************
mPositions = ByteBuffer.allocateDirect(positionData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mPositions.put(positionData).position(0);
mColors = ByteBuffer.allocateDirect(colorData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColors.put(colorData).position(0);
mNormals = ByteBuffer.allocateDirect(normalData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mNormals.put(normalData).position(0);
mTextureCoordinates = ByteBuffer.allocateDirect(textureCoordinateData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTextureCoordinates.put(textureCoordinateData).position(0);
//********************************************
}
}
SpaceShip
package mycompany.OpenGLES20_2DEngine;
public class SpaceShip extends GameObject{
public SpaceShip() {
spriteResId = R.drawable.spaceship;
boundingBox = new BoundingBox(null);
}
}
Got it. I added the spaceship to the room AFTER I loaded it's bitmap (from the room).
//Load The Level******************************
//Create a new room
room = new Room(800,600, 0);
//Load game objects
SpaceShip user = new SpaceShip();
**//Load sprites
for(int i=0;i<room.numberOfGameObjects;i++) {
room.gameObjects[i].spriteGLIndex = room.gameObjects[i].loadSprite(context, room.gameObjects[i].spriteResId);
}
//Add them to the room
room.addGameObject(user);**
//********************************************
I have a packed vertex buffer containing postion coordinates aswell as color values for a vertex in the format {X, Y, Z, R, G, B, A}.
I am able to display the rectangle properly with a hardcoded color when I alter the fragment shader by taking out the a_Color attribute and hard coding a vec4 value for gl_FragColor but I am not able to pass the color vec4 attribute into the fragment shader (the rectangle won't display in that scenario).
What is the correct way to use glVertexAttribPointer(...) and glDrawElements(...) to draw from a packed vertex buffer in OpenGL ES 2.0?
See my code below:
public class GameRenderer implements Renderer {
public static final int POS_SZ = 3;
public static final int COL_SZ = 4;
public static final int FLOAT_SZ = 4;
public static final int SHORT_SZ = 2;
private FloatBuffer gridVB;
private ShortBuffer gridIndices;
int programCode, paPositionHandle, paColorHandle, puMVPMatrixHandle;
private final String vertexShaderCode =
"uniform mat4 u_MVPMatrix; \n" +
"attribute vec4 a_Position; \n" +
"void main(){ \n" +
" gl_Position = u_MVPMatrix * a_Position; \n" +
"} \n";
private String fragmentShaderCode =
"precision mediump float; \n" +
"attribute vec4 a_Color; \n" +
"void main(){ \n" +
" gl_FragColor = a_Color; \n" +
"} \n";
public void staticGrid() {
float vertexArray[] = {
-0.75f, 0.75f, 0.0f, // position
0.0f, 0.0f, 1.0f, 1.0f, // colour
0.75f, 0.75f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f,
0.75f, -0.75f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f,
-0.75f, -0.75f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
short indicesArray[] = {
0, 1, 2, 0, 2, 3
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertexArray.length * FLOAT_SZ);
vbb.order(ByteOrder.nativeOrder());
gridVB = vbb.asFloatBuffer();
gridVB.put(vertexArray);
gridVB.position(0);
ByteBuffer ibb = ByteBuffer.allocateDirect(indicesArray.length * SHORT_SZ);
ibb.order(ByteOrder.nativeOrder());
gridIndices = ibb.asShortBuffer();
gridIndices.put(indicesArray);
gridIndices.position(0);
}
public void onSurfaceCreated(GL10 unused, EGLConfig arg1) {
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
staticGrid();
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
programCode = GLES20.glCreateProgram();
GLES20.glAttachShader(programCode, vertexShader);
GLES20.glAttachShader(programCode, fragmentShader);
GLES20.glLinkProgram(programCode);
paPositionHandle = GLES20.glGetAttribLocation(programCode, "a_Position");
paColorHandle = GLES20.glGetAttribLocation(programCode, "a_Color");
puMVPMatrixHandle = GLES20.glGetUniformLocation(programCode, "u_MVPMatrix");
}
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(programCode);
int stride = (POS_SZ + COL_SZ) * FLOAT_SZ;
int indices_cnt = 6;
gridVB.position(0);
GLES20.glEnableVertexAttribArray(paPositionHandle);
GLES20.glVertexAttribPointer(paPositionHandle, POS_SZ, GLES20.GL_FLOAT, false, stride, gridVB);
gridVB.position(POS_SZ);
GLES20.glEnableVertexAttribArray(paColorHandle);
GLES20.glVertexAttribPointer(paColorHandle, COL_SZ, GLES20.GL_FLOAT, false, stride, gridVB);
// matrix manipulation ...
GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices_cnt, GLES20.GL_UNSIGNED_SHORT, gridIndices);
}
}
Fragment shaders don't use attributes. Attributes are per-vertex values, which a fragment shader wouldn't know anything about. What you want to do is to take the color as an attribute to the vertex shader, and then use a varying to pass the color to the fragment shader.
Also please start using error checking for your shaders, it will tell you when you make mistakes like this:
http://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetProgramiv.xml (check for GL_LINK_STATUS)
http://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetShaderiv.xml (check for GL_COMPILE_STATUS)
http://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetProgramInfoLog.xml
http://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetShaderInfoLog.xml