CSharpGL(46)用Billboard绘制头顶文字

本文介绍CSharpGL用Billboard绘制头顶文字的方法。效果如下图所示。

下载

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

固定大小的Billboard

在OpenGL的渲染流水线上,描述顶点位置的坐标,依次要经过object space, world space, view/camera space, clip space, normalized device space, Screen/window space这几个状态。下表列出了各个状态的特点。

Space

Coordinate

feature

object

(x, y, z, 1)

从模型中读取的原始位置(x,y,z),可在shader中编辑

world

(x, y, z, w)

可在shader中编辑

view/camera

(x, y, z, w)

可在shader中编辑

clip

(x, y, z, w)

vertex shader中,赋给gl_Position的值

normalized device

(x, y, z, 1)

上一步的(x, y, z, w)同时除以w。OpenGL自动完成。x, y, z的绝对值小于1时,此顶点在窗口可见范围内。即可见范围为[-1, -1, -1]到[1, 1, 1]。

screen/window

glViewport(x, y, width, height);

glDepthRange(near, far)

窗口左下角为(0, 0)。

上一步的顶点为(-1, -1, z)时,screen上的顶点为(x, y)。

上一步的顶点为(1, 1, z)时,screen上的顶点为(width, height)。

为了让Billboard保持他应有的位置和深度值,object space, world space, view space这三步是必须照常进行的。

在normalized device space这个状态下,[-1,-1,-1]和[1,1,1]之间就是Billboard能显示出来的部分。例如,如果一个Billboard矩形的四个角落,恰好落在(-1,-1)和(1,1)上,那么这个Billboard就会恰好覆盖整个画布。所以,如果知道了Billboard和画布的尺寸(像素值),就可以按比例计算出Billboard在此状态时应有的尺寸了。

这两段分析就是下面的vertex shader的精髓。Billboard的位置,由一个位于矩形中心的点表示。在object space里,这个点自然要位于(0, 0, 0, 1)。

  1. #version core
  2.  
  3. uniform mat4 projectionMatrix;
  4. uniform mat4 viewMatrix;
  5. uniform mat4 modelMatrix;
  6. uniform vec2 screenSize; // screen size in pixels.
  7.  
  8. uniform float width; // Billboard’s width in pixels
  9. uniform float height;// Billboard’s height in pixels.
  10.  
  11. in vec2 inPosition;// character's quad's position relative to left bottom(0, 0).
  12. in vec3 inSTR;// character's quad's texture coordinate.
  13.  
  14. out vec3 passSTR;
  15.  
  16. void main(void) {
  17. vec4 position = projectionMatrix * viewMatrix * modelMatrix * vec4(, , , );
  18. position = position / position.w;// 代替OpenGL pipeline除以w的步骤。
  19. position.xy += (inPosition * height - vec2(width, height)) / screenSize;
  20. gl_Position = position;
  21.  
  22. passSTR = inSTR;
  23. }

绘制文字

首先,你要知道如何准备文字Texture(参考这里)。

然后,根据给定的字符串Text,找到各个char的位置,更新positionBuffer和uvBuffer,更新Billboard的Width和Height。为了减少客户端的计算量,在安排char的位置时,是从左下角(0,0)开始,到右上角(width, height)结束的。不然,就该把char的位置整体移动到以(0,0)为中心了。

下图中,把一个一个字符围起来的框框,说明了文字是如何排列的。

多个Billboard的重叠问题

在Billboard中,为了显示文字,启用了OpenGL的混合(blend)功能。

  1. public static TextBillboardNode Create(int width, int height, int capacity, GlyphServer glyphServer = null)
  2. {
  3. var vs = new VertexShader(vertexCode);// this vertex shader has no vertex attributes.
  4. var fs = new FragmentShader(fragmentCode);
  5. var provider = new ShaderArray(vs, fs);
  6. var map = new AttributeMap();
  7. map.Add(inPosition, GlyphsModel.position);
  8. map.Add(inSTR, GlyphsModel.STR);
  9. // 启用混合功能
  10. var blendState = new BlendState(BlendingSourceFactor.SourceAlpha, BlendingDestinationFactor.OneMinusSourceAlpha);
  11. var builder = new RenderMethodBuilder(provider, map, blendState);
  12. var node = new TextBillboardNode(width, height, new GlyphsModel(capacity), builder, glyphServer);
  13. node.Initialize();
  14.  
  15. return node;
  16. }

由于blend功能是与渲染顺序相关的(即渲染顺序不同,产生的结果就可能不同),所以在渲染多个Billboard时,就可能产生不好的效果:近处的Billboard可能遮挡住远处的。

