OpenGL系列之七:纹理贴图
目录
相关文章
OpenGL系列之一:OpenGL第一个程序
OpenGL系列之二:绘制三角形
OpenGL系列之三:三角形顶点增加颜色
OpenGL系列之四:绘制四边形
OpenGL系列之五:绘制点和线
OpenGL系列之六:绘制立方体
实现效果
实现步骤
1.引入stb_image
https://github.com/nothings/stb/blob/master/stb_image.h
在main->cpp文件夹下新建libstbimage/include,然后将stb_image.h放进去
2.修改CMakeLists.txt
增加如下代码
#增加stbimage
SET(STBIMAGE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/3rdparty/libstbimage)
INCLUDE_DIRECTORIES(${STBIMAGE_DIR}/include)
3.增加JNI函数
这里我增加了ndkLoadGLTexTureID函数用于加载纹理
private external fun ndkLoadGLTexTureID(fileName:String)
然后在onSurfaceCreated方法中调用
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
//初始化
ndkInitGL()
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),"texture/rabit.png")
if(file.exists()){
Log.e("测试",file.absolutePath)
ndkLoadGLTexTureID(file.absolutePath)
}
}
其中使用的图片是我导入到SD卡中的
4.编写JNI函数
首先引入stb_image.h头文件,其中通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。
//引入stb_image
#define STB_IMAGE_IMPLEMENTATION
extern "C"{
#include "stb_image.h"
}
然后是编写加载纹理的JNI函数
GLuint m_texID;
int width, height, nrChannels;
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_opengldemo_GLRender_ndkLoadGLTexTureID(JNIEnv *env, jobject thiz,
jstring file_name) {
glEnable(GL_TEXTURE_2D);
glGenTextures(1,&m_texID);//产生纹理索引
glBindTexture(GL_TEXTURE_2D,m_texID);//绑定纹理索引,之后的操作都针对当前纹理索引
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//指当纹理图象被使用到一个大于它的形状上时
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//指当纹理图象被使用到一个小于或等于它的形状上时
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
const char* fileName = env->GetStringUTFChars(file_name,0);
// 让图像正过来(如果不加图像是倒过来的)
stbi_set_flip_vertically_on_load(true);
// 加载并生成纹理
unsigned char *data = stbi_load(fileName, &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
// 释放资源
env->ReleaseStringUTFChars(file_name, fileName);
stbi_image_free(data);
}
5.函数讲解
然后接下来对上面使用的函数进行讲解:
glEnable:开启2D纹理
glGenTextures:函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中,我们这里就一个所以直接取地址了
glBindTexture:绑定纹理
纹理环绕方式:
这里我们使用glTexParameteri设置纹理环绕方式,纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分),但OpenGL提供了更多的选择:
当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出。我们来看看这些纹理图像的例子:
前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(s、t(如果是使用3D纹理那么还有一个r)它们和x、y、z是等价的):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
纹理过滤:
其实就是放大缩小图片所展示的效果,纹理过滤有很多个选项,但是现在我们只讨论最重要的两种:GL_NEAREST和GL_LINEAR。
GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:
GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出,但有些开发者更喜欢8-bit风格,所以他们会用GL_NEAREST选项。
当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。这段代码看起来会和纹理环绕方式的设置很相似:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//指当纹理图象被使用到一个大于它的形状上时
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//指当纹理图象被使用到一个小于或等于它的形状上时
stbi_set_flip_vertically_on_load:这个函数是让图像正过来的,因为默认显示的纹理是倒的
stbi_load:这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的宽度、高度和颜色通道的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。
glTexImage2D:将数据加载到纹理上,函数很长,参数也不少,所以我们一个一个地讲解:
●第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
●第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
●第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
●第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
下个参数应该总是被设为0(历史遗留的问题)。
●第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
●最后一个参数是真正的图像数据。
stbi_image_free:释放资源
6.编写绘制代码
定义结构体用于存储点的信息
struct CCFloat5{
float x;
float y;
float z;
float u;
float v;
};
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_opengldemo_GLRender_ndkPaintGL(JNIEnv *env, jobject thiz) {
//清空颜色缓冲区或深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//加载模型视图矩阵
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glCullFace(GL_BACK);
//定义立方体顶点
CCFloat5 cubeVert[] = {
{ -1.0f, 1.0f, 0.0f, 0, 1 },
{ 1.0f,1.0f, 0.0f, 1, 1 },
{ -1.0f,-1.0f, 0.0f, 0, 0 },
{ 1.0f, -1.0f, 0.0f, 1, 0 },
};
glBindTexture(GL_TEXTURE_2D,m_texID);
//启动一组顶点坐标
glEnableClientState(GL_VERTEX_ARRAY);
//启动TEXTURE
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// 指定绘制的定点数组
// 第一个参数:绘制几个点(这里其实就是CCFloat5中的前三个点x,y,z)
// 第二个参数:类型为float
// 第三个参数:第一个点到第二个点之间的步长(也就是结构体的长度)
// 第四个参数:绘制数据的地址
glVertexPointer(3,GL_FLOAT,sizeof(CCFloat5),cubeVert);
// 指定绘制的定点数组
// 第一个参数:绘制几个点(这里其实就是CCFloat5中的后两个点u,v)
// 第二个参数:类型为float
// 第三个参数:第一个点到第二个点之间的步长(也就是结构体的长度)
// 第四个参数:绘制数据的地址
glTexCoordPointer(2,GL_FLOAT,sizeof(CCFloat5),&cubeVert[0].u);
glm::mat4x4 cubeMat;
//平移矩阵
glm::mat4x4 cubeTransMat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -0.5));
//旋转矩阵
glm::mat4x4 cubeRotMat = glm::rotate(glm::mat4(1.0f),m_angle,glm::vec3(0.5f, 0.5f, 1.0) );
//缩放矩阵
glm::mat4x4 cubeScaleMat = glm::scale(glm::mat4(1.0f),glm::vec3(0.5f, 0.5f, 0.5) );
cubeMat = cubeTransMat * cubeRotMat * cubeScaleMat;
//加载矩阵
glLoadMatrixf(glm::value_ptr(cubeMat));
// 绘制四边形
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
//关闭一组顶点坐标
glDisableClientState(GL_VERTEX_ARRAY);
//关闭TEXTURE
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
首先说一下定义的点,其中x,y,z与之前的是一样的意思,新加的u,v是纹理的坐标,纹理的坐标与图形的坐标有所差异,纹理坐标是左下角为(0,0),因此定义点的时候要对应起来
然后是OpenGL的函数其实与之前大差不差,主要就是使用了纹理渲染