CSharpGL(6)在OpenGL中绘制UI元素

2016-08-13

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

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

主要内容

学习使用IUILayout接口及其机制,以实现在OpenGL中绘制UI元素。

以SimpleUIAxis为例演示如何使用IUILayout。

下载

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

什么是OpenGL中的UI元素

您可以在源码中找到SimpleUIAxis这一示例。

如上图所示,有5个坐标轴,中间那个是一个普通的三维模型(元素),作为对照。

四个角上各有一个坐标轴,这四个坐标轴的位置是绑定到窗口对应的边的,即会随着窗口的缩放自动调整位置,就想Winform里的Control一样。这样的元素就称为OpenGL里的UI元素。

上面那个UI元素是立体的,一般我们在Winform里常见的UI都是二维的,像下面这个色标条一样。当然了,如果我们能实现上图中的三维的UI元素,自然就能实现二维的UI元素了。

IUILayout机制

接口

为实现UI元素,我的思路是:设计一个接口IUILayout,让那些应当作为UI元素布局的元素实现此接口,之后就可以通过简单地调用IUILayout的扩展方法来实现UI布局。

  1. 1 /// <summary>
  2. 2 /// 实现在OpenGL窗口中的UI布局
  3. 3 /// </summary>
  4. 4 public interface IUILayout
  5. 5 {
  6. 6 IUILayoutParam Param { get; set; }
  7. 7 }

一个UI元素,需要哪些参数呢?它需要知道它应绑定到窗口的上下左右哪边;需要知道其长度是固定的还是随窗口变化的;需要知道它是否应显示在所有元素的最前方(即不被其他元素覆盖)。

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

熟悉Winform里控件的同学,一定常用Control.Anchor属性、Padding属性和Control.Size属性,这里我们完全借用了Winform现成的这三个数据结构。我希望这样能方便理解。

实现

