[OpenGL] 延迟渲染 - 多个点光源

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZJU_fish1996/article/details/86564295

概念引入

       在前向渲染中,如果场景中有N个物体,M个光源,那么我们就需要对于每个光源下,每个物体逐一绘制,这也就意味着我们需要绘制N * M次。这意味着光照计算除了会随着光的数量增加而增加复杂度,还会随着场景复杂度而变得更加耗时。因此,在使用前向渲染的游戏中,往往会小心翼翼地控制场景中的光源数量,来保证游戏帧率。

        可以看出,由于我们需要对每个物体进行处理,而有些处理的像素颜色实际上最终并没有显示到屏幕上,这中间存在着一些计算上的浪费。为了减少这一开销,我们引入了延迟渲染的概念,也就是说,把必要的信息先处理成屏幕空间的数据,将其写入多张纹理,我们称为G-Buffer。此时,不管场景中有多少物体,最终都只有一个屏幕。然后,在屏幕空间进行光照计算,这意味着我们只需要对M个光源进行计算,复杂度下降到了O(M)。(再算上写入G-Buffer的复杂度,为O(N+M))

        当然,延迟渲染由于使用了多重渲染对象,需要写入G-Buffer,这一过程也是比较耗的。此外,由于我们需要在写入G-Buffer的时候统一指定我们写入的数据,比如法线、深度、粗糙度、透明度等信息,这也就意味着我们在屏幕空间处理的时候,只能使用我们设计好的这些数据,按照特定的方式进行渲染,如果我们想对少量特定物体做特殊的材质,这是一件比较麻烦的事情。

        因此,我们在什么情况下适合选择延迟渲染呢?以下是我个人的一些理解,延迟渲染的优点在于花费比较少的代价,就能获得前向渲染需要花更多代价的效果。所以,当我们希望做出接近3A游戏的效果时,延迟渲染是一个不错的选择。但如果我们的游戏运行在手机等移动平台,或者不需要那么好的效果,那么使用前向渲染架构就足够了。

渲染的流程

        我们先来看一下传统前向渲染的基本流程:我们对场景中的每个物体(或者相同材质批处理后的物体组)都需要进行一次drawcall。在顶点着色器中,我们实际上做的事情是:确定物体的顶点的最终变换坐标。因此,在这个过程中,我们需要完成模型到世界,世界到相机,相机到裁剪空间的变换,并给每个顶点附加一些必要的信息,比如,该顶点对应的纹理坐标。

        在经过一系列变换后,(OpenGL中)x,y,z的坐标都被映射到了[-1,1]的范围中。此时,我们已经把视野外或远近裁剪面后的片元剔除了,它们不再会进入片元的计算流程。

         之后,我们进行光栅化的过程,简而言之,就是根据顶点的数据,决定哪些像素是属于这个三角形面片中,此时我们会自上而下,从左到右进行位置、纹理坐标等顶点数据的硬件插值。处理结束后,我们开始做逐片元的操作,首先要通过深度、模板等测试,之后,再来决定每个像素最终要显示什么颜色。

        我自己猜测了一下,可能会出现以下情况:我们先绘制了一个三角形面片A,由于此时还没有绘制物体,它通过了深度测试,然后做逐片元的操作;之后,我们绘制面片B,B在A之前,它也通过了深度测试,我们又为B做了逐片元操作。在合并阶段,此时我们没有开启混合,所以就直接将B的运算结果写入了颜色缓冲区。

        这里就有这么一个问题,A的逐片元计算被完全浪费了。这里的问题在于,我们只知道当前和在这之前处理的顶点数据,不知道之后还会来什么数据,所以我们无法完全确定哪些像素是无需计算绘制的。延迟渲染也就相当于,先遍历一遍所有物体,将这些必要信息记录下来,再进行一次渲染,就会避免很多不必要的计算,它实际上是一种渲染架构的设计思想。

效果演示

