阅读目录(Content) CSharpGL(1)从最简单的例子开始使用CSharpGL 主要内容在VS2013中使用设计好的控件GLCanvas。 借助GLCanvas,用legacy OpenGL绘制一个四面体。 借助GLCanvas,用modern OpenGL绘制一个四面体。 下载您可以在(https://github.com/bitzhuwei/CSharpGL)找到最新的源码。欢迎感兴趣的同学fork之。 使用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 shaderModern 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)
Shader
ShaderProgram类用于加载ShaderProgram。
ShaderProgram
另外,添加一个辅助类ShaderCompilationException。
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 }
用VAO/VBO设置四面体模型四面体模型的数据还是legacy OpenGL里的数据,但是不再用glVertex设置,而是用VAO/VBO来指定。
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并没有差别。
对比可以看到,用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。 总结本篇分别用legacy OpenGL和modern OpenGL实现了一个渲染四面体的例子。例子虽简单,但是包含了OpenGL渲染的整个编码过程。今后我们的工作都是基于这个基本流程进行的,只不过在各个方面进行强化,增加新的功能。 |
|
来自: 昵称10504424 > 《工作》