You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyOpenGL着色器无法正常工作仅显白屏,是否顶点存在问题?

问题描述

尝试使用Pygame字体表面,通过data = pygame.image.tostring(img, "RGBA", 1)将其转换为像素数据,再调用OpenGL函数创建纹理,结合着色器用glDrawArrays绘制,但结果仅显示白屏,无文字显示。

原代码

from __future__ import division
import pygame
import numpy as np
from OpenGL.GL import *
from OpenGL.GL import shaders
from pygame import *

pygame.init()
screen = pygame.display.set_mode((600, 400), DOUBLEBUF | OPENGL)

vertex_shader = shaders.compileShader("""
#version 130

in vec3 position;
out vec2 fragTexCoord;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  fragTexCoord = gl_Position.xy;
}
""", GL_VERTEX_SHADER)

fragment_shader = shaders.compileShader("""
#version 130

in vec2 fragTexCoord;
uniform sampler2D textureSampler;

out vec4 fragmentColor;

void main() {
  fragmentColor = texture(textureSampler, fragTexCoord);
}
""", GL_FRAGMENT_SHADER)
shader_program = shaders.compileProgram(vertex_shader, fragment_shader)

img = pygame.font.Font(None, 50).render("Hello", True, (255, 255, 255))
w, h = img.get_size()
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
data = pygame.image.tostring(img, "RGBA", 1)
glTexImage2D(GL_TEXTURE_2D, 0, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)

quad_vertices = np.array([
    [-1.0, -1.0, 0.0],
    [ 1.0, -1.0, 0.0],
    [ 1.0,  1.0, 0.0],
    [-1.0,  1.0, 0.0],
], dtype=np.float32)


projection_matrix_loc = glGetUniformLocation(shader_program, "projectionMatrix")
modelview_matrix_loc = glGetUniformLocation(shader_program, "modelViewMatrix")

projection_matrix = np.array([[1.0, 0.0, 0.0, 0.0],
                              [0.0, 1.0, 0.0, 0.0],
                              [0.0, 0.0, -1.0, -1.0],
                              [0.0, 0.0, -1.0, 1.0]], dtype=np.float32)
modelview_matrix = np.identity(4, dtype=np.float32)

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()

    glClear(GL_COLOR_BUFFER_BIT)

    glUseProgram(shader_program)

    glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, projection_matrix)
    glUniformMatrix4fv(modelview_matrix_loc, 1, GL_FALSE, modelview_matrix)

    VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, quad_vertices.size * quad_vertices.dtype.itemsize, quad_vertices, GL_STATIC_DRAW)

    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)

    glBindTexture(GL_TEXTURE_2D, texture)

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4)

    glUseProgram(0)

    pygame.display.flip()
    pygame.time.Clock().tick(60)
问题分析与修正

核心问题点

  1. 纹理坐标错误:原着色器用裁剪空间坐标(范围[-1,1])作为纹理坐标,不符合OpenGL纹理坐标[0,1]的要求,且未适配Pygame图像Y轴翻转的特性。
  2. 纹理上传不规范:glTexImage2D内部格式使用4不兼容,需改用标准枚举值;未处理Pygame图像的Y轴翻转问题。
  3. VBO重复创建:循环内每次生成新VBO,导致GPU内存泄漏。
  4. 顶点属性位置硬编码:直接使用0作为属性位置,未确保与着色器中属性位置匹配。
  5. 投影矩阵不适配:原矩阵无法正确映射屏幕像素到OpenGL空间,导致文字显示异常。

修正后的代码

from __future__ import division
import pygame
import numpy as np
import ctypes
from OpenGL.GL import *
from OpenGL.GL import shaders
from pygame import *

pygame.init()
screen_width, screen_height = 600, 400
screen = pygame.display.set_mode((screen_width, screen_height), DOUBLEBUF | OPENGL)

# 编译着色器
vertex_shader = shaders.compileShader("""
#version 130

in vec3 position;
in vec2 texCoord;
out vec2 fragTexCoord;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  fragTexCoord = texCoord;
}
""", GL_VERTEX_SHADER)