(开发平台,Qt+OpenGL)

        为了掌握延迟渲染的基本流程,我做了一个比较简单的demo,也就是一个多点光源的场景。后续可能考虑引入的是,阴影的加入(由于我的阴影在前向渲染单光源下都还有一点问题,所以暂时就不折腾延迟渲染了)、支持PBR材质的G-Buffer数据设计。

       场景中一共渲染了10个点光源,对于每个光源,会随机生成一个颜色和位置,并且可以在运行时调节光源位置和光源衰减参数。场景中还有一个小灯泡图标,用于显示当前灯光的具体位置,它是一个billboard。

       在这里,G-Buffer仅使用了一张纹理来存储必要的数据,传递的数据包括了法线和深度。当然,这个demo中,还可以传递漫反射强度、高光参数等数据,来指定每个物体的不同参数,这将耗费更多的纹理。

       对于法线和深度而言,用4个分量存储已经足够了,所以就没有必要对法线进行压缩了。其中,深度使用的是透视变换后的非线性深度,所以实际上是可以直接读取硬件生成的深度缓存的。

点光源衰减公式

       我们认为点光源会随着距离衰减,假设点光源处于点P位置,原有强度为C0,那么它在Q处的光强C为:

        

       其中,kc,kl,kq都是系数,d代表P和Q之间的距离。

先了解一些投影变换的细节

        我使用的是透视变换后的深度,它会有一个问题,存储的深度是非线性的,在近处深度精度高、远处精度低。由下图可见,我一把镜头拉远一点,就能看到在z轴方向出现了明显的条纹状,这就是远处深度精度不够的表现。

       

       如果我们想要存储线性深度,可以在顶点着色器中,记录视图变换后的深度,然后传递到片元着色器,再除以远裁剪面的值以把数据压缩到0~1之间,使之可以存储在纹理里。注意,此处要取绝对值,因为在OpenGL的视图空间下,相机前可见的物体的深度为负数。

        关于透视投影之后深度为什么是非线性的,有一个比较直白的答案:经过透视变换后,得到的z值是是a + b/z形式的,那么它显然是一个非线性的值。

        我又特地看了透视投影的推导过程,实际上,我们是把那个像梯台一样的视锥体里的东西,都映射到了近裁剪面上。也就是说,透视变换后的z坐标实际上应该是一个定值,即zNear,但这是一个无效信息,所以我们可以不存储它。我们为了方便硬件进行深度测试等操作,可以在此处存储深度信息。

        绝大多数的推导透视变换矩阵的流程,都是基于z是a + b/z形式的这一结论进行推导的。但为什么要特地处理成这一形式呢?我在《Mathematics for 3D Game Programming and Computer Graphics》一书中找到了答案,如果我们处理成线性深度,将不利于硬件执行相关的操作:

       在投影平面上等间距采样时,随着与相机距离增加,在三角形面上步长会越大。因此,沿三角形面的插值不是线性插值,这将会导致纹理贴图的扭曲。

根据深度纹理来重建世界坐标

       现在回到正题,我们得到了深度后,需要根据深度来还原世界坐标,否则我们无法做最基本的光照计算。那么,比较简单的想法就是,我们只需要做透视、视图变换的逆变换就可以了。现在,我们已经有了z值信息,那么,x,y的值从何而来呢?

       这需要我们对透视投影的过程有一定了解。我们已经清楚,经过透视投影后,坐标投影到近裁剪面上,x,y坐标的取值范围为[-1,1]。我们实际上已经完成了降维的操作,最后得到的结果可以和屏幕上每个像素建立线性的映射关系。

       在后处理的过程中,我们将所有数据绘制到一个铺满屏幕的面片上,此时该面片的uv坐标如下分布。

        

     可以看出,我们直接将uv坐标映射到[-1,1]范围内,就能得到透视变换后的结果:

        pos.x = v_texcoord.x * 2 - 1;
        pos.y = v_texcoord.y * 2 - 1;

