10

I'm new to OpenGL and GLSL, and am learning it through http://open.gl/ .

I've managed to draw a triangle and change the color of all the vertexes using:

glUniform3f(uniColor, red, 0.0, 0.0)

Where the value of "red" is constantly changing, but this updates the color value of all the vertexes in the triangle, while I only want to change one or two of the vertexes.

Looking over the code I don't see where I can implement any logic to focus on one vertex instead of them all (the code is almost completely based on http://open.gl/content/code/c2_triangle_uniform.txt)

In this code however: http://open.gl/content/code/c2_color_triangle.txt , each vertex gets it's own color, but it seems to be hard coded, I can't dynamically change the colors as the program progresses.

I'm guessing

uniColor = glGetUniformLocation(shader.handle, "triangleColor")

gives me the location of a variable I can change and this variable is used to update the color of all the vertexes, and that perhaps what I need to do is create 3 variables, one for each vertex and then access those, but how do I do that?

If I look at the GLSL I have a "uniform vec3 triangleColor;" which is then used in

void main()
{
    outColor = vec4(triangleColor, 1.0);
}

but even if I create 3 such triangleColor variables how would I tell void main() to distinguish which vertex gets what variable?

The code:

import pyglet
from pyglet.gl import *
from shader import Shader
from ctypes import pointer, sizeof
import math
import time


window = pyglet.window.Window(800, 600, "OpenGL")
window.set_location(100, 100)


# Vertex Input
## Vertex Array Objects
vao = GLuint()
glGenVertexArrays(1, pointer(vao))
glBindVertexArray(vao)

## Vertex Buffer Object
vbo = GLuint()
glGenBuffers(1, pointer(vbo)) # Generate 1 buffer

vertices = [0.0, 0.5,
            0.5, -0.5,
            -0.5, -0.5]
## Convert the verteces array to a GLfloat array, usable by glBufferData
vertices_gl = (GLfloat * len(vertices))(*vertices)

## Upload data to GPU
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_gl), vertices_gl, GL_STATIC_DRAW)


# Shaders (Vertex and Fragment shaders)
vertex = """
#version 150

in vec2 position;

void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
}
"""
fragment = """
#version 150

uniform vec3 triangleColor;

out vec4 outColor;

void main()
{
    outColor = vec4(triangleColor, 1.0);
}
"""
## Compiling shaders and combining them into a program 
shader = Shader(vertex, fragment)
shader.bind() #glUseProgram


# Making the link between vertex data and attributes
## shader.handle holds the value of glCreateProgram()
posAttrib = glGetAttribLocation(shader.handle, "position")
glEnableVertexAttribArray(posAttrib)
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0)

uniColor = glGetUniformLocation(shader.handle, "triangleColor")

# Set clear color
glClearColor(0.0, 0.0, 0.0, 1.0)


@window.event
def on_draw():
    # Set the color of the triangle
    red = (math.sin(time.clock() * 4.0) + 1.0) / 2.0
    glUniform3f(uniColor, red, 0.0, 0.0)

    # Clear the screen to black
    glClear(GL_COLOR_BUFFER_BIT)

    # Draw a triangle from the 3 vertices
    glDrawArrays(GL_TRIANGLES, 0, 3)

@window.event
def on_key_press(symbol, modifiers):
    pass

@window.event
def on_key_release(symbol, modifiers):
    pass

def update(dt):
    pass
pyglet.clock.schedule(update)


pyglet.app.run()
01AutoMonkey
  • 2,515
  • 4
  • 29
  • 47
  • make a second buffer containing colors, it should be the same like your positions but instead of using it in the vertex shader, you use them in the fragment shader – Anton D Jul 16 '14 at 20:02

2 Answers2

9

Setting a uniform for each vertex isn't really scalable. A better way would be to create another Vertex Buffer Object to store the values. This can be done similar to the one you create for positions, except this one will contain 3 GLfloats. You can re-buffer the data as it changes.

My python is rubbish, so i've used yours as a syntax guide, but it should be something along the lines of:

## Vertex Buffer Object
vbocolors = GLuint()
glGenBuffers(1, pointer(vbocolors ))

colors = [1.0, 0.0, 0.0, # red vertex
          0.0, 1.0, 0.0, # green vertex
          0.0, 0.0, 1.0] # blue vertex

## Convert the verteces array to a GLfloat array, usable by glBufferData
colors_gl = (GLfloat * len(colors))(*colors)

## Upload data to GPU
glBindBuffer(GL_ARRAY_BUFFER, vbocolors )
glBufferData(GL_ARRAY_BUFFER, sizeof(colors_gl), colors_gl, GL_STATIC_DRAW)

