opengles纹理_计算机图形学大作业OpenGL

(1) 2024-08-02 13:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
opengles纹理_计算机图形学大作业OpenGL,希望能够帮助你!!!。

辅助颜色

一般情况下,我们设置纹理的环境为 GL_MODULATE 模式,在这种情况下,受到光照的几何图形会和纹理的颜色进行结合。正常情况下,OpenGL 进行光照计算,并根据标准的光照模型进行单个片段的颜色计算。然后,再把片段的颜色乘以纹理的颜色,等到结合后的颜色。但是这样的话会削弱图形的光照效果。因为经过光照计算过后的片段的颜色值最大值是 1.0(即最亮的颜色),任何值乘以小于 1.0 的值,必定小于其本身(即不可能比原来更亮)。(if y <= 1.0 then x * y <= x. x y 是正数)。

没有应用纹理之前:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第1张

应用纹理之后光照效果被削弱了:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第2张

要解决这个问题,我们可以在纹理映射之后再应用镜面光高亮的效果(通过加而不是乘的方式)。这个技巧成为辅助镜面光颜色。通过设置光照的模型来达到此目的,函数调用如下:

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

加了这一行之后的效果如下:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第3张

要切回正常状态,指定光照模型为 GL_SINGLE_COLOR 即可,函数调用如下:

glLightModeli(GL_LIGHT_COLOR_CONTROL, GL_COLOR_SINGLE);

使用没有开启光照,我们可以手动来设置辅助颜色,通过 glSecondarycolor 函数调用设置辅助颜色和通过 glEnable (GL_COLOR_SUM); 来开启。手动设置的辅助颜色只有在没有开启光照的情况下有作用。

各向异性过滤

各向异性过滤并非 OpenGL 核心 API 的一部分,但其作为扩展被广泛用于提升纹理过滤操作的质量。在先前学习的两个基本的过滤器最邻近过滤(GL_NEAREST)和线性过滤(GL_LINEAR)。OpenGL 使用纹理坐标计算得到纹理将映射到几何图形的哪一个片段上。然后通过对该位置周围的纹理元素以 GL_NEAREST 过滤或 GL_LINEAR 过滤方式进行采样。

当我们的视角是垂直于该几何图形的时候,这样的方式没有问题。然而当我们的视角与几何图形形成一个斜角的时候,以常规的方式对周边纹理进行采样会丢失一些纹理的信息,它看起来变模糊了。更真实和精确的采样是,沿着平面倾斜的方向,拉长纹理的采样。如下的第二个图:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第4张

我们可以把各向异性过滤应用去基本的和 mipmap 方式的纹理过滤模式上。在使用之前我们需要检查各向异性过滤扩展是否被支持,使用 glTools 函数里的函数:

if(gltIsExtSupported(“GL_EXT_texture_filter_anisotropic”))

如果扩展是被支持的,我们可以查到支持各向异性过滤的最大值。通过调用 glGetFloatv 参数为 GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT。

GLfloat fLargest;

glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);

值越大各向异性过滤的粒度越大,如果值为 1.0 就代表普通的纹理过滤。各向异性过滤会带来一定的开销。现代的显卡都已经支持各向异性过滤,而且做了优化。最后我们通过 glTexParameterf 函数来设置各向异性过滤的最大值,如下:

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);

非开启各向异性过滤的通道效果图,可以看到远处的砖块较模糊:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第5张

开启各向异性过滤之后的效果图:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第6张

纹理压缩

使用纹理的缺陷是纹理需要大量的内存来存储和处理。在早期我们会把纹理压缩成 JPG 的格式,然后在加载之前(调用 glTexImage 之前) 对其进行解压。这样仅仅是节省了磁盘的空间以及加快了在网络上传输纹理的速度,但并没有减少对显存(加载到显存中还是原格式那么大)。

