文章出處

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詳細演示了各個功能是如何實現的。


文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()