Working with multiple shader programs - what am I doing wrong?

Started by DSG, August 23, 2013, 21:36:48

Previous topic - Next topic

DSG

Hi guys,

I've ran into a problem trying to use two different shader programs to render two different meshes. The trouble I'm having is that when I use two programs with two different vertex shaders - each utilising a different number of input attributes - only one of the meshes is drawn correctly whilst the other comes out 'deformed' (or not at all).  I'm new to GLSL (but not to OpenGL or shaders or rendering in general) so I suspect that I'm missing something obvious, but after too many hours experimenting and searching for an explanation, I'm still no closer to understanding the behaviour that I'm seeing. Please help!

I've put together a little example that reproduces my problem. The following program renders two indexed meshes; a triangle and a square. There are two very simple shader programs that just render the triangle primitives without transforming the vertices. I use two different vertex shaders that do the same thing - the only difference between them is that I force the second shader to use two input attributes, even though I never actually bind the second attribute to any data, so it always supplies the default value:

import java.nio.*;
import org.lwjgl.*;
import org.lwjgl.opengl.*;

public class Main {

    static final float[] TRIANGLE_VERTS = { 
        -0.15f, 0.0f, 0.0f, 1.0f, 
        -0.35f, 0.5f, 0.0f, 1.0f, 
        -0.55f, 0.0f, 0.0f, 1.0f 
    };

    static final byte[] TRIANGLE_INDICES = { 
        0, 1, 2 
    };

    static final float[] QUAD_VERTS = { 
        0.15f, 0.0f, 0.0f, 1.0f, 
        0.55f, 0.0f, 0.0f, 1.0f, 
        0.55f, 0.5f, 0.0f, 1.0f,
        0.15f, 0.5f, 0.0f, 1.0f, 
    };

    static final byte[] QUAD_INDICES = { 
        1, 2, 3, 
        3, 0, 1 
     };

    /*
     * 2. After following the instructions below, un-comment the extra lines in 
     * this shader and re-run to see the quad once again render correctly
     */
    static final String VERTEX_SHADER1 = 
            "layout(location = 0) in vec4 position;" +
//            "layout(location = 2) in vec3 nothing;" +
            "void main() {" + "  gl_Position = position;" +
//            "  gl_Position = position + vec4(nothing, 0);" +
            "}";

    static final String VERTEX_SHADER2 = 
            "layout(location = 0) in vec4 position;" +
            "layout(location = 1) in vec3 nothing;" + 
            "void main() {" +
            "  gl_Position = position + vec4(nothing, 0);" + 
            "}";

    static final String FRAGMENT_SHADER = 
            "void main() {" + 
            "  gl_FragColor = vec4(1, 1, 1, 1);" + 
            "}";

    public static void main(String[] args) {
        if (!maybeSetDisplayMode()) {
            return;
        }

        int program1 = createProgram(VERTEX_SHADER1, FRAGMENT_SHADER);
        int program2 = createProgram(VERTEX_SHADER2, FRAGMENT_SHADER);

        int triangleVao = createVao(TRIANGLE_VERTS, TRIANGLE_INDICES);
        int quadVao = createVao(QUAD_VERTS, QUAD_INDICES);

        while (!Display.isCloseRequested()) {
            GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

            GL20.glUseProgram(program1);
            GL30.glBindVertexArray(triangleVao);
            GL11.glDrawElements(GL11.GL_TRIANGLES, TRIANGLE_INDICES.length, GL11.GL_UNSIGNED_BYTE, 0);

            /*
             * 1. Un-comment the line below and re-run to see the quad "disappear"
             */
//            GL20.glUseProgram(program2);
            GL30.glBindVertexArray(quadVao);
            GL11.glDrawElements(GL11.GL_TRIANGLES, QUAD_INDICES.length, GL11.GL_UNSIGNED_BYTE, 0);

            Display.update();
            Display.sync(30);
        }

        Display.destroy();
    }

    static boolean maybeSetDisplayMode() {
        try {
            Display.setDisplayMode(new DisplayMode(640, 480));
            Display.create();

            return true;
        } catch (LWJGLException e) {
            return false;
        }
    }

    static int createProgram(String vertexShader, String fragmentShader) {
        int vs = loadShader(GL20.GL_VERTEX_SHADER, vertexShader);
        int fs = loadShader(GL20.GL_FRAGMENT_SHADER, fragmentShader);
        int program = GL20.glCreateProgram();

        GL20.glAttachShader(program, vs);
        GL20.glAttachShader(program, fs);
        GL20.glLinkProgram(program);

        GL20.glDetachShader(program, vs);
        GL20.glDetachShader(program, fs);
        GL20.glDeleteShader(vs);
        GL20.glDeleteShader(fs);

        return program;
    }

    static int loadShader(int type, String shaderCode) {
        int shader = GL20.glCreateShader(type);

        GL20.glShaderSource(shader, shaderCode);
        GL20.glCompileShader(shader);

        return shader;
    }

    static int createVao(float[] verts, byte[] indices) {
        int vaoId = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vaoId);

        configureVerts(verts);
        configureIndices(indices);

        return vaoId;
    }

    static void configureVerts(float[] verts) {
        FloatBuffer vertsBuffer = BufferUtils.createFloatBuffer(verts.length);
        vertsBuffer.put(verts).flip();

        int vertsVbo = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertsVbo);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertsBuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(0, 4, GL11.GL_FLOAT, false, 0, 0);
        GL20.glEnableVertexAttribArray(0);
    }

    static void configureIndices(byte[] indices) {
        ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indices.length);
        indicesBuffer.put(indices).flip();

        int indicesVbo = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesVbo);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
    }
}


Now, if you run the program as is, it will render both the triangle and the square using only the first shader program with no issues. So far so good.

Next, uncomment the glUseProgram() call in the main loop so that the square should be rendered with the second program. If I re-run the program at this point then only the triangle draws correctly! The only real difference between the vertex shaders at this point is that shader 1 has one active attribute where as shader 2 has two.

Finally, uncomment the two lines in the first shader so that it too will now have two active attributes. If I re-run the program at this point then both triangle and square are drawn.

I don't understand what's causing the square not to render properly after initially introducing the second shader - can anyone please explain? Any help would be greatly appreciated.

Cheers!