部分代码和实现思路

        首先,我们需要将必要的数据写入G-Buffer,以下是写入部分: 

        gbuffer.vsh

        这里随便说一点,我们之所以可以将法线所乘的那个逆矩阵直接压缩为mat3x3,是因为在齐次坐标的表达下,法线是一个向量,所以第四维的数值为0,因此矩阵多出的一维计算是无效的。

#version 450 core
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif

uniform mat4 ModelMatrix;
uniform mat4 IT_ModelMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ProjectMatrix;

attribute vec4 a_position;
attribute vec3 a_normal;

varying vec3 v_normal;
varying vec2 v_depth;

void main()
{
    gl_Position = ModelMatrix * a_position;
    gl_Position = ViewMatrix * gl_Position;
    gl_Position = ProjectMatrix * gl_Position;

    v_depth = gl_Position.zw;
    mat3 M = mat3(IT_ModelMatrix[0].xyz, IT_ModelMatrix[1].xyz, IT_ModelMatrix[2].xyz);
    v_normal = M * a_normal;
}

       gbuffer.fsh

       此处,由于深度和法线分布在[-1,1]之间,所以都需要特殊处理。

#version 450 core

uniform float zFar;

varying vec3 v_normal;
varying vec2 v_depth;
layout(location = 0) out vec4 NormalAndDepth;

void main(void)
{
    NormalAndDepth.xyz = (normalize(v_normal) + 1)/2;
    NormalAndDepth.w = (v_depth.x/v_depth.y + 1)/2;
}

     接下来,是进行光照渲染的部分:

      default.vsh

      顶点着色器中并没有什么特别要做的,因为绘制的是面片,所以也不需要经过视图、透视之类的变换了。

#version 450 core
attribute vec4 a_position;
attribute vec2 a_texcoord;

varying vec2 v_texcoord;

void main()
{
    gl_Position = a_position;
    v_texcoord = a_texcoord;
}

     default.fsh

      光照计算部分。首先,由于我没有找到向shader中传入结构数组的方法,所以传入了多个数组。我也不清楚shader中是否支持可变长数组,感觉大概率是不支持的,所以传入的是定长数组。如果项目中涉及到动态创建销毁的光源,我的想法是先预留一个特定长度的数组,即渲染引擎只能支持这个数量以下的光源,多余的会直接被舍弃。

       在下面,首先通过乘以变换矩阵的逆,得到世界空间的坐标worldPos,然后,遍历场景中的10个光源,计算衰减系数,然后计算该光源产生的漫反射和高光强度,并乘以光源颜色,得到最终该光源对物体的颜色贡献。将这10个光源的颜色贡献叠加,再加上一个比较暗淡的环境光,得到最终的物体表面颜色。为了避免亮度过高,可以将每个光源调暗一点。

#version 450 core

in vec2 v_texcoord;

uniform vec3 cameraPos;
uniform sampler2D NormalAndDepth;
uniform mat4 IT_ViewProjMatrix;
uniform vec3 lightPos[10];

uniform float Kl[10];
uniform float Kc[10];
uniform float Kq[10];

uniform vec3 lightColor[10];

