OpenGL学习脚印: 顶点数据传送和着色器处理2
写在前面
本节内容翻译和整理自《Learning Modern 3D Graphics Programming》Chapter2内容。作为学习目的,本文提炼其主要观点,删除了大部分细节部分的详述内容。另外原文示例代码有它独有的框架组织方式,为了保持自己的一贯风格,这里重写了示例程序代码,如果发现错误,请纠正我。转载需经过作者同意。
通过本节,你可以了解到:
- 通过着色器修改图形颜色
- 在顶点着色器中使用多个属性索引
- 简单的颜色插值
1.简单的 线性颜色插值
正如《OpenGL学习脚印: 顶点数据传送和着色器处理1》所述,片元着色器中包含片元的屏幕坐标信息。这样我们可以通过这些坐标计算颜色值来指定三角形的颜色。
片元着色器可以这样书写:
- #version 330
-
- out vec4 outputColor;
-
- void main()
- {
- float lerpValue = gl_FragCoord.y / 500.0f;
-
- outputColor = mix(vec4(1.0f, 1.0f, 1.0f, 1.0f),
- vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpValue);
- }
这里,gl_FragCoord是一个内置变量,仅仅在片元着色器中可见。它是一个vec3类型变量,其中XY为屏幕坐标,它们的绝对值会随着屏幕分辨率而变化。记得,前面说过,屏幕左下角为(0,0),因此屏幕下方的点的Y值比上方的点小。这里假定500.0是屏幕宽度(除非你改变窗口大小),那么lerpValue的值将会在[0,1]之间。
上述代码的第二行,利用lerpValue进行颜色的线性插值,mix函数是GLSL提供了很多个标准函数中的一个。
补充:
线性插值计算颜色很简单,就是C(x)=color1*(1-x)+color2*x 这里x在[0,1]之间,C(x)代表的则是x值处的颜色,这里使用lerpValue作为计算参数。
注意:用于mix的第三个参数必须在[0,1]之间,当然OpenGL不会进行严格检查,如果值不符合规定,函数值将是未定义的,未定义的意味着你将得不到你想要的颜色。
下面给出一个自己整理的示例,示例代码中三角形定义为等腰三角形,它的坐标和插值时计算与上述代码略有差别,但不影响解释结果。关于shader.h头文件,请参看《OpenGL学习脚印: 顶点数据传送和着色器处理1》,其他代码及运行效果如下:
文件一: 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()
- {
- float lerpValue = (gl_FragCoord.y-256.0f)/128.0f;
- outputColor = mix(vec4(1.0f,0.0f,0.0f,1.0f),
- vec4(0.0f,0.0f,1.0f,1.0f),lerpValue);
- }
文件三: shaderDemo.cpp
- //依赖库glew32.lib freeglut.lib
- //使用着色器颜色插值绘制三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.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 句柄
-
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //顶点位置和颜色数据
- const GLfloat vertices[] = {
- -0.5f,0.0f,0.0f,1.0f,
- 0.5f,0.0f,0.0f,1.0f,
- 0.0f,0.5f,0.0f,1.0f
- };
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_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 reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //绘制回调函数
- 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);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glutSwapBuffers();
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
运行效果如下图:
可以看到位于512屏幕中心x轴处颜色为红色,而顶部的顶点的颜色为蓝色,符合线性插值的预期效果。
2.使用多个属性索引来指定顶点位置和颜色
使用插值计算颜色很方便,但是更好的控制是允许显示指定各个顶点的颜色。我们的想法是:
- 对传递给顶点着色器的位置,我们想同时传递一个对应的颜色值
- 对每个顶点着色器输出的位置,我们想同时输出同它接收的那个颜色值相同的颜色
- 在片元着色器中,我们想接收顶点着色器输入的颜色,并将其作为片元的输出颜色
为了完成第一项任务我们将顶点数据修改为:
- //顶点位置和颜色数据
- const GLfloat vertexData[] = {
- -0.5f,0.0f,0.0f,1.0f,
- 0.5f,0.0f,0.0f,1.0f,
- 0.0f,0.5f,0.0f,1.0f,
- 1.0f,0.0f,0.0f,1.0f,
- 0.0f,1.0f,0.0f,1.0f,
- 0.0f,0.0f,1.0f,1.0f
- };
它的内存结构如下图所示:
原文讲得太详细,就不再细细赘述,这里我们需要清楚的就是:
- float类型由4个字节构成,vec4由4个float构成共16字节;vertexData中前3个vec4是顶点数据,后3个vec4是颜色数据。
- 顶点数据在数组中首地址为&vertexData[0];颜色数据在数组中的首地址为&vertexData[12],偏移量为3*4*4共48字节。
我们创建VBO对象如下:
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
利用glVertexAttribPointer向着色器传递数据代码如下:
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- //启用顶点位置属性索引
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
- //启用顶点颜色属性索引
- glEnableVertexAttribArray(1);
- glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)48);
需要在顶点着色器中开启两个属性索引,一个用于顶点位置,一个用于颜色,vertex shader书写如下:
文件一 vertex.glsl
- #version 330
-
- layout (location = 0) in vec4 pos;
- layout (location = 1) in vec4 incolor;
-
- smooth out vec4 thecolor;
-
- void main()
- {
- gl_Position = pos;
- thecolor = incolor;
- }
绘制三角形时,每次取顶点数据和颜色数据,都由着色器计算。例如顶点着色器第一次运行时取顶点位置属性从bufferObject[0 + (0 * 4 * sizeof(float))] ,颜色属性从bufferObject[48 + (0 * 4 * sizeof(float))] 取;第二次位置属性从bufferObject[0
+ (1 * 4 * sizeof(float))] ,颜色属性从bufferObject[48 + (1 * 4 * sizeof(float))] 取,依次类推。对应的数据获取方式如下图所示:
片元着色器则定义如下:
文件二 fragment.glsl
- #version 330
-
- smooth in vec4 thecolor;
-
- out vec4 outputColor;
-
- void main()
- {
- outputColor = thecolor;
- }
这里thecolor名字不是偶然。OpenGL要求,前一阶段的输出变量和下一阶段的输入变量必须名称和类型相同,这里使用了smooth限定符,那么两者也必须相同。
颜色插值
这里顶点着色器仅运行了3次,产生3个输出顶点位置和颜色属性,用来绘制一个三角形,产生了多个片元。
片元着色器却不止运行3次。在光栅化产生这个三角形时,每产生一个片元就运行一次。绘制这个三角形产生的片元数目取决于显示器分辨率和这个三角形覆盖的区域有多大。假设一个边长为1的等边三角形,其面积为sqrt(3)/4,整个屏幕区域中XY范围均为[-1,1]因此面积为4,则三角形所占面积为屏幕面积的十分之一。假设屏幕像素为500x500,则共有250,000个像素,而三角形所占像素为10分之1则有25,000个,因此片元着色器大概执行了25,000次。
这个理解见下图:
这有点令人失望。如果顶点着色器直接和片元着色器通信,而顶点着色器仅仅输出3个颜色值,
那么剩下的24,997个值从哪里来呢?
答案就是: 使用片元插值。
通过使用插值限定符smooth,我们告诉了OpenGL对这个颜色值进行特殊处理。每个片元不是从单个顶点获取一个值,而是获取三个顶点颜色属性值在三角形表面的混合值。片元越接近一个顶点,那么顶点颜色值贡献到该点的颜色值越多。
插值在顶点和片元着色器之间是最常见模式,因此如果你在顶点和片元着色器之间不提供插值关键字,那么smooth将是默认设置。还有其他的插值方式如noperspective 和flat。(这里不做介绍了,你可以自己尝试)。
shaderDemo2.cpp文件如下:
- //依赖库glew32.lib freeglut.lib
- //使用着色器颜色插值绘制三角形
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.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 句柄
-
- int main( int argc, char **argv )
- {
- glutInit(&argc, argv);
- glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
- glutInitWindowPosition(100,100);
- glutInitWindowSize( 512, 512 );
- glutCreateWindow( "Triangle demo" );
-
- glewInit();
- userInit();
- glutReshapeFunc(reshape);
- glutDisplayFunc( display );
- glutKeyboardFunc( keyboardAction );
- glutMainLoop();
- return 0;
- }
- //自定义初始化函数
- void userInit()
- {
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- //顶点位置和颜色数据
- const GLfloat vertexData[] = {
- -0.5f,0.0f,0.0f,1.0f,
- 0.5f,0.0f,0.0f,1.0f,
- 0.0f,0.5f,0.0f,1.0f,
- 1.0f,0.0f,0.0f,1.0f,
- 0.0f,1.0f,0.0f,1.0f,
- 0.0f,0.0f,1.0f,1.0f
- };
- //创建vertex array object对象
- glGenVertexArrays(1,&vaoId);
- glBindVertexArray(vaoId);
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_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 reshape(int w,int h)
- {
- glViewport(0,0,(GLsizei)w,(GLsizei)h);
- }
- //绘制回调函数
- 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);
- //启用顶点颜色属性索引
- glEnableVertexAttribArray(1);
- glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)48);
- //绘制三角形
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
- glBindBuffer(GL_ARRAY_BUFFER,0);
- glUseProgram(0);
- glDisableVertexAttribArray(0);
- glDisableVertexAttribArray(1);
-
- glutSwapBuffers();
- }
- //键盘按键回调函数
- void keyboardAction( unsigned char key, int x, int y )
- {
- switch( key )
- {
- case 033: // Escape key
- exit( EXIT_SUCCESS );
- break;
- }
- }
程序运行效果如下图:
这里三个顶点的颜色分别是红绿蓝,其中间的属于插值产生的颜色,符合插值效果。
|