openGl系列之纹理加载

221 阅读8分钟

opengl系列篇二

本篇目标:

本篇主要介绍opengl 在实现基本绘制的时候需要了解的opengl中常用的通用语的含义,以及纹理在opengl中的使用场景, 在Android 中使用纹理

一:opengl 通用语

  1. 顶点数组对象:(Vertex Array Object,VAO ) opengl中向GPU发送指令时需要创建不同种类的对象,通过这些对象承载需要GPU实现的一些指令的数据源,其中一种叫做 顶点数组对象,创建时需要指令类型为GL_ARRAY_BUFFER,创建完成之后,opengl会给你生成一个int id 值,这个值是你需要操作的GPU小程序的对应的句柄值,简单说下,就是通过opengl生成的这个唯一的 id,你就能给这个程序中的变量赋值 修改等操作,简单看下代码如何生成VAO

     //创建和绑定vao
        //unsigned  int * vaoId // vaoId会在创建成功之后成为操作vao缓存对象的一个句柄,由GPU生
        //成返回给到CPU
        GLCall( glGenVertexArrays(1, vaoId));
        GLCall( glBindVertexArray( *vaoId));
    
  2. 顶点缓冲对象:(Vertex Buffer Object,VBO),VBO的定义和VAO,基本一致,区别在于创建生成VBO的时候指定的指令类型是 ,具体代码如下:

    //创建和绑定vbo,并为VBO指定数据源长度
        GLCall( glGenBuffers(1, vboId));
        GLCall(glBindBuffer(GL_ARRAY_BUFFER, *vboId));
        //glBufferData 函数第二个参数 指的是 顶点坐标的长度,一般是定义的顶点个数 * 每一个定的类型
        //长度(sizeof (类型) ),顶点坐标类型一般是float
        GLCall(glBufferData(GL_ARRAY_BUFFER, (vertexLength )*sizeof(float) , (void *)vertex, GL_STATIC_DRAW));
    
  3. 元素缓冲对象:(Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO),元素缓冲对象定义和上面2种也基本一致,创建需要 指定的标识也是 ,另外其实索引缓冲对象主要是为了解决cpu在向GPU一次传送很少的顶点占用物理空间的问题,因此将顶点数组对象在 顶点坐标中的索引摘取出来,形成索引缓冲对象,具体代码如下:

     //创建和绑定ibo并为IBO 指定具体数据源和长度
        GLCall( glGenBuffers(1, iboId));
        GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *iboId));
        GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexLength *sizeof(float), index, GL_STATIC_DRAW));
    

4 顶点坐标: opengl中绘制的图像的顶点在opengl的标准坐标系下的坐标值,一般在代码中长这个样子

```
float vertices[] = {
            0.5f,  0.5f, 0.0f,  // top right    //0
            0.5f, -0.5f, 0.0f,  // bottom right //1
            -0.5f, -0.5f, 0.0f,  // bottom left //2
            -0.5f,  0.5f, 0.0f   // top left    //3
 };
```

在2维不涉及Z轴的时候,opengl的坐标系一般是屏幕中心为原点,坐标为(0,0),从左上按照顺时针方向旋转,屏幕四个顶点的坐标依次为 左上(-1,1),右上(1,1),右下(1,-1),左下(-1,-1) opengl坐标1.png

对应到android 中就是手机屏幕正中心是原点,向右x轴正方向,向上y轴正方向,具体从网上找了一张图,可参考如下所示:

3806049-904418d6a165a20f.jpg

opengl_coor.png

5 纹理坐标:opengl在绘制中存在一种纹理坐标系,纹理在opengl中就理解成图片即可,纹理坐标系的原点在屏幕的左下角,左下角为(0,0)X轴沿着屏幕向右为正方向,Y轴沿着屏幕向上为正方向,右上纹理点的坐标为(1.1)

float texCoords[] = {
    0.0f, 0.0f, // 左下角
    1.0f, 0.0f, // 右下角
    0.5f, 1.0f // 上中
};

