CSharpGL(2)设计和使用场景元素及常用接口

2016-08-13

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

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

主要内容

描述在OpenGL中绘制场景的思路。

设计场景元素的抽象基类SceneELementBase。

以PyramidElement为例演示SceneELementBase的用法。

下载

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

在OpenGL中绘制场景的思路

规范用词

首先我们明确2个关键的用词。

场景

场景是指用OpenGL渲染出来的所有东西,包括每个模型和背景。

元素

元素是指场景中的一个模型。

所有元素的基类

上一篇里我们已经体验了用legacy OpenGL和modern OpenGL渲染3D场景的过程。这一过程对任意简单的或复杂的场景都适用。我们将任意场景的渲染过程抽象出共性来,就是"初始化"和"渲染"这两点。也就是说,一个在场景中进行渲染的元素,必须具有"初始化"和"渲染"这两个功能。这就找到了场景元素的基类。

 
 1     /// <summary>
2 /// 用OPENGL初始化和渲染一个元素。
3 /// </summary>
4 public abstract class SceneElementBase
5 {
6 protected bool initialized = false;
7
8 /// <summary>
9 /// 初始化此Element
10 /// </summary>
11 public void Initialize()
12 {
13 if (!initialized)
14 {
15 DoInitialize();
16
17 initialized = true;
18 }
19 }
20
21 /// <summary>
22 /// 初始化此Element
23 /// </summary>
24 protected abstract void DoInitialize();
25
26 /// <summary>
27 /// 渲染
28 /// </summary>
29 /// <param name="renderMode"></param>
30 public abstract void Render(RenderEventArgs e);
31 }

这个抽象基类告诉我们,任何一个场景中的元素,必须实现"初始化"和"渲染"这两个方法,即必须知道如何初始化自己的数据,如何渲染自己。

常用接口

有了上面的SceneElementBase,整个渲染的蓝图就定型了。下一个问题是,modern OpenGL需要加载很多东西,不同的元素要实现的功能的多少、种类也各不相同,这如何实现?方法是:为每项功能设计相应的接口,让具有此功能的元素继承此接口。下面是几个常用的功能接口的例子。

IMVP

定义

这是最常用的接口。对于用modern OpenGL方式渲染的元素,这是一个必选项。(当然,不实现此接口也可以,但是本质上仍然是实现了此接口的功能)

 
 1     /// <summary>
2 /// 通过此接口设置元素的MVP矩阵
3 /// </summary>
4 public interface IMVP
5 {
6 /// <summary>
7 /// 更新此元素的MVP值。
8 /// </summary>
9 /// <param name="mvp">三个矩阵的乘积(Projection * View * Model)</param>
10 void SetShaderProgram(mat4 mvp);
11
12 /// <summary>
13 /// 解绑当前shader program。
14 /// </summary>
15 void ResetShaderProgram();
16
17 /// <summary>
18 ///
19 /// </summary>
20 /// <returns></returns>
21 Shaders.ShaderProgram GetShaderProgram();
22
23 }

MVP是投影矩阵(Projection) * 视图矩阵(View) * 模型矩阵(Model)的简写。由于在很多GLSL的shader里都有"uniform mat4 mvp;"这样的命名方式,所以我将此接口命名为IMVP。

IMVP的用处,是在渲染前设置mvp矩阵。任何一个元素都应该有位置(Position)这个属性(否则就没有可画的东西了),而输入的位置VBO里存储的是模型自身的位置,要想变换到窗口合适的位置上,就必然用到mvp矩阵。所以说这个IMVP接口是任何一个用modern OpenGL方式渲染的元素必须实现的。

如何使用

要调用此接口,就必须与SceneElementBase.Render()配合,在SceneElementBase.Render()渲染之前调用IMVP.SetShaderProgram()。

 1 public override void Render(RenderEventArgs e)
