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。
总结
现在能够绘制少量的文字,为其指定的位置的过程与为一个点指定位置的过程是相同的,所以此方式的应用范围还是比较广的。