实现UI布局的根本问题就是得到一个特殊的变换矩阵,能够让指定元素在窗口的固定位置显示(根据其UIParam值)。这个变换矩阵的计算过程有点长,其思路就是根据viewpoint大小和UI元素的布局设定(UIParam值),计算其应有的宽高及其在ortho()或perspective()中应有的参数。

  1. 1 public static class IUILayoutHelper
  2. 2 {
  3. 3 /// <summary>
  4. 4 /// 获取此UI元素的投影矩阵、视图矩阵和模型矩阵
  5. 5 /// </summary>
  6. 6 /// <param name="uiElement"></param>
  7. 7 /// <param name="projectionMatrix"></param>
  8. 8 /// <param name="viewMatrix"></param>
  9. 9 /// <param name="modelMatrix"></param>
  10. 10 /// <param name="camera">如果为null,会以glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0))计算默认值。</param>
  11. 11 /// <param name="maxDepth">UI元素的外接球半径的倍数。</param>
  12. 12 public static void GetMatrix(this IUILayout uiElement,
  13. 13 out mat4 projectionMatrix, out mat4 viewMatrix, out mat4 modelMatrix,
  14. 14 IViewCamera camera = null, float maxDepth = 2.0f)
  15. 15 {
  16. 16 IUILayoutArgs args = uiElement.GetArgs();
  17. 17 float max = (float)Math.Max(args.UIWidth, args.UIHeight);
  18. 18
  19. 19 {
  20. 20 //projectionMatrix = glm.ortho((float)args.left, (float)args.right, (float)args.bottom, (float)args.top,
  21. 21 // TODO: / 2后与legacy opengl的UI元素显示就完全一致了。为什么???
  22. 22 projectionMatrix = glm.ortho((float)args.left / 2, (float)args.right / 2, (float)args.bottom / 2, (float)args.top / 2,
  23. 23 uiElement.Param.zNear, uiElement.Param.zFar);
  24. 24 // 下面注释掉的代码是用来测试legacy OpenGL的matrix与GLM库计算的matrix是否相同用的。已经证明了两者完全相同,此处仅作留念+以防万一。
  25. 25 //{
  26. 26 // float[] matrix = new float[16];
  27. 27
  28. 28 // GL.MatrixMode(GL.GL_PROJECTION);
  29. 29 // GL.PushMatrix();
  30. 30 // GL.GetFloat(GetTarget.ProjectionMatrix, matrix);
  31. 31
  32. 32 // GL.LoadIdentity();
  33. 33 // GL.GetFloat(GetTarget.ProjectionMatrix, matrix);
  34. 34
  35. 35 // GL.Ortho(args.left / 2, args.right / 2, args.bottom / 2, args.top / 2, uiElement.Param.zNear, uiElement.Param.zFar);
  36. 36 // GL.GetFloat(GetTarget.ProjectionMatrix, matrix);// this equals projectionMatrix
  37. 37
  38. 38 // GL.PopMatrix();
  39. 39 //}
  40. 40 // 把UI元素移到ortho长方体的最靠近camera的地方,这样就可以把UI元素放到OpenGL最前方。
  41. 41 projectionMatrix = glm.translate(projectionMatrix, new vec3(0, 0, uiElement.Param.zFar - max / 2 * maxDepth));
  42. 42 }
  43. 43 {
  44. 44 // UI元素不在三维场景中,所以其Camera可以是null。
  45. 45 if (camera == null)
  46. 46 {
  47. 47 //viewMatrix = glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0));
  48. 48 viewMatrix = glm.lookAt(
  49. 49 Camera.defaultPosition,
  50. 50 Camera.defaultTarget,
  51. 51 Camera.defaultUpVector);
  52. 52 }
  53. 53 else
  54. 54 {
  55. 55 vec3 position = camera.Position - camera.Target;
  56. 56 position.Normalize();
  57. 57 viewMatrix = glm.lookAt(position, new vec3(0, 0, 0), camera.UpVector);
  58. 58 }
  59. 59 // 下面注释掉的代码是用来测试legacy OpenGL的matrix与GLM库计算的matrix是否相同用的。已经证明了两者完全相同,此处仅作留念+以防万一。
  60. 60 //{
  61. 61 // float[] matrix = new float[16];
  62. 62
  63. 63 // GL.MatrixMode(GL.GL_MODELVIEW);
  64. 64 // GL.PushMatrix();
  65. 65 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);
  66. 66
  67. 67 // GL.LoadIdentity();
  68. 68 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);
  69. 69
  70. 70 // if(camera==null)
  71. 71 // {
  72. 72 // GL.gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);
  73. 73 // }
  74. 74 // else
  75. 75 // {
  76. 76 // vec3 position = camera.Position - camera.Target;
  77. 77 // position.Normalize();
  78. 78 // GL.gluLookAt(position.x, position.y, position.z, 0, 0, 0, camera.UpVector.x, camera.UpVector.y, camera.UpVector.z);
  79. 79 // }
  80. 80 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);// this equals viewMatrix
  81. 81
  82. 82 // GL.PopMatrix();
  83. 83 //}
  84. 84 }
  85. 85 {
  86. 86 modelMatrix = glm.scale(mat4.identity(), new vec3(args.UIWidth / 2, args.UIHeight / 2, max / 2));
  87. 87 // 下面注释掉的代码是用来测试legacy OpenGL的matrix与GLM库计算的matrix是否相同用的。已经证明了两者完全相同,此处仅作留念+以防万一。
  88. 88 //{
  89. 89 // float[] matrix = new float[16];
  90. 90
  91. 91 // GL.MatrixMode(GL.GL_MODELVIEW);
  92. 92 // GL.PushMatrix();
  93. 93 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);
  94. 94
  95. 95 // GL.LoadIdentity();
  96. 96 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);
  97. 97
  98. 98 // GL.Scale(args.UIWidth / 2, args.UIHeight / 2, max / 2);
  99. 99 // GL.GetFloat(GetTarget.ModelviewMatix, matrix);// this equals modelMatrix
  100. 100
  101. 101 // GL.PopMatrix();
  102. 102 //}
  103. 103 }
  104. 104 }
  105. 105
  106. 106
  107. 107 /// <summary>
  108. 108 /// leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
  109. 109 /// </summary>
  110. 110 const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
  111. 111
  112. 112 /// <summary>
  113. 113 /// topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
  114. 114 /// </summary>
  115. 115 const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
  116. 116
  117. 117 /// <summary>
  118. 118 /// 获取为UI元素布局所需的参数对象。
  119. 119 /// </summary>
  120. 120 /// <param name="uiElement"></param>
  121. 121 /// <returns></returns>
  122. 122 public static IUILayoutArgs GetArgs(this IUILayout uiElement)
  123. 123 {
  124. 124 var args = new IUILayoutArgs();
  125. 125
  126. 126 CalculateViewport(args);
  127. 127
  128. 128 CalculateCoords(uiElement, args.viewportWidth, args.viewportHeight, args);
  129. 129
  130. 130 return args;
  131. 131 }
  132. 132
  133. 133 /// <summary>
  134. 134 /// 计算opengl画布的大小。
  135. 135 /// </summary>
  136. 136 /// <param name="args"></param>
  137. 137 static void CalculateViewport(IUILayoutArgs args)
  138. 138 {
  139. 139 int[] viewport = new int[4];
  140. 140 GL.GetInteger(GetTarget.Viewport, viewport);
  141. 141 args.viewportWidth = viewport[2];
  142. 142 args.viewportHeight = viewport[3];
  143. 143 }
  144. 144
  145. 145 /// <summary>
  146. 146 /// 根据UI元素的布局设定,计算其应有的宽高及其在ortho()中应有的参数。
  147. 147 /// </summary>
  148. 148 /// <param name="uiElement"></param>
  149. 149 /// <param name="viewportWidth"></param>
  150. 150 /// <param name="viewportHeight"></param>
  151. 151 /// <param name="args"></param>
  152. 152 static void CalculateCoords(IUILayout uiElement, int viewportWidth, int viewportHeight, IUILayoutArgs args)
  153. 153 {
  154. 154 IUILayoutParam param = uiElement.Param;
  155. 155
  156. 156 if ((param.Anchor & leftRightAnchor) == leftRightAnchor)
  157. 157 {
  158. 158 args.UIWidth = viewportWidth - param.Margin.Left - param.Margin.Right;
  159. 159 if (args.UIWidth < 0) { args.UIWidth = 0; }
  160. 160 }
  161. 161 else
  162. 162 {
  163. 163 args.UIWidth = param.Size.Width;
  164. 164 }
  165. 165
  166. 166 if ((param.Anchor & topBottomAnchor) == topBottomAnchor)
  167. 167 {
  168. 168 args.UIHeight = viewportHeight - param.Margin.Top - param.Margin.Bottom;
  169. 169 if (args.UIHeight < 0) { args.UIHeight = 0; }
  170. 170 }
  171. 171 else
  172. 172 {
  173. 173 args.UIHeight = param.Size.Height;
  174. 174 }
  175. 175
  176. 176 if ((param.Anchor & leftRightAnchor) == AnchorStyles.None)
  177. 177 {
  178. 178 args.left = -(args.UIWidth / 2
  179. 179 + (viewportWidth - args.UIWidth)
  180. 180 * ((double)param.Margin.Left / (double)(param.Margin.Left + param.Margin.Right)));
  181. 181 }
  182. 182 else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Left)
  183. 183 {
  184. 184 args.left = -(args.UIWidth / 2 + param.Margin.Left);
  185. 185 }
  186. 186 else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Right)
  187. 187 {
  188. 188 args.left = -(viewportWidth - args.UIWidth / 2 - param.Margin.Right);
  189. 189 }
  190. 190 else // if ((Anchor & leftRightAnchor) == leftRightAnchor)
  191. 191 {
  192. 192 args.left = -(args.UIWidth / 2 + param.Margin.Left);
  193. 193 }
  194. 194
  195. 195 if ((param.Anchor & topBottomAnchor) == AnchorStyles.None)
  196. 196 {
  197. 197 args.bottom = -viewportHeight / 2;
  198. 198 args.bottom = -(args.UIHeight / 2
  199. 199 + (viewportHeight - args.UIHeight)
  200. 200 * ((double)param.Margin.Bottom / (double)(param.Margin.Bottom + param.Margin.Top)));
  201. 201 }
  202. 202 else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
  203. 203 {
  204. 204 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
  205. 205 }
  206. 206 else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Top)
  207. 207 {
  208. 208 args.bottom = -(viewportHeight - args.UIHeight / 2 - param.Margin.Top);
  209. 209 }
  210. 210 else // if ((Anchor & topBottomAnchor) == topBottomAnchor)
  211. 211 {
  212. 212 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
  213. 213 }
  214. 214 }
  215. 215 }

IUILayoutHelper

如何使用

示例SimpleUIAxis

以本文开头的坐标轴元素为例。这个例子很常用,所以我放到CSharpGL.UIs类库里了,顺便可以作为参考。SimpleUIAxis实现了IUILayout,说明它想要实现UI布局;实现了IMVP,说明它要通过指定mvp矩阵的方式来设置自己的位置。

  1. 1 /// <summary>
  2. 2 /// 用一个<see cref="AxisElement"/>绘制一个固定在窗口某处的坐标系。
  3. 3 /// </summary>
  4. 4 public class SimpleUIAxis : SceneElementBase, IUILayout, IMVP, IDisposable
  5. 5 {
  6. 6 public AxisElement axisElement;
  7. 7
  8. 8 /// <summary>
  9. 9 ///
  10. 10 /// </summary>
  11. 11 /// <param name="anchor">the edges of the viewport to which a SimpleUIRect is bound and determines how it is resized with its parent.
  12. 12 /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para></param>
  13. 13 /// <param name="margin">the space between viewport and SimpleRect.</param>
  14. 14 /// <param name="size">Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left & <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None.
  15. 15 /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top & <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para></param>
  16. 16 /// <param name="zNear"></param>
  17. 17 /// <param name="zFar"></param>
  18. 18 /// <param name="rectColor">default color is red.</param>
  19. 19 public SimpleUIAxis(IUILayoutParam param, GLColor rectColor = null,
  20. 20 float radius = 0.3f, float axisLength = 10, int faceCount = 10)
  21. 21 {
  22. 22 // 把AxiesElement缩放到恰好放进此UI
  23. 23 radius = radius / axisLength / 2;
  24. 24 axisLength = 0.5f;
  25. 25 this.axisElement = new AxisElement(radius, axisLength, faceCount);
  26. 26
  27. 27 IUILayout layout = this;
  28. 28 layout.Param = param;
  29. 29 }
  30. 30
  31. 31 #region IDisposable Members
  32. 32
  33. 33 /// <summary>
  34. 34 /// Internal variable which checks if Dispose has already been called
  35. 35 /// </summary>
  36. 36 protected Boolean disposed;
  37. 37
  38. 38 /// <summary>
  39. 39 /// Releases unmanaged and - optionally - managed resources
  40. 40 /// </summary>
  41. 41 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  42. 42 protected void Dispose(Boolean disposing)
  43. 43 {
  44. 44 if (disposed)
  45. 45 {
  46. 46 return;
  47. 47 }
  48. 48
  49. 49 if (disposing)
  50. 50 {
  51. 51 //Managed cleanup code here, while managed refs still valid
  52. 52 this.axisElement.Dispose();
  53. 53 }
  54. 54 //Unmanaged cleanup code here
  55. 55
  56. 56 disposed = true;
  57. 57 }
  58. 58
  59. 59 /// <summary>
  60. 60 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  61. 61 /// </summary>
  62. 62 public void Dispose()
  63. 63 {
  64. 64 // Call the private Dispose(bool) helper and indicate
  65. 65 // that we are explicitly disposing
  66. 66 this.Dispose(true);
  67. 67
  68. 68 // Tell the garbage collector that the object doesn't require any
  69. 69 // cleanup when collected since Dispose was called explicitly.
  70. 70 GC.SuppressFinalize(this);
  71. 71 }
  72. 72
  73. 73 #endregion
  74. 74
  75. 75 #region IUILayout
  76. 76
  77. 77 public IUILayoutParam Param { get; set; }
  78. 78
  79. 79 #endregion IUILayout
  80. 80
  81. 81
  82. 82 protected override void DoInitialize()
  83. 83 {
  84. 84 this.axisElement.Initialize();
  85. 85
  86. 86 this.BeforeRendering += this.GetSimpleUI_BeforeRendering();
  87. 87 this.AfterRendering += this.GetSimpleUI_AfterRendering();
  88. 88 }
  89. 89
  90. 90 protected override void DoRender(RenderEventArgs e)
  91. 91 {
  92. 92 this.axisElement.Render(e);
  93. 93 }
  94. 94
  95. 95 void IMVP.SetShaderProgram(mat4 mvp)
  96. 96 {
  97. 97 IMVP element = this.axisElement as IMVP;
  98. 98 element.SetShaderProgram(mvp);
  99. 99 }
  100. 100
  101. 101
  102. 102 void IMVP.ResetShaderProgram()
  103. 103 {
  104. 104 IMVP element = this.axisElement as IMVP;
  105. 105 element.ResetShaderProgram();
  106. 106 }
  107. 107
  108. 108 ShaderProgram IMVP.GetShaderProgram()
  109. 109 {
  110. 110 return ((IMVP)this.axisElement).GetShaderProgram();
  111. 111 }
  112. 112 }