2 {
3 // 绑定shader,设置MVP
4 mat4 projectionMatrix = e.Camera.GetProjectionMat4();
5 mat4 viewMatrix = e.Camera.GetViewMat4();
6 mat4 modelMatrix = mat4.identity();
7 mat4 mvp = projectionMatrix * viewMatrix * modelMatrix;
8 IMVP element = this as IMVP;
9 element.SetShaderProgram(mvp);
10
11 // 此时进行渲染
12 // ...
13
14 // 解绑shader
15 element.ResetShaderProgram();
16 }

你注意到,此时RenderEventArgs参数里需要有一个Camera字段,Camera需要实现获取投影矩阵和视图矩阵的方法GetProjectionMat4()和GetViewMat4()。关于Camera的实现我们以后再详述。

改进

再思考一下这个Render()方法,它有2个问题:

A:设置mvp矩阵的代码写死到元素的Render方法里,灵活性不够。如果以后我希望用别的方式指定mvp值,就必须修改Camera。在此处对Camera的改动就牵涉过多了。

B:如果场景中的元素很多,那么每个元素内部的Render方法都要计算一遍mvp值,这显然是重复计算。更好的做法是:提前计算出mvp值,然后依次喂给每个元素的SetShaderProgram(mvp);方法。

为解决这2个问题,我们对SceneElementBase进行改造,使得元素外部代码可以动态改变指定mvp的方式。

 1     /// <summary>
2 /// 用OPENGL初始化和渲染一个元素。
3 /// </summary>
4 public abstract class SceneElementBase : IRenderable
5 {
6 protected bool initialized = false;
7
8 /// <summary>
9 /// 初始化此Element
10 /// </summary>
11 public void Initialize()
12 {
13 if (!initialized)
14 {
15 DoInitialize();
16
17 initialized = true;
18 }
19 }
20
21 /// <summary>
22 /// 初始化此Element
23 /// </summary>
24 protected abstract void DoInitialize();
25
26 /// <summary>
27 /// 渲染
28 /// </summary>
29 /// <param name="renderMode"></param>
30 public void Render(RenderEventArgs e)
31 {
32 if (!initialized) { Initialize(); }
33
34 EventHandler<RenderEventArgs> beforeRendering = this.BeforeRendering;
35 if (beforeRendering != null)
36 {
37 beforeRendering(this, e);
38 }
39
40 DoRender(e);
41
42 EventHandler<RenderEventArgs> afterRendering = this.AfterRendering;
43 if (afterRendering != null)
44 {
45 afterRendering(this, e);
46 }
47 }
48
49 /// <summary>
50 /// 执行渲染操作
51 /// </summary>
52 /// <param name="renderMode"></param>
53 protected abstract void DoRender(RenderEventArgs e);
54
55 /// <summary>
56 /// 在渲染前进行某些准备(更新camera矩阵信息等)
57 /// </summary>
58 public event EventHandler<RenderEventArgs> BeforeRendering;
59
60 /// <summary>
61 /// 在渲染后进行某些善后(恢复OpenGL状态等)
62 /// </summary>
63 public event EventHandler<RenderEventArgs> AfterRendering;
64
65 }
 

改进后的使用

现在,我们可以在元素外部通过为BeforeRendering和AfterRendering添加自定义事件函数的方式自由指定mvp。

 1 PyramidElement[] elements = new PyramidElement[10];
2 mat4 mvp; //每次渲染场景前被更新
3
4 public InitElements()
5 {
6 for (int i = 0; i < 10; i++)
7 {
8 var element = new PyramidElement();
9 element.Initialize();
10 element.BeforeRendering += element_BeforeRendering;
11 element.AfterRendering += element_AfterRendering;
12
13 this.elements[i] = element;
14 }
15 }
16
17 void element_AfterRendering(object sender, Objects.RenderEventArgs e)
18 {
19 IMVP element = sender as IMVP;
20 element.ResetShaderProgram();
21 }
22
23 void element_BeforeRendering(object sender, Objects.RenderEventArgs e)
24 {
25 IMVP element = sender as IMVP;
26 element.SetShaderProgram(mvp);
27 }
28 void glCanvas1_OpenGLDraw(object sender, PaintEventArgs e)
29 {
30 mat4 modelMatrix = glm.identity();
31 mat4 viewMatrix = this.camera.GetViewMat4();
32 mat4 projectionMatrix = this.camera.GetProjectionMat4();
33 mvp = projectionMatrix * viewMatrix * modelMatrix;
34
35 GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
36
37 var arg = new RenderEventArgs(RenderModes.Render, this.camera);
38 for (int i = 0; i < 10; i++)
39 {
40 this.elements[i].Render(arg);
41 }
42 }
 

