文章目录

一、前置知识

光源

  • 环境光:环境光来自四面八方;所有场景中的对象都处于环境光的照射中;如果没有环境光,未被漫射光照到的地方就会变得十分黑暗;
  • 漫射光:由特定的光源产生,并在场景中的对象表明上产生反射;处于漫射光直接照射下的任何对象表明都变得很亮,而几乎未被照射到的区域就显得要暗一些;
GLfloat lightAmbient[4] = {0.5, 0.5, 0.5, 1.0};     //半亮(0.5)的白色环境光,四个参数分别是r、、g、b、a
GLfloat lightDiffuse[4] = {1.0, 1.0, 1.0, 1.0}; //最亮的漫射光
GLfloat lightPosition[4] = {0.0, 0.0, 2.0, 1.0}; //保存光源的位置,四个参数分别是x、y、z、待定(告诉OPenGL这里指定的坐标就是光源的位置)

光源位置详解:我们知道OPenGL的坐标系,我们可以将电脑屏幕视为x-y面,z是伸出屏幕的,所以屏幕时z轴的原点;现在我们创建一个立方体,立方体必须在屏幕里我们才是可以看到的,所以立方体的z坐标必须是负数;现在我们要摆一个光源照射立方体的前面,那么这个光源必须得在屏幕外面,它才能照到立方体的前面,所以光源位置的z坐标必须是正数,如上{0.0, 0.0, 2.0(z), 1.0};


纹理滤波

通过之前的学习,我们知道,创建纹理的方式如下:

OPenGL笔记--纹理滤波和光源_OPenGL

void GL_Test::loadGLTextures()
{
/// 1
QImage tex,buf;
if(!buf.load(":/Images/1591561503-gB5rD.jpg")) { //用QImage类载入纹理图片
QImage dummy(128,128,QImage::Format_RGB32); //如果载入不成功,生成一个128*128的32位色的绿色图片
dummy.fill(Qt::green);
buf = dummy;
}
tex = QGLWidget::convertToGLFormat(buf); //QGLWidget的静态函数,专门用来转换图片

/// 2
glGenTextures(1,&texture[0]); //创建1个纹理

//---------------------------------------------------------------------------------------
/// 3
glBindTexture(GL_TEXTURE_2D,texture[0]); //使用来自位图数据生成的典型纹理
/*告诉OPenGL将纹理名字texture[0]绑定到纹理目标上;2D纹理只有高度(在Y轴上)和宽度(在X轴上)*/

/// 4
//真正的创建纹理
//GL_TEXTURE_2D:告诉 OpenGL 此纹理是一个 2D 纹理;
//数字0:代表图像的详细程度;
//数字3:是数据的成分数;
//tex.width():是纹理的宽度
//tex.height():是纹理的高度;
//GL_RGBA:告诉 OpenGL 图像数据由红、绿、蓝三色数据以及 alpha 通道数据组成;
//GL_UNSIGNED_BYTE:意味着组成图像的数据是无符号字节类型的;
//tex.bits():告诉 OpenGL 纹理数据的来源;
glTexImage2D(GL_TEXTURE_2D,
0,
3,
tex.width(),
tex.height(),
0, GL_RGBA,
GL_UNSIGNED_BYTE,
tex.bits());

/// 5
//告诉 OpenGL 在显示图像时,当它比放大得原始的纹理大(GL_TEXTURE_MAG_FILTER)或缩小得比原始得纹理小(GL_TEXTURE_MIN_FILTER)时,
//OpenGL 采用的滤波方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//我们都采用 GL_LINEAR,这使得纹理从很远处到离屏幕很近时都平滑显示;
//使 用 GL_LINEAR 需要 CPU 和显卡做更多的运算,如果您的机器很慢,您也许应该采用 GL_NEAREST;
//过滤的纹理在放大的时候,看起来斑驳的很,您也可以结合这两种滤波方式: 在近处时使用 GL_LINEAR,远处时 GL_NEAREST;
}
  • 上述代码汇总使用了==线性插值滤波(GL_LINEAR)==的纹理贴图,这需要机器有相当高的处理能力,但是看起来效果会很好;
  • 最临近值滤波(GL_NEAREST),它只占用很小的处理能力,看起来效果会很差,但是使用它因为不占用资源,工程在很快和很慢的机器上都可以正常运行;也可以混合使用线性插值滤波和最临近值滤波,纹理看起来效果会好一些;
  • Mipmap,这是一种创建纹理的新方法;您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失,刚才还很不错的图案变得很难看;当您告诉OPenGL创建一个mipmaped纹理时,OPenGL将选择它已经创建的外观最佳的纹理(带有很多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失);

glTexCoord2f(GLfloat s, GLfloat t)

  • s代表x坐标,t代表y坐标;
  • s∈[0.0, 1.0],t∈[0.0, 1.0];
  • 一张位图的4个坐标顶点分别为:左下角(0.0,0.0)、右下角(1.0,0.0)、右上角(1.0,1.0)、左上角(0.0,1.0);
  • 0.0f 是纹理的左侧、 0.5f 是纹理的中点,、1.0f 是纹理的右侧;
  • 用法:通常配合​​glVertex3f​​​使用,​​glTexCoord2f​​​用来定义纹理左边,​​glVertex3f​​用来定义几何定点坐标。