void main(void)
{
    vec4 result = texture2D(NormalAndDepth, v_texcoord);

    if(result != vec4(1,1,1,1))
    {
        vec3 normal = result.xyz * 2 - 1;
        float depth = result.w;

        vec4 pos;
        pos.w = 1;
        pos.z = depth * 2 - 1;
        pos.x = v_texcoord.x * 2 - 1;
        pos.y = v_texcoord.y * 2 - 1;

        vec4 ret = IT_ViewProjMatrix * pos;
        vec3 worldPos = ret.xyz/ret.w;
        vec3 color;
        vec3 result;
        for(int i = 0; i < 10; i++)
        {
            vec3 dis = lightPos[i] - worldPos;
            float dis2 = dis.x * dis.x + dis.y * dis.y + dis.z * dis.z;

            float attenuation = 1.0 / (Kc[i] + Kl[i] * sqrt(dis2) + Kq[i] * dis2);

            vec3 ViewDir = normalize( cameraPos - worldPos );
            vec3 lightDir = normalize( lightPos[i] - worldPos );
            vec3 halfDir = normalize(lightDir + ViewDir);
            float diffuse = 0.1 * clamp(dot(normal, lightDir), 0, 1) * attenuation;
            vec3 reflectDir = normalize(reflect(-lightDir,normal));
            float specular =  0.1 * pow(clamp(dot(reflectDir,halfDir),0,1),50.0) * attenuation;
            color = (diffuse + specular) * lightColor[i];

            result += color;
        }
        float ambient = 0.2;
        gl_FragColor = vec4(result + ambient * vec3(1,1,1),1);
    }
    else
    {
        gl_FragColor = vec4(1,1,1,1);
    }

}

       以下是灯光图标的绘制代码:

       首先灯光图标我依然是按前向渲染的思路绘制的,为了保证它始终面向相机,所以在变换矩阵上多乘了一个相机旋转的逆矩阵。由于延迟渲染绘制的是一个面片,所以它的深度是按照面片深度来的,而非原有深度,这会导致我们的图标在延迟渲染时被遮挡,所以需要在顶点着色器中,手动把z值设为比面片更靠前的值(在这里,我们的面片深度为0,这里就把图标的裁剪后深度强制设为-0.5)。

       icon.vsh

uniform mat4 TransformMat;
attribute vec4 a_position;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;
void main(void)
{
    gl_Position = TransformMat * a_position;
    gl_Position.z = gl_Position.w * -0.5;
    v_texcoord = a_texcoord;
}

        icon.fsh

uniform sampler2D LightTex;
varying vec2 v_texcoord;

void main(void)
{
    vec4 color = texture2D(LightTex, v_texcoord);
    if(color.w < 0.1)
        discard;
    gl_FragColor = color;
}

     然后是c++部分,渲染调用的代码,此处是对象的基类和对象管理类。由于延迟渲染是针对所有物体的,所以就把延迟渲染的drawcall调用先写在了对象管理类里:

     object.h/object.cpp

#ifndef COBJECT_H
#define COBJECT_H

#include <vector>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include "rendercommon.h"
#include "reflect.h"
using namespace std;

class Object : protected QOpenGLExtraFunctions
{
protected:
    QOpenGLShaderProgram program;

    QMatrix4x4           modelMatrix;
    QMatrix4x4           IT_modelMatrix;
public:

    QVector3D            position = QVector3D(0,0,0);
    Shape                shape;

    virtual void         UpdateLocation() = 0;
    virtual void         Create() = 0;
    virtual void         Render() { }
    virtual void         LateRender() { }
    virtual void         Draw();
    virtual              ~Object() { }
};

class ObjectInfo : protected QOpenGLFunctions
{
private:
    QOpenGLShaderProgram delayProgram;
    vector<Object*>      vecObj;

    static ObjectInfo* objInfo;
public:
    static ObjectInfo* Inst() {
        if(!objInfo) {
            objInfo = new ObjectInfo();
        }
        return objInfo;
    }

    void                 Create();
    void                 Render();
    Object*              CreateObject(string name,QVector3D pos = QVector3D(0,0,0));
    void                 DelayRender();
};

#endif // COBJECT_H
#include "object.h"
#include "ground.h"
#include "skybox.h"
#include "plane.h"
#include "rendercommon.h"
#include "hatch.h"
#include "water.h"
#include "cartoon.h"
#include "phong.h"
#include "cameramanager.h"

ObjectInfo* ObjectInfo::objInfo = nullptr;

