With this we wrap up two topics. Textures will be done; well more done, my texture code was done after the UV coordinates. As well as transformations and animation. Or at least the basics of animation are done, this just shows how to move and distort meshes, actually making it look like real movement takes a bit more planning and effort.
As far as the C# code goes there's not a lot interesting to talk about. I add nine values to the GLModel class to hold the translations, rotations, and scaling for each of the three axes. Then add a 20 millisecond timer to the main form, when this triggers it calls this function:
1: private void RedrawTimer(object oSender, EventArgs eaArgs) {
2: if (cbMoveLeft == true) {
3: cModel.TranslateX -= 0.05f;
4:
5: if (cModel.TranslateX < -3) {
6: cbMoveLeft = false;
7: }
8: } else {
9: cModel.TranslateX += 0.05f;
10:
11: if (cModel.TranslateX > 3) {
12: cbMoveLeft = true;
13: }
14: }
15:
16: if (cbMoveUp == false) {
17: cModel.TranslateY -= 0.05f;
18:
19: if (cModel.TranslateY < -2.5) {
20: cbMoveUp = true;
21: }
22: } else {
23: cModel.TranslateY += 0.05f;
24:
25: if (cModel.TranslateY > 2.5) {
26: cbMoveUp = false;
27: }
28: }
29:
30: cModel.RotateX += 1.0f;
31: if (cModel.RotateX >= 360) {
32: cModel.RotateX = 0.0f;
33: }
34:
35: cModel.RotateY += 0.5f;
36: if (cModel.RotateY >= 360) {
37: cModel.RotateY = 0.0f;
38: }
39:
40: cModel.RotateZ += 1.5f;
41: if (cModel.RotateZ >= 360) {
42: cModel.RotateZ = 0.0f;
43: }
44:
45: cModel.ScaleX = (cModel.TranslateY + 3.5f) / 4.0f;
46: cModel.ScaleY = (cModel.TranslateY + 3.5f) / 4.0f;
47: cModel.ScaleZ = (cModel.TranslateY + 3.5f) / 4.0f;
48:
49: GLViewPainting(null, null);
50: }
There's a lot of code in there which controls how the cube moves on the screen. Each time we render the screen the cube will be moved a little bit in a bunch of directions. I'm trying to get the cube to bounce around on the screen without actually getting out of view. There's probably a simpler way to do it, but I wrote this first and don't feel like fixing it.
Lines 2 through 28 move the cube vertically and horizontally. It goes till it reaches the edge and reverses direction.
Lines 30 through 43 have it rotate on all three axes. Which is less visually appealing than I had hoped, there are times when it just seems to wobble and not really spin. Cutting out one axis of rotation makes the spinning look better, but I left it all in to be sure my rotation matrices were all working properly.
Lines 45 to 57 set the cube's scale. As the cube move up on the screen it gets bigger, and shrinks when it goes back down. It's more interesting to watch when only one or two of the axes are scaled since it gets this squashing effect.
Finally line 49 calls the painting function to refresh whats on screen, otherwise we'd make all these changes and the effects would never be shown.
The GLModel class now has all of the information to move the cube around, but it still has to pass that on to the shader so that it can draw the cube in its new position. This all takes place in its RenderObject() method:
1: public bool RenderObject() {
2: float[] afUniformData;
3:
4: cstrErrDesc = "";
5:
6: //Be sure the buffer objects have been specified
7: if ((ciUniformID == -1)||(ciUniformBuffer == -1)) {
8: cstrErrDesc = "Unable to render with no Uniform Buffer or ID specified.";
9: return false;
10: }
11:
12: if ((ciVertexBuffer == -1)||(ciVertexID == -1)) {
13: cstrErrDesc = "Unable to render with no Vertex Buffer or ID specified.";
14: return false;
15: }
16:
17: //Check if buffer data needs refreshed
18: if (cbVertexChange == true) {
19: GL.BindBuffer(BufferTarget.ArrayBuffer, ciVertexBuffer);
20: GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(cav3Vertexes.Length * Vector3.SizeInBytes), cav3Vertexes, BufferUsageHint.DynamicDraw);
21: cbVertexChange = false;
22: }
23:
24: if (cbUVCoordsChange == true) {
25: GL.BindBuffer(BufferTarget.ArrayBuffer, ciUVCoordsBuffer);
26: GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(cav2UVCoords.Length * Vector2.SizeInBytes), cav2UVCoords, BufferUsageHint.DynamicDraw);
27: cbVertexChange = false;
28: }
29:
30: if (cbNormalChange == true) {
31:
32: cbNormalChange = false;
33: }
34:
35: if (cbUniformChange == true) {
36: //Set Uniform buffer data
37: afUniformData = new float[4 * 4];
38:
39: afUniformData[0] = cv4BaseColor.X;
40: afUniformData[1] = cv4BaseColor.Y;
41: afUniformData[2] = cv4BaseColor.Z;
42: afUniformData[3] = cv4BaseColor.W;
43:
44: afUniformData[4] = cav3Transforms[0].X;
45: afUniformData[5] = cav3Transforms[0].Y;
46: afUniformData[6] = cav3Transforms[0].Z;
47: afUniformData[7] = 0.0f;
48:
49: afUniformData[8] = cav3Transforms[1].X;
50: afUniformData[9] = cav3Transforms[1].Y;
51: afUniformData[10] = cav3Transforms[1].Z;
52: afUniformData[11] = 0.0f;
53:
54: afUniformData[12] = cav3Transforms[1].X;
55: afUniformData[13] = cav3Transforms[1].Y;
56: afUniformData[14] = cav3Transforms[1].Z;
57: afUniformData[15]= 0.0f;
58:
59: GL.BindBuffer(BufferTarget.UniformBuffer, ciUniformBuffer);
60: GL.BufferData(BufferTarget.UniformBuffer, (IntPtr)(afUniformData.Length * sizeof(float)), afUniformData, BufferUsageHint.DynamicDraw);
61: }
62:
63: //Draw the model
64: if (ciTextureID != -1) {//A texture is loaded, use it
65: GL.BindTexture(TextureTarget.Texture2D, ciTextureID);
66: }
67:
68: GL.BindBuffer(BufferTarget.UniformBuffer, ciUniformBuffer);
69: GL.BindBufferBase(BufferTarget.UniformBuffer, ciUniformID, ciUniformBuffer);
70:
71: GL.BindBuffer(BufferTarget.ArrayBuffer, ciVertexBuffer);
72: GL.EnableVertexAttribArray(ciVertexID);
73: GL.VertexAttribPointer(ciVertexID, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);
74:
75: GL.BindBuffer(BufferTarget.ArrayBuffer, ciUVCoordsBuffer);
76: GL.EnableVertexAttribArray(ciUVCoordsID);
77: GL.VertexAttribPointer(ciUVCoordsID, 2, VertexAttribPointerType.Float, false, Vector2.SizeInBytes, 0);
78:
79: GL.DrawArrays(BeginMode.Triangles, 0, cav3Vertexes.Length);
80: GL.DisableVertexAttribArray(ciVertexID);
81: GL.DisableVertexAttribArray(ciUVCoordsID);
82:
83: return true;
84: }
This is mostly the same as it was after we modified it to load and apply textures. The only real changes are in lines 35 through 61. This is where I'm setting the uniform variables that will hold all of our transforms. Previously I was just setting a color in there. Now I'm adding three 3D vectors to hold all of the transformation details for this model. The first vector is for scale, the second is for rotation, and the third is for translations.
As a reminder, uniform variables are expected to be in 4D elements, which is one value more than the 3D vectors I'm supplying. Which is why on lines 47, 52, and 57 I'm putting in filler values. This makes sure that the data will line up properly when the shader tries to read it.
Since I'm packing new data into the uniform variable, I'll need to update my shader code as well. Mostly the vertex shader, the functionality of the fragment shader remains the same. Here's the new vertex shader:
1: #version 130
2: #extension GL_ARB_uniform_buffer_object : enable
3:
4: uniform Data {
5: mat4 m4Projection;
6: vec4 v4BaseColor;
7: };
8:
9: uniform ModelData {
10: vec4 v4ModelColor;
11: vec4 v3ModelScale;
12: vec4 v3ModelRotate;
13: vec4 v3ModelTranslate;
14: };
15:
16: attribute vec3 v3Location;
17: attribute vec2 v2UVCoord;
18:
19: varying vec4 v4FragColor;
20: varying vec2 v2UV;
21:
22: void main() {
23: mat4 m4Scale, m4Translate, m4RotateX, m4RotateY, m4RotateZ;
24:
25: m4Translate = mat4(1.0);
26: m4Translate[0][3] = v3ModelTranslate.x;
27: m4Translate[1][3] = v3ModelTranslate.y;
28: m4Translate[2][3] = v3ModelTranslate.z;
29:
30: m4Scale = mat4(1.0);
31: m4Scale[0][0] = v3ModelScale.x;
32: m4Scale[1][1] = v3ModelScale.y;
33: m4Scale[2][2] = v3ModelScale.z;
34:
35: m4RotateX = mat4(1.0);
36: m4RotateX[1][1] = cos(v3ModelRotate.x);
37: m4RotateX[1][2] = -1 * sin(v3ModelRotate.x);
38: m4RotateX[2][1] = sin(v3ModelRotate.x);
39: m4RotateX[2][2] = cos(v3ModelRotate.x);
40:
41: m4RotateY = mat4(1.0);
42: m4RotateY[0][0] = cos(v3ModelRotate.y);
43: m4RotateY[0][2] = sin(v3ModelRotate.y);
44: m4RotateY[2][0] = -1 * sin(v3ModelRotate.y);
45: m4RotateY[2][2] = cos(v3ModelRotate.y);
46:
47: m4RotateZ = mat4(1.0);
48: m4RotateZ[0][0] = cos(v3ModelRotate.z);
49: m4RotateZ[0][1] = -1 * sin(v3ModelRotate.z);
50: m4RotateZ[1][0] = sin(v3ModelRotate.z);
51: m4RotateZ[1][1] = cos(v3ModelRotate.z);
52:
53: gl_Position = vec4(v3Location, 1.0) * m4Scale * m4RotateX * m4RotateY * m4RotateZ * m4Translate * m4Projection;
54:
55: v4FragColor = v4ModelColor;
57: v2UV = v2UVCoord;
58: }
It got significantly larger. Most of that code is for creating the various matrices to transform the model.
Lines 9 through 14 change the declaration of the uniform data. You can see the new vec3 variables that are a part of it now.
Lines 23 through 51 create all the matrices needed to transform this model. Nothing too magical, just a lot of data being copied around.
Line 53 applies all of the transforms to the vertex. The order of the multiplication is important, if you do the math in a different order you can get some very different and bizarre results on screen. Just to repeat the order:
- Start with the vertex coordinates
- Multiply by the scale matrix
- Multiply by the rotation matrices
- Multiply by the translation matrix
- Multiply by the projection matrix
After I had completed all this I of course ran the program and was bewildered to see the cube not at all doing what I expected. It didn't rotate and zoomed across the screen and out of view. It took me some time to remember that when I created the OBJ file I pushed the vertex Z coordinates a bit into the distance so that it would appear on screen. This moved the center of my model away from the origin (coordinate 0,0,0), and caused a bunch of my transforms to misbehave spectacularly.
A quick change is needed in the OBJ file now. Previously the Z coordinates were either -7 or -9, I changed them to 1 and -1 respectively and everything worked just the way I wanted it to.
I suppose an animated GIF might capture it better, but laziness prevented that. You'll have to take my word on it that the cube is moving around in such a way that its obviously a cube and the textures line up right so that it resembles a die as well.
Alternatively you can grab all the source code and compile it yourself and watch it move around. If so, you can get the code here.
No comments:
Post a Comment