Later new colors can be re-buffered:

## Upload new data to GPU
glBindBuffer(GL_ARRAY_BUFFER, vbocolors )
glBufferData(GL_ARRAY_BUFFER, sizeof(new_colors_gl), new_colors_gl, GL_STATIC_DRAW)

Setting the vertex attribute pointers:

colorAttrib = glGetAttribLocation(shader.handle, "color")
glEnableVertexAttribArray(colorAttrib )
glVertexAttribPointer(colorAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0)

Then in your shader, you want to pass this vertex color value from the vertex shader to the fragment shader, which will interpolate its value accordingly during the rasterization process.

# Shaders (Vertex and Fragment shaders)
vertex = """
#version 150

in vec2 position;
in vec3 color;
out vec3 interpColor;

void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
    interpColor= color; 
}
"""
fragment = """
#version 150

in vec3 interpColor;
out vec4 outColor;

void main()
{
    outColor = vec4(interpColor, 1.0);
}
"""
kbirk
  • 3,906
  • 7
  • 48
  • 72
  • 2
    Actually the idiomatic way of doing this is to place all vertex attributes into a common VBO, often in an interlaced format, i.e. `[(x,y,z,r,g,b,…), …]` – datenwolf Jul 16 '14 at 23:31
  • I agree, but I think for a beginner it was simpler to suggest two separate VBOs rather than interlacing and how to use pointer offsets and stride for the attribute pointers. – kbirk Jul 17 '14 at 02:42
  • In this case, I think it would be better to specify `GL_DYNAMIC_DRAW` in `glBufferData()`, and then use `glBufferSubData()` to update the color value. – Reto Koradi Jul 17 '14 at 03:34
  • @datenwolf Why is that approach *idiomatic*? It's probably more performant when not changing, at the cost of flexibility (imagine the positions are `STATIC_DRAW` and colours `STREAM_DRAW`, for example). – Bartek Banachewicz Jul 17 '14 at 08:54
  • 2
    @BartekBanachewicz: Of course the corner case you mention *may* benefit of using multiple VBOs. However cache coherence has much more influence on overall performance; also some implementations, like AMD (EDIT: sorry, I mixed them up with NVidia) ignore the `usage` hint to `glBufferData` alltogether and infer it from runtime behavior. Also if you have some vertex attributes changine, while others are not, instead of interlacing it's very common to concatenate the attribute arrays, of course with strides and elements aligned to some catch-all boundary (8 byte). – datenwolf Jul 17 '14 at 11:16
  • 2
    @BartekBanachewicz: And a small detail; currently for all OpenGL implementations existing so far O(glVertexAttribPointer) < O(glBindBuffer) << O(glBindVertexArray) – yes, that *<<* is not a typo, glBindVertexArray is prohibitively slow in current OpenGL implementations; Valve explicitly mentions this fact in their postmortem about porting their Source Engine to OpenGL and Linux. – datenwolf Jul 17 '14 at 11:19
1

The approach proposed in @Pondwater's answer is perfectly valid and reasonable. Because alternatives are always valuable, here's a slightly different approach.

The idea here is that you have two uniforms with two different colors. To specify which vertex uses which of the two colors, you introduce an additional vertex attribute. This additional attribute is a single float, where value 0.0 means that you want to use the first color, and 1.0 that you want to use the second color.

For your triangle example, say you want to color the 1st and 3rd vertex blue, and the 2nd vertex red. Extend the vertex data like this:

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

Then you set up a second vertex attribute (named colorWeight in the code below), following the same pattern you used for position. You'll have a second set of glEnableVertexAttribArray(), glVertexAttribPointer(), etc. calls for this new attribute.

In your vertex shader, you add the new colorWeight attribute, and pass it through to the fragment shader:

in vec2 position;
in float colorWeight;
out float fragColorWeight;

void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
    fragColorWeight = colorWeight;
}

Then in the fragment shader, you now have two uniform colors, and mix them based on the relative color weight:

uniform vec3 triangleColor;
uniform vec3 secondaryColor;
in float fragColorWeight;
out vec4 outColor;

void main()
{
    vec3 mixedColor = mix(triangleColor, secondaryColor, fragColorWeight);
    outColor = vec4(mixedColor, 1.0);
}

Now you can get the location of the secondaryColor uniform variable, and set it independently of triangleColor to modify the color of just the second vertex of the triangle.

Reto Koradi
  • 53,228
  • 8
  • 93
  • 133