opengl渲染器入门04

引言

本篇文章主要表述一下帧缓冲和环境贴图,主要以帧缓冲为主。

帧缓冲

帧缓冲是opengl渲染中一个非常重要的组成分布,它进行离屏渲染,可以用来保存数据和进行一些后处理相关的操作,一个帧缓冲,还必须要一个纹理附件来保存得到的信息,和一个渲染缓冲附件,包含一些深度值模板值可能。

js 复制代码
    unsigned int framebuffer;             //渲染帧
    glGenFramebuffers(1, &framebuffer);   //生成一个渲染帧给与framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);   //绑定当前的渲染帧

    unsigned int textureColorbuffer;           //附加的纹理对象
    glGenTextures(1, &textureColorbuffer);    //生成一个纹理给与textureColorbuffer
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);   //指明为2d的纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); //指明纹理的大小,参数等,这个一个RGB的2D等
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);   //低通
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);   //高通
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);    //把当前的纹理的对象给与当前的渲染帧

    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);   //绑定好当前的渲染缓冲对象
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); //指明大小和深度和模板值
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);    //把当前的缓冲对象给与帧
    
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)   //检查帧缓冲对象
    {
        std::cout << "error:framebuffer iis not complete!" << std::endl;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);   //切换到主屏幕,主缓冲

值得注意的是,一个帧缓冲可以携带多个纹理对象,在后面的延迟着色法中会有比较重要的体现: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0); 在这里指定了一张纹理附件为GL_COLOR_ATTACHMENT0,并且如果只有一张纹理的,在输出纹理图片的时候不需要指定layout=0,当然指定到0也没有什么错误.

js 复制代码
#version 330 core
out vec4 FragColor;     //默认为0输出通道 layout (location = 0) out vec4 FragColor;也可以实现
in vec2 TexCoords;
uniform sampler2D texture1;
void main()
{    
    FragColor = texture(texture1, TexCoords);
}

同时在输出中可以输出的维度可以和纹理的维度不一致,在本纹理中定义的为RGB的三维的纹理,但是在输出的是一个四维的纹理,也没有什么问题,但是可能一般还是不建议这样。

这里也简单的说明一下多张纹理的定义的格式:

js 复制代码
unsigned int gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
unsigned int gPosition, gNormal, gAlbedoSpec;    //附加的纹理的图片
//下面不详细处理纹理,只给出大概
glGenTextures(1, &gPosition);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
glGenTextures(1, &gNormal);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
glGenTextures(1, &gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);   //这张纹理需要RGBA
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);
unsigned attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);
GLuint rboDepth;
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
       std::cout << "Framebuffer not complete!" << std::endl;
   glBindFramebuffer(GL_FRAMEBUFFER, 0);

在这里可以看出,一个缓冲帧可以有多个纹理对象,为了指明输出的需要,需要使用layout,用unsigned attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments);指明每一个的输出通道。

并且纹理对象的RGBA的值是有关系,如果缓冲输出的RGBA,但是纹理只有RGB格式,可能有出问题。下面给出输出的片元着色器:

js 复制代码
#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;      
in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;
uniform sampler2D texture_diffuse1;
uniform sampler2D texture_specular1;
void main()
{    
    gPosition = FragPos;   //保存位置信息,三维,使用RGB纹理
    gNormal = normalize(Normal);   //保存法线信息,也是三维RGB
    gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb;
    gAlbedoSpec.a = texture(texture_specular1, TexCoords).r;  //保存采样的颜色值,四维,同时第四位在纹理的r上
}

在片元着色器中,输出的名字gPosition,gNormal,gAlbedoSpec和纹理的名字没有关系,只是相当于编程语言中的形参而已。

在渲染中一般需要离屏渲染,需要先绑定该缓冲帧,得到渲染的纹理,再切回主缓冲帧,进行二次处理等:

js 复制代码
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);  //切到缓冲帧
//离屏渲染得到对应的纹理对象或者缓冲数据
glBindFramebuffer(GL_FRAMEBUFFER, 0);  //切回主渲染帧
//绑定得到的纹理对象或者缓冲数据
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
//二次渲染

利用帧缓冲技术可以实现不必再一个着色器中进行大量的计算渲染,可以分开进行,为后面的多光源渲染,延迟着色法提供方法,同时也可以实现一些简单的效果:

js 复制代码
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);   //反相

FragColor = vec4(col, 1.0);
float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
FragColor = vec4(average, average, average, 1.0);    //灰度

