opengl高级光照之延迟渲染以及光体积

延迟着色法

  • 延迟着色法官方文档

  • 我们现在一直使用的光照方式叫做正向渲染(Forward Rendering)或者正向着色法(Forward Shading),它是我们渲染物体的一种非常直接的方式,在场景中我们根据所有光源照亮一个物体,之后再渲染下一个物体,以此类推。它非常容易理解,也很容易实现,但是同时它对程序性能的影响也很大,因为对于每一个需要渲染的物体,程序都要对每一个光源每一个需要渲染的片段进行迭代,这是非常多的!因为大部分片段着色器的输出都会被之后的输出覆盖,正向渲染还会在场景中因为高深的复杂度(多个物体重合在一个像素上)浪费大量的片段着色器运行时间

  • 延迟着色法(Deferred Shading),或者说是延迟渲染(Deferred Rendering),为了解决上述问题而诞生了,它大幅度地改变了我们渲染物体的方式。这给我们优化拥有大量光源的场景提供了很多的选择,因为它能够在渲染上百甚至上千光源的同时还能够保持能让人接受的帧率。下面这张图片包含了一共1874个点光源,它是使用延迟着色法来完成的,而这对于正向渲染几乎是不可能的(图片来源:Hannes Nevalainen)。
    图片1

  • 延迟着色法核心思想:基于我们延迟(Defer)或推迟(Postpone)大部分计算量非常大的渲染(像是光照)到后期进行处理的想法。

  • 它包含两个处理阶段(Pass):在第一个几何处理阶段(Geometry Pass)中,我们先渲染场景一次,之后获取对象的各种几何信息,并储存在一系列叫做G缓冲(G-buffer)的纹理中;想想位置向量(Position Vector)、颜色向量(Color Vector)、法向量(Normal Vector)和/或镜面值(Specular Value)。场景中这些储存在G缓冲中的几何信息将会在之后用来做(更复杂的)光照计算。下面是一帧中G缓冲的内容:
    在这里插入图片描述

  • 我们会在第二个光照处理阶段(Lighting Pass)中使用G缓冲内的纹理数据。在光照处理阶段中,我们渲染一个屏幕大小的方形,并使用G缓冲中的几何数据对每一个片段计算场景的光照;在每个像素中我们都会对G缓冲进行迭代。我们对于渲染过程进行解耦,将它高级的片段处理挪到后期进行,而不是直接将每个对象从顶点着色器带到片段着色器。光照计算过程还是和我们以前一样,但是现在我们需要从对应的G缓冲而不是顶点着色器(和一些uniform变量)那里获取输入变量了。
    在这里插入图片描述

代码
  • 生成组合着色器

        // build and compile shaders
        // -------------------------
        Shader shaderGeometryPass("8.1.g_buffer.vert", "8.1.g_buffer.frag");//第一阶段几何处理纹理附件
        Shader shaderLightingPass("8.1.deferred_shading.vert", "8.1.deferred_shading.frag");//第二阶段光照处理纹理附件
        Shader shaderLightBox("8.1.deferred_light_box.vert", "8.1.deferred_light_box.frag");//渲染光源
    
  • 设置g-buffer

