在 OpenGL 着色器中渲染纹理

Rendering texture in OpenGL shader

我正在尝试学习如何将纹理应用于片段着色器中的 OpenGL 对象,但似乎只是以静态结束。这是我目前使用 PyOpenGL 执行此操作的 python 代码。

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.yRotDeg = 0.0

    def initializeGL(self):
        self.initGeometry()

        self.vertex_code = """
            #version 120
            uniform float scale;
            attribute vec4 color;
            attribute vec2 texcoord;
            attribute vec2 position;
            varying vec4 v_color;
            varying vec2 v_texcoord;
            void main()
            {
                gl_Position = vec4(scale*position, 0.0, 1.0);
                v_color = color;
                v_texcoord = texcoord;
            } """

        self.fragment_code = """
            #version 120
            uniform sampler2D tex;
            varying vec4 v_color;
            varying vec2 v_texcoord;
            void main()
            {
                gl_FragColor = texture2D(tex, v_texcoord);
            } """

        ## Build and activate program
        # Request program and shader slots from GPU
        self.program = GL.glCreateProgram()
        self.vertex = GL.glCreateShader(GL.GL_VERTEX_SHADER)
        self.fragment = GL.glCreateShader(GL.GL_FRAGMENT_SHADER)

        # Set shaders source
        GL.glShaderSource(self.vertex, self.vertex_code)
        GL.glShaderSource(self.fragment, self.fragment_code)

        # Compile shaders
        GL.glCompileShader(self.vertex)
        GL.glCompileShader(self.fragment)

        # Attach shader objects to the program
        GL.glAttachShader(self.program, self.vertex)
        GL.glAttachShader(self.program, self.fragment)

        # Build program
        GL.glLinkProgram(self.program)

        # Get rid of shaders (not needed anymore)
        GL.glDetachShader(self.program, self.vertex)
        GL.glDetachShader(self.program, self.fragment)

        # Make program the default program
        GL.glUseProgram(self.program)

        # Create array object
        self.vao = GL.glGenVertexArrays(1)
        GL.glBindVertexArray(self.vao)

        # Request buffer slot from GPU
        self.data_buffer = GL.glGenBuffers(1)
        self.indices_buffer = GL.glGenBuffers(1)

        # Make this buffer the default one
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.data_buffer)

        # Upload data
        GL.glBufferData(GL.GL_ARRAY_BUFFER, self.data.nbytes, self.data, GL.GL_DYNAMIC_DRAW)

        ## Bind attributes
        self.stride = self.data.strides[0]
        self.offset = ctypes.c_void_p(0)
        self.loc = GL.glGetAttribLocation(self.program, "position".encode('utf-8'))
        GL.glEnableVertexAttribArray(self.loc)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.data_buffer)
        GL.glVertexAttribPointer(self.loc, 3, GL.GL_FLOAT, False, self.stride, self.offset)

        self.offset = ctypes.c_void_p(self.data.dtype["position"].itemsize)
        self.loc = GL.glGetAttribLocation(self.program, "color".encode('utf-8'))
        if self.loc != -1:
            print('COLOR')
            GL.glEnableVertexAttribArray(self.loc)
            GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.data_buffer)
            GL.glVertexAttribPointer(self.loc, 4, GL.GL_FLOAT, False, self.stride, self.offset)

        self.offset = ctypes.c_void_p(self.data.dtype["texcoord"].itemsize)
        self.loc = GL.glGetAttribLocation(self.program, "texcoord".encode('utf-8'))
        if self.loc != -1:
            print('TEXCOORD')
            GL.glEnableVertexAttribArray(self.loc)
            GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.data_buffer)
            GL.glVertexAttribPointer(self.loc, 2, GL.GL_FLOAT, False, self.stride, self.offset)

        ## BEGIN TEX

        img = Image.open('kitten.png') # .jpg, .bmp, etc. also work
        img_data = np.array(list(img.getdata()), np.int8)

        texture = GL.glGenTextures(1)
        GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT,1)
        GL.glBindTexture(GL.GL_TEXTURE_2D, texture)
        GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP)
        GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP)
        GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
        GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
        GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB, img.size[0], img.size[1], 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, img_data)

        self.loc = GL.glGetUniformLocation(self.program, "tex".encode('utf-8'))
        print(self.loc)
        GL.glUniform1i(self.loc, 0)

        GL.glActiveTexture(GL.GL_TEXTURE0 + 0)
        GL.glBindTexture(GL.GL_TEXTURE_2D, texture)

        ## END TEX

        self.offset = ctypes.c_void_p(self.indices.itemsize)
        GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.indices_buffer)
        GL.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, sys.getsizeof(self.indices), self.indices, GL.GL_STATIC_DRAW)

        ## Bind uniforms
        self.loc = GL.glGetUniformLocation(self.program, "scale".encode('utf-8'))
        GL.glUniform1f(self.loc, 1.0)


    def resizeGL(self, width, height):
        if height == 0: height = 1

        GL.glViewport(0, 0, width, height)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        GL.glMatrixMode(GL.GL_MODELVIEW)

    def paintGL(self):
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

        #GL.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4)

        self.offset = ctypes.c_void_p(self.indices.itemsize)
        GL.glDrawElements(GL.GL_QUADS, 4, GL.GL_UNSIGNED_BYTE, self.offset)

    def initGeometry(self):
        # Indices not implemented yet (need to get correct size for it as well)
        self.data = np.zeros(4, [("position", np.float32, 2),
                            ("color",    np.float32, 4),
                            ("texcoord", np.float32, 2)])
        self.data['color']    = [ 
            (1,0,0,1),
            (0,1,0,1),
            (0,0,1,1),
            (1,1,0,1)
        ]
        self.data['position'] = [
            (-1,-1),
            (-1,+1),
            (+1,-1),
            (+1,+1)
        ]

        self.data['texcoord'] = [
            (1,1),
            (1,1),
            (1,1),
            (0,1)
        ]

        self.indices = np.array([
            0,
            2,
            3,
            1
        ], dtype=np.uint8)



    def spin(self):
        #print('spin')
        self.yRotDeg = (self.yRotDeg  + 1) % 360.0
        self.parent.statusBar().showMessage('rotation %f' % self.yRotDeg)
        self.updateGL()

