美文网首页
纹理-给你一个真实的世界

纹理-给你一个真实的世界

作者: ty_Chen | 来源:发表于2018-01-20 10:46 被阅读331次
    前提

    《Hello, OpenGL World!》《着色器-绚丽多彩的世界,从你开始!》两篇文章过后,发现一些重复的代码,例如对于着色器、着色器程序的创建以及着色器程序链接着色器等代码。因此,将这些重复的代码进行了简单地封装,具体实现在ShaderCompiler文件夹中。后续的代码便使用该类进行着色器的一些处理。

    正文

    我们已经大概了解了一些着色器的知识,而OpenGL大部分视觉神奇的阶段是在片段着色器阶段,片段着色器的核心方面既是对表面应用纹理。
    我们通过使用OpenGL来绘制一张图片到屏幕上来具体学习纹理的一些知识点。最终我们绘制的图片如下效果。


    最终效果

    我们绘制一张长方形的图片,这张图片是“贴”上去的,所以,我们可以使用前面使用的知识先绘制一个长方形,这里我也会讲述如何绘制一个长方形,从而加深对前面知识的理解。

    绘制长方形的步骤

    1.设置上下文
    2.设置着色器
    3.设置VAO、VBO、EBO(IBO)
    4.清除窗口开始渲染

    上下文

    设置上下文相关在这就不做赘述了,千篇一律的设置。查看相关函数即可。

    着色器

    个人一般习惯先创建需要使用到的顶点着色器和片段着色器对象,再使用glsl语言去书写着色器。前面已经提到过,因这一部分代码是可以复用的,所以在这里,我只需要调用SMShaderCompiler类中的初始化方法就会实现着色器、着色器程序的创建,并通过着色器程序将顶点着色器和片段着色器进行链接,需要传入的参数仅仅是顶点着色器和片段着色器的文件名(包括后缀)。
    此后,我们便需要使用glsl语言来书写顶点着色器以及片段着色器。在书写之前,我们先思考一下,绘制一个四边形我们需要的东西有哪些。
    因为绘制的是四边形,所以四个顶点是必不可少的,但是我们绘制的图元使用的三角形,那是否需要六个顶点坐标呢?答案是不需要,因为我们使用EBO的方式去绘制。通过效果图我们可以很轻松的得到长方形的顶点坐标如下:

    float vertices[] = {
        // location   
        1.0,  0.5, 0, 
        1.0, -0.5, 0, 
       -1.0, -0.5, 0,  
       -1.0,  0.5, 0
    };
    

    在讲解着色器时我们说到,书写着色器一般以版本号开始,所以我们我们先声明版本号。又我们需要一个顶点坐标的输入,所以一个用于表示位置的3分量的向量是必不可少的。为了明显地看到绘制的四边形的效果,我们再添加颜色相关的数据,那么一个表示颜色的3分量的向量也是具有存在的必要的。我们需要将顶点数组中的颜色信息传递给片段着色器,所以一个输出的颜色变量也需要实现。为了简单地知道位置信息和颜色信息的位置我们使用layout来说明。因此到这时的顶点着色器看起来就像下面一样。

    #version 300 es
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    
    out vec3 ourColor;
    void main() {
        gl_Position = vec4(aPos, 1);
        ourColor = aColor;
    }
    

    相对于顶点着色器,片段着色器便容易的多了,同样我们声明版本信息,并使用一个ourColor来接收顶点着色器传入的颜色信息。片段着色器看起来便是下面的样子。

    #version 300 es
    precision mediump float;
    in vec3 ourColor;
    out vec4 fragColor;
    
    void main() {
        fragColor = vec4(ourColor, 1);
    }
    

    紧接着,我们就需要告诉OpenGL如何解析顶点数据了。

    VAO、VBO、EBO

    在前面我们已经书写出了矩形的顶点坐标,再加上颜色相关的信息,顶点数据就成了下面的样子。

    float vertices[] = {
        // location    // colors       
        1.0,  0.5, 0,  1.0, 0.0, 0.0,  
        1.0, -0.5, 0,  0.0, 1.0, 0.0,  
       -1.0, -0.5, 0,  0.0, 0.0, 1.0,  
       -1.0,  0.5, 0,  1.0, 1.0, 1.0
    };
    

    因为我们使用EBO的方式去实现矩形的绘制,所以我们需要知道绘制矩形的索引(索引需要根据顶点数据的位置信息并以0开始):

    GLuint indices[] = {
        0, 1, 3,
        1, 2, 3
    };
    

    下面就是VAO、VBO、EBO的生成绑定了,这里便不做赘述,直接贴代码。

    glGenVertexArrays(1, &(_VAO));
    glBindVertexArray(_VAO);
    
    glGenBuffers(1, &(_VBO));
    glBindBuffer(GL_ARRAY_BUFFER, _VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    glGenBuffers(1, &(_EBO));
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    

    然后便是告诉OpenGL如何解析顶点数据了。通过分析顶点数据我们可以分析到与坐标相关的位置消息起始于0,颜色起始于1,因为我们在顶点着色器里是这么写的。数据类型当然是float类型,并且步进值很明显是6,位置的偏移量是0,颜色的偏移量是3 * sizeof(float)。那么链接顶点属性的代码看起来是这样的。

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    

    最后便是渲染了。这里没有什么比较难以理解的东西,所以直接贴代码。

    glClearColor(0.2, 0.5, 0.8, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    
    [self.shaderCompiler userProgram];
    glBindVertexArray(_VAO);
    
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    

    运行程序,我们便可以看到绘制出一个漂亮的矩形了。


    漂亮的矩形

    下面我们便开始纹理相关知识的讲解:
    个人理解,将图片贴入到显示器上,是将图片上的各个点的颜色进行采样(使用纹理坐标获取纹理颜色),并让顶点进行关联。纹理同样具有纹理坐标,纹理坐标的范围是0-1,起始于(0,0),终止于(1,1)。


    纹理坐标

    在该案例中,我们把左下角的坐标设置为(0,0),那么右下方向上的纹理坐标则为(1, 0),右上方向上的纹理坐标则为(1, 1),左上方向上的纹理坐标则为(1, 1)。我们把纹理坐标写入顶点数组,那么被修改后的顶点数组如下:

    float vertices[] = {
        // location    // colors       // texture
        1.0,  0.5, 0,  1.0, 0.0, 0.0,  1.0, 1.0,
        1.0, -0.5, 0,  0.0, 1.0, 0.0,  1.0, 0.0,
       -1.0, -0.5, 0,  0.0, 0.0, 1.0,  0.0, 0.0,
       -1.0,  0.5, 0,  1.0, 1.0, 1.0,  0.0, 1.0
    };
    

    得到纹理坐标以后,我们便要告诉OpenGL如何对纹理进行采样了。

    纹理环绕方式

    纹理环绕方式的用途是,当我们设置的纹理坐标超过(0,0)到(0,1)的范围时OpenGL会对纹理进行怎样的处理。OpenGL的默认的纹理环绕方式是GL_REPEAT,看名称我们应该知道它是如何处理的,对超出的部分进行复制。效果如下:


    GL_REPEAT纹理环绕方式

    纹理的环绕方式除了GL_REPEAT外,还有GL_MIRRORED_REPEAT、GL_CLAMP_TO_EDGE和GL_CLAMP_TO_BORDER这几种方式,其效果便不一一在此展示,大家可以把该文章的案例实现以后去修改纹理坐标以及环绕方式进行查看。
    设置纹理环绕方式我们使用glTexParameteri函数进行设置。

    // 参数说明:
    // target: 指定纹理目标,在这里我们绘制的是2D纹理,所以使用GL_TEXTURE_2D,当然也有1D和3D纹理
    //  pname: 指定设置的选项与应用纹理坐标(s、t、r对应顶点坐标的x、y、z)
    //  param: 纹理环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    纹理过滤

    纹理过滤是告诉OpenGL如何将纹理像素映射到纹理坐标。OpenGL有许多纹理过滤的选项,在此我们暂时只说明GL_NEAREST(邻近过滤)和GL_LINEAR(线性过滤)两种。

    GL_NEAREST(邻近过滤)
    邻近过滤指的是OpenGL会选择中心点最接近于纹理坐标的那个像素。 邻近过滤 图来源于LearOpenGL CN
    GL_LINEAR(线性过滤)
    线性过滤会基于纹理坐标附近的纹理像素计算出一个插值,近似出这些纹理像素的颜色,一个纹理像素的中心距离纹理坐标的距离越近,该纹理像素所占的比重越大。 线性过滤 图来源于LearOpenGL CN

    设置纹理的过滤方式也是使用glTexParameteri函数,只是传入的值不同而已。这里我们统一使用GL_NEAREST的过滤方式。

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    

    在前面已经得到了一个包含位置、颜色、纹理坐标的顶点数组,所以这里我们需要对顶点着色器以及片段着色器进行相应的修改。
    我们对纹理采样并获得对应纹理坐标的纹理像素,而纹理坐标在我们的顶点数组中定义的是一个2分量的向量,在片段着色器中,我们同样需要纹理坐标来获得纹理像素的颜色,所以这里便多出一个一个纹理坐标的输入和输出,那么顶点着色器也就变成了下面的样子。

    #version 300 es
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    layout (location = 2) in vec2 aTexCoord;
    
    out vec3 ourColor;
    out vec2 ourTexCoord;
    
    void main() {
        gl_Position = vec4(aPos, 1);
        ourColor = aColor;
        ourTexCoord = aTexCoord;
    }
    

    在片段着色器中我们当然需要一个2分量的向量来接收顶点着色器的纹理坐标的输入。此外,我们还要知道对应的纹理来进行着色,在这里我们使用uniform的方式来设置。在main函数中,使用texture函数来得到最终的颜色。

    #version 300 es
    precision mediump float;
    
    in vec2 ourTexCoord;
    in vec3 ourColor;
    out vec4 fragColor;
    
    uniform sampler2D ourTexture;
    
    void main() {
        fragColor = texture(ourTexture, ourTexCoord);
    }
    

    在修改完着色器后,紧接着便要修改链接顶点属性相关的代码了。这里主要因为增加了纹理坐标,所以我们的步进值由6变为了8,在下标为2时,偏移量为3 + 3 = 6。因此,设置VAO...函数的代码如下:

    float vertices[] = {
        // location    // colors       // texture
        1.0,  0.5, 0,  1.0, 0.0, 0.0,  2.0, 2.0,
        1.0, -0.5, 0,  0.0, 1.0, 0.0,  2.0, 0.0,
       -1.0, -0.5, 0,  0.0, 0.0, 1.0,  0.0, 0.0,
       -1.0,  0.5, 0,  1.0, 1.0, 1.0,  0.0, 2.0
    };
    
    GLuint indices[] = {
        0, 1, 3,
        1, 2, 3
    };
    
    glGenVertexArrays(1, &(_VAO));
    glBindVertexArray(_VAO);
    
    glGenBuffers(1, &(_VBO));
    glBindBuffer(GL_ARRAY_BUFFER, _VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    glGenBuffers(1, &(_EBO));
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
    

    到这里,我们还未对纹理做任何的处理,只是设置了它的环绕方式和过滤方式。现在我们要生成和应用纹理。生成纹理和应用纹理的学习我们可以参照VAO、VBO、EBO。因为它们的步骤是如此的相似。
    生成纹理:

    glGenTextures(1, &_texture);
    glBindTexture(GL_TEXTURE_2D, _texture);
    

    是不是很VBO等十分相似?同样地在后面,我们需要对纹理进行绑定,使用glTexImage2D方法即可。

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData);
    

    最后,便是物体的渲染,在渲染函数中,我们绑定VAO以及纹理,再通过绘制三角形的方式进行渲染即可。

    glClearColor(0.2, 0.5, 0.8, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glBindTexture(GL_TEXTURE_2D, _texture);
    [self.shaderCompiler userProgram];
    glBindVertexArray(_VAO);
    
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    

    纹理贴图就完成了。如果你做到这里运行程序后并没有得到与效果图中的结果,请参考源码

    后续

    在后续的文章中,会新开一个专题总结一些计算机图形学基础相关的知识,当然笔者也是边学边总结,若有不对之处,尽请提出,互相学习提高。

    相关文章

      网友评论

          本文标题:纹理-给你一个真实的世界

          本文链接:https://www.haomeiwen.com/subject/ahedoxtx.html