这样一来,上面2个问题都解决了。

用Helper实现最常用的IMVP

说了这么多,还没有说明如何实现IMVP。

 
 1 class PyramidElement : SceneElementBase, IMVP
2 {
3 // other stuff
4
5 ShaderProgram shaderProgram;
6
7 void IMVP.SetShaderProgram(mat4 mvp)
8 {
9 IMVPHelper.DoUpdateMVP(this, mvp);
10 }
11
12 void IMVP.ResetShaderProgram()
13 {
14 IMVPHelper.DoUnbindShaderProgram(this);
15 }
16
17 ShaderProgram IMVP.GetShaderProgram()
18 {
19 return this.shaderProgram;
20 }
21 }
22
23 public static class IMVPHelper
24 {
25 /// <summary>
26 /// public static string strMVP = "MVP";
27 /// <para>使用此<see cref="IMVPHelper"/>的<see cref="SceneElement"/>所使用的Vertex Shader必须含有<code>uniform mat4 MVP;</code>并使其作为变换矩阵。</para>
28 /// </summary>
29 public static string strMVP = "MVP";
30
31 /// <summary>
32 /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。
33 /// </summary>
34 /// <param name="element"></param>
35 /// <param name="mvp"></param>
36 public static void DoUpdateMVP(this IMVP element, mat4 mvp)
37 {
38 ShaderProgram shaderProgram = element.GetShaderProgram();
39 shaderProgram.Bind();
40 shaderProgram.SetUniformMatrix4(strMVP, mvp.to_array());
41 }
42
43 /// <summary>
44 /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。
45 /// </summary>
46 /// <param name="element"></param>
47 public static void DoUnbindShaderProgram(this IMVP element)
48 {
49 ShaderProgram shaderProgram = element.GetShaderProgram();
50 shaderProgram.Unbind();
51 }
52 }

IColorCodedPicking

这是为实现在VBO中拾取一个图元而设计的接口。继承此接口的SceneElementBase的子类能够告诉你你用鼠标拾取了哪个图元。