G缓冲

  • G缓冲(G-buffer)是对所有用来储存光照相关的数据,并在最后的光照处理阶段中使用的所有纹理的总称。趁此机会,让我们顺便复习一下在正向渲染中照亮一个片段所需要的所有数据:

    • 一个3D位置向量来计算(插值)片段位置变量供lightDir和viewDir使用
    • 一个RGB漫反射颜色向量,也就是反照率(Albedo)
    • 一个3D法向量来判断平面的斜率
    • 一个镜面强度(Specular Intensity)浮点值
    • 所有光源的位置和颜色向量
    • 玩家或者观察者的位置向量
  • 有了这些(逐片段)变量的处置权,我们就能够计算我们很熟悉的(布林-)冯氏光照(Blinn-Phong Lighting)了。光源的位置,颜色,和玩家的观察位置可以通过uniform变量来设置,但是其它变量对于每个对象的片段都是不同的。如果我们能以某种方式传输完全相同的数据到最终的延迟光照处理阶段中,我们就能计算与之前相同的光照效果了,尽管我们只是在渲染一个2D方形的片段。

  • OpenGL并没有限制我们能在纹理中能存储的东西,所以现在你应该清楚在一个或多个屏幕大小的纹理中储存所有逐片段数据并在之后光照处理阶段中使用的可行性了。因为G缓冲纹理将会和光照处理阶段中的2D方形一样大,我们会获得和正向渲染设置完全一样的片段数据,但在光照处理阶段这里是一对一映射。

  • 对于每一个片段我们需要储存的数据有:**一个位置向量、一个法向量,一个颜色向量,一个镜面强度值。**所以我们在几何处理阶段中需要渲染场景中所有的对象并储存这些数据分量到G缓冲中。我们可以再次使用多渲染目标(Multiple Render Targets)来在一个渲染处理之内渲染多个颜色缓冲,在之前的泛光教程中我们也简单地提及了它。

  • 对于几何渲染处理阶段,我们首先需要初始化一个帧缓冲对象,我们很直观的称它为gBuffer,它包含了多个颜色缓冲和一个单独的深度渲染缓冲对象(Depth Renderbuffer Object)。对于位置和法向量的纹理,我们希望使用高精度的纹理(每分量16或32位的浮点数),而对于反照率和镜面值,使用默认的纹理(每分量8位浮点数)就够了。

        // configure g-buffer 设置g-buffer
        // ------------------------------
        unsigned int gBuffer;
        glGenFramebuffers(1, &gBuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
            unsigned int gPosition, gNormal, gAlbedoSpec;//生成3个ID
            // position color buffer
            glGenTextures(1, &gPosition);
                glBindTexture(GL_TEXTURE_2D, gPosition);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
            // normal color buffer
            glGenTextures(1, &gNormal);
                glBindTexture(GL_TEXTURE_2D, gNormal);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
            // color + specular color buffer
            glGenTextures(1, &gAlbedoSpec);//将RGBA储存到一起
                glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);
            // tell OpenGL which color attachments we'll use (of this framebuffer) for rendering 
            // 告诉OpenGL这些缓冲中的哪一个颜色附件使我们要用到渲染上的
                unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };//3个附件
                glDrawBuffers(3, attachments);
            // 创建并绑定深度缓冲 (renderbuffer)
                unsigned int rboDepth;
                glGenRenderbuffers(1, &rboDepth);
                glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
                glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
            // finally check if framebuffer is complete
            if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
                std::cout << "Framebuffer not complete!" << std::endl;
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    
    
  • 接下来我们需要渲染它们到G缓冲中。假设每个对象都有漫反射,一个法线和一个镜面强度纹理,我们会想使用一些像下面这个片段着色器的东西来渲染它们到G缓冲中去。

    #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()
    {    
        // 存储第一个G缓冲纹理中的片段位置向量
        gPosition = FragPos;
        // 同样存储对每个逐片段法线到G缓冲中
        gNormal = normalize(Normal);
        // 和漫反射对每个逐片段颜色
        gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb;
        // 存储镜面强度到gAlbedoSpec的alpha分量
        gAlbedoSpec.a = texture(texture_specular1, TexCoords).r;
    }  
    
  • 因为我们使用了多渲染目标,这个布局指示符(Layout Specifier)告诉了OpenGL我们需要渲染到当前的活跃帧缓冲中的哪一个颜色缓冲。注意我们并没有储存镜面强度到一个单独的颜色缓冲纹理中,因为我们可以储存它单独的浮点值到其它颜色缓冲纹理的alpha分量中。

  • 伪代码

    while(...) // 游戏循环
    {
        // 1. 几何处理阶段:渲染所有的几何/颜色数据到G缓冲 
        glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        gBufferShader.Use();
        for(Object obj : Objects)
        {
            ConfigureShaderTransformsAndUniforms();
            obj.Draw();
        }  
        // 2. 光照处理阶段:使用G缓冲计算场景的光照
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        lightingPassShader.Use();
        BindAllGBufferTextures();
        SetLightingUniforms();
        RenderQuad();
    }
    

