CSharpGL(1)从最简单的例子开始使用CSharpGL

2016-08-13

由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了。CSharpGL源码中包含20多个独立的Demo,更适合入门参考。

为了尽可能提升渲染效率,CSharpGL是面向Shader的,因此稍有难度。

主要内容

在VS2013中使用设计好的控件GLCanvas。

借助GLCanvas,用legacy OpenGL绘制一个四面体。

借助GLCanvas,用modern OpenGL绘制一个四面体。

+BIT祝威+悄悄在此留下版了个权的信说:

下载

您可以在(https://github.com/bitzhuwei/CSharpGL)找到最新的源码。欢迎感兴趣的同学fork之。

如果您不会用GitHub,可以点此(https://github.com/bitzhuwei/CSharpGL/archive/master.zip)下载zip包。

使用GLCanvas

打开CSharpGL

万事开头难,你在下载打开CSharpGL后,应该能看到下图所示的解决方案。打开CSharpGL.Winforms.Demo项目下的FormPyramidVAOElement,会看到一个窗口里的四面体在慢慢旋转。这就是用OpenGL绘制的。

新建Winform项目

为了演示全部过程,我们新建一个项目"HelloCSharpGL"。

刚刚新建的项目如下图所示。

添加引用

我们需要添加对CSharpGL各个类库的引用,如下图所示。

如下图所示,添加这么几个类库:

Utilities:含有一些辅助类型。

CSharpGL:封装了OpenGL指令。

CSharpGL.Maths:封装了对矩阵和向量的操作。

CSharpGL.Objects:含有Camera、RenderContext、Shader、SceneElement、Picking、UI等类型。

CSharpGL.Winforms:含有GLCanvas控件。

这几个库都是必须的。

使用GLCanvas控件

此时,打开"工具箱",就会看到GLCanvas控件。

把GLCanvas控件拖拽到Form1窗体上,并设置其Anchor属性。

下面,我们先编译一下。

编译成功之后,关闭Form1,然后再次打开Form1,你会看到本篇最开头所示的GLCanvas控件中出现一个旋转的四面体。

注意,这只是在设计阶段的效果,在运行时并不会显示任何内容。不信的话,现在我们把HelloCSharpGL项目设为启动项。

然后,点击"启动",我们来看看启动后的程序是什么效果。

你会看到一个漆黑的窗口。此时GLCanvas并没有绘制任何内容。

这样,GLCanvas就成功添加到窗口中了。

下面我们分别说明如何用legacy OpenGL和modern OpenGL绘图。

+BIT祝威+悄悄在此留下版了个权的信说:

用legacy OpenGL绘制一个四面体

添加代码:绘制四面体

继续上文所述,双击Form1中的GLCanvas控件,进入"OpenGLDraw"事件代码。编写下面所述的代码。

 1         private void glCanvas1_OpenGLDraw(object sender, PaintEventArgs e)
2 {
3 // Clear the color and depth buffer.
4 GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
5
6 DrawPyramid();
7 }
8
9 public static void DrawPyramid()
10 {
11 // Load the identity matrix.
12 GL.LoadIdentity();
13
14 // Rotate around the Y axis.
15 GL.Rotate(rotation, 0.0f, 1.0f, 0.0f);
16
17 // Draw a coloured pyramid.
18 GL.Begin(GL.GL_TRIANGLES);
19 GL.Color(1.0f, 0.0f, 0.0f);
20 GL.Vertex(0.0f, 1.0f, 0.0f);
21 GL.Color(0.0f, 1.0f, 0.0f);
22 GL.Vertex(-1.0f, -1.0f, 1.0f);
23 GL.Color(0.0f, 0.0f, 1.0f);
24 GL.Vertex(1.0f, -1.0f, 1.0f);
25 GL.Color(1.0f, 0.0f, 0.0f);
26 GL.Vertex(0.0f, 1.0f, 0.0f);
27 GL.Color(0.0f, 0.0f, 1.0f);
28 GL.Vertex(1.0f, -1.0f, 1.0f);
29 GL.Color(0.0f, 1.0f, 0.0f);
30 GL.Vertex(1.0f, -1.0f, -1.0f);
31 GL.Color(1.0f, 0.0f, 0.0f);
32 GL.Vertex(0.0f, 1.0f, 0.0f);
33 GL.Color(0.0f, 1.0f, 0.0f);
34 GL.Vertex(1.0f, -1.0f, -1.0f);
35 GL.Color(0.0f, 0.0f, 1.0f);
36 GL.Vertex(-1.0f, -1.0f, -1.0f);
37 GL.Color(1.0f, 0.0f, 0.0f);
38 GL.Vertex(0.0f, 1.0f, 0.0f);
39 GL.Color(0.0f, 0.0f, 1.0f);
40 GL.Vertex(-1.0f, -1.0f, -1.0f);
41 GL.Color(0.0f, 1.0f, 0.0f);
42 GL.Vertex(-1.0f, -1.0f, 1.0f);
43 GL.End();
44
45 rotation += 3.0f;
46 }
47
48 private double rotation;

查看效果

我们已经添加了用legacy OpenGL绘制四面体的代码,现在就编译运行起来看看效果。

可以看到,确实绘制出了一个四面体。不过它距离窗口太近了,以至于一部分内容被削去了。下面我们把它放到合适的位置去。更准确地说,是把Camera移动到合适的位置去。

添加代码:设定投影矩阵和视图矩阵

为GLCanvas控件的Resize事件添加代码。

在GLCanvas调整大小时,就会自动调用Resize事件,所以在这里调整投影矩阵和视图矩阵是最合适的。

视图矩阵指定了Camera的位置、朝向和上方。投影矩阵指定了Camera的透视模式和拍摄范围。

 1         private void glCanvas1_Resize(object sender, EventArgs e)
2 {
3 ResizeGL(glCanvas1.Width, glCanvas1.Height);
4 }
5 void ResizeGL(double width, double height)
6 {
7 // Set the projection matrix.
8 GL.MatrixMode(GL.GL_PROJECTION);
9
10 // Load the identity.
11 GL.LoadIdentity();
12
13 // Create a perspective transformation.
14 GL.gluPerspective(60.0f, width / height, 0.01, 100.0);
15
16 // Use the 'look at' helper function to position and aim the camera.
17 GL.gluLookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0);
18
19 // Set the modelview matrix.
20 GL.MatrixMode(GL.GL_MODELVIEW);
21 }

查看效果

现在再次编译运行,可以看到效果如下。

Legacy OpenGL绘制四面体到此就成功完成了。可以看到这是十分简单的,拖拽一个GLCanvas控件,在"OpenGLDraw"事件里绘制模型,在"Resize"事件里调整Camera。就这么点事。

Legacy OpenGL的缺点是,当模型的顶点很多时,需要频繁调用glVertex(还有glColor、glTexCoord等),这样的执行效率是很低的。下面要讲的modern OpenGL就可以大幅提升渲染效率。

+BIT祝威+悄悄在此留下版了个权的信说:

用modern OpenGL绘制一个四面体

用modern OpenGL需要准备的东西比较多,我们一个一个来。

准备一个窗体

我们新建一个窗体来演示modern OpenGL的写法。

新建的窗体名就叫做"FormModernOpenGL"。

然后也拖拽一个GLCanvas给FormModernOpenGL。也设置好Anchor属性。

准备PyramidDemo

我们添加一个PyramidDemo类,用于加载shader、四面体模型和渲染操作。

我们暂时先不实现PyramidDemo,就让它占个坑位。

准备vertex shader

Modern OpenGL需要用GLSL编写的shader进行渲染。其中必不可少的是vertex shader和fragment shader。现在来准备vertex shader。

shader本质是一个供GPU使用的源代码,所以用"文本文件"即可。Vertex shader命名为"PyramidDemo.vert"。

PyramidDemo.vert内容如下:

 1 #version 150 core
2
3 in vec3 in_Position;
4 in vec3 in_Color;
5 out vec4 pass_Color;
6
7 uniform mat4 MVP;
8
9 void main(void)
10 {
11 gl_Position = MVP * vec4(in_Position, 1.0);// setup vertex's position
12
13 pass_Color = vec4(in_Color, 1.0);// pass color to fragment shader
14 }

注意:shader里即使是注释也不能有中文字符,否则会出现编译错误。也许以后的OpenGL版本会改善这一点。

准备fragment shader

同理准备fragment shader。

Fragment shader内容如下:

1 #version 150 core
2
3 in vec4 pass_Color;
4 out vec4 out_Color;// any name for 'out_Color' is OK, but make sure it's a 'out vec4'
5
6 void main(void)
7 {
8 out_Color = pass_Color;// setup color for this fragment
9 }
+BIT祝威+悄悄在此留下版了个权的信说:

设置shader文件属性

为了使用shader文件,我们需要设置一下shader文件的属性。

设置"复制到输出目录"属性为"如果较新则复制"。

Shader类和ShaderProgram类

有了shader的源代码,现在我们来加载shader。这就需要添加一个Shader类和一个ShaderProgram类。

Shader类用于加载一个Shader(vertex shader或fragment shader)

 1     /// <summary>
2 /// This is the base class for all shaders (vertex and fragment). It offers functionality
3 /// which is core to all shaders, such as file loading and binding.
4 /// </summary>
5 public class Shader
6 {
7 public void Create(uint shaderType, string source)
8 {
9 // Create the OpenGL shader object.
10 ShaderObject = GL.CreateShader(shaderType);
11
12 // Set the shader source.
13 GL.ShaderSource(ShaderObject, source);
14
15 // Compile the shader object.
16 GL.CompileShader(ShaderObject);
17
18 // Now that we've compiled the shader, check it's compilation status. If it's not compiled properly, we're
19 // going to throw an exception.
20 if (GetCompileStatus() == false)
21 {
22 string log = GetInfoLog();
23 throw new ShaderCompilationException(string.Format("Failed to compile shader with ID {0}.", ShaderObject), log);
24 }
25 }
26
27 public void Delete()
28 {
29 GL.DeleteShader(ShaderObject);
30 ShaderObject = 0;
31 }
32
33 public bool GetCompileStatus()
34 {
35 int[] parameters = new int[] { 0 };
36 GL.GetShader(ShaderObject, GL.GL_COMPILE_STATUS, parameters);
37 return parameters[0] == GL.GL_TRUE;
38 }
39
40 public string GetInfoLog()
41 {
42 // Get the info log length.
43 int[] infoLength = new int[] { 0 };
44 GL.GetShader(ShaderObject,
45 GL.GL_INFO_LOG_LENGTH, infoLength);
46 int bufSize = infoLength[0];
47
48 // Get the compile info.
49 StringBuilder il = new StringBuilder(bufSize);
50 GL.GetShaderInfoLog(ShaderObject, bufSize, IntPtr.Zero, il);
51
52 string log = il.ToString();
53 return log;
54 }
55
56 /// <summary>
57 /// Gets the shader object.
58 /// </summary>
59 public uint ShaderObject { get; protected set; }
60 }

Shader

ShaderProgram类用于加载ShaderProgram。

  1     public class ShaderProgram
2 {
3 private readonly Shader vertexShader = new Shader();
4 private readonly Shader fragmentShader = new Shader();
5
6 /// <summary>
7 /// Creates the shader program.
8 /// </summary>
9 /// <param name="vertexShaderSource">The vertex shader source.</param>
10 /// <param name="fragmentShaderSource">The fragment shader source.</param>
11 /// <param name="attributeLocations">The attribute locations. This is an optional array of
12 /// uint attribute locations to their names.</param>
13 /// <exception cref="ShaderCompilationException"></exception>
14 public void Create(string vertexShaderSource, string fragmentShaderSource,
15 Dictionary<uint, string> attributeLocations)
16 {
17 // Create the shaders.
18 vertexShader.Create(GL.GL_VERTEX_SHADER, vertexShaderSource);
19 fragmentShader.Create(GL.GL_FRAGMENT_SHADER, fragmentShaderSource);
20
21 // Create the program, attach the shaders.
22 ShaderProgramObject = GL.CreateProgram();
23 GL.AttachShader(ShaderProgramObject, vertexShader.ShaderObject);
24 GL.AttachShader(ShaderProgramObject, fragmentShader.ShaderObject);
25
26 // Before we link, bind any vertex attribute locations.
27 if (attributeLocations != null)
28 {
29 foreach (var vertexAttributeLocation in attributeLocations)
30 GL.BindAttribLocation(ShaderProgramObject, vertexAttributeLocation.Key, vertexAttributeLocation.Value);
31 }
32
33 // Now we can link the program.
34 GL.LinkProgram(ShaderProgramObject);
35
36 // Now that we've compiled and linked the shader, check it's link status. If it's not linked properly, we're
37 // going to throw an exception.
38 if (GetLinkStatus() == false)
39 {
40 throw new ShaderCompilationException(string.Format("Failed to link shader program with ID {0}.", ShaderProgramObject), GetInfoLog());
41 }
42 }
43
44 public void Delete()
45 {
46 GL.DetachShader(ShaderProgramObject, vertexShader.ShaderObject);
47 GL.DetachShader(ShaderProgramObject, fragmentShader.ShaderObject);
48 vertexShader.Delete();
49 fragmentShader.Delete();
50 GL.DeleteProgram(ShaderProgramObject);
51 ShaderProgramObject = 0;
52 }
53
54 public uint GetAttributeLocation(string attributeName)
55 {
56 // If we don't have the attribute name in the dictionary, get it's
57 // location and add it.
58 if (attributeNamesToLocations.ContainsKey(attributeName) == false)
59 {
60 int location = GL.GetAttribLocation(ShaderProgramObject, attributeName);
61 if (location < 0) { throw new Exception(); }
62
63 attributeNamesToLocations[attributeName] = (uint)location;
64 }
65
66 // Return the attribute location.
67 return attributeNamesToLocations[attributeName];
68 }
69
70 public void BindAttributeLocation(uint location, string attribute)
71 {
72 GL.BindAttribLocation(ShaderProgramObject, location, attribute);
73 }
74
75 public void Bind()
76 {
77 GL.UseProgram(ShaderProgramObject);
78 }
79
80 public void Unbind()
81 {
82 GL.UseProgram(0);
83 }
84
85 public bool GetLinkStatus()
86 {
87 int[] parameters = new int[] { 0 };
88 GL.GetProgram(ShaderProgramObject, GL.GL_LINK_STATUS, parameters);
89 return parameters[0] == GL.GL_TRUE;
90 }
91
92 public string GetInfoLog()
93 {
94 // Get the info log length.
95 int[] infoLength = new int[] { 0 };
96 GL.GetProgram(ShaderProgramObject, GL.GL_INFO_LOG_LENGTH, infoLength);
97 int bufSize = infoLength[0];
98
99 // Get the compile info.
100 StringBuilder il = new StringBuilder(bufSize);
101 GL.GetProgramInfoLog(ShaderProgramObject, bufSize, IntPtr.Zero, il);
102
103 string log = il.ToString();
104 return log;
105 }
106
107 public void AssertValid()
108 {
109 if (vertexShader.GetCompileStatus() == false)
110 {
111 string log = vertexShader.GetInfoLog();
112 throw new Exception(log);
113 }
114 if (fragmentShader.GetCompileStatus() == false)
115 {
116 string log = fragmentShader.GetInfoLog();
117 throw new Exception(log);
118 }
119 if (GetLinkStatus() == false)
120 {
121 string log = GetInfoLog();
122 throw new Exception(log);
123 }
124 }
125
126 /// <summary>
127 /// 请注意你的数据类型最终将转换为int还是float
128 /// </summary>
129 /// <param name="uniformName"></param>
130 /// <param name="v1"></param>
131 public void SetUniform(string uniformName, int v1)
132 {
133 GL.Uniform1(GetUniformLocation(uniformName), v1);
134 }
135
136 /// <summary>
137 /// 请注意你的数据类型最终将转换为int还是float
138 /// </summary>
139 /// <param name="uniformName"></param>
140 /// <param name="v1"></param>
141 /// <param name="v2"></param>
142 public void SetUniform(string uniformName, int v1, int v2)
143 {
144 GL.Uniform2(GetUniformLocation(uniformName), v1, v2);
145 }
146
147 /// <summary>
148 /// 请注意你的数据类型最终将转换为int还是float
149 /// </summary>
150 /// <param name="uniformName"></param>
151 /// <param name="v1"></param>
152 /// <param name="v2"></param>
153 /// <param name="v3"></param>
154 public void SetUniform(string uniformName, int v1, int v2, int v3)
155 {
156 GL.Uniform3(GetUniformLocation(uniformName), v1, v2, v3);
157 }
158
159 /// <summary>
160 /// 请注意你的数据类型最终将转换为int还是float
161 /// </summary>
162 /// <param name="uniformName"></param>
163 /// <param name="v1"></param>
164 /// <param name="v2"></param>
165 /// <param name="v3"></param>
166 /// <param name="v4"></param>
167 public void SetUniform(string uniformName, int v1, int v2, int v3, int v4)
168 {
169 GL.Uniform4(GetUniformLocation(uniformName), v1, v2, v3, v4);
170 }
171
172 /// <summary>
173 /// 请注意你的数据类型最终将转换为int还是float
174 /// </summary>
175 /// <param name="uniformName"></param>
176 /// <param name="v1"></param>
177 public void SetUniform(string uniformName, float v1)
178 {
179 GL.Uniform1(GetUniformLocation(uniformName), v1);
180 }
181
182 /// <summary>
183 /// 请注意你的数据类型最终将转换为int还是float
184 /// </summary>
185 /// <param name="uniformName"></param>
186 /// <param name="v1"></param>
187 /// <param name="v2"></param>
188 public void SetUniform(string uniformName, float v1, float v2)
189 {
190 GL.Uniform2(GetUniformLocation(uniformName), v1, v2);
191 }
192
193 /// <summary>
194 /// 请注意你的数据类型最终将转换为int还是float
195 /// </summary>
196 /// <param name="uniformName"></param>
197 /// <param name="v1"></param>
198 /// <param name="v2"></param>
199 /// <param name="v3"></param>
200 public void SetUniform(string uniformName, float v1, float v2, float v3)
201 {
202 GL.Uniform3(GetUniformLocation(uniformName), v1, v2, v3);
203 }
204
205 /// <summary>
206 /// 请注意你的数据类型最终将转换为int还是float
207 /// </summary>
208 /// <param name="uniformName"></param>
209 /// <param name="v1"></param>
210 /// <param name="v2"></param>
211 /// <param name="v3"></param>
212 /// <param name="v4"></param>
213 public void SetUniform(string uniformName, float v1, float v2, float v3, float v4)
214 {
215 GL.Uniform4(GetUniformLocation(uniformName), v1, v2, v3, v4);
216 }
217
218 /// <summary>
219 /// 请注意你的数据类型最终将转换为int还是float
220 /// </summary>
221 /// <param name="uniformName"></param>
222 /// <param name="m"></param>
223 public void SetUniformMatrix3(string uniformName, float[] m)
224 {
225 GL.UniformMatrix3(GetUniformLocation(uniformName), 1, false, m);
226 }
227
228 /// <summary>
229 /// 请注意你的数据类型最终将转换为int还是float
230 /// </summary>
231 /// <param name="uniformName"></param>
232 /// <param name="m"></param>
233 public void SetUniformMatrix4(string uniformName, float[] m)
234 {
235 GL.UniformMatrix4(GetUniformLocation(uniformName), 1, false, m);
236 }
237
238 public int GetUniformLocation(string uniformName)
239 {
240 // If we don't have the uniform name in the dictionary, get it's
241 // location and add it.
242 if (uniformNamesToLocations.ContainsKey(uniformName) == false)
243 {
244 uniformNamesToLocations[uniformName] = GL.GetUniformLocation(ShaderProgramObject, uniformName);
245 // TODO: if it's not found, we should probably throw an exception.
246 }
247
248 // Return the uniform location.
249 return uniformNamesToLocations[uniformName];
250 }
251
252 /// <summary>
253 /// Gets the shader program object.
254 /// </summary>
255 /// <value>
256 /// The shader program object.
257 /// </value>
258 public uint ShaderProgramObject { get; protected set; }
259
260
261 /// <summary>
262 /// A mapping of uniform names to locations. This allows us to very easily specify
263 /// uniform data by name, quickly looking up the location first if needed.
264 /// </summary>
265 private readonly Dictionary<string, int> uniformNamesToLocations = new Dictionary<string, int>();
266
267 /// <summary>
268 /// A mapping of attribute names to locations. This allows us to very easily specify
269 /// attribute data by name, quickly looking up the location first if needed.
270 /// </summary>
271 private readonly Dictionary<string, uint> attributeNamesToLocations = new Dictionary<string, uint>();
272 }

ShaderProgram

另外,添加一个辅助类ShaderCompilationException。

 1     [Serializable]
2 public class ShaderCompilationException : Exception
3 {
4 private readonly string compilerOutput;
5
6 public ShaderCompilationException(string compilerOutput)
7 {
8 this.compilerOutput = compilerOutput;
9 }
10 public ShaderCompilationException(string message, string compilerOutput)
11 : base(message)
12 {
13 this.compilerOutput = compilerOutput;
14 }
15 public ShaderCompilationException(string message, Exception inner, string compilerOutput)
16 : base(message, inner)
17 {
18 this.compilerOutput = compilerOutput;
19 }
20 protected ShaderCompilationException(
21 System.Runtime.Serialization.SerializationInfo info,
22 System.Runtime.Serialization.StreamingContext context)
23 : base(info, context) { }
24
25 public string CompilerOutput { get { return compilerOutput; } }
26 }

ShaderCompilationException

实现PyramidDemo

加载shader,设置shader program

在PyramidDemo里实现。

 1         private ShaderProgram shaderProgram;
2
3 public void Initilize()
4 {
5 InitShaderProgram();
6 }
7
8 private void InitShaderProgram()
9 {
10 var vertexShaderSource = File.ReadAllText(@"PyramidDemo.vert");
11 var fragmentShaderSource = File.ReadAllText(@"PyramidDemo.frag");
12
13 this.shaderProgram = new ShaderProgram();
14
15 this.shaderProgram.Create(vertexShaderSource, fragmentShaderSource, null);
16 this.shaderProgram.AssertValid();
17
18 }
+BIT祝威+悄悄在此留下版了个权的信说:

用VAO/VBO设置四面体模型

四面体模型的数据还是legacy OpenGL里的数据,但是不再用glVertex设置,而是用VAO/VBO来指定。

 1         const int vertexCount = 12;
2 private uint[] vertexArrayObject;
3
4 public void Initilize()
5 {
6 InitShaderProgram();
7
8 InitVAO();
9 }
10
11 private void InitVAO()
12 {
13 // reserve a vertex array object(VAO) 预约一个VAO
14 this.vertexArrayObject = new uint[1];
15 GL.GenVertexArrays(1, this.vertexArrayObject);
16
17 // prepare vertex buffer object(VBO) for vertexes' positions 为顶点位置准备VBO
18 uint[] positionBufferObject = new uint[1];
19 {
20 // specify position array
21 var positionArray = new UnmanagedArray<vec3>(vertexCount);
22 positionArray[0] = new vec3(0.0f, 1.0f, 0.0f);
23 positionArray[1] = new vec3(-1.0f, -1.0f, 1.0f);
24 positionArray[2] = new vec3(1.0f, -1.0f, 1.0f);
25 positionArray[3] = new vec3(0.0f, 1.0f, 0.0f);
26 positionArray[4] = new vec3(1.0f, -1.0f, 1.0f);
27 positionArray[5] = new vec3(1.0f, -1.0f, -1.0f);
28 positionArray[6] = new vec3(0.0f, 1.0f, 0.0f);
29 positionArray[7] = new vec3(1.0f, -1.0f, -1.0f);
30 positionArray[8] = new vec3(-1.0f, -1.0f, -1.0f);
31 positionArray[9] = new vec3(0.0f, 1.0f, 0.0f);
32 positionArray[10] = new vec3(-1.0f, -1.0f, -1.0f);
33 positionArray[11] = new vec3(-1.0f, -1.0f, 1.0f);
34
35 // put positions into VBO
36 GL.GenBuffers(1, positionBufferObject);
37 GL.BindBuffer(BufferTarget.ArrayBuffer, positionBufferObject[0]);
38 GL.BufferData(BufferTarget.ArrayBuffer, positionArray, BufferUsage.StaticDraw);
39
40 positionArray.Dispose();
41 }
42
43 // prepare vertex buffer object(VBO) for vertexes' colors
44 uint[] colorBufferObject = new uint[1];
45 {
46 // specify color array
47 UnmanagedArray<vec3> colorArray = new UnmanagedArray<vec3>(vertexCount);
48 colorArray[0] = new vec3(1.0f, 0.0f, 0.0f);
49 colorArray[1] = new vec3(0.0f, 1.0f, 0.0f);
50 colorArray[2] = new vec3(0.0f, 0.0f, 1.0f);
51 colorArray[3] = new vec3(1.0f, 0.0f, 0.0f);
52 colorArray[4] = new vec3(0.0f, 0.0f, 1.0f);
53 colorArray[5] = new vec3(0.0f, 1.0f, 0.0f);
54 colorArray[6] = new vec3(1.0f, 0.0f, 0.0f);
55 colorArray[7] = new vec3(0.0f, 1.0f, 0.0f);
56 colorArray[8] = new vec3(0.0f, 0.0f, 1.0f);
57 colorArray[9] = new vec3(1.0f, 0.0f, 0.0f);
58 colorArray[10] = new vec3(0.0f, 0.0f, 1.0f);
59 colorArray[11] = new vec3(0.0f, 1.0f, 0.0f);
60
61 // put colors into VBO
62 GL.GenBuffers(1, colorBufferObject);
63 GL.BindBuffer(BufferTarget.ArrayBuffer, colorBufferObject[0]);
64 GL.BufferData(BufferTarget.ArrayBuffer, colorArray, BufferUsage.StaticDraw);
65
66 colorArray.Dispose();
67 }
68
69 uint positionLocation = shaderProgram.GetAttributeLocation("in_Position");
70 uint colorLocation = shaderProgram.GetAttributeLocation("in_Color");
71
72 {
73 // bind the vertex array object(VAO), we are going to specify data for it.
74 GL.BindVertexArray(vertexArrayObject[0]);
75
76 // specify vertexes' positions
77 GL.BindBuffer(BufferTarget.ArrayBuffer, positionBufferObject[0]);
78 GL.VertexAttribPointer(positionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
79 GL.EnableVertexAttribArray(positionLocation);
80
81 // specify vertexes' colors
82 GL.BindBuffer(BufferTarget.ArrayBuffer, colorBufferObject[0]);
83 GL.VertexAttribPointer(colorLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
84 GL.EnableVertexAttribArray(colorLocation);
85
86 // Unbind the vertex array object(VAO), we've finished specifying data for it.
87 GL.BindVertexArray(0);
88 }
89 }

InitVAO

关于这里的UnmanagedArray<vec3>类型,您可以在这里找到详细介绍(C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword))。

用VAO进行渲染

现在shader已加载,VAO/VBO已准备好了模型数据(位置和颜色),就差渲染这一步了。

        public void Render()
{
mat4 mvp;
{
// model rotates
mat4 modelMatrix = glm.rotate(rotation, new vec3(0, 1, 0)); // same as gluLookAt()
mat4 viewMatrix = glm.lookAt(new vec3(-5, 5, -5), new vec3(0, 0, 0), new vec3(0, 1, 0)); // same as gluPerspective()
int[] viewport = new int[4];
GL.GetInteger(GetTarget.Viewport, viewport);
float width = viewport[2];
float height = viewport[3];
mat4 projectionMatrix = glm.perspective((float)(60.0f * Math.PI / 180.0f), width / height, 0.01f, 100.0f); // get MVP in "uniform mat4 MVP;" in the vertex shader
mvp = projectionMatrix * viewMatrix * modelMatrix;
} // bind the shader program to setup uniforms
this.shaderProgram.Bind();
// setup MVP
this.shaderProgram.SetUniformMatrix4("MVP", mvp.to_array());
{
// bind vertex array object(VAO)
GL.BindVertexArray(this.vertexArrayObject[0]);
// draw the model: in GL_TRIANGLES mode, there are 'vertexCount' vertexes
GL.DrawArrays(GL.GL_TRIANGLES, 0, vertexCount);
// unbind vertex array object(VAO)
GL.BindVertexArray(0);
}
// unbind the shader program
this.shaderProgram.Unbind(); rotation += 3.0f;
} private float rotation;

查看效果

Modern OpenGL渲染的效果与legacy OpenGL并没有差别。

+BIT祝威+悄悄在此留下版了个权的信说:

对比

可以看到,用legacy OpenGL的步骤相对modern OpenGL而言是十分简单的。而想用modern OpenGL渲染时,我们编写了Shader、ShaderProgram、mat4、vec3、UnmanagedArray<T>等大量的类型,到此才完成了modern OpenGL的一个简单示例的代码。这其中任何一个步骤出一点错误都可能导致最终无法正常渲染,且调试难度很大。OpenGL难学大概就在这里了。

但是legacy OpenGL在渲染时调用的OpenGL指令比modern OpenGL多得多。另外,modern OpenGL用VAO/VBO会把模型数据上传到显卡内存,这也大大加速的渲染过程。再者,modern OpenGL用shader代替了固定管线,shader比固定管线灵活得多,用惯了会觉得既好用又强大。所以我们还是要坚持学用modern OpenGL。

+BIT祝威+悄悄在此留下版了个权的信说:

CSharpGL.vsix

我制作了一个CSharpGL.vsix插件,安装后可以使用模板项目来体会CSharpGL的用法。

详情在此(http://www.cnblogs.com/bitzhuwei/p/install-and-use-CSharpGL-vsix.html)。

总结

本篇分别用legacy OpenGL和modern OpenGL实现了一个渲染四面体的例子。例子虽简单,但是包含了OpenGL渲染的整个编码过程。今后我们的工作都是基于这个基本流程进行的,只不过在各个方面进行强化,增加新的功能。

CSharpGL(1)从最简单的例子开始使用CSharpGL的更多相关文章

  1. 简单的例子了解自定义ViewGroup(一)

    在Android中,控件可以分为ViewGroup控件与View控件.自定义View控件,我之前的文章已经说过.这次我们主要说一下自定义ViewGroup控件.ViewGroup是作为父控件可以包含多 ...

  2. 用一个简单的例子来理解python高阶函数

    ============================ 用一个简单的例子来理解python高阶函数 ============================ 最近在用mailx发送邮件, 写法大致如 ...

  3. Spring-Context之一:一个简单的例子

    很久之前就想系统的学习和掌握Spring框架,但是拖了很久都没有行动.现在趁着在外出差杂事不多,就花时间来由浅入深的研究下Spring框架.Spring框架这几年来已经发展成为一个巨无霸产品.从最初的 ...

  4. C#调用存储过程简单完整例子

    CREATE PROC P_TEST@Name VARCHAR(20),@Rowcount INT OUTPUTASBEGIN SELECT * FROM T_Customer WHERE NAME= ...

  5. 关于apriori算法的一个简单的例子

    apriori算法是关联规则挖掘中很基础也很经典的一个算法,我认为很多教程出现大堆的公式不是很适合一个初学者理解.因此,本文列举一个简单的例子来演示下apriori算法的整个步骤. 下面这个表格是代表 ...

  6. 为什么C语言在2013年仍然很重要:一个简单的例子

    附注:在最初的文章里,我没说明进行模2^64的计算——我当然明白那些不是“正确的”斐波那契数列,其实我不是想分析大数,我只是想探寻编译器产生的代码和计算机体系结构而已. 最近,我一直在开发Dynvm— ...

  7. Singleton模式(Singleton创建类型)c#简单的例子

    单(Singleton创建模式)c#简单的例子 当需要生成一个实例,可单发模式 样品可以在短短的球员中产生,玩家和测试.单线程例子,如以下: namespace singletonpattern { ...

  8. 修饰模式(Decorator结构化)C#简单的例子

    修饰模式(Decorator结构化)C#简单的例子 播放器的基本功能是移动.执行等.BaseAbility 新增功能:1.伤害技能harmAbility:2.阻碍技能BaulkAbility:3.辅助 ...

  9. 观察者模式(observer行为)c#简单的例子

    观察者模式(observer行为)c#简单的例子 几点:模式使观察目标和实现松耦合之间的依赖关系.通知会传播自己主动 样本:玩家击中后发生一系列变化的敌人:后发爆炸.敌人少1一个.... namesp ...

随机推荐

  1. C# ORM中Dto Linq Expression 和 数据库Model Linq Expression之间的转换

    今天在百度知道中看到一个问题,研究了一会便回答了: http://zhidao.baidu.com/question/920461189016484459.html 如何使dto linq 表达式转换 ...

  2. 改变按钮在iPhone下的默认风格

    -webkit-appearance: none; "来改变按钮在iPhone下的默认风格,其实我们可以反过来思路,使用"appearance"属性,来改变任何元素的浏览 ...

  3. 如何使用IconFont字体图标代替网页图片?

    一.IconFont的优点 1.轻量性 可以减少http请求,可以配合html5离线存储做性能优化,有利于后期维护. 2.灵活性 可以自由变换IconFont大小(不失真),可以修改IconFont颜 ...

  4. POJ 2774 Long Long Message ——后缀数组

    [题目分析] 用height数组RMQ的性质去求最长的公共子串. 要求sa[i]和sa[i-1]必须在两个串中,然后取height的MAX. 利用中间的字符来连接两个字符串的思想很巧妙,记得最后还需要 ...

  5. 【刷题笔记】--lintcode木头加工(java)

    木头加工 题目描述 有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k.当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度. 注意事项 木头 ...

  6. java.lang.OutOfMemoryError: PermGen space错误解决方法

    1. MyEclipse 中报 PermGen space       window--> preferences-->Myclipse-->Servers-->Tomcat- ...

  7. 关于handler 和 looper 的问题

    重新去学习回顾looper和handler ,还是需要重新认识这个经常使用的机制. 我首先是看任玉刚老师的书<android的开发艺术探索>的第十章. 里面一句话开始说出了我们大概的理解— ...

  8. HTC辟谣: HTC Vive2不会在CES 2017上公布

    HTC官方:第二代Vive不会在CES 2017上发布.曾有消息称HTC Vive的下一代--Vive 2将在CES 2017上展出能成为首个4K头显以及无线头显.但日前HTC官方给于否认,此消息不实 ...

  9. CGContextRef 画线简单用法

    CGContextRef CGContextMoveToPoint(context,150,50);//圆弧的起始点 CGContextAddArcToPoint(context,100,80,130 ...

  10. Flask_more1

    #DB ``` import os basedir = os.path.abspath(os.path.dirname(__file__))   SQLALCHEMY_DATABASE_URI = ' ...