具体使用方法请参考(http://www.cnblogs.com/bitzhuwei/p/modern-opengl-picking-primitive-in-VBO.html)。不再重述。

IUILayout

定义

实现IUILayout接口的元素能够在窗口固定位置显示,类似Winform里的控件那样,设置其长度、宽度,指定其Anchor(绑定到上下左右)。

 
 1     /// <summary>
2 /// 实现在OpenGL窗口中的UI布局
3 /// </summary>
4 public interface IUILayout
5 {
6 IUILayoutParam Param { get; set; }
7
8 }
9 public struct IUILayoutParam
10 {
11 /// <summary>
12 /// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent.
13 /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para>
14 /// </summary>
15 public System.Windows.Forms.AnchorStyles Anchor;
16
17 /// <summary>
18 /// Gets or sets the space between viewport and SimpleRect.
19 /// </summary>
20 public System.Windows.Forms.Padding Margin;
21
22 /// <summary>
23 /// Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left &amp; <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None.
24 /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top &amp; <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para>
25 /// </summary>
26 public System.Drawing.Size Size;
27
28 public int zNear;
29
30 public int zFar;
31
32 public IUILayoutParam(AnchorStyles anchorStyle, Padding padding, System.Drawing.Size size,
33 int zNear = -1000, int zFar = 1000)
34 {
35 this.Anchor = anchorStyle;
36 this.Margin = padding;
37 this.Size = size;
38 this.zNear = zNear;
39 this.zFar = zFar;
40 }
41 }

当然, 仅仅一个接口是不能"实现"这个功能的。还需要一些辅助类型。

如何实现UI布局

最核心的是下面这个能够让元素像UI一样布局的Helper类型。

这个Helper类型会根据IUILayout接口提供的此UI元素的布局参数,计算出它应该使用的透视矩阵、投影矩阵和模型矩阵。所以,本质上,UI元素也是场景中的一种元素,只不过由于其mvp值比较特殊,使其看起来像Winform里的控件而已。

  1     public static class IUILayoutHelper
2 {
3 /// <summary>
4 /// 获取此UI元素的投影矩阵、视图矩阵和模型矩阵
5 /// </summary>
6 /// <param name="uiElement"></param>
7 /// <param name="projectionMatrix"></param>
8 /// <param name="viewMatrix"></param>
9 /// <param name="modelMatrix"></param>
10 /// <param name="camera">如果为null,会以glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0))计算默认值。</param>
11 /// <param name="maxDepth">UI元素的外接球半径的倍数。</param>
12 public static void GetMatrix(this IUILayout uiElement,
13 out mat4 projectionMatrix, out mat4 viewMatrix, out mat4 modelMatrix,
14 IViewCamera camera = null, float maxDepth = 2.0f)
15 {
16 IUILayoutArgs args = uiElement.GetArgs();
17 float max = (float)Math.Max(args.UIWidth, args.UIHeight);
18
19 {
20 projectionMatrix = glm.ortho((float)args.left / 2, (float)args.right / 2, (float)args.bottom / 2, (float)args.top / 2,
21 uiElement.Param.zNear, uiElement.Param.zFar);
22 projectionMatrix = glm.translate(projectionMatrix, new vec3(0, 0, uiElement.Param.zFar - max / 2 * maxDepth));
23 }
24 {
25 // UI元素不在三维场景中,所以其Camera可以是null。
26 if (camera == null)
27 {
28 //viewMatrix = glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0));
29 viewMatrix = glm.lookAt(
30 ScientificCamera.defaultPosition,
31 ScientificCamera.defaultTarget,
32 ScientificCamera.defaultUpVector);
33 }
34 else
35 {
36 vec3 position = camera.Position - camera.Target;
37 position.Normalize();
38 viewMatrix = glm.lookAt(position, new vec3(0, 0, 0), camera.UpVector);
39 }
40 }
41 {
42 modelMatrix = glm.scale(mat4.identity(), new vec3(args.UIWidth / 2, args.UIHeight / 2, max / 2));
43 }
44 }
45
46 const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
47 const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
48
49 /// <summary>
50 /// 获取为UI元素布局所需的参数对象。
51 /// </summary>
52 /// <param name="uiElement"></param>
53 /// <returns></returns>
54 public static IUILayoutArgs GetArgs(this IUILayout uiElement)
55 {
56 var args = new IUILayoutArgs();
57
58 CalculateViewport(args);
59
60 CalculateCoords(uiElement, args.viewportWidth, args.viewportHeight, args);
61
62 return args;
63 }
64
65 /// <summary>
66 /// 计算opengl画布的大小。
67 /// </summary>
68 /// <param name="args"></param>
69 static void CalculateViewport(IUILayoutArgs args)
70 {
71 int[] viewport = new int[4];
72 GL.GetInteger(GetTarget.Viewport, viewport);
73 args.viewportWidth = viewport[2];
74 args.viewportHeight = viewport[3];
75 }
76
77 /// <summary>
78 /// 根据UI元素的布局设定,计算其应有的宽高及其在ortho()中应有的参数。
79 /// </summary>
80 /// <param name="uiElement"></param>
81 /// <param name="viewportWidth"></param>
82 /// <param name="viewportHeight"></param>
83 /// <param name="args"></param>
84 static void CalculateCoords(IUILayout uiElement, int viewportWidth, int viewportHeight, IUILayoutArgs args)
85 {
86 IUILayoutParam param = uiElement.Param;
87
88 if ((param.Anchor & leftRightAnchor) == leftRightAnchor)
89 {
90 args.UIWidth = viewportWidth - param.Margin.Left - param.Margin.Right;
91 if (args.UIWidth < 0) { args.UIWidth = 0; }
92 }
93 else
94 {
95 args.UIWidth = param.Size.Width;
96 }
97
98 if ((param.Anchor & topBottomAnchor) == topBottomAnchor)
99 {
100 args.UIHeight = viewportHeight - param.Margin.Top - param.Margin.Bottom;
101 if (args.UIHeight < 0) { args.UIHeight = 0; }
102 }
103 else
104 {
105 args.UIHeight = param.Size.Height;
106 }
107
108 if ((param.Anchor & leftRightAnchor) == AnchorStyles.None)
109 {
110 args.left = -(args.UIWidth / 2
111 + (viewportWidth - args.UIWidth)
112 * ((double)param.Margin.Left / (double)(param.Margin.Left + param.Margin.Right)));
113 }
114 else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Left)
115 {
116 args.left = -(args.UIWidth / 2 + param.Margin.Left);
117 }
118 else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Right)
119 {
120 args.left = -(viewportWidth - args.UIWidth / 2 - param.Margin.Right);
121 }
122 else // if ((Anchor & leftRightAnchor) == leftRightAnchor)
123 {
124 args.left = -(args.UIWidth / 2 + param.Margin.Left);
125 }
126
127 if ((param.Anchor & topBottomAnchor) == AnchorStyles.None)
128 {
129 args.bottom = -viewportHeight / 2;
130 args.bottom = -(args.UIHeight / 2
131 + (viewportHeight - args.UIHeight)
132 * ((double)param.Margin.Bottom / (double)(param.Margin.Bottom + param.Margin.Top)));
133 }
134 else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
135 {
136 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
137 }
138 else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Top)
139 {
140 args.bottom = -(viewportHeight - args.UIHeight / 2 - param.Margin.Top);
141 }
142 else // if ((Anchor & topBottomAnchor) == topBottomAnchor)
143 {
144 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
145 }
146 }
147 }

