OpenGL进阶(十九) - 多光源

从光说起

先看一段wiki中对光的定义

光是一种人类眼睛可以见的电磁波(可见光谱),视知觉就是对于光的知觉[1]。光只是电磁波谱上的某一段频谱,一般是定义为波长介于400至700纳米(nm)之间的电磁波,也就是波长比紫外线长,比红外线短的电磁波。有些资料来源定义的可见光的波长范围也有不同,较窄的有介于420至680纳米,较宽的有介于380至800纳米。

光既是一种高频的电磁波,又是一种由称为光子的基本粒子组成的粒子流。因此光同时具有粒子性与波动性,或者说光具有“波粒二象性”。

按波动理论来解释,不同颜色的光就是不同波长的电磁波。

光的衰减(Attenuation)

任何点光源的光照亮度随着距离增加会迅速衰减。这个就叫光的衰减。

不同波长的光有不同的衰减方式,还有就是灯光所处的环境,比如是否有雾霾,下面是常见的几种衰减模型。

这里实现一个衰减的类,作为灯光的成员

#pragma once
class Attenuation
{
public:
	Attenuation(float range, float constant, float linear, float quadratic) :
		m_range(range),
		m_constant(constant),
		m_linear(linear),
		m_quadratic(quadratic) {}

	inline float getRange() const { return m_range; }
	inline float getConstant() const { return m_constant; }
	inline float getLinear()   const { return m_linear; }
	inline float getQuadratic() const { return m_quadratic; }

private:
	float m_constant;
	float m_linear;
	float m_quadratic;
	float m_range;
};

Attenuation类有4个成员后面三项是常量项,线性项还有二次项,最后的衰减率的计算是由下面的公式确定的,Distance表示光源到点的距离。

attenuation = Constant + Linear * Distance + Quadratic * Distance ^ 2

第一个成员表示光源照亮的范围,下面有一个表可以用来查询四者之间的关系

当你选定了一个Rang的时候,你就可以找到对应的constant,linear 和 quadratic。

Constant 越趋近于0,灯光就越亮,反之越暗。

Linear越大,灯光衰减得就越快。不建议改变Quadratic值或者减少Linear的值,这样做需要重新计算Range.

减少Range值可以提升渲染的速度,但是减少得太多,在游戏中可能会造成灯光效果的突变。

点光源

首先创建一个BaseLight类,作为各种灯光的基类

#pragma once
#include "common.h"

class BaseLight
{
public:
	BaseLight(const glm::vec3& color, const glm::vec3& pos,  float intensity) :
		m_color(color),
		m_pos(pos),
		m_intensity(intensity) {}

	inline glm::vec3 getPos() const  {return m_pos;}
	inline float getIntensity() { return m_intensity; }
	inline glm::vec3 getColor() { return m_color; }

private:
	glm::vec3    m_color;
	glm::vec3 m_pos;
	float      m_intensity;
};

注意,很多教程上光的属性有ambient,diffuse,specular之类,按照前面的原理,这都是不科学的,包括材质的Ambient,其实环境光应该是一个全局常量,所以材质也只能diffuse, specular. 插一段StackOverflow上的回答。

这里基础 灯光只有三个成员,颜色,位置,强度。

点光源的类

#pragma once
#include "baselight.h"
#include "attenuation.h"
class PointLight :public BaseLight
{
public:
public:
	PointLight(const glm::vec3& color = glm::vec3(0, 0, 0), const glm::vec3& pos = glm::vec3(0, 0, 0), const float intensity = 1.0, const Attenuation& atten = Attenuation()):
	BaseLight(color,pos,intensity),m_attenuation(atten){}

	inline const Attenuation& getAttenuation() const { return m_attenuation; }

private:
	Attenuation m_attenuation;
};

灯光的初始化,So easy.

	pointLight = new PointLight(glm::vec3(0, 1, 0), glm::vec3(3, 3, 3), 1.8, Attenuation(20, 1.0, 0.22, 0.20));

