OpenGL -纹理的使用

余生长醉 提交于 2019-12-11 23:06:18

1.1 纹理的概念

如何让模型表面的细节效果更突显

  1. 细化模型表面的几何,这种方法会造成大量的数据
  2. 采用纹理映射的方法,纹理图中携带着物体表面的各种细节

Texture mapping(纹理映射,纹理贴图)

纹理映射的主要思想

将一给定的 纹理函数 映射到物体表面上,在对物体表面进行光亮度计算时可采用相应的纹理函数值来影响光照模型中的参数(如漫反射光亮度)以产生纹理效果,例如下面的光照明模型中漫射反射光的影响

$ I = K_a + K_d K_tI_l(N \cdot L) + K_sI_l(N \cdot H)^n$

上面的 KtK_t就是改变了漫反射的颜色,纹理映射不仅限于改变颜色,而且还可以改变 颜色、高光、凹凸、反射以及透明度等等都可以采用纹理映射来改变

例如在3D Max 中可以设置很多贴图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gn4dFogx-1576058740560)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574761225260.png)]

颜色纹理

大概分为二维纹理映射,以及三维纹理映射,二维纹理就是一个面,三维是一个块,又称体纹理映射,就是实现定义好一幅三维纹理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35nCZnRw-1576058740560)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574761700097.png)]

三维物体每一个点(x,y,z)均有一个纹理值t(x,y,z),那么物体空间就可以映射到一个三维空间中,但是三维纹理映射不常用,因为其太消耗空间

几何纹理

凹凸纹理映射以及位移纹理映射

1.2 凹凸纹理

Bump mapping(凹凸纹理映射),是图形学中应用最广泛的技术之一,由Blinn在1978年提出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-micsBtHc-1576058740561)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574762006856.png)]

大致意思就是对物体表面各采样点的法向作微小的扰动,由于表面光亮度是景物表面的法向的函数,上述法向的扰动必将导致表面光亮度的突变,从而产生表面凹凸不平的效果

1. 基本思想

  • 用纹理去修改物体的法向而不是颜色
  • 物体表面的几何法向保持不变,仅仅改变光照明模型计算的法向
    在这里插入图片描述
    例如让上面每个像素的 NN都发生变化,并且物体表面每个点的法向的扰动量就可以定义在一幅纹理中

2. 三种记录方法

1. Offset map

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TO1OeEtm-1576058740561)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574762928989.png)]

Offset map 是偏移贴图,比如原来法向是 N\vec{N},它的偏移量是bu\vec {b_u}bv\vec{b_v},偏移之后是N\vec{N}^`,而纹理里面记录的就是bu\vec {b_u}bv\vec{b_v}每一个点的法向的偏移量

2. Hieght-field map

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akMdVKtX-1576058740562)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574843539926.png)]

图中记录每一点的高度,通过一点和另一点高度的差,然后法向就可以求出来(高度图,高度场以及对应的Normal)

3. Normal map

在这里插入图片描述

法向图应用的比较广泛,是直接记录法向然后拿到法向后直接用,上面蓝色的图就是法向图,blue通道最大并且都是一个方向因此是偏蓝色的(rgb通道分别表示(x,y,z方向))

1.3 位移纹理

凹凸纹理映射缺陷,不能使物体的边缘也产生凹凸,由下图可以看到球体的轮廓线还是光滑的,没有任何凹凸感,并且凹凸纹理也不会投下自身的阴影

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUjlVgpH-1576058740563)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574844146721.png)]

1. 概念

Displacement map(位移纹理映射),在绘制物体时,对物体表面采样点的位置做微小扰动,产生凹凸不平的细节,也就是物体表面上的每一个点PuvP(u,v),都沿该点的法向量方向位移F(u,v)F(u,v)个单位长度

  • 新表面位置

    P(u,v)=P(u,v)+F(u,v)×N(u,v)P^`(u,v) = P(u,v) + F(u,v) \times N(u,v)

位移函数F(u,v)F(u,v)可以记录在一幅纹理中

1.4 映射关系

如何定义二维纹理与物体表面的映射关系,例如把一个平面映射到球面上,需要建立映射关系,球在数学上是不可展表面,不能完全展开,因此很难无变形的映射到球面上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PojFpYC4-1576058740563)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574845549604.png)]

表面如果太复杂的话是很难建立映射关系的

1. 投影式纹理映射

投影式纹理映射(projective texture mapping)是从投影点出发,将二维纹理投到三维物体表面上,如同放映机中投影出的画面一样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GJtLDQtO-1576058740564)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574845746210.png)]

2. 两步法纹理映射

例如下面的花瓶与圆柱比较相近,可以先把纹理映射到圆柱上,然后再将圆柱映射到花瓶上,相当于有一个中间几何体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rftY3sih-1576058740564)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574845893155.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2UxKFVZB-1576058740565)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574845987706.png)]

