OpenGL学习脚印: 绘制移动三角形
写在前面
本节内容翻译和整理自《Learning Modern 3D Graphics Programming》Chapter3内容。作为学习目的,本文提炼其主要观点,删除了大部分细节部分的详述内容。另外原文示例代码有它独有的框架组织方式,为了保持自己的一贯风格,这里重写了示例程序代码,如果发现错误,请纠正我。转载需经过作者同意。
通过本节,你可以了解到:
- 更新顶点数据的3种步步改进的方式
- 在着色器中使用uniform变量
本节的要点就在于: 体会如何从最初的基本思路出发,逐步改进,并完成顶点数据更新的方法。
1.在cpu内计算更新的顶点值
绘制一个可移动三角形的简单思路就是修改顶点的位置。从前面讲述可知,顶点位置存储在缓存对象VBO中,那么我们要做的就是更新VBO中的数据。更改顶点数据由两个步骤,第一计算坐标偏移量,第二应用偏移量。
计算偏移量的代码如下所示:
- void ComputePositionOffsets(float &fXOffset, float &fYOffset)
- {
- const float fLoopDuration = 5.0f;
- const float fScale = 3.14159f * 2.0f / fLoopDuration;
-
- float fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
-
- float fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);
-
- fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
- fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
- }
补充:如何理解偏移量和三角形的圆周运动
这涉及到一些简单的数学计算,如下图所示:
这里fLoopDuration设为5.0,然后做求模计算,则fCurrTimeThroughLoop范围在[0,5.0f)之间,最后利用正弦余弦求出XY偏移量。
这里偏移量形成了一个半径为0.5f的圆,根据角度范围在[0,2Pi]之间,则形成了偏移量在[-0.5,0.5]之间。
从图中红色三角形直角边边长为0.5,从它上面为(0,0)的原点来看,合成XY轴运动后,即可看做该点在绕圆周做循环运动。这里三角形形状保持不变,作图即可得出上图所示情形。红色三角形从起始0可能运动到1,2,3的位置,这样就行了圆周运动。
如何应用偏移量呢?
代码如下所示:
- void AdjustVertexData(float fXOffset, float fYOffset)
- {
- std::vector<float> fNewData(ARRAY_COUNT(vertexPositions));
- memcpy(&fNewData[0], vertexPositions, sizeof(vertexPositions));
-
- for(int iVertex = 0; iVertex < ARRAY_COUNT(vertexPositions); iVertex += 4)
- {
- fNewData[iVertex] += fXOffset;
- fNewData[iVertex + 1] += fYOffset;
- }
-
- glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertexPositions), &fNewData[0]);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- }
这里使用glBufferSubData来更新已分配的缓存对象中的内容。
注意: glBufferData和 glBufferSubData的区别在于SubData函数不分配内存。glBufferData函数会分配一定大小的内存,而glBufferSubData仅仅向已有的内存中传输数据。在已分配过内存的缓存对象上执行glBufferData会要求OpenGL重新分配内存,丢弃之前的数据,分配一块新的内存。然而,如果在没有由
glBufferData函数分配内存的缓存对象上执行glBufferSubData将发生错误。可以讲glBufferData函数看做malloc和memcpy的组合,而glBufferSubData 仅仅使用memcpy函数。
同时在绘制移动三角形时,分配内存使用
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STREAM_DRAW);
注意最后一个参数是 GL_STREAM_DRAW而不是GL_STATIC_DRAW,这是因为
GL_STATIC_DRAW表明只会设置缓存对象BO中的数据一次,而GL_STREAM_DRAW表明会经常改动数据,基本上是每帧都会改动。这些参数对于API而言没什么意义,他们仅仅是OpenGL实现的提示而已。在经常做更改时,合理的使用这些提示,对于获取好的缓存性能至关重要,这点稍后再做解释。
本实例完整代码如下:
文件一: vertex shader vertex.glsl
- #version 330
-
- layout(location = 0) in vec4 pos;
-
- void main()
- {
- gl_Position = pos;
-
- }
文件二: fragment shader fragment.glsl
- #version 330
-
- out vec4 outputColor;
-
- void main()
- {
- outputColor = vec4(1.0f,1.0f,0.0f,1.0f);
- }
文件三 实现文件vertex.cpp
- //依赖库glew32.lib freeglut.lib
- //使用glBufferSubData更新顶点数据绘制移动三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.h>
- #include <math.h>
- #include "shader.h"
-
- #define ARRAY_COUNT( array ) (sizeof( array ) / (sizeof( array[0] ) * (sizeof( array ) != sizeof(void*) || sizeof( array[0] ) <= sizeof(void*))))
-
- using namespace std;
-
- void userInit();
- void reshape(int w,int h);
- void display( void );
- void keyboardAction( unsigned char key, int x, int y );
-
-
- GLuint vboId;//vertex buffer object句柄
- GLuint vaoId;//vertext array object句柄
- GLuint programId;//shader program 句柄
- //顶点位置和颜色数据
- const GLfloat vertices[] = {
- 0.25f, 0.25f, 0.0f, 1.0f,
- 0.25f, -0.25f, 0.0f, 1.0f,
- -0.25f, -0.25f, 0.0f, 1.0f,
- };
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Moving triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //注意GL_STREAM_DRAW
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STREAM_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- //从文件创建着色器
- std::vector<GLuint> idVector;
- idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,"data\\vertex.glsl"));
- idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,"data\\fragment.glsl"));
- programId = Shader::createProgram(idVector);
- }
- //根据时间计算偏移量
- void ComputePositionOffsets(GLfloat &fXOffset, GLfloat &fYOffset)
- {
- const GLfloat fLoopDuration = 5.0f;
- const GLfloat fScale = 3.14159f * 2.0f / fLoopDuration;
-
- GLfloat fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
-
- GLfloat fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);
-
- fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
- fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
- }
- //重新调整vbo中顶点位置
- void AdjustVertexData(GLfloat fXOffset, GLfloat fYOffset)
- {
- //重新计算每个点的坐标
- std::vector<GLfloat> vertexList;
- for(int ix = 0;ix < ARRAY_COUNT(vertices);ix += 4)
- {
- vertexList.push_back( vertices[ix]+fXOffset);
- vertexList.push_back( vertices[ix+1]+fYOffset);
- vertexList.push_back( vertices[ix+2]);
- vertexList.push_back( vertices[ix+3]);
- }
- //更新vbo顶点数据
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(vertices),&vertexList[0]);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- }
- //绘制回调函数
- void display( void )
- {
-
- glClear(GL_COLOR_BUFFER_BIT);
- //调整顶点数据
- GLfloat fXOffset = 0.0f,fYOffset = 0.0f;
- ComputePositionOffsets(fXOffset,fYOffset);//计算偏移量
- AdjustVertexData(fXOffset,fYOffset);//更新数据
-
- glUseProgram(programId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //启用顶点位置属性索引
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glutSwapBuffers();
-
- glutPostRedisplay();//不断刷新
- }
- //调整窗口大小回调函数
- void reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
运行效果如下图所示:
2.改进-在顶点着色器中应用偏移量
上述方式对于3个顶点的简单示例还行。但是,想象下如果成千上万个顶点。按上述方式移动物体,需要从原始顶点拷贝数百万的顶点,然后应用偏移量,并发送到OpenGL中的缓存对象VBO中。这些都需要在渲染前完成,很明显,肯定有一种更好的方式,游戏不可能这样处理每帧同时还保持相当好的帧率。
同以前的GeForce 256 对比部分此处省略。
在现代OpenGL 3.x ,我们可以更加灵活的使用顶点着色器。
顶点着色器(片元着色器同1)代码如下:
文件一: vertex shader vertex.glsl
- #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;
- }
这里使用了一个uniform变量来保存偏移量。uniform变量,称作uniforms。
用uniform定义的变量,不像用in定义的变量那样经常变动。
in定义的变量在每次执行着色器时都会变动,而uniform变量,仅仅在渲染之间进行变动。而且,他们仅仅在用户显式地设定他们为新的值时他们也会改变。
顶点着色器的输入从顶点属性数组定义和缓存对象里获取,然而,uniform变量直接在程序对象中设置。
例如这里offset变量,首先获取它的位置
offsetLocation = glGetUniformLocation(theProgram, "offset");
然后使用
glUniform2f(offsetLocation, fXOffset, fYOffset);
来设定它的值。
完整的c++/C实现代码如下,运行效果同1:
文件二 : vertex2.cpp
- //依赖库glew32.lib freeglut.lib
- //使用shader uniform更新顶点数据绘制移动三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.h>
- #include <math.h>
- #include "shader.h"
-
- using namespace std;
-
- void userInit();
- void reshape(int w,int h);
- void display( void );
- void keyboardAction( unsigned char key, int x, int y );
-
-
- GLuint vboId;//vertex buffer object句柄
- GLuint vaoId;//vertext array object句柄
- GLuint programId;//shader program 句柄
- GLuint offsetLocationId;//uniform变量句柄
-
- //顶点位置和颜色数据
- const GLfloat vertices[] = {
- 0.25f, 0.25f, 0.0f, 1.0f,
- 0.25f, -0.25f, 0.0f, 1.0f,
- -0.25f, -0.25f, 0.0f, 1.0f,
- };
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Moving triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //注意GL_STREAM_DRAW
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STREAM_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- //从文件创建着色器
- std::vector<GLuint> idVector;
- idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,"data\\vertex.glsl"));
- idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,"data\\fragment.glsl"));
- programId = Shader::createProgram(idVector);
- //获取uniform变量句柄
- offsetLocationId = glGetUniformLocation(programId,"offset");
- }
- //根据时间计算偏移量
- void ComputePositionOffsets(GLfloat &fXOffset, GLfloat &fYOffset)
- {
- const GLfloat fLoopDuration = 5.0f;
- const GLfloat fScale = 3.14159f * 2.0f / fLoopDuration;
-
- GLfloat fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
-
- GLfloat fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);
-
- fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
- fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
- }
- //绘制回调函数
- void display( void )
- {
-
- glClear(GL_COLOR_BUFFER_BIT);
- //计算偏移量
- GLfloat fXOffset = 0.0f,fYOffset = 0.0f;
- ComputePositionOffsets(fXOffset,fYOffset);
-
- glUseProgram(programId);
- glUniform2f(offsetLocationId,fXOffset,fYOffset);//偏移量发送到顶点着色器
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //启用顶点位置属性索引
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glutSwapBuffers();
-
- glutPostRedisplay();//不断刷新
- }
- //调整窗口大小回调函数
- void reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
3.改进-在顶点着色器中计算偏移量
我们能不能把代码中计算偏移量的整个代码搬到着色器中呢?
这是不行的,因为glutGet(GL_ELAPSED_TIME) 这种c/c++函数,着色器无法直接调用,但是我们可以把时间作为参数传入着色器。
改进后的顶点着色器代码如下(片元着色器代码同1):
文件一: vertex shader vertex.glsl
- #version 330
-
- layout(location = 0) in vec4 position;
- uniform float fLoopDuration;
- uniform float fElapsedTime;
-
- void main()
- {
- float fScale = 3.14159f * 2.0f / fLoopDuration;
- float fCurrTimeThroughLoop = mod(fElapsedTime, fLoopDuration);
- vec4 totalOffset = vec4(
- cos(fCurrTimeThroughLoop * fScale) * 0.5f,
- sin(fCurrTimeThroughLoop * fScale) * 0.5f,
- 0.0,
- 0.0
- );
- gl_Position = position + totalOffset;
- }
可以看到在着色器中使用很多的内置函数,像正弦余弦函数等。
C/C++实现代码如下,运行效果同1:
文件二 vertex3.cpp
- //依赖库glew32.lib freeglut.lib
- //在shader中计算偏移量 更新顶点数据 绘制移动三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.h>
- #include <math.h>
- #include "shader.h"
-
- using namespace std;
-
- void userInit();
- void reshape(int w,int h);
- void display( void );
- void keyboardAction( unsigned char key, int x, int y );
-
-
- GLuint vboId;//vertex buffer object句柄
- GLuint vaoId;//vertext array object句柄
- GLuint programId;//shader program 句柄
- GLuint elapsedTimeLocationId;//uniform变量句柄
-
- //顶点位置和颜色数据
- const GLfloat vertices[] = {
- 0.25f, 0.25f, 0.0f, 1.0f,
- 0.25f, -0.25f, 0.0f, 1.0f,
- -0.25f, -0.25f, 0.0f, 1.0f,
- };
- //三角形圆周运动周期
- const float fLoopDuration = 5.0f;
-
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Moving triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //注意GL_STREAM_DRAW
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STREAM_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- //从文件创建着色器
- std::vector<GLuint> idVector;
- idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,"data\\vertex.glsl"));
- idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,"data\\fragment.glsl"));
- programId = Shader::createProgram(idVector);
- //获取uniform变量句柄
- elapsedTimeLocationId = glGetUniformLocation(programId,"fElapsedTime");
- //loopDuration作为常量则可以提前设置
- GLuint loopDurationId = glGetUniformLocation(programId,"fLoopDuration");
- glUseProgram(programId);
- glUniform1f(loopDurationId,fLoopDuration);
- glUseProgram(0);
- }
- //绘制回调函数
- void display( void )
- {
-
- glClear(GL_COLOR_BUFFER_BIT);
- glUseProgram(programId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //启用顶点位置属性索引
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
- //时间发送到顶点着色器
- glUniform1f(elapsedTimeLocationId,glutGet(GLUT_ELAPSED_TIME) / 1000.0f);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glutSwapBuffers();
-
- glutPostRedisplay();//不断刷新
- }
- //调整窗口大小回调函数
- void reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
4.添加基于时间的颜色
为上例在添加基于时间的颜色变化吧。
片元着色器不能改变对象的位置,但是可以改变对象的颜色。片元着色器书写如下(顶点着色器代码同3):
文件一 fragment shader fragment.glsl
- #version 330
-
- out vec4 outputColor;
-
- uniform float fFragLoopDuration;
- uniform float fElapsedTime;
-
- const vec4 firstColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
- const vec4 secondColor = vec4(0.0f, 0.0f, 1.0f, 1.0f);
-
- void main()
- {
- float currTime = mod(fElapsedTime, fFragLoopDuration);
- float currLerp = currTime / fFragLoopDuration;
-
- outputColor = mix(firstColor, secondColor, currLerp);
- }
这里需要注意两点:
1)着色器之间共享uniforms
只设置了一个fElapsedTime,会在两个着色器中生效吗?
OpenGL编译模型的一大优势就是,在连接顶点和片元着色器时把他们集成到一个对象中去时,名称和类型相同的uniform变量将会被连接起来。因此,这里也就只有一个fElapsedTime 的uniform变量,它即指向两个着色器中的uniform变量(即共享同一个uniform变量)。这一特性的负面是,如果你在一个着色器中创建了一个与另一个着色器中同名但类型不同的uniform变量,那么OpenGL在产生程序对象时会给出链接错误。而且,偶然将两个uniforms链接成一个也是有可能的。在我们的案例中,给两个着色器的Loop
duration取了两个不同的名字,就是为了避免共享该变量。
2)着色器中的全局变量
GLSL中的全局变量可以使用几种限定符来定义:const ,uniform ,in , 和
out .
const变量就像C99和C++中工作一样,他们保持不变,他们必须被初始化;
没有限定符的变量像C/C++里一样工作,他们是全局变量,可以被更改;
GLSL着色器可以调用函数,全局变量可以再函数之间共享。
但是,不像in、out和uniforms,非常量和常量在渲染各个阶段之间不可共享。
程序C/C++实现代码如下:
文件二 vertexAndFragment.cpp
- //依赖库glew32.lib freeglut.lib
- //在shader中更新顶点位置并颜色插值 绘制移动三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.h>
- #include <math.h>
- #include "shader.h"
-
- using namespace std;
-
- void userInit();
- void reshape(int w,int h);
- void display( void );
- void keyboardAction( unsigned char key, int x, int y );
-
-
- GLuint vboId;//vertex buffer object句柄
- GLuint vaoId;//vertext array object句柄
- GLuint programId;//shader program 句柄
- GLuint elapsedTimeLocationId;//uniform变量句柄
-
- //顶点位置和颜色数据
- const GLfloat vertices[] = {
- 0.25f, 0.25f, 0.0f, 1.0f,
- 0.25f, -0.25f, 0.0f, 1.0f,
- -0.25f, -0.25f, 0.0f, 1.0f,
- };
-
- const float fLoopDuration = 5.0f;//三角形圆周运动周期
- const float fFragLoopDuration = 10.0f;//颜色插值周期
-
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Moving triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //注意GL_STREAM_DRAW
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STREAM_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- //从文件创建着色器
- std::vector<GLuint> idVector;
- idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,"data\\vertex.glsl"));
- idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,"data\\fragment.glsl"));
- programId = Shader::createProgram(idVector);
- //fElapsedTime作为两个着色器间共享变量
- elapsedTimeLocationId = glGetUniformLocation(programId,"fElapsedTime");
- //loopDuration作为常量则可以提前设置
- GLuint vertexLoopDurationId = glGetUniformLocation(programId,"fLoopDuration");
- GLuint fragLoopDurationId = glGetUniformLocation(programId,"fFragLoopDuration");
- glUseProgram(programId);
- glUniform1f(vertexLoopDurationId,fLoopDuration);
- glUniform1f(fragLoopDurationId,fFragLoopDuration);
- glUseProgram(0);
- }
- //绘制回调函数
- void display( void )
- {
-
- glClear(GL_COLOR_BUFFER_BIT);
- glUseProgram(programId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //启用顶点位置属性索引
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
- //时间发送到着色器
- glUniform1f(elapsedTimeLocationId,glutGet(GLUT_ELAPSED_TIME) / 1000.0f);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glutSwapBuffers();
-
- glutPostRedisplay();//不断刷新
- }
- //调整窗口大小回调函数
- void reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
运行效果如下图所示:
5.几种方式的性能比较
本节的例子比较简单,应该运行得足够快,但是来分析下几种操作之间的性能仍然很重要。
我们使用了3中方式来传输顶点数据:
在CPU中计算顶点数据并上传到缓存对象中
在CPU中计算顶点的偏移量并传递给着色器,让它来计算顶点数据
仅通过CPU提供基本参数,让着色器完成更多的任务
哪一种方式最好呢?
回答这个问题可不是很容易。然而,通常情况下让CPU来执行坐标转换,应当比让GPU来做慢些。例外情况是,在同一帧中你需要进行多次同样的转换。即使在这种情况下,让GPU计算一次转换并存储在缓存对象中,让后从缓存对象中取出,还是要好些。这称为transform feedback,在后面的教程中会讲到。
在另外的两种方式中,哪一种更好取决于特定情况。拿我们的例子来说。一种情况是,我们在CPU中计算偏移量,然后传递给GPU。GPU将其应用到每个顶点上。另一种情况是,我们仅仅提供时间参数,对于每个顶点,GPU必须计算同样的偏移量。这意味着,顶点着色器做了很多产生同样数字的工作。
即使如此,这并不意味着这种方式一定慢些。这取决于改变数据的开销。改变一个uniforms耗费时间;改变一个vector类型的uniform花的时间不必改变一个float花的时间多,因为很多显卡处理了浮点运算的数学。真正的问题在于:在顶点着色器进行复杂操作和这些操作进行的频率相对比的结果。
我们使用的第二个顶点着色器,那个自己计算偏移量的,进行了许多的复杂数学运算。因为正弦和余弦不是很快就能完成的,他们需要很多计算指令来完成。同时,由于在一次渲染中偏移量对于每个顶点并不改变,性能要想好的话,最好是让CPU来计算偏移量,然后把它以uniform变量传送给着色器。
而且典型地,这也是通常渲染的处理方式。把由CPU提前计算好的转换值传递给顶点着色器。但是这不意味着,这是唯一或者最好的方式。在某些情况下,通过向顶点着色器传递参数来计算偏移量也是有用的。
在着色器的输入被抽走(abstracted away)后,这也是最好的方式。也就是,与其传第一个位置,用户传递更多的一般信息,着色器在某个时刻产生位置或者其他参数。这可以在基于forces的粒子系统中完成;顶点着色器基于时间执行force函数,就能够计算任何时刻的粒子位置(This can be done for particle systems based on forces; the vertex shader executes the force
functions based on time, and is able to thus compute the location of the particle at an arbitrary time.)。
这也有我们见过的一种优势。通过传递高层信息给着色器,让它进行复杂数学计算,你可以影响不仅仅限于一个偏移量。如果仅仅靠一个偏移量,片元着色器中的颜色动画是不可能的。高度的参数化,给了着色器很大的自由度。
|