给shader传参数

	prog.setUniform("pointLight.pos", pointLight->getPos());
	prog.setUniform("pointLight.color", pointLight->getColor());
	prog.setUniform("pointLight.intensity", pointLight->getIntensity());
	prog.setUniform("pointLight.constant", pointLight->getAttenuation().getConstant());
	prog.setUniform("pointLight.linear", pointLight->getAttenuation().getLinear());
	prog.setUniform("pointLight.quadratic", pointLight->getAttenuation().getQuadratic());
	prog.setUniform("pointLight.range", pointLight->getAttenuation().getRange());

接下来就是shader了

vertex shader 就是传个值。

#version 400
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec2 VertexUv;
layout (location = 2) in vec3 VertexNormal;  

uniform mat4 MVP;

out vec3 position;
out vec3 normal;  

void main()
{
	normal = VertexNormal;
	position =  VertexPosition;
	gl_Position = MVP * vec4( VertexPosition, 1.0);
}

fragment shader也就是三板斧,ambient,diffuse,specular,具体计算看Code

#version 400

struct PointLight
{
	float range;
	vec3 pos;
	vec3 color;
	float intensity;
	float constant;
    float linear;
    float quadratic;
};

struct MaterialInfo{
    vec3 diffuse;
    vec3 specular;
    float shininess;
};
uniform vec3 ambient;
uniform PointLight pointLight;
uniform MaterialInfo materialInfo;
uniform vec3 cameraPosition;

in vec3 position;
in vec3 normal;
out vec4 finalColor;

vec3 calculatePointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
	vec3 lightDir = normalize(light.pos - fragPos);
	//ambient
	vec3 ambFactor = ambient;
	// Diffuse shading
	float diffFactor = max(dot(normal, lightDir), 0.0) * light.intensity ;
	// Specular shading
	vec3 reflectDir = normalize(reflect(-lightDir, normal));
	float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), materialInfo.shininess) * light.intensity;
	// Attenuation
    float distance = length(light.pos - fragPos);
	float attenuation = 1.0f;
	if(distance < light.range)
	{
	    attenuation = 1.0f / (light.constant + light.linear * distance +
  			    light.quadratic * (distance * distance));
	}

	vec3 ambientColor = ambFactor;
	vec3 diffuseColor = diffFactor * materialInfo.diffuse * light.color;
	vec3 specularColor = specFactor * materialInfo.specular * light.color ;
	return ambientColor + attenuation * (diffuseColor + specularColor);
}

void main(void)
{
	vec3 totalLight = vec3(0,0,0);
	vec3 norm = normalize(normal);
	vec3 viewDir = normalize(cameraPosition - position);
	totalLight += calculatePointLight(pointLight, normal, position, viewDir);
	finalColor = vec4(totalLight, 1.0);
	return;
}

运行结果

多光源

先看下最终的效果,是不是有点炫酷!?

整体的思路是:创建3个点光源,然后传到把点光源的信息都传递进去,开一个定时器,不断更新光源的位置,再更新shader数据,最后再绘制。

首先创建一个简单的场景类,注意这个类是要继承QObject的,因为要用到Qt的Signal/Slot机制。

#ifndef SCENE_H
#define SCENE_H
#include <vector>
#include <QDebug>
#include <QTimer>
#include <QObject>
#include "light/pointlight.h"
#include "shader/shaderprogram.h"
#include <QObject>

class Scene : public QObject
{
	Q_OBJECT

public:
	Scene(QObject *parent = 0);
	~Scene();
	void addLight(PointLight* pLight);
	void setShader(ShaderProgram *pShader);
	void setUniform();

private:

	std::vector<PointLight * > pointLights;
	ShaderProgram *shaderProgram;
	QTimer *updateTimer;

private slots:
	void updateLight();

signals:
	void updateScene();
};

#endif // SCENE_H

接下来是cpp的实现

