如何让模型表面的细节效果更突显
Texture mapping(纹理映射,纹理贴图)
纹理映射的主要思想
将一给定的 纹理函数 映射到物体表面上,在对物体表面进行光亮度计算时可采用相应的纹理函数值来影响光照模型中的参数(如漫反射光亮度)以产生纹理效果,例如下面的光照明模型中漫射反射光的影响
上面的 K t K_t Kt就是改变了漫反射的颜色,纹理映射不仅限于改变颜色,而且还可以改变 颜色、高光、凹凸、反射以及透明度等等都可以采用纹理映射来改变
颜色纹理
大概分为二维纹理映射,以及三维纹理映射,二维纹理就是一个面,三维是一个块,又称体纹理映射,就是实现定义好一幅三维纹理
三维物体每一个点(x,y,z)均有一个纹理值t(x,y,z),那么物体空间就可以映射到一个三维空间中,但是三维纹理映射不常用,因为其太消耗空间
几何纹理
凹凸纹理映射以及位移纹理映射
Bump mapping(凹凸纹理映射),是图形学中应用最广泛的技术之一,由Blinn在1978年提出
大致意思就是对物体表面各采样点的法向作微小的扰动,由于表面光亮度是景物表面的法向的函数,上述法向的扰动必将导致表面光亮度的突变,从而产生表面凹凸不平的效果
Offset map 是偏移贴图,比如原来法向是 N ⃗ \vec{N} N,它的偏移量是 b u ⃗ \vec {b_u} bu和 b v ⃗ \vec{b_v} bv,偏移之后是 N ⃗ ‘ \vec{N}^` N‘,而纹理里面记录的就是 b u ⃗ \vec {b_u} bu和 b v ⃗ \vec{b_v} bv每一个点的法向的偏移量
图中记录每一点的高度,通过一点和另一点高度的差,然后法向就可以求出来(高度图,高度场以及对应的Normal)
法向图应用的比较广泛,是直接记录法向然后拿到法向后直接用,上面蓝色的图就是法向图,blue通道最大并且都是一个方向因此是偏蓝色的(rgb通道分别表示(x,y,z方向))
凹凸纹理映射缺陷,不能使物体的边缘也产生凹凸,由下图可以看到球体的轮廓线还是光滑的,没有任何凹凸感,并且凹凸纹理也不会投下自身的阴影
Displacement map(位移纹理映射),在绘制物体时,对物体表面采样点的位置做微小扰动,产生凹凸不平的细节,也就是物体表面上的每一个点 P ( u , v ) P(u,v) P(u,v),都沿该点的法向量方向位移 F ( 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) P‘(u,v)=P(u,v)+F(u,v)×N(u,v)
位移函数 F ( u , v ) F(u,v) F(u,v)可以记录在一幅纹理中
如何定义二维纹理与物体表面的映射关系,例如把一个平面映射到球面上,需要建立映射关系,球在数学上是不可展表面,不能完全展开,因此很难无变形的映射到球面上
表面如果太复杂的话是很难建立映射关系的
投影式纹理映射(projective texture mapping)是从投影点出发,将二维纹理投到三维物体表面上,如同放映机中投影出的画面一样
例如下面的花瓶与圆柱比较相近,可以先把纹理映射到圆柱上,然后再将圆柱映射到花瓶上,相当于有一个中间几何体
3ds Max中的 U V W UVW UVW贴图修改器就是通过中间几何体来进行的纹理映射
表面不可展,然后就把表面给抛开分成不同的部件,然后每个部件再进行展开,展开之后就可以进行纹理映射
例如上面图中的三角形需要在 P 0 P_0 P0处映射纹理,只需要指定 P 0 P_0 P0处所对应的纹理空间中的坐标, P 0 P_0 P0在纹理空间中对应的点 ( s 0 , t 0 ) (s_0,t_0) (s0,t0)就是它的纹理坐标
可以直接给定多边形定点的纹理坐标,多边形内的点的纹理坐标可以通过双线性插值来取得,因此纹理映射是从顶点模块开始计算
纹理映射在图形流水线中的阶段
应用Texture Mapping(纹理)的四个步骤
主要步骤如下:
glGenTextures(1, &texName); 生成一个纹理编号,也可以定义好几个纹理对象
void glGenTetures(GLsizei n, GLuint *textures);纹理编号可以放一个数组中,每个纹理对象对应一个标号,它代表了当前纹理的数据、参数等所有信息
glBindTexture(GL_TEXTURE_2D, txeName); 绑定 texName 纹理编号
void glTexture2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); 把一幅纹理数据从内存中装载进显存
纹理的大小
OpenGL 2.0 及以上版本中,纹理可以是任意大小,OpenGL 3.1 及以上版本不支持 border,要求 border 必须是 0
用完纹理资源之后需要释放纹理资源,通过下面这个函数
void glDeleteTextures(GLsizei n, const Gluint * textures); // textures 数组
确定纹理如何应用到每个像素上需要用到下面这个函数
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
:用纹理值替换片元颜色启用纹理贴图用到如下函数
glEnable(GL_TEXTURE_2D); // 启用纹理贴图
glBindTexture(GL_TEXTURE_2D, texName); // 绑定纹理参数
在绘制场景的时候,需要提供纹理坐标和几何图形坐标
纹理坐标的取值范围是[0, 1]
,如果超出则可以设置纹理重复映射方式
用以下函数可以对纹理坐标进行几何变换
glMatrixMode(GL_TEXTURE);// 纹理几何变换
glTranslatef(x, y, z); // 平移
glRotatef(angle, x, y, z); // 旋转
glScalef(sx, sy, sz); // 缩放
glMatrixMode(GL_MODELVIEW); // 将矩阵变换模式切换回来
// 绘制几何物体
首先,像素片元都是有面积的,因此纹理映射也是有面积的,如果贴图的分辨率比较高,可以看出一个坐标覆盖了好几个纹元,因此在屏幕上显示的像素就需要通过某种方式来决定显示哪一个纹元,比如可以让覆盖的几个纹元通过加权平均来获得像素的显示
Texture Filtering(纹理过滤)
一个像素一般不会正好对应一个纹元(texel),所以像素的颜色无法直接得到,需要经过一定的运算,这个过程就是纹理过滤(过滤就是经过一定处理)
纹理与多边形在屏幕上的区域另种对应方式时的表示
在OpenGL中对应上面两种方式,可以使用以下参数
GL_TEXTURE_MAG_FILTER
纹理需要放大时GL_TEXTURE_MIN_FILTER
纹理需要缩放时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);
首先,GL_LINEAR是选择距像素中心最近的 四个像素的加权平均值作为像素的颜色,但是当一个屏幕像素覆盖的纹理区域大于4个纹元的时候改如何处理,可以使用以下方法
首先,生成原纹理贴图不同分辨率版本的纹理,然后再降采样边长各减去 1 2 \frac{1}{2} 21得到降采样的版本,最后降采样到一个像素
Linear
过滤假设文元覆盖了16个纹元,就直接选取对应分辨率版本的纹理直接进行过滤,总之就是预先生成了中间结果,然后在过滤的时候在中间结果进行选择既可,然后在Linear过滤的时候同样还是加权平均了4个纹元
Mipmap的处理方式,只需要对纹理缩小的时候(Minification Filters)GL_TEXTURE_MIN_LILTER
,放大时只需要使用GL_NEAREST
和GL_LINEAR
,因为放大时纹理本身分辨率就不够因此没必要再使用Mipmap去缩减分辨率
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中产生纹理值,最终纹理值是这两个取值的加权平均值GL_LINEAR_MIPMAP_LINEAR 被称为三线性过滤
如何选择合适的Mipmap层,Mipmap层的选择在OpenGL中会自动选取,主要方法是,计算一个像素与其所覆盖的纹理区域的面积比例(实际上取的是x,y方向上的最大缩放值),由此选定Mipmap层
可以通过程序来生成或者直接调用gluScaleImage()
来逐级生成,然后通过glTexImage2D(GLenum target, GLint level,...)
设置参数 level,来加载不同分辨率的Mipmap
参数 level:0 是最高分辨率,1是四分之一分辨率,…
也可以通过int gluBuild2DMipmaps()
来直接自动生成并加载 mipmap。
OpenGL 3.0 及以后,可使用
glGenerateMipmap(GLenum target)
来生成当前纹理的 mipmap,也就是将 mipmap 从 glu库提到了核心库中