zoukankan      html  css  js  c++  java
  • CSharpGL(46)用Billboard绘制头顶文字

    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 330 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(0, 0, 0, 1);
    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 = 0, right = this.depthList.Count - 1;
    57             while (left <= right)
    58             {
    59                 int middle = (left + right) / 2;
    60                 float value = this.depthList[middle];
    61                 if (value < viewPosition.z)
    62                 {
    63                     left = middle + 1;
    64                 }
    65                 else if (value == viewPosition.z)
    66                 {
    67                     left = middle;
    68                     break;
    69                 }
    70                 else //(viewPosition.z < value)
    71                 {
    72                     right = middle - 1;
    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(200, 40, 100);
    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天才找到错误所在。

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

  • 相关阅读:
    硝烟中的Scrum和XP-我们如何实施Scrum 12)发布计划 13)组合XP
    php array key 的存储规则
    IE下单选按钮隐藏后点击对应label无法选中的bug解决
    使用QML自绘页面导航条
    unity 获取物体尺寸
    输出众数,输出超限问题
    HTTP请求具体解释
    app 设计原则 ,步骤
    Distinct Subsequences
    OSX: 禁止iCloud钥匙链?
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/SharpGL-46-text-billboard.html
Copyright © 2011-2022 走看看