延迟光照处理阶段

  • 现在我们已经有了一大堆的片段数据储存在G缓冲中供我们处置,我们可以选择通过一个像素一个像素地遍历各个G缓冲纹理,并将储存在它们里面的内容作为光照算法的输入,来完全计算场景最终的光照颜色。由于所有的G缓冲纹理都代表的是最终变换的片段值,我们只需要对每一个像素执行一次昂贵的光照运算就行了。这使得延迟光照非常高效,特别是在需要调用大量重型片段着色器的复杂场景中。

  • 对于这个光照处理阶段,我们将会渲染一个2D全屏的方形(有一点像后期处理效果)并且在每个像素上运行一个昂贵的光照片段着色器。

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    shaderLightingPass.Use();
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, gPosition);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, gNormal);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
    // 同样发送光照相关的uniform
    SendAllLightUniformsToShader(shaderLightingPass);
    glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "viewPos"), 1, &camera.Position[0]);
    RenderQuad(); 
    
  • 整个过程在代码中会是这样的:

    // render loop
    // -----------
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        // --------------------
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
    
        // input
        // -----
        processInput(window);
    
        // render
        // ------
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        // 1. geometry pass: render scene's geometry/color data into gbuffer
        // -----------------------------------------------------------------
        glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
            glm::mat4 view = camera.GetViewMatrix();
            glm::mat4 model = glm::mat4(1.0f);
            shaderGeometryPass.use();
            shaderGeometryPass.setMat4("projection", projection);
            shaderGeometryPass.setMat4("view", view);
            for (unsigned int i = 0; i < objectPositions.size(); i++)
            {
                model = glm::mat4(1.0f);
                model = glm::translate(model, objectPositions[i]);
                model = glm::scale(model, glm::vec3(0.5f));
                shaderGeometryPass.setMat4("model", model);
                backpack.Draw(shaderGeometryPass);
            }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
        // 2. lighting pass: calculate lighting by iterating over a screen filled quad pixel-by-pixel using the gbuffer's content.
        // -----------------------------------------------------------------------------------------------------------------------
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        shaderLightingPass.use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, gPosition);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, gNormal);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
        // send light relevant uniforms
        for (unsigned int i = 0; i < lightPositions.size(); i++)
        {
            shaderLightingPass.setVec3("lights[" + std::to_string(i) + "].Position", lightPositions[i]);
            shaderLightingPass.setVec3("lights[" + std::to_string(i) + "].Color", lightColors[i]);
            // update attenuation parameters and calculate radius
            const GLfloat constant = 1.0; // Note that we don't send this to the shader, we assume it is always 1.0 (in our case)
            const float linear = 0.7;
            const float quadratic = 1.8;
            shaderLightingPass.setFloat("lights[" + std::to_string(i) + "].Linear", linear);
            shaderLightingPass.setFloat("lights[" + std::to_string(i) + "].Quadratic", quadratic);
            // Then calculate radius of light volume/sphere
            const GLfloat maxBrightness = std::fmaxf(std::fmaxf(lightColors[i].r, lightColors[i].g), lightColors[i].b);
            GLfloat radius = (-linear + std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * maxBrightness))) / (2 * quadratic);
            shaderLightingPass.setFloat("lights[" + std::to_string(i) + "].Radius", radius);
        }
        shaderLightingPass.setVec3("viewPos", camera.Position);
        // finally render quad
        renderQuad();
    
    
        // 2.5. 将几何体深度缓冲区的内容复制到默认帧缓冲区的深度缓冲区
        //沿用帧缓冲中的深度值,不然在渲染灯光的时候一样会出现灯光始终在前面
        // ----------------------------------------------------------------------------------
        glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);//读
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 写入默认缓冲区
        //blit设置为默认帧缓冲区。请注意,这可能有效,也可能无效,因为FBO和默认帧缓冲区的内部格式都必须匹配。
        //内部格式由实现定义。这在我所有的系统上都有效,但如果在你的系统上不起作用,你可能不得不向
        //另一个着色器阶段中的深度缓冲区(或以某种方式查看以匹配默认帧缓冲区的内部格式与FBO的内部格式)。
        glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST);   //将像素块从读取的帧缓冲区复制到绘制帧缓冲区
        glBindFramebuffer(GL_FRAMEBUFFER, 0); //使用默认缓冲区帧缓冲区对象
    
    
        // 3. render lights on top of scene
        // --------------------------------
        shaderLightBox.use();
        shaderLightBox.setMat4("projection", projection);
        shaderLightBox.setMat4("view", view);
        for (unsigned int i = 0; i < lightPositions.size(); i++)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, lightPositions[i]);
            model = glm::scale(model, glm::vec3(0.125f));
            shaderLightBox.setMat4("model", model);
            shaderLightBox.setVec3("lightColor", lightColors[i]);
            renderCube();
        }
    
    
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
  • 光照处理阶段的片段着色器和我们之前一直在用的光照教程着色器是非常相似的,除了我们添加了一个新的方法,从而使我们能够获取光照的输入变量,当然这些变量我们会从G缓冲中直接采样。

    #version 330 core
    out vec4 FragColor;
    in vec2 TexCoords;
    
    uniform sampler2D gPosition;
    uniform sampler2D gNormal;
    uniform sampler2D gAlbedoSpec;
    
    struct Light {
        vec3 Position;
        vec3 Color;
    };
    const int NR_LIGHTS = 32;
    uniform Light lights[NR_LIGHTS];
    uniform vec3 viewPos;
    
    void main()
    {             
        // 从G缓冲中获取数据
        vec3 FragPos = texture(gPosition, TexCoords).rgb;
        vec3 Normal = texture(gNormal, TexCoords).rgb;
        vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;
        float Specular = texture(gAlbedoSpec, TexCoords).a;
    
        // 然后和往常一样地计算光照
        vec3 lighting = Albedo * 0.1; // 硬编码环境光照分量
        vec3 viewDir = normalize(viewPos - FragPos);
        for(int i = 0; i < NR_LIGHTS; ++i)
        {
            // 漫反射
            vec3 lightDir = normalize(lights[i].Position - FragPos);
            vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color;
            lighting += diffuse;
        }
    
        FragColor = vec4(lighting, 1.0);
    }  
    