fragment_shader = shaders.compileShader("""
#version 130

in vec2 fragTexCoord;
uniform sampler2D textureSampler;

out vec4 fragmentColor;

void main() {
  fragmentColor = texture(textureSampler, fragTexCoord);
}
""", GL_FRAGMENT_SHADER)
shader_program = shaders.compileProgram(vertex_shader, fragment_shader)

# 获取属性与Uniform位置
position_loc = glGetAttribLocation(shader_program, "position")
tex_coord_loc = glGetAttribLocation(shader_program, "texCoord")
projection_matrix_loc = glGetUniformLocation(shader_program, "projectionMatrix")
modelview_matrix_loc = glGetUniformLocation(shader_program, "modelViewMatrix")
texture_sampler_loc = glGetUniformLocation(shader_program, "textureSampler")

# 创建文字表面(添加背景色避免透明采样异常)
font = pygame.font.Font(None, 50)
img = font.render("Hello", True, (255, 255, 255), (0, 0, 0))
w, h = img.get_size()

# 创建并配置纹理
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
# 转换图像数据并上传
data = pygame.image.tostring(img, "RGBA", True)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)

# 定义带纹理坐标的四边形顶点(顶点格式:x,y,z,u,v)
quad_data = np.array([
    -0.5, -0.5, 0.0, 0.0, 1.0,  # 左下
     0.5, -0.5, 0.0, 1.0, 1.0,  # 右下
     0.5,  0.5, 0.0, 1.0, 0.0,  # 右上
    -0.5,  0.5, 0.0, 0.0, 0.0,  # 左上
], dtype=np.float32)

# 创建VBO并上传数据(仅执行一次)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, quad_data.nbytes, quad_data, GL_STATIC_DRAW)

# 正交投影矩阵:将屏幕像素坐标映射到OpenGL裁剪空间
projection_matrix = np.array([
    [2/screen_width, 0, 0, -1],
    [0, -2/screen_height, 0, 1],
    [0, 0, -1, 0],
    [0, 0, 0, 1]
], dtype=np.float32)

# 模型视图矩阵:将文字定位到屏幕中心并缩放至实际尺寸
modelview_matrix = np.identity(4, dtype=np.float32)
modelview_matrix[3][0] = screen_width//2 - w//2  # X轴偏移
modelview_matrix[3][1] = screen_height//2 - h//2 # Y轴偏移
modelview_matrix[0][0] = w  # X轴缩放
modelview_matrix[1][1] = h  # Y轴缩放

running = True
clock = pygame.time.Clock()

while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glClearColor(0.0, 0.0, 0.0, 1.0)  # 设置清除颜色为黑色

    glUseProgram(shader_program)

    # 设置Uniform参数
    glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, projection_matrix)
    glUniformMatrix4fv(modelview_matrix_loc, 1, GL_FALSE, modelview_matrix)
    glUniform1i(texture_sampler_loc, 0)  # 绑定纹理单元0

    # 绑定VBO并配置顶点属性
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    # 位置属性
    glEnableVertexAttribArray(position_loc)
    glVertexAttribPointer(position_loc, 3, GL_FLOAT, GL_FALSE, 5*4, ctypes.c_void_p(0))
    # 纹理坐标属性
    glEnableVertexAttribArray(tex_coord_loc)
    glVertexAttribPointer(tex_coord_loc, 2, GL_FLOAT, GL_FALSE, 5*4, ctypes.c_void_p(3*4))

    # 绑定纹理
    glBindTexture(GL_TEXTURE_2D, texture)

    # 绘制四边形
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4)

    # 清理顶点属性
    glDisableVertexAttribArray(position_loc)
    glDisableVertexAttribArray(tex_coord_loc)
    glUseProgram(0)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
exit()

关键修正说明

  • 纹理坐标适配:单独定义每个顶点的纹理坐标,翻转Y轴(v值从1.0到0.0),匹配Pygame图像的原点特性。
  • 正交投影:使用正交投影直接映射屏幕像素坐标,简化文字的位置和尺寸调整。
  • VBO优化:VBO仅创建一次,避免GPU内存泄漏。
  • 属性位置动态获取:通过glGetAttribLocation获取着色器属性位置,避免硬编码错误。
  • 纹理参数配置:设置GL_CLAMP_TO_EDGE避免纹理边缘的异常采样。

内容的提问来源于stack exchange,提问作者DryWall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:37:04