博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CSharpGL(2)设计和使用场景元素及常用接口
阅读量:6334 次
发布时间:2019-06-22

本文共 24222 字,大约阅读时间需要 80 分钟。

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

2016-08-13

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

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

主要内容

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

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

以PyramidElement为例演示SceneELementBase的用法。

下载

您可以在()找到最新的源码。欢迎感兴趣的同学fork之。

 

在OpenGL中绘制场景的思路

规范用词

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

场景

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

元素

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

所有元素的基类

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

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

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

常用接口

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

IMVP

定义

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

 
1     ///  2     /// 通过此接口设置元素的MVP矩阵 3     ///  4     public interface IMVP 5     { 6         ///  7         /// 更新此元素的MVP值。 8         ///  9         /// 三个矩阵的乘积(Projection * View * Model)10         void SetShaderProgram(mat4 mvp);11 12         /// 13         /// 解绑当前shader program。14         /// 15         void ResetShaderProgram();16 17         /// 18         /// 19         /// 20         /// 
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 // 解绑shader15 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     ///  2     /// 用OPENGL初始化和渲染一个元素。 3     ///  4     public abstract class SceneElementBase : IRenderable 5     { 6         protected bool initialized = false; 7  8         ///  9         /// 初始化此Element10         /// 11         public void Initialize()12         {13             if (!initialized)14             {15                 DoInitialize();16 17                 initialized = true;18             }19         }20 21         /// 22         /// 初始化此Element23         /// 24         protected abstract void DoInitialize();25 26         /// 27         /// 渲染28         /// 29         /// 30         public void Render(RenderEventArgs e)31         {32             if (!initialized) { Initialize(); }33 34             EventHandler
beforeRendering = this.BeforeRendering;35 if (beforeRendering != null)36 {37 beforeRendering(this, e);38 }39 40 DoRender(e);41 42 EventHandler
afterRendering = this.AfterRendering;43 if (afterRendering != null)44 {45 afterRendering(this, e);46 }47 }48 49 ///
50 /// 执行渲染操作51 /// 52 ///
53 protected abstract void DoRender(RenderEventArgs e);54 55 ///
56 /// 在渲染前进行某些准备(更新camera矩阵信息等)57 /// 58 public event EventHandler
BeforeRendering;59 60 ///
61 /// 在渲染后进行某些善后(恢复OpenGL状态等)62 /// 63 public event EventHandler
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 IMVPHelper24     {25         /// 26         /// public static string strMVP = "MVP";27         /// 
使用此
所使用的Vertex Shader必须含有
uniform mat4 MVP;并使其作为变换矩阵。
28 ///
29 public static string strMVP = "MVP";30 31 /// 32 /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。33 /// 34 /// 35 /// 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 /// 44 /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。45 /// 46 /// 47 public static void DoUnbindShaderProgram(this IMVP element)48 {49 ShaderProgram shaderProgram = element.GetShaderProgram();50 shaderProgram.Unbind();51 }52 }

IColorCodedPicking

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

具体使用方法请参考()。不再重述。

IUILayout

定义

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

 
1     ///  2     /// 实现在OpenGL窗口中的UI布局 3     ///  4     public interface IUILayout 5     { 6         IUILayoutParam Param { get; set; } 7  8 } 9     public struct IUILayoutParam10     {11         /// 12         /// the edges of the 
to which a UI’s rect is bound and determines how it is resized with its parent.13 ///
something like AnchorStyles.Left | AnchorStyles.Bottom.
14 ///
15 public System.Windows.Forms.AnchorStyles Anchor;16 17 /// 18 /// Gets or sets the space between viewport and SimpleRect.19 /// 20 public System.Windows.Forms.Padding Margin;21 22 /// 23 /// Stores width when
.Left &
.Right is
.None.24 ///
and height when
.Top &
.Bottom is
.None.
25 ///
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         ///   4         /// 获取此UI元素的投影矩阵、视图矩阵和模型矩阵  5         ///   6         ///   7         ///   8         ///   9         ///  10         /// 如果为null,会以glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0))计算默认值。 11         /// UI元素的外接球半径的倍数。 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         ///  50         /// 获取为UI元素布局所需的参数对象。 51         ///  52         ///  53         /// 
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 /// 66 /// 计算opengl画布的大小。 67 /// 68 /// 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 /// 78 /// 根据UI元素的布局设定,计算其应有的宽高及其在ortho()中应有的参数。 79 /// 80 /// 81 /// 82 /// 83 /// 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 else104 {105 args.UIHeight = param.Size.Height;106 }107 108 if ((param.Anchor & leftRightAnchor) == AnchorStyles.None)109 {110 args.left = -(args.UIWidth / 2111 + (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 / 2131 + (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     ///   2     /// Draw a rectangle on OpenGL control like a 
drawn on a
. 3 /// Set its properties(Anchor, Margin, Size, etc) to adjust its behaviour. 4 ///
5 public class SimpleUIRect : SceneElementBase, IUILayout, IMVP//, IRenderable, IHasObjectSpace 6 { 7 /// 8 /// shader program 9 /// 10 public ShaderProgram shaderProgram; 11 const string strin_Position = "in_Position"; 12 const string strin_Color = "in_Color"; 13 14 /// 15 /// VAO 16 /// 17 protected uint[] vao; 18 19 /// 20 /// 图元类型 21 /// 22 protected DrawMode axisPrimitiveMode; 23 24 /// 25 /// 顶点数 26 /// 27 protected int axisVertexCount; 28 29 vec3 rectColor; 30 31 /// 32 /// 33 /// 34 /// the edges of the viewport to which a SimpleUIRect is bound and determines how it is resized with its parent. 35 ///
something like AnchorStyles.Left | AnchorStyles.Bottom.
36 /// the space between viewport and SimpleRect. 37 /// Stores width when
.Left &
.Right is
.None. 38 ///
and height when
.Top &
.Bottom is
.None.
39 /// 40 /// 41 /// default color is red. 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
positionArray = new UnmanagedArray
(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
colorArray = new UnmanagedArray
(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
simpleUIAxis_BeforeRendering = null; 5 private static EventHandler
simpleUIAxis_AfterRendering = null; 6 7 ///
8 /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的After事件。 9 ///
此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。
10 ///
11 ///
12 ///
13 ///
14 public static EventHandler
GetSimpleUI_AfterRendering
(this T element) 15 where T : SceneElementBase, IUILayout, IMVP16 {17 if (simpleUIAxis_AfterRendering == null)18 {19 lock (synObj)20 {21 if (simpleUIAxis_AfterRendering == null)22 {23 simpleUIAxis_AfterRendering = new EventHandler
(SimpleUI_AfterRendering);24 }25 }26 }27 28 return simpleUIAxis_AfterRendering;29 }30 31 ///
32 /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的Before事件。33 ///
此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。
34 ///
35 ///
36 ///
37 ///
38 public static EventHandler
GetSimpleUI_BeforeRendering
(this T element)39 where T : SceneElementBase, IUILayout, IMVP40 {41 if (simpleUIAxis_BeforeRendering == null)42 {43 lock (synObj)44 {45 if (simpleUIAxis_BeforeRendering == null)46 {47 simpleUIAxis_BeforeRendering = new EventHandler
(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详细演示了各个功能是如何实现的。

转载地址:http://isioa.baihongyu.com/

你可能感兴趣的文章
oracle11g R2 RAC卸载grid
查看>>
ES6 结构和扩展运算符
查看>>
王利阳:电商大促 决战6.18
查看>>
kafka消息传输的事务定义
查看>>
JAVA 后台数据校验
查看>>
实现LNMMP
查看>>
mysql的pid文件出现问题
查看>>
计算rem单位
查看>>
第七章 大网高级 ASA
查看>>
rsync+inotify触发式远程同步
查看>>
优秀设计师应当知道的几大UI设计原则(一)
查看>>
mongodb高级查询
查看>>
struts2.1 struts.devMode BUG解决方案
查看>>
日本法院裁定三星诉苹果专利侵权案败诉
查看>>
Windows Server 2012R2 桌面体验问题直通车
查看>>
桌面支持--复印证件技巧
查看>>
Silverlight实用窍门系列:50.InkPresenter涂鸦板的基本使用,以及将效果保存为Png图片【附带源码实例】...
查看>>
MySQL数据库经典书籍share
查看>>
给出三个数,要求输出 最大的一个
查看>>
Linux系统中获取帮助的方法及Linux系统的哲学思想
查看>>