我使用 Qt5 作为窗口框架,但是当我 运行 程序时,顶点显示正确,颜色显示正确,但应该覆盖在上面的纹理似乎只是是噪音。

这是我要完成的 video。如果这还不够,我可以将完整代码放在 GitHub 或其他东西上,但它似乎包含所有相关信息。我可能在这里做错了什么?

编辑: 好的,我采纳了 Reto 的建议并更新了我的代码,这似乎基本上解决了我的问题,只是我的纹理现在被分成两半并颠倒渲染。结果如下:

知道我在这里遗漏了什么吗?我以为我一定是把我的坐标放在了错误的顺序,但似乎改变它们的顺序根本不会改变方向。

我不确定我是否完全理解此代码的 python 细节,但是:使用当前代码中的纹理坐标,您根本不应该得到该图像。四个中的 3 个相同,您在纹理 space 中定义了一些零面积三角形。您应该会看到一些完全单色的三角形和一个从图像中的单行颜色奇怪的三角形。

但是,我认为那些 texcoords 根本没有被使用过。罪魁祸首是您如何为 texcoord 属性设置属性指针:

self.offset = types.c_void_p(self.data.dtype["texcoord"].itemsize)

你的意思可能类似于

self.offset = types.c_void_p(self.data.dtype["position"].itemsize + self.data.dtype["color"].itemsize )

跳过位置和颜色数据,这也与您通过跳过那里的位置设置 "color" 属性的方式一致。

目前,您实际上似乎指向颜色数据中的某处以获取您的 texcoord 属性,这恰好是一些不错的 0 和 1 序列。