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

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渲染的整个编码过程。今后我们的工作都是基于这个基本流程进行的,只不过在各个方面进行强化,增加新的功能。

原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-1-start-from-a-simple-demo-with-legacy-modern-opengl.html