如何使用 PyOpenGL 渲染文本?

exi*_*led 2 python opengl pyopengl freetype text-rendering

我正在学习现代 openGL,目前我在渲染文本方面遇到了麻烦。我正在遵循C++教程,但我正在尝试用 python 实现。

这是我的代码:

from OpenGL.GL import *
from OpenGL.GLU import *

from OpenGL.GL import shaders

import glfw
import freetype
import glm

import numpy as np
from PIL import Image
import math
import time


class CharacterSlot:
    def __init__(self, texture, glyph):
        self.texture = texture
        self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)

        if isinstance(glyph, freetype.GlyphSlot):
            self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
            self.advance = glyph.advance.x
        elif isinstance(glyph, freetype.BitmapGlyph):
            self.bearing = (glyph.left, glyph.top)
            self.advance = None
        else:
            raise RuntimeError('unknown glyph type')

def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
    return np.asarray([
        xpos, ypos - h, zfix, 0.0, 1.0,
        xpos, ypos, zfix, 0.0, 0.0,
        xpos + w, ypos, zfix, 1.0, 0.0,
        xpos, ypos - h, zfix, 0.0, 1.0,
        xpos + w, ypos, zfix, 1.0, 0.0,
        xpos + w, ypos - h, zfix, 1.0, 1.0
    ], np.float32)


VERTEX_SHADER = """
        #version 330 core
        layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
        out vec2 TexCoords;

        uniform mat4 projection;

        void main()
        {
            gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
            TexCoords = vertex.zw;
        }


       """

FRAGMENT_SHADER = """
        #version 330 core
        in vec2 TexCoords;
        out vec4 color;

        uniform sampler2D text;
        uniform vec3 textColor;

        void main()
        {    
            vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
            color = vec4(textColor, 1.0) * sampled;
        }

        """

shaderProgram = None
Characters = dict()
VBO = None
VAO = None

def initliaze():
    global VERTEXT_SHADER
    global FRAGMENT_SHADER
    global shaderProgram
    global Characters
    global VBO
    global VAO

    #compiling shaders
    vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
    fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)

    #creating shaderProgram
    shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)

    #get projection
    #problem
    
    shader_projection = glGetUniformLocation(shaderProgram, "projection")
    projection = glm.ortho(0.0,640,0.0,640)
    glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
    
    #disable byte-alignment restriction
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    face = freetype.Face("Vera.ttf")
    face.set_char_size( 48*64 )

    #load first 128 characters of ASCII set
    for i in range(0,128):
        face.load_char(chr(i))
        glyph = face.glyph
        
        #generate texture
        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glTexImage2D(GL_TEXTURE_2D,
                     0,
                     GL_RGB,
                     glyph.bitmap.width, glyph.bitmap.rows,
                     0,
                     GL_RGB,
                     GL_UNSIGNED_BYTE,
                     glyph.bitmap.buffer)

        #texture options
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        #now store character for later use
        Characters[chr(i)] = CharacterSlot(texture,glyph)
        
    glBindTexture(GL_TEXTURE_2D, 0);

    #configure VAO/VBO for texture quads
    VAO = glGenVertexArrays(1)
    glBindVertexArray(VAO)
    
    VBO = glGenBuffers(1);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    
def render_text(window,text,x,y,scale,color):
    global shaderProgram
    global Characters
    global VBO
    global VAO
    
    face = freetype.Face("Vera.ttf")
    face.set_char_size(48*64)
    glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
                color[0]/255,color[1]/255,color[2]/255)
               
    glActiveTexture(GL_TEXTURE0);
    

    for c in text:
        ch = Characters[c]
        w,h = ch.textureSize
        w = w*scale
        h = w*scale
        vertices = _get_rendering_buffer(x,y,w,h)

        #render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.texture);
        #update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        #render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);
        #now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (ch.advance+6)*scale;

    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glfwSwapBuffers(window);
    glfwPollEvents();
    
