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     }

總結

本文還沒有徹底解決血條型文字"在屏幕上大小不變"的問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM