There is probably no public file format that contains all the information you need in a convenient form (collada might have a good solution--I'm not sure). Practically every engine I've ever worked on has required a custom exporter at some point. to answer your original questions: 1. Around 80 bones is pretty common. Depending on your min spec, you may be able to store that many bones in shader constants, but typically that isn't the case. For example, our min spec only allows a bone cache of 24. This requires us to use some fairly complicated mesh processing code to break our models up into efficient, appropriately-sized chunks. 2. 4 bones per vertex is the standard. This is usually passed into the vertex shader as a float3 for the weights (the 4th weight is determined implicitly as one minus the sum of the other three) and a float4 for the indices, which is converted from a ubyte4 or a color. 3. The quaternion/translation approach might work, but it wouldn't allow non-uniform scale or other kinds of transformations. It might be worth pursuing, depending on your needs. At best, it will save half the storage compared to matrices, but be a bit less flexible. 4. In my experience, individual animations are usually stored separately. Sometimes, groups of animations might be glommed together into a single file for each character, but the output from the animators is usually individual files. File format is wide open, again, a custom exporter is usually the way to go. What we do is export regularly-sampled keyframes for each bone and then do a curve reduction on them to generate irregular keyframes (which is really just a form of lossy data compression). Anyway, it all adds up to a lot of work. Good luck to you. === If you use, say, second set of texture coordinates to store indices to the bones, and, say, the colour as the weights, you would have 4 bones per vertex max - which is fine for most purposes. You don't really want/need more. You pass in the bone matrices as uniforms to the vertex shader, and use the second set of texture coordinates ( cast to int ) to pick out which matrix you are using. Multiply the vertex by this. Then multiply that vertex by the colour value that is associated with it. r for first influence, g for second influence, b for third influence, and a for fourth influence. Add these all up to get your final vertex. Sorry if my explanation is a bit, well, crap. I am useless at explanations, I am hoping that someone else here might be able to explain a little better, but I shall post my skinning vertex shader ( Cg, although easily turned into GLSL ) for reference. Another thing to note - each bone matrix needs to be: Inverse bind pose * bone matrix. The inverse bind pose matrix will transform the vertex about the origin, ready to be transformed by the real bone matrix. Anyways...some maybe helpful code, its got some half written lighting in there, but you can safely ignore it: === /************************************************************************** * * File: RsVsSkin.cgfx * Author: Neil Richardson * Ver/Date: * Description: * Vertex Skinning + Per-pixel lighting * * * * **************************************************************************/ ////////////////////////////////////////////////// // Input float4x4 ModelView; float4x4 ModelViewProj; float4x4 BoneMatrices[12]; // Lighting float4 LightPos[4]; float4 LightDiffCol[4]; float4 LightSpecCol[4]; float LightAttnCons[4]; float LightAttnLine[4]; float LightAttnQuad[4]; // Material float MatSpecular; // Diffuse Map sampler2D TexMap0 = sampler_state { minFilter = Linear; magFilter = Linear; }; // Specular Map sampler2D TexMap1 = sampler_state { minFilter = Linear; magFilter = Linear; }; // Normal Map sampler2D TexMap2 = sampler_state { minFilter = Linear; magFilter = Linear; }; ////////////////////////////////////////////////// // Structures struct appdata { float4 TexCoord : TEXCOORD0; float4 Normal : NORMAL; float4 Tangent : TEXCOORD1; float4 Position : POSITION; float4 Weights : TEXCOORD2; float4 Indices : TEXCOORD3; }; struct vfconn { // Basic Params. float4 Position : POSITION; float4 TexCoord : TEXCOORD0; float4 FragPos; float4 LightDir[4]; }; ////////////////////////////////////////////////// // Vertex void RsVsSkinVert( appdata IN, out vfconn OUT, uniform float4x4 ModelView, uniform float4x4 ModelViewProj, uniform float4x4 BoneMatrices[12] ) { // Calculate vertex position, normal and tangent float4 Position = float4( 0.0, 0.0, 0.0, 0.0 ); float3 Normal = float3( 0.0, 0.0, 0.0 ); float3 Tangent = float3( 0.0, 0.0, 0.0 ); Position = Position + ( mul( BoneMatrices[ IN.Indices.x ], IN.Position ) * IN.Weights.x ); Position = Position + ( mul( BoneMatrices[ IN.Indices.y ], IN.Position ) * IN.Weights.y ); Position = Position + ( mul( BoneMatrices[ IN.Indices.z ], IN.Position ) * IN.Weights.z ); Position = Position + ( mul( BoneMatrices[ IN.Indices.w ], IN.Position ) * IN.Weights.w ); Normal = Normal + mul( (float3x3)BoneMatrices[ IN.Indices.x ], IN.Normal.xyz ) * IN.Weights.x; Normal = Normal + mul( (float3x3)BoneMatrices[ IN.Indices.y ], IN.Normal.xyz ) * IN.Weights.y; Normal = Normal + mul( (float3x3)BoneMatrices[ IN.Indices.z ], IN.Normal.xyz ) * IN.Weights.z; Normal = Normal + mul( (float3x3)BoneMatrices[ IN.Indices.w ], IN.Normal.xyz ) * IN.Weights.w; Normal = normalize( Normal ); Tangent = Tangent + mul( (float3x3)BoneMatrices[ IN.Indices.x ], IN.Tangent.xyz ) * IN.Weights.x; Tangent = Tangent + mul( (float3x3)BoneMatrices[ IN.Indices.y ], IN.Tangent.xyz ) * IN.Weights.y; Tangent = Tangent + mul( (float3x3)BoneMatrices[ IN.Indices.z ], IN.Tangent.xyz ) * IN.Weights.z; Tangent = Tangent + mul( (float3x3)BoneMatrices[ IN.Indices.w ], IN.Tangent.xyz ) * IN.Weights.w; Tangent = normalize( Tangent ); // Transform the normal and tangent. Normal = mul( (float3x3)ModelView, Normal ); Tangent = mul( (float3x3)ModelView, Tangent ); // Build TBN Matrix float3 BiTangent = cross( Normal.xyz, Tangent.xyz ); float3x3 TBNMatrix = float3x3( Tangent, BiTangent, Normal ); OUT.FragPos = mul( ModelView, Position ); OUT.Position = mul( ModelViewProj, Position ); OUT.TexCoord = IN.TexCoord; // Calculate light directions // NOTE: Light positions should be in world space already. //for( int i = 0; i < 4; ++i ) //{ // float3 LightDir = OUT.FragPos - LightPos[ i ].xyz; // LightDir = normalize( LightDir ); // OUT.LightDir[ i ] = float4( mul( TBNMatrix, LightDir.xyz ), 1.0 ); //} } ////////////////////////////////////////////////// // Fragment float3 expandNormal( float3 N ) { return ( N - 0.5 ) * 2.0; } float4 RsVsSkinFrag( vfconn IN, uniform sampler2D TexMap0, uniform sampler2D TexMap1, uniform sampler2D TexMap2 ) : COLOR { float4 DiffMap = tex2D( TexMap0, IN.TexCoord.xy ); float3 SpecMap = tex2D( TexMap1, IN.TexCoord.xy ).xyz; float3 NormMap = expandNormal( tex2D( TexMap2, IN.TexCoord.xy ).xyz ); // Diffuse Lighting float3 TotalDiffuse = float3( 0.0, 0.0, 0.0 ); for( int i = 0; i < 4; ++i ) { float3 LightDir = normalize( IN.LightDir[ i ].xyz ); float4 LightVec = IN.FragPos - LightPos[ i ]; float Distance = length( LightVec ); float Attenuation = 1.0 / ( ( LightAttnCons[ i ] ) + ( LightAttnLine[ i ] * Distance ) + ( LightAttnQuad[ i ] * Distance * Distance ) ); float3 DiffuseColour = LightDiffCol[ i ] * Attenuation * ( ( dot( LightDir, NormMap ) + 1.0 ) * 0.5 ); TotalDiffuse = TotalDiffuse + DiffuseColour; } return float4( DiffMap.xyz * TotalDiffuse, DiffMap.w ); } ////////////////////////////////////////////////// // Techniques technique RsVsSkin_nv40 { pass { FragmentProgram = compile fp40 RsVsSkinFrag( TexMap0, TexMap1, TexMap2 ); VertexProgram = compile vp40 RsVsSkinVert( ModelView, ModelViewProj, BoneMatrices ); } } technique RsVsSkin_nv30 { pass { FragmentProgram = compile fp30 RsVsSkinFrag( TexMap0, TexMap1, TexMap2 ); VertexProgram = compile vp30 RsVsSkinVert( ModelView, ModelViewProj, BoneMatrices ); } } === You want your shaders fast, so compute the matrix and put it in 3 constants per bone. The effort involved in trying to drop that to 2 makes it not worth it. You don't need an artist to split your model. I wrote our bone-splitting code in a day or so. It tries to get all polys with low boneids first. This is going from memory/logic, so I might have left something out... maxboneid = 3 numbonesleft = 20 while(facesremain) { begin new mesh section while(maxboneid < bonecountinmesh) { for each face { each bone on each vertex is < maxboneid? { Are the number of newboneids less than numbonesleft? { numbonesleft-- add new boneids to already accepted array Add face to accepted face list } } } maxboneid++ // try higher boneids next pass } Accumulate vertices used in accepted faces Remap vertices from 0 to numverts Remap indices in faces Remap morph target vertices Remap boneids in vertices to accepted boneid subset Stripify faces } === Sorry if my explanations are a little too full of graphics jargon. I have a hard time explaining things in simple terms ^_^ By "skinning" I mean the process of transforming the mesh verticies into their final animated pose based on the bone positions. Calculating the bone positions is always (I'm fairly certain) done on the CPU (software) and transforming the verticies based on those bones is quite often done on the CPU, but doing it on the GPU through a vertex shader is becoming quite popular. Yes, this requires you to use up a lot of uniforms in order to store all of the matricies. Shader 1.1 hardware usually has enough room to handle approx 25 bones at a time with this method, and that goes up considerably as you get into PS 2.0+ hardware, but often with GPU skinning you have to break your mesh up into several chunks that are only affected by 25-ish bones at a time. This is a little inconvinent, and does require more batches, but it's very nice to have that calculation unloaded from your CPU. === Actually I was a little confused by the same point. I implemented skeletal anim in OpenGL with a 1.1 vertex program doing the skinning. My test humanoid has 19 bones. I can either submit these as auxiliary matrices using GL_MATRIX0-18_ARB which are accessible through state in vp1.1, or (what i did) send em as a big array of uniforms, obviously noting that you never need a projection transform. I figured using 19 aux matrices would be equivalent to setting 76 uniforms, where as 19 3x4 matrices only require 57 uniforms. Then I submit a boneIndex as a per-vertex attribute. At load time I put all vertex,texcoord,indices,bone indexes, into a Vertex Array. Rendering then only consists of setting the 57 uniforms and a single draw call. I think this worked out to be incredibly efficient, I can render about 2 million tri/sec with this method on my FX5700. Here's the vertex program source if anyone wants it. Couldn't find any thing like this just searching on google, so maybe it will be useful to someone. !!ARBvp1.0 #Input ATTRIB InPos = vertex.position; ATTRIB InColor = vertex.color; ATTRIB InTC = vertex.texcoord; ATTRIB InNorm = vertex.normal; #Output OUTPUT OutPos = result.position; OUTPUT OutColor = result.color; PARAM arr[57] = { program.local[0..56] }; PARAM one = { 1,1,1,1 }; PARAM MVP[4] = { state.matrix.mvp }; TEMP boneVector; # Vertex attrib that holds the bone index * 3 ATTRIB v1 = vertex.attrib[1]; # Store in addr the index that was retrieved from the vertex attribute ADDRESS addr; ARL addr.x, v1.x; # Set bone vector.w to one MOV boneVector.w, one; # Transform in-position by 3x4 transformations indexed by addr DP4 boneVector.x, arr[addr.x+0], InPos; DP4 boneVector.y, arr[addr.x+1], InPos; DP4 boneVector.z, arr[addr.x+2], InPos; # Transform bone-position to out-position using modelview-projection DP4 OutPos.x, MVP[0], boneVector; DP4 OutPos.y, MVP[1], boneVector; DP4 OutPos.z, MVP[2], boneVector; DP4 OutPos.w, MVP[3], boneVector; # Copy out texture coordinates and colors for fragment program MOV result.texcoord[0], vertex.texcoord[0]; MOV OutColor, one; END === Disclaimer: Haven't worked in Direct X for a while, and I've only done a bit of shader work with it, so I may be off here. If I'm not mistaken, most of the time that you want to pass in "extra" information to a shader it is done through any free TexCoord attributes you may have. Many times only one or two set of texcoords are needed at most, leaving another 6-7 avalible for whatever attributes you may need them for. Since a texcoord can be anywhere from 1-4 floats, it works really well. That said, in OpenGL there are ways of specifying an entirely new shader-specific attribute stream, which makes life quite a bit easier in certain cases. I'm almost certain that I heard of a reasonable equivalent in DX, but I can't for the life of me remember where I heard that or what it was. Sorry! === Yes, you would - but you should have enough constants in your vertex shader for a reasonably large sized batch of bones. you need 3 constants per bone (assuming matrices). You get at least 96 constants with DX8 level shaders, assuming you just use half for bones (the rest for other stuff like lighting parameters etc.) you get 16 bones per batch, which isn't too bad. Edit: and it's notable that is just with the lowest vertex shader model, with shader 2 & 3 you have at least 256 vertex shader constants, possibly more (in D3D this is controlled by a caps parameter, there are similar queries in OpenGL but I can't remember the names). -Mezz === Well, I was wondering how the vertices in the batch (or vertex array or whatever is used) know which weights/which bones they are supposed to use. But I found a solution myself: otherwise unused texture coordinates could be used for this. And it seems this is a solution that is commonly used. I found this example code in a gamasutra article on animation with Cg: void C6E5v_skin4m(float3 position : POSITION, float3 normal : NORMAL, float2 texCoord : TEXCOORD0, float4 weight : TEXCOORD1, float4 matrixIndex : TEXCOORD2, out float4 oPosition : POSITION, out float2 oTexCoord : TEXCOORD0, out float4 color : COLOR, uniform Light light, uniform float4 boneMatrix[72], // 24 matrices uniform float4x4 modelViewProj) { float3 netPosition = 0, netNormal = 0; for (int i = 0; i < 4; i++) { float index = matrixIndex[i]; float3x4 model = float3x4(boneMatrix[index + 0], boneMatrix[index + 1], boneMatrix[index + 2]); float3 bonePosition = mul(model, float4(position, 1)); // Assume no scaling in matrix, just rotate & translate float3x3 rotate = float3x3(model[0].xyz, model[1].xyz, model[2].xyz); float3 boneNormal = mul(rotate, normal); netPosition += weight[i] * bonePosition; netNormal += weight[i] * boneNormal; } netNormal = normalize(netNormal); oPosition = mul(modelViewProj, float4(netPosition, 1)); oTexCoord = texCoord; color = computeLighting(light, netPosition, netNormal); } === You can also create these matrices on the gpu instead of cpu. I think on vs1.1 you have 96 4D vectors, three of them can be used to rotate a vertex. You got then 32 matrices but alas some of them will be used for modelview and projection matrices. New gpus allow for many more matrices in your vertex shader. === Still having problems. In the following code I'm just trying to display the third key frame of the animation. I know that the matrix array I'm sending to the CG program is identical to the matrices I use to draw the bone positions(for testing). The bones are in the correct positions, but the mesh is all warpy. Take a look at the images. Images: bindpose to test if everything fits. This is supposed to be frame three. And the figure is just for testing so don't rag on me for bad modeling. :) Here is my bloody code: void fluSkinMesh::drawMesh() { glCullFace(GL_BACK); glEnable(GL_CULL_FACE); float *BoneMatrixArray = new float[16 * m_joints.size()]; unsigned int matindx = 0; for (vector::iterator jntIter = m_joints.begin(); jntIter != m_joints.end(); jntIter++, matindx++) { // Get third key Matrix for testing CMatrix mat = jntIter->getKeyMatrix(3); // Get Bind Pose CMatrix bmat = jntIter->getBindMatrix(); bmat.Invert(); mat *= bmat; for(int ii=0; ii<16; ii++) BoneMatrixArray[ii + (matindx*16)] = mat[ii]; } // Set the varying parameters cgGLSetParameterPointer(m_cgParamVerts, 3, GL_FLOAT, sizeof(FLUVERTDATA), m_vertArray); cgGLEnableClientState(m_cgParamVerts); cgGLSetParameterPointer(m_cgParamBoneIndxs, 4, GL_FLOAT, sizeof(FLUVERTDATA), &m_vertArray[0].boneindx[0]); cgGLEnableClientState(m_cgParamBoneIndxs); cgGLSetParameterPointer(m_cgParamWeights, 4, GL_FLOAT, sizeof(FLUVERTDATA), &m_vertArray[0].weights[0]); cgGLEnableClientState(m_cgParamWeights); //cgGLSetMatrixParameterArrayfc(m_cgParamBoneMatrixArray, 0, 18, BoneMatrixArray); cgGLSetMatrixParameterfc(m_cgParamBone00, &BoneMatrixArray[0]); cgGLSetMatrixParameterfc(m_cgParamBone01, &BoneMatrixArray[16]); cgGLSetMatrixParameterfc(m_cgParamBone02, &BoneMatrixArray[32]); cgGLSetMatrixParameterfc(m_cgParamBone03, &BoneMatrixArray[48]); cgGLSetMatrixParameterfc(m_cgParamBone04, &BoneMatrixArray[64]); cgGLSetMatrixParameterfc(m_cgParamBone05, &BoneMatrixArray[80]); cgGLSetMatrixParameterfc(m_cgParamBone06, &BoneMatrixArray[96]); cgGLSetMatrixParameterfc(m_cgParamBone07, &BoneMatrixArray[112]); cgGLSetMatrixParameterfc(m_cgParamBone08, &BoneMatrixArray[128]); cgGLSetMatrixParameterfc(m_cgParamBone09, &BoneMatrixArray[144]); cgGLSetMatrixParameterfc(m_cgParamBone10, &BoneMatrixArray[160]); cgGLSetMatrixParameterfc(m_cgParamBone11, &BoneMatrixArray[176]); cgGLSetMatrixParameterfc(m_cgParamBone12, &BoneMatrixArray[192]); cgGLSetMatrixParameterfc(m_cgParamBone13, &BoneMatrixArray[208]); cgGLSetMatrixParameterfc(m_cgParamBone14, &BoneMatrixArray[224]); cgGLSetMatrixParameterfc(m_cgParamBone15, &BoneMatrixArray[240]); cgGLSetMatrixParameterfc(m_cgParamBone16, &BoneMatrixArray[256]); cgGLSetMatrixParameterfc(m_cgParamBone17, &BoneMatrixArray[272]); // Set the uniform parameters that change every frame cgGLSetStateMatrixParameter(m_modelViewMatrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); cgGLBindProgram(m_cgSkinMesh); cgGLEnableProfile(m_profile); glDrawArrays(GL_TRIANGLES, 0, m_ifluNumVerts); cgGLDisableProfile(m_profile); cgGLDisableClientState(m_cgParamVerts); cgGLDisableClientState(m_cgParamBoneIndxs); cgGLDisableClientState(m_cgParamWeights); glDisable(GL_CULL_FACE); // Draw bone positions for DEBUGGING glPushMatrix(); for (vector::iterator jntIter = m_joints.begin(); jntIter != m_joints.end(); jntIter++) { glLoadIdentity(); // TODO: Dont forget to change this if view changes gluLookAt(0.0, 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Get third key matrix for testing CMatrix mat = jntIter->getKeyMatrix(3); CMatrix bmat = jntIter->getBindMatrix(); bmat.Invert(); mat *= bmat; glMultMatrixf(mat); drawQuad(); } glPopMatrix(); delete [] BoneMatrixArray; } And the CG program: struct a2vskinMesh { float4 objCoord; float4 boneWeights0; float4 boneIndices0; //float4 normal; //float2 texCoords : TEXCOORD0; }; struct v2fskinMesh { float4 projCoord : POSITION; float4 color : COLOR0; }; float4x4 skin(float4x4 bones[18], float4 boneWeights0, float4 boneIndices0) { float4x4 result = boneWeights0.x*bones[boneIndices0.x]; result = result + boneWeights0.y*bones[boneIndices0.y]; result = result + boneWeights0.z*bones[boneIndices0.z]; result = result + boneWeights0.w*bones[boneIndices0.w]; return result; } v2fskinMesh skinMesh( a2vskinMesh a2v, uniform float4x4 model[18], // Change num of bones when using different mesh! uniform float4x4 ModelViewProj) { v2fskinMesh v2f; float4x4 modelSkin = skin(model, a2v.boneWeights0, a2v.boneIndices0); float4 objCoord = a2v.objCoord; float4 worldCoord = mul(modelSkin, objCoord); float4 projCoord = mul(ModelViewProj, worldCoord); v2f.projCoord = projCoord; v2f.color = a2v.objCoord; // Temporary return v2f; } === Vertex Skinning in a Vertex Program The C6E5v_skin4m vertex program in Example 6-5 implements vertex skinning, assuming that no more than four bone matrices affect each vertex (a common assumption). An array of 24 bone matrices, each a 3x4 matrix, represents each pose. The entire array is a uniform parameter to the program. The program assumes that each bone matrix consists of a translation and a rotation (no scaling or projection). The per-vertex matrixIndex input vector provides a set of four bone-matrix indices for accessing the boneMatrix array. The per-vertex weight input vector provides the four weighting factors for each respective bone matrix. The program assumes that the weighting factors for each vertex sum to 100 percent. For performance reasons, the program treats boneMatrix as an array of float4 vectors rather than an array of float3x4 matrices. The matrixIndex array contains floating-point values instead of integers, and so the addressing of a single array of vectors is more efficient than accessing an array of matrices. The implication of this is that the indices in the matrixIndex vector should be three times the actual matrix index. So, the program assumes 0 is the first matrix in the array, 3 is the second matrix, and so on. The indices are fixed for each vertex, so you improve performance by moving this "multiply by 3" outside the vertex program. A for loop, looping four times, transforms the default pose position and normal by each bone matrix. Each result is weighted and summed. The program computes both the weighted position and normal for the pose. The same computeLighting internal function from Example 6-4 computes per-vertex object-space lighting with the weighted position and normal. Although this example is rather limited, you could generalize it to handle more bone matrices, general bone matrices (for example, allowing scaling), and matrices influencing each vertex-and to compute a better lighting model void C6E5v_skin4m(float3 position : POSITION, float3 normal : NORMAL, float2 texCoord : TEXCOORD0, float4 weight : TEXCOORD1, float4 matrixIndex : TEXCOORD2, out float4 oPosition : POSITION, out float2 oTexCoord : TEXCOORD0, out float4 color : COLOR, uniform Light light, uniform float4 boneMatrix[72], // 24 matrices uniform float4x4 modelViewProj) { float3 netPosition = 0, netNormal = 0; for (int i = 0; i < 4; i++) { float index = matrixIndex[i]; float3x4 model = float3x4(boneMatrix[index + 0], boneMatrix[index + 1], boneMatrix[index + 2]); float3 bonePosition = mul(model, float4(position, 1)); // Assume no scaling in matrix, just rotate & translate float3x3 rotate = float3x3(model[0].xyz, model[1].xyz, model[2].xyz); float3 boneNormal = mul(rotate, normal); netPosition += weight[i] * bonePosition; netNormal += weight[i] * boneNormal; } netNormal = normalize(netNormal); oPosition = mul(modelViewProj, float4(netPosition, 1)); oTexCoord = texCoord; color = computeLighting(light, netPosition, netNormal); } === You are confusing immediate mode commands and vertex array pointers. A vertex can have several different attributes: a position, a colour, a normal, one or multiple texture coordinates, etc. In the pre-vertex program days, each of those attributes had a well defined meaning. OpenGL has a set of immediate mode commands to set them (eg. glVertex3f(), glColor4ub(), etc), as well as vertex arrays support to transfer batches: glVertexPointer(), glColorPointer(), etc. But when vertex programs came up, the well defined meaning of each attribute was lost. After all, the vertex program can do whatever it wants with each attribute. For example, a VP can use the colour attribute as the vertex normal, and the normal attribute as some bone weight. There is no direct connection between usage and attribute naming anymore. That's why it was eventually decided to drop the naming completely, and replace the conventional named attributes by a set of generic attributes. Those are identified by an attribute number, rather than by a specific name. They are also all equal and orthogonal, and don't show any function specific behaviour (eg. glNormal() doesn't have 4 component types, glSecondaryColor() doesn't have an alpha component, etc). So generic attributes are a generalization of the specific attributes, just as a programmable vertex pipeline is a generalization of the fixed function pipeline. Now, for the sake of backwards compatibility, the old named attributes will still work, aliased to certain generic attributes. glVertexAttribPointerARB() defines the vertex array entry pointer for the specified generic attribute. It's totally analogous to glVertexPointer(), glNormalPointer(), etc, and behaves the exact same way. Ie. it will work with VBOs, you'll use glDrawElements() to draw stuff, etc. It just defines the vertex attributes in a more general way than the old method did. The glVertexAttrib() family is the immediate mode version, and is analogous to glVertex(), glColor(), glNormal(), etc. === I do exactly this. In the vertex shader I use for example the vertex.attrib[0] attribute and in the code I call glVertexAttribPointerARB (0,..). But it still doesnt show up anything :( === uniform sampler2D DepthTexture; uniform vec3 lightVector; varying float fresnel; varying vec3 Point[3]; varying vec3 Line[3]; varying vec4 TriNorm; void main() { gl_Position = ftransform(); Point[0] = gl_MultiTexCoord0.xyz; Point[1] = gl_MultiTexCoord1.xyz; Point[2] = gl_MultiTexCoord2.xyz; Line[0] = normalize(gl_MultiTexCoord3.xyz-Point[0]); Line[1] = normalize(gl_MultiTexCoord4.xyz-Point[1]); Line[2] = normalize(gl_MultiTexCoord5.xyz-Point[2]); TriNorm.xyz = normalize(-cross(Point[1]-Point[0],Point[2]-Point[0])); TriNorm.w = length(cross(Point[1]-Point[0], Point[2]-Point[0])); vec3 lightVec = normalize(lightVector); /*float a = acos(dot(TriNorm.xyz,-lightVector)); float b = acos(dot(-TriNorm.xyz,refract(lightVector, TriNorm.xyz, 1.0/1.33))); fresnel = ( sin(a-b)*sin(a-b) / (sin(a+b)*sin(a+b)) ) + tan(a-b)*tan(a-b)*tan(a+b)*tan(a+b); */ fresnel = 1.0; // (1.0-fresnel); }