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); 
} 

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

相关推荐
扶尔魔ocy14 小时前
【QT OpenGL】知识点编目及传送门
qt·opengl·osg
扶尔魔ocy13 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行并进行态势标绘(四)
qt·opengl·osg
:mnong14 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
扶尔魔ocy16 天前
【qt openGL入门笔记】OSG实现显示飞机模型飞行的点迹(二)
qt·opengl·osg
扶尔魔ocy16 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行(三)
opengl·osg
扶尔魔ocy17 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行
qt·opengl·osg
扶尔魔ocy21 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy21 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy22 天前
【qt openGL入门笔记】基础光照
qt·opengl
扶尔魔ocy23 天前
【qt openGL入门笔记】坐标系统和摄像机
qt·opengl