在 OpenGL1.3 后,OpenGL 原生支持了纹理压缩的特性。在更低的版本中,通过扩展来支持,你可以通过 GL_ARB_texture_compression 来检查是否支持这个扩展。OpenGL 对纹理的压缩不仅仅是加载压缩的纹理,而且在显卡内存中也是保存着压缩的纹理。这可以减少加载纹理时使用的内存以及提升处理纹理的性能(减少了移动纹理和切换纹理的时间,因为要操作的内存空间变小了)。

你可以通过下表的一个常量作为 glTexImage 函数中 internalFormat 参数的值,来达到压缩纹理的目的。当纹理无法被压缩时,将使用对应的基本内部格式。

压缩格式 基本内部格式
GL_COMPRESSED_ALPHA GL_ALPHA
GL_COMPRESSED_LUMINANCE GL_LUMINANCE
GL_COMPRESSED_LUMINANCE_ALPHA GL_LUMINANCE_ALPHA
GL_COMPRESSED_RGB GL_RGB
GL_COMPRESSED_RGBA GL_RGBA
GL_COMPRESSED_INTENSITY GL_INTENSITY

在这种方式下,加载压缩的图像会多耗一点时间,但却提升了处理纹理内存的速度。但你使用这种方式压缩了纹理之后,你可以通过 glGetTexLevelParameteriv 参数为 GL_TEXTURE_COMPRESSED 来检查纹理是否压缩成功。

GLint compFlag;

glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compFlag);

此函数接受的参数如下表:

参数 返回值
GL_TEXTURE_COMPRESSED 返回 1 代表压缩成功,0 代表失败
GL_TEXTURE_COMPRESSED_IMAGE_SIZE 返回压缩后纹理的大小(字节为单位)
GL_TEXTURE_INTERNAL_FORMAT 使用的压缩格式
GL_NUM_COMPRESSED_TEXTURE_FORMATS 支持的压缩格式的数量
GL_COMPRESSED_TEXTURE_FORMATS 返回一个保存每一个被支持的压缩格式的数组常量
GL_TEXTURE_COMPRESSION_HINT 纹理压缩的提示值

我们还可以通过 glHint 函数来告诉 OpenGL 我们要用的是最快的压缩算法还是最高质量的压缩算法。通过使用 GL_NUM_COMPRESSED_TEXTURE_FORMATS 和 GL_COMPRESSED_TEXTURE_FORMATS 来获得被支持的压缩格式的列表。几乎所有的 OpenGl 实现都支持 GL_EXT_texture_compression_s3tc 纹理压缩格式,如果这个扩展被支持那下面表格的所有格式都是支持的(仅适用于 2 维纹理)

格式 描述
GL_COMPRESSED_RGB_S3TC_DXT1 RGB 数据被压缩。alpha 为 1.0
GL_COMPRESSED_RGBA_S3TC_DXT1 RGB 数据被压缩。alpha 值为 1.0 或 0.0
GL_COMPRESSED_RGBA_S3TC_DXT3 RGB 数据被压缩。alpha 值用 4 位存储
GL_COMPRESSED_RGBA_S3TC_DXT5 RGB 数据被压缩。alpha 为一些 8 位值的加权平均值

加载压缩的纹理

在前面我们已经介绍了,如何压缩纹理数据。然后我们可以通过 glGetCompressedTexImage(与 glGetTexImage 获取未压缩数据一样)来获取被压缩的数据,并把它存到硬盘上。在随后的加载中,直接加载已经压缩过的纹理数据会更快。此技术完全依赖于硬件的实现。

加载已经预先压缩过的纹理数据,可以调用下面的函数:

void glCompressedTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint border, GLsizei imageSize, void *data);

void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, void *data);

void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLint width, GLint height, GLint depth, GLint border, GLint imageSize, void *data);

这个方法与 glTexImage 几乎是一样的,不一样的是其 internalFormat 必须是压缩的格式。如果实现支持 GL_EXT_texture_compression_s3tc 扩展,那么其参数值就可以是上面的表格列出的值。当然也有 glCompressedTexSubImage 函数来更新部分已加载的压缩过的纹理数据,就像 glTexSubImage 一样。

