【OpenGL】蓝宝书第七章——纹理高级知识

目录

矩形纹理

加载矩形纹理

使用矩形纹理

立方体贴图

加载立方体贴图

创建天空盒、创建反射球体

多重纹理

多重纹理坐标

多重纹理示例

点精灵(点块纹理)Point Sprite

使用点

点大小

综合运用

点参数

异形点

点的旋转

纹理数组

加载2D纹理数组

纹理数组索引

访问纹理数组

纹理代理


矩形纹理

GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D是我们所了解到的纹理目标,而矩形纹理是GL_TEXTURE_RECTANGLE,它与GL_TEXTURE_2D类似,区别在于不能进行Mip贴图,glTexImage2D只能加载第0层,而且纹理坐标并不是在[0,1]范围内的,而是对像素寻址,如(5,19)则代表从左边数起第六列,从上面数起第20行的像素,纹理坐标不能重复且不支持纹理压缩。此种纹理效率更高、支持更好。

加载矩形纹理

bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
	{
	GLbyte *pBits;
	int nWidth, nHeight, nComponents;
	GLenum eFormat;
	
	// Read the texture bits
	pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
	if(pBits == NULL) 
		return false;
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
		
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
				 eFormat, GL_UNSIGNED_BYTE, pBits);
	
    free(pBits);

    if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
       minFilter == GL_LINEAR_MIPMAP_NEAREST ||
       minFilter == GL_NEAREST_MIPMAP_LINEAR ||
       minFilter == GL_NEAREST_MIPMAP_NEAREST)
        glGenerateMipmap(GL_TEXTURE_2D);
            
	return true;
	}

使用矩形纹理

将矩形纹理加载到uiTextures[3]纹理对象中

	// Load the Logo
	glBindTexture(GL_TEXTURE_RECTANGLE, uiTextures[3]);
	LoadTGATextureRect("OpenGL-Logo.tga", GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE);

准备好正交投影

M3DMatrix44f mScreenSpace;
m3dMakeOrthographicMatrix(mScreenSpace, 0.0f, 800.0f, 0.0f, 600.0f, -1.0f, 1.0f);

与之前学习到的GLFrustum::SetOrthographic(GLfloat xMin, GLfloat xMax, GLfloat yMin, GLfloat yMax, GLfloat zMin, GLfloat zMax);不同,它是原地在左下角而不是左上角的,让所有纹理坐标都绘制在第一象限内。

接着,创建GLBatch批次对象,注意的是纹理坐标是如何设定的。

    int x = 500;
	int y = 155;
	int width = 300;
	int height = 155;
	logoBatch.Begin(GL_TRIANGLE_FAN, 4, 1);

	// Upper left hand corner
	logoBatch.MultiTexCoord2f(0, 0.0f, height);
	logoBatch.Vertex3f(x, y, 0.0);

	// Lower left hand corner
	logoBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
	logoBatch.Vertex3f(x, y - height, 0.0f);

	// Lower right hand corner
	logoBatch.MultiTexCoord2f(0, width, 0.0f);
	logoBatch.Vertex3f(x + width, y - height, 0.0f);

	// Upper righ hand corner
	logoBatch.MultiTexCoord2f(0, width, height);
	logoBatch.Vertex3f(x + width, y, 0.0f);

	logoBatch.End();

渲染矩形纹理的片段着色器内容:

// Rectangle Texture (replace) Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 140

out vec4 vFragColor;

uniform sampler2DRect  rectangleImage;

smooth in vec2 vVaryingTexCoord;

void main(void)
    { 
    vFragColor = texture(rectangleImage, vVaryingTexCoord);
    }
    

变化之处在sampler2DRect类型指定矩形纹理单元,而采样函数依旧是texture。

顶点着色器:

// Rectangle Texture (replace) Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 140

// Incoming per vertex... position and texture coordinate
in vec4 vVertex;
in vec2 vTexCoord;

uniform mat4   mvpMatrix;

// Texture Coordinate to fragment program
smooth out vec2 vVaryingTexCoord;


void main(void) 
    {
    // Pass on the texture coordinates 
    vVaryingTexCoord = vTexCoord;
    
    // Don't forget to transform the geometry!
    gl_Position = mvpMatrix * vVertex;
    }

最后,设置纹理应用第0层贴图 并重新捆绑纹理对象是uiTexture[3],设置MVP矩阵统一值,渲染批次,注意会开启混合和关闭深度测试。

	// Draw the solid ground
	glEnable(GL_BLEND);
	glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	static GLfloat vFloorColor[] = { 1.0f, 1.0f, 1.0f, 0.75f };
	shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE,
		transformPipeline.GetModelViewProjectionMatrix(),
		vFloorColor,
		0);

	floorBatch.Draw();
	glDisable(GL_BLEND);

完整案例:https://blog.csdn.net/qq_39574690/article/details/115474304

立方体贴图

立方体贴图是由6个面,每个面都是正方形的2D图像组成。应用范围:3D光线贴图、反射和高精度环境贴图等。立方体贴图是投影到一个对象上的。

