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.