void Object::Draw()
{
    switch(shape)
    {
    case SHA_Cube:
        RenderCommon::Inst()->GetGeometryEngine()->drawCube(&program);
        break;
    case SHA_Sphere:
        RenderCommon::Inst()->GetGeometryEngine()->drawSphere(&program);
        break;
    case SHA_Plane:
        RenderCommon::Inst()->GetGeometryEngine()->drawPlane(&program);
        break;
    default:
        RenderCommon::Inst()->GetGeometryEngine()->drawPlane(&program);
    }
}

void ObjectInfo::Create()
{
    initializeOpenGLFunctions();

    QOpenGLShader* vShader = new QOpenGLShader(QOpenGLShader::Vertex);
    vShader->compileSourceFile(":/default.vsh");

    QOpenGLShader* fShader = new QOpenGLShader(QOpenGLShader::Fragment);
    fShader->compileSourceFile(":/default.fsh");

    delayProgram.addShader(vShader);
    delayProgram.addShader(fShader);
    delayProgram.link();

    vector<string> name =
    {
        "Phong",
        "Phong",
        "Ground",
        "Phong",
    };
    QVector3D pos[] =
    {
        QVector3D(0,1,0),
        QVector3D(1,1,3),
        QVector3D(0,0,0),
        QVector3D(-2,1,-2),
    };


    for (int i = 0; i < name.size(); i++)
    {
        CreateObject(name[i],pos[i]);
    }
}

Object* ObjectInfo::CreateObject(string name, QVector3D pos)
{
    Object* obj = Helper::Inst()->CreateObject(name);
    if (obj)
    {
        obj->Create();
        obj->position = pos;
        vecObj.push_back(obj);
    }
    return obj;
}

void ObjectInfo::Render()
{
    // 更新位置
    glClearColor(1,1,1,1);
    for(size_t i = 0;i < vecObj.size(); i++)
    {
        vecObj[i]->UpdateLocation();
    }

    // 生成延迟渲染所需数据阶段
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, RenderCommon::Inst()->GetGBuffer());
    glClearColor(1,1,1,1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glCullFace(GL_BACK);

    for(size_t i = 0;i < vecObj.size(); i++)
    {
        vecObj[i]->Render();
        vecObj[i]->Draw();
    }

    // 延迟渲染
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    DelayRender();

    // 混入前向渲染,用于处理一些特殊数据(这里是灯光图标)
    for(size_t i = 0;i < vecObj.size(); i++)
    {
        vecObj[i]->LateRender();
    }
}