纹理压缩时非常流行的特性。更小的纹理意味着更快的加载速度,更快地在网上传输,更快地拷贝到显卡中,可以加载更多的纹理。下面做了个简单的实验:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第7张

不压缩和压缩后的图片大小的对比,压缩前是 196kb 左右,压缩后只有 32kb 了:

GLint flag;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &flag);
printf("compress flag : %d\n", flag);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &flag);
printf("compress size : %d\n", flag);

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第8张

生成纹理坐标

在前面我们学习过使用纹理坐标来把纹理映射到几何图形上。在球体和平滑的平面上手动指定纹理坐标是简单的。但是遇到了复杂的表面,我们要为其指定纹理坐标就有写困难了。OpenGL 提供了自动生成纹理坐标的特性来解决这个问题。

通过 glEnable 来开启 S,T,R 和 Q 的纹理坐标自动生成:

glEnable(GL_TEXTURE_GEN_S);

glEnable(GL_TEXTURE_GEN_T);

glEnable(GL_TEXTURE_GEN_R);

glEnable(GL_TEXTURE_GEN_Q);

当自动生成纹理坐标的功能被开启,那么 glTexCoord 的函数调用将被忽略。OpenGL 为自动为每一个顶点计算纹理坐标。我们可以通过相应的 glDisable 来关闭纹理坐标的自动生成。

我们可以通过下面的两个函数来设置自动生成纹理坐标的方法:

void glTexGenf(GLenum coord, GLenum pname, GLfloat param);

void glTexGenfv(GLenum coord, GLenum pname, GLfloat *param);

第一个参数指定了纹理坐标轴,可以是 GL_S,GL_T,GL_R 或 GL_Q。第二个参数必须是 GL_TEXTURE_SPHERE,GL_OBJECT_PLANE 或 GL_EYE_PLANE. 最后一个参数设置纹理生成的方法或模式。glTexGen 也有相应的 GLint 和 GLdouble 模式。

下面是 TEXGEN 示例:

#include "gltools.h" #include <stdio.h> #define ENV 0 #define STRIPES 1 #define TEXTURENUM 2 const char* texFileName[] = {"..\\Environment.tga","..\\stripes.tga"}; static GLuint textureName[TEXTURENUM]; static GLfloat yRot = 0.0f; static GLfloat zPos = -2.0f; static GLint iRenderMode = 3; void ProcessMenu(int value) { //投影平面 GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f}; //渲染模式 iRenderMode = value; switch(value) { case 1: //物体线性 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane); break; case 2: //视觉线性 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); glTexGenfv(GL_T, GL_EYE_PLANE, zPlane); break; case 3: default: //球体贴图 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); } glutPostRedisplay(); } void SetupRC() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); GLint iWidth, iHeight, iComponents; GLenum eFormat; //设置纹理环境 glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV, GL_REPLACE); //生成纹理名称 glGenTextures(TEXTURENUM, textureName); for (int i = 0; i < TEXTURENUM; ++i) { //加载纹理图像 void *pImage = gltLoadTGA(texFileName[i], &iWidth, &iHeight, &iComponents, &eFormat); if (pImage) { //绑定纹理 glBindTexture(GL_TEXTURE_2D, textureName[i]); //构建mimap gluBuild2DMipmaps(GL_TEXTURE_2D, iComponents, iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pImage); //设置纹理参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); } free(pImage); } if (gltIsExtSupported("GL_EXT_texture_filter_anisotropic")) { GLfloat fLargest; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest); } glEnable(GL_TEXTURE_2D); //设置为球体贴图 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); //开启S、T坐标的纹理坐标生成 glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); } void ShutdownRC() { glDeleteTextures(TEXTURENUM, textureName); } void RenderScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); //背景图,使用正交投影 glPushMatrix(); glLoadIdentity(); gluOrtho2D(0.0, 1.0, 0.0, 1.0); glDepthMask(GL_FALSE); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glBindTexture(GL_TEXTURE_2D, textureName[ENV]); //关闭纹理坐标的生成 glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, 1.0f); glEnd(); //还原投影矩阵 glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glDepthMask(GL_TRUE); if (iRenderMode != 3) { glBindTexture(GL_TEXTURE_2D, textureName[STRIPES]); } glPushMatrix(); glTranslatef(0.0f, 0.0f, zPos); glRotatef(yRot, 0.0f, 1.0f, 0.0f); gltDrawTorus(0.35, 0.15, 61, 37); glPopMatrix(); glutSwapBuffers(); } void ChangeSize(GLsizei w, GLsizei h) { if (h == 1) h = 0; glViewport(0, 0, w, h); GLfloat aspect = (GLfloat)w/(GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.5, aspect, 1.0, 150.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glutPostRedisplay(); } void SpecialKey(int value, int x, int y) { if (value == GLUT_KEY_LEFT) { yRot += 0.5f; } if (value == GLUT_KEY_RIGHT) { yRot -= 0.5f; } if (value == GLUT_KEY_UP) { zPos += 0.5f; } if (value == GLUT_KEY_DOWN) { zPos -= 0.5f; } if (yRot > 365.5f) { yRot = 0.0f; } glutPostRedisplay(); } int main(int arg, char **argv) { glutInit(&arg, argv); glutInitDisplayMode(GL_RGB | GL_DOUBLE | GL_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("TEXGEN"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutSpecialFunc(SpecialKey); glutCreateMenu(ProcessMenu); glutAddMenuEntry("Object Linear", 1); glutAddMenuEntry("Eye linear", 2); glutAddMenuEntry("sphere map", 3); glutAttachMenu(GLUT_RIGHT_BUTTON); SetupRC(); glutMainLoop(); ShutdownRC(); return 0; }