#include "scene.h"

Scene::Scene(QObject *parent)
	: QObject(parent)
{
	updateTimer = new QTimer();
	updateTimer->setInterval(30);
	connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateLight()));
	updateTimer->start();
}

Scene::~Scene()
{
	for (int i = 0; i < pointLights.size(); i++)
	{
		delete pointLights[i];
	}
}

void Scene::addLight(PointLight* pLight)
{
	pointLights.push_back(pLight);
}

void Scene::setUniform()
{
	char tmp[100];
	int count = static_cast<int>(pointLights.size());
	shaderProgram->setUniform("pointLightCount", count);
	for (int i = 0; i < count; i++)
	{
		sprintf(tmp, "pointLights[%d].pos", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getPos());
		sprintf(tmp, "pointLights[%d].color", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getColor());
		sprintf(tmp, "pointLights[%d].intensity", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getIntensity());
		sprintf(tmp, "pointLights[%d].constant", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getAttenuation().getConstant());
		sprintf(tmp, "pointLights[%d].linear", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getAttenuation().getLinear());
		sprintf(tmp, "pointLights[%d].quadratic", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getAttenuation().getQuadratic());
		sprintf(tmp, "pointLights[%d].range", i);
		shaderProgram->setUniform(tmp, pointLights[i]->getAttenuation().getRange());
	}

}

void Scene::setShader(ShaderProgram *pShader)
{
	shaderProgram = pShader;
}

void Scene::updateLight()
{
	glm::mat4 transMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0, 2, 0));
	glm::mat4 rotationX = glm::rotate(transMatrix, 0.1f, glm::vec3(1, 0, 0));
	glm::mat4 rotationY = glm::rotate(transMatrix, 0.1f, glm::vec3(0, 1, 0));
	glm::mat4 rotationZ = glm::rotate(transMatrix, 0.1f, glm::vec3(0, 0, 1));

	glm::vec4  newPos = glm::vec4(pointLights[0]->getPos(), 1.0f) * rotationX;
	pointLights[0]->setPos(glm::vec3(newPos));

	newPos = glm::vec4(pointLights[1]->getPos(), 1.0f) * rotationY;
	pointLights[1]->setPos(glm::vec3(newPos));

	newPos = glm::vec4(pointLights[2]->getPos(), 1.0f) * rotationZ;
	pointLights[2]->setPos(glm::vec3(newPos));

	this->setUniform();
	emit updateScene();
}

解释两个函数,

setUniform

向shader中传递当前灯光的参数。

updateLight

更新灯光的位置,不知道咋转的回去看线性代数。想偷懒的看这个Real-Time Rendering (2) - 变换和矩阵(Transforms and Matrics)

更新完之后调用setUniform传递参数。

最后看他们的初始化

void MainWidget::initScene()
{
	// Calculate aspect ratio
	float aspect = float(width()) / float(height() ? height() : 1);
	const float zNear = 0.01, zFar = 100.0, fov = 45.0;

	// Set projection
	mainCamera = new Camera(glm::vec3(0, 5, 10), glm::vec3(0, 3, 0), glm::vec3(0.0, 1.0, 0.0));
	mainCamera->setPerspectiveParameter(fov, aspect, zNear, zFar);
	modelMatrix = glm::mat4(1.0f);

	scene = new Scene();
	connect(scene, SIGNAL(updateScene()), this, SLOT(update()));
	scene->setShader(&prog);

	PointLight *pointLight1 = new PointLight(glm::vec3(0, 1, 0), glm::vec3(0, 2, 3), 1.8, Attenuation(20, 0.1, 0.22, 0.20));
	PointLight *pointLight2 = new PointLight(glm::vec3(1, 0, 0), glm::vec3(3, 2, 0), 1.8, Attenuation(20, 0.1, 0.22, 0.20));
	PointLight *pointLight3 = new PointLight(glm::vec3(0, 0, 1), glm::vec3(-3, 2, 0), 1.8, Attenuation(20, 0.1, 0.22, 0.20));

	scene->addLight(pointLight1);
	scene->addLight(pointLight2);
	scene->addLight(pointLight3);

	compileShader();
	setUniform();
	objModel.loadFromFile("../Assets/model/bunny.obj");
	objModel.setShader(prog);
}

