A Better Way
This is fine for a 3-vertex example. But imagine a scene involving millions of vertices (and no, that's not an exaggeration for high-end applications). Moving objects this way means having to copy millions of vertices from the original vertex data, add an offset to each of them, and then upload that data to an OpenGL buffer object. And all of that is before rendering. Clearly there must be a better way; games can not possibly do this every frame and still hold decent framerates.
Actually for quite some time, they did. In the pre-GeForce 256 days, that was how all games worked. Graphics hardware just took a list of vertices in clip space and rasterized them into fragments and pixels. Granted, in those days, we were talking about maybe 10,000 triangles per frame. And while CPUs have come a long way since then, they have not scaled with the complexity of graphics scenes.
The GeForce 256 (note: not a GT 2xx card, but the very first GeForce card) was the first graphics card that actually did some from of vertex processing. It could store vertices in GPU memory, read them, do some kind of transformation on them, and then send them through the rest of the pipeline. The kinds of transformations that the old GeForce 256 could do were quite useful, but fairly simple.
Having the benefit of modern hardware and OpenGL 3.x, we have something far more flexible: vertex shaders.
Remember what it is that we are doing. We compute an offset. Then we apply that offset to each vertex position. Vertex shaders are given each vertex position. So it makes sense to simply give the vertex shader the offset and let it compute the final vertex position, since it operates on each vertex. This is what vertPositionOffset.cpp
does.
回忆一下我们前面做的工作.计算offset.将其作用于顶点.然后将顶点位置数据传送给vertex shader.因为vertex shader作用于每个顶点,所以我们可以将offset传送给vertex shader 让它来计算最终的顶点位置.
The vertex shader is found in data\positionOffset.vert
. The vertex shader used here is as follows:
Example 3.4. Offsetting Vertex Shader
#version 330 layout(location = 0) in vec4 position; uniform vec2 offset; void main() { vec4 totalOffset = vec4(offset.x, offset.y, 0.0, 0.0); gl_Position = position + totalOffset; }
After defining the input position
, the shader defines a 2-dimensional vector offset
. But it defines it with the term uniform
, rather thanin
or out
. This has a particular meaning.
Shaders and Granularity. Recall that, with each execution of a shader, the shader gets new values for variables defined as in
. Each time a vertex shader is called, it gets a different set of inputs from the vertex attribute arrays and buffers. That is useful for vertex position data, but it is not what we want for the offset. We want each vertex to use the same offset; a “uniform” offset, if you will.
Shaders and Granularity. 回忆一下,对于shader的每次执行,它都会获得一个被定义为in类型变量的新的数值.每次调用vertex shader,它都会从vertex attribute和buffer获得一系列新的input.这对顶点数据有用, 但是不是我们想要对offset所做的.我们对每个顶点使用相同的offset,一个"nuiform"类型的offset.
Variables defined as uniform
do not change at the same frequency as variables defined as in
. Input variables change with every execution of the shader. Uniform variables (called uniforms) change only between executions of rendering calls. And even then, they only change when the user sets them explicitly to a new value.
变量被定义为uniform不会像in那样不断的改变.input随着shader每次执行都会变量不断变化.uniform变量只会在多次调用render()函数之间变化.尽管那样,也只是当user定义他们为新的数值时才会改变.
Vertex shader inputs come from vertex attribute array definitions and buffer objects. By contrast, uniforms are set directly on program objects.
vertex shader的inputs来源于对应的vertex attribute array 和buffer object.相反,uniform是直接定义在program objects上的.
In order to set a uniform in a program, we need two things. The first is a uniform location. Much like with attributes, there is an index that refers to a specific uniform value. Unlike attributes, you cannot set this location yourself; you must query it. In this tutorial, this is done in theInitializeProgram
function, with this line:
想要在一个program中设置一个uniform,需要做两件事情.第一件是uniform的位置.有些像attributes,存在一个定位到响应uniform值的索引.不像attributes的是你不能自己设置这个uniform的位置,你只能查询它.在InitializeProgram()函数中使用如下语句进行:
offsetLocation = glGetUniformLocation(theProgram, "offset");
The function glGetUniformLocation
retrieves the uniform location for the uniform named by the second parameter. Note that just because a uniform is defined in a shader, GLSL does not have to provide a location for it. It will only have a location if the uniform is actually used in the program, as we see in the vertex shader; glGetUniformLocation
will return -1 if the uniform has no location.
glGetUniformLocation()函数获得名为"offset"的uniform型变量的location.因为uniform变量是定义在shader中,GLSL不需要为它提供一个location.像我们看到的,只有在program中使用uniform时,该uniform才拥有一个location.如果该uniform没有location,函数将会返回-1.
Once we have the uniform location, we can set the uniform's value. However, unlike retrieving the uniform location, setting a uniform's value requires that the program be currently in use with glUseProgram
. Thus, the rendering code looks like this:
一旦我们有了uniform location,我们就可以设置uniform的值了.但是不像获得uniform 的location,设置一个uniform的值需要在使用glUseProgram()之后.代码如下:
Example 3.5. Draw with Calculated Offsets
void display() { float fXOffset = 0.0f, fYOffset = 0.0f; ComputePositionOffsets(fXOffset, fYOffset); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(theProgram); glUniform2f(offsetLocation, fXOffset, fYOffset); glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(0); glUseProgram(0); glutSwapBuffers(); glutPostRedisplay(); }
We use ComputePositionOffsets
to get the offsets, and then use glUniform2f
to set the uniform's value. The buffer object's data is never changed; the shader simply does the hard work. Which is why those shader stages exist in the first place.
我们使用ComputePositionOffsets()函数获得offsets,然后使用glUniform2f来设置uniform的值.buffer object的数据从未改变过,shader来做改变数值工作.这也是为什么这些shader stages在管线中放在第一位置的原因.
Vector Math. You may be curious about how these lines work:
vec4 totalOffset = vec4(offset.x, offset.y, 0.0, 0.0); gl_Position = position + totalOffset;
The vec4 that looks like a function here is a constructor; it creates a vec4 from 4 floats. This is done to make the addition easier.
The addition of position
to totalOffset
is a component-wise addition. It is a shorthand way of doing this:
gl_Position.x = position.x + totalOffset.x; gl_Position.y = position.y + totalOffset.y; gl_Position.z = position.z + totalOffset.z; gl_Position.w = position.w + totalOffset.w;
GLSL has a lot of vector math built in. The math operations are all component-wise when applied to vectors. However, it is illegal to add vectors of different dimensions to each other. So you cannot have a vec2 + vec4. That is why we had to convert offset
to a vec4 before performing the addition.