void ObjectInfo::DelayRender()
{
    delayProgram.bind();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, RenderCommon::Inst()->GetNormalAndDepthTex());
    delayProgram.setUniformValue("NormalAndDetph", 0);

    delayProgram.setUniformValue("ProjectMatrix",RenderCommon::Inst()->GetProjectMatrix());
    delayProgram.setUniformValue("ViewMatrix", Camera::Inst()->GetViewMatrix());


    QMatrix4x4 IT_ViewProjMatrix = RenderCommon::Inst()->GetProjectMatrix() * Camera::Inst()->GetViewMatrix();
    IT_ViewProjMatrix = IT_ViewProjMatrix.inverted();
    delayProgram.setUniformValue("IT_ViewProjMatrix", IT_ViewProjMatrix);


    delayProgram.setUniformValue("cameraPos",Camera::Inst()->GetCameraPos());

    {
        int location = delayProgram.uniformLocation("lightPos");
        QVector3D lightPos[10];
        for(int i = 0;i < 10;i++)
        {
            lightPos[i] = RenderCommon::Inst()->GetLightInfo()->vecLight[i].lightPos;
        }
        delayProgram.setUniformValueArray(location,lightPos,10);
    }

    {
        int location = delayProgram.uniformLocation("Kl");
        float Kl[10];
        for(int i = 0;i < 10;i++)
        {
            Kl[i] = RenderCommon::Inst()->GetLightInfo()->vecLight[i].Kl;
        }
        delayProgram.setUniformValueArray(location,Kl,10,1);
    }

    {
        int location = delayProgram.uniformLocation("Kq");
        float Kq[10];
        for(int i = 0;i < 10;i++)
        {
            Kq[i] = RenderCommon::Inst()->GetLightInfo()->vecLight[i].Kq;
        }
        delayProgram.setUniformValueArray(location,Kq,10,1);
    }

    {
        int location = delayProgram.uniformLocation("Kc");
        float Kc[10];
        for(int i = 0;i < 10;i++)
        {
            Kc[i] = RenderCommon::Inst()->GetLightInfo()->vecLight[i].Kc;
        }
        delayProgram.setUniformValueArray(location,Kc,10,1);
    }

    {
        int location = delayProgram.uniformLocation("lightColor");
        QVector3D lightColor[10];
        for(int i = 0;i < 10;i++)
        {
            lightColor[i] = RenderCommon::Inst()->GetLightInfo()->vecLight[i].lightColor;
        }
        delayProgram.setUniformValueArray(location,lightColor,10);
    }

    delayProgram.setUniformValue("zFar", RenderCommon::Inst()->GetZFarPlane());
    delayProgram.setUniformValue("zNear", RenderCommon::Inst()->GetZNearPlane());
    delayProgram.setUniformValue("ScreenX", RenderCommon::Inst()->GetScreenX());
    delayProgram.setUniformValue("ScreenY", RenderCommon::Inst()->GetScreenY());
    delayProgram.setUniformValue("fov", RenderCommon::Inst()->GetFov());
    delayProgram.setUniformValue("centerPos",Camera::Inst()->GetCenterPos());

    RenderCommon::Inst()->GetGeometryEngine()->drawPlane(&delayProgram);


}

       继承的一个对象Phong的代码示例:

        phong.h/phong.cpp

#ifndef PHONG_H
#define PHONG_H

#include "object.h"

class Phong : public Object
{
public:
    void Create() override;
    void Render() override;
    void UpdateLocation() override;
};

#endif // PHONG_H
#include "phong.h"
#include "cameramanager.h"

REGISTER(Phong)
void Phong::Create()
{
    initializeOpenGLFunctions();
    shape = SHA_Sphere;

    QOpenGLShader* vShader = new QOpenGLShader(QOpenGLShader::Vertex);
    vShader->compileSourceFile(":/gbuffer.vsh");

    QOpenGLShader* fShader = new QOpenGLShader(QOpenGLShader::Fragment);
    fShader->compileSourceFile(":/gbuffer.fsh");

    program.addShader(vShader);
    program.addShader(fShader);
    program.link();
}

void Phong::Render()
{
    program.bind();

    program.setUniformValue("ModelMatrix",modelMatrix);
    program.setUniformValue("IT_ModelMatrix",IT_modelMatrix);
    program.setUniformValue("ProjectMatrix",RenderCommon::Inst()->GetProjectMatrix());
    program.setUniformValue("ViewMatrix", Camera::Inst()->GetViewMatrix());
    program.setUniformValue("IT_ViewMatrix", Camera::Inst()->GetViewMatrix().inverted());
    program.setUniformValue("zFar", RenderCommon::Inst()->GetZFarPlane());

}

void Phong::UpdateLocation()
{
    modelMatrix.setToIdentity();
    modelMatrix.translate(position);

    IT_modelMatrix = modelMatrix;
    IT_modelMatrix = IT_modelMatrix.transposed();
    IT_modelMatrix = IT_modelMatrix.inverted();
}

     灯光数据的部分。

      light.h/light.cpp

#ifndef LIGHT_H
#define LIGHT_H

#include <QVector3D>
#include <QColor>
#include <vector>
#include <QOpenGLFunctions>
using namespace std;

struct SSpotLight
{
    QVector3D  lightPos;
    QVector3D  lightColor;
    float Kc;
    float Kl;
    float Kq;

    float GetAttenuation(int i)
    {
        switch(i)
        {
        case 0:return Kc;
        case 1:return Kl;
        case 2:return Kq;
        }
        return -1.0f;
    }

