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

posted @ 2016-06-05 14:47  BIT祝威  阅读(3257)  评论(3编辑  收藏  举报