def main():
    glfw.init()
    window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
    
    glfw.make_context_current(window)

    
    initliaze()
    while not glfw.window_should_close(window):
        glfw.poll_events()
        glClearColor(0,0,0,1);
        glClear(GL_COLOR_BUFFER_BIT);
        render_text(window,'hello',1,1,1,(100,100,100))

        
    glfw.terminate()


if __name__ == '__main__':
    main()

Run Code Online (Sandbox Code Playgroud)

到目前为止我能理解,我在两部分中遇到了麻烦。initliaze()中的第一个问题,以下部分引发错误。

    shader_projection = glGetUniformLocation(shaderProgram, "projection")
    projection = glm.ortho(0.0,640,0.0,640)
    glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
Run Code Online (Sandbox Code Playgroud)

我已注释掉上面的部分以忽略。第二个问题是在render_text()函数中,以下部分引发错误。

glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
                color[0]/255,color[1]/255,color[2]/255)
Run Code Online (Sandbox Code Playgroud)

更多地方可能会出现问题。我不明白为什么文本渲染会如此困难。我在这里缺少什么?

Rab*_*d76 5

您错过了通过以下方式安装着色器程序glUseProgram

shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
glUseProgram(shaderProgram) # <---
Run Code Online (Sandbox Code Playgroud)

的第二个参数glBufferData和第三个参数是以字节为单位的glBufferSubData大小:

glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW)

glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
Run Code Online (Sandbox Code Playgroud)

glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)

glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
Run Code Online (Sandbox Code Playgroud)

顶点属性由二维顶点坐标(x,y)和二维纹理坐标组成。zfix从顶点属性数据数组中删除线。此外,您必须翻转纹理坐标的第二个分量(否则文本是颠倒的)

def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
    return np.asarray([
        xpos,     ypos - h, 0, 0,
        xpos,     ypos,     0, 1,
        xpos + w, ypos,     1, 1,
        xpos,     ypos - h, 0, 0,
        xpos + w, ypos,     1, 1,
        xpos + w, ypos - h, 1, 0
    ], np.float32)
Run Code Online (Sandbox Code Playgroud)

步幅参数glVertexAttribIPointer必须以字节为单位指定。如果步长为 0,则通用顶点属性被理解为紧密包装在数组中。因此,在您的情况下,步幅必须为 16 或 0:

glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0)

glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
Run Code Online (Sandbox Code Playgroud)

face.load_char(chr(i))生成具有颜色通道的图像(每像素 1 字节)。使用内部格式和格式GL_RED而不是GL_RGB生成二维纹理图像:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
             GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer) 
Run Code Online (Sandbox Code Playgroud)

在绘制文本之前,您必须绑定顶点数组:

glBindVertexArray(VAO)
for c in text:
    # [...]

    glDrawArrays(GL_TRIANGLES, 0, 6)
Run Code Online (Sandbox Code Playgroud)

当您递增 时存在拼写错误x,您必须使用>>- 运算符而不是+- 运算符:

x += (ch.advance+6)*scale

x += (ch.advance>>6)*scale
Run Code Online (Sandbox Code Playgroud)

当你计算时还有另一个错字h

h = w*scale

h = h*scale
Run Code Online (Sandbox Code Playgroud)

您必须启用 alpha混合

glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
Run Code Online (Sandbox Code Playgroud)

在 NDC(标准化设备坐标)中,左下角为 (-1, -1),右上角为 (1, 1)。以这种方式设置正交投影,使窗口的左上角位于 (0, 0):

projection = glm.ortho(0.0,640,0.0,640)

projection = glm.ortho(0, 640, 640, 0)
Run Code Online (Sandbox Code Playgroud)

文本的参考点位于底部。因此,您必须将轴坐标设置为大于文本高度:

render_text(window,'hello',1,1,1,(100,100,100))

render_text(window,'hello', 20, 50, 1, (255, 100, 100))
Run Code Online (Sandbox Code Playgroud)

查看完整的示例(我使用了不同的字体):

from OpenGL.GL import *
from OpenGL.GLU import *

from OpenGL.GL import shaders

