文章目录
- [6.1 纹理综述](#6.1 纹理综述)
- [6.2 基木纹理类型](#6.2 基木纹理类型)
- [6.3 创建并初始化纹理](#6.3 创建并初始化纹理)
- [6.4 指定纹理数据](#6.4 指定纹理数据)
-
- [6.4.1 显式设置纹理数据](#6.4.1 显式设置纹理数据)
- [6.4.2 从缓存(目标对象GL_PIXEL_UNPACK_BUFFER)中加载纹理](#6.4.2 从缓存(目标对象GL_PIXEL_UNPACK_BUFFER)中加载纹理)
- [6.4.3 从文件加载图像(DDS为例)](#6.4.3 从文件加载图像(DDS为例))
- [6.4.4 获取纹理数据(回内存或缓存)](#6.4.4 获取纹理数据(回内存或缓存))
- [6.4.5 纹理数据的排列布局](#6.4.5 纹理数据的排列布局)
- [6.5 纹理格式](#6.5 纹理格式)
-
- [6.5.1 内部格式internalformat](#6.5.1 内部格式internalformat)
- [6.5.2 外部格式](#6.5.2 外部格式)
- [6.6 压缩纹理](#6.6 压缩纹理)
- [6.7 采样器对象](#6.7 采样器对象)
- [6.8 纹理的使用(着色器中)](#6.8 纹理的使用(着色器中))
-
- [6.8.1 纹理坐标](#6.8.1 纹理坐标)
- [6.8.2 排列纹理数据 SWIZZLE](#6.8.2 排列纹理数据 SWIZZLE)
- [6.8.3 使用多重纹理](#6.8.3 使用多重纹理)
- [6.9 复杂纹理类型](#6.9 复杂纹理类型)
-
- [6.9.1 3D纹理](#6.9.1 3D纹理)
- [6.9.2 纹理数组](#6.9.2 纹理数组)
-
- [无绑定加载不限制尺寸的纹理数组sampler2D textures[16]](#无绑定加载不限制尺寸的纹理数组sampler2D textures[16])
- [6.9.3 立方体映射纹理](#6.9.3 立方体映射纹理)
- [6.9.4 阴影采样器](#6.9.4 阴影采样器)
- [6.9.5 深度------模板纹理](#6.9.5 深度——模板纹理)
- [6.9.6 缓存纹理](#6.9.6 缓存纹理)
6.1 纹理综述
- 纹理贴图(或者简称为纹理),可以是一张图片中获得,纹理也可能通过程序生成(参见第8章)或者由OpenGL将纹理作为一种显示设备渲染得到(RTT)。
- 纹理映射mapping就是允许用户在着色器中从一种特殊的表类型变量中查找数据,例如颜色值。
- OpenGL的所有着色阶段都允许我们访问这类纹理贴图(texture map)变量。
- 纹理最常被使用在片元处理过程中。
- 纹理是由一维、二维或者三维纹素(texel)组成的,其中通常包含颜色数据信息;也可以当作一种数据表,在着色器中进行查询和指定工作。
- 在自己的程序中使用纹理映射步骤:
- 创建一个纹理对象并且加载纹素数据(例如从buffer中传输数据到纹理对象)。
- 为顶点数据增加纹理坐标。
- 如果要在着色器中使用纹理采样器,将它关联到纹理(实际是通过纹理卡槽/纹理单元)。
- 在着色器中通过纹理采样器获取纹素数据(依据是纹理坐标)。
6.2 基木纹理类型
- OpenGL支持很多种纹理对象的类型,包括不同的维度和布局,例如一维纹理、二维纹理、三维纹理、立方体映射纹理,以及缓存纹理(任意一维的纹素数组的纹理)和数组纹理。
- 每个纹理对象都代表一种可以构成完整纹理的图片形式。
- 我们有必要将一个纹理对象看作是一组图片的集合,每张图片都可以独立访问,然后所有图片一起进行操作!而这一过程在概念上是不同于纹理(纹理贴图)这个自然名词本身的。
- 每张图片都是由一维、二维或者三维纹素的数组构成 ,多张图片可以进行"堆叠 ",也就是将一张图片摞在另一张上面,因此构成了一个被称作mipmap 的金字塔形式。
- 数组纹理:由一维或者二维图片切片组成的数组。
- 立方体映射纹理:它只有6个切片的数组纹理。
- 立方体映射数组:它只有6的倍数个切片的数组纹理。
- 纹理也可以用来表达多重采样的表面。
多重采样(multisampling)是一种反锯齿的实现方案,它要求每个纹素(或者像素)都记录多个独立的颜色数据 ,然后在渲染流程当中将这些颜色进行合并得到最终的输出结果。一张多重采样纹理的每个纹素可能会有多个采样值(通常是2~8个)。
- 纹理绑定到 OpenGL环境中需要通过纹理单元(texture unit)来完成,它是一个不小于0,不大于设备所支持的最大单元数量的绑定点整数值。如果环境支持多个纹理单元,多个纹理可以同时绑定到同一个环境当中。
- 一旦纹理绑定到环境中,我们就可以在着色器中通过采样器变量的方式去访问它(采样器的值为需要访问的纹理单元),但需要保证纹理维度一致。
- 以下是着色器中可用的一系列纹理维度信息(也称作纹理目标),以及对应的采样器类型(从而实现对这类纹理纹素的访问):
- 长方形纹理目标(二维纹理类型):可以表达简单的长方形区域中的纹素集合;它不能有 mipmap也不能构成数组类型。
- 缓存纹理(任意一维的纹素数组的纹理) :它也没有mipmap且无法构成数组。缓存纹理的存储区域(即内存)通常是通过缓存对象来表达的。
- 缓存纹理的存在使得我们可以在任意着色器阶段访问诸如顶点数据这样的内容(例如transform feedback实现的粒子系统),而不需要将数据再重新复制到纹理图片中(将数据从buffer复制到纹理对象)。
6.3 创建并初始化纹理
- 讲解如何在用户程序中创建和设置纹理,包括使用纹理代理(proxy texture)来检查openGL具体实现中是否支持某个功能的方法。
- 创建纹理对象:创建时需要设置名称,纹理的维度和类型
cpp
//返回n个当前没有使用的纹理对象名称,并保存到textures数组中(不一定连续数值)。
//target指定了维度
//0是一个保留的纹理名称,永远不会由glCreateTextures()返回。
void glCreateTextures(GLenum target,
GLsizei n,
GLuint *textures);
- 删除纹理对象:
cpp
//纹理所对应的存储内容的所有引用都会被删除,存储空间本身则会在它不再被使用之后由OpenGL负责释放。
//如果一个当前绑定到环境的纹理被删除了,那么这个绑定点也会被删除,
//相当于调用 glBindTextureUnit()并设置texture 参数为0。
//如果试图删除不存在的纹理名称,将被忽略.
void glDeleteTextures( GLsizei n,
const GLuint * textures);
- 是否为纹理对象:
cpp
//如果texture为0或者是一个非0值,但是并非是已有纹理的名称,那么返回GL_FALSE.
GLboolean glIsTexture(GLuint texture);
- 创建好纹理对象的名称之后,纹理会保持target对应的默认纹理状态,但是没有任何内容。
- 在向纹理传入数据之前,需要告诉**OpenGL这个纹理的大小是多少(**纹理就可以分配内存空间来管理这些数据):
- 而对于某个维度的纹理数组数据的分配而言,通常我们需要把存储空间的维度加1,多出来的维度是用来设置数组大小的。
- 对于一维数组纹理来说,height就是纹理切片的数量,而对于二维数组纹理来说,depth是切片的数量。
- 对于立方体映射数组,可以使用glTextureStorage3D并且设置depth为立方体映射表面的数量,depth应该为6的倍数。
- internalformat参考
- 一旦我们为纹理分配了存储空间,那么它就无法被重新分配或者释放了,只有删除的时候才会删除对应的存储空间。
- 以下都是用来为纹理创建永久的存储空间的。
- 纹理用来存储给定纹理中的所有纹素的内存总量,它需要根据选定的内部格式和对应的分辨率(w,h,d)来决定。
- 至于纹理的内容,是可以通过glTextureSubImage2D()这样的函数来更改的。
cpp
//或者glTexStorage1D-3D
// levels是分配给纹理的 mipmap的层数。
//第0层也就是纹理的基础层,后续金字塔的每一层都会比之前的层数据要更少。
//internalformat设置纹理存储时使用的内部数据格式。
void glTextureStorage1D( GLuint texture
GLsizei levels
GLenum internalformat
GLsizei width);
void glTextureStorage2D( GLuint texture,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height);
void glTextureStorage3D( GLuint texture,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLsizei depth);
// 多重采样纹理创建存储空间,或glTexStorage2DMultisample
//texture必须是对应类型。
//samples设置了纹理中采样点的数值。
//fixedsamplelocations指定采样位置是否固定。
//为GL_TRUE则OpenGL将会对每个纹素中的同一个采样点使用同一个子纹素位置,这意味着采样点在像素格子中的具体位置在所有像素上都是一致的。
//GL_FALSE则采样位置在不同像素中可以不同,即采样位置是可变的
void glTextureStorage2DMultisample( GLuint texture,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLboolean fixedsamplelocations);
void glTextureStorage3DMultisample( GLuint texture,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLboolean fixedsamplelocations);
- 绑定到纹理单元 (用于实际使用纹理,也就是在着色器中读取它的数据):
- OpenGL支持的纹理单元的最大值可以通过GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 常量的数值来查询,在OpenGL4.0中应当至少是80个。
cpp
//会完成两项工作,相当于结合了glActiveTexture和glBindTexture
//如果设置绑定名称texture为0,
//那么OpenGL会删除当前激活的纹理单元上所有已经绑定的对象,也就是不绑定纹理的状态。
//如果texture不是0也不是通过glCreateTextures0创建的名称,那么将产生GL_INVALID_OPERATION错误。
//
void glBindTextureUnit( GLuint unit,
GLuint texture);
代理纹理
- 纹理代理(proxy texture)可用来检查openGL具体实现中是否支持某个功能的方法。
- 如果通过代理纹理目标来请求分配一个必然失败的纹理的话,硬件系统会告诉用户这个请求对于标准目标而言是否可以成功,或者必然会失败。
- 如果代理纹理目标对应的纹理分配失败的话,那么虚拟代理目标产生的纹理的宽度和高度都是0。如果查询代理目标的尺寸,就可以知道刚才的调用是否成功。
- 除了GL_TEXTURE_BUFFER之外的所有纹理目标都有一个对应的代理目标。
6.4 指定纹理数据
- 介绍将图像数据载入到纹理对象的方法。
- 如何在 OpenGL,中格式化设置纹理数据,以及如何将数据传递给纹理对象。
6.4.1 显式设置纹理数据
- 纹理数据的排列方式:从左到右,从上到下排列。
- OpenGL所支持的一些参数可以改变图像数据在内存中的布局方式。
- OpenGL所支持的一些参数可以改变图像数据在内存中的布局方式。
- 将数据载人到纹理对象当中:
cpp
//或者glTexImage*:分配新的存储空间并上传整个3D纹理数据。
//如果纹理对象之前已经分配过存储,这个函数会重新分配存储(并释放之前的存储).会改变纹理尺寸的。
//或者glTexSubImage*(GLenum target。。。。
//替换 texture 所指定的纹理的某个区域中的数据。
//level中设置了需要更新的mipmap层,而format和type 参数指定了新的纹理数据的外部存储格式。
//xofset、yoffset和zofset(如果存在)分别表示x、y、z三个维度上的纹素偏移量。
//如果texture或者target表示一维的数组纹理,那么yofset和height分别指定更新后数组的第一层切片和总的切片数;
//否则,它们表示的就是纹理坐标。
//下面为更新3D纹理的一部分(或全部)数据,但不重新分配存储空间。
void glTextureSubImage1D( GLuint texture,
GLint level,
GLint xoffset,
GLsizei width,
GLenum format,
GLenum type,
const void *pixels);
void glTextureSubImage2D( GLuint texture,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void *pixels);
void glTextureSubImage3D( GLuint texture,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
const void *pixels);
将静态数据载入到纹理对象
cpp
//分配纹理数据的存储空间
glTextureStorage2D(tex_checkerboard,4,GL_R8,8,8);
//设置纹理数据
glTextureSubImage2D(tex_checkerboard,
0,//mipmap层0
0,0,//x,y偏移
8,8,//宽高
GL_RED,GL_UNSIGNED_BYTE,// 内部格式和数据类型
tex_checkerboard_data//详见上文);
//浮点数的
glTextureStorage2D(tex_color,2,GL_RGBA32F,2,2);
//设置纹理数据
glTextureSubImage2D(tex_color,
0,//mipmap层0
0,0,//x,y偏移
2,2,//宽高
GL_RGBA,GL_FLOAT,// 内部格式和数据类型
tex_color_data//详见上文);
- 纹理的内部格式,它需要和提供的纹理数据互相匹配。
- 我们不一定要使用和数据完全一致的内部格式。OpenGL中定义了一些很好的规则来进行用户数据和内部格式的转化,我们可以在OpenGl规格说明书中找到相关的详细解释。
- 数据在应用程序本地内存中,glTextureSubImage2D需要先对数据进行拷贝,然后函数才会返回,好处在于:应用程序在函数返回之后依然可以自由修改之前传输的data 数据。
6.4.2 从缓存(目标对象GL_PIXEL_UNPACK_BUFFER)中加载纹理
cpp
glCreateBuffers(1,&buf);
glNameBufferStorage(buf,sizeof(tex_checkerboard_data),tex_checkerboard_data,0);
glTextureStorage2D(texture,4,GL_R8,8,8);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER,buf);
glTextureSubImage2D(texture,
0,
0,0,
GL_RED,GL_UNSIGNED_BYTE,
NULL
);
- 好处在于: 数据不是立即从缓存对象向纹理进行传输的,而是在着色器请求数据的时候才会执行这一操作。因此应用程序的运行和数据的传输操作可以并行进行。
6.4.3 从文件加载图像(DDS为例)
- 对于磁盘上的大尺寸图像数据,一般使用JPEG、PNG、GIF、DDS,或者其他一些格式。
- OpenGL的纹理可以使用无压缩的像素数据,也可以使用特定算法压缩后的数据。
- 需要设法将图像数据文件解码到内存中,然后OpenGL才可以读取数据并初始化内部的纹理存储空间。
读取一个图像文件并返回内存中的纹素数据
- 并且会传递一些信息,帮助ogl对像素数据进行解析,包括:
- 宽度,高度(像素为单位)
- opengl像素格式(GL_RGB表示RGB形式的像素点)
- 建议在纹理中使用的内部格式
- 纹理中mipmap的层次数量
- 像素中每个分量的数据类型
- 图像数据本身
cpp
// Enough mips for 16K x 16K, which is the minumum required for OpenGL 4.x
#define MAX_TEXTURE_MIPS 14
// Each texture image data structure contains an array of MAX_TEXTURE_MIPS,对于16Kx16K的纹理也是足够的
// of these mipmap structures. The structure represents the mipmap data for
// all slices at that level.
struct vglImageMipData
{
GLsizei width; // Width of this mipmap level
GLsizei height; // Height of this mipmap level
GLsizei depth; // Depth pof mipmap level
GLsizeiptr mipStride; // Distance in bytes between mip levels in memory,相邻级别的mipmap 在内存中的距离
GLvoid* data; // Pointer to data
};
struct vglImageData
{
GLenum target; // Texture target (1D, 2D, cubemap, array, etc.)
GLenum internalFormat; // Recommended internal format (GL_RGBA32F, etc).
GLenum format; // Format in memory,内存中的格式
GLenum type; // Type in memory (GL_RED, GL_RGB, etc.),内存中的数据类型
GLenum swizzle[4]; // Swizzle for RGBA,RGBA 分量的乱序设置(swizzle)
GLsizei mipLevels; // Number of present mipmap levels
GLsizei slices; // Number of slices (for arrays),(对于纹理数组)切片的数量
GLsizeiptr sliceStride; // Distance in bytes between slices of an array texture,纹理数组中相邻切片之间的距离
GLsizeiptr totalDataSize; // Complete amount of data allocated for texture
vglImageMipData mip[MAX_TEXTURE_MIPS]; // Actual mipmap data
};
- 对于内存中图像数据的创建、初始化、属性修改和删除操作:
cpp
// 向指针填充数据
extern "C" void vglLoadDDS(const char* filename, vglImageData* image);
void vglLoadImage(const char* filename, vglImageData* image)
{
vglLoadDDS(filename, image);
}
// 释放上一次分配的所有资源
void vglUnloadImage(vglImageData* image)
{
delete [] reinterpret_cast<uint8_t *>(image->mip[0].data);
}
//
将纹素数据传递到纹理对象
-
注意,我们不能直接设置多重采样纹理的图像数据。
-
如果要对一个多重采样纹理传递数据,唯一的方法是将它关联到一个帧缓存对象上,然后渲染到纹理中,详见后文。
6.4.4 获取纹理数据(回内存或缓存)
- 当我们向纹理中传输了数据之后,可以再次读取数据并传递回用户程序的本地内存中,或者传递给一个缓存对象。
cpp
//pixels用来存储图像数据。
//如果有缓存对象绑定到GL_PIXEL_PACK_BUFFER(更推荐),pixels设置的就是图像数据传递到缓存对象时的数据偏移地址.
//需要注意,ogl不会对用户内存进行任何边界检查,所以可能会造成缓存溢出或不可预知后果。
void glGetTextureImage( GLuint texture,
GLint level,
GLenum format,
GLenum type,
GLsizei bufSize,
void *pixels);
6.4.5 纹理数据的排列布局
- 大多情况下,图像数据在内存中的物理排列方式(图像数据的布局)是按照从左到右,从上到下。的顺序存放在内存中的,并且各个纹素之间是紧密排列的。
- 这里的的上下取决于设定的纹理坐标。
- 可以使用ogl控制函数,自己描述程序中的图像数据布局方式。
cpp
// 设置解包或打包参数
void glPixelStoref( GLenum pname,
GLfloat param);
void glPixelStorei( GLenum pname,
GLint param);
- pname必须是下面像素解包unpack参数名之一: GL_UNPACK_SWAP_BYTES, GL_UNPACK_LSB_FIRST, GL_UNPACK_ROW_LENGTH, GL_UNPACK_IMAGE_HEIGHT, GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_IMAGES, and GL_UNPACK_ALIGNMENT.
- 是ogl从用户内存或者GL_PIXEL_PACK_BUFFER中读取数据时的布局方式。
- pname或者是下面像素打包pack参数名之一:GL_PACK_SWAP_BYTES, GL_PACK_LSB_FIRST, GL_PACK_ROW_LENGTH, GL_PACK_IMAGE_HEIGHT, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS, GL_PACK_SKIP_IMAGES, and GL_PACK_ALIGNMENT.
- 打包参数设置的是 OpenGL将纹理数据写人内存中的布局方式,例如glGetTextureImage
- *SWAP_BYTES:默认为GL_FALSE,即用户内存中的字节顺序保持原样;否则字节将进行反转,会应用到每一个数据元素上,但是它仅对多字节的元素才有意义(以字节为单位)。
- *LSB_FIRST:默认为GL_FALSE,系统将会从最高位开始读取数据;否则系统将会沿着相反的方向进行处理。
- 例如字节数据是 0x31,默认读取顺序{0,0,1,1,0,0,0,1}。
- ROW_LENGTH:可以配合 SKIP_PIXELS和SKIP_ROWS(指定内存数据起始的多行或者多个像素是否需要被忽略,默认都为0,也就是说数据直接从左下角开始读取。),来实现从内存中的图像数据选取一个子矩形,然后绘制或者读取其中的子数据。
- *ROW_LENGTH默认为0,代表了内存数据的每行长度和 gITextureSublmage2D()设置的宽度值是相等的;否者设置的就是较大矩形的实际长度(以像素为单位)。
ALIGNMENT(默认为4):能够确保图像的第一行以及后续每一行都是按照ALIGNMENT设置的字节边界的方式来排列。- 对于32位的机器来说,硬件针对4字节的倍数(32位)的边界对齐的数据传输可能会更快。同理,64位的机器硬件针对8字节倍数的边界对齐的数据传输也会更快。如果系统支持边界对齐数据进行优化,可以使用该参数进行对应设置。
- 如果为1,则每个字节之间都是紧密排列的。
IMAGE_HEIGHT和SKIP_IMAGES只会影响三维纹理和二维纹理数组的定义和查询。可以实现glTextureSublmage3D()和glGetTextureImage()函数访问纹理数组的子切片时的空间区域设置。- *IMAGE_HEIGHT默认为0,不能为负数,那么每个二维矩形图像的行数也就是三维纹理的高度值--gITextureSubImage3D()中传递的参数。
- 如果三维纹理在内存中的大小比用户需要设置的子区域更大,需要通过*IMAGE_HEIGHT参数设置单个子图像的高度/行数(像素为单位)。
- SKIP_IMAGES定义了可用的子区域数据之前还需要跳过的图像层数,如果是一个正整数,那么纹理图像数据中的指针将首先递增n层(n每层的的纹素数据大小)。
- 如果*SKIP_IMAGES为默认值0,那么系统将从纹素数组的第一层开始读取纹素数据。
6.5 纹理格式
- 介绍用于存储纹理数据的内部和外部格式。
- glTextureStorage*函数都需要设置一个internalformat 参数,它负责判断OpenGL存储内部纹理数据的格式。
6.5.1 内部格式internalformat
- 用来存储用户提供的纹理数据的格式。在图像传输的过程中,用户数据将被转换(如果有必要的话)为这种格式。
- internalformat参考
- 或者参考一下含尺寸信息的:
- 由基础格式的定义、一个或者多个尺寸标识符,以及可选的数据类型标识符构成。
- 基础格式从本质上定义了纹理像素的分量构成。
- 例如以G_LR开头的格式只有红色的分量。。。。以此类推。
- 尺寸标识符记录了纹理数据存储的位大小。
- 大多数情况下,我们只需要一个尺寸参数即可。这种时候所有的像素分量都是按照同样的位尺寸来存储的。
- 默认情况下OpenGL,会将纹理数据存储为无符号归一化;那么纹素保存在内存中的值是一个整数,而它被读取到着色器之后将会除以对应整数类型的最大值,并转换为浮点数形式,因此着色器中的数据结果将被限制在0.0~1.0的范围之内(即归一化)。
- 有_SNORM就是有符号归一化的形式,内存中的数据将被视为一个有符号的整数,当我们在着色器中读取它的时候它会除以有符号整型类型的最大值,然后转换为浮点数,因此着色器中得到的浮点数结果被限制在-1.0~1.0的范围内。
- 数据类型标识符包括I,UI,F,分别表示有符号整数、无符号整数,以及浮点数。
- 有符号和无符号的整数类型内部格式分别对应于着色器中的有符号和无符号采样器(例如isampler2D和usampler2D)。
- 浮点数类型的内部格式则会直接在内存中保存真正的浮点数据,并且在着色器中返回的数值也是全精度的浮点数(根据具体OpenGL实现的支持)。在这种情况下,纹素对应的浮点数据范围不一定在-1.0~1.0的范围内。
- 有些情况下,对于像素的每个通道都会使用不同的尺寸标识符,即使用不同位大小的内存来存储不同的通道。
- 例如GL_RGB10_A2,RGB分别都有10位的存储空间,A有2位,即每个纹素都有32位大小。
- 例如GL_R11F_G11F_B10F,每个通道中存储的是特殊类型的低精度浮点数。这种11位的分量并没有符号位而是由5位的指数位(exponent)和6位的尾数位(mantissa)组成。
- GL_SRGB8和GL_SRGB8_ALPHA8格式都是建立在SRGB 颜色空间的RGB 纹理。其中带alpha的是单独存储 ,因为并不属于sRGB。
6.5.2 外部格式
- 是用户向OpenGL API提供数据时所用的格式,他是通过 gITextureSubImage*这种函数中的format和type参数来设置的。必要时候数据会从用户提供的外部格式转换为内部格式。
- format 描述了每个像素数据的分量组成方式,也可以使用可选的INTEGER后缀(使用整数来压缩数据)。
- 此外,我们可以使用一种打包整数(packed integer)的格式来表达打包之前的纹理数据,然后使用内部格式将它们保存到纹理中。
- INTEGER后缀:那么数据传递到OpenGL,的时候会被看待为一个没有归一化的整数数据,依次使用。
- 如果纹理的内部格式是浮点数格式,这个数据会直接转换为浮点数的形式。也就是说,整数100将会变成浮点数100.0。输入的数据类型在这里并不重要。
- 如果希望在着色器中直接获取整数,可以直接使用整型采样器类型,以及整型的内部格式,或者整型的外部数据和类型。
- type为数据标识符,除了传统类型的标识符,还有一些特殊的标识符来表达打包格式或者混合类型。
6.6 压缩纹理
- OpenGL支持压缩形式的纹理存储方法来降低纹理的尺寸,内存耗费方面会有更大的提升。
- 压缩算法的实现方式有两大类:无损和有损。
- 无损压缩算法不会丢失任何信息,解压缩之后可以得到原始数据的完全准确的拷贝结果。
- 有损压缩的过程中就会牺牲掉一些原始的信息,以确保剩下的数据内容能够充分适用于压缩算法并缩减它的尺寸。
- OpenGL中有两种可以传输压缩纹理数据的方法:
- 一种可以直接提供未压缩的数据,但是要设置一个压缩格式的内部格式。OpenGL的实现平台会使用未压缩的、原始的纹理数据并尝试进行压缩;实时进行的,而得到的压缩纹理结果质量并不会很高。
- 另一种方法是用户采用离线的方式(在程序运行之前)压缩纹理数据,然后将结果直接传递给OpenGL。
- 无论是哪种方法,第一步要做的都是选择一种压缩形式的内部格式。
纹理压缩算法和格式的种类繁多,而不同的硬件和OpenGL实现也会支持不同类型的格式,大部分都是采用扩展功能的形式实现。如果想知道你所使用的OpenGL平台能够支持什么格式,可以直接查阅它的扩展功能列表。
- 最常用且肯定被OpenGL本身支持的压缩格式(有对应的内部格式):
- RGTC(Red Green纹理压缩算法)
- BPTC(Block Partitioned纹理压缩算法)。
这两种格式都是基于区块的,可按照4x4的纹素块来保存数据。也就是说,每幅图像都是按照4x4的纹素区块形式来记录的,而每个区块会独立进行压缩。这样的块在硬件层面的解压非常迅速,这过程会在数据从主内存传递图形处理器的纹理缓存的阶段完成。
- 如果我们需要为压缩格式的纹理数据分配永久存储空间,还是使用glTextureStorage系列函数。
- 当设置压缩数据的时候,数据的绝对尺寸是由压缩格式决定的。
- 当我们分配了纹理对象的存储空间之后,就可以使用下面的函数来更新纹理数据的部分了:
cpp
//glCompressedTexSubImage*
//glCompressedTexImage*
//xoffset和width负责设置x方向的偏移值和纹理数据的宽度(以纹素为单位)。
//对于一维数组纹理来说,yoffset和height相当于数组中的起始切片和要更新的切片数量。
//format设置了压缩图像数据的格式,它必须和纹理的内部格式匹配。
//imageSize 和 data设置了准备更新的纹理数据的尺寸以及数据的地址。
void glCompressedTextureSubImage1D( GLuint texture,
GLint level,
GLint xoffset,
GLsizei width,
GLenum format,
GLsizei imageSize,
const void *data);
void glCompressedTextureSubImage2D( GLuint texture,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLsizei imageSize,
const void *data);
void glCompressedTextureSubImage3D( GLuint texture,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLsizei imageSize,
const void *data);
6.7 采样器对象
- 可以用来替代传统方式(glTexParameter*),可以避免在每个纹理对象上重复设置相同的过滤器或者寻址模式等参数。
- 可以通过着色器中带有纹理单元 信息的采样器变量 (每个采样器变量 都是由纹理对象 传人的图像数据,以及采样器对象(或者纹理内置的采样器对象)传人的采样参数来共同定义的)来读取纹理,使用GLSL内置的函数从纹理图像中读取纹素。
- 采样器对象 会绑定到采样器单元,这类似于纹理对象绑定到纹理单元,它们共同作用来实现纹理图像的读取。这一过程被称作采样(sampling)。
- 每一个纹理对象 中都可以包含了一个默认内置的采样器对象。
- 创建一个采样器对象:
cpp
//0是一个保留字,glCreateSamplers()不会返回这个数值。
// 返回没有使用过的采样器对象名字
//会返回一系列没有使用过的采样器对象名称,它们对应了一系列新初始化的采样器对象。
void glCreateSamplers( GLsizei n,
GLuint *samplers);
- 绑定采样器对象到对应的采样器绑定点(glActiveTexture和glBindTexture激活并绑定纹理的纹理单元,或者是glBindTextureUnit的):
cpp
//将一个采样器对象 sampler 绑定到 unit 所设置的采样器单元。
//类似于将 samplerID 所代表的采样器状态绑定到纹理单元 unit
void glBindSampler( GLuint unit,
GLuint sampler);
//是将多个采样器对象绑定到连续的采样器单元。
void glBindSamplers( GLuint first,
GLsizei count,
const GLuint *samplers);
- 如果 sampler 或者 samplers 中某个元素为0,那么对应的目标单元中已经绑定的采样器对象将被解除绑定,当前单元位置恢复为未绑定的状态。
- 判断采样器对象是否合法:
- 在一个名称被真正绑定到采样器单元之前,我们不认为它是一个合法的采样器对象。
cpp
GLboolean glIsSampler( GLuint id);
- 绑定函数都没有target参数,这和glBindTextureUnit不同。
- 这是因为采样器并没有目标的概念,它们总是与纹理相关联。
- 功能在于处理坐标、状态、过滤,驱动获取数据,而不是如同纹理单元功能主要为存储和获取纹理数据(纹素)。
- 删除采样器对象:
cpp
void glDeleteSamplers( GLsizei n,
const GLuint * samplers);
采样器参数
- 每个采样器对象都对应了一系列用于控制纹理对象中纹素读取过程的参数。
cpp
void glSamplerParameter{fi}(GLuint sampler,
GLenum pname,
GLfloat param);
void glSamplerParameter{fi}v( GLuint sampler,
GLenum pname,
const GLfloat * params);
void glSamplerParameterI{i ui}v( GLuint sampler,
GLenum pname,
const GLint *params);
- pname can be one of the following: GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_R, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_BORDER_COLOR, GL_TEXTURE_MIN_LOD, GL_TEXTURE_MAX_LOD, GL_TEXTURE_LOD_BIAS GL_TEXTURE_COMPARE_MODE, or GL_TEXTURE_COMPARE_FUNC.
- 但是也要注意,每个纹理对象都会包含一个默认的采样器对象,可以在不需要绑定到相应采样器单元的前提下直接从纹理中读取数据.
- 需要注意glTextureParameter函数的一些pname参数值和采样器并没有关系。
cpp
void glTextureParameter{fi}(GLuint texture,
GLenum pname,
GLfloat param);
void glTextureParameter{fi}v( GLuint texture,
GLenum pname,
const GLfloat * params);
void glTextureParameterI{i ui}v( GLuint texture,
GLenum pname,
const GLint *params);
6.8 纹理的使用(着色器中)
- 着色器中的纹理是通过不同维度和类型的采样器变量来表达的。
- 每个采样器变量 都是由纹理对象 传人的图像数据,以及采样器对象(或者纹理内置的采样器对象)传人的采样参数来共同定义的。
- 纹理需要绑定到纹理单元上,而采样器对象需要绑定到对应的采样器单元上,它们共同作用来实现纹理图像的读取。这一过程被称作采样(sampling),通过GLSL内置函数texture实现:
cpp
// p为纹理坐标。
// 如果对象支持 mipmap,并且设置了bias,
//那么这个参数将被用于mipmap细节层次(level-of-detail)的偏移量计算,
//来判断采样应当在哪一层进行。
//函数的返回值也就是从原始纹理经过采样器计算得到的结果。
gvec4 texture( gsampler1D sampler,
float P,
[float bias]);
gvec4 texture( gsampler2D sampler,
vec2 P,
[float bias]);
gvec4 texture( gsampler3D sampler,
vec3 P,
[float bias]);
gvec4 texture( gsamplerCube sampler,
vec3 P,
[float bias]);
float texture( sampler1DShadow sampler,
vec3 P,
[float bias]);
float texture( sampler2DShadow sampler,
vec3 P,
[float bias]);
float texture( samplerCubeShadow sampler,
vec4 P,
[float bias]);
gvec4 texture( gsampler1DArray sampler,
vec2 P,
[float bias]);
gvec4 texture( gsampler2DArray sampler,
vec3 P,
[float bias]);
gvec4 texture( gsamplerCubeArray sampler,
vec4 P,
[float bias]);
float texture( sampler1DArrayShadow sampler,
vec3 P,
[float bias]);
float texture( sampler2DArrayShadow sampler,
vec4 P,
[float bias]);
gvec4 texture( gsampler2DRect sampler,
vec2 P);
float texture( sampler2DRectShadow sampler,
vec3 P);
float texture( samplerCubeArrayShadow sampler,
vec4 P,
float compare);

6.8.1 纹理坐标
- 用于对图像进行采样,纹理坐标可经过采样器计算得到采样结果,作为结果片元。
- 通常是按照逐顶点的方式来设置的,然后对结果几何体区域进行插值来获得逐片元的坐标值。
简单纹理示例
- 简单的纹理查找示例
cpp
#version 330 core
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texcoord;
out vec2 vs_texcoord;
void main(void)
{
gl_Position = vec4(pos,1.0);
vs_texcoord = texcoord;
}
cpp
#version 330 core
uniform sampler2D tex;
in vec2 vs_texcoord;
layout (location = 0) out vec4 color;
void main(void)
{
color = texture(tex,vs_texcoord);
}
- 设置纹理坐标
cpp
glUseProgram(prog);
glBindTexture(GL_TEXTURE_2D,tex);
// 顶点位置
float vertices[] = {
// positions
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.0f, 0.0f, // 左下
1.0f, 0.0f, // 右下
1.0f, 1.0f, // 右上
0.0f, 1.0f // 左上
};
GLuint buf;
glGenbuffer(1,&buf);
glBindBuffer(GL_ARRAY_BUFFER,buf);
glBufferData(GL_ARRAY_BUFFER,vertices,sizeof(vertices),GL_STATIC_DRAW);
GLuint vao;
glGenVertexArray(1,&vao);
glBindVertexArray(vao);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,(GLvoid*)(12*sizeof(float)));
glEnableVertexAttribArray(1);
glDrawArray(GL_TRIANGLE_FAN,0,4);
截断方式
- 我们将纹理视为一片区域,它的覆盖范围会沿着每个坐标轴从0.0扩展到1.0。(记住你可能会使用一维、维甚至三维的纹理。)
- OpenGL负责沿着每个多边形的每个面进行数据的插值,然后传递到片元着色器中。
- 纹理坐标范围是0.0~1.0,因此所有的插值坐标结果也都会落在这个范围之内。
- 如果传递给纹理查找函数的纹理坐标超出 了0.0~10的取值范围,那么它们必须经过重新修改以便正确落在范围之内。
- 提供多种方式完成修改,通过GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T,GL_TEXTURE_WRAP_R这几个采样器参数来进行控制。
- 其中glsl中r为红色分量,所以纹理坐标四个分量将被定义为s,t,p,q。
- 每个轴向对应的数据截断方式:
- GL_CLAMP_EDGE :如果纹理坐标超出0.0~1.0的范围,那么纹理边缘的纹素将作为结果值直接返回给着色器。
- GL_REPEAT:纹理将被视为是无限重复的形式然后直接截断。
- GL_MIRRORED_REPEAT :纹理将被视为特殊的镜像方式,然后作为无限重复的形式进行截取。这种模式可以有效避免简单重复纹理带来的贴图边界瑕疵。
- GL_CLAMP_TO_BORDER :读取到纹理范围之外的时候将返回一个常数边界颜色值,作为函数的最终结果。
- 默认情况下,边界颜色是黑色透明的(即颜色的每个分量都是0.0)。也可以通过GL_TEXTURE_BORDER_COLOR来设置新的数值。
- 默认情况下,边界颜色是黑色透明的(即颜色的每个分量都是0.0)。也可以通过GL_TEXTURE_BORDER_COLOR来设置新的数值。
设置采样器的边界颜色(GL_CLAMP_TO_BORDER )
cpp
GLuint sampler;
GLuint texture;
const GLfloat red[] = {1.0f,0.f,0.f,1.0f};
//方法一:
glSamplerParamterfv(sampler,GL_TEXTURE_BORDER_COLOR,red);
//方法二:
//如果纹理已经绑定到纹理单元,但是没有设置采样器对象的话,需要采用这种方式
glTextureParamterfv(sampler,GL_TEXTURE_BORDER_COLOR,red);
6.8.2 排列纹理数据 SWIZZLE
- 如果是一个外部纹理数据源或者别的原因导致纹理数据可能是采用特殊的分量顺序来存储的,而不是常见的rgba,需要将它转换到标准的RGBA数据并传递给着色器。
- 需要使用纹理乱序(texture swizzle)机制,也就是对当前实时纹理数据的分量顺序重新进行排列,然后交给图形硬件去读取。
- 通常使用glTexParameteri来设置GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A,GL_TEXTURE_SWIZZLE_RGBA。
读取ABGR或者RGBx类型的数据
- 对于RGBx格式,我们将其中缺失的alpha通道设置为常数1.0。
- ABGR是小段顺序存储的RGBA字节
cpp
GLuint abgr_texture;
GLuint rgbx_texture;
static const GLenum abgr_swizzle[] =
{
GL_ALPHA,GL_RED,GL_GREEN,GL_BLUE
};
glBindTexture(GL_TEXTURE_2D,abgr_texture);
//设置所有的乱序参数
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_SWIZZLE_RGBA,abgr_swizzle);
glBindTexture(GL_TEXTURE_2D,rgbx_texture);
//设置所有的乱序参数
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_SWIZZLE_A,GL_ONE);
6.8.3 使用多重纹理
- 事实上,OpenGL可以同时支持很多个纹理。一个着色器阶段可以最少支持16个纹理,如果乘以OpenGL支持的着色器阶段的数量那就是80个纹理了,对应GL_TEXTURE0~GL_TEXUTRE79。
- 如果要在着色器中使用多重纹理,我们还需要定义多个uniform 类型的采样器变量(每个变量都对应着一个不同的纹理单元)。
- 在一个着色器(或者程序对象)中使用多重纹理的步骤如下:
- 将纹理绑定到纹理单元(所有要使用的纹理)。
- 设置uniform采样器的值,将每个采样器uniform 都关联到一个纹理单元上。
- 着色器中使用多个纹理混合采样,例如:
cpp
color = texture(tex1,tex_coord0)+texture(tex2,tex_coord1);
6.9 复杂纹理类型
- 介绍OpenGL中提供的一些更为高级的纹理类型,包括3D纹理、纹理数组、立方体映射纹理、阴影、深度-模板和缓存纹理。
- 纹理详细讲解参考
6.9.1 3D纹理
- 需将纹理对象绑定到GL_TEXTURE_3D目标
- glTextureStorage3D来分配纹理对象的存储空间。
- 3D纹理的最大宽度和高度与2D纹理相同,可以通过GL_MAX_TEXTURE_SIZE的值来获取。
- OpenGL实现所支持的3D纹理的最大深度值可以通过GL_MAX_3D_TEXTURE_SIZE来获取,它可能与纹理宽度和高度的最大值不同。
- 需要使用分量为三的纹理坐标,并且归一化。
- 3D纹理的一种典型用途就是体渲染 ,对模型截面采样时仍能保持连续纹理,用在医疗影像和流体模拟的领域。
- 纹理的内容通常是一个密度图像,每个体素(voxel)都表达介质密度中的一个采样值。
- 体素也就是体积内的一个元素,它和像素(表示一幅图像中的一个元素)以及纹素(即纹理中的一个元素)的意义类似,每个体素(voxel)存储颜色或标量值。
- 渲染体积的一个简单方法就是,沿着体积,像渲染有纹理的四边形一样渲染切平面,而3D纹理坐标取自顶点属性。参考
- 3D纹理使用参考
- 创建3D纹理:
cpp
// 创建OpenGL纹理对象
glGenTextures(1, &texture3D);
glBindTexture(GL_TEXTURE_3D, texture3D);
// 设置纹理参数
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 上传纹理数据
glTexImage3D(
GL_TEXTURE_3D,
0, // Mipmap级别
GL_RGBA, // 内部格式
width, // 宽度
height, // 高度
depth, // 深度
0, // 边框
GL_RGBA, // 数据格式
GL_UNSIGNED_BYTE, // 数据类型
textureData.data() // 数据指针
);
// 生成Mipmap
glGenerateMipmap(GL_TEXTURE_3D);
// 解绑纹理
glBindTexture(GL_TEXTURE_3D, 0);
- 如何着色器使用变换矩阵将2D纹理坐标转换为三维空间:
其实类似屏幕坐标转换为世界空间等的做法。
cpp
//顶点
#version 330core
layout(location=0) in vec3 in_position;
layout(location=1)in vec2 in_tex_coord;
//输出变换后的三维纹理坐标
out vec3 tex_coord;
//用于将纹理坐标变换到三维空间的变换矩阵
uniform mat4 tc_rotate;
void main(void)
{
tex_coord=(vec4(in_tex_coord,0.0,1.0)*tc_rotate).stp;
gl_Position=vec4(in_position,1.0);
}
// 片元
in vec3 tex_coord;
layout(location=0) out vec4 color;
//体纹理
uniform sampler3D tex;
void main(void)
{
//直接从3D纹理中读取纹理坐标对应位置的数据,然后在R、G、B、A四个通道上重复,或者四个通道也可.
color = texture(tex,tex_coord).rrrr;
}
6.9.2 纹理数组
- 如果需要同时链接一系列一维或者二维的纹理,并且希望再一个绘制回调中完成,可以使用纹理数组。
- 尺寸和格式必须是相同的,然后保存到更高一个维度的纹理中。
直接用一个三维纹理去保存一组二维纹理,用来索引的纹理坐标r在这种情况下是被归一化到[0,1]区间的,所以如果想访问一组7张纹理,访问第三张的时候需要设置坐标为小数,不方便。
- 纹理数组还允许按索引进行纹理之间的 mipmap滤波。
- 需将纹理对象绑定到GL_TEXTURE_2D[1D]_ARRAY目标
- glTexStorage3D或glTextureStorage3D来分配纹理对象的存储空间。
- 遍历纹理数量,每一个使用glTexSubImage3D,并通过设置参数zoffset来设置纹理数组中不同层的纹理数据。
无绑定加载不限制尺寸的纹理数组sampler2D textures[16]
cpp
GLuint textureArray[3]; //纹理数组
GLuint64 handles[3]; //纹理句柄
for (int i = 0; i < 3; i++)
{
glBindTexture(GL_TEXTURE_2D, textureArray[i]);
handles[i] = glGetTextureHandleARB(textureArray[i]);
glMakeTextureHandleResidentARB(handles[i]);
}
//第一种--单独的纹理数组--uniform sampler2D textArray[3];
glUniformHandleui64vARB(glGetUniformLocation(programObject[0], "textArray"), 3,handles);
//第二种--被UBO包裹的纹理数组
//glBindBuffer(GL_UNIFORM_BUFFER, UBO);
//glBufferSubData(GL_UNIFORM_BUFFER, 0, 3*sizeof(GLuint64), &handles);
- 两个方法的着色器
cpp
#version 430 core
#extension GL_ARB_bindless_texture : require //无绑定纹理必须要有的
out vec4 FragColor;
in vec2 vTexPosition1;
//第一种--单独的纹理数组
uniform sampler2D textArray[3];
//第二种--被UBO包裹的纹理数组
//layout(binding = 0) uniform UBO_data
//{
// sampler2D textArray[3];
//};
void main()
{
//纹理采样
FragColor = texture(textArray[0], vTexPosition1);//以数组成员方式采样
}
6.9.3 立方体映射纹理
- 特别适用于环境贴图,例如天空。
- 需将纹理对象绑定到GL_TEXTURE_CUBE_MAP目标。
- glTextureStorage2D来创建纹理对象的存储空间,这一次调用会直接创建立方体映射6个面的存储空间,纹理目标为GL_TEXTURE_CUBE_MAP_POSITIVE_或者GL_TEXTURE_CUBE_MAP_NEGATIVE_。
- 然后glTextureSubImage3D来设置立方体每个面的数据,纹理目标为GL_TEXTURE_CUBE_MAP_POSITIVE_或者GL_TEXTURE_CUBE_MAP_NEGATIVE_。。
- 纹理目标:可以借助ogl坐标系理解
纹理目标 | 方位 |
---|---|
GL_TEXTURE_CUBE_MAP_POSITIVE_X | 右 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_X | 左 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Y | 上 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | 下 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Z | 后 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | 前 |

天空盒子
- 假设一个单位立方体包围了物体中心(世界),当物体中心(原点)看向物体表面某个位置 (x,y,z)时,从中心朝这个位置发出一条射线,此时射线会与单位立方体相交于某点。
- 可以将所有顶点的纹理坐标 当做是立方体的顶点位置。天空盒的关键在于不需要显式纹理坐标 ,而是使用顶点位置作为方向向量进行立方体贴图采样。
- 创建立方体纹理
cpp
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
// 也可以先使用glTextureStorage2D,此时会一次性分配6个面的存储空间。
// 然后通过glTextureSubImage3D,传入不同纹理目标的参数来更新6个面的纹理数据。
for (unsigned int i = 0; i < 6; i++)
{
......
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
- 渲染:
cpp
#version 330 core
layout (location = 0) in vec3 aPos;
// 纹理坐标是3维的
out vec3 TexCoords;
// 不用model转换到世界矩阵
uniform mat4 projection;
uniform mat4 view;
void main()
{
// 纹理坐标等于位置坐标
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
// z为w,透视除法除后z=(z=w/w)=1,深度为最远///
gl_Position = pos.xyww;
}
#version 330 core
out vec4 FragColor;
// 纹理坐标是3维的
in vec3 TexCoords;// 纹理坐标
// 天空盒纹理采样
uniform samplerCube skybox;
void main(){
FragColor = texture(skybox, TexCoords);
}
cpp
glDepthFunc(GL_LEQUAL); // 因为天空盒子深度为1
glDepthMask(GL_FALSE); // 禁用深度写入
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
立方体数组纹理
cpp
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, textureID);
// 五个立方体纹理,总共30个切片
glTextureStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY,10,GL_RGBA8,1024,1024,5);
for(int cubeindex=0;cubeindex<5;cubeindex++)
{
for(int face =0;face <6;face++)
{
glTexSubImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, 0,0,cubeindex,1024, 1024, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
}
使用立方体映射实现环境映射
- 环境映射参考原文
- 为了实现接受环境光的效果,我们可以用一个贴图来存放环境光信息,即 环境贴图 (Environment Map) 或 反射贴图 (Reflection Map),在给物体着色的时候不仅采样普通纹理,也采样环境贴图,按一定比例混合这两者的颜色(例如物体材质越光滑,镜面反射效果越强)。
- 对于单一方向需要反射现象的物体(如一面镜子),可以使用普通方式映射。
- 对于360°方向均需要镜面反射现象的物体(如金属球),应采用立方体贴图方式映射(立方体映射被当作一种环境贴图)。
- 重新计算纹理坐标 :在准备贴图的物体表面点上,围绕表面法线对入射的视角向量进行反射,然后通过反射向量对立方体映射进行采样。
- 生成立方体贴图方式的环境贴图(一个最简单的方式):设置6个摄像机位于物体中心,每帧分别朝六个方向渲染得到的图像输出到立方体贴图里对应的面上。
- 一般的环境贴图并不需要高精度的环境光信息,因此可以使用分辨率更低的环境贴图(这样生成环境贴图的开销也会减少不少)。
- 顶点着色器:
cpp
#version 330core
//输入位置和法线
layout(location=0)in vec4 in_position;
layout(location=1)in vec3 in_normal;
//输出表面法线和人眼空间的位置
out vec3 vs_fs_normal;
out vec3 vs_fs_position;
uniform mat4 mat_mvp;
uniform mat4 mat_mv;
void main(void)
{
//剪切空间的位置坐标
gl Position =mat_mvp*in_position:
//人眼/世界/相机空间的法线和位置
vs_fs_normal = mat3(mat_mv)*in_normal;
vs_fs_position = (mat_mv*in_position).xyz;
}
- 片元着色器
cpp
#version 330core
in vec3 vs_fs_normal;
in vec3 vs_fs_position;
layout(location=0) out vec4 color;
uniform samplerCube tex;
void main(void)
{
//沿着表面法线对人眼空间的位置坐标进行反射,从而计算纹理坐标
vec3 tc = reflect(-vs_fs_position,normalize(vs_fs_normal));
color = texture(tex,tc);
}
- reflect的第一个参数应该是入射光线方向向量(从表面指向光源/人眼(0,0,0))。
无缝的立方体映射采样
- 为了避免立方体映射的相邻面之间有这样的接缝出现,我们可以开启无缝立方体映射滤波。
- 开启之后,OpenGL就会使用相邻的立方体映射面上的纹素来获取滤波后的纹素结果。
cpp
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
6.9.4 阴影采样器
- 阴影详解或详见7.4节
- 阴影采样器需要纹理坐标中增加一个额外的分量,用来与返回的纹素数据进行比较。
- 使用阴影采样器的时候,纹理函数返回的值是0.0~1.0的一个浮点数,它表示准备进行比较的纹素数据的小数部分。
- 如果访问纹理的时候使用只采样一个纹素的方式(需要GL_NEAREST滤波方式,没有mipmap,并且每个纹素一个采样点),那么比较之后返回的值可能是0.0也可能是1.0(这取决于这个纹素是否通过了比较)。
- 如果着色器中返回的数据是通过不止一个纹素构建而成的(例如线性的滤波方式,或者使用了多重采样),那么返回值应当在0.0~10之间,这取决于有多少个纹素通过了比较测试。
cpp
//对绑定到 tex 所对应的纹理单元的阴影纹理进行采样,纹理坐标通过P来设置。
//返回值是一个浮点数,用来衡量阴影比较操作的结果,即获取的纹素数据中通过了比较测试的数据所占的分数。
float texture(sampler1DShadow sampler,
vec3 P,
[float bias]);
float texture(sampler2DShadow sampler,
vec3 P,
[float bias]);
float texture(samplerCubeShadow sampler,
vec4 P,
[float bias]);
float texture( sampler1DArrayShadow sampler,
vec3 P,
[float bias]);
float texture( sampler2DArrayShadow sampler,
vec4 P,
[float bias]);
float texture( sampler2DRectShadow sampler,
vec3 P);
float texture( samplerCubeArrayShadow sampler,
vec4 P,
float compare);
- 开启采样器的比较功能:
- 方法1:调用glSamplerParameteri(),并且设置pname为GL_TEXTURE_COMPARE_MODE,param为GL_COMPARE_REF_TO _TEXTURE,关闭则设置为GL_NONE;然后再调用glSamplerParameteri,并设置pname为GL_TEXTURE_COMPARE_FUNC,param为以下这些比较函数中的一个:r 为当前插值纹理坐标(多出的那个分量),Dt 为从当前绑定纹理中采样的纹理值。
- 方法1:调用glSamplerParameteri(),并且设置pname为GL_TEXTURE_COMPARE_MODE,param为GL_COMPARE_REF_TO _TEXTURE,关闭则设置为GL_NONE;然后再调用glSamplerParameteri,并设置pname为GL_TEXTURE_COMPARE_FUNC,param为以下这些比较函数中的一个:r 为当前插值纹理坐标(多出的那个分量),Dt 为从当前绑定纹理中采样的纹理值。
6.9.5 深度------模板纹理
- 纹理还可以存储深度和模板数据,采用逐纹素的方式,对应的纹理内部格式为GL_DEPTH_STENCIL。
- 一般在想用帧缓存来存储深度数据或模版数据时用到,纹理当做帧缓存的附件。
- 使用深度-模板纹理去进行映射的时候,着色器默认会直接读取深度信息。
- 如果想要读取模版数据则需要设置纹理参数GL_DEPTH_STENCIL_TEXTURE_MODE为GL_STENCIL_COMPONENTS.
6.9.6 缓存纹理
- 需创建并将纹理对象绑定到GL_TEXTURE_BUFFER目标。
- 使用glTextureBuffer()函数将纹理与一个缓存对象(需要有名字)关联起来。
- buffer的存储数据将被视为一组数据格式为internalformat的元素进行解析,注意数据格式必须是有尺寸后缀的!
- 如果bufer为0,那么缓存纹理texture 中当前已经存在的关联信息将被断开,缓存数据将无法再读取。
cpp
void glTexBuffer(GLenum target,
GLenum internalformat,
GLuint buffer);
void glTextureBuffer( GLuint texture,
GLenum internalformat,
GLuint buffer);
- 如果只需要关联缓存对象的一部分到缓存纹理中,使用glTextureBufferRange:
- offset必须是一个整数值,并且是系统平台所定义的常量GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT的倍数。
cpp
//将缓存对象buffer中从ofset开始,总共size字节的一部分存储区域。
void glTexBufferRange( GLenum target,
GLenum internalformat,
GLuint buffer,
GLintptr offset,
GLsizeiptr size);
void glTextureBufferRange( GLuint texture,
GLenum internalformat,
GLuint buffer,
GLintptr offset,
GLsizei size);
- 缓存纹理是一种特殊形式的纹理,它允许从着色器中直接访问缓存对象的内容,将它当作一个巨大的一维纹理使用。
- 我们可以把它当作一般的纹理对象创建,绑定到纹理单元,并使用 glTextureParameteri()控制它们的参数。但是,纹理数据的存储实际上是由一个缓存对象(它必须是有名称的)来管理和控制的。
- 没有内置的采样器,并且采样器对象也不会对缓存纹理产生效果。
缓存纹理与一般的一维纹理不同之处:
1.一维纹理的尺寸受限于GL_MAX_TEXTURE_SIZE对应的最大值,但是缓存纹理的尺寸受限于GL_MAX_TEXTURE_BUFFER_SIZE的值,通常能达到2GB其至更多。
2.一维纹理支持滤波、mipmap、纹理坐标的截取,以及其他一些采样器参数,但是缓存纹理不行。
3.一维纹理的纹理坐标是归一化的浮点数值,但是缓存纹理使用的是没有归一化的整数纹理坐标。
- 创建和初始化缓存纹理
cpp
GLuint buf;
GLuint tex;
//数据应当被保存到程序内存中了
extern conat void* data;
glGenBuffers(1,&buf);
glBindBuffer(GL_TEXTURE_BUFFER,buf);
glBufferData(GL_TEXTURE_BUFFER,1024*1024
data,GL STATIC DRAW);
glCreateTextures(1,GL_TEXTURE_BUFFER,&tex);
glTextureBuffer(tex,GLR32F,buf);
着色器中访问缓存纹理
- 需要使用
texelFetch函数
对uniform samplerBuffer
采样器进行单独的纹素采样数据并使用。 - 更多texelFetch
- texelFetch 函数除了用于缓存纹理,也可以用于一般的纹理。
- 如果我们用它来采样一个非缓存纹理,那白么纹理的采样器参数将被忽略,并且纹理坐标在这里会被视为一个没有经过归一化的整数数值。
cpp
//对一个单独的纹素执行查找,纹理当前绑定到s,而纹理坐标设置为coord。
vec4 texelFetch( samplerBuffer sampler,
int P);
ivec4 texelFetch( isamplerBuffer sampler,
int P);
uvec4 texelFetch( usamplerBuffer sampler,
int P);
cpp
#version 450 core
layout (binding =0) uniform samplerBuffer buf;
in int buf_tex_coord;
layout (location = 0)out vec4 color;
void main(void)
{
color = texelFetch(buf,buf_tex_coord);
}