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的研究范圍,而是更高層的功能了,所以暫時不再深入考慮。
文章列表