    float GetPos(int i)
    {
        switch(i)
        {
        case 0:return lightPos.x();
        case 1:return lightPos.y();
        case 2:return lightPos.z();
        }
        return -1.0f;
    }

    void SetAttenuation(int i, float data)
    {
        switch(i)
        {
        case 0:Kc = data;break;
        case 1:Kl = data;break;
        case 2:Kq = data;break;
        }
    }

    void SetPos(int i, float data)
    {
        switch(i)
        {
        case 0:lightPos.setX(data);break;
        case 1:lightPos.setY(data);break;
        case 2:lightPos.setZ(data);break;
        }
    }
};

class CLightInfo
{
private:
    enum { LightNum = 10 };

public:
    vector<SSpotLight> vecLight;
    CLightInfo();

};

#endif // LIGHT_H
#include "light.h"

CLightInfo::CLightInfo()
{

    vecLight.resize(LightNum);
    for(size_t i = 0;i < LightNum; i++)
    {
        float y = rand() % 10;
        float x = rand() % 20 - 10;
        float z = rand() % 20 - 10;

        if(y < 3) y = 3;
        vecLight[i].lightPos.setY(y);
        vecLight[i].lightPos.setX(x);
        vecLight[i].lightPos.setZ(z);

        float r = (float)(rand() % 100) / 100;
        float g = (float)(rand() % 100) / 100;
        float b = (float)(rand() % 100) / 100;

        vecLight[i].lightColor.setX(r);
        vecLight[i].lightColor.setY(g);
        vecLight[i].lightColor.setZ(b);

        vecLight[i].Kc = 0.01;
        vecLight[i].Kl = 0.01;
        vecLight[i].Kq = 0.01;
    }
}

         交互工具部分:

         toolwidget.h/toolwidget.cpp

#ifndef TOOLWIDGET_H
#define TOOLWIDGET_H

#include <QWidget>
#include <QHBoxLayout>

class ToolWidget : public QWidget
{
    Q_OBJECT
private:
    int m_activeId = 0;
    class OpenGLWidget* openglWidget;
public:
    ToolWidget();
    class QSlider* m_slider1[3];
    class QLabel* m_label1[3];
    class QSlider* m_slider2[3];
    class QLabel* m_label2[3];
    class Icon* icon = nullptr;
public slots:
    void InitParam();
    void OnScroll1(int value);
    void OnScroll2(int value);
    void OnSelectLight(int index);
};

#endif // TOOLWIDGET_H
#include "toolwidget.h"
#include "rendercommon.h"
#include "mainwidget.h"
#include "object.h"
#include "icon.h"
#include <QWidget>
#include <QLineEdit>
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QSignalMapper>
#include <QComboBox>

