Search code examples
pythonopenglglslshaderpyopengl

PyOpenGL passing variable to the vertex shader


After a few suggestion I decided the learn a little bit OpenGL with the hard way. I tried to pass a (float - named myAttrib) variable to the vertex shader and it seemed to work (line 33), but obviously doesn't. Later I would like to divide the code into further parts.

Code:

from OpenGL.GL import *
from OpenGL.GL.shaders import *

import pygame
from pygame.locals import *
import numpy, time

def getFileContent(file):
    content = open(file, 'r').read()
    return content

def initDraw():
    pygame.init()
    pygame.display.set_mode((640, 480), HWSURFACE | OPENGL | DOUBLEBUF)

    vertices = [0.0, 0.5,
                0.5, -0.5,
                -0.5, -0.5,]

    vertices = numpy.array(vertices, dtype = numpy.float32)

    myAttrib = 0.3

    vertexShader = compileShader(getFileContent("helloTriangle.vert"), GL_VERTEX_SHADER)
    fragmentShader = compileShader(getFileContent("helloTriangle.frag"), GL_FRAGMENT_SHADER)

    shaderProgram = glCreateProgram()
    glAttachShader(shaderProgram, vertexShader)
    glAttachShader(shaderProgram, fragmentShader)
    glBindAttribLocation(shaderProgram, 1, "myAttrib")
    glLinkProgram(shaderProgram)

    print(glGetAttribLocation(shaderProgram, "myAttrib"))

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices)
    glEnableVertexAttribArray(0)

    glClearColor(0, 0, 0, 1)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

    glUseProgram(shaderProgram)
    glDrawArrays(GL_TRIANGLES, 0, 3)

    pygame.display.flip()
    time.sleep(2)

initDraw()

Vertex shader:

#version 130

attribute vec2 vPosition;
attribute float myAttrib;

void main()
{
    gl_Position = vec4(vPosition.x, vPosition.y + myAttrib, 0.0, 1.0);
}

Fragment shader:

#version 130

void main()
{
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);            
}

Solution

  • If you use an attribute, the you have to define an array of generic vertex attributes, with one attribute value per vertex coordinate (3 in your case). See Vertex Specification.
    If you would use a uniform, then you can set one value for the program stored in an uniform variable in the default uniform block (you can imagine this somehow like a global variable). See Uniform (GLSL):

    Case 1 - Attribute myAttrib:

    Vertex shader

    #version 130
    
    attribute vec2 vPosition;
    attribute float myAttrib;
    
    void main()
    {
        gl_Position = vec4(vPosition.x, vPosition.y + myAttrib, 0.0, 1.0);
    }
    

    You have to define an array of generic vertex attributes as you do it for the vertex coordinates:

    attrib = [-0.2, 0.2, 0.0]
    myAttrib  = numpy.array(attrib, dtype = numpy.float32)
    
    # .....
    
    glLinkProgram(shaderProgram)
    myAttrib_location = glGetAttribLocation(shaderProgram, "myAttrib")
    
    # .....
    
    glVertexAttribPointer(myAttrib_location, 1, GL_FLOAT, GL_FALSE, 0, myAttrib)
    glEnableVertexAttribArray(myAttrib_location)
    

    or

    glBindAttribLocation(shaderProgram, 1, "myAttrib")
    glLinkProgram(shaderProgram)
    
    # .....
    
    glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, myAttrib)
    glEnableVertexAttribArray(1)
    

    Case 2 - Uniform myUniform

    Vertex shader

    #version 130
    
    attribute vec2 vPosition;
    uniform float myUniform;
    
    void main()
    {
        gl_Position = vec4(vPosition.x, vPosition.y + myUniform, 0.0, 1.0);
    }
    

    Get the location of the uniform ("myUniform") by glGetUniformLocation and set the value to the uniform by glUniform:

    myUniform = 0.3
    
    # .....
    
    glLinkProgram(shaderProgram)
    myUniform_location = glGetUniformLocation(shaderProgram, "myUniform")
    
    # .....
    
    glUseProgram(shaderProgram)
    glUniform1f(myUniform_location, myUniform)
    
    glDrawArrays(GL_TRIANGLES, 0, 3)