概念引入
在前向渲染中,如果场景中有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();
}