6. glsl语言:glsl语言属于opengl独有的一种类C类语言,专门用于操作opengl中的shader逻辑,类型不多,逻辑简单,具体类似下面的就是glsl语言,详细的glsl语言中的类型请参考下面的连接

#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
 }

7 顶点着色器:

顶点着色器指的是一般首先将需要绘制的图像的边界或者位置信息的坐标接受器,也就是说顶点着色器会将上面的顶点坐标指定的范围和坐标的值传递到由glsl语言所书写的shader中,此时shader可以将由cpu传过来的顶点坐标进行计算,更改,即所有的逻辑运算,表现在图像层,就是人眼看到的图像的平移,旋转,缩放都是 在 一定时间内对坐标的运算

  1. 片元着色器:

片元着色器是经过顶点着色器处理过后 的另外一个shader,片元着色器主要是操作要绘制的图像 的颜色,从流程上来说,顶点着色器通知到opengl绘制图像的形状,片元着色器通知opengl需要绘制的图像的颜色,经过这2个shader的处理之后就可以在电子屏幕界面上展示出图像了。

pipeline.png

  1. 视口:在opengl使用中一般就是使用 glViewport函数指定下窗口的大小和原点坐标

上面的这些内容在下面的代码中进行体现,同时介绍下一般在项目中的一些基本使用规范

二:opengl在PC端使用基本组件加载纹理