Fragment Shader需要做一些改变

#version 400
const int MAX_POINT_LIGHTS = 5;                                                       

struct PointLight
{
	float range;
	vec3 pos;
	vec3 color;
	float intensity;
	float constant;
    float linear;
    float quadratic;
};

struct MaterialInfo{
    vec3 diffuse;
    vec3 specular;
    float shininess;
};  

uniform vec3 ambient; 

uniform int pointLightCount;
uniform PointLight pointLights[MAX_POINT_LIGHTS];
uniform MaterialInfo materialInfo;
uniform vec3 cameraPosition;

in vec3 position;
in vec3 normal;
out vec4 finalColor;

vec3 calculatePointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
	vec3 lightDir = normalize(light.pos - fragPos);
	//ambient
	vec3 ambFactor = ambient;
	// Diffuse shading
	float diffFactor = max(dot(normal, lightDir), 0.0) * light.intensity ;
	// Specular shading
	vec3 reflectDir = normalize(reflect(-lightDir, normal));
	float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), materialInfo.shininess) * light.intensity;
	// Attenuation
    float distance = length(light.pos - fragPos);
	float attenuation = 1.0f;
	if(distance < light.range)
	{
	    attenuation = 1.0f / (light.constant + light.linear * distance +
  			    light.quadratic * (distance * distance));
	}

	vec3 ambientColor = ambFactor;
	vec3 diffuseColor = diffFactor * materialInfo.diffuse * light.color;
	vec3 specularColor = specFactor * materialInfo.specular * light.color ;
	//return ambientColor + attenuation * (diffuseColor + specularColor);
	return attenuation * (diffuseColor + specularColor);
}

void main(void)
{
	vec3 totalLight = vec3(0,0,0);
	vec3 norm = normalize(normal);
	vec3 viewDir = normalize(cameraPosition - position);
	for(int i = 0; i < pointLightCount; i++)
	{
		totalLight += calculatePointLight(pointLights[i], normal, position, viewDir);
	}
	finalColor = vec4(totalLight, 1.0);
	return;
}

打完收工。

参考

Point Light Attenuation - http://www.ogre3d.org/tikiwiki/tiki-index.php?page=-Point+Light+Attenuation

Multiple lights - http://www.learnopengl.com/#!Lighting/Multiple-lights

Modern OpenGL 07 – More Lighting: Ambient, Specular, Attenuation, Gamma - http://www.tomdalling.com/blog/modern-opengl/07-more-lighting-ambient-specular-attenuation-gamma/

BennyQBD/3DEngineCpp - https://github.com/BennyQBD/3DEngineCpp

时间: 2024-07-31 02:25:27

OpenGL进阶(十九) - 多光源的相关文章

19. 蛤蟆的数据结构进阶十九外部排序相关概念

19. 蛤蟆的数据结构进阶十九外部排序相关概念 本篇名言:"一个人最怕不老实,青年人最可贵的是老实作风. "老实 " 就是不自欺欺人,做到不欺骗人家容易,不欺骗自己最难. "老实作风 " 就是脚踏实地,不占便宜.世界上没有便宜的事,谁想占便宜水就会吃亏. --徐特立" 之前我们学习的排序都是内部排序的,接下去来看下外部排序. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47

分布式进阶(十九) 基于集群的动态反馈负载均衡策略

