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的函数其实与之前大差不差,主要就是使用了纹理渲染

案例源码

https://gitee.com/itfitness/opengl-texture01

你可能感兴趣的:(OpenGL系列之七:纹理贴图)