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

效果图

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

下载

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

UI控件布局关键点

ILayout

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

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

     /// <summary>
/// Supports layout UI element in OpenGL canvas.
/// 实现在OpenGL窗口中的UI布局
/// </summary>
public interface ILayout : ITreeNode<UIRenderer>
{
//event EventHandler afterLayout; /// <summary>
/// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent.
/// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para>
/// </summary>
System.Windows.Forms.AnchorStyles Anchor { get; set; } /// <summary>
/// Gets or sets the space between viewport and SimpleRect.
/// </summary>
System.Windows.Forms.Padding Margin { get; set; } /// <summary>
/// 相对于Parent左下角的位置(Left Down location)
/// </summary>
System.Drawing.Point Location { get; set; } /// <summary>
/// Stores width when <see cref="Anchor"/>.Left &amp; <see cref="Anchor"/>.Right is <see cref="Anchor"/>.None.
/// <para> and height when <see cref="Anchor"/>.Top &amp; <see cref="Anchor"/>.Bottom is <see cref="Anchor"/>.None.</para>
/// </summary>
System.Drawing.Size Size { get; set; } /// <summary>
///
/// </summary>
System.Drawing.Size ParentLastSize { get; set; } /// <summary>
///
/// </summary>
int zNear { get; set; } /// <summary>
///
/// </summary>
int zFar { get; set; } }

实现在OpenGL窗口中的UI布局

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

         /// <summary>
/// layout controls in OpenGL canvas.
/// <para>This coordinate system is as below.</para>
/// <para> /\ y</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |-----------------&gt;x</para>
/// <para>(0, 0)</para>
/// </summary>
/// <param name="uiRenderer"></param>
internal static void Layout(this ILayout uiRenderer)
{
ILayout parent = uiRenderer.Parent;
if (parent != null)
{
uiRenderer.Self.DoBeforeLayout();
NonRootNodeLayout(uiRenderer, parent);
uiRenderer.Self.DoAfterLayout();
} foreach (var item in uiRenderer.Children)
{
item.Layout();
} if (parent != null)
{
uiRenderer.ParentLastSize = parent.Size;
}
} /// <summary>
/// leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
/// </summary>
private const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right); /// <summary>
/// topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
/// </summary>
private const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom); /// <summary>
/// Gets <paramref name="currentNode"/>'s location and size according to its state and parent's information.
/// </summary>
/// <param name="currentNode"></param>
/// <param name="parent"></param>
private static void NonRootNodeLayout(ILayout currentNode, ILayout parent)
{
int x, y, width, height;
if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
width = parent.Size.Width - currentNode.Margin.Left - currentNode.Margin.Right;
//width = currentNode.Size.Width + (parent.Size.Width - currentNode.ParentLastSize.Width);
if (width < ) { width = ; }
}
else
{
width = currentNode.Size.Width;
} if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
height = parent.Size.Height - currentNode.Margin.Top - currentNode.Margin.Bottom;
//height = currentNode.Size.Height + (parent.Size.Height - currentNode.ParentLastSize.Height);
if (height < ) { height = ; }
}
else
{
height = currentNode.Size.Height;
} if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.None)
{
x = (int)(
(parent.Size.Width - width)
* ((double)currentNode.Margin.Left / (double)(currentNode.Margin.Left + currentNode.Margin.Right)));
}
else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Left)
{
x = parent.Location.X + currentNode.Margin.Left;
}
else if ((currentNode.Anchor & leftRightAnchor) == AnchorStyles.Right)
{
x = parent.Location.X + parent.Size.Width - currentNode.Margin.Right - width;
}
else if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
x = parent.Location.X + currentNode.Margin.Left;
}
else
{ throw new Exception("uiRenderer should not happen!"); } if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.None)
{
y = (int)(
(parent.Size.Height - height)
* ((double)currentNode.Margin.Bottom / (double)(currentNode.Margin.Bottom + currentNode.Margin.Top)));
}
else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
{
//y = currentNode.Margin.Bottom;
y = parent.Location.Y + currentNode.Margin.Bottom;
}
else if ((currentNode.Anchor & topBottomAnchor) == AnchorStyles.Top)
{
//y = parent.Size.Height - height - currentNode.Margin.Top;
y = parent.Location.Y + parent.Size.Height - currentNode.Margin.Top - height;
}
else if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
//y = currentNode.Margin.Top + parent.Location.Y;
y = parent.Location.Y + currentNode.Margin.Bottom;
}
else
{ throw new Exception("This should not happen!"); } currentNode.Location = new System.Drawing.Point(x, y);
currentNode.Size = new Size(width, height);
}

public static void Layout(this ILayout uiRenderer)

glViewport/glScissor

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

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

     /// <summary>
