纹理过滤
纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素映射到纹理坐标。当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。OpenGL也有对于纹理过滤(Texture Filtering)的选项。纹理过滤有很多个选项,但是现在我们只讨论最重要的两种:GL_NEAREST
和GL_LINEAR
。
Texture Pixel
也叫Texel
,你可以想象你打开一张.jpg
格式图片,不断放大你会发现它是由无数像素点组成的,这个点就是纹理像素;注意不要和纹理坐标搞混,纹理坐标是你给模型顶点设置的那个数组,OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。
- 纹理坐标的精度是无限的,可以是任意的浮点值
- 纹理像素是有限的(图像分辨率)
- 一个像素需要一个颜色
- 所以采样就是,通过纹理坐标。问图片要纹理像素的颜色值
- 大纹理贴小面片时,纹理精度高,相邻纹理像素往往色差不大,无需融合,直接就近选取即可
GL_NEAREST
(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST
的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:
GL_LINEAR
(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
那么这两种纹理过滤方式有怎样的视觉效果呢?让我们看看在一个很大的物体上应用一张低分辨率的纹理会发生什么吧(纹理被放大了,每个纹理像素都能看到):
GL_NEAREST
产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR
能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR
可以产生更真实的输出,但有些开发者更喜欢8-bit
风格,所以他们会用GL_NEAREST
选项。
当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。我们需要使用glTexParameter*
函数为放大和缩小指定过滤方式。这段代码看起来会和纹理环绕方式的设置很相似:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
示例测试
邻近过滤
注意,我们需要修改纹理设置前,必须先绑定对应的纹理对象
textureNose->bind(2);
float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
线性过滤
textureNose->bind(2);
float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
多级渐远纹理
简单来说就是一系列的纹理图像,根据观察者与物体的距离,参考临界值,选择最适合物体的距离的哪个纹理。
在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST
和LINEAR
过滤。为了指定不同多级渐远纹理级别之间的过滤方式,你可以使用下面四个选项中的一个代替原有的过滤方式:
就像纹理过滤一样,我们可以使用glTexParameteri将过滤方式设置为前面四种提到的方法之一:
textureNose = new QOpenGLTexture(QImage(":/image/nose.png").mirrored());
textureNose->generateMipMaps();
......
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM
错误代码。
使用键盘控制纹理透明度
在着色器增加一个uniform变量,用于传递透明度值:
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D textureWall;
uniform sampler2D textureSmile;
uniform sampler2D textureNose;
uniform float ratio;
void main()
{
FragColor = mix(texture(textureWall, TexCoord),texture(textureSmile, TexCoord),ratio);
// FragColor = texture(textureNose, TexCoord);
}
在头文件增加键盘事件的函数:
#ifndef ZJJOPENGLWIDGET_H
#define ZJJOPENGLWIDGET_H
......
#include <QKeyEvent>
class ZjjOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_4_5_Core
{
......
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
void keyPressEvent(QKeyEvent *event);
float ratio = 0.5;
......
};
#endif // ZJJOPENGLWIDGET_H
设置窗口的焦点事件:
ZjjOpenGLWidget::ZjjOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
setFocusPolicy(Qt::StrongFocus);
......
}
设置键盘按下的相应逻辑:
void ZjjOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Up:
ratio+=0.1;
break;
case Qt::Key_Down:
ratio-=0.1;
break;
default:
break;
}
if(ratio>1) ratio=1;
if(ratio<0) ratio=0;
shaderProgram.bind();
shaderProgram.setUniformValue("ratio", ratio);
update();
}