C#+OpenGL+FreeType显示3D文字(2) - 用GLSL+VBO绘制文字
上一篇得到了字形贴图及其位置字典(可导出为XML)。本篇就利用此贴图和位置字典,把文字绘制到OpenGL窗口。
基本流程
有了贴图,绘制文字和绘制普通纹理的过程是一样的。我们需要用glTexImage2D设定纹理,然后用GLSL+VBO设置一个长方形,把纹理的某个字形所占据的位置贴到长方形上,就可以绘制一个字符。连续设置多个长方形,就可以显示字符串了。
当然,用legacy opengl里的glVertex和glTexCoord来设置长方形和贴图也可以,不过本文推荐用modern opengl的GLSL+VBO的方式来实现。
您可以。为节省空间,此demo只能显示ASCII范围内的字符。实际上它具有显示所有Unicode字符的能力。
编辑GLSL
我们只需vertex shader和fragment shader。
Vertex shader只是进行最基本的变换操作,并负责传递纹理坐标。
1 #version 120 2 3 attribute vec3 in_Position; 4 attribute vec2 in_TexCoord; 5 varying vec2 texcoord; 6 uniform mat4 projectionMatrix; 7 uniform mat4 viewMatrix; 8 uniform mat4 modelMatrix; 9 10 void main(void) {11 gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1);12 texcoord = in_TexCoord;13 }
Fragment shader根据纹理坐标所在位置的纹理颜色决定此位置是否显示(透明与否)。这就绘制出了一个字形。
1 #version 1202 3 varying vec2 texcoord;4 uniform sampler2D tex;5 uniform vec4 color;6 7 void main(void) {8 gl_FragColor = vec4(1, 1, 1, texture2D(tex, texcoord).r) * color;9 }
设定VAO
每个字符的宽度是不同的,所以每个长方形都要据此调整宽度。下面是根据字符串生成VAO/VBO的片段。
1 private void InitVAO(string value) 2 { 3 if (value == null) { value = string.Empty; } 4 5 this.mode = PrimitiveModes.Quads; 6 this.vertexCount = 4 * value.Length; 7 8 // Create a vertex buffer for the vertex data. 9 UnmanagedArrayin_Position = new UnmanagedArray (this.vertexCount);10 UnmanagedArray in_TexCoord = new UnmanagedArray (this.vertexCount);11 Bitmap bigBitmap = this.ttfTexture.BigBitmap;12 vec3[] tmpPositions = new vec3[this.vertexCount];13 float totalLength = 0;14 for (int i = 0; i < value.Length; i++)15 {16 char c = value[i];17 CharacterInfo cInfo;18 if (this.ttfTexture.CharInfoDict.TryGetValue(c, out cInfo))19 {20 float glyphWidth = (float)cInfo.width / (float)this.ttfTexture.FontHeight;21 if (i == 0)22 {23 tmpPositions[i * 4 + 0] = new vec3(0, 0, 0);24 tmpPositions[i * 4 + 1] = new vec3(glyphWidth, 0, 0);25 tmpPositions[i * 4 + 2] = new vec3(glyphWidth, 1, 0);26 tmpPositions[i * 4 + 3] = new vec3(0, 1, 0);27 }28 else29 {30 tmpPositions[i * 4 + 0] = tmpPositions[i * 4 + 0 - 4 + 1];31 tmpPositions[i * 4 + 1] = tmpPositions[i * 4 + 0] + new vec3(glyphWidth, 0, 0);32 tmpPositions[i * 4 + 3] = tmpPositions[i * 4 + 3 - 4 - 1];33 tmpPositions[i * 4 + 2] = tmpPositions[i * 4 + 3] + new vec3(glyphWidth, 0, 0);34 }35 totalLength += glyphWidth;36 }37 38 }39 for (int i = 0; i < value.Length; i++)40 {41 char c = value[i];42 CharacterInfo cInfo;43 float x1 = 0;44 float x2 = 1;45 float y1 = 0;46 float y2 = 1;47 if (this.ttfTexture.CharInfoDict.TryGetValue(c, out cInfo))48 {49 x1 = (float)cInfo.xoffset / (float)bigBitmap.Width;50 x2 = (float)(cInfo.xoffset + cInfo.width) / (float)bigBitmap.Width;51 y1 = (float)cInfo.yoffset / (float)bigBitmap.Height;52 y2 = (float)(cInfo.yoffset + this.ttfTexture.FontHeight) / (float)bigBitmap.Height;53 }54 55 in_Position[i * 4 + 0] = tmpPositions[i * 4 + 0] - new vec3(totalLength / 2, 0, 0);56 in_Position[i * 4 + 1] = tmpPositions[i * 4 + 1] - new vec3(totalLength / 2, 0, 0);57 in_Position[i * 4 + 2] = tmpPositions[i * 4 + 2] - new vec3(totalLength / 2, 0, 0);58 in_Position[i * 4 + 3] = tmpPositions[i * 4 + 3] - new vec3(totalLength / 2, 0, 0);59 60 in_TexCoord[i * 4 + 0] = new vec2(x1, y2);61 in_TexCoord[i * 4 + 1] = new vec2(x2, y2);62 in_TexCoord[i * 4 + 2] = new vec2(x2, y1);63 in_TexCoord[i * 4 + 3] = new vec2(x1, y1);64 }65 66 GL.GenVertexArrays(1, vao);67 GL.BindVertexArray(vao[0]);68 69 GL.GenBuffers(2, vbo);70 71 uint in_PositionLocation = shaderProgram.GetAttributeLocation(strin_Position);72 GL.BindBuffer(BufferTarget.ArrayBuffer, vbo[0]);73 GL.BufferData(BufferTarget.ArrayBuffer, in_Position, BufferUsage.StaticDraw);74 GL.VertexAttribPointer(in_PositionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);75 GL.EnableVertexAttribArray(in_PositionLocation);76 77 uint in_TexCoordLocation = shaderProgram.GetAttributeLocation(strin_TexCoord);78 GL.BindBuffer(BufferTarget.ArrayBuffer, vbo[1]);79 GL.BufferData(BufferTarget.ArrayBuffer, in_TexCoord, BufferUsage.StaticDraw);80 GL.VertexAttribPointer(in_TexCoordLocation, 2, GL.GL_FLOAT, false, 0, IntPtr.Zero);81 GL.EnableVertexAttribArray(in_TexCoordLocation);82 83 GL.BindVertexArray(0);84 85 in_Position.Dispose();86 in_TexCoord.Dispose();87 }
其它
在上一篇,我们通过TTF文件得到了贴图文件及其位置信息(XML文件)。此时其实不再需要借助freetype就可以直接使用这些贴图了。
另外,本文所给的demo已经包含了perspective和ortho两种透视的camera功能,固定在窗口左下角显示坐标系的功能,感兴趣的话通过反编译即可得到。
总结
现在能够绘制文字了,但是换行之类的高级功能还没有实现。这已经不熟悉opengl的研究范围,而是更高层的功能了,所以暂时不再深入考虑。