加载立方体贴图

立方体贴图纹理目标:GL_TEXTURE_CUBE_MAP_POSITIVE_X、GL_TEXTURE_CUBE_MAP_POSITIVE_Y、GL_TEXTURE_CUBE_MAP_POSITIVE_Z以及GL_TEXTURE_CUBE_MAP_NEGATIVE_X、GL_TEXTURE_CUBE_MAP_NEGATIVE_Y、GL_TEXTURE_CUBE_MAP_NEGATIVE_Z。代表正X、Y、Z和负X、Y、Z的贴图。
例如:glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pImage);

glTexParameteri函数的纹理目标(第一个参数)设为GL_TEXTURE_CUBE_MAP,其他参数一样。
例如:glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

加载时需要将六个面的贴图都加载出来,并且可以开启Mip贴图 例如: glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

立方体贴图的纹理坐标是三维的,即设置环绕模式时需要设定GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T、GL_TEXTURE_WRAP_R,而这个三维纹理坐标实际是一个从立方体贴图中心发起的有符号向量,向量与立方体贴图其中一面相交,其交点的纹理会被采样进行过滤颜色。

创建天空盒、创建反射球体

立方体贴图普遍用法是创建一个反应它周围景象的对象,即天空盒。

Cubemap完整案例:https://blog.csdn.net/qq_39574690/article/details/115499082

多重纹理

OpenGL允许我们将独立的纹理对象绑定到一些可用的纹理单元上,从而提供了将两个或更多纹理同事应用到几何图形上的能力。可查询支持的纹理单元数量,如下所示。

GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);

调用如下代码将当前纹理单元切换到第二个纹理单元,然后捆绑到纹理对象,此时活动的纹理单元就是第二个。

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureID);

在多个纹理进行渲染时,需注意哪个纹理单元是当前活动的,可能是一维,二维,三维的。

多重纹理坐标

最多可在GLBatch::Begin时指定4组纹理坐标,即nTextureUnits参数控制数量。

void GLBatch::Begin(GLenum primitive, GLuint nVerts, GLuint nTextureUnits = 0);

有两个方法设定纹理坐标:
①效率最快的方法,一次复制整个一组纹理坐标

void GLBatch::CopyTexCoordData2f(M3DVector2f *vTexCoords, GLuint uiTextureLayer);

②使用较慢的每次指定一个顶点对应的纹理坐标

void GLBatch::MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
void GLBatch::MultiTexCoord2fv(GLuint texture, M3DVector2f vTexCoord);

多重纹理示例

Multitexture示例程序:https://blog.csdn.net/qq_39574690/article/details/115534467

点精灵(点块纹理)Point Sprite

一种应用于点的纹理,即每一个顶点都会有一个点精灵渲染。

使用点

一样用sampler2D纹理,用texture采样,新的东西是gl_PointCoord内置变量作为点精灵的纹理坐标采样。

点大小

glPointSize(5.0); 设置固定大小 每一个点都一样大小

glEnable(GL_PROGRAM_POINT_SIZE); 开启顶点着色器设定点大小  GLSL代码写这一步
gl_PointSize = 1.0;   设定当前点的大小,一般在片段着色器进行这一步

综合运用

模拟宇宙星光:https://blog.csdn.net/qq_39574690/article/details/115560165

点参数

glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT); 点精灵的纹理坐标系原点位于左下角

glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_UPPER_LEFT); 点精灵的纹理坐标系原点位于左上角(默认)

其他参数参考附录C ,例如alpha属性

异形点

gl_FragCoord会包含当前片段的屏幕空间坐标,其中x,y分量代表屏幕坐标值,即使它是一个点,也有所谓的点区域,点区域不同位置是不同(x,y)屏幕坐标的。其中z,w都是常量,因点是作为一个平面进行渲染的,而平面与近端面和远端面平行。

可利用它和discard关键字来渲染出不同的点形状,而不再是简单的正方形。

圆形:

vec2 p = gl_FragCoord * 2.0 - vec2(1.0);
if (dot(p, p) > 1.0)
   discard;

花朵:

vec2 temp = gl_FragCoord * 2.0 - vec2(1.0);
if( dot(temp, temp) > sin(atan(temp.y, temp.x) * 5.0))
   discard;

点的旋转

需要传递旋转角度和在片段着色器使用mat2定义旋转矩阵来对纹理坐标gl_PointCoord进行旋转变换来进行旋转点。其中angle和mat2都是常量,旋转矩阵的计算放在了顶点着色器进行,而不是片段着色器,因为节省开销,没必要在每一个片段都进行一遍计算旋转矩阵,在代码上理解因为矩阵的数据是常量,因此矩阵也是常量,即传给片段着色器并不会插值而改变矩阵,而我们的确也不需要插值矩阵;最后在片段着色器进行了一个对偏移到[-0.5, 0.5]范围的纹理坐标gl_PointCoord进行旋转变换 再偏移回[0,1],然后采样颜色输出。为什么旋转前要进行偏移纹理坐标,这是为了让纹理中心点是(0,0)即围绕点精灵中心旋转,而不是默认的围绕点精灵左上角(0,0)旋转,最后还要记得偏移回[0,1]才能正常采样。

