CSharpGL(1)從最簡單的例子開始使用CSharpGL
2016-08-13
由於CSharpGL一直在更新,現在這個教程已經不適用最新的代碼了。CSharpGL源碼中包含20多個獨立的Demo,更適合入門參考。
為了盡可能提升渲染效率,CSharpGL是面向Shader的,因此稍有難度。
主要內容
在VS2013中使用設計好的控件GLCanvas。
借助GLCanvas,用legacy OpenGL繪制一個四面體。
借助GLCanvas,用modern OpenGL繪制一個四面體。
下載
您可以在(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繪圖。
用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就可以大幅提升渲染效率。
用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 }
設置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 }
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 }
另外,添加一個輔助類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 }
實現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 }
用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 }
關於這里的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並沒有差別。
對比
可以看到,用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。
CSharpGL.vsix
我制作了一個CSharpGL.vsix插件,安裝后可以使用模板項目來體會CSharpGL的用法。
詳情在此(http://www.cnblogs.com/bitzhuwei/p/install-and-use-CSharpGL-vsix.html)。
總結
本篇分別用legacy OpenGL和modern OpenGL實現了一個渲染四面體的例子。例子雖簡單,但是包含了OpenGL渲染的整個編碼過程。今后我們的工作都是基於這個基本流程進行的,只不過在各個方面進行強化,增加新的功能。