Search code examples
pythonopenglglslshaderpyopengl

How could I use glDrawArrayInstanced in python?


I have a python OpenGL application in which I am trying to render multiple objects at the same time, without any luck. For some reason, only one object is drawn no matter how much I change the offsets. This are my fragment shaders:

self.vertex_shader_source = b"""
#version 330

in layout(location = 0) vec3 positions;
in layout(location = 1) vec2 textureCoords;
in layout(location = 2) vec3 normals;

uniform mat4 light;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 rotate;
uniform mat4 translate;
uniform mat4 scale;
uniform vec2 offsets[2];

out vec2 textures;
out vec3 fragNormal;

void main()
{
    fragNormal = (light * vec4(normals, 0.0f)).xyz;
    vec2 offset = offsets[gl_InstanceID];
    gl_Position =  projection * view * model * rotate * translate * scale * vec4(positions + (offset, 0), 1.0f);
    textures = vec2(textureCoords.x, 1 - textureCoords.y);
}
"""

self.fragment_shader_source = b"""
#version 330

in vec2 textures;
in vec3 fragNormal;

uniform sampler2D sampTexture;

out vec4 outColor;

void main()
{
    vec3 ambientLightIntensity = vec3(0.9f, 0.9f, 0.9f);
    vec3 sunLightIntensity = vec3(0.0f, 0.2f, 0.0f);
    vec3 sunLightDirection = normalize(vec3(0.0f, 0.0f, 0.0f));
    vec4 texel = texture(sampTexture, textures);
    vec3 lightIntensity = ambientLightIntensity + sunLightIntensity * fragNormal * max(dot(fragNormal, sunLightDirection), 0.0f);
    outColor = vec4(texel.rgb * lightIntensity, texel.a);
}
"""

And after compiling them, this is how I set up the environment:

self.vertex_array_object = glGenVertexArrays(1)
glBindVertexArray(self.vertex_array_object)

vertex_buffer_object = GLuint(0)
glGenBuffers(1, vertex_buffer_object)
glBindBuffer(GL_ARRAY_BUFFER,  vertex_buffer_object)
glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_STATIC_DRAW) 

# vertices
vertex_offset = 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
glEnableVertexAttribArray(0)

# textures
texture_offset = len(self.object.vertex_index) * 12
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
glEnableVertexAttribArray(1)

# normals
normal_offset = texture_offset + len(self.object.texture_index) * 8
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
glEnableVertexAttribArray(2)

/* texture mapping … */

view = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0, 0.0, -3.0])).flatten()
projection = pyrr.matrix44.create_perspective_projection_matrix(80.0, 1280 / 720, 0.1, 100.0).flatten()
model = pyrr.matrix44.create_from_translation(pyrr.Vector3([2.0, -0.5, -1.7])).flatten()
translate = pyrr.matrix44.create_from_translation(pyrr.Vector3([0, -0.15, 0])).flatten()
scale = pyrr.matrix44.create_from_scale(pyrr.Vector3([0, 0, 0])).flatten()
light = pyrr.matrix44.create_from_translation(pyrr.Vector3([2.0, 10.5, 1.7])).flatten()

self.rot_y = pyrr.Matrix44.from_y_rotation(0).flatten()

c_view = (GLfloat * len(view))(*view)
c_projection = (GLfloat * len(projection))(*projection)
c_model = (GLfloat * len(model))(*model)
c_translate = (GLfloat * len(translate))(*translate)
c_scale = (GLfloat * len(translate))(*translate)
c_rotate = (GLfloat * len(self.rot_y))(*self.rot_y)
c_light = (GLfloat * len(light))(*light)

self.rotate_location = glGetUniformLocation(self.shader, b"rotate")
self.translate_location = glGetUniformLocation(self.shader, b"translate")
self.scale_location = glGetUniformLocation(self.shader, b"scale")
self.view_location = glGetUniformLocation(self.shader, b"view")
self.proj_location = glGetUniformLocation(self.shader, b"projection")
self.model_location = glGetUniformLocation(self.shader, b"model")
self.light_location = glGetUniformLocation(self.shader, b"light")
self.offset_location = glGetUniformLocation(self.shader, b"offsets")

glUniformMatrix4fv(self.view_location, 1, GL_FALSE, c_view)
glUniformMatrix4fv(self.proj_location, 1, GL_FALSE, c_projection)
glUniformMatrix4fv(self.model_location, 1, GL_FALSE, c_model)
glUniformMatrix4fv(self.translate_location, 1, GL_FALSE, c_translate)
glUniformMatrix4fv(self.scale_location, 1, GL_FALSE, c_scale)
glUniformMatrix4fv(self.rotate_location, 1, GL_FALSE, c_rotate)
glUniformMatrix4fv(self.light_location, 1, GL_FALSE, c_light)

I tried by setting up the offset vector like

offset = [(0, 0), (10, 10)]

and pass it to the shader using glUniform2fv but this shows up only one object (I only need 2 for now). This is how I draw the objects:

glBindVertexArray(self.vertex_array_object)
glDrawArraysInstanced(GL_TRIANGLES, 0, len(self.object.vertex_index), 2)
glBindVertexArray(0)

Solution

  • The vertex shader is semantically incorrect. You have to construct a vec3 from the variable offset with type vec2:

    vec4(positions + (offset, 0), 1.0f)

    vec4(positions + vec3(offset, 0.0), 1.0)
    

    Actually you have used the sequence (,) operator. See GLSL 4.60 - Expressions:

    [...] The sequence (,) operator that operates on expressions by returning the type and value of the right-most expression in a comma separated list of expressions.

    Hence the result of positions + (offset, 0) is the same as the result of positions + 0.


    Use glUniform2fv to set the uniforms:

    self.offset_location = glGetUniformLocation(self.shader, b"offsets")
    glUniform2fv(self.offset_location, 2, [x0, y0, x1, y1])
    

    Alternatively you can set the uniforms in the array separately:

    self.offset_0_location = glGetUniformLocation(self.shader, b"offsets[0]")
    glUniform2f(self.offset_0_location, x0, y0)
    
    self.offset_1_location = glGetUniformLocation(self.shader, b"offsets[1]")
    glUniform2f(self.offset_1_location, x1, y1)
    

    In addition, a scale of (0, 0, 0) makes no sense at all, since it means that all vertices become (0, 0, 0). Change the scale to (1, 1, 1):

    scale = pyrr.matrix44.create_from_scale(pyrr.Vector3([1, 1, 1])).flatten()