纹理
通常来讲,计算机图像学的目标是决定组成图像的每个 部分的颜色。用高级着色算法计算像素的颜色是可能的,但这样的着色器的复杂度是很大的,以至于实现这样的方法是不实际的。因此,可以依赖纹理——大块的图像数据来绘制物体的表面使它们看起来更逼真。
这里面包含很多内容,比如OpenGL中的高级纹理类型,包括数组纹理、立方体映射纹理、深度纹理和缓存纹理。这里先只介绍如何在应用程序中使用纹理映射,并通过一个示例来形象说明。
一般纹理映射的步骤如下:
1. 创建一个纹理对象,为它加载纹素数据 。
2. 为顶点增加纹理坐标 。
3. 把纹理图与着色器中将要使用的纹理采样器关联。
4. 在着色器中使用纹理采样器来查询纹素值。
示例演示
我们利用上面的纹理映射步骤来给一个三角形贴纹理。首先创建一个纹理对象,代码如下:
void MyQGLWidget::loadGLTextures()
{
QImage tex, buf;
if(!buf.load(":/new/img/texture.jpg")){
QImage dummy(128, 128, QImage::Format_RGB32);
dummy.fill(Qt::green);
buf = dummy;
}
tex = QGLWidget::convertToGLFormat(buf);
glGenTextures(1, &texture[0]);
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glBindTexture(GL_TEXTURE_2D, texture[0]);
}
为了能够把纹理映射到三角形上,我们需要指定三角形的每个顶点各自对应纹理哪里部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样 (即采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpretation)。纹理坐标在x和y轴还是那个,范围为0和1之间。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0,0),也就是纹理图像的左下角,终始于(1, 1),即纹理图像的右上角。下图展示了我们如何把纹理坐标映射到三角形。
在下面代码中, vertices数组就添加了每个顶点的纹理坐标。
void MyQGLWidget::initShader()
{
glGenVertexArrays(NumVAOs, VAOs);
glBindVertexArray(VAOs[Triangles]);
GLfloat vertices[] = {
// 位置 // 纹理坐标
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.5f, 1.0f, // 顶部
};
glGenBuffers(NumBuffers, Buffers);
glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(vPosition);
glVertexAttribPointer(vPosition, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(vColor);
glVertexAttribPointer(vColor, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));
const char* vertexShaderCode =
"#version 430 \n"
""
"layout(location = 0) in vec3 vPosition;"
"layout(location = 1) in vec2 textureCoord;"
"out vec2 myTextureCoord;"
""
"void main()"
"{"
" gl_Position = vec4(vPosition, 1.0);"
" myTextureCoord = textureCoord;"
"}";
const char* fragmentShaderCode =
"#version 430 \r \n"
""
"in vec2 myTextureCoord;"
"out vec4 fColor;"
"uniform sampler2D myTexture;"
""
"void main()"
"{"
" fColor = texture(myTexture, myTextureCoord);"
"}";
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
const char* adapter[1];
adapter[0] = vertexShaderCode;
glShaderSource(vertexShaderID, 1, adapter, 0);
adapter[0] = fragmentShaderCode;
glShaderSource(fragmentShaderID, 1, adapter, 0);
glCompileShader(vertexShaderID);
GLint compiled;
glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &compiled );
if (!compiled)
{
std::cout<<"vertexShaderID";
GLsizei len;
glGetShaderiv( vertexShaderID, GL_INFO_LOG_LENGTH, &len );
GLchar* log = new GLchar[len+1];
glGetShaderInfoLog( vertexShaderID, len, &len, log );
std::cerr << "Shader compilation failed: " << log << std::endl;
delete [] log;
}
glCompileShader(fragmentShaderID);
glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &compiled );
if (!compiled)
{
std::cout<<"fragmentShaderID";
GLsizei len;
glGetShaderiv( fragmentShaderID, GL_INFO_LOG_LENGTH, &len );
GLchar* log = new GLchar[len+1];
glGetShaderInfoLog( fragmentShaderID, len, &len, log );
std::cerr << "Shader compilation failed: " << log << std::endl;
delete [] log;
}
GLuint programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
GLint linked;
glGetProgramiv( programID, GL_LINK_STATUS, &linked );
if (!linked)
std::cout<<"Error";
glUseProgram(programID);
//GLint texLoc;
//texLoc = glGetUniformLocation(programID, "myTexture");
//glUniform1i(texLoc, 0); //GL_TEXTURE0, 关联
}
上面代码中,glBindTexture绑定纹理,自动把纹理赋值给片段着色器程序的采样器。如上面的:uniform sampler2D myTexture。GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler)。片段着色器程序中“fColor = texture(myTexture, myTextureCoord);”在着色器中使用纹理采样器来查询纹素值。
如果GLSL传入多个纹理,可以利用gluniform1来显式地把纹理赋值给片段着色器程序的采样器。
运行结果:
着色语言语法
这里用到了uniform存储限制符,我们详细介绍下。在着色器运行之前,uniform修饰符可以指定一个在应用程序中设置好的变量,它不会在图元 处理的过程中发生变化。uniform变量在所有可用的着色阶段之间都是共享的,它必须定义为全局变量。着色器无法写入到uniform变量,也无法改变它的值。
举例来说,我们在上面需要设置一个纹理采样器。因此可以声明一个uniform变量,将纹理对象传递给着色器中,在着色器中进行如下声明:
uniform sampler2D myTexture;