IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]OpenGL进阶(十九) - 多光源

    qp120291570发表于 2015-01-16 11:22:56
    love 0

    从光说起

    先看一段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 
    #include 
    #include 
    #include 
    #include "light/pointlight.h"
    #include "shader/shaderprogram.h"
    #include 
    
    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 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(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



沪ICP备19023445号-2号
友情链接