在上一个案例基础上新增点旋转内容的案例:https://blog.csdn.net/qq_39574690/article/details/115561690

纹理数组

纹理数组是直接捆绑到纹理对象的,类似多重纹理,差别就是不需要分配多个纹理对象,而是用一个纹理对象去捆绑一个纹理数组。

加载2D纹理数组

纹理数组的每一张纹理都支持Mip贴图,绑定到纹理对象后作为统一值传输给着色器,在着色器可对它们进行使用数组一样使用。书中会以一个序列帧动画来讲解,即一个月亮动态图,通过一个纹理数组(29张图片)和一个时间来切换图片。其加载过程和原来没多少区别,就是纹理目标参数(target)换成用GL_TEXTURE_1D_ARRAY和GL_TEXTURE_2D_ARRAY。

1、分配纹理对象、捆绑纹理状态GL_TEXTURE_2D_ARRAY(即纹理对象捆绑2D纹理数组类型)

GLuint moonTexture;
...
glGenTextures(1, &moonTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, moonTexture);

2、 设置纹理参数:环绕模式和过滤器

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

3、将纹理数组内容填充到当前纹理单元的纹理状态内(默认纹理单元是第一个即0单元) 

void glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data);

其中,target填GL_TEXTURE_2D_ARRAY( 或GL_TEXTURE_1D_ARRAY),data是纹理数组指针,width,height指定图像大小,depth指定数组长度。
也可以将data设置为NULL,这样就会开辟depth个(width,height)图像空间,接着使用glTexSubImage3D来进行加载内容,例如:

glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 64, 64, 29, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);

	for (int i = 0; i < 29; i++) {
		char cFile[32];
		sprintf_s(cFile, "moon%02d.tga", i); //'sprintf' unsafe, replace with 'sprintf_s'

		GLbyte *pBits;
		int nWidth, nHeight, nComponents;
		GLenum eFormat;

		// Read the texture bits
		pBits = gltReadTGABits(cFile, &nWidth, &nHeight, &nComponents, &eFormat);
		glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, nWidth, nHeight, 1, GL_BGRA, GL_UNSIGNED_BYTE, pBits);

		free(pBits);
	}

函数信息:void glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data); 

我怀疑它是不是搞错了,zoffset填了i,depth填了一个固定值1。也就是说zoffset才是深度索引?对应glTexImage3D的depth(神奇~,但实例表现上没啥问题)

纹理数组索引

在以上工作基础上,我们可以在着色器使用了,只需将纹理对象作为统一值传输给着色器接收即可。但在此之前需要一个[0,28]范围的索引值,而且它会随着时间而循环变化。

	// fTime goes from 0.0 to 28.0 and recycles
	float fTime = timer.GetElapsedSeconds();
	fTime = fmod(fTime, 28.0f);
	glUniform1f(locTimeStamp, fTime);

	moonBatch.Draw();

在顶点着色器会接收fTime赋值给一个vec3的p分量传递给片段着色器,其s,t分量存储着纹理坐标,它是顶点纹理坐标属性赋值。

// MoonShader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

in vec4 vVertex;
in vec4 vTexCoords;

uniform mat4 mvpMatrix;
uniform float fTime;

smooth out vec3 vMoonCoords;

void main(void) 
    { 
    vMoonCoords.st = vTexCoords.st;
    vMoonCoords.p = fTime;

    gl_Position = mvpMatrix * vVertex;
    }

访问纹理数组

新类型 2D纹理数组采样器 sampler2DArray, 并有一个新函数texture2DArray对这个纹理采样,传递的是3分量纹理坐标(s和t分量是二维纹理坐标,p是数组索引值代表采样的是哪一张图)

// MoonShader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130

// Another NVidia Driver non-conformance bug
// You should not have to put this here for a 3.3 driver.
#extension GL_EXT_gpu_shader4: enable

out vec4 vFragColor;

uniform sampler2DArray moonImage;

smooth in vec3 vMoonCoords;

void main(void)
   { 
   vFragColor = texture2DArray(moonImage, vMoonCoords.stp);
   }

纹理数组的完整案例: https://blog.csdn.net/qq_39574690/article/details/115585138

纹理代理

利用代理能让我们查询出真机设备是否支持这种配置的纹理加载,为了校验是否支持某种配置的纹理,需创建一个相应配置的纹理代理如下,注意纹理代理并不需要数据,它不是真正的纹理。

glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, 2048, 4096, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);

如上代理是想检查是否支持 GL_RBGA格式的(2048,4096)纹理 压缩成BGRA 无符号8字节形式存储。

查询是否支持高度4096,可用如下方法

int height;
glGetTexLevelParameter(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

如果height返回0则代表不支持,否则支持?(书上没说明否则就是支持,也可能是返回的是最大的支持高度,待测试)其他纹理配置数据的查询支持可参考附录C。


版权声明:本文为qq_39574690原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。