//前面
glNormal3f(0.0, 0.0, 1.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 ); //左下角
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 ); //右下角
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 ); //右上角
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 ); //左上角

二、运行效果

OPenGL笔记--纹理滤波和光源_qt5_02


三、完整代码

#ifndef GL_TEST_H
#define GL_TEST_H

#include <qgl.h> //因为QGLWidget类被包含在qgl.h头文件中
#include <glut.h> //使用glut库中的API
#include <QKeyEvent>
#include <QTimer>

//继承QGLWidget得到OPenGL窗口部件类
class GL_Test : public QGLWidget
{
public:
GL_Test(QWidget* parent = 0, bool fs = false);
~GL_Test();

protected:
/*************************************************************************************************
QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这个三个函数实现
*************************************************************************************************/
void initializeGL() override; //用来初始化OPenGL窗口,可以在里面设定一些有关选项
void paintGL() override; //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
void resizeGL(int w, int h) override; //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕

void keyPressEvent(QKeyEvent* e) override; //Qt键盘事件处理函数

void loadGLTextures(); //载入指定的图片并生成相应的纹理

protected:
bool fullscreen; //用来保存窗口是否处于全屏状态的变量
GLfloat xRot, yRot, zRot; //用来处理立方体的旋转
GLuint texture[3]; //用来存储纹理(长度为3的数组)
GLfloat zoom; //场景深入屏幕的距离
GLfloat xSpeed, ySpeed; //立方体在X轴和Y轴上旋转的速度
GLuint filter; //表明是使用哪个纹理

bool light; //表明现在是否使用光源
};

#endif // GL_TEST_H
#include "gl_test.h"

//描述和光源有关的信息
GLfloat lightAmbient[4] = {0.5, 0.5, 0.5, 1.0}; //环境光
GLfloat lightDiffuse[4] = {1.0, 1.0, 1.0, 1.0}; //漫射光
GLfloat lightPosition[4] = {0.0, 0.0, 2.0, 1.0}; //保存光源的位置

GL_Test::GL_Test(QWidget* parent, bool fs)
: QGLWidget(parent)
{
fullscreen = fs;

xRot = 0.0;
yRot = 0.0;
zRot = 0.0;

zoom = -5.0;

xSpeed = 0.0;
ySpeed = 0.0;

filter = 0;

light = false;

setGeometry(500,500,640,480); //设置窗口大小、位置

setWindowTitle("The first OpenGL Window"); //设置窗口标题

if(fullscreen) {
showFullScreen();
}

QTimer* timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=]{
updateGL();
});
timer->start(50);
}

GL_Test::~GL_Test()
{

}

void GL_Test::initializeGL()
{
loadGLTextures(); //载入纹理

glEnable(GL_TEXTURE_2D); //启用纹理

glShadeModel(GL_SMOOTH); //启用smooth shading(阴影平滑)

glClearColor(0.0, 0.0, 0.0, 0.5); //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】

glClearDepth(1.0); //设置深度缓存

glEnable(GL_DEPTH_TEST); //启动深度测试

glDepthFunc(GL_LEQUAL); //所作深度测试的类型

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点

//启动1号光源
glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient);

glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse);

glLightfv(GL_LIGHT1, GL_POSITION, lightPosition);

glEnable(GL_LIGHT1);
}

void GL_Test::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存

//-----------------------------------------
glLoadIdentity(); //重置当前的模型观察矩阵

glTranslatef(0.0, 0.0, zoom);

glRotatef(xRot, 1.0, 0.0, 0.0); //绕X轴旋转xRot度
glRotatef(yRot, 0.0, 1.0, 0.0); //绕X轴旋转yRot度

//选择我们使用的纹理
glBindTexture(GL_TEXTURE_2D, texture[filter]);
//如果您在您的场景中使用多个纹理,您应该使用来 glBindTexture(GL_TEXTURE_2D, texture[所使用纹理对应的数字]) 选择要绑定的纹理;
//当您想改变纹理时,应该绑定新的纹理。并且您不能在glBegin()和glEnd()之间绑定纹理,必须在glBegin()之前或glEnd()之后绑定;

//绘制正方形开始
glBegin(GL_QUADS);

//前面
glNormal3f(0.0, 0.0, 1.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );

//后面
glNormal3f(0.0, 0.0, -1.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );

//顶面
glNormal3f(0.0, 1.0, 0.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );

//底面
glNormal3f(0.0, -1.0, 0.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );

//右面
glNormal3f(1.0, 0.0, 0.0); //x、y、z,垂直于面的法线向量
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );

//左面
glNormal3f(-1.0, 0.0, 0.0);
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );

glEnd();
//绘制正方形结束

