Help me make my game engine.

Started by sasha-san, November 04, 2013, 20:05:21

Previous topic - Next topic

sasha-san

@quew That would be awesome. The shader is compiled and active. The problem must be so simple, I can't even see it. But it's there, I can smell it.

quew8

So what is the actual error OpenGL is giving?

The Vertex Shader:
#version 120

#extension GL_EXT_gpu_shader4 : enable

uniform mat4 projectionViewingMatrix;
uniform mat4 modelMatrix;
uniform mat4 bindShapeMatrix;
uniform mat4[6] invBindMatrices;
uniform mat4[6] jointMatrices;

attribute vec3 position;
attribute vec3 normal;
attribute vec2 texCoords;
attribute ivec4 jointIndices;
attribute vec4 jointWeights;

varying vec2 fInTexCoords;
varying vec3 fInNormal;

void main(void) {
    mat4 mvp = projectionViewingMatrix * modelMatrix;
    vec4 vbsm = vec4((vec4(position.xyz, 1) * bindShapeMatrix).xyz, 1);
    vec3 animatedPosition = vec3(
        (
            ( ( vbsm * invBindMatrices[int(jointWeights[0])] * jointMatrices[int(jointWeights[0])] ).xyz * jointWeights[0] ) +
            ( ( vbsm * invBindMatrices[int(jointWeights[1])] * jointMatrices[int(jointWeights[1])] ).xyz * jointWeights[1] ) +
            ( ( vbsm * invBindMatrices[int(jointWeights[2])] * jointMatrices[int(jointWeights[2])] ).xyz * jointWeights[2] ) +
            ( ( vbsm * invBindMatrices[int(jointWeights[3])] * jointMatrices[int(jointWeights[3])] ).xyz * jointWeights[3] )
        ).xyz
    );
    vec3 nbsm = normal * mat3(bindShapeMatrix);
    vec3 animatedNormal = vec3(
        ( nbsm * mat3(invBindMatrices[int(jointWeights[0])]) * mat3(jointMatrices[int(jointWeights[0])]) * jointWeights[0] ) +
        ( nbsm * mat3(invBindMatrices[int(jointWeights[1])]) * mat3(jointMatrices[int(jointWeights[1])]) * jointWeights[1] ) +
        ( nbsm * mat3(invBindMatrices[int(jointWeights[2])]) * mat3(jointMatrices[int(jointWeights[2])]) * jointWeights[2] ) +
        ( nbsm * mat3(invBindMatrices[int(jointWeights[3])]) * mat3(jointMatrices[int(jointWeights[3])]) * jointWeights[3] )
    );
    fInTexCoords = texCoords;
    fInNormal = mat3(modelMatrix) * animatedNormal;
    gl_Position = mvp * vec4(animatedPosition.xyz, 1);
}


On the extension I use here, I have no idea why. On this computer, I get a warning saying it isn't supported in the current profile so it shouldn't be doing anything at all. However if it isn't there, I get an error saying that "ivec4 isn't supported by OpenGL," which is not true since it was added in GLSL 1.1. I've even looked at what the extension does and there is nothing about ivec4. So...

The Skeletal Render Mode (uses the interface system I posted earlier):
/**
 *
 * @author Quew8
 */
public class FSkeletalRenderMode extends DynamicRenderMode<Skeleton> {
    private final ShaderProgram shaderProgram;
    private final String bindShapeMatrixVar, invBindShapeMatrix, jointMatrix;
    
    public FSkeletalRenderMode(ShaderProgram shaderProgram, String bindShapeMatrixVar, String invBindShapeMatrix, String jointMatrix) {
        this.shaderProgram = shaderProgram;
        this.bindShapeMatrixVar = bindShapeMatrixVar;
        this.invBindShapeMatrix = invBindShapeMatrix;
        this.jointMatrix = jointMatrix;
    }
    
