C#+OpenGL+FreeType显示3D文字(2) - 用GLSL+VBO绘制文字
上一篇得到了字形贴图及其位置字典(可导出为XML)。本篇就利用此贴图和位置字典,把文字绘制到OpenGL窗口。
基本流程
有了贴图,绘制文字和绘制普通纹理的过程是一样的。我们需要用glTexImage2D设定纹理,然后用GLSL+VBO设置一个长方形,把纹理的某个字形所占据的位置贴到长方形上,就可以绘制一个字符。连续设置多个长方形,就可以显示字符串了。
当然,用legacy opengl里的glVertex和glTexCoord来设置长方形和贴图也可以,不过本文推荐用modern opengl的GLSL+VBO的方式来实现。
您可以在此下载查看上图所示的demo。为节省空间,此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 120 2 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 UnmanagedArray<vec3> in_Position = new UnmanagedArray<vec3>(this.vertexCount); 10 UnmanagedArray<vec2> in_TexCoord = new UnmanagedArray<vec2>(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 else 29 { 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的研究范围,而是更高层的功能了,所以暂时不再深入考虑。