C#+OpenGL+FreeType顯示3D文字(3) - 用PointSprite繪制文字
上一篇實現了把文字繪制到OpenGL窗口,但實質上只是把含有文字的貼圖貼到矩形模型上。本篇我們介紹用PointSprite繪制文字,這可以只用1個點繪制文字,并確保文字始終面相窗口。用PointSprite繪制的文字,其大小范圍有限,本篇提供的Demo中,Max Row Width最大只有256。現在能夠繪制少量的文字,為其指定的位置的過程與為一個點指定位置的過程是相同的,所以此方式的應用范圍還是比較廣的。
基本流程
與前文相同的是仍然用GLSL+VBO+貼圖來繪制。PointSprite只是Enable了一些OpenGL開關,然后把貼圖貼到一個Point圖元上。
您可以在此下載查看上圖所示的demo。為節省空間,此demo只能顯示ASCII范圍內的字符。實際上它具有顯示所有Unicode字符的能力。
編輯GLSL
我們只需vertex shader和fragment shader。
Vertex shader只是進行最基本的變換操作,并負責傳遞紋理大小。
1 #version 150 core 2 3 in vec3 in_Position; 4 5 uniform mat4 MVP; 6 uniform float pointSize; 7 8 void main(void) { 9 gl_Position = MVP * vec4(in_Position, 1.0); 10 gl_PointSize = pointSize; 11 }
Fragment shader根據紋理坐標所在位置的紋理顏色決定此位置是否顯示(透明與否)。這就繪制出了一個字形。還可以順便用一個uniform vec3 textColor指定文字顏色。
1 #version 150 core 2 3 out vec4 out_Color; 4 5 uniform sampler2D tex; 6 uniform vec3 textColor; 7 8 void main(void) { 9 float transparency = texture2D(tex, gl_PointCoord).r; 10 if (transparency == 0.0f) 11 { 12 discard; 13 } 14 else 15 { 16 out_Color = vec4(1, 1, 1, transparency) * vec4(textColor, 1.0f); 17 } 18 }
設定VAO
模型只需一個Point。
1 private void InitVAO() 2 { 3 GL.GenVertexArrays(1, vao); 4 GL.BindVertexArray(vao[0]); 5 6 // Create a vertex buffer for the vertex data. 7 { 8 UnmanagedArray<vec3> in_Position = new UnmanagedArray<vec3>(1); 9 in_Position[0] = this.position; 10 11 uint[] ids = new uint[1]; 12 GL.GenBuffers(1, ids); 13 GL.BindBuffer(BufferTarget.ArrayBuffer, ids[0]); 14 GL.BufferData(BufferTarget.ArrayBuffer, in_Position, BufferUsage.StaticDraw); 15 GL.VertexAttribPointer(attributeIndexPosition, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero); 16 GL.EnableVertexAttribArray(attributeIndexPosition); 17 } 18 19 // Unbind the vertex array, we've finished specifying data for it. 20 GL.BindVertexArray(0); 21 }
程序生成文字貼圖
要想顯示任意的字符串,必須借助前文的貼圖來一個字符一個字符地拼接出需要的字符串貼圖并用之生成Texture。
這是從上面的圖片中計算出的"bithuwei.cnblogs.com"的貼圖。
由于PointSprite支持的貼圖大小有限(最大256),所以計算字符串貼圖的程序有點繁瑣。

1 /// <summary> 2 /// 為指定的字符串生成貼圖。 3 /// </summary> 4 /// <param name="fontResource"></param> 5 /// <param name="content"></param> 6 /// <param name="fontSize"></param> 7 /// <param name="maxRowWidth"></param> 8 /// <returns></returns> 9 public static System.Drawing.Bitmap GenerateBitmapForString(this FontResource fontResource, 10 string content, int fontSize, int maxRowWidth) 11 { 12 // step 1: get totalLength 13 int totalLength = 0; 14 { 15 int glyphsLength = 0; 16 for (int i = 0; i < content.Length; i++) 17 { 18 char c = content[i]; 19 CharacterInfo cInfo; 20 if (fontResource.CharInfoDict.TryGetValue(c, out cInfo)) 21 { 22 int glyphWidth = cInfo.width; 23 glyphsLength += glyphWidth; 24 } 25 //else 26 //{ throw new Exception(string.Format("Not support for display the char [{0}]", c)); } 27 } 28 29 //glyphsLength = (glyphsLength * this.fontSize / FontResource.Instance.FontHeight); 30 int interval = fontResource.FontHeight / 10; if (interval < 1) { interval = 1; } 31 totalLength = glyphsLength + interval * (content.Length - 1); 32 } 33 34 // step 2: setup contentBitmap 35 System.Drawing.Bitmap contentBitmap = null; 36 { 37 int interval = fontResource.FontHeight / 10; if (interval < 1) { interval = 1; } 38 //int totalLength = glyphsLength + interval * (content.Length - 1); 39 int currentTextureWidth = 0; 40 int currentWidthPos = 0; 41 int currentHeightPos = 0; 42 if (totalLength * fontSize > maxRowWidth * fontResource.FontHeight)// 超過1行能顯示的內容 43 { 44 currentTextureWidth = maxRowWidth * fontResource.FontHeight / fontSize; 45 46 int lineCount = (totalLength - 1) / currentTextureWidth + 1; 47 // 確保整篇文字的高度在貼圖中間。 48 currentHeightPos = (currentTextureWidth - fontResource.FontHeight * lineCount) / 2; 49 //- FontResource.Instance.FontHeight / 2; 50 } 51 else//只在一行內即可顯示所有字符 52 { 53 if (totalLength >= fontResource.FontHeight) 54 { 55 currentTextureWidth = totalLength; 56 57 // 確保整篇文字的高度在貼圖中間。 58 currentHeightPos = (currentTextureWidth - fontResource.FontHeight) / 2; 59 //- FontResource.Instance.FontHeight / 2; 60 } 61 else 62 { 63 currentTextureWidth = fontResource.FontHeight; 64 65 currentWidthPos = (currentTextureWidth - totalLength) / 2; 66 //glyphsLength = fontResource.FontHeight; 67 } 68 } 69 70 //this.textureWidth = textureWidth * this.fontSize / FontResource.Instance.FontHeight; 71 //currentWidthPosition = currentWidthPosition * this.fontSize / FontResource.Instance.FontHeight; 72 //currentHeightPosition = currentHeightPosition * this.fontSize / FontResource.Instance.FontHeight; 73 74 contentBitmap = new Bitmap(currentTextureWidth, currentTextureWidth); 75 Graphics gContentBitmap = Graphics.FromImage(contentBitmap); 76 Bitmap bigBitmap = fontResource.FontBitmap; 77 for (int i = 0; i < content.Length; i++) 78 { 79 char c = content[i]; 80 CharacterInfo cInfo; 81 if (fontResource.CharInfoDict.TryGetValue(c, out cInfo)) 82 { 83 if (currentWidthPos + cInfo.width > contentBitmap.Width) 84 { 85 currentWidthPos = 0; 86 currentHeightPos += fontResource.FontHeight; 87 } 88 89 gContentBitmap.DrawImage(bigBitmap, 90 new Rectangle(currentWidthPos, currentHeightPos, cInfo.width, fontResource.FontHeight), 91 new Rectangle(cInfo.xoffset, cInfo.yoffset, cInfo.width, fontResource.FontHeight), 92 GraphicsUnit.Pixel); 93 94 currentWidthPos += cInfo.width + interval; 95 } 96 } 97 gContentBitmap.Dispose(); 98 //contentBitmap.Save("PointSpriteStringElement-contentBitmap.png"); 99 System.Drawing.Bitmap bmp = null; 100 if (totalLength * fontSize > maxRowWidth * fontResource.FontHeight)// 超過1行能顯示的內容 101 { 102 bmp = (System.Drawing.Bitmap)contentBitmap.GetThumbnailImage( 103 maxRowWidth, maxRowWidth, null, IntPtr.Zero); 104 } 105 else//只在一行內即可顯示所有字符 106 { 107 if (totalLength >= fontResource.FontHeight) 108 { 109 bmp = (System.Drawing.Bitmap)contentBitmap.GetThumbnailImage( 110 totalLength * fontSize / fontResource.FontHeight, 111 totalLength * fontSize / fontResource.FontHeight, 112 null, IntPtr.Zero); 113 114 } 115 else 116 { 117 bmp = (System.Drawing.Bitmap)contentBitmap.GetThumbnailImage( 118 fontSize, fontSize, null, IntPtr.Zero); 119 } 120 } 121 contentBitmap.Dispose(); 122 contentBitmap = bmp; 123 //contentBitmap.Save("PointSpriteStringElement-contentBitmap-scaled.png"); 124 } 125 126 return contentBitmap; 127 128 }
缺陷
用PointSprite繪制的文字,其大小范圍有限,本篇提供的Demo中,Max Row Width最大只有256。
總結
現在能夠繪制少量的文字,為其指定的位置的過程與為一個點指定位置的過程是相同的,所以此方式的應用范圍還是比較廣的。
文章列表