SimpleUIAxis

这里我还为BeforeRendering和AfterRendering事件提供了一个默认的事件函数。有了它,连BeforeRendering和AfterRendering事件函数都不用再写了。

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

IUILayoutRenderingHelper

总结

元素的UI布局是一个很实用的功能。所以我尽早地为其写了此篇说明。有什么问题请留言。

CSharpGL(6)在OpenGL中绘制UI元素的更多相关文章

  1. CSharpGL(26)在opengl中实现控件布局/渲染文字

    CSharpGL(26)在opengl中实现控件布局/渲染文字 效果图 如图所示,可以将文字.坐标轴固定在窗口的一角. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入( ...

  2. 如何跨线程访问Winform中的UI元素

    如何跨线程访问Winform中的UI元素 假如制作一个Socket聊天应用程序,很可能会用到多线程: 例如为Receive方法开辟单独一个线程,例如为Receive方法开辟单独一个线程(后台运行的线程 ...

  3. Wpf从资源中重用UI元素

    在我的界面上有几个选项卡,每个选项卡中都有下面的元素: <StackPanel Orientation="Horizontal"> <Button Content ...

  4. 在openGL中绘制图形

    点的绘制.: glVertex*():星号表示函数要有后缀 该函数 需要放在glBegin函数和glEnd函数之间,glBegin函数的向量指定绘制图元的类型,而glEnd函数没有参数,例如: glB ...

  5. 关于opengl中的矩阵平移,矩阵旋转,推导过程理解 OpenGL计算机图形学的一些必要矩阵运算知识

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/12166896.html 为什么引入齐次坐标的变换矩阵可以表示平移呢? - Yu Mao的回答 ...

  6. appium— Android定位webView里面的UI元素

    Android SDK中的UIAutomator中本身是不支持网页中的UI元素定位,下面介绍几种常用的定位app内部的网页的UI元素的方法. 一.使用chrome浏览器调试移动端网页 这是使用最多的一 ...

  7. OpenGL中常用的函数

    OPengl的官方文档如下:https://www.opengl.org/sdk/docs/man4/ void glGetIntegerv(   GLenum pname,      GLint * ...

  8. 在WPF中减少逻辑与UI元素的耦合

    原文:在WPF中减少逻辑与UI元素的耦合             在WPF中减少逻辑与UI元素的耦合 周银辉 1,    避免在逻辑中引用界面元素,别把后台数据强加给UI  一个糟糕的案例 比如说主界 ...

  9. CSharpGL(31)[译]OpenGL渲染管道那些事

    CSharpGL(31)[译]OpenGL渲染管道那些事 +BIT祝威+悄悄在此留下版了个权的信息说: 开始 自认为对OpenGL的掌握到了一个小瓶颈,现在回头细细地捋一遍OpenGL渲染管道应当是一 ...

随机推荐

  1. sweetAlert

    SweetAlert2是一款功能强大的纯Js模态消息对话框插件.SweetAlert2用于替代浏览器默认的弹出对话框,它提供各种参数和方法,支持嵌入图片,背景,HTML标签等,并提供5种内置的情景类, ...

  2. react native AsyncStorage的使用

    如果现在有一个需求,是要把用户的账号密码保存到本地,大家会怎么做的呢?如果在android中,我相信一大部分人会想到SharedPreferences,这是一个以键值对的形式进行存储的.那如果在rea ...

  3. 浩瀚技术团队... 安卓智能POS移动PDA开单器 开单器 进销存系统 进销存系统

    浩瀚技术团队... 智能POS移动PDA开单器 开单器 进销存系统 进销存系统 点餐 会员管理 会员管理 深度解读 手机APP移动办公到底是什么? 快速打单POS·不仅仅是快那么简单!  

  4. VBA实例收集

    1.工作表事件:固定制定区域激活,使之不能选择其他区域. Private Sub Worksheet_SelectionChange(ByVal Target As Range) If Target. ...

  5. sping注解

    1.@Autowired(已不推荐使用) 按类型装配,如果匹配不到或者匹配到多个则抛BeanCreationException异常.如果是多个时可以用@Qualifier指定来解决 eg. @Auto ...

  6. java与javac命令的功用

    一.javac用来编译java程序,比如说我写了一个Server.java文件,首先通过命令行进入.java文件所在的路径, 然后通过输入 javac Server.java 命令行来完成编译,编译之 ...

  7. svn的牛逼操作反向merge

    反向merge,轻松回滚.

  8. Docker学习笔记第一章:补充

    只记得学习后面的命令,忘记整理一些概念性的东西了,只能做个补充了=.= Docker虽然也是一种虚拟技术,但是不同于虚拟机的概念.Docker是一种以容器为主的技术,容器运行不需要模拟层(emulat ...

  9. C语言与java 20155317 王新玮第二次

    20155317 王新玮第二次写作感想   你有什么技能比大多数人(超过90%以上)更好? 刚刚看到这个题目,我的首先想到的是会一些中医,懂得中医的理论框架知识,懂得大部分的中医脉象,能够解决日常生活 ...

  10. MongoDB aggregate 运用篇

    基础知识 操作符介绍: $project:包含.排除.重命名和显示字段 $match:查询,需要同find()一样的参数 $limit:限制结果数量 $skip:忽略结果的数量 $sort:按照给定的 ...