zoukankan      html  css  js  c++  java
  • CSharpGL(15)用GLSL渲染2种类型的文字

    CSharpGL(15)用GLSL渲染2种类型的文字

    2016-08-13

    由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了。CSharpGL源码中包含10多个独立的Demo,更适合入门参考。

    为了尽可能提升渲染效率,CSharpGL是面向Shader的,因此稍有难度。

    下载

    这个示例是CSharpGL的一部分,CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

    血条

    玩家头顶的血条、名字随着玩家在3D世界移动,但始终朝向摄像机,且在屏幕上的大小不变。

    始终朝向camera

    如何使模型始终朝向camera?

    对模型进行坐标变换,使模型的顶点坐标从物体坐标系变换到世界坐标系再到屏幕坐标系。这过程需要三个矩阵。

    1 gl_Position = projectionMatrix * viewMatrix * modelMatrix * position;

    其中经过viewMatrix后,模型就变换到了世界坐标空间里。那么,只需调整viewMatrix(去掉旋转变换),就不会改变模型的朝向了。

    在4x4矩阵中,旋转变换由左上角的3x3矩阵实现。所以,只要把viewMatrix的3x3矩阵变为单位矩阵即可。

    1 mat4 translateView = mat4(1.0f);//单位矩阵
    2 
    3 for (int t = 0; t < 3; t++) 
    4 { translateView[t].w = viewMatrix[t].w; }
    5 
    6 translateView[3] = viewMatrix[3];
    7 
    8 gl_Position = projectionMatrix * translateView * (modelMatrix * position);

    当然,也可以在C#里直接计算translateView。

     1         private mat4 AlwaysFaceCamera(mat4 viewMatrix)
     2         {
     3             mat4 result = mat4.identity();
     4             for (int i = 0; i < 3; i++)
     5             {
     6                 vec4 v = result[i];
     7                 v.w = viewMatrix[i].w;
     8                 result[i] = v;
     9             }
    10             result[3] = viewMatrix[3];
    11 
    12             return result;
    13         }

     

    在屏幕上的大小不变

    上面解决了始终朝向camera的问题。但是此时的血条仍会在远离camera时和其他模型一样缩小。我希望血条大小保持不变。

    虽然还不能完全说明原理,不过还是做出来了。只需将血条的modelMatrix设置为下面值即可。

     1         /// <summary>
     2         /// 
     3         /// </summary>
     4         /// <param name="length">camera的Position和Target之间的距离</param>
     5         /// <param name="height">血条高度</param>
     6         /// <returns></returns>
     7         private mat4 AlwaysSameSize(float length, float height)
     8         {
     9             mat4 result = glm.translate(glm.scale(mat4.identity(),
    10                 new vec3(length, length, 1)),
    11                 new vec3(0, height / length, 0));
    12 
    13             return result;
    14         }

    大体原理是:血条模型本身必须是中心对称的;摄像机远离其Target时,应该放大血条(glm.scale());之后向上移动一段距离,这段距离与摄像机到Target的距离是减函数关系(我就认为是成反比,具体原因我说不清)。这样才可能保持其大小不变。经过试验,上述AlwaysSameSize()方法是正确的。

    demo

    您可以在下图的例子中观察血条类型的模型是如何实现的。此demo顺便加上了后面要介绍的血条型文字(teapot)和UI文字(Hello Label!)。

    字符串模型

    渲染文字的基本思想很简单:字符都是贴图,贴到Quad上就行了。

    贴图

    所以要准备好贴图。这一步我已经在(http://www.cnblogs.com/bitzhuwei/p/generate-bitmap-from-ttf.html)详细叙述过了。

    模型

    文字模型是在同一平面内的若干个Quad。所以定义其模型如下。

      1     /// <summary>
      2     /// 用于渲染一段文字
      3     /// </summary>
      4     public class StringModel : IModel
      5     {
      6         public sampler2D glyphTexture { get; set; }
      7         public GlyphPosition[] positions { get; set; }
      8         public GlyphColor[] colors { get; set; }
      9         public GlyphTexCoord[] texCoords { get; set; }
     10 
     11         public Objects.VertexBuffers.BufferRenderer GetPositionBufferRenderer(string varNameInShader)
     12         {
     13             using (var buffer = new PositionBuffer(varNameInShader))
     14             {
     15                 buffer.Alloc(positions.Length);
     16                 unsafe
     17                 {
     18                     var array = (GlyphPosition*)buffer.FirstElement();
     19                     for (int i = 0; i < positions.Length; i++)
     20                     {
     21                         array[i] = positions[i];
     22                     }
     23                 }
     24 
     25                 return buffer.GetRenderer();
     26             }
     27         }
     28 
     29         public Objects.VertexBuffers.BufferRenderer GetColorBufferRenderer(string varNameInShader)
     30         {
     31             using (var buffer = new ColorBuffer(varNameInShader))
     32             {
     33                 buffer.Alloc(colors.Length);
     34                 unsafe
     35                 {
     36                     var array = (GlyphColor*)buffer.FirstElement();
     37                     for (int i = 0; i < colors.Length; i++)
     38                     {
     39                         array[i] = colors[i];
     40                     }
     41                 }
     42 
     43                 return buffer.GetRenderer();
     44             }
     45         }
     46 
     47         public Objects.VertexBuffers.BufferRenderer GetTexCoordBufferRenderer(string varNameInShader)
     48         {
     49             using (var buffer = new TexCoordBuffer(varNameInShader))
     50             {
     51                 buffer.Alloc(texCoords.Length);
     52                 unsafe
     53                 {
     54                     var array = (GlyphTexCoord*)buffer.FirstElement();
     55                     for (int i = 0; i < texCoords.Length; i++)
     56                     {
     57                         array[i] = texCoords[i];
     58                     }
     59                 }
     60 
     61                 return buffer.GetRenderer();
     62             }
     63         }
     64 
     65         public Objects.VertexBuffers.BufferRenderer GetNormalBufferRenderer(string varNameInShader)
     66         {
     67             return null;
     68         }
     69 
     70         public Objects.VertexBuffers.BufferRenderer GetIndexes()
     71         {
     72             using (var buffer = new ZeroIndexBuffer(DrawMode.Quads, 0, this.positions.Length * 4))
     73             {
     74                 return buffer.GetRenderer();
     75             }
     76         }
     77 
     78         public struct GlyphPosition
     79         {
     80             public vec2 leftUp;
     81             public vec2 leftDown;
     82             public vec2 rightUp;
     83             public vec2 rightDown;
     84 
     85             public GlyphPosition(
     86                 vec2 leftUp,
     87                 vec2 leftDown,
     88                 vec2 rightUp,
     89                 vec2 rightDown)
     90             {
     91                 this.leftUp = leftUp;
     92                 this.leftDown = leftDown;
     93                 this.rightUp = rightUp;
     94                 this.rightDown = rightDown;
     95             }
     96         }
     97 
     98         public struct GlyphColor
     99         {
    100             public vec4 leftUp;
    101             public vec4 leftDown;
    102             public vec4 rightUp;
    103             public vec4 rightDown;
    104 
    105             public GlyphColor(
    106                 vec4 leftUp,
    107                 vec4 leftDown,
    108                 vec4 rightUp,
    109                 vec4 rightDown)
    110             {
    111                 this.leftUp = leftUp;
    112                 this.leftDown = leftDown;
    113                 this.rightUp = rightUp;
    114                 this.rightDown = rightDown;
    115             }
    116         }
    117 
    118         public struct GlyphTexCoord
    119         {
    120             public vec2 leftUp;
    121             public vec2 leftDown;
    122             public vec2 rightUp;
    123             public vec2 rightDown;
    124 
    125             public GlyphTexCoord(
    126                 vec2 leftUp,
    127                 vec2 leftDown,
    128                 vec2 rightUp,
    129                 vec2 rightDown)
    130             {
    131                 this.leftUp = leftUp;
    132                 this.leftDown = leftDown;
    133                 this.rightUp = rightUp;
    134                 this.rightDown = rightDown;
    135             }
    136         }
    137 
    138         class PositionBuffer : PropertyBuffer<GlyphPosition>
    139         {
    140             public PositionBuffer(string varNameInShader)
    141                 : base(varNameInShader, 2, GL.GL_FLOAT, BufferUsage.StaticDraw)
    142             { }
    143         }
    144         class ColorBuffer : PropertyBuffer<GlyphColor>
    145         {
    146             public ColorBuffer(string varNameInShader)
    147                 : base(varNameInShader, 4, GL.GL_FLOAT, BufferUsage.StaticDraw)
    148             { }
    149         }
    150 
    151         class TexCoordBuffer : PropertyBuffer<GlyphTexCoord>
    152         {
    153             public TexCoordBuffer(string varNameInShader)
    154                 : base(varNameInShader, 2, GL.GL_FLOAT, BufferUsage.StaticDraw)
    155             { }
    156         }
    157     }
    StringModel

    Shader

    vertex shader如下。

     1 #version 150 core
     2 
     3 in vec2 position;
     4 in vec4 color;
     5 out vec4 passColor;
     6 in vec2 texCoord;
     7 out vec2 passTexCoord;
     8 uniform mat4 mvp;
     9 
    10 void main(void)
    11 {
    12     gl_Position = mvp * vec4(position, 0.0f, 1.0f);
    13     passColor = color;
    14     passTexCoord = texCoord;
    15 }

     

    fragment shader如下。

     1 #version 150 core
     2 
     3 in vec4 passColor;
     4 in vec2 passTexCoord;
     5 uniform sampler2D glyphTexture;
     6 out vec4 outputColor;
     7 
     8 void main(void)
     9 {
    10     float transparency = texture(glyphTexture, passTexCoord).r;
    11     if (transparency == 0.0f)
    12     {
    13         discard;
    14     }
    15     else
    16     {
    17         outputColor = vec4(1, 1, 1, transparency) * passColor;
    18     }
    19 }

    根据字符串创建模型

    给定一个字符串,我们可以计算出相应的模型。

     1     public static class DummyStringModelFactory
     2     {
     3         /// <summary>
     4         /// 简单地生成一行文字。
     5         /// </summary>
     6         /// <param name="content"></param>
     7         /// <returns></returns>
     8         public static StringModel GetModel(this string content)
     9         {
    10             StringModel model = new StringModel();
    11 
    12             var glyphPositions = new StringModel.GlyphPosition[content.Length];
    13             FontResource fontResource = CSharpGL.GlyphTextures.FontResource.Default;
    14             var glyphTexCoords = new StringModel.GlyphTexCoord[content.Length];
    15             //fontResource.GenerateBitmapForString(content, 10, 10000);
    16             int currentWidth = 0; int currentHeight = 0;
    17             /*
    18              * 0     3  4     6 8     11 12   15
    19              * -------  ------- -------  -------
    20              * |     |  |     | |     |  |     |
    21              * |     |  |     | |     |  |     |
    22              * |     |  |     | |     |  |     |
    23              * -------  ------- -------  -------
    24              * 1     2  5     6 9     10 13   14 
    25              */
    26             for (int i = 0; i < content.Length; i++)
    27             {
    28                 char ch = content[i];
    29                 CharacterInfo info = fontResource.CharInfoDict[ch];
    30                 glyphPositions[i] = new StringModel.GlyphPosition(
    31                     new GLM.vec2(currentWidth, currentHeight + fontResource.FontHeight),
    32                     new GLM.vec2(currentWidth, currentHeight),
    33                     new GLM.vec2(currentWidth + info.width, currentHeight),
    34                     new GLM.vec2(currentWidth + info.width, currentHeight + fontResource.FontHeight));
    35                 const int shrimp = 2;
    36                 glyphTexCoords[i] = new StringModel.GlyphTexCoord(
    37                     new GLM.vec2((float)(info.xoffset + shrimp) / (float)fontResource.FontBitmap.Width, (float)(currentHeight) / (float)fontResource.FontBitmap.Height),
    38                     new GLM.vec2((float)(info.xoffset + shrimp) / (float)fontResource.FontBitmap.Width, (float)(currentHeight + fontResource.FontHeight) / (float)fontResource.FontBitmap.Height),
    39                     new GLM.vec2((float)(info.xoffset - shrimp + info.width) / (float)fontResource.FontBitmap.Width, (float)(currentHeight + fontResource.FontHeight) / (float)fontResource.FontBitmap.Height),
    40                     new GLM.vec2((float)(info.xoffset - shrimp + info.width) / (float)fontResource.FontBitmap.Width, (float)(currentHeight) / (float)fontResource.FontBitmap.Height)
    41                     );
    42                 currentWidth += info.width + 10;
    43             }
    44             // move to center
    45             for (int i = 0; i < content.Length; i++)
    46             {
    47                 StringModel.GlyphPosition position = glyphPositions[i];
    48 
    49                 position.leftUp.x -= currentWidth / 2;
    50                 position.leftDown.x -= currentWidth / 2;
    51                 position.rightUp.x -= currentWidth / 2;
    52                 position.rightDown.x -= currentWidth / 2;
    53                 position.leftUp.y -= (currentHeight + fontResource.FontHeight) / 2;
    54                 position.leftDown.y -= (currentHeight + fontResource.FontHeight) / 2;
    55                 position.rightUp.y -= (currentHeight + fontResource.FontHeight) / 2;
    56                 position.rightDown.y -= (currentHeight + fontResource.FontHeight) / 2;
    57 
    58                 position.leftUp.x /= (currentHeight + fontResource.FontHeight);
    59                 position.leftDown.x /= (currentHeight + fontResource.FontHeight);
    60                 position.rightUp.x /= (currentHeight + fontResource.FontHeight);
    61                 position.rightDown.x /= (currentHeight + fontResource.FontHeight);
    62                 position.leftUp.y /= (currentHeight + fontResource.FontHeight);
    63                 position.leftDown.y /= (currentHeight + fontResource.FontHeight);
    64                 position.rightUp.y /= (currentHeight + fontResource.FontHeight);
    65                 position.rightDown.y /= (currentHeight + fontResource.FontHeight);
    66                 glyphPositions[i] = position;
    67             }
    68 
    69             var glyphColors = new StringModel.GlyphColor[content.Length];
    70             for (int i = 0; i < glyphColors.Length; i++)
    71             {
    72                 glyphColors[i] = new StringModel.GlyphColor(
    73                     new GLM.vec4(0, 0, 0, 1),
    74                     new GLM.vec4(0, 0, 0, 1),
    75                     new GLM.vec4(0, 0, 0, 1),
    76                     new GLM.vec4(0, 0, 0, 1)
    77                     );
    78             }
    79 
    80             model.positions = glyphPositions;
    81             model.texCoords = glyphTexCoords;
    82             model.colors = glyphColors;
    83             model.glyphTexture = FontTextureManager.Instance.GetTexture2D(fontResource.FontBitmap);
    84 
    85             return model;
    86         }
    87     }
    public static StringModel GetModel(this string content);

    标签(Label)

    在OpenGL场景中,像Winform里的标签(Label)一样的控件如何实现?如何在UI空间内渲染大量文字?

    之前实现过的"标签"是用point sprite做的,其大小范围有限,最多到256x256个像素。当时不用GLSL+VBO,是因为那会还不知道如何使模型始终朝向camera。

    现在在StringModel的基础上,只需借助IUILayout机制即可实现opengl里的标签控件。

     1     public class DummyLabel : RendererBase, IUILayout
     2     {
     3         public StringRenderer renderer;
     4 
     5         /// <summary>
     6         /// 
     7         /// </summary>
     8         /// <param name="param">the edges of the viewport to which a SimpleUIRect is bound and determines how it is resized with its parent.
     9         /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para></param>
    10         /// <param name="content"></param>
    11         public DummyLabel(IUILayoutParam param, string content)
    12         {
    13             this.renderer = new StringRenderer(content.GetModel());
    14             
    15             IUILayout layout = this;
    16             layout.Param = param;
    17         }
    18 
    19         protected override void DisposeUnmanagedResources()
    20         {
    21             this.renderer.Dispose();
    22         }
    23 
    24         #region IUILayout
    25 
    26         public IUILayoutParam Param { get; set; }
    27 
    28         #endregion IUILayout
    29 
    30         protected override void DoInitialize()
    31         {
    32             this.renderer.Initialize();
    33         }
    34 
    35         protected override void DoRender(RenderEventArgs e)
    36         {
    37             mat4 projectionMatrix, viewMatrix, modelMatrix;
    38             {
    39                 IUILayout element = this as IUILayout;
    40                 element.GetMatrix(out projectionMatrix, out viewMatrix, out modelMatrix, null);
    41             }
    42             this.renderer.mvp = projectionMatrix * viewMatrix * modelMatrix;
    43 
    44             this.renderer.Render(e);
    45 
    46         }
    47     }

    总结

    本文还没有彻底解决血条型文字"在屏幕上大小不变"的问题。

     

  • 相关阅读:
    ES6-Generator
    ES6-Iterator & for...of循环
    ES6-Proxy and Reflect
    在Main中定义student的结构体,进行年龄从大到小依次排序录入学生信息。(结构体的用法以及冒泡排序)
    函数的调用(取两个整型变量中的最大值)
    将一个字符串数组的元素的顺序进行翻转。。
    枚举类型练习
    利用Arraylist输入学生的成绩,求出平均分和总分。
    简单的推箱子游戏(利用数组)
    枚举类型的声明
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-15-rendering-text-with-opengl.html
Copyright © 2011-2022 走看看