为了解决这个问题,我想了一个办法:先按深度给各个Billboard排序,然后依序渲染各个Billboard。为此,需要新建一些东西。

排序动作BillboardSortAction

首先要将各个Billboard排序,并保存到数组。显然,在这里,使用二分插入排序是最快的排序方式。

  1. public class BillboardSortAction : DependentActionBase
  2. {
  3. private List<float> depthList = new List<float>();
  4. private List<TextBillboardNode> billboardList = new List<TextBillboardNode>();
  5.  
  6. /// <summary>
  7. /// Sorted billboard list.
  8. /// </summary>
  9. public List<TextBillboardNode> BillboardList
  10. {
  11. get { return billboardList; }
  12. }
  13.  
  14. /// <summary>
  15. /// Sort billboards in depth order.
  16. /// </summary>
  17. /// <param name="scene"></param>
  18. public BillboardSortAction(Scene scene) : base(scene) { }
  19.  
  20. public override void Act()
  21. {
  22. this.depthList.Clear();
  23. this.billboardList.Clear();
  24.  
  25. mat4 viewMatrix = this.Scene.Camera.GetViewMatrix();
  26. this.Sort(this.Scene.RootElement, viewMatrix);
  27. }
  28.  
  29. private void Sort(SceneNodeBase sceneElement, mat4 viewMatrix)
  30. {
  31. if (sceneElement != null)
  32. {
  33. var billboard = sceneElement as TextBillboardNode;
  34. if (billboard != null)
  35. {
  36. Insert(billboard, viewMatrix);
  37. }
  38.  
  39. foreach (var item in sceneElement.Children)
  40. {
  41. this.Sort(item, viewMatrix);
  42. }
  43. }
  44. }
  45.  
  46. /// <summary>
  47. /// binary insertion sort.
  48. /// </summary>
  49. /// <param name="billboard"></param>
  50. /// <param name="camera"></param>
  51. /// <param name="list"></param>
  52. private void Insert(TextBillboardNode billboard, mat4 viewMatrix)
  53. {
  54. // viewPosition.z is depth in view/camera space.
  55. vec3 viewPosition = billboard.GetAbsoluteViewPosition(viewMatrix);
  56. int left = , right = this.depthList.Count - ;
  57. while (left <= right)
  58. {
  59. int middle = (left + right) / ;
  60. float value = this.depthList[middle];
  61. if (value < viewPosition.z)
  62. {
  63. left = middle + ;
  64. }
  65. else if (value == viewPosition.z)
  66. {
  67. left = middle;
  68. break;
  69. }
  70. else //(viewPosition.z < value)
  71. {
  72. right = middle - ;
  73. }
  74. }
  75.  
  76. this.depthList.Insert(left, viewPosition.z);
  77. this.billboardList.Insert(left, billboard);
  78. }
  79. }

BillboardSortAction

渲染动作BillboardRenderAction

虽然我们有专门的渲染动作RenderAction,但是RenderAction只会按结点的树结构顺次渲染。因此,我们要新建一个专门渲染已经排序好了的Billboard数组的动作。

  1. /// <summary>
  2. /// Render sorted billboards.
  3. /// </summary>
  4. public class BillboardRenderAction : DependentActionBase
  5. {
  6. private BillboardSortAction sortAction;
  7. public BillboardRenderAction(Scene scene, BillboardSortAction sortAction)
  8. : base(scene)
  9. {
  10. this.sortAction = sortAction;
  11. }
  12.  
  13. public override void Act()
  14. {
  15. var arg = new RenderEventArgs(this.Scene, this.Scene.Camera);
  16. foreach (var item in this.sortAction.BillboardList)
  17. {
  18. item.RenderBeforeChildren(arg);
  19. }
  20. }
  21. }
  22. }

当然,不要忘了取消Billboard在RenderAction里的渲染动作。

  1. var billboard = TextBillboardNode.Create(, , );
  2. billboard.Text = string.Format("Hello TextBillboardNode[{0}]!", index);
  3. // we don't render it in RenderAction. we render it in BillboardRenderAction.
  4. billboard.EnableRendering = ThreeFlags.None;

总结

又一次,又一次,又一次,犯了很二的错误。

TextBillboardNode.cs是复制过来的,然后我就忘记了修改里面的AttributeMap的数据。原本2个小时就能完成的东西,花了2天才找到错误所在。

这个事情告诉我,即使很类似的代码,也不要复制过来。一点一点写才是最快的。

