zoukankan      html  css  js  c++  java
  • CSharpGL(26)在opengl中实现控件布局/渲染文字

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

    效果图

    如图所示,可以将文字、坐标轴固定在窗口的一角。

    下载

    CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

    UI控件布局关键点

    ILayout

    类似Winform控件那样,控件的位置、大小由其Anchor等属性决定。窗口大小改变时,控件的位置、大小会随之改变。

    所以模仿Control类,直接使用Anchor作为UIRenderer的接口。

     1     /// <summary>
     2     /// Supports layout UI element in OpenGL canvas.
     3     /// 实现在OpenGL窗口中的UI布局
     4     /// </summary>
     5     public interface ILayout : ITreeNode<UIRenderer>
     6     {
     7         //event EventHandler afterLayout;
     8 
     9         /// <summary>
    10         /// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent.
    11         /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para>
    12         /// </summary>
    13         System.Windows.Forms.AnchorStyles Anchor { get; set; }
    14 
    15         /// <summary>
    16         /// Gets or sets the space between viewport and SimpleRect.
    17         /// </summary>
    18         System.Windows.Forms.Padding Margin { get; set; }
    19 
    20         /// <summary>
    21         /// 相对于Parent左下角的位置(Left Down location)
    22         /// </summary>
    23         System.Drawing.Point Location { get; set; }
    24 
    25         /// <summary>
    26         /// Stores width when <see cref="Anchor"/>.Left &amp; <see cref="Anchor"/>.Right is <see cref="Anchor"/>.None.
    27         /// <para> and height when <see cref="Anchor"/>.Top &amp; <see cref="Anchor"/>.Bottom is <see cref="Anchor"/>.None.</para>
    28         /// </summary>
    29         System.Drawing.Size Size { get; set; }
    30 
    31         /// <summary>
    32         /// 
    33         /// </summary>
    34         System.Drawing.Size ParentLastSize { get; set; }
    35 
    36         /// <summary>
    37         /// 
    38         /// </summary>
    39         int zNear { get; set; }
    40 
    41         /// <summary>
    42         /// 
    43         /// </summary>
    44         int zFar { get; set; }
    45 
    46     }

    实现在OpenGL窗口中的UI布局

    有了数据结构,就可以实现窗口中的UI布局了。当窗口大小改变时,调用下面的函数。

      1         /// <summary>
      2         /// layout controls in OpenGL canvas.
      3         /// <para>This coordinate system is as below.</para>
      4         /// <para>   / y</para>
      5         /// <para>   |</para>
      6         /// <para>   |</para>
      7         /// <para>   |</para>
      8         /// <para>   |</para>
      9         /// <para>   |</para>
     10         /// <para>   |-----------------&gt;x</para>
     11         /// <para>(0, 0)</para>
     12         /// </summary>
     13         /// <param name="uiRenderer"></param>
     14         internal static void Layout(this ILayout uiRenderer)
     15         {
     16             ILayout parent = uiRenderer.Parent;
     17             if (parent != null)
     18             {
     19                 uiRenderer.Self.DoBeforeLayout();
     20                 NonRootNodeLayout(uiRenderer, parent);
     21                 uiRenderer.Self.DoAfterLayout();
     22             }
     23 
     24             foreach (var item in uiRenderer.Children)
     25             {
     26                 item.Layout();
     27             }
     28 
     29             if (parent != null)
     30             {
     31                 uiRenderer.ParentLastSize = parent.Size;
     32             }
     33         }
     34 
     35         /// <summary>
     36         /// leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right); 
     37         /// </summary>
     38         private const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
     39 
     40         /// <summary>
     41         /// topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
     42         /// </summary>
     43         private const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
     44 
     45         /// <summary>
     46         /// Gets <paramref name="currentNode"/>'s location and size according to its state and parent's information.
     47         /// </summary>
     48         /// <param name="currentNode"></param>
     49         /// <param name="parent"></param>
     50         private static void NonRootNodeLayout(ILayout currentNode, ILayout parent)
     51         {
     52             int x, y, width, height;
     53             if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
     54             {
     55                 width = parent.Size.Width - currentNode.Margin.Left - currentNode.Margin.Right;
     56                 //width = currentNode.Size.Width + (parent.Size.Width - currentNode.ParentLastSize.Width);
     57                 if (width < 0) { width = 0; }
     58             }
     59             else
     60             {
     61                 width = currentNode.Size.Width;
     62             }
     63 
     64             if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
     65             {
     66                 height = parent.Size.Height - currentNode.Margin.Top - currentNode.Margin.Bottom;
     67                 //height = currentNode.Size.Height + (parent.Size.Height - currentNode.ParentLastSize.Height);
     68                 if (height < 0) { height = 0; }
     69             }
     70             else
     71             {
     72                 height = currentNode.Size.Height;
     73             }
     74 
     75             if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.None)
     76             {
     77                 x = (int)(
     78                     (parent.Size.Width - width)
     79                     * ((double)currentNode.Margin.Left / (double)(currentNode.Margin.Left + currentNode.Margin.Right)));
     80             }
     81             else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Left)
     82             {
     83                 x = parent.Location.X + currentNode.Margin.Left;
     84             }
     85             else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Right)
     86             {
     87                 x = parent.Location.X + parent.Size.Width - currentNode.Margin.Right - width;
     88             }
     89             else if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
     90             {
     91                 x = parent.Location.X + currentNode.Margin.Left;
     92             }
     93             else
     94             { throw new Exception("uiRenderer should not happen!"); }
     95 
     96             if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.None)
     97             {
     98                 y = (int)(
     99                     (parent.Size.Height - height)
    100                     * ((double)currentNode.Margin.Bottom / (double)(currentNode.Margin.Bottom + currentNode.Margin.Top)));
    101             }
    102             else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
    103             {
    104                 //y = currentNode.Margin.Bottom;
    105                 y = parent.Location.Y + currentNode.Margin.Bottom;
    106             }
    107             else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Top)
    108             {
    109                 //y = parent.Size.Height - height - currentNode.Margin.Top;
    110                 y = parent.Location.Y + parent.Size.Height - currentNode.Margin.Top - height;
    111             }
    112             else if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
    113             {
    114                 //y = currentNode.Margin.Top + parent.Location.Y;
    115                 y = parent.Location.Y + currentNode.Margin.Bottom;
    116             }
    117             else
    118             { throw new Exception("This should not happen!"); }
    119 
    120             currentNode.Location = new System.Drawing.Point(x, y);
    121             currentNode.Size = new Size(width, height);
    122         }
    public static void Layout(this ILayout uiRenderer)

    glViewport/glScissor

    这是避免复杂的矩阵操作,实现稳定的UI布局显示的关键。glViewport指定了GLRenderer在窗口的渲染位置,glScissor将GLRenderer范围之外的部分保护起来。

    在渲染之前,根据UIRenderer的位置和大小更新viewport和scissor即可。不再需要为UI固定在窗口某处而煞费苦心地设计projection,view,model矩阵了。

      1     /// <summary>
      2     /// Renderer  that supports UI layout.
      3     /// 支持2D UI布局的渲染器
      4     /// </summary>
      5     public class UIRenderer : RendererBase, ILayout
      6     {
      7         private ViewportSwitch viewportSwitch;
      8         private ScissorTestSwitch scissorTestSwitch;
      9         private GLSwitchList switchList = new GLSwitchList();
     10 
     11         /// <summary>
     12         /// 
     13         /// </summary>
     14         public GLSwitchList SwitchList
     15         {
     16             get { return switchList; }
     17         }
     18 
     19         /// <summary>
     20         /// triggered before layout in <see cref="ILayout"/>.Layout().
     21         /// </summary>
     22         public event EventHandler BeforeLayout;
     23         /// <summary>
     24         /// triggered after layout in <see cref="ILayout"/>.Layout().
     25         /// </summary>
     26         public event EventHandler AfterLayout;
     27 
     28         internal void DoBeforeLayout()
     29         {
     30             EventHandler BeforeLayout = this.BeforeLayout;
     31             if (BeforeLayout != null)
     32             {
     33                 BeforeLayout(this, null);
     34             }
     35         }
     36 
     37         internal void DoAfterLayout()
     38         {
     39             EventHandler AfterLayout = this.AfterLayout;
     40             if (AfterLayout != null)
     41             {
     42                 AfterLayout(this, null);
     43             }
     44         }
     45 
     46         /// <summary>
     47         /// 
     48         /// </summary>
     49         public RendererBase Renderer { get; protected set; }
     50         /// <summary>
     51         /// 
     52         /// </summary>
     53         /// <param name="anchor"></param>
     54         /// <param name="margin"></param>
     55         /// <param name="size"></param>
     56         /// <param name="zNear"></param>
     57         /// <param name="zFar"></param>
     58         public UIRenderer(
     59             System.Windows.Forms.AnchorStyles anchor, System.Windows.Forms.Padding margin,
     60             System.Drawing.Size size, int zNear, int zFar)
     61         {
     62             this.Children = new ChildList<UIRenderer>(this);// new ILayoutList(this);
     63 
     64             this.Anchor = anchor; this.Margin = margin;
     65             this.Size = size; this.zNear = zNear; this.zFar = zFar;
     66         }
     67 
     68         /// <summary>
     69         /// 
     70         /// </summary>
     71         public System.Windows.Forms.AnchorStyles Anchor { get; set; }
     72 
     73         /// <summary>
     74         /// 
     75         /// </summary>
     76         public System.Windows.Forms.Padding Margin { get; set; }
     77 
     78         /// <summary>
     79         /// 
     80         /// </summary>
     81         public System.Drawing.Point Location { get; set; }
     82 
     83         /// <summary>
     84         /// 
     85         /// </summary>
     86         public System.Drawing.Size Size { get; set; }
     87         /// <summary>
     88         /// 
     89         /// </summary>
     90         public System.Drawing.Size ParentLastSize { get; set; }
     91 
     92         /// <summary>
     93         /// 
     94         /// </summary>
     95         public int zNear { get; set; }
     96 
     97         /// <summary>
     98         /// 
     99         /// </summary>
    100         public int zFar { get; set; }
    101 
    102         /// <summary>
    103         /// 
    104         /// </summary>
    105         protected override void DoInitialize()
    106         {
    107             this.viewportSwitch = new ViewportSwitch();
    108             this.scissorTestSwitch = new ScissorTestSwitch();
    109 
    110             RendererBase renderer = this.Renderer;
    111             if (renderer != null)
    112             {
    113                 renderer.Initialize();
    114             }
    115         }
    116 
    117         /// <summary>
    118         /// 
    119         /// </summary>
    120         /// <param name="arg"></param>
    121         protected override void DoRender(RenderEventArg arg)
    122         {
    123             this.viewportSwitch.X = this.Location.X;
    124             this.viewportSwitch.Y = this.Location.Y;
    125             this.viewportSwitch.Width = this.Size.Width;
    126             this.viewportSwitch.Height = this.Size.Height;
    127             this.scissorTestSwitch.X = this.Location.X;
    128             this.scissorTestSwitch.Y = this.Location.Y;
    129             this.scissorTestSwitch.Width = this.Size.Width;
    130             this.scissorTestSwitch.Height = this.Size.Height;
    131 
    132             this.viewportSwitch.On();
    133             this.scissorTestSwitch.On();
    134             int count = this.switchList.Count;
    135             for (int i = 0; i < count; i++) { this.switchList[i].On(); }
    136 
    137             // 把所有在此之前渲染的内容都推到最远。
    138             // Push all rendered stuff to farest position.
    139             OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT);
    140 
    141             RendererBase renderer = this.Renderer;
    142             if (renderer != null)
    143             {
    144                 renderer.Render(arg);
    145             }
    146 
    147             for (int i = count - 1; i >= 0; i--) { this.switchList[i].Off(); }
    148             this.scissorTestSwitch.Off();
    149             this.viewportSwitch.Off();
    150         }
    151 
    152         /// <summary>
    153         /// 
    154         /// </summary>
    155         protected override void DisposeUnmanagedResources()
    156         {
    157             RendererBase renderer = this.Renderer;
    158             if (renderer != null)
    159             {
    160                 renderer.Dispose();
    161             }
    162         }
    163 
    164         /// <summary>
    165         /// 
    166         /// </summary>
    167         public UIRenderer Self { get { return this; } }
    168 
    169         /// <summary>
    170         /// 
    171         /// </summary>
    172         public UIRenderer Parent { get; set; }
    173 
    174         //ChildList<UIRenderer> children;
    175 
    176         /// <summary>
    177         /// 
    178         /// </summary>
    179         [Editor(typeof(IListEditor<UIRenderer>), typeof(UITypeEditor))]
    180         public ChildList<UIRenderer> Children { get; private set; }
    181     }
    UIRenderer

    叠加/覆盖

    注意在UIRenderer.DoRender(RenderEventArgs arg)中,使用

    1             // 把所有在此之前渲染的内容都推到最远。
    2             // Push all rendered stuff to farest position.
    3             OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT);

    把所有在此之前渲染的内容都推到最远。

    从ILayout的定义中可以看到,控件与控件组成了一个树结构。其根结点是覆盖整个窗口的控件,在渲染UI时处于第一个渲染的位置,然后渲染它的各个子结点代表的控件。这就实现了子控件能够完全覆盖在父控件之上。

    我突然想到了WPF。

    渲染文字

    从TTF文件获取字形

    https://github.com/MikePopoloski/SharpFont)是一个纯C#的解析TTF文件的库,能够代替C++的FreeType。我将其稍作修改,实现了从TTF文件获取任意uncode字形,进而获取字形纹理,实现渲染文字的功能。

    例如下面这几个字形纹理。

    使用FontResource

    FontResource类型封装了使用字形贴图的功能。

    使用方式也非常简单。首先创建一个字体资源对象。

    1 FontResource fontResouce = FontResource.Load(ttfFilename, ' ', (char)126);

    然后交给GLText。

    1 var glText = new GLText(AnchorStyles.Left | AnchorStyles.Top,
    2     new Padding(3, 3, 3, 3), new Size(850, 50), -100, 100, fontResouce);
    3 glText.Initialize();
    4 glText.SetText("The quick brown fox jumps over the lazy dog!");

    GLText在初始化时指定此字体对象包含的二维纹理。

    1         protected override void DoInitialize()
    2         {
    3             base.DoInitialize();
    4 
    5             this.Renderer.SetUniform("fontTexture", this.fontResource.GetSamplerValue());
    6         }

    2016-07-30

    现在我已经废弃了FontResource,改用更简单的实现方式(FontTexture)。

    FontResource需要通过复杂的SharpFont来自行解析TTF文件。我至今没有详细看过SharpFont的代码,因为SharpFont实在太大了。而FontTexture直接借助System.Drawing.Font类型的Font.MeasureString()方法来获取字形的大小,并且可以通过Graphics.DrawString()把字形贴到 Bitmap 对象上。这就解决了获取文字贴图及其UV字典的问题。

    不得不说.net framework自带类库的功能之丰富,简直富可敌国。

    2016-8-3

    如何创建一个对象,然后用UI的方式渲染?

    创建一个对象SomeRenderer时,像普通对象一样,用IBufferable+Renderer的方式创建模型和渲染器(或者用RendererBase,这可以使用Legacy OpenGL)。注意,模型的边界应该是(-0.5, -0.5, -0.5)到(0.5, 0.5, 0.5),即边长为(1, 1, 1)且中心在原点的立方体。如此一来,就可以在SomeRenderer的DoRender()方法里指定对象的缩放比例为:

    1 mat4 model = glm.scale(mat4.identity(), new vec3(this.Size.Width, this.Size.Height, 1));

    这样的缩放比例就可以恰好使得SomeRenderer的模型填满UI的矩形范围。

    总结

    CSharpGL支持控件布局,支持渲染文字了。

    欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL

  • 相关阅读:
    树莓派学习笔记(三)——远程调试树莓派程序(Pycharm实现)
    树莓派学习笔记(一)——系统安装与远程显示
    记 laravel 排除CSRF验证
    thinkPHP5 生成微信小程序二维码 保存在本地
    微信小程序 rich-text 富文本中图片自适应
    Laravel 中自定义 手机号和身份证号验证
    laravel Excel 导入
    微信小程序之页面跳转(tabbar跳转及页面内跳转)
    关于MySQL事务和存储引擎常见FAQ
    微信小程序点击保存图片到本地相册——踩坑
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-26-UI-layout-and-text-rendering.html
Copyright © 2011-2022 走看看