3ds Max中的UVWUVW贴图修改器就是通过中间几何体来进行的纹理映射

3. 展UV方法

表面不可展,然后就把表面给抛开分成不同的部件,然后每个部件再进行展开,展开之后就可以进行纹理映射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yo1sWV5C-1576058740566)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574846140036.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5fnhs8xk-1576058740566)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574846545590.png)]

例如上面图中的三角形需要在P0P_0处映射纹理,只需要指定P0P_0处所对应的纹理空间中的坐标,P0P_0在纹理空间中对应的点(s0,t0)(s_0,t_0)就是它的纹理坐标

可以直接给定多边形定点的纹理坐标,多边形内的点的纹理坐标可以通过双线性插值来取得,因此纹理映射是从顶点模块开始计算

纹理映射在图形流水线中的阶段

  1. Vertex operation 顶点处理阶段:给定顶点的纹理坐标
  2. Rasteration 光栅化阶段:插值得到每个片元的纹理坐标
  3. Fragment operation 片元处理阶段:根据纹理坐标获取片元的纹理值

1.5 OpenGL中的使用

应用Texture Mapping(纹理)的四个步骤

  1. 创建纹理对象,并为它装载一个纹理
  2. 确定纹理如何应用到每个像素上
  3. 启用纹理贴图功能
  4. 绘制场景,提供纹理坐标和几何图形坐标

主要步骤如下:

1. 创建纹理对象

  1. glGenTextures(1, &texName); 生成一个纹理编号,也可以定义好几个纹理对象

    void glGenTetures(GLsizei n, GLuint *textures);纹理编号可以放一个数组中,每个纹理对象对应一个标号,它代表了当前纹理的数据、参数等所有信息

  2. glBindTexture(GL_TEXTURE_2D, txeName); 绑定 texName 纹理编号

  3. void glTexture2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); 把一幅纹理数据从内存中装载进显存

    • target :纹理目标
    • level:层次
    • internalformat:纹理数据在显存中的格式
    • width/height:纹理的尺寸
    • border:纹理边框
    • format:纹理数据在内存中的格式
    • type:纹理在内存中每个分量的数据类型
    • pixels:纹理数据

纹理的大小

  • width:2m+2×(border)2^m + 2 \times(border)
  • height:2n+2×(border)2^n + 2 \times(border)
  • border:0或者1

OpenGL 2.0 及以上版本中,纹理可以是任意大小,OpenGL 3.1 及以上版本不支持 border,要求 border 必须是 0

用完纹理资源之后需要释放纹理资源,通过下面这个函数

void glDeleteTextures(GLsizei n, const Gluint * textures); // textures 数组

2. 应用纹理到像素

确定纹理如何应用到每个像素上需要用到下面这个函数

void glTexEnvf(GLenum target, GLenum pname, GLfloat param);
  • target必须是:GL_TEXTURE_ENV
  • pname必须是:GL_TEXTURE_ENV_MODE
  • param可以有四种选择,纹理应用到每个像素的方式,在片元处理阶段片元取得纹理值之后如何改变片元的颜色方式
    • GL_MODULATE :纹理值和片元相乘
    • GL_DECAL:混合
    • GL_BLEND:混合
    • GL_REPLACE:用纹理值替换片元颜色

3. 启用纹理贴图

启用纹理贴图用到如下函数

glEnable(GL_TEXTURE_2D); // 启用纹理贴图
glBindTexture(GL_TEXTURE_2D, texName); // 绑定纹理参数

4. 绘制场景

在绘制场景的时候,需要提供纹理坐标和几何图形坐标

  • glTexCoord2f(0.0, 0.0);
  • glVertex3f(-2.0, -1.0, 0.0);

纹理坐标的取值范围是[0, 1],如果超出则可以设置纹理重复映射方式

  • glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //s轴
  • glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // t轴

5. 纹理坐标变换

用以下函数可以对纹理坐标进行几何变换

glMatrixMode(GL_TEXTURE);// 纹理几何变换
glTranslatef(x, y, z); // 平移
glRotatef(angle, x, y, z); // 旋转
glScalef(sx, sy, sz); // 缩放
glMatrixMode(GL_MODELVIEW); // 将矩阵变换模式切换回来
// 绘制几何物体

1.6 纹理过滤

首先,像素片元都是有面积的,因此纹理映射也是有面积的,如果贴图的分辨率比较高,可以看出一个坐标覆盖了好几个纹元,因此在屏幕上显示的像素就需要通过某种方式来决定显示哪一个纹元,比如可以让覆盖的几个纹元通过加权平均来获得像素的显示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xaeoAz3d-1576058740567)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574928811943.png)]

Texture Filtering(纹理过滤)

一个像素一般不会正好对应一个纹元(texel),所以像素的颜色无法直接得到,需要经过一定的运算,这个过程就是纹理过滤(过滤就是经过一定处理)