物体线性映射

当设置纹理生成的模式为 GL_OBJECT_LINEAR 的时候,纹理坐标生成使用的公式如下:

coord = P1*X + P2*Y + P3*Z + P4*W

其中 X,Y,Z,W 是被映射物体的顶点坐标值,P1-P4 是平面方程的系数。纹理坐标是从此平面透视投影到几何图形上的。例如,为了从平面 Z=0 上投影纹理坐标 S 和 T 我们可以使用下面的代码:


//投影平面 
GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f}; 
... 
... 

//物体线性 
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 
glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); 
glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane);

注意每个坐标都可以用不同的平面方程来生成纹理坐标,我们这里把 S 和 T 坐标的平面方程设置成一样的。在这里使用了物体线性的模式,不管你怎么调整这个圆环,纹理总是固定在几何图元上的。效果如下:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第9张

视觉线性映射

当选择视觉线性模式是,纹理坐标的生成方程与物体线性模式是相似的。不同的是现在的 X,Y,Z 和 W 值代表着视点的纹理(照相机或眼睛的位置)。平面方程的那些系数也要反转过来。事实上现在所有东西都用视觉坐标来表示了。代码如下:


//投影平面 
 GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f}; 
... 
... 

//视觉线性 
 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); 
 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); 
 glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); 
 glTexGenfv(GL_T, GL_EYE_PLANE, zPlane);

效果如下,纹理会随着你视角的旋转而改变了:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第10张

球体映射

当纹理生成模式设置为 GL_SPHERE_MAP 的时候,OpenGL 生成坐标的方式是物体呈现着当前纹理的倒影。想象一下鱼眼睛的效果。示例中设置球体映射模式的代码如下:

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

效果如下:

opengles纹理_计算机图形学大作业OpenGL_https://bianchenghao6.com/blog__第11张

为了获得更为逼真的效果,使用立方体映射。但球体映射还是有一定用途的,因为它只要求 1 个纹理开销较小,而立方体映射则要 6 个纹理,如果你不需要真正的反射,球体映射可以满足你的要求了。

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复