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 400struct 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);//ambientvec3 ambFactor = ambient;// Diffuse shadingfloat diffFactor = max(dot(normal, lightDir), 0.0) * light.intensity ;// Specular shadingvec3 reflectDir = normalize(reflect(-lightDir, normal));float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), materialInfo.shininess) * light.intensity;// Attenuationfloat 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_OBJECTpublic: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 ratiofloat aspect = float(width()) / float(height() ? height() : 1);const float zNear = 0.01, zFar = 100.0, fov = 45.0;// Set projectionmainCamera = 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);//ambientvec3 ambFactor = ambient;// Diffuse shadingfloat diffFactor = max(dot(normal, lightDir), 0.0) * light.intensity ;// Specular shadingvec3 reflectDir = normalize(reflect(-lightDir, normal));float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), materialInfo.shininess) * light.intensity;// Attenuationfloat 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
OpenGL进阶(十九) - 多光源相关推荐
- NeHe OpenGL第十九课:粒子系统
NeHe OpenGL第十九课:粒子系统 粒子系统: 你是否希望创建爆炸,喷泉,流星之类的效果.这一课将告诉你如何创建一个简单的例子系统,并用它来创建一种喷射的效果. 欢迎来到第十九课.你已经学习了很 ...
- NeHe OpenGL第二十九课:Blt函数
NeHe OpenGL第二十九课:Blt函数 Blitter 函数: 类似于DirectDraw的blit函数,过时的技术,我们有实现了它.它非常的简单,就是把一块纹理贴到另一块纹理上. 这篇文章是有 ...
- OpenGL(十九)——Qt OpenGL波动纹理(旗子的飘动效果)
OpenGL(十九)--Qt OpenGL波动纹理(旗子的飘动效果) 一.场景 在日常的项目中,我们经常会实现波动的一些纹理效果,比如飘动的旗子,水的波纹,地图上某一点的波浪圈圈等...,本篇介绍波动 ...
- OpenGL学习十九:纹理过滤
当物体放大缩小时导致投影在上面的纹理也随着变化,OpenGL为了 优化其细节使其效果更好,因此可以采用纹理过滤 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MA ...
- 【OpenGL】十九、OpenGL 绘制模式 ( 绘制线框模式 | 绘制点模式 )
文章目录 一.绘制线框模式 二.绘制点模式 三.绘制点模式 ( 圆点 ) 四.相关资源 一.绘制线框模式 使用 glPolygonMode(GL_FRONT, GL_LINE) 设置当前的绘制模式是线 ...
- mysql查询某一时间段之后数据的sql_mysql进阶(十九)SQL语句如何精准查找某一时间段的数据...
SQL语句如何精准查找某一时间段的数据 在项目开发过程中,自己需要查询出一定时间段内的交易.故需要在sql查询语句中加入日期时间要素,sql语句如何实现? SELECT * FROM lmapp.lm ...
- mysql进阶(十九)SQL语句如何精准查找某一时间段的数据
SQL语句如何精准查找某一时间段的数据 在项目开发过程中,自己需要查询出一定时间段内的交易.故需要在sql查询语句中加入日期时间要素,sql语句如何实现? SELECT * FROM lmapp.lm ...
- 分布式进阶(十九) 基于集群的动态反馈负载均衡策略
一.动态WRR调度算法 这是一个目前普遍使用的调度算法,算法在WRR的基础上加入了根据服务器端的负载信息周期性地调整服务器性能权值的过程.其基本思想是:根据CPU利用率.内存利用率.磁盘使用情况.连接 ...
- AngularJS进阶(十九)在AngularJS应用中集成百度地图实现定位功能
在AngularJS应用中集成百度地图实现定位功能 注:请点击此处进行充电! 前言 根据项目需求,需要实现手机定位功能,考虑到百度业务的强大能力,遂决定使用百度地图第三方服务. 添加第三方模块的步骤与 ...
最新文章
- 记一次简单的 JVM 调优经历
- 使用CSS实现三栏自适应布局(两边宽度固定,中间自适应)
- 关于maven依赖中的scopeprovided/scope使用
- tcp3次握手、4次挥手
- 小说阅读网站设计HTML,40多个漂亮的网页表单设计实例
- [重拾Oracle - 00]既然青春留不住,那么还是要学习-----工具:在线Oracle(Oracle Live SQL)...
- php 反射 调用私有方法,PHP通过反射方法调用执行类中的私有方法
- POJ 1661 DP
- 1600802047 android 第三次作业(音乐播放器)
- 一分钟了解阿里云产品:安骑士
- 信号集 信号屏蔽字/pending的处理
- Linux驱动开发: USB驱动开发
- 微信红包系统设计 优化
- Rapid SCADA中文使用说明书(一)
- Package java.util.stream
- EXCEL 字符替换为换行符
- 购买云服务器如何选择cpu与内存搭配
- 详细Ubuntu系统下搭建Hadoop完全分布式
- vol.173 乱炖 · 公司基因论靠不靠谱?
- 【BZOJ】【P4407】【于神之怒加强版】【题解】【数论】
热门文章
- 支持网络和局域网共享文件的windows pe
- Linux下命令补全工具bash-completion
- jQuery+PHP实现的砸金蛋中奖程序
- 3D汽车作品大赏!汇集世界各地CG大佬们的“汽车梦”
- 百分点认知智能实验室出品:智能问答中的对抗攻击及防御策略
- Java面向对象基础练习
- error: cannot lock ref ‘refs/remotes/origin/douyin/open‘: ‘refs/remotes/origin/douyin‘ exists;
- SpringBoot 整合 Elasticsearch
- 工业物联网·无线路由器中继桥功能
- 软件推荐:Typora -新手上路-夏凌玥