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. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  2. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  3. HTML5 input元素新的特性

    在HTML5中,<input>元素增加了许多新的属性.方法及控件.本文章分别对这三方面进行介绍. 目录 1. 属性 2. 方法 3. 新控件 1. 属性 <input>元素在H ...

  4. ExtJS 4.2 组件介绍

    目录 1. 介绍 1.1 说明 1.2 组件分类 1.3 组件名称 1.4 组件结构 2. 组件的创建方式 2.1 Ext.create()创建 2.2 xtype创建 1. 介绍 1.1 说明 Ex ...

  5. mysql 学习总结

    MYSQL的增.删.查.改   注册.授权 #创建一个对数据库中的表有一些操作权限的用户,其中OPERATION可以用all privileges替换,DBNAME.TABLENAME可以用*替换,表 ...

  6. 【开源】.Net 分布式服务中心

    分布式服务中心 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ServiceCenter 当垂直应用越来越多,应用之间交互不可避免,将 ...

  7. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  8. C#——传值参数(1)

    //我的C#是跟着猛哥(刘铁猛)(算是我的正式老师)<C#语言入门详解>学习的,微信上猛哥也给我讲解了一些不懂得地方,对于我来说简直是一笔巨额财富,难得良师! 这次与大家一起学习C#中的值 ...

  9. TYPESDK手游聚合SDK服务端设计思路与架构之二:服务端设计

    在前一篇文中,我们对一个聚合SDK服务端所需要实现的功能作了简单的分析.通过两个主要场景的功能流程图,我们可以看到,作为多款游戏要适配多个渠道的统一请求转发中心,TYPESDK服务端主要需要实现的功能 ...

  10. Js 数组返回去重后的数据

    function removeRepeat(data) { var temp = ""; var mainData = []; for (var i = 0; i < dat ...