纹理与多边形在屏幕上的区域另种对应方式时的表示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7klEaYC6-1576058740568)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574929319208.png)]

在OpenGL中对应上面两种方式,可以使用以下参数

  • GL_TEXTURE_MAG_FILTER 纹理需要放大时
  • GL_TEXTURE_MIN_FILTER 纹理需要缩放时

1. 常用方式

  • GL_NEAREST:选择距像素中心距离最小的纹元(texel)的颜色作为像素的颜色;
  • GL_LINEAR:选择距像素中心最近的四个纹元(texel)的加权平均值作为像素的颜色

在OpenGL中的设置如下

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 纹元需要放大时
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 纹元需要缩小时
  • GL_TEXTURE_MIN_FILTER:表示一个像素对应的纹理区域比一个纹元(textel )区域大时的处理方法
    • 即从纹理区域到屏幕区域是被缩小了的,即物体被推远了
  • GL_TEXTURE_MAG_FILTER:表示一个像素对应的纹理区域比一个纹元(textel)区域小或等于时的处理方法
    • 即从纹理区域到屏幕区域是被放大了的,即物体被拉近了

放缩图像

放缩即是上采样和下采样,在OpenGL中可以通过下面这个函数进行放缩处理

int gluScaleImage(GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void * dataout);

2. Mipmap过滤

首先,GL_LINEAR是选择距像素中心最近的 四个像素的加权平均值作为像素的颜色,但是当一个屏幕像素覆盖的纹理区域大于4个纹元的时候改如何处理,可以使用以下方法

  1. 扩大加权平均的面积—效率比较低
  2. Mipmap方法

1. Mipmap概念

首先,生成原纹理贴图不同分辨率版本的纹理,然后再降采样边长各减去12\frac{1}{2}得到降采样的版本,最后降采样到一个像素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVbRRm4f-1576058740569)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574931922979.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuIboef0-1576058740569)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574931935369.png)]

  • 首先对纹理进行预处理,生成不同分辨率的版本
  • 纹理过滤时首先选取合适的分辨率版本,然后进行Linear过滤

假设文元覆盖了16个纹元,就直接选取对应分辨率版本的纹理直接进行过滤,总之就是预先生成了中间结果,然后在过滤的时候在中间结果进行选择既可,然后在Linear过滤的时候同样还是加权平均了4个纹元

Mipmap的处理方式,只需要对纹理缩小的时候(Minification Filters)GL_TEXTURE_MIN_LILTER,放大时只需要使用GL_NEARESTGL_LINEAR,因为放大时纹理本身分辨率就不够因此没必要再使用Mipmap去缩减分辨率

2. 过滤方式

  1. GL_TEXTURE_MAG_FILTER:过滤方式
    • GL_NEAREST
    • GL_LINEAR
  2. GL_TEXTURE_MIN_FILTER:过滤方式
    • GL_NEAREST、GL_LINEAR
    • GL_NEAREST_MIPMAP_NEAREST:选择最匹配像素大小的Mipmap,并用GL_NEAREST方式获得纹理值
    • GL_NEAREST_MIPMAP_LINEAR:选择最匹配像素大小的Mipmap,并用GL_LINEAR方式获得纹理值
    • GL_LINEAR_MIPMAP_NEAREST:选择两个最匹配像素大小的Mipmap,并使用GL_NEAREST方式从两个Mipmap中产生纹理值,最终纹理值是这两个取值的加权平均值
    • GL_LINEAR_MIPMAP_LINEAR:选择两个最匹配像素大小的Mipmap,并使用GL_LINEAR方式从两个Mipmap中产生纹理值,最终纹理值是这两个取值的加权平均值
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vC5InKDN-1576058740570)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574933591521.png)]

GL_LINEAR_MIPMAP_LINEAR 被称为三线性过滤

3. 如何选取

如何选择合适的Mipmap层,Mipmap层的选择在OpenGL中会自动选取,主要方法是,计算一个像素与其所覆盖的纹理区域的面积比例(实际上取的是x,y方向上的最大缩放值),由此选定Mipmap层

4. 如何生成

可以通过程序来生成或者直接调用gluScaleImage()来逐级生成,然后通过glTexImage2D(GLenum target, GLint level,...)设置参数 level,来加载不同分辨率的Mipmap

​ 参数 level:0 是最高分辨率,1是四分之一分辨率,…

也可以通过int gluBuild2DMipmaps()来直接自动生成并加载 mipmap。

OpenGL 3.0 及以后,可使用glGenerateMipmap(GLenum target)来生成当前纹理的 mipmap,也就是将 mipmap 从 glu库提到了核心库中

5. 存在问题

过像素覆盖区域非常狭长,与正方形相差较大,效果不好,但是可以使用各向异性滤波来进行克服[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t0oRXVH9-1576058740571)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1574934745355.png)]

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!