These are arrays of data, each element of the array should correspond to one of the vertexes. When the shader executes it will only read the element that corresponds to the current vertex. This is handy for things like locations, colors, normals and so forth which need applied individually to each vertex.
Starting in the vertex shader, some new global variables are needed. I plan on passing through location and color data so we'll need something to hold that data. In our program we'll be handling this data as an array, however the shaders only care about one element of that array at a time, so we'll need scaler values in there. The location data has three values (X, Y, and Z coordinates) so a 3D vector is needed, and the color has four values (red, green, blue, and alpha) so a 4D vector is needed. When we declare these variables the attribute keyword is needed.
Adding the variables and updating the code and the vertex shader looks like this:
1: #version 130
2: #extension GL_ARB_uniform_buffer_object : enable
3:
4: uniform staticdata {
5: mat4 m4Projection;
6: vec4 v4Color;
7: };
8:
9: attribute vec3 v3Location;
10: attribute vec4 v4Color;
11:
12: varying vec4 v4FragColor;
13:
14: void main() {
15: gl_Position = vec4(v3Location, 1.0) * m4Projection;
16:
17: v4FragColor = v4Color;
18: }
Lines 9 and 10 are the new variables that will hold the attribute data.
I'm also declaring another global variable on line 12. Because fragments don't line up with vertexes the fragment shader can't accept attribute values, which means a different mechanism is necessary to get our color information over there. In the OpenGL rendering pipeline the fragment shader always goes first, so it can pass information to the fragment shader, it does this through varying variables. Which is what I've created here. This variable will be set by the vertex shader, and can be used in the fragment shader making it a good vehicle for getting our color information where we need it.
On line 15 we have to do a little math. The gl_Position needs four coordinates, but when I created my vertex coordinates I omitted the W coordinate. Using the vec4 constructor we can add in that missing coordinate again.
Line 17 copies the color information from the attribute variable to the varying variable, which carries it forward into the fragment shader.
Since there's this new source of color information a small change is needed in the fragment shader:
1: #version 130
2: #extension GL_ARB_uniform_buffer_object : enable
3:
4: uniform staticdata {
5: mat4 m4Projection;
6: vec4 v4Color;
7: };
8:
9: varying vec4 v4FragColor;
10:
11: void main() {
12: gl_FragColor = v4FragColor;
13: }
We add the varying variable on line 9; and then line 12 changes to get color information from that variable instead of from the uniform block.
The shaders are ready to use the new data, best get the rest of the program on board as well. As with the uniform blocks OpenGL creates a table of all the attribute arrays that will be handed to the shaders. This table has an ID that we can use to put data into them. Once again we'll want to create constants to hold our ID's.
1: protected const int ATTRIBLOCATION = 10;
2: protected const int ATTRIBCOLORS = 5;
OpenGL would assign our attributes ID's of zero and one, which correspond to some of the built in variables OpenGL provides the shaders. For example gl_Vertex pulls its data from whatever buffer object is bound to ID zero. There's nothing special about 5 or 10, just that they aren't the automatically assigned numbers.
Next, update the LoadShaders() function:
1: private int LoadShaders(string strVertexFile, string strFragFile) {
2: int iProgram, iVertShader, iFragShader, iUniformID;
3:
4: //Create vertex shader
5: iVertShader = GL.CreateShader(ShaderType.VertexShader);
6: using (StreamReader srShader = new StreamReader(strVertexFile)) {
7: GL.ShaderSource(iVertShader, srShader.ReadToEnd());
8: }
9: GL.CompileShader(iVertShader);
10: WriteLog("Vertex Shader: " + GL.GetShaderInfoLog(iVertShader));
11:
12: //Create fragment shader
13: iFragShader = GL.CreateShader(ShaderType.FragmentShader);
14: using (StreamReader srShader = new StreamReader(strFragFile)) {
15: GL.ShaderSource(iFragShader, srShader.ReadToEnd());
16: }
17: GL.CompileShader(iFragShader);
18: WriteLog("Fragment Shader: " + GL.GetShaderInfoLog(iFragShader));
19:
20: //Build GL program
21: iProgram = GL.CreateProgram();
22: GL.AttachShader(iProgram, iVertShader);
23: GL.AttachShader(iProgram, iFragShader);
24:
25: //Assign attribute buffers
26: GL.BindAttribLocation(iProgram, ATTRIBLOCATION, "v3Location");
27: GL.BindAttribLocation(iProgram, ATTRIBCOLORS, "v4Color");
28:
29: //Link the shading program
30: GL.LinkProgram(iProgram);
31: WriteLog("Shader Link: " + GL.GetProgramInfoLog(iProgram));
32:
33: //Define Uniform block
34: iUniformID = GL.GetUniformBlockIndex(iProgram, "staticdata");
35: GL.UniformBlockBinding(iProgram, iUniformID, UNIFORMBINDING);
36:
37: //Cleanup
38: GL.DeleteShader(iVertShader);
39: GL.DeleteShader(iFragShader);
40:
41: return iProgram;
42: }
Setting the ID's for attribute arrays must be done before you link the program. I'm doing this on lines 26 and 27 with the GL.BindAttribLocation() function. It takes the program ID, the ID you want to use for the attributes, and the name of the attributes that the shaders expect. Simple enough.
Next I need to create arrays to hold all our data, and this means adding another block of code to GLViewLoading()
1: private void GLViewLoading(object oSender, EventArgs eaArgs) {
2: //The GLControl is now loading
3: Matrix4 pmatMatrix;
4: float fAspect;
5: Vector3[] av3Triangle;
6: float[] afUniform;
7: Vector4[] av4Colors;
8:
9: WriteLog("GLControl: Loading...");
10: WriteLog("OpenGL Version: " + GL.GetString(StringName.Version));
11:
12: //Setup GL
13: GL.ClearColor(0.0f, 0.0f, 0.2f, 0.0f);
14: GL.Enable(EnableCap.DepthTest);
15:
16: //Setup the viewport
17: GL.Viewport(0, 0, cglGLView.Width, cglGLView.Height);
18: fAspect = cglGLView.Width / cglGLView.Height;
19: pmatMatrix = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, fAspect, 0.1f, 100.0f);
20: GL.MatrixMode(MatrixMode.Projection);
21: GL.LoadMatrix(ref pmatMatrix);
22: GL.MatrixMode(MatrixMode.Modelview);
23:
24: //Create video memory buffers
25: cauiVBOIDs = new uint[3];
26: GL.GenBuffers(3, cauiVBOIDs);
27:
28: //Vertex and uniform data hasn't changed, omitting it for brevity
29:
30: //Set color values
31: av4Colors = new Vector4[6];
32:
33: av4Colors[0].X = 0.0f;
34: av4Colors[0].Y = 0.0f;
35: av4Colors[0].Z = 0.0f;
36: av4Colors[0].W = 1.0f;
37: av4Colors[1].X = 0.5f;
38: av4Colors[1].Y = 0.5f;
39: av4Colors[1].Z = 0.5f;
40: av4Colors[1].W = 1.0f;
41: av4Colors[2].X = 1.0f;
42: av4Colors[2].Y = 1.0f;
43: av4Colors[2].Z = 1.0f;
44: av4Colors[2].W = 1.0f;
45:
46: av4Colors[3].X = 1.0f;
47: av4Colors[3].Y = 1.0f;
48: av4Colors[3].Z = 0.0f;
49: av4Colors[3].W = 1.0f;
50: av4Colors[4].X = 0.0f;
51: av4Colors[4].Y = 1.0f;
52: av4Colors[4].Z = 1.0f;
53: av4Colors[4].W = 1.0f;
54: av4Colors[5].X = 1.0f;
55: av4Colors[5].Y = 0.0f;
56: av4Colors[5].Z = 1.0f;
57: av4Colors[5].W = 1.0f;
58:
59: //Copy the vertex data into video memory
60: GL.BindBuffer(BufferTarget.ArrayBuffer, cauiVBOIDs[0]);
61: GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(av3Triangle.Length * Vector3.SizeInBytes), av3Triangle, BufferUsageHint.StaticDraw);
62:
63: GL.BindBuffer(BufferTarget.UniformBuffer, cauiVBOIDs[1]);
64: GL.BufferData(BufferTarget.UniformBuffer, (IntPtr)(afUniform.Length * sizeof(float)), afUniform, BufferUsageHint.DynamicDraw);
65: GL.BindBufferBase(BufferTarget.UniformBuffer, UNIFORMBINDING, cauiVBOIDs[1]);
66:
67: GL.BindBuffer(BufferTarget.ArrayBuffer, cauiVBOIDs[2]);
68: GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(av4Colors.Length * Vector4.SizeInBytes), av4Colors, BufferUsageHint.StaticDraw);
69:
70: //Load Shaders
71: ciProgramID = LoadShaders("Vertex.glsl", "Fragment.glsl");
72: //Done setting up GL for use
73: cbGLReady = true;
74: }
Line 7 adds the new array to hold our color data.
Lines 25 and 26 were updated to create a third buffer object so we'll have space to store the color data.
Lines 31 through 57 puts all of our data into the array.
Finally lines 67 and 68 we bind the buffer object and copy in the data. Nothing new here just once again adjusting the sizes to reflect the Vector4 objects this array is made of.
The final place where we need to make some changes is when we do the drawing. It's been a while since I've had to do anything in GLViewPainting(), given that the whole point of using OpenGL is to draw fancy shapes I figured this would be the function that would be seeing the most changes.
1: private void GLViewPainting(object oSender, EventArgs eaArgs) {
2: if (cbGLReady == false) {//Control is not loaded, do nothing
3: return;
4: }
5: WriteLog("Painting...");
6:
7: //Specify Shader
8: GL.UseProgram(ciProgramID);
9:
10: //Clear the GL View
11: GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
12: GL.MatrixMode(MatrixMode.Modelview);
13:
14: GL.BindBuffer(BufferTarget.ArrayBuffer, cauiVBOIDs[0]);
15: GL.EnableVertexAttribArray(ATTRIBLOCATION);
16: GL.VertexAttribPointer(ATTRIBLOCATION, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);
17:
18: GL.BindBuffer(BufferTarget.ArrayBuffer, cauiVBOIDs[2]);
19: GL.EnableVertexAttribArray(ATTRIBCOLORS);
20: GL.VertexAttribPointer(ATTRIBCOLORS, 4, VertexAttribPointerType.Float, false, Vector4.SizeInBytes, 0);
21:
22: GL.DrawArrays(BeginMode.Triangles, 0, 6);
23: GL.DisableVertexAttribArray(ATTRIBLOCATION);
24: GL.DisableVertexAttribArray(ATTRIBCOLORS);
25:
26: //Drawing complete
27: cglGLView.SwapBuffers();
28: }
Lines 15 and 19 enable the attribute arrays using GL.EnableVertexAttribArray(), we just have to tell it the ID's of the ones we'd like to use. This will link the buffer current bound by a call to GL.BindBuffer() to that attribute ID such that it's data will be given to the shaders.
Then we call GL.VertexAttribPointer() to describe the way the data is packaged in that array so that the array elements are properly extracted from the buffer on lines 16 and 20.
After we're done drawing we can turn off the attribute arrays in order to reuse those ID's by calling GL.DisableVertexAttribArray() on lines 23 and 24. We probably don't really need to do this here, it's only when you have multiple shader programs that you'll need to fuss around with which ID's are in use.
Compile this and run it and the results should look something like this:
Finally, back to displaying colorful triangles. I doubt I'm done with these triangles just yet though. I haven't loaded textures yet nor have I made anything move on screen. I also look at this mess of example code and cringe a little. There's a whole lot of stuff that needs cleaned up or converted into functions to make it easier to use and read. I've got a list of things I want to putter with, it's just a question of which one to do next.
The complete code for this post can be found here.
No comments:
Post a Comment