zoukankan      html  css  js  c++  java
  • CSharpGL(6)在OpenGL中绘制UI元素

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

     

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

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

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

    实现

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

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

    如何使用

    示例SimpleUIAxis

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

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

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

     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

    总结

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

  • 相关阅读:
    MySql触发器简介
    MySQL存储过程
    MySQL自定义函数
    MySql视图
    MySQL增删改
    MySQL内联和外联查询
    MySql运算符
    SQL scripts
    Adding Swagger to Web API project
    Unable to get setting value Parameter name: profileName
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-6-using-UI-elements-in-CSharpGL.html
Copyright © 2011-2022 走看看