创建和初始化纹理
贴图在OpenGL中代表了图片,它包含了一些特性。比如,下面的图,一个是没有贴图的一个是有贴图的。
在OpenGL中,有两种存储方式:
1.缓冲
2.贴图
缓冲是未定义类型的线性结构的数据,可以被视为通常的内存分配。贴图是多维度的数据,比如图片。
在OpenGL中,属性数据如下:
1.顶点位置
2.法线
3.u-v坐标
这些都存储在OpenGL的缓冲中。相反的,图片数据则是存储在OpenGL的贴图对象中。
为了存储图片数据到纹理对象,你要按照如下的步骤:
1.创建纹理对象
2.分配纹理内存
3.绑定纹理对象
我们使用如下的函数去创建、绑定、分配内存。
清单1:
//创建纹理对象
glCreateTextures(...);
//分配内存
glTexStorage2D(...);
//绑定它到GL_Texture_2D目标
glBindTexture(...);
上面是程序片段,用来展示如何创建纹理对象、分配存储空间、绑定它到OpenGL上下文。
清单2:
//OpengL中使用名字的类型为GLuint
GLuint texture;
//创建2D的纹理对象
glCreateTextures(GL_TEXTURE_2D,1,&texture);
//定义贴图的使用内存大小
glTextureStorage2D(texture, //纹理对象
1, //1 mimap 等级
GL_RGBA32F, //32位浮点数RGBA数据
256,256); //256 x 256 texels
//绑定它到OpenGL上下文,使用的是GL_TEXTURE_2D作为绑定点
glBindTexture(GL_TEXTURE_2D, texture);
在这个纹理对象绑定之后,你可以加载数据到纹理。数据加载的方式如下:
清单3:
glTexSubImage2D(...)
清单4:
//定义一些数据
float *data=new float[256*256*4];
glTexSubImage2D(texture, //纹理对象
0, //level 0
0,0, //offset 0,0
256,256, //256 x 256 texels
GL_RGBA, //Four channel data
GL_FLOAT, //Floating point data
data); //Pointer to data
纹理对象和类型
在理解OpenGL纹理的时候遇到的第一个问题是,什么是目标。事实证明,目标是非常简单和直接的。一个纹理目标决定了纹理对象的类型。
比如,纹理以2D纹理创建。因此,纹理对象绑定到2D纹理目标,GL_TEXTURE_2D。如果有一个1D的纹理,你就要把它绑定到1D纹理目标,GL_TEXTURE_1D。
由于你要使用多个图片,你会发现经常使用的是GL_TEXTURE_2D类型的。这是因为图片是用2D数据展示的。
从shaders中读取纹理
一旦纹理绑定了,且包含了数据,它就可以被shader使用。在shader中,纹理是作为全局Sampler变量的。
比如:
清单5:
uniform sampler2D myTexture;
采样器的维度和纹理的维度有关。我们的纹理是2D的,所以要指定为2D的采样器,那就是sampler2D。
如果你的纹理是1D的,采样器就是sampler1D的。如果纹理是3D的,那么采样器就是sampler3D。
纹理坐标
采样器代表了纹理以及采样参数。纹理坐标从0.0到1.0。这个两个坐标在应用纹理到物体所必须的。你已经知道如何在shader中代表纹理了,使用的是sampler。但是怎样在shader中得到纹理坐标呢?
属性数据是通过OpenGL的缓冲传递给GPU的。你加载属性数据。这些属性可以是顶点的位置、法线、纹理坐标。纹理坐标也称为uv坐标。
顶点着色器从顶点属性接收信息。只有顶点着色器可以接收属性数据。细分曲面、几何和片段着色器都不能接收属性数据。如果这些着色器需要数据,你必须从顶点着色器开始向他们传递。
因此,顶点着色器从顶点属性中接收纹理坐标。然后由顶点着色器向片段着色器传递。一旦这个坐标传递到了片段着色器,OpenGL就可以应用纹理到物体了。
清单6展示纹理坐标作为顶点的属性。在main函数中,这个坐标未经变换直接传递给片段着色器。
清单6:
#version 450 core
//uniform variables
uniform mat4 mvMatrix;
uniform mat4 projMatrix;
//Vertex Attributes for position and UV coordinates
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texCoordinates;
//output to fragment shader
out TextureCoordinates_Out{
vec2 texCoordinates;
}textureCoordinates_out;
void main(){
//计算每个顶点的裁剪空间坐标
vec4 position_vertex=mvMatrix*position;
//未修改直接传出
textureCoordinates_out.texCoordinates=texCoordinates;
gl_Position=projMatrix*position_vertex;
}
清单7展示了如何使用texture()函数,它使用纹理坐标采样纹理,然后应用到一个像素。
清单7:
#version 450 core
//Sampler2D declaration
layout(binding = 0) uniform sampler2D textureObject;
//Input from vertex shader
in TextureCoordinates_Out{
vec2 textureCoordinates;
}textureCoordinates_in;
//Output to framebuffer
out vec4 color;
void main(){
//使用纹理坐标和采样器,应用纹理,得到颜色
color=texture(textureObject,textureCoordinates_in.textureCoordinates;
}
控制纹理数据如何读取
采样器在纹理单元中找到纹理数据并且遍历。纹理单元包含了一个纹理对象和一个采样对象。你已经知道纹理对象是什么了,现在我们来讨论下采样对象吧。
纹理坐标在0.0和1.0之间。OpenGL让我们自己决定对于那些超出了这个范围的坐标怎么办。这叫包围模式。OpenGL同样让我们自己决定对于不是1比1的比例,怎么处理。这个叫做过滤模式。一个采样器存储了包围模式和过滤模式这些参数。
一个采样器需要纹理对象和采样器对象,他们都要绑定到纹理单元。当这个数据集合是完整的,采样才能能正常应用一个纹理。
在特殊情况下,你将会创建一个采样器对象、然后把它绑定到纹理单元。但是更多的情况下,你不需要创建一个采样器对象。这是因为纹理对象有一个默认的采样器对象,你可以使用这个默认的。默认的采样器对象有默认的包围/过滤模式参数配置。
为了访问存储在纹理对象中的默认的采样器对象,你可以调用:
清单8:
//accessing the default sampler object in a texture object and setting the sampling parameters
glTexParameter()
在绑定纹理对象到纹理单元之前,你必须激活纹理单元。这个需要调用下面的函数,来激活指定的纹理单元
清单9:
//Activate Texture Unit 0
glActiveTexture(GL_TEXTURE0);