基于集群的动态反馈负载均衡策略 基于动态反馈机制的集群负载均衡算法研究 目前应用最为广泛的集群计算技术主要分为三大类:高可用性集群技术.高性能计算集群技术和负载均衡集群技术. 德国的CarlAdamPetri于1962年在他的博士论文<自动机通信>中提出了Petri网的概念,它是一种适合于描述异步.并发.分布式系统的图形数学工具. 动态WRR调度算法 这是一个目前普遍使用的调度算法,算法在WRR的基础上加入了根据服务器端的负载信息周期性地调整服务器性能权值的过程.其基本思想是:根据CPU利用率

mysql进阶 十九 SQL语句如何精准查找某一时间段的数据

SQL语句如何精准查找某一时间段的数据 在项目开发过程中,自己需要查询出一定时间段内的交易.故需要在sql查询语句中加入日期时间要素,sql语句如何实现? SELECT * FROM lmapp.lm_bill where tx_time Between '2015-12-20' And '2015-12-31'; 仔细研究还是能够发现一些细节性的问题的. SQL语句1 SELECT * FROM lmapp.lm_bill where merch_uid='S18853883587' AND 

NeHe OpenGL教程 第二十九课:Blt函数

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第二十九课:Blt函数 Blitter 函数: 类似于DirectDraw的blit函数,过时的技术,我们有实现了它.它非常的简单,就是把一块纹理贴到另一块纹理上. 这篇文章是有Andreas Lffler所写的,它写了一份原始的教

NeHe OpenGL教程 第三十九课:物理模拟

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十九课:物理模拟 物理模拟简介: 还记得高中的物理吧,直线运动,自由落体运动,弹簧.在这一课里,我们将创造这一切. 物理模拟介绍 如果你很熟悉物理规律,并且想实现它,这篇文章很适合你. 在这篇教程里,你会创建一个非常简单的物理引

Python进阶(三十九)-数据可视化の使用matplotlib进行绘图分析数据

Python进阶(三十九)-数据可视化の使用matplotlib进行绘图分析数据 ??matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. ??它的文档相当完备,并且 Gallery页面 中有上百幅缩略图,打开之后都有源程序.因此如果你需要绘制某种类型的图,只需要在这个页面中浏览/复制/粘贴一下,基本上都能搞定. ??在Linux下比较著名的数据图工具还有gnuplot

Python进阶(四十九)-初识Flask Blueprint

Python进阶(四十九)-初识Flask Blueprint 前言   在进行Python Web开发时选择Flask框架.项目模块划分阶段,使用Blueprint(这里暂且称之为“蓝本”).Blueprint通过把实现不同功能的module分开,从而把一个大的application分割成各自实现不同功能的module.在一个Blueprint中可以调用另一个blueprint的view function, 但要加相应的blueprint名.   Blueprint还有其他好处,其本质上来说就

QT开发(三十九)——GraphicsView框架

QT开发(三十九)--GraphicsView框架 本文主要翻译自QT 5.6.2GraphicsView官方文档 一.GraphicsView框架简介 QT4.2开始引入了Graphics View框架用来取代QT3中的Canvas模块,并作出了改进,Graphics View框架实现了模型-视图结构的图形管理,能对大量图元进行管理,支持碰撞检测,坐标变换和图元组等多种方便的功能. GraphicsView框架结构主要包含三个主要的类QGraphicsScene(场景).QGraphicsVi

无限互联奖学金文章连载北京总部四十九期胡梦川 第一篇

无限互联奖学金文章连载北京总部四十九期胡梦川 第一篇: 今天是来到无限互联的第四天,严格来说已经第六天了,刚来就是开班典礼,给人一种很好的氛围.老师讲了很多关于以后学习的技巧和规定,我的第一感觉是,比备战高考还要严格,不过这才能体现一个组织的负责任.正式开讲才感觉到这个班级的大神无处不在,不努力根本赶不上,就是这个学习氛围和高强度的练习很重要.多用心你才能感觉到有些事其实很简单.关于学习时间大家基本都是一天不动的在敲代码,等于给自己一个机会吧.时间飞逝,抓住机会才重要.刚来第一周,感受最深就是好