IUILayoutHelper

 

如何使用

我们以画一个简单的边框为例说明如何使用IUILayout。这个边框画出了IUILayout元素本身的范围,在调试期间也是很有用的。

  1     /// <summary>
2 /// Draw a rectangle on OpenGL control like a <see cref="Windows.Forms.Control"/> drawn on a <see cref="windows.Forms.Form"/>.
3 /// Set its properties(Anchor, Margin, Size, etc) to adjust its behaviour.
4 /// </summary>
5 public class SimpleUIRect : SceneElementBase, IUILayout, IMVP//, IRenderable, IHasObjectSpace
6 {
7 /// <summary>
8 /// shader program
9 /// </summary>
10 public ShaderProgram shaderProgram;
11 const string strin_Position = "in_Position";
12 const string strin_Color = "in_Color";
13
14 /// <summary>
15 /// VAO
16 /// </summary>
17 protected uint[] vao;
18
19 /// <summary>
20 /// 图元类型
21 /// </summary>
22 protected DrawMode axisPrimitiveMode;
23
24 /// <summary>
25 /// 顶点数
26 /// </summary>
27 protected int axisVertexCount;
28
29 vec3 rectColor;
30
31 /// <summary>
32 ///
33 /// </summary>
34 /// <param name="anchor">the edges of the viewport to which a SimpleUIRect is bound and determines how it is resized with its parent.
35 /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para></param>
36 /// <param name="margin">the space between viewport and SimpleRect.</param>
37 /// <param name="size">Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left &amp; <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None.
38 /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top &amp; <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para></param>
39 /// <param name="zNear"></param>
40 /// <param name="zFar"></param>
41 /// <param name="rectColor">default color is red.</param>
42 public SimpleUIRect(IUILayoutParam param, GLColor rectColor = null)
43 {
44 IUILayout layout = this;
45 layout.Param = param;
46
47 if (rectColor == null)
48 { this.rectColor = new vec3(0, 0, 1); }
49 else
50 { this.rectColor = new vec3(rectColor.R, rectColor.G, rectColor.B); }
51 }
52
53 protected override void DoInitialize()
54 {
55 this.shaderProgram = InitializeShader();
56
57 InitVAO();
58
59 base.BeforeRendering += this.GetSimpleUI_BeforeRendering();
60 base.AfterRendering += this.GetSimpleUI_AfterRendering();
61 }
62
63 private void InitVAO()
64 {
65 this.axisPrimitiveMode = DrawMode.LineLoop;
66 this.axisVertexCount = 4;
67 this.vao = new uint[1];
68
69 GL.GenVertexArrays(1, vao);
70
71 GL.BindVertexArray(vao[0]);
72
73 // Create a vertex buffer for the vertex data.
74 {
75 UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(4);
76 positionArray[0] = new vec3(-0.5f, -0.5f, 0);
77 positionArray[1] = new vec3(0.5f, -0.5f, 0);
78 positionArray[2] = new vec3(0.5f, 0.5f, 0);
79 positionArray[3] = new vec3(-0.5f, 0.5f, 0);
80
81 uint positionLocation = shaderProgram.GetAttributeLocation(strin_Position);
82
83 uint[] ids = new uint[1];
84 GL.GenBuffers(1, ids);
85 GL.BindBuffer(BufferTarget.ArrayBuffer, ids[0]);
86 GL.BufferData(BufferTarget.ArrayBuffer, positionArray, BufferUsage.StaticDraw);
87 GL.VertexAttribPointer(positionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
88 GL.EnableVertexAttribArray(positionLocation);
89
90 positionArray.Dispose();
91 }
92
93 // Now do the same for the colour data.
94 {
95 UnmanagedArray<vec3> colorArray = new UnmanagedArray<vec3>(4);
96 vec3 color = this.rectColor;
97 for (int i = 0; i < colorArray.Length; i++)
98 {
99 colorArray[i] = color;
100 }
101
102 uint colorLocation = shaderProgram.GetAttributeLocation(strin_Color);
103
104 uint[] ids = new uint[1];
105 GL.GenBuffers(1, ids);
106 GL.BindBuffer(BufferTarget.ArrayBuffer, ids[0]);
107 GL.BufferData(BufferTarget.ArrayBuffer, colorArray, BufferUsage.StaticDraw);
108 GL.VertexAttribPointer(colorLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
109 GL.EnableVertexAttribArray(colorLocation);
110
111 colorArray.Dispose();
112 }
113
114 // Unbind the vertex array, we've finished specifying data for it.
115 GL.BindVertexArray(0);
116 }
117
118 protected ShaderProgram InitializeShader()
119 {
120 var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"UIs.SimpleUIRect.vert");
121 var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"UIs.SimpleUIRect.frag");
122
123 shaderProgram = new ShaderProgram();
124 shaderProgram.Create(vertexShaderSource, fragmentShaderSource, null);
125
126 shaderProgram.AssertValid();
127
128 return shaderProgram;
129 }
130
131 protected override void DoRender(RenderEventArgs e)
132 {
133 GL.BindVertexArray(vao[0]);
134
135 GL.DrawArrays(this.axisPrimitiveMode, 0, this.axisVertexCount);
136
137 GL.BindVertexArray(0);
138 }
139
140 public IUILayoutParam Param { get; set; }
141
142
143 void IMVP.SetShaderProgram(mat4 mvp)
144 {
145 IMVPHelper.DoUpdateMVP(this, mvp);
146 }
147
148
149 void IMVP.ResetShaderProgram()
150 {
151 IMVPHelper.DoUnbindShaderProgram(this);
152 }
153
154 ShaderProgram IMVP.GetShaderProgram()
155 {
156 return this.shaderProgram;
157 }
158 }