//-----------------------------------------
xRot += xSpeed;
yRot += ySpeed;
}

void GL_Test::resizeGL(int w, int h)
{
if(h == 0) { //防止h为0
h = 1;
}

glViewport(0, 0, (GLint)w, (GLint)h); //重置当前的视口(Viewport)

glMatrixMode(GL_PROJECTION); //选择投影矩阵

glLoadIdentity(); //重置投影矩阵

gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.1, 100.0 ); //建立透视投影矩阵

glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵

glLoadIdentity(); //重置模型观察矩阵
}

void GL_Test::keyPressEvent(QKeyEvent* e)
{
switch (e->key()) {
case Qt::Key_L: { //L键启动/关闭光源
light = !light;
if(!light) {
glDisable(GL_LIGHTING);
}else {
glEnable(GL_LIGHTING);
}
updateGL();
break;
}

case Qt::Key_F: { //F键切换纹理
filter += 1;
if(filter > 2) {
filter = 0;
}
updateGL();
break;
}

case Qt::Key_Equal: { //=键增加深度
zoom += 1;
updateGL();
break;
}

case Qt::Key_Minus: { //-键减少深度
zoom -= 1;
updateGL();
break;
}

case Qt::Key_Up: { //上箭头减少x速度
xSpeed -= 1;
updateGL();
break;
}

case Qt::Key_Down: { //下箭头增加x速度
xSpeed += 1;
updateGL();
break;
}

case Qt::Key_Left: { //左箭头减少y速度
ySpeed -= 1;
updateGL();
break;
}

case Qt::Key_Right: { //右箭头增加y速度
ySpeed += 1;
updateGL();
break;
}

case Qt::Key_Q: { //Q键x-y速度归零
xSpeed = 0;
ySpeed = 0;
updateGL();
break;
}

case Qt::Key_S: { //S键全屏切换
fullscreen = !fullscreen;
if(fullscreen) {
showFullScreen();
}else {
showNormal();
setGeometry(500,500,640,480);
}
updateGL();
break;
}//case Qt::Key_S

case Qt::Key_Escape: { //ESC键退出
close();
}//Qt::Key_Escape

}//switch (e->key())
}

void GL_Test::loadGLTextures()
{
QImage tex,buf;
if(!buf.load(":/Images/1591561503-gB5rD.jpg")) { //用QImage类载入纹理图片
QImage dummy(128,128,QImage::Format_RGB32); //如果载入不成功,生成一个128*128的32位色的绿色图片
dummy.fill(Qt::green);
buf = dummy;
}
tex = QGLWidget::convertToGLFormat(buf); //QGLWidget的静态函数,专门用来转换图片

glGenTextures(3,&texture[0]); //创建3个纹理

//---------------------------------------------------------------------------------------
glBindTexture(GL_TEXTURE_2D,texture[0]); //使用来自位图数据生成的典型纹理
/*告诉OPenGL将纹理名字texture[0]绑定到纹理目标上;2D纹理只有高度(在Y轴上)和宽度(在X轴上)*/

//真正的创建纹理
//GL_TEXTURE_2D:告诉 OpenGL 此纹理是一个 2D 纹理;
//数字0:代表图像的详细程度;
//数字3:是数据的成分数;
//tex.width():是纹理的宽度
//tex.height():是纹理的高度;
//GL_RGBA:告诉 OpenGL 图像数据由红、绿、蓝三色数据以及 alpha 通道数据组成;
//GL_UNSIGNED_BYTE:意味着组成图像的数据是无符号字节类型的;
//tex.bits():告诉 OpenGL 纹理数据的来源;
glTexImage2D(GL_TEXTURE_2D,
0,
3,
tex.width(),
tex.height(),
0, GL_RGBA,
GL_UNSIGNED_BYTE,
tex.bits());

//告诉 OpenGL 在显示图像时,当它比放大得原始的纹理大(GL_TEXTURE_MAG_FILTER)或缩小得比原始得纹理小(GL_TEXTURE_MIN_FILTER)时,
//OpenGL 采用的滤波方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//我们都采用 GL_LINEAR,这使得纹理从很远处到离屏幕很近时都平滑显示;
//使 用 GL_LINEAR 需要 CPU 和显卡做更多的运算,如果您的机器很慢,您也许应该采用 GL_NEAREST;
//过滤的纹理在放大的时候,看起来斑驳的很,您也可以结合这两种滤波方式: 在近处时使用 GL_LINEAR,远处时 GL_NEAREST;


//---------------------------------------------------------------------------------------
//创建纹理,存于texture[1]
glBindTexture(GL_TEXTURE_2D,texture[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
3,
tex.width(),
tex.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
tex.bits());

//---------------------------------------------------------------------------------------
//创建纹理,存于texture[2]
glBindTexture(GL_TEXTURE_2D,texture[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D,
GL_RGB,
tex.width(),
tex.height(),
GL_RGBA,
GL_UNSIGNED_BYTE,
tex.bits());
}