/// Renderer that supports UI layout.
/// 支持2D UI布局的渲染器
/// </summary>
public class UIRenderer : RendererBase, ILayout
{
private ViewportSwitch viewportSwitch;
private ScissorTestSwitch scissorTestSwitch;
private GLSwitchList switchList = new GLSwitchList(); /// <summary>
///
/// </summary>
public GLSwitchList SwitchList
{
get { return switchList; }
} /// <summary>
/// triggered before layout in <see cref="ILayout"/>.Layout().
/// </summary>
public event EventHandler BeforeLayout;
/// <summary>
/// triggered after layout in <see cref="ILayout"/>.Layout().
/// </summary>
public event EventHandler AfterLayout; internal void DoBeforeLayout()
{
EventHandler BeforeLayout = this.BeforeLayout;
if (BeforeLayout != null)
{
BeforeLayout(this, null);
}
} internal void DoAfterLayout()
{
EventHandler AfterLayout = this.AfterLayout;
if (AfterLayout != null)
{
AfterLayout(this, null);
}
} /// <summary>
///
/// </summary>
public RendererBase Renderer { get; protected set; }
/// <summary>
///
/// </summary>
/// <param name="anchor"></param>
/// <param name="margin"></param>
/// <param name="size"></param>
/// <param name="zNear"></param>
/// <param name="zFar"></param>
public UIRenderer(
System.Windows.Forms.AnchorStyles anchor, System.Windows.Forms.Padding margin,
System.Drawing.Size size, int zNear, int zFar)
{
this.Children = new ChildList<UIRenderer>(this);// new ILayoutList(this); this.Anchor = anchor; this.Margin = margin;
this.Size = size; this.zNear = zNear; this.zFar = zFar;
} /// <summary>
///
/// </summary>
public System.Windows.Forms.AnchorStyles Anchor { get; set; } /// <summary>
///
/// </summary>
public System.Windows.Forms.Padding Margin { get; set; } /// <summary>
///
/// </summary>
public System.Drawing.Point Location { get; set; } /// <summary>
///
/// </summary>
public System.Drawing.Size Size { get; set; }
/// <summary>
///
/// </summary>
public System.Drawing.Size ParentLastSize { get; set; } /// <summary>
///
/// </summary>
public int zNear { get; set; } /// <summary>
///
/// </summary>
public int zFar { get; set; } /// <summary>
///
/// </summary>
protected override void DoInitialize()
{
this.viewportSwitch = new ViewportSwitch();
this.scissorTestSwitch = new ScissorTestSwitch(); RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Initialize();
}
} /// <summary>
///
/// </summary>
/// <param name="arg"></param>
protected override void DoRender(RenderEventArg arg)
{
this.viewportSwitch.X = this.Location.X;
this.viewportSwitch.Y = this.Location.Y;
this.viewportSwitch.Width = this.Size.Width;
this.viewportSwitch.Height = this.Size.Height;
this.scissorTestSwitch.X = this.Location.X;
this.scissorTestSwitch.Y = this.Location.Y;
this.scissorTestSwitch.Width = this.Size.Width;
this.scissorTestSwitch.Height = this.Size.Height; this.viewportSwitch.On();
this.scissorTestSwitch.On();
int count = this.switchList.Count;
for (int i = ; i < count; i++) { this.switchList[i].On(); } // 把所有在此之前渲染的内容都推到最远。
// Push all rendered stuff to farest position.
OpenGL.Clear(OpenGL.GL_DEPTH_BUFFER_BIT); RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Render(arg);
} for (int i = count - ; i >= ; i--) { this.switchList[i].Off(); }
this.scissorTestSwitch.Off();
this.viewportSwitch.Off();
} /// <summary>
///
/// </summary>
protected override void DisposeUnmanagedResources()
{
RendererBase renderer = this.Renderer;
if (renderer != null)
{
renderer.Dispose();
}
} /// <summary>
///
/// </summary>
public UIRenderer Self { get { return this; } } /// <summary>
///
/// </summary>
public UIRenderer Parent { get; set; } //ChildList<UIRenderer> children; /// <summary>
///
/// </summary>
[Editor(typeof(IListEditor<UIRenderer>), typeof(UITypeEditor))]
public ChildList<UIRenderer> Children { get; private set; }
}

UIRenderer

叠加/覆盖

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

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

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

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

我突然想到了WPF。

渲染文字

从TTF文件获取字形

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

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

使用FontResource

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

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

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

然后交给GLText。

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

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

         protected override void DoInitialize()
{
base.DoInitialize(); this.Renderer.SetUniform("fontTexture", this.fontResource.GetSamplerValue());
}

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()方法里指定对象的缩放比例为:

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

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

总结

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

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