首先介绍上面的opengl 基本组件的使用,代码如下: `


#include "iostream"
#include "Utils.h"

using namespace std;

void createOpenglElement(unsigned  int * vaoId,unsigned int * vboId,unsigned int *iboId ,
                         float *vertex, int vertexLength, int * index, int indexLength);

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

void key_callback(GLFWwindow   * window, int key, int scancode, int action, int mode)
{
    //如果按下ESC,把windowShouldClose设置为True,外面的循环会关闭应用
    if(key==GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    std::cout<<"ESC"<<mode;
}

int main() {

    std::cout << "Hello, World!" << std::endl;

    if(!glfwInit())
        return -1;
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE ,GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //创建窗口以及上下文,窗口大小为960 * 540,这个值可以随便更改,电脑会根据你配置的值生成对应大小的窗口显示器
    GLFWwindow* window = glfwCreateWindow(960, 540, "hello opengl", NULL, NULL);
    if(!window)
    {
        //创建失败会返回NULL
        glfwTerminate();
        std::cout << "create window failed" << std::endl;
        return -1;
    }
    glfwMakeContextCurrent (window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))

    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    //设置opengl窗口显示的原点和宽高,opengl的显示视图窗口和 我们上面创建的 window窗口一样大小
    glViewport(0,0,960,540);

    //设置刷新频率
    glfwSwapInterval(1);


    Utils utils;
    string verTextShaderPath = "D:\\code\\clion_workspace\\openGlLearingForCPlusPlus\\glsl\\cpp_vertex.glsl";
    string fragmentPath = "D:\\code\\clion_workspace\\openGlLearingForCPlusPlus\\glsl\\cpp_fragment.glsl";
    string  vertexStr = utils.readShaderFromFile(const_cast<char *>(verTextShaderPath.c_str()));
    string  fragmentStr = utils.readShaderFromFile(const_cast<char *>(fragmentPath.c_str()));

    int vertexId = -1;
    int fragmentId = -1;
    utils.createAndCompileShader(const_cast<char *>(vertexStr.c_str()), utils.TYPE_VERTERX, vertexId);
    utils.createAndCompileShader(const_cast<char *>(fragmentStr.c_str()), utils.TYPE_FRAGMENT, fragmentId);

    cout<<"vertexId:" <<vertexId<<endl;
    cout<<"fragmentId:" <<fragmentId<<endl;

   int renderId =  utils.linkProgram(vertexId,fragmentId);

    //顶点坐标
    float vertices[] = {
            0.5f,  0.5f, 0.0f,  // top right    //0
            0.5f, -0.5f, 0.0f,  // bottom right //1
            -0.5f, -0.5f, 0.0f,  // bottom left //2
            -0.5f,  0.5f, 0.0f   // top left    //3
    };
    //顶点坐标对应的索引坐标
    int indices[] = {
            // note that we start from 0!
            0, 1, 3,  // first Triangle
            1, 2, 3   // second Triangle
    };

    unsigned  int vaoId = 99;
    unsigned int vboId = 99;
    unsigned int eboId = 99;

    createOpenglElement(&vaoId,&vboId,&eboId,vertices, (sizeof (vertices)/sizeof (float)) ,
                        indices, (sizeof (indices) / sizeof (int )));
//    initVertexConfiguration(vertices,12,indices,6);

    cout<<"vaoId: " <<vaoId<<endl;
    cout<<"vboId: " <<vboId<<endl;
    cout<<"iboId: " <<eboId<<endl;

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);
        //初始化背景颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // draw our first triangle
        GLCall(glUseProgram(renderId));

        GLCall(glBindVertexArray(vaoId)); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized

        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
        // glBindVertexArray(0); // no need to unbind it every time

        // -------------------------------------------------------------------------------
        GLCall(glfwSwapBuffers(window));
        GLCall(glfwPollEvents());
    }

    //reinterpret_cast 强制转换
    //删除
    glDeleteVertexArrays(1,&vaoId);
    glDeleteBuffers(1, &vboId);
    glDeleteBuffers(1, &eboId);
    glDeleteProgram(renderId);
    /*****************标准opengl 代码 在不同平台上 使用opengl 均需要以上代码*********************/

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}


//注意传入的是指针,需要在opengl中使用指针和解指针

void createOpenglElement(unsigned  int * vaoId,unsigned int * vboId,unsigned int *iboId ,
                         float *vertex, int vertexLength, int * index, int indexLength)
{
    cout << "createOpenglElement :" << "vertexLength:" <<vertexLength << ",indexLength :" <<indexLength<<endl;
    std::cout << "createOpenglElement   sizeof(vertexArray) : " <<  sizeof(vertex) << std::endl;
    std::cout << "createOpenglElement   sizeof(index : " <<  sizeof(index) << std::endl;


    //创建和绑定vao
    GLCall( glGenVertexArrays(1, vaoId));
    GLCall( glBindVertexArray( *vaoId));

    //创建和绑定vbo,并为VBO指定数据源长度
    GLCall( glGenBuffers(1, vboId));
    GLCall(glBindBuffer(GL_ARRAY_BUFFER, *vboId));
    //glBufferData 函数第二个参数 指的是 顶点坐标的长度,一般是定义的顶点个数 * 每一个定的类型长度(sizeof (类型) ),顶点坐标类型一般是float
    GLCall(glBufferData(GL_ARRAY_BUFFER, (vertexLength )*sizeof(float) , (void *)vertex, GL_STATIC_DRAW));

    //创建和绑定ibo并为IBO 指定具体数据源和长度
    GLCall( glGenBuffers(1, iboId));
    GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *iboId));
    GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexLength *sizeof(float), index, GL_STATIC_DRAW));


    //关键步骤,指定传递出去的数据之间的排列方式和数据大小 数据类型等
    GLCall(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0));
    GLCall(glEnableVertexAttribArray(0));

    //解绑
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

#include "Utils.h"

#include "iostream"
#include "../include/GLFW/glfw3.h"
#include "../include/glad/glad.h"

#include <string>
#include <fstream>
#include <sstream>

void GlClearError()
{
    while (glGetError() != GL_NO_ERROR);
}

bool GlLogCall(const char *fun, const char *file, int line) {
    while (GLenum error = glGetError()){
        cout<<"OpenGl Error:" <<error<<endl;
        cout<<"file:"<<file<<",LINE:"<<line<<",FUNC:"<<fun <<endl;
        return false;
    }
    return true;
}


bool Utils::createAndCompileShader(char *glslStr, int glslType, int &shaderId) {
    unsigned  int shaderType = -1;
    if(glslType == TYPE_VERTERX ){
        shaderType =GL_VERTEX_SHADER;
    } else if(glslType == TYPE_FRAGMENT){
        shaderType = GL_FRAGMENT_SHADER;
    } else{
        cout << "please input right glslType\n" <<endl;
        return false;
    }

    GLCall(shaderId = glCreateShader(shaderType));
    GLCall( glShaderSource(shaderId, 1, &glslStr, NULL));
    GLCall( glCompileShader(shaderId));
    int success;
    char infoLog[512];
    GLCall( glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success));
    cout << "createAndCompileShader success is   " << success<< std::endl;
    if (!success)
    {
        GLCall( glGetShaderInfoLog(shaderId, 512, NULL, infoLog));
        if(glslType == TYPE_VERTERX){
            cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
            return false;
        } else{
            cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
            return false;
        }
    }
    cout << "success::SHADER::VERTEX::COMPILATION " <<  std::endl;
    return true;
}


int Utils::linkProgram(int &vertexId, int &fragmentId) {

    //shaderProgram为opengl运行时 GPU上跑的程序Id, 此程序id 会被用来查找顶点和片元的着色器中定义的变量
    unsigned int shaderProgram;
    GLCall( shaderProgram = glCreateProgram());
    GLCall( glAttachShader(shaderProgram, vertexId));
    GLCall( glAttachShader(shaderProgram, fragmentId));
    GLCall( glLinkProgram(shaderProgram));

    // check for linking errors
    int success = -1;
    string infoLog = "";
    GLCall( glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success));
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, const_cast<char *>(infoLog.c_str()));
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    std::cout << "linkProgram success result" << shaderProgram << std::endl;
    glDeleteShader(vertexId);
    glDeleteShader(fragmentId);
    return shaderProgram;


}

string Utils::readShaderFromFile(char *vertexPath) {
    std::string vertexCode = "";
    std::ifstream vShaderFile;
    vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);

    try
    {
        // open files
        vShaderFile.open(vertexPath);
        std::stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        // convert stream into string
        vertexCode   = vShaderStream.str();
    }
    catch (std::ifstream::failure& e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
    }
    return vertexCode;

}

主要步骤都相似的步骤都移植到until.cpp中实现,后续的关键步骤即可减少。

代码传送门

三:android中的opengl使用组件加载纹理贴图

主要实现加载纹理的render代码如下:

package com.example.opengldemo1_background.render;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.util.Printer;

import com.example.opengldemo1_background.R;
import com.example.opengldemo1_background.utils.OpenGlUtils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class TextureRender implements GLSurfaceView.Renderer {

    private final  static  String TAG = "TextureRender";
    Context mContext;
    int textureId = -1 ;
    public TextureRender(Context context) {
        mContext = context;
        createBuffer();

    }

    private void loadPic() {
        textureId = OpenGlUtils.loadPic(R.raw.tescat2,mContext);
        if(textureId == -1){
            Log.e(TAG,"loadPic failed" );
            return;
        }
        Log.i(TAG,"loadPic ,textureId is  " +textureId);
    }

    private FloatBuffer mIndexBuffer = null;
    private float cube[] = {
        //顶点大小位置        纹理坐标
         -1.0f,-1.0f,       0.0f,0.0f,
          1.0f,-1.0f,       1.0f,0.0f,
          -1.0f,1.0f,       0.0f,1.0f,
          1.0f,1.0f,        1.0f,1.0f
    };
    //定点数量 一般是2个 或者3个
    private static final int POSITION_COMPONENT_COUNT = 2;

    //纹理坐标顶点数量
    private static final int TEXTURE_COMPONENT_COUNT = 2;

    //顶点中的Stride
    private static final int STRIDE_VERTEX_TEXTURE =
            (POSITION_COMPONENT_COUNT + TEXTURE_COMPONENT_COUNT) * 4;



    //使用者顶点着色器和片元着色器的gpu程序在CPU对应的ID 值
    private int mRendeId = -1;
    //顶点着色器中的 顶点坐标
    private int aPositionLocation = -1;
    //顶点着色器中的  纹理坐标
    private int aTextureCoordinatesLocation = -1;
    //点段着色器中的 采样器坐标
    private int uTextureUnitLocation = -1;
    private static String A_POSITION  = "a_Position";
    private static String F_sample2D = "u_TextureUnit";
    protected static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";


    private void updateShader(){
        String vertxtStr  = null;
        String framentStr = null;
        vertxtStr = OpenGlUtils.readTextFileFromResource(mContext, R.raw.test_load_texture_vertex);
        framentStr = OpenGlUtils.readTextFileFromResource(mContext,R.raw.test_load_texture_fragement);

        mRendeId =OpenGlUtils.buildProgram(vertxtStr, framentStr);
        aPositionLocation = glGetAttribLocation(mRendeId, A_POSITION);
        aTextureCoordinatesLocation = glGetAttribLocation(mRendeId, A_TEXTURE_COORDINATES);
        uTextureUnitLocation = glGetAttribLocation(mRendeId,F_sample2D);

        Log.i(TAG,"updateShader floatBuffer size :" + mIndexBuffer.capacity());
        Log.i(TAG,"updateShader mRendeId  :" + mRendeId);
        Log.i(TAG,"updateShader aPositionLocation  :" + aPositionLocation);
        Log.i(TAG,"updateShader aTextureCoordinatesLocation  :" + aTextureCoordinatesLocation);
        Log.i(TAG,"updateShader uTextureUnitLocation  :" + uTextureUnitLocation);

    }
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        Log.i(TAG,"onSurfaceCreated ");
        loadPic();
        updateShader();
    }

    private void createBuffer(){
        mIndexBuffer = ByteBuffer.allocateDirect(cube.length * 4).
                order(ByteOrder.nativeOrder()).asFloatBuffer().put(cube);
        mIndexBuffer.position(0);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        Log.i(TAG,"onSurfaceCreated ");
        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        Log.i(TAG,"onDrawFrame ");
        drawTexture();
    }

    private void drawTexture(){
        glClear(GL_COLOR_BUFFER_BIT);
        //开始使用 gpu小程序
        glUseProgram(mRendeId);

        //激活纹理并通知采样函数去哪个纹理插槽去采样
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,textureId);
        glUniform1i(uTextureUnitLocation,0);

        //设置顶点坐标
        mIndexBuffer.position(0);
        glVertexAttribPointer(aPositionLocation,POSITION_COMPONENT_COUNT,
                GL_FLOAT,false, STRIDE_VERTEX_TEXTURE,mIndexBuffer);
        glEnableVertexAttribArray(aPositionLocation);
        mIndexBuffer.position(0);

        //纹理坐标使能
        mIndexBuffer.position(POSITION_COMPONENT_COUNT);

        glVertexAttribPointer(aTextureCoordinatesLocation, TEXTURE_COMPONENT_COUNT
                , GL_FLOAT,
                false, STRIDE_VERTEX_TEXTURE, mIndexBuffer);
        glEnableVertexAttribArray(aTextureCoordinatesLocation);
        mIndexBuffer.position(0);

        // 开始绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }
}

对应的顶点着色器和片元着色器

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main() {
        v_TextureCoordinates = a_TextureCoordinates;
        gl_Position = a_Position;
}

片元着色器

precision mediump float;

uniform sampler2D u_TextureUnit;

varying vec2 v_TextureCoordinates;

void main()
{
        gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}

代码传送门

三:结语

本篇文章主要介绍了opengl在实现基本效果之纹理加载中 使用到的一些特有概念如纹理,采样器等 和一些在使用opengl进行绘制时共同的概念如 VBO,IBO,顶点着色器,片元着色器等基本概念,辅助开发者在第一次使用opengl开发的时候进行理解,一般只要理解了本篇介绍的这些基本概念,opengl中的一些基本用法已经基本掌握。

本篇还提到的纹理的概念还可以运用到android的相机上,即将相机获取的实时流采用纹理进行显示,因此在显示的时候即可以运用opengl进行一些特效的增加,一些特殊场景的实现,以及一些录制好的视频流在解码之后也可以进行播放,这些都是opengl在android中的一些使用场景

接下来opengl在使用的过程中一些特效的增加,一些特殊场景的实现需要均涉及到opengl的MVP矩阵以及opengl的坐标系等知识,具体使用请参考下一章