SimpleUIRect

 

这段代码关注如下几点:

A:实现IUILayout只需一句" public IUILayoutParam Param { get; set; }"。

B:实现IUILayout的元素也必须实现IMVP。实际上任何用modern OpenGL方式进行渲染的元素都应该实现IMVP。

C:其他方面与普通元素无异。

D:此元素借助了2个扩展方法:

 1     public static class IUILayoutRenderingHelper
2 {
3 private static readonly object synObj = new object();
4 private static EventHandler<RenderEventArgs> simpleUIAxis_BeforeRendering = null;
5 private static EventHandler<RenderEventArgs> simpleUIAxis_AfterRendering = null;
6
7 /// <summary>
8 /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的After事件。
9 /// <para>此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。</para>
10 /// </summary>
11 /// <typeparam name="T"></typeparam>
12 /// <param name="element"></param>
13 /// <returns></returns>
14 public static EventHandler<RenderEventArgs> GetSimpleUI_AfterRendering<T>(this T element)
15 where T : SceneElementBase, IUILayout, IMVP
16 {
17 if (simpleUIAxis_AfterRendering == null)
18 {
19 lock (synObj)
20 {
21 if (simpleUIAxis_AfterRendering == null)
22 {
23 simpleUIAxis_AfterRendering = new EventHandler<RenderEventArgs>(SimpleUI_AfterRendering);
24 }
25 }
26 }
27
28 return simpleUIAxis_AfterRendering;
29 }
30
31 /// <summary>
32 /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的Before事件。
33 /// <para>此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。</para>
34 /// </summary>
35 /// <typeparam name="T"></typeparam>
36 /// <param name="element"></param>
37 /// <returns></returns>
38 public static EventHandler<RenderEventArgs> GetSimpleUI_BeforeRendering<T>(this T element)
39 where T : SceneElementBase, IUILayout, IMVP
40 {
41 if (simpleUIAxis_BeforeRendering == null)
42 {
43 lock (synObj)
44 {
45 if (simpleUIAxis_BeforeRendering == null)
46 {
47 simpleUIAxis_BeforeRendering = new EventHandler<RenderEventArgs>(SimpleUI_BeforeRendering);
48 }
49 }
50 }
51
52 return simpleUIAxis_BeforeRendering;
53 }
54
55 static void SimpleUI_AfterRendering(object sender, RenderEventArgs e)
56 {
57 IMVP element = sender as IMVP;
58 element.ResetShaderProgram();
59 }
60
61 static void SimpleUI_BeforeRendering(object sender, RenderEventArgs e)
62 {
63 mat4 projectionMatrix, viewMatrix, modelMatrix;
64 {
65 IUILayout element = sender as IUILayout;
66 element.GetMatrix(out projectionMatrix, out viewMatrix, out modelMatrix, e.Camera);
67 }
68
69 {
70 IMVP element = sender as IMVP;
71 element.SetShaderProgram(projectionMatrix * viewMatrix * modelMatrix);
72 }
73 }
74 }