也可以实现一些比较高级的效果,模糊,锐化等,这些大多数都是通过卷积核来实现的:

js 复制代码
const float offset = 1.0 / 300.0;  

void main()
{
    vec2 offsets[9] = vec2[](
        vec2(-offset,  offset), // 左上
        vec2( 0.0f,    offset), // 正上
        vec2( offset,  offset), // 右上
        vec2(-offset,  0.0f),   // 左
        vec2( 0.0f,    0.0f),   // 中
        vec2( offset,  0.0f),   // 右
        vec2(-offset, -offset), // 左下
        vec2( 0.0f,   -offset), // 正下
        vec2( offset, -offset)  // 右下
    );

    float kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );  //锐化

    vec3 sampleTex[9];
    for(int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    FragColor = vec4(col, 1.0);
}

另外可能还有应该会无视的点,渲染到屏幕需要屏幕的纹理坐标和顶点坐标:

js 复制代码
float quadVertices[] = { 
    // positions   // texCoords   //是逆时针的
    -1.0f,  1.0f,  0.0f, 1.0f,
    -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, 0.0f,
     1.0f,  1.0f,  1.0f, 1.0f
};

在输入的时候是0-1,不是屏幕的宽度和高度,另外记得,输入点是二维的:

js 复制代码
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 
}  

关于帧缓冲需要了解基本就是这样,可能后面在做补充。

环境贴图

环境贴图有一点像是天空盒,不知道这么理解是否正确,是由六张纹理组成一个盒子而已:

js 复制代码
vector<std::string> faces
    {
        "skybox/right.jpg",     //x+
        "skybox/left.jpg",      //x-
        "skybox/top.jpg",       //y+
        "skybox/bottom.jpg",    //y-
        "skybox/front.jpg",     //z+
        "skybox/back.jpg"       //z-
    };
unsigned int cubemapTexture = loadCubemap(faces);

unsigned int loadCubemap(vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);   //是一张三维的纹理cube

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);   //第一个量是枚举量,在增加的过程中分别为x+ x- y+ y- z+ z-的六张纹理
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}

在渲染的过程中,可以在渲染盒子的时候把深度值给予为1,也可以最后渲染,一般使用第二种,可以减少一定的消耗 glDepthFunc(GL_LEQUAL); 小于等于进行渲染,因为前面已经渲染了环境中的物体,在渲染盒子的时候,当深度小于等于其实就是渲染没有深度值的地方,补充为环境的颜色。 在着色器中有:

js 复制代码
//顶点着色器中
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;  //z一直为1
}  
//片元着色器中
#version 330 core
out vec4 FragColor;
in vec3 TexCoords;    //纹理坐标是三维的
uniform samplerCube skybox;   //三维的纹理对象
void main()
{    
    FragColor = texture(skybox, TexCoords);
}

在对于环境贴图进行折射和反射的过程中,传入对于贴图就可以:

js 复制代码
#version 330 core 
out vec4 FragColor; 
in vec3 Normal; 
in vec3 Position; 
uniform vec3 cameraPos;     //相机的位置
uniform samplerCube skybox; 
void main() 
{   //反射
    vec3 I = normalize(Position - cameraPos); 
    vec3 R = reflect(I, normalize(Normal));         //计算反射光线I指向片元
    FragColor = vec4(texture(skybox, R).rgb, 1.0); 
}
//折射
void main() 
{ 
    float ratio = 1.00 / 1.52; 
    vec3 I = normalize(Position - cameraPos); 
    vec3 R = refract(I, normalize(Normal), ratio); 
    FragColor = vec4(texture(skybox, R).rgb, 1.0); 
} 

计算动态的贴图,需要大量的资源,后面学习。

相关推荐
闲暇部落14 小时前
Android OpenGL ES详解——绘制圆角矩形
opengl·圆形·矩形·圆角矩形
凌云行者2 天前
OpenGL入门008——环境光在片段着色器中的应用
c++·cmake·opengl
闲暇部落5 天前
Android OpenGL ES详解——立方体贴图
opengl·天空盒·立方体贴图·环境映射·动态环境贴图
闲暇部落6 天前
Android OpenGL ES详解——实例化
android·opengl·实例化·实例化数组·小行星带
闲暇部落8 天前
Android OpenGL ES详解——几何着色器
opengl·法线·法向量·几何着色器
刘好念13 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
闲暇部落14 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤
凌云行者15 天前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者15 天前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
凌云行者18 天前
OpenGL入门004——使用EBO绘制矩形
c++·cmake·opengl