标签:
CSharpGL(26)在opengl中实现控件布局/渲染文字
如图所示,可以将文字、坐标轴固定在窗口的一角。
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
类似Winform控件那样,控件的位置、大小由其Anchor等属性决定。窗口大小改变时,控件的位置、大小会随之改变。
所以模仿Control类,直接使用Anchor作为UIRenderer的接口。
1 /// <summary> 2 /// 实现在OpenGL窗口中的UI布局 3 /// </summary> 4 public interface ILayout 5 { 6 /// <summary> 7 /// parent node 8 /// </summary> 9 ILayout Container { get; set; } 10 11 /// <summary> 12 /// children nodes 13 /// </summary> 14 ICollection<ILayout> Controls { get; } 15 16 /// <summary> 17 /// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent. 18 /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para> 19 /// </summary> 20 System.Windows.Forms.AnchorStyles Anchor { get; set; } 21 22 /// <summary> 23 /// Gets or sets the space between viewport and SimpleRect. 24 /// </summary> 25 System.Windows.Forms.Padding Margin { get; set; } 26 27 /// <summary> 28 /// 相对于<see cref="Container"/>左下角的位置(Left Down location) 29 /// </summary> 30 System.Drawing.Point Location { get; set; } 31 32 /// <summary> 33 /// Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left & <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None. 34 /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top & <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para> 35 /// </summary> 36 System.Drawing.Size Size { get; set; } 37 38 int zNear { get; set; } 39 40 int zFar { get; set; } 41 42 }
有了数据结构,就可以实现窗口中的UI布局了。当窗口大小改变时,调用下面的函数。
1 /// <summary> 2 /// 实现在OpenGL窗口中的UI布局 3 /// </summary> 4 /// <param name="uiRenderer"></param> 5 public static void Layout(this ILayout uiRenderer) 6 { 7 if (uiRenderer.Container != null) 8 { 9 NonRootNodeLayout(uiRenderer, uiRenderer.Container); 10 } 11 12 foreach (var item in uiRenderer.Controls) 13 { 14 item.Layout(); 15 } 16 } 17 18 /// <summary> 19 /// leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right); 20 /// </summary> 21 private const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right); 22 23 /// <summary> 24 /// topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom); 25 /// </summary> 26 private const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom); 27 28 private static void NonRootNodeLayout(ILayout currentNode, ILayout parent) 29 { 30 int x, y, width, height; 31 if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor) 32 { 33 width = parent.Size.Width - currentNode.Margin.Left - currentNode.Margin.Right; 34 if (width < 0) { width = 0; } 35 } 36 else 37 { 38 width = currentNode.Size.Width; 39 } 40 41 if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor) 42 { 43 height = parent.Size.Height - currentNode.Margin.Top - currentNode.Margin.Bottom; 44 if (height < 0) { height = 0; } 45 } 46 else 47 { 48 height = currentNode.Size.Height; 49 } 50 51 if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.None) 52 { 53 x = (int)( 54 (parent.Size.Width - width) 55 * ((double)currentNode.Margin.Left / (double)(currentNode.Margin.Left + currentNode.Margin.Right))); 56 } 57 else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Left) 58 { 59 x = currentNode.Margin.Left; 60 } 61 else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Right) 62 { 63 x = parent.Size.Width - width - currentNode.Margin.Right; 64 } 65 else if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor) 66 { 67 x = currentNode.Margin.Left; 68 } 69 else 70 { throw new Exception("uiRenderer should not happen!"); } 71 72 if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.None) 73 { 74 y = (int)( 75 (parent.Size.Height - height) 76 * ((double)currentNode.Margin.Bottom / (double)(currentNode.Margin.Bottom + currentNode.Margin.Top))); 77 } 78 else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Bottom) 79 { 80 y = currentNode.Margin.Bottom; 81 } 82 else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Top) 83 { 84 y = parent.Size.Height - height - currentNode.Margin.Top; 85 } 86 else if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor) 87 { 88 y = currentNode.Margin.Bottom; 89 } 90 else 91 { throw new Exception("This should not happen!"); } 92 93 currentNode.Location = new System.Drawing.Point(x, y); 94 currentNode.Size = new Size(width, height); 95 }
这是避免复杂的矩阵操作,实现稳定的UI布局显示的关键。glViewport指定了GLRenderer在窗口的渲染位置,glScissor将GLRenderer范围之外的部分保护起来。
在渲染之前,根据UIRenderer的位置和大小更新viewport和scissor即可。不再需要为UI固定在窗口某处而煞费苦心地设计projection,view,model矩阵了。
1 /// <summary> 2 /// 支持UI布局的渲染器 3 /// </summary> 4 public class UIRenderer : RendererBase, ILayout 5 { 6 private ViewportSwitch viewportSwitch; 7 private ScissorTestSwitch scissorTestSwitch; 8 9 public Renderer Renderer { get; protected set; } 10 11 public UIRenderer(Renderer renderer, 12 System.Windows.Forms.AnchorStyles anchor, System.Windows.Forms.Padding margin, 13 System.Drawing.Size size, int zNear, int zFar) 14 { 15 this.Controls = new ILayoutCollection(this); 16 17 this.Renderer = renderer; 18 this.Anchor = anchor; this.Margin = margin; 19 this.Size = size; this.zNear = zNear; this.zFar = zFar; 20 } 21 22 public ILayout Container { get; set; } 23 24 public ICollection<ILayout> Controls { get; internal set; } 25 26 public System.Windows.Forms.AnchorStyles Anchor { get; set; } 27 28 public System.Windows.Forms.Padding Margin { get; set; } 29 30 public System.Drawing.Point Location { get; set; } 31 32 public System.Drawing.Size Size { get; set; } 33 34 public int zNear { get; set; } 35 36 public int zFar { get; set; } 37 38 protected override void DoInitialize() 39 { 40 this.viewportSwitch = new ViewportSwitch(); 41 this.scissorTestSwitch = new ScissorTestSwitch(); 42 43 Renderer renderer = this.Renderer; 44 if (renderer != null) 45 { 46 renderer.Initialize(); 47 } 48 } 49 50 protected override void DoRender(RenderEventArgs arg) 51 { 52 Renderer renderer = this.Renderer; 53 if (renderer != null) 54 { 55 this.viewportSwitch.X = this.Location.X; 56 this.viewportSwitch.Y = this.Location.Y; 57 this.viewportSwitch.Width = this.Size.Width; 58 this.viewportSwitch.Height = this.Size.Height; 59 this.scissorTestSwitch.X = this.Location.X; 60 this.scissorTestSwitch.Y = this.Location.Y; 61 this.scissorTestSwitch.Width = this.Size.Width; 62 this.scissorTestSwitch.Height = this.Size.Height; 63 64 this.scissorTestSwitch.On(); 65 this.viewportSwitch.On(); 66 67 // 把所有在此之前渲染的内容都推到最远。 68 OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT); 69 70 renderer.Render(arg); 71 72 this.viewportSwitch.Off(); 73 this.scissorTestSwitch.Off(); 74 } 75 } 76 77 protected override void DisposeUnmanagedResources() 78 { 79 Renderer renderer = this.Renderer; 80 if (renderer != null) 81 { 82 renderer.Dispose(); 83 } 84 } 85 }
注意在UIRenderer.DoRender(RenderEventArgs arg)中,使用
1 // 把所有在此之前渲染的内容都推到最远。 2 OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT);
把所有在此之前渲染的内容都推到最远。
从ILayout的定义中可以看到,控件与控件组成了一个树结构。其根结点是覆盖整个窗口的控件,在渲染UI时处于第一个渲染的位置,然后渲染它的各个子结点代表的控件。这就实现了子控件能够完全覆盖在父控件之上。
我突然想到了WPF。
(https://github.com/MikePopoloski/SharpFont)是一个纯C#的解析TTF文件的库,能够代替C++的FreeType。我将其稍作修改,实现了从TTF文件获取任意uncode字形,进而获取字形纹理,实现渲染文字的功能。
例如下面这几个字形纹理。
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 }
CSharpGL支持控件布局,支持渲染文字了。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)
CSharpGL(26)在opengl中实现控件布局/渲染文字
标签:
原文地址:http://www.cnblogs.com/bitzhuwei/p/CSharpGL-26-UI-layout-and-text-rendering.html