结合延迟渲染与正向渲染

  • 现在我们想要渲染每一个光源为一个3D立方体,并放置在光源的位置上随着延迟渲染器一起发出光源的颜色。很明显,我们需要做的第一件事就是在延迟渲染方形之上正向渲染所有的光源,它会在延迟渲染管线的最后进行。所以我们只需要像正常情况下渲染立方体,只是会在我们完成延迟渲染操作之后进行。

  • 然而,这些渲染出来的立方体并没有考虑到我们储存的延迟渲染器的几何深度(Depth)信息,并且结果是它被渲染在之前渲染过的物体之上,这并不是我们想要的结果。

  • 我们需要做的就是首先复制出在几何渲染阶段中储存的深度信息,并输出到默认的帧缓冲的深度缓冲,然后我们才渲染光立方体。这样之后只有当它在之前渲染过的几何体上方的时候,光立方体的片段才会被渲染出来。我们可以使用glBlitFramebuffer复制一个帧缓冲的内容到另一个帧缓冲中,这个函数我们也在抗锯齿的教程中使用过,用来还原多重采样的帧缓冲。glBlitFramebuffer这个函数允许我们复制一个用户定义的帧缓冲区域到另一个用户定义的帧缓冲区域。

  • 我们储存所有延迟渲染阶段中所有物体的深度信息在gBuffer这个FBO中。如果我们仅仅是简单复制它的深度缓冲内容到默认帧缓冲的深度缓冲中,那么光立方体就会像是场景中所有的几何体都是正向渲染出来的一样渲染出来。就像在抗锯齿教程中介绍的那样,我们需要指定一个帧缓冲为读帧缓冲(Read Framebuffer),并且类似地指定一个帧缓冲为写帧缓冲(Write Framebuffer):

    glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 写入到默认帧缓冲
    glBlitFramebuffer(
      0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST
    );
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    // 现在像之前一样渲染光立方体
    [...]  
    

效果

在这里插入图片描述

更多的光源

  • 延迟渲染一直被称赞的原因就是它能够渲染大量的光源而不消耗大量的性能。然而,延迟渲染它本身并不能支持非常大量的光源,因为我们仍然必须要对场景中每一个光源计算每一个片段的光照分量。真正让大量光源成为可能的是我们能够对延迟渲染管线引用的一个非常棒的优化:光体积(Light Volumes)

  • 通常情况下,当我们渲染一个复杂光照场景下的片段着色器时,我们会计算场景中每一个光源的贡献,不管它们离这个片段有多远。很大一部分的光源根本就不会到达这个片段,所以为什么我们还要浪费这么多光照运算呢?

  • 隐藏在光体积背后的想法就是计算光源的半径,或是体积,也就是光能够到达片段的范围。由于大部分光源都使用了某种形式的衰减(Attenuation),我们可以用它来计算光源能够到达的最大路程,或者说是半径。我们接下来只需要对那些在一个或多个光体积内的片段进行繁重的光照运算就行了。这可以给我们省下来很可观的计算量,因为我们现在只在需要的情况下计算光照。

  • 这个方法的难点基本就是找出一个光源光体积的大小,或者是半径。

计算一个光源的体积或半径