IUILayoutRenderingHelper

 

借助扩展方法、类型约束等等机制,编写OpenGL程序效率高了很多。

下面是效果图。下图中,在窗口的四个角落各安排了1个SimpUIRect。无论Camera如何改变,窗口大小如何改变,这四个蓝色矩形框的大小、边距都不会改变。

总结

本篇是写起来最有难度的一篇。本篇所实现的类型、接口,都是在上一篇的基础上设计的。上一篇里讲的渲染过程,隐含着本篇的设计方案的前提条件。

本篇里的类型、接口都有各自的一套辅助类型构成一套实现某种功能的机制。但愿这不太复杂难用。我已经用Demo详细演示了各个功能是如何实现的。

CSharpGL(2)设计和使用场景元素及常用接口的更多相关文章

  1. CSharpGL(4)设计和使用Camera

    CSharpGL(4)设计和使用Camera +BIT祝威+悄悄在此留下版了个权的信息说: 主要内容 描述在OpenGL中Camera的概念和用处. 设计一个Camera以及操控Camera的Sate ...

  2. Loadrunder场景设计篇——手工场景设计

    概述 通过选择需要运行的脚本,分配运行脚本的负载生成器,在脚本中分配Vuser来建立手工场景 手工场景就是自行设置虚拟用户的变化,主要是通过设计用户的添加和减少过程,来模拟真实的用户请求模型,完成负载 ...

  3. 用事件队列来处理pixi中的场景元素入场

    在pixi中,添加一个精灵元素,你可能需要,先将贴图load进来,然后才能添加到场景中去,所以一般会这么操作 Loader.add("tree","static/imag ...

  4. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  5. HTML(七)HTML 表单(form元素介绍,input元素的常用type类型,input元素的常用属性)

    前言 表单是网页与用户的交互工具,由一个<form>元素作为容器构成,封装其他任何数量的表单控件,还有其他任何<body>元素里可用的标签 表单能够包含<input> ...

  6. select元素javascript常用操作 转

    /*------------------------------------------------------ *作者:xieyu @ 2007-08-14 *语言:JavaScript *说明:s ...

  7. 第2章 面向对象的设计原则(SOLID):4_接口隔离原则(ISP)

    4. 接口隔离原则(Interface Segregation Principle,ISP) 4.1 定义 (1)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口.类间的 ...

  8. 使用Flask设计带认证token的RESTful API接口

    大数据时代 Just a record. 使用Flask设计带认证token的RESTful API接口[翻译] 上一篇文章, 使用python的Flask实现一个RESTful API服务器端  简 ...

  9. 【python+selenium的web自动化】- 元素的常用操作详解(一)

    如果想从头学起selenium,可以去看看这个系列的文章哦! https://www.cnblogs.com/miki-peng/category/1942527.html ​ 本篇主要内容:1.元素 ...

随机推荐

  1. CozyRSS开发记录17-Html2Xaml

    CozyRSS开发记录17-Html2Xaml 1.RssContentView还需要优化 上回做了RssContentView的显示,但是对于rss返回的描述(摘要),连换行的没有,更别说里面还有h ...

  2. 忘记XP密码的解决方案

    仅供教学与研究用,后果自负! !! USE AT YOUR OWN RISK !! !! ONLY FOR EDUCATIONAL PURPOSE !! 介绍 获取SYSTEM权限.测试通过. 进入G ...

  3. linux中的环境变量

    linux中用到很多的环境变量的设置,这里推荐一个博客: http://www.cnblogs.com/mengyan/archive/2012/09/04/2669894.html

  4. linux shell基础命令

    du -h  #查询磁盘文件大小和列表 df  -h   # 查询服务器磁盘使用情况 top/free   # 查询服务器内存,cpu等资源使用情况 iptables    # 防火墙相关的命令 vi ...

  5. AOP详解

    什么是AOP AOP Aspect Oriented Programing 面向切面编程 AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视.事务管理.安全检查.缓存) Spring ...

  6. JavaScript(二) DOM

    当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素. 通过 id 查找 HTML ...

  7. 如何解决插入Oracle数据中文为乱码问题

    1.首先,Oracle查询编码:select * from v$nls_parameters;//看看是否GBK 2.如果是用Servlet或者别的,插入数据之前输出一下,看看是否乱码.比如: doP ...

  8. update maven之后jre被改成1.5的问题

    在 pom.xml 中添加如下代码: <build> <plugins> <plugin> <groupId>org.apache.maven.plug ...

  9. Win10系统怎样让打开图片方式为照片查看器

    转载自:百度经验 http://jingyan.baidu.com/article/5d368d1ef0cad13f60c057e3.html 1.首先,我们需要使用注册表编辑器来开启Win10系统照 ...

  10. Thinkphp3.2.3使用Ajax一定注意 数据返回

    Thinkphp3.2.3使用Ajax一定注意 数据返回 $data = 'ok'; $this->ajaxReturn($data); 不能直接 echo $data;