ToolWidget::ToolWidget()
{
    setMinimumSize(QSize(640,480));
    QHBoxLayout* hlayout = new QHBoxLayout();
    QVBoxLayout* vlayout = new QVBoxLayout();
    openglWidget = new OpenGLWidget();

    QComboBox* combo = new QComboBox();
    for(int i=0;i<10;i++)
    {
        combo->addItem(QString::number(i));
    }
    QLabel* label0 = new QLabel("当前光源");
    QLabel* label1 = new QLabel("光源衰减参数");

    vlayout->addWidget(label0);
    vlayout->addWidget(combo);
    vlayout->addWidget(label1);
    hlayout->addWidget(openglWidget,5);
    QString name1[] =
    {
        "Kc","Kl","Kq",
    };
    QString name2[] =
    {
        "x","y","z",
    };

    for(int i = 0;i < 3;i++)
    {
        QHBoxLayout* hlayout1 = new QHBoxLayout();
        QLabel* label = new QLabel(name1[i]);
        m_slider1[i] = new QSlider();
        m_slider1[i]->setOrientation(Qt::Horizontal);
        m_label1[i] = new QLabel();
        hlayout1->addWidget(label,1);
        hlayout1->addWidget(m_slider1[i],2);
        hlayout1->addWidget(m_label1[i],1);
        vlayout->addLayout(hlayout1);

        connect(m_slider1[i],SIGNAL(valueChanged(int)),this,SLOT(OnScroll1(int)));
    }

    QLabel* label2 = new QLabel("光源位置");
    vlayout->addWidget(label2);

    for(int i = 0;i < 3;i++)
    {
        QHBoxLayout* hlayout1 = new QHBoxLayout();
        QLabel* label = new QLabel(name2[i]);
        m_slider2[i] = new QSlider();
        m_slider2[i]->setOrientation(Qt::Horizontal);
        m_label2[i] = new QLabel();
        hlayout1->addWidget(label,1);
        hlayout1->addWidget(m_slider2[i],2);
        hlayout1->addWidget(m_label2[i],1);
        vlayout->addLayout(hlayout1);

        connect(m_slider2[i],SIGNAL(valueChanged(int)),this,SLOT(OnScroll2(int)));
    }

    vlayout->addSpacing( (int)(height() * 0.8));

    hlayout->addLayout(vlayout,1);

    setLayout(hlayout);
    setFocusPolicy(Qt::FocusPolicy::ClickFocus);

    connect(openglWidget,SIGNAL(OnCreate()),this,SLOT(InitParam()));
    connect(combo,SIGNAL(currentIndexChanged(int)),this,SLOT(OnSelectLight(int)));
}

void ToolWidget::OnScroll1(int value)
{
    QSlider* slider = qobject_cast <QSlider*>(sender());
    for(int i = 0;i < 3;i++)
    {
        if(slider == m_slider1[i])
        {
            float data = (float)value / 100;
            m_label1[i]->setText(QString::number((double)data));
            RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId].SetAttenuation(i,data);
            break;
        }
    }
}

void ToolWidget::OnScroll2(int value)
{
    QSlider* slider = qobject_cast <QSlider*>(sender());
    for(int i = 0;i < 3;i++)
    {
        if(slider == m_slider2[i])
        {
            float data;
            if(i == 1)
            {
                data = (float)value / 10;
            }
            else
            {
                data = (float)value / 5 - 10;
            }

            m_label2[i]->setText(QString::number((double)data));
            RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId].SetPos(i,data);
            break;
        }
    }

    if(icon)
    {
        SSpotLight& spotLight = RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId];
        icon->position.setX(spotLight.lightPos.x());
        icon->position.setY(spotLight.lightPos.y());
        icon->position.setZ(spotLight.lightPos.z());
    }
}

void ToolWidget::InitParam()
{
    for(int i = 0;i < 3;i++)
    {
        float num = RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId].GetAttenuation(i);
        m_slider1[i]->setValue(num * 100);
        m_label1[i]->setText(QString::number((double)num));
    }
    for(int i = 0;i < 3;i++)
    {
        float num = RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId].GetPos(i);
        if(i == 1)
        {
            m_slider2[i]->setValue(num * 10);
        }
        else
        {
            m_slider2[i]->setValue((num + 10) * 5);
        }
        m_label2[i]->setText(QString::number((double)num));
    }
    if(!icon)
    {
        icon = static_cast<Icon*>(ObjectInfo::Inst()->CreateObject("Icon"));
        if(icon)
        {
            SSpotLight& spotLight = RenderCommon::Inst()->GetLightInfo()->vecLight[m_activeId];
            icon->position.setX(spotLight.lightPos.x());
            icon->position.setY(spotLight.lightPos.y());
            icon->position.setZ(spotLight.lightPos.z());
        }
    }
}

void ToolWidget::OnSelectLight(int index)
{
    m_activeId = index;
    InitParam();
}

猜你喜欢

转载自blog.csdn.net/ZJU_fish1996/article/details/86564295