CSharpGL(26)在opengl中实现控件布局/渲染文字的更多相关文章

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

    CSharpGL(6)在OpenGL中绘制UI元素 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo,更适合入 ...

  2. wxPython中基本控件学习

    wxPython工具包提供了多种不同的窗口部件,包括了本章所提到的基本控件.我们涉及静态文本.可编辑的文本.按钮.微调.滑块.复选框.单选按钮.选择器.列表框.组合框和标尺.对于每种窗口部件,我们将提 ...

  3. WPF中TreeView控件SelectedItemChanged方法的MVVM绑定

    问题描述:左侧treeview控件中点击不同类别的节点时,右侧的页面会显示不同的权限.比如对于My Publications,拥有Modify和Delete两种权限,对于My Subscription ...

  4. WPF中Ribbon控件的使用

    这篇博客将分享如何在WPF程序中使用Ribbon控件.Ribbon可以很大的提高软件的便捷性. 上面截图使Outlook 2010的界面,在Home标签页中,将所属的Menu都平铺的布局,非常容易的可 ...

  5. Android线程中设置控件

    在Android中经常出现多线程中设置控件的值报错的情况,今天教大家封装一个简单的类避免这样的问题,同样也调用实现也非常的方便. 自定义类: /** * Created by wade on 2016 ...

  6. [转载]ASP.NET中TextBox控件设立ReadOnly="true"后台取不到值

    原文地址:http://www.cnblogs.com/yxyht/archive/2013/03/02/2939883.html ASP.NET中TextBox控件设置ReadOnly=" ...

  7. Android中ListView控件的使用

    Android中ListView控件的使用 ListView展示数据的原理 在Android中,其实ListView就相当于web中的jsp,Adapter是适配器,它就相当于web中的Servlet ...

  8. .net dataGridView当鼠标经过时当前行背景色变色;然后【给GridView增加单击行事件,并获取单击行的数据填充到页面中的控件中】

    1.首先在前台dataGridview属性中增加onRowDataBound属性事件 2.然后在后台Observing_RowDataBound事件中增加代码 protected void Obser ...

  9. 041. asp.net中内容页访问母版页中的控件

    母版页运行机制: 用户通过输入内容也的URL来请求某个页面, 获取该页面后, 读取@Page指令, 如果该指令引用了一个母版页, 则也读取该母版页, 如果也是第一次请求这两个页面, 则母版页和被请求的 ...

随机推荐

  1. Microservice Anti-patterns

    在最近的一次Microservices Practitioner Summit中,原Netflix工程师介绍了一种越来越常见的对Microservice的误用.简单地说,大家在搭建一个基于Micros ...

  2. SSH实战 · 唯唯乐购项目(下)

    后台模块 一:后台用户模块 引入后台管理页面 创建adminuser表: CREATE TABLE `adminuser` (   `uid` int(11) NOT NULL AUTO_INCREM ...

  3. Ajax 概念 分析 举例

    Ajax是结合了访问数据库,数据访问,Jquery 可以做页面局部刷新或者说是页面不刷新,我可以让页面不刷新,仅仅是数据的刷新,没有频繁的刷页面,是现在比较常用的一种方式做页面那么它是怎么实现页面无刷 ...

  4. load和initialize方法

      一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使 ...

  5. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  6. Oracle数据库该如何着手优化一个SQL

    这是个终极问题,因为优化本身的复杂性实在是难以总结的,很多时候优化的方法并不是用到了什么高深莫测的技术,而只是一个思想意识层面的差异,而这些都很可能连带导致性能表现上的巨大差异. 所以有时候我们应该先 ...

  7. web api接口同步和异步的问题

    一般来说,如果一个api 接口带上Task和 async 一般就算得上是异步api接口了. 如果我想使用异步api接口,一般的动机是我在我的方法里面可能使用Task.Run 进行异步的去处理一个耗时的 ...

  8. prometheus监控系统

    关于Prometheus Prometheus是一套开源的监控系统,它将所有信息都存储为时间序列数据:因此实现一种Profiling监控方式,实时分析系统运行的状态.执行时间.调用次数等,以找到系统的 ...

  9. BPM配置故事之案例3-参与者与数据自动加载

    这才过了两天,阿海又来了. 阿海:公司决定改进管理方式,以后物资申请的申请人和申请部门要写具体使用人的名字和部门了. 小明:不是要让我改回去吧? 阿海:那太麻烦了,你能不能把申请人改成选择,选好人自动 ...

  10. Android Studio切换为eclipse的快捷键之后还是有区别的部分快捷键

    Android Studio Eclipse 把代码提示换成了Class Name Completion, 快捷键是Ctrl+Alt+Space(空格键). 代码提示快捷键Alt+/,         ...