• OpenGL编程指南第九章:纹理映射


    纹理(texture)是一块矩形数据序列,存储的数据为颜色、亮度、alpha值。纹理数据的每个单位叫做texel,纹理数据可以被映射到任何几何形状的表面。

    1、纹理映射基础

    使用纹理是一个相对复杂的操作,一般需要以下几个步骤:
    1、创建texture对象,并指定数据:
    纹理数据可以是二维的图像,也可以是一维或三维的;

    2、指定texture将被以何种方式与像素进行映射:
    有四种函数可以用于对fragment color和texture color进行计算,一是直接使用纹理颜色进行替换,二是用纹理颜色对fragment颜色进行模运算,三是进行scale运算,四是依据纹理值,用一个颜色常量与fragment进行混合;

    3、激活像素映射功能:
            调用glEnable()函数来激活,参数可以是GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_CUBE_MAP,分别激活一维、二维、三维、立体纹理,如果先后激活多个那个生效的是较大维数的;

    4、绘制场景,指定几何坐标和纹理坐标:
    就像为顶点指定颜色一样,为对应顶点指定纹理坐标,对二维的纹理来说,坐标就是一对0~1的浮点数。就像颜色的flat shade一样,几何顶点之间的纹理坐标自动进行插值计算。这样,顶点之外的部分,纹理坐标超出了[0,1],其计算方式需要你来指定:重复,剪切...。2\


    2、纹理数据

    为纹理指定数据的api是glTexImage2D(GLenum target, GLint level, GLint internalFormat,GLsizei width, GLsizei height, GLint border,GLenum format, GLenum type, const GLvoid *texels);
    taget:GL_TEXTURE_2D等常量
    level:当你准备为同一纹理对象,提供多个分辨率的版本,level值派上用场。否则设为0即可
    internalFormat:指定了纹理元素的数据成员,比如GL_RBGA,GL_ALPHA
    width,height,border:指定了纹理的尺寸、边宽,OpenGL2.0以前,width和height必须是2的指数
    format,type:传入数据的格式,类型,与glDrawPixels一致

    可以直接读取frameBuffer数据作为纹理glCopyTexImage2D(GLenum target, GLint level,GLint internalFormat, GLint x, GLint y,GLsizei width, GLsizei height, GLint border);
    GLint internalFormat, GLint x, GLint y,GLsizei width, GLsizei height, GLint border);
    x,y:欲读取frameBuffer的坐标,其他参数与glTexImage2D相同

    修改现有纹理对象的数据比重新创建纹理对象的代价要低,glTexSubImage2D(GLenum target, GLint level, GLint xoffset,GLint yoffset, GLsizei width, GLsizei height,
    GLenum format, GLenum type,const GLvoid *texels);
    xOffset,yOffset:为目标纹理的坐标

    glCopyTexSubImage2D与glTexSubImage2D功能类似,数据来自framebuffer。


    纹理可以以压缩形式存储,或直接从压缩的模式加载。
    判断纹理是否压缩:glGetTexLevelParameteriv(GL_TEXTURE_2D, GL_TEXTURE_COMPRESSED,BOOL &compressed);
    纹理的压缩模式OpenGL并没有做规定,具体的实现会定义,加载压缩纹理glCompressedTexImage*D()


    3、Mipmaps

    纹理对象同样要放在场景中显示,当物理远离视点是,纹理对象会被缩放过滤。在这个持续过程中,会出现闪烁现象。为了避免,可以为纹理对象提供一组不同分辨率的图像数据,叫做Mipmap。
    你需要提供从最大尺寸到1x1之间所有2的冥次尺寸的图像;glTexImage2D() api从level=0开始设置,0代表最大尺寸。
    可以使用API基于最大尺寸图像为纹理产生Mipmaps,glGenerateMipmap(GLenum target);

    有时候可以对Mipmaps的level进行限制,比如不希望有尺寸特别小的mipmap,或者不希望纹理在极大和极小level之间进行切换,这些可以通过API glTexParameter*()完成,参数如下:
    GL_TEXTURE_BASE_LEVEL:最大尺寸的level限制;
    GL_TEXTURE_MAX_LEVEL:最小尺寸的mipmap level限制;通过这两个值可以减少需要提供的mipmap
    GL_TEXTURE_MIN_LOD和GL_TEXTURE_MAX_LOD:则影响OpengGL具体渲染时对mimap level的选择

    4、过滤

    纹理texel和像素之间没有一一对应关系,有可能1个texel需要对应一片像素,也有可能一片texel最终对应到一个像素。这些操作叫做过滤,OpenGL允许设置一些选项,来控制过滤,API也是glTexParameteri。
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST):将纹理放大滤镜设置为GL_NEAREST;
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST):蒋纹理缩小滤镜设置为GL_NEAREST;
    GL_NEAREST的意思是选择离纹理坐标最近的一个像素;其他值还有GL_LINEAR—坐标中心点附近的2x2像素进行加权计算。

    5、纹理对象管理

    每个纹理对象有一个无符号整形作为名字,为了避免重复,使用API glGenTextures(GLsizei n, GLuint *textureNames)来生成,glIsTexture()可以判断一个名字是不是使用中的纹理对象名字。
    使用纹理对象前,需要先绑定glBindTexture(),之后glTexImage*(), glTexSubImage*()这些操作就针对绑定的Texture了。
    glDeleteTextures可以删除不再使用的Texture。

    某些OpenGL实现支持一组高效的texture,叫做resident,通常有专门的硬件来存储和操作这些texture,所以你应当尽量把texture放进这个组。如果程序中texture的数量超出了这个组的限制,那么有些texture就不能进入这个组,可以通过glGetTexParameter(GL_TEXTURE_RESIDENT)或glAreTexturesResident来判断texture是否在resident中。可以使用glPrioritizeTextures()给texture附加一个优先级,来增加进入resident的几率。

    6、纹理函数

    纹理的数据一般是作为颜色取代物体表面的颜色,但也可以使用别的纹理函数。
    glTexEnv{if}v(GLenum target, GLenum pname, const TYPE *param):
    targe:GL_TEXTURE_FILTER_CONTROL 或 GL_TEXTURE_ENV
    pname:当target是GL_TEXTURE_ENV时,如果pname是GL_TEXTURE_ENV_MODE,param可以是GL_DECAL, GL_REPLACE, GL_MODULATE,GL_BLEND, GL_ADD, GL_COMBINE,指定了使用何种函数对fragement和纹理颜色进行结合计算;如果pname是GL_TEXTURE_ENV_COLOR,则指定一个颜色常量用于纹理操作。
    当然texture的internalformat也会影响这里的计算。

    7、纹理坐标

    为顶点指定纹理坐标,就像为顶点指定坐标一样,一般用(s,t,r,q)来表示各分量,就像用(x,y,z,w)来表示顶点坐标一样。设置纹理坐标的API是glTexCoord{1234}{sifd}v(const TYPE *coords);

    纹理是矩形区域,而几何物体表面未必能够映射为矩形,比如曲面。除了圆柱、圆锥这样规则的曲面外,其他的曲面映射到矩形texture时必然发生扭曲。比如球体,越接近两极扭曲越严重。曲面都是通过大量的小多边形来组成的,为这些多边形顶点指定纹理坐标可能会比较复杂。

    如果指定[0,1]以外的纹理坐标值,就会发生clamp或repeat,前者将超出范围的坐标值调整为0或1;后者会对纹理进行重复铺开。clamp和repeat的模式可以通过glTexParameteri来设置。

    OpenGL提供API自动为几何顶点产生纹理坐标:
    void glTexGen{ifd}(GLenum coord, GLenum pname, TYPE param);
    void glTexGen{ifd}v(GLenum coord, GLenum pname, const TYPE *param);
    void glTexGen{ifd}v(GLenum coord, GLenum pname, const TYPE *param);
    coord:GL_S, GL_T, GL_R, or GL_Q指定产生那个维度的坐标
    pname:GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, or GL_EYE_PLANE,GL_TEXTURE_GEN_MODE指定产生坐标的方式,值可以是GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP,GL_REFLECTION_MAP, or GL_NORMAL_MAP;GL_OBJECT_PLANE和GL_EYE_PLANE则提供一个参考平面。

    如果GL_TEXTURE_GEN_MODE指定为GL_OBJECT_LINEAR,在加上GL_OBJECT_PLANE用(P0,P1,P2,P3)表示,那么顶点的纹理坐标值的计算表达式:P0*x+P1*y+P2*z+p3*w,即顶点到平面的距离。如果GL_TEXTURE_GEN_MODE是GL_EYE_LINEAR那么顶点坐标会转换到眼坐标系,在进行计算。实际效果是,前者纹理相对于物体固定,后者纹理相对于场景固定。

    GL_SPHERE_MAP:
    这种生成纹理坐标的方式主要用于对环境产生反射,具体见参考书

    Cube MAP:
    这个是使用六个纹理对象,组成一个中心在原点的立方体,纹理坐标就是一个单位向量,指向立方体上的一点。这种情况下使用自动坐标生成,将生成模式设为GL_REFLECTION_MAP或GL_NORMAL_MAP。原书代码中有一个例子。

    8、Multitexturing

    OpenGL可以同时使用多个纹理,这些纹理被绑定到一个纹理单元序列,Texture Units,会被依次计算、应用到顶点。每个纹理单元有其独立的图像、Filter参数、坐标变换矩阵、坐标自动产生方法、Vertex-Array。

    void glActiveTexture(GLenum texUnit)激活对应的纹理单元,之后所有的纹理操作都是针对这个纹理单元;参数GL_TEXTUREi,i取值0至k,最大值由具体实现决定。

    此时glTexCoord相当于指定了GL_TEXTURE0的坐标,为其他单元指定坐标必须使用glMultiTexCoord。


    9、纹理组合函数

    接第六节,在使用单个或多个纹理单元时,纹理组合函数可以灵活控制fragment和纹理颜色的组合。组合函数可以操作3来源的颜色或alpha值进行计算,产生一个输出值。在使用多个纹理单元时,组合函数就形成了一个流水线。

    void glTexEnv{if}(GLenum target, GLenum pname, TYPE param)对组合函数进行控制:target是GL_TEXTURE_ENV,pname是GL_TEXTURE_ENV_MODE,param是GL_DECAL, GL_REPLACE, GL_MODULATE,GL_BLEND, GL_ADD, or GL_COMBINE;当param是GL_COMBINE时,可以继续对组合函数进行配置。

    通过pname=GL_COMBINE_RGB可以配置RGB部分的组合函数:GL_REPLACE, GL_MODULATE, GL_ADD,GL_ADD_SIGNED, GL_INTERPOLATE,GL_SUBTRACT, GL_DOT3_RGB, or GL_DOT3_RGBA;这些函数使用1至3个参数进行计算,这里分别表示为arg0,arg1,arg2。 于是GL_REPLACE可用表达式arg0表示,GL_ADD是arg0+arg1,GL_MODULATE是arg0*arg1。

    上面arg0,arg1的来源可以通过pname=GL_SRC0_RGB,GL_SRC1_RGB,GL_SRC2_RGB来设定,param可以是GL_TEXTURE:纹理颜色值,GL_TEXTUREn:纹理单元n的颜色值,GL_CONSTANT:颜色常量,GL_PRIMARY_COLOR:fragement应用纹理之前的颜色,GL_PREVIOUS:前一轮组合函数的输出值,对于纹理单元0来说,等同于GL_PRIMARY_COLOR。一般来说我们把GL_SRC2_RGB(arg2)指定为常量,实际OpenGL默认就是这样的。

    对于选定的颜色源,还可以指定使用它的哪一部分参与运算,pname=GL_OPERANDi_RGB,param可以是GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR,  GL_SRC_ALPHA,  or  GL_ONE_MINUS_SRC_ALPHA,如果是GL_SRC_ALPHA,那么参与计算的RGB值就是(a,a,a)。pname=GL_OPERANDi_ALPHA,param可以是GL_SRC_ALPHA or GL_ONE_MINUS_SRC_ALPHA.

    还可以设定一个scale因子,glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE, 1.0);glTexEnvf(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1.0);

    10、secondary color

    上 一节中提到的primary color就是光照效果计算之后的fragment color,应用纹理后,雾效果之前,OpenGL可以给fragment增加一个secondary color以达到更逼真的高光效果。

    没有光照的情况下,glSecondaryColor*()设置了颜色,glEnable(GL_COLOR_SUM)开启,则secondary color会被加到纹理之后的fragment上。

    在有光照的情况下,将光照处理后的fragment颜色与纹理颜色组合后,会削弱高光效果。这时,可以在处理光照效果是计算两个颜色,一个叫primary color,没有包含specular分量,另一个就是secondary color,值就是specular分量。后者会在texture之后加到fragment。

    11、Point Sprites

    glPointSize()可以控制点的大小,但纯色的点显然不能满足要求,point sprite就是解决这个问题的。

    可以对点图元里面的fragment应用纹理,需要调用glTexEnv*(GL_POINT_SPRITE, GL_COORD_REPLACE,GL_TRUE)激活这个功能。sprite的texture坐标s分量从在sprite最左侧fragement处为0,最右侧为1,线性增加,t分量增可以通过设置来改变方向,glPointParameter(GL_POINT_SPRITE_COORD_ORIGIN,GL_LOWER_LEFT or GL_UPPER_LEFT)。

    12、Texture Matrix

    就像顶点可以使用模型视图变换,纹理坐标也可以通过一个4x4的矩阵来进行变换。默认这个矩阵就是单位矩阵,调用glMatrixMode(GL_TEXTURE)后,就可以像操作模型矩阵一样操作纹理矩阵了。

    13、Depth Texture

    光照系统中,并没有为物体生成阴影,通过读取depth buffer作为texture,再加上多轮的渲染,可以生成阴影。

    首先把视点移动到光源处,绘制场景,读取depth buffer作为texture保存。

    第二次绘制场景,为图元生成纹理坐标,(s,t)用来引用texture的坐标位置,第三维度r则是顶点到光源的距离。

    应用纹理时,比较fragment的纹理坐标r和texture (s,t)的值,可以决定对该fragment是否应用阴影纹理。




  • 相关阅读:
    C# Array.Sort 省内排序
    Centos7开机启动tomcat8
    使用GeoWebCache发布ArcGIS切片地图(实现高清电子地图)
    获取经纬度之间距离的Java工具类
    centos7上安装rar解压软件
    GeoServer之发布Geotiff存在的问题
    $GPRMC解析
    如何在IDEA单元测试中使用Scanner获取输入内容
    GeoServer修改使用内存
    Github无法访问解决办法
  • 原文地址:https://www.cnblogs.com/longhuihu/p/10423331.html
Copyright © 2020-2023  润新知