SharpGL(46)用Billboard绘制头顶文字的更多相关文章

  1. CSharpGL(46)用Billboard绘制头顶文字

    CSharpGL(46)用Billboard绘制头顶文字 本文介绍CSharpGL用Billboard绘制头顶文字的方法.效果如下图所示. 下载 CSharpGL已在GitHub开源,欢迎对OpenG ...

  2. 在Arcscene绘制管线三维横断面(AE绘制三维点阵文字)

    根据数据信息动态生成三维管线及横断面表格.效果图如下: 在获取信息后,直接构造点阵进行文字绘制即可. 绘制IElement代码: /// <summary> /// 绘制三维文字 /// ...

  3. 使用 NGUI 实现头顶文字及血条

    以下是 NGUI HUD Text 实现的: 基本原理: 1. 在角色头顶绑一个点 Pivot,用于对齐 2. 因为界面总是覆盖在人物头顶信息的上面,所以将 UIRoot 分为2个 Panel:1) ...

  4. 测试canvas绘制旋转文字的性能

    canvas 绘制各种动画效果时,我们经常会使用画布旋转,使绘制上去的元素有旋转的效果. 最近在项目中碰到了很严重的性能问题,经常排查发现是因为绘制批量文字时使用了画布旋转,且每行文字的旋转角度是不一 ...

  5. canvas一周一练 -- canvas绘制立体文字(2)

    运行效果: <!DOCTYPE html> <html> <head> </head> <body> <canvas id=" ...

  6. Canvas里绘制矩阵文字

    效果如下 实现方法: [ [0,0,1,1,1,0,0], [0,1,1,0,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1 ...

  7. C#利用GDI+绘制旋转文字等效果

    C#中利用GDI+绘制旋转文本的文字,网上有很多资料,基本都使用矩阵旋转的方式实现.但基本都只提及按点旋转,若要实现在矩形范围内旋转文本,资料较少.经过琢磨,可以将矩形内旋转转化为按点旋转,不过需要经 ...

  8. 基本形状的绘制&添加文字

    本次用opencv在图像上绘制了线,矩形,椭圆,圆的形状和放置了文字. #include<iostream> using namespace std; using namespace cv ...

  9. CAD绘制单行文字(网页版)

    在CAD设计时,需要绘制文字,用户可以设置设置绘制文字的高度等属性. 主要用到函数说明: _DMxDrawX::DrawText 绘制一个单行文字.详细说明如下: 参数 说明 DOUBLE dPosX ...

随机推荐

  1. Java总结篇:Java多线程

    Java总结篇系列:Java多线程 多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: ...

  2. 通过npm写一个cli命令行工具

    前言 如果你想写一个npm插件,如果你想通过命令行来简化自己的操作,如果你也是个懒惰的人,那么这篇文章值得一看. po主的上一篇文章介绍了定制自己的模版,但这样po主还是不满足啊,项目中我们频繁的需要 ...

  3. window开启remote desktop服务

    确定自己的PC支持远程桌面   1 先确定被遥控的电脑的系统必须是Professional或Enterprise以上版本,家庭版不支持远程桌面.以Win8.1(7和8同理)为例,依次打开控制面板→系统 ...

  4. zoj 1889 ones 数学

    Ones Time Limit: 2 Seconds      Memory Limit: 65536 KB Given any integer 0 <= n <= 10000 not d ...

  5. PhoneWindow,ViewRoot,Activity之间的大致关系

    http://www.nowamagic.net/academy/detail/50160216 在android里,我们都知道activity.但是一个activity跟一个Window是一个什么关 ...

  6. win10 删除设备和驱动器中你不要的图标

    设备和驱动器可能有很多你不想要的东西,360云盘,百度网盘,微云-- 删除设备和驱动器中的百度云图标,360网盘图标,要去注册表 运行 regedit 点开 HKEY_CURRENT_USER\SOF ...

  7. git镜像仓库

    有时候我们会把一些仓库放到本地,当他更新的时候,可以使用简单命名更新他. 不是所有时间我们都有网,所以把远程的仓库作为镜像,可以方便我们查看 普通的git clone不能下载所有分支,想要简单的git ...

  8. C++每次读取一行字符串输入(学习笔记) (转)

    1. 面向行的输入:getline()getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾. cin.getline(Arr, 20) // Arr为用来输入行的数组的名称:   ...

  9. 2017上海QCon之旅总结(上)

    本来这个公众号的交流消息中间件相关的技术的.这周去上海参加了QCon,第一次参加这样的技术会议,感受挺多的,所以整理一下自己的一些想法接公众号和大家交流一下. 下面进入正题,从自己参加了的一些分享中挑 ...

  10. (转)利用JConsole工具监控java程序内存和JVM

    转自:http://www.cnblogs.com/luihengk/p/5446279.html 一.找到java应用程序对应的进程PI 性能测试应用程序访问地址:http://192.168.29 ...