封装Renderer,纹理

今天开始写Renderer类,在这个类中,我们会写一个Draw方法,它会接收实际要绘制的数据。我们需要考虑这个类应该是static或者是一个单例之类的,这取决于我们如何去使用它,这里我们不用static也不用单例模式,把它当作一个普通的类来处理。
首先添加一个Draw函数,参数需要顶点数组、索引缓冲区和着色器,这里顶点数组已经绑定了顶点缓冲区,所以不需要顶点缓冲区。还添加了一个Clear函数,将glClear封装进来。
VertexBufferLayout.h中包含了Renderer.h,Renderer.h中包含了VertexArray.h,而VertexArray.h中又包含了VertexBufferLayout.h,这相当于Renderer.h和VertexBufferLayout.h互相包含了,像是进入了一个无限循环的包含。
我们只需要在VertexArray.h将VertexBufferLayout.h的包含删除,直接声明这个类,然后在cpp文件中包含这个类的头文件


材质基本上就是一个着色器加上一系列数据,包括所有的统一变量,不管是特殊的统一变量还是每个对象都有的统一变量, 当我们把材质传递给渲染器时,渲染器就会绑定材质,意味着它会绑定着色器,设置它所需的所有统一变量,然后根据索引绘制。目前我们想修改一个着色器的统一变量,只能自己手动完成,这有点烦人,我们之后会接触到材质。无论如何,Renderer类的方法相当多,我们现在有一个类了,随着我们实际需要渲染的东西越来越多,可以持续往里面添加东西。

纹理,简单来说我们可以把它看作是我们在渲染时可以使用的一个图像,我们可以在电脑中创建任何图像文件,然后把它上传到我们的显存中,然后在我们的着色器中使用它来绘制我们正在做的任何东西,这可能就像我们在OpenGL程序中画一个矩形一样简单,但也可能是更复杂的东西,就比如使用预先计算好的数学值,将其融入到我们的纹理中,然后在着色器中对它们进行采样,这样我们可以做一些很酷的效果,但现在,我们只在OpenGL中获取到计算机上图像然后绘制出来。
我们先把电脑上的图像文件上传过来,让它可以在着色器中进行采样,然后把它绘制在屏幕上。第一步,我们需要将图像加载到CPU内存中,我们要使用stb_image库来来加载图像文件,我们会给它一个文件路径,他会给我们一个指向RGBA像素缓冲区的指针,我们会拿到这个像素数组,把这些数据作为纹理发送到GPU,着色器读取这个纹理,在绘制时,片段着色器就会从纹理内存中读取并计算出每个像素应该是什么,构成那个纹理每部分的颜色是什么。
我们下载stb_image头文件,将它加入我们的工程,并另创建一个新的C++文件,输入以下代码并编译就可以了

OpenGL中有很多插槽(slot)可以绑定纹理,在Windows上,一般显卡会有32个纹理插槽,而移动设备上一般是8个,这取决于显卡以及它们的OpenGL实现。
创建Texture类
首先用stbi_set_flip_vertically_on_load垂直翻转我们的纹理,因为OpenGL扫描我们的纹理像素是从左下角开始的,而通常当我们加载图像时,存储是从图像的顶部到底部的,所以实际上我们需要在加载时翻转它,不过这取决于我们的纹理格式,这里是PNG或者JPG ,那么我们需要翻转它。
我们就有了在这个LocalBuffer中所有的纹理,然后就可以设置纹理参数,
GL_TEXTURE_MIN_FILTER是缩小过滤器,如果我们在一个比实际的纹理更小的区域上渲染纹理,那么需要将纹理缩小
GL_TEXTURE_MAG_FILTER是放大过滤器,如果我们在一个比实际的纹理更大的区域上渲染纹理,那么需要将纹理放大
GL_TEXTURE_WRAP_S是嵌入模式(环绕模式),S指水平环绕,
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_Width, m_Heigh, m_LocalBuffer)这里GL_RGBA是我们希望OpenGL处理数据的方式,内部格式与格式是不一样的,内部格式是OpenGL存储纹理数据的方式,而格式是纹理数据,是我们提供给OpenGL的数据格式,这里就是m_LocalBuffer的格式。最后一个参数指定像素,如果我们还没准备好数据就可以在这里传递一个空指针或者0。
这样就加载好了纹理,接下来要告诉Shader如何采样纹理, 所以我们要更新顶点数据,将纹理坐标添加进去,并且在绑定顶点缓冲区时将参数做相应的调整

接着我们需要在顶点着色器中添加纹理及其坐标,使其能够接受顶点坐标为一个顶点属性,并把纹理坐标传给片段着色器,片段着色器可以通过sampler(采样器)来访问纹理对象,sampler以纹理类型作为后缀,这里因为是2D的图片,所以我们声明一个uniform sampler2D,通过GLSL内建的texture函数来采样纹理的颜色,第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。
最后在调用glDrawElements之前绑定纹理,就完成了把纹理赋值给采样器的工作,最后运行就能将图片输出
