Reputation: 3887
Please see Edit at end for progress.
I'm in the process of trying to learn OpenGL ES 2.0 (I'm going to be developing on Android devices)
I'm a little confused about the Vertex and Fragment shaders. I understand their purpose, but if I'm building a shape from a custom built class (say a 'point') and setting it's size and colour or applying a texture and assuming that both shaders are declared and defined initially in the object class's constructor, would this mean that each instance of that class would have it's very own pair of shaders?
That is my first question. My second is that if this is the case (shader pairs for each object).........is this the way to go? I've heard that having one shader pair and switching it's parameters isn't a good idea because of performance, but if I have 100 sprites all of the same size and colour (or texture) does it make sense for them all to have a different pair of shaders with exactly the same parameters?
I hope I'm asking the correct question, I've not been studying ES 2.0 for long so find it a little confusing. I currently only have a limited understanding of OpenGL!
Edit
Adding code as requested.
public class Dot {
int iProgId;
int iPosition;
float size = 10;
FloatBuffer vertexBuf;
float r = 1f;
float g = 1f;
float b = 1f;
float a = 1f;
int iBaseMap;
int texID;
Bitmap imgTexture;
//Constructor
public Dot() {
float[] vertices = {
0,0,0f
};
//Create vertex shader
String strVShader =
"attribute vec4 a_position;\n"+
"void main()\n" +
"{\n" +
"gl_PointSize = " +size+ ";\n" +
"gl_Position = a_position;\n"+
"}";
//Create fragment shader
String strFShader =
"precision mediump float;" +
"void main() " +
"{" +
"gl_FragColor = vec4(0,0,0,1);" +
"}";
iProgId = Utils.LoadProgram(strVShader, strFShader);
iPosition = GLES20.glGetAttribLocation(iProgId, "a_position");
vertexBuf = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuf.put(vertices).position(0);
}
My setTexture method
public void setTexture(GLSurfaceView view, Bitmap imgTexture){
this.imgTexture=imgTexture;
//Create vertex shader
String strVShader =
"attribute vec4 a_position;\n"+
"void main()\n" +
"{\n" +
"gl_PointSize = " +size+ ";\n" +
"gl_Position = a_position;\n"+
"}";
//Fragment shader
String strFShader =
"precision mediump float;" +
"uniform sampler2D u_baseMap;" +
"void main()" +
"{" +
"vec4 color;" +
"color = texture2D(u_baseMap, gl_PointCoord);" +
"gl_FragColor = color;" +
"}";
iProgId = Utils.LoadProgram(strVShader, strFShader);
iBaseMap = GLES20.glGetUniformLocation(iProgId, "u_baseMap");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(iBaseMap, 0);
texID = Utils.LoadTexture(view, imgTexture); //See code below
}
LoadTexture() method from my Utils
class:
public static int LoadTexture(GLSurfaceView view, Bitmap imgTex) {
int textures[] = new int[1];
try {
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, imgTex, 0);
} catch
}
return textures[0];
}
And finally my Drawing method:
public void drawDot(float x, float y){
float[] vertices = {
x,y,0f
};
vertexBuf = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuf.put(vertices).position(0);
GLES20.glUseProgram(iProgId);
GLES20.glVertexAttribPointer(iPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuf);
GLES20.glEnableVertexAttribArray(iPosition);
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 1);
}
So I can create stuff like so:
Dot dot1 = new Dot();
dot1.setSize(40);
setTexture(myBitmap); //(created earlier with BitmapFactory)
drawDot(0,0);
Thank you!
Edit 1: Thanks for the answer so far. On further research, it seem a few other people have had this exact same problem. The issue seems to be that I'm not calling glBindTexture in my rendering routine, thus OpenGL is just using the last texture that it loaded, which I guess makes sense.
If I put the following into my Rendering routine:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 1);
it will apply the first bitmap and display it
if I put:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 2);
it will apply the second bitmap and display it
So, getting somewhere! But my question would now be how can I get my rendering method to automatically know which bitmap to use based on which object is calling it (the rendering routine)?
Thanks again
Upvotes: 3
Views: 1077
Reputation: 3887
I will answer myself as I found out what the problem was.
Added this to my drawDot method:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texID);
texID being the texture ID that corresponds to the object calling the drawDot() method.
Works perfectly
Hope this helps anyone who may have a similar problem in the future.
Upvotes: 2
Reputation: 1555
How it works (briefly)
Shaders are just programs that run in the graphics card. You compile and link them, and then you can pass some variables to them to modify properties of vertices and fragments. This means that when you call certain drawing functions, such as glDrawElements or glDrawArrays, the vertex data (this means position, texture coords, normals, color, etc. depending on what you want to send) will be sent to the pipeline. This means that the currently loaded vertex shader will get the vertices one by one and run its code to apply whatever transformations it needs. After that OpenGL will apply rasterization to generate the fragments for the current frame. Then the fragment shader will take every fragment and modify it accordingly.
You can always unload a shader and load a different one. If you need different shaders for different objects, you can group your objects according to their shader and render them independently while reloading the corresponding shader for every group.
However, sometimes it's easier to pass some parameters to the shader and change them for every object. For instance, if you want to render a 3D model, you can split it in submeshes, with every submesh having a different texture. Then, when you pass the vertex data for a mesh, you load the texture and pass it to the shader. For the next mesh you will pass another texture, and so on.
In the real world everything is more complex, but I hope it's useful for you to get an idea of how it works.
Your example
You are loading a pair of shaders on the constructor (with no texture), and then creating a new shader every time you set a texture. I'm not sure this is the better approach.
Without knowing what Utils.LoadShader does is difficult to know, but you could log the result every time you call it. Maybe the second time you are linking the shader it doesn't work.
If I were you, I would just use a pair of shaders outside your dot object. You can pass parameters to the shader (with glUniform...), indicating the dot size, texture, etc. The setTexture function would just bind the new texture without loading the shaders. Compile then at the beggining (after setting the GL context and so on).
When this works you may consider to change your shaders every time, only if it is really necessary.
Upvotes: 3