    @Override
    public void onPreRendering() {
        shaderProgram.use();
        ShaderUtils.setVertexAttribPointer(0, 3, GL_FLOAT, false, 52, 0);
        ShaderUtils.setVertexAttribPointer(1, 3, GL_FLOAT, false, 52, 12);
        ShaderUtils.setVertexAttribPointer(2, 2, GL_FLOAT, false, 52, 24);
        ShaderUtils.setVertexAttribPointer(3, 4, GL_BYTE, false, 52, 36);
        ShaderUtils.setVertexAttribPointer(4, 4, GL_FLOAT, false, 52, 50);
    }
    
    @Override
    public void updateModelMatrix(FloatBuffer matrix) {
        ShaderUtils.setUniformMatrix(shaderProgram.getId(), "modelMatrix", matrix);
    }
    
    @Override
    public void updateProjectionMatrix(FloatBuffer matrix) {
        ShaderUtils.setUniformMatrix(shaderProgram.getId(), "projectionViewingMatrix", matrix);
    }
    
    @Override
    public void onPreDraw(Skeleton data) {
        data.uploadSkeleton(shaderProgram.getId(), bindShapeMatrixVar, invBindShapeMatrix, jointMatrix);
    }
}


EDIT: Forgot these: Skeleton and Joint

/**
 *
 * @author Quew8
 */
public class Skeleton {
    private final FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
    private final Matrix bindShapeMatrix;
    private final Joint[] joints;
    private final int nJoints;
    
    public Skeleton(Matrix bindShapeMatrix, Joint[] joints, int nJoints) {
        this.bindShapeMatrix = bindShapeMatrix;
        this.joints = joints;
        this.nJoints = nJoints;
    }
    
    public int getNJoints() {
        return nJoints;
    }
    
    public void uploadSkeleton(int programId, String bsmVar, String ibmVar, String jmVar) {
        bindShapeMatrix.putIn(matrixBuffer);
        ShaderUtils.setUniformMatrix(programId, bsmVar, matrixBuffer);
        for(int i = 0; i < joints.length; i++) {
            joints[i].uploadJoint(programId, ibmVar, jmVar, matrixBuffer);
        }
    }
}

/**
 *
 * @author Quew8
 */
public class Joint {
    private final int index;
    private final String indexString;
    private final Matrix invBindMatrix;
    private final Matrix jointMatrix;
    private final Matrix tempMatrix = new Matrix();
    private final Joint[] children;
    
    public Joint(int index, Matrix invBindMatrix, Matrix jointMatrix, Joint[] children) {
        this.index = index;
        this.indexString = "[" + Integer.toString(index) + "]";
        this.invBindMatrix = invBindMatrix;
        this.jointMatrix = jointMatrix;
        this.children = children;
    }
    
    public void uploadJoint(int programId, String ibmVar, String jmVar, Matrix parentWJM, FloatBuffer matrixBuffer) {
        Matrix.times(tempMatrix, parentWJM, jointMatrix);
        tempMatrix.putIn(matrixBuffer);
        ShaderUtils.setUniformMatrix(programId, jmVar + indexString, matrixBuffer);
        invBindMatrix.putIn(matrixBuffer);
        ShaderUtils.setUniformMatrix(programId, ibmVar + indexString, matrixBuffer);
        for(int i = 0; i < children.length; i++) {
            children[i].uploadJoint(programId, ibmVar, jmVar, tempMatrix, matrixBuffer);
        }
    }
    
    public void uploadJoint(int programId, String ibmVar, String jmVar, FloatBuffer matrixBuffer) {
        uploadJoint(programId, ibmVar, jmVar, new Matrix(), matrixBuffer);
    }
}