import glfw
import freetype
import glm

import numpy as np
from PIL import Image
import math
import time


fontfile = "Vera.ttf"
#fontfile = r'C:\source\resource\fonts\gnu-freefont_freesans\freesans.ttf'

class CharacterSlot:
    def __init__(self, texture, glyph):
        self.texture = texture
        self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)

        if isinstance(glyph, freetype.GlyphSlot):
            self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
            self.advance = glyph.advance.x
        elif isinstance(glyph, freetype.BitmapGlyph):
            self.bearing = (glyph.left, glyph.top)
            self.advance = None
        else:
            raise RuntimeError('unknown glyph type')

def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
    return np.asarray([
        xpos,     ypos - h, 0, 0,
        xpos,     ypos,     0, 1,
        xpos + w, ypos,     1, 1,
        xpos,     ypos - h, 0, 0,
        xpos + w, ypos,     1, 1,
        xpos + w, ypos - h, 1, 0
    ], np.float32)


VERTEX_SHADER = """
        #version 330 core
        layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
        out vec2 TexCoords;

        uniform mat4 projection;

        void main()
        {
            gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
            TexCoords = vertex.zw;
        }
       """

FRAGMENT_SHADER = """
        #version 330 core
        in vec2 TexCoords;
        out vec4 color;

        uniform sampler2D text;
        uniform vec3 textColor;

        void main()
        {    
            vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
            color = vec4(textColor, 1.0) * sampled;
        }
        """

shaderProgram = None
Characters = dict()
VBO = None
VAO = None

def initliaze():
    global VERTEXT_SHADER
    global FRAGMENT_SHADER
    global shaderProgram
    global Characters
    global VBO
    global VAO

    #compiling shaders
    vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
    fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)

    #creating shaderProgram
    shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
    glUseProgram(shaderProgram)

    #get projection
    #problem
    
    shader_projection = glGetUniformLocation(shaderProgram, "projection")
    projection = glm.ortho(0, 640, 640, 0)
    glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection))
    
    #disable byte-alignment restriction
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

    face = freetype.Face(fontfile)
    face.set_char_size( 48*64 )

    #load first 128 characters of ASCII set
    for i in range(0,128):
        face.load_char(chr(i))
        glyph = face.glyph
        
        #generate texture
        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
                     GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)

        #texture options
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        #now store character for later use
        Characters[chr(i)] = CharacterSlot(texture,glyph)
        
    glBindTexture(GL_TEXTURE_2D, 0)

    #configure VAO/VBO for texture quads
    VAO = glGenVertexArrays(1)
    glBindVertexArray(VAO)
    
    VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)
    
def render_text(window,text,x,y,scale,color):
    global shaderProgram
    global Characters
    global VBO
    global VAO
    
    face = freetype.Face(fontfile)
    face.set_char_size(48*64)
    glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
                color[0]/255,color[1]/255,color[2]/255)
               
    glActiveTexture(GL_TEXTURE0)
    
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    glBindVertexArray(VAO)
    for c in text:
        ch = Characters[c]
        w, h = ch.textureSize
        w = w*scale
        h = h*scale
        vertices = _get_rendering_buffer(x,y,w,h)

        #render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.texture)
        #update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, VBO)
        glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)

        glBindBuffer(GL_ARRAY_BUFFER, 0)
        #render quad
        glDrawArrays(GL_TRIANGLES, 0, 6)
        #now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (ch.advance>>6)*scale

    glBindVertexArray(0)
    glBindTexture(GL_TEXTURE_2D, 0)

    glfw.swap_buffers(window)
    glfw.poll_events()
    
def main():
    glfw.init()
    window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)    
    glfw.make_context_current(window)
    
    initliaze()
    while not glfw.window_should_close(window):
        glfw.poll_events()
        glClearColor(0,0,0,1)
        glClear(GL_COLOR_BUFFER_BIT)
        render_text(window,'hello', 20, 50, 1, (255, 100, 100))

    glfw.terminate()

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

另请参见FreeType / OpenGL 文本渲染