在这里插入图片描述
在这里插入图片描述

  • 它给我们了一个通用公式从而允许我们计算x的值,即光源的光体积半径,只要我们提供了一个常量,线性和二次项参数:它会返回一个大概在1.0到5.0范围内的半径值,它取决于光的最大强度。

    GLfloat constant  = 1.0; 
    GLfloat linear    = 0.7;
    GLfloat quadratic = 1.8;
    GLfloat lightMax  = std::fmaxf(std::fmaxf(lightColor.r, lightColor.g), lightColor.b);
    GLfloat radius    = 
      (-linear +  std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * lightMax))) 
      / (2 * quadratic);  
    
  • 对于场景中每一个光源,我们都计算它的半径,并仅在片段在光源的体积内部时才计算该光源的光照。下面是更新过的光照处理阶段片段着色器,它考虑到了计算出来的光体积。注意这种方法仅仅用作教学目的,在实际场景中是不可行的,我们会在后面讨论它:

    struct Light {
        [...]
        float Radius;
    }; 
    
    void main()
    {
        [...]
        for(int i = 0; i < NR_LIGHTS; ++i)
        {
            // 计算光源和该片段间距离
            float distance = length(lights[i].Position - FragPos);
            if(distance < lights[i].Radius)
            {
                // 执行大开销光照
                [...]
            }
        }   
    }
    
  • 片段着色器

    #version 330 core
    out vec4 FragColor;
    in vec2 TexCoords;
    
    uniform sampler2D gPosition;
    uniform sampler2D gNormal;
    uniform sampler2D gAlbedoSpec;
    
    struct Light {
        vec3 Position;
        vec3 Color;
        
        float Linear;
        float Quadratic;
        float Radius;
    };
    const int NR_LIGHTS = 32;
    uniform Light lights[NR_LIGHTS];
    uniform vec3 viewPos;
    
    void main()
    {             
        // Retrieve data from gbuffer
        vec3 FragPos = texture(gPosition, TexCoords).rgb;
        vec3 Normal = texture(gNormal, TexCoords).rgb;
        vec3 Diffuse = texture(gAlbedoSpec, TexCoords).rgb;
        float Specular = texture(gAlbedoSpec, TexCoords).a;
        
        // Then calculate lighting as usual
        vec3 lighting  = Diffuse * 0.1; // hard-coded ambient component
        vec3 viewDir  = normalize(viewPos - FragPos);
        for(int i = 0; i < NR_LIGHTS; ++i)
        {
            // Calculate distance between light source and current fragment
            float distance = length(lights[i].Position - FragPos);
            if(distance < lights[i].Radius)
            {
                // Diffuse
                vec3 lightDir = normalize(lights[i].Position - FragPos);
                vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * lights[i].Color;
                // Specular
                vec3 halfwayDir = normalize(lightDir + viewDir);  
                float spec = pow(max(dot(Normal, halfwayDir), 0.0), 16.0);
                vec3 specular = lights[i].Color * spec * Specular;
                // Attenuation
                float attenuation = 1.0 / (1.0 + lights[i].Linear * distance + lights[i].Quadratic * distance * distance);
                diffuse *= attenuation;
                specular *= attenuation;
                lighting += diffuse + specular;
            }
        }      
        FragColor = vec4(lighting, 1.0);
    }
    
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 课程简介:本课程详细讲解了OpenGL从入门到精通的理论+实践知识,对于每一个知识点都会带领学员通过代码来实现功能。其中涵盖了基础图元绘制,基础光照高级过程,高级光照等内容;当前图形引擎的应用已经越来越广泛,春晚以及各大综艺节目已经开始使用XR作为主流的内容制作技术,房地产漫游及Web渲染技术已经开始茁壮发展,VR也即将突破硬件瓶颈;普遍的游戏引擎在独特的领域已经无法完全实用,且我们国家要发展自主科技技术,图形引擎以及CAD等卡脖子技术一定会蓬勃发展,所以同学们要抓住机会,趁势而上,熟悉底层,博取更大发展,学习OpenGL底层接口的应用以及图形学算法,将是您向纵深发展的第一步!2 课程解决优势:很多同学学习OpenGL最难的是找到路径,并且其中牵扯到的理论知识点无法完全理解透彻(比如VAO与VBO的区别,MVP矩阵变换的推导及原理,光照系统的设计及算法推导,帧缓存的灵活应用等),我们的课程可以带领大家从原理+实践的角度进行学习,每一个知识点都会:a 推导基础公式及原理 b 一行一行进行代码实践从而能够保证每位同学都学有所得,能够看得懂,学得会,用得上,并且能够培养自主研究的能力。学习课程所得:学习本课程完毕之后,学员可以全方位的完全了解OpenGL当中的必要接口,并且可以对图形学的基础知识融会贯通,可以制作中级的特效。并且对于UnrealEngine以及Unity3D的学习更加轻松,对于各类商业引擎当中的算法以及内容制作手法更加深刻理解把控。学员也可以自行进行图形引擎的设计以及研究,并且将本课程的知识点进行代码模块化编写;能够自主推导图形学管线以及应用当中的各类公式,并且理解其几何含义。 代码与PPT资源,已随课程附赠,请同学们对应课程下载 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值