The Skin GeometricDataInterpreter (I've actually changed that interface slightly since you last saw it):
/**
 *
 * @author Quew8
 */
public class SkinInterpreter extends GeometricObjectInterpreter<Skin, WeightedVertex> {
    public static SkinInterpreter INSTANCE = new SkinInterpreter();
    
    private SkinInterpreter() {
        
    }
    
    @Override
    public ByteBuffer toVertexData(Skin[] skins) {
        int length = 0;
        WeightedVertex[][] vertices = new WeightedVertex[skins.length][];
        for(int i = 0; i < vertices.length; i++) {
            vertices[i] = skins[i].getVertices();
            length += skins[i].getVerticesLength();
        }
        length *= WeightedVertex.WEIGHTED_VERTEX_BYTE_SIZE;
        ByteBuffer bb = BufferUtils.createByteBuffer(length);
        for(int i = 0; i < vertices.length; i++) {
            for(int j = 0; j < vertices[i].length; j++) {
                vertices[i][j].appendData(bb);
            }
        }
        bb.flip();
        return bb;
    }
    
}


And WeightedVertex (or at least the relevant bits) to show how the data is arranged in the VBO.
/**
 *
 * @author Quew8
 */
public class WeightedVertex extends AbstractVertex<WeightedVertex> {
    public static final int WEIGHTED_VERTEX_BYTE_SIZE = 52;
    
    public WeightedVertex(float[] data, float[] weights) {
        super(ArrayUtils.concatArrays(new float[][]{data, weights}, VERTEX_SIZE + weights.length));
    }
    
    public WeightedVertex(Vector pos, Vector normal, float tx, float ty, float[] weights) {
        this(new float[]{
            pos.getX(), pos.getY(), pos.getZ(), 
            normal.getX(), normal.getY(), normal.getZ(),
            tx, ty
        }, weights);
    }
    
    public int getNWeights() {
        return data.length - VERTEX_SIZE;
    }
    
    public float getWeight(int i) {
        return data[VERTEX_SIZE + i];
    }
    
    public void setWeight(int i, float weight) {
        data[VERTEX_SIZE + i] = weight;
    }
    
    @Override
    public void appendData(ByteBuffer bb) {
        for(int i = 0; i < VERTEX_SIZE; i++) {
            bb.putFloat(data[i]);
        }
        int nWeights = getNWeights();
        int nUsedWeights = 0;
        int[] jointIndices = new int[4];
        float[] usedWeights = new float[4];
        for(int i = 0; i < nWeights && nUsedWeights < 4; i++) {
            float w = getWeight(i);
            if(w != 0) {
                jointIndices[nUsedWeights] = i;
                usedWeights[nUsedWeights++] = w;
            }
        }
        for(int i = 0; i < 4; i++) {
            bb.put((byte) jointIndices[i]);
        }
        for(int i = 0; i < 4; i++) {
            bb.putFloat(usedWeights[i]);
        }
    }
    
}


Hopefully that's enough to make it clear how it works. I know there are some holes in my implementation according to the code I posted but that's due to abstraction and some slightly unclear inheritance (I haven't posted the links in the chain). I'm happy to take questions or better still suggestions.

Also notice how little code (excluding the data structures which would have to be there no matter what) it actually took for me to implement skeletal animation with my RenderMode s. In the short term they can be difficult to fit into your engine especially if you don't have them in mind from the off (which I didn't). The generic data thing in particular took me about a week of programming (I'm also in school so it's not any where near a full days work but still) but it's also the bit I'm most proud of and has been the most useful. I'm just trying to persuade you since I believe it will make life so much easier for you later.

I haven't actually done the lighting for this game yet and I've never worked out the normal matrix from the model matrix in the shader before (they are orthonormal matrices so it's just the top left 3x3 but I don't know if casting to mat3 will do that) so I have no idea if those normal calculations are correct. Cross that bridge when I come to it.

sasha-san

@quew8 Found the bug. My vertex/fragment shaders were so barebone, that glsl compiler deleted the uniform because it didn't influence any output. I kinda mentioned that possibility, thinking that just mentioning the uniform would work, but the compiler is one thorough sonuva... With that behind me, let me take a gander at the code you posted.

Update
@quew8 Does the index of the attribute pointer is reflected by the order of attributes in the shader. Does the data in pointer 0 got to vec3 position because it's first in the shader?
And this:
public void uploadJoint(int programId, String ibmVar, String jmVar, Matrix parentWJM, FloatBuffer matrixBuffer) {
        Matrix.times(tempMatrix, parentWJM, jointMatrix);
        tempMatrix.putIn(matrixBuffer);
        ShaderUtils.setUniformMatrix(programId, jmVar + indexString, matrixBuffer);
        invBindMatrix.putIn(matrixBuffer);
        ShaderUtils.setUniformMatrix(programId, ibmVar + indexString, matrixBuffer);
        for(int i = 0; i < children.length; i++) {
            children[i].uploadJoint(programId, ibmVar, jmVar, tempMatrix, matrixBuffer);
        }
    }

Can you really send an array by just getting attribute location for "anArray[0]", "anArray[1]", "anArray[2]" and so on? If so, I must go through glsl documentation once again(for the first time with focus).

Update
Making my own .dae parser using jdom. Heaving a lot of fun.

quew8

That's nasty of the compiler, never heard of that before. Good to know.

Quote from: sasha-san on November 10, 2013, 16:59:20
@quew8 Does the index of the attribute pointer is reflected by the order of attributes in the shader. Does the data in pointer 0 got to vec3 position because it's first in the shader?

I have a feeling that is the default, but I would never rely on the defaults for something like this, take a look here: http://www.opengl.org/sdk/docs/man2/xhtml/glBindAttribLocation.xml. It just binds an attribute name to an index which you can then use. But generally I just set them in the order they appear in the shader - makes it all simpler.

Quote from: sasha-san on November 10, 2013, 16:59:20
Can you really send an array by just getting attribute location for "anArray[0]", "anArray[1]", "anArray[2]" and so on? If so, I must go through glsl documentation once again(for the first time with focus).

Indeed it it so. You can fill up an array with the glUniformXXv() functions, but generally I don't find much point. I'm sure it might be quicker if you were going for performance.

Quote from: sasha-san on November 10, 2013, 16:59:20
Making my own .dae parser using jdom. Heaving a lot of fun.

I used Dom4J personally. Are you using that tutorial I linked? How are you finding it?

sasha-san

@quew8 Kinda wanted to reacquaint myself with jdom so I'm reading a tutorial on that. I'm finishing a material extracting method atm. If I get stuck I'll go for the tutorial you linked, but I think that since the part containing vertex data is so much different form .obj, I'll use it for sure. The armature data will be a drag too. BTW are you making contingencies for all type of lighting calculations(phong, blin etc.)?

quew8

Yeah, I only ask because looking over it again having (almost) finished my parser, it does make more sense to me than initially (obviously really) but I'm starting to wonder whether my initial thoughts on the tutorial were justified and thus whether my writing a new tutorial is really necessary. So impressions of an "impartial" (and not-me) individual would be appreciated.

Quote from: sasha-san on November 11, 2013, 16:53:30
are you making contingencies for all type of lighting calculations(phong, blin etc.)?

No. What I do support, I support completely but everything else I've just ignored. So that means I read geometries, controllers, armature stuff, animations, textures (materials if I ever get round to it) and ignore everything else (I think that's everything). A real game programmer would write his own binary format containing only what is necessary but I think I might just cannibalize COLLADA and just take out everything I don't read. Ultimately that will just increase the size of my game (I'll have to include the Dom4J library as well as the larger COLLADA files) but I'll cross that bridge when I come to it (story of my life).

sasha-san

@quew8 My first thoughts on the tutorial are that it's in one big chunk(examples aside). I'd like an index with links to separate parts with in-depth information on a topic. It generally lacks clarity imo. Some thing I think are important are lost in the text. That's it atm.

Update
A real clusterf**k here. I'm trying to brake this thing into smaller pieces, but parsing collada is making me a bit crazy. Have to play BF4 to keep sane, so the work slows down.

@quew8 About that tutorial.. A map! It's missing a good color-coded map. There is an attempt at such thing, but I had to write my own. Have to do it again, forgot about normal/bump maps.