OpenGL ES光照相关

颜色可以数字化:红色(red),绿色(green),蓝色(blue)三个分量组成,缩写为RGB;
定义一个颜色向量:
glm::vec3 coral(1.0f,0.5f,0.31f); //得到一个珊瑚红的颜色

场景1:
当我们将光源设置为白色,把光源颜色与物体颜色相乘,得到的就是物体所反射的颜色。
glm::vec3 lightColor(1.0f,1.0f,1.0f);
glm::vec3 toyColor(1.0f,0.5f,0.31f);
glm::vec3 result=lightColor*toyColor; //=(1.0f,0.5f,0.31f);

定义一个片段着色器:
#version 330 core
out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

冯氏光照模型:
3个分量组成:环境,漫反射和镜面光照。

环境光照:即使在黑暗情况下,仍然有一些光亮(月光,远处的光),为模拟这个,会使用一个环境常量,给物体细一些颜色

漫反射光照:模拟光源对物体的方向性影响。是冯氏光照模型中最显著的分量。(物体某一部分越是正对光源,就越亮)

镜面光照:模拟有光泽物体上面出现的两点
环境光照:
添加到场景里非常简单:用光的颜色*很小的常量环境因子*物体的颜色

void main()
{
float ambientStrength=0.1;
vec3 ambient= ambientStrength*lightColor;

vec3 result=ambient*objectColor;
fragColor=vec4(result,1.0);
}
漫反射光照
漫反射光照使物体上与光线方向越接近的片段能从光源获得更多的亮度。

在这里插入图片描述
为测量光线和片段的角度,使用一个法向量(垂直于片段表面的一个向量,上图中的黄色箭头),于光线向量的
夹角θ,θ越大,光对片段的影响就越小。

法向量:
垂直于顶点表面的单位向量。
小技巧:使用叉乘对立方体所有的顶点计算法向量。

因向顶点数组添加了额外的数据,所以应该更新光照的顶点着色器:
#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;

更新顶点属性指针:
glVertexAttribPointer(0,3,GL_FLOAT,6*sizeof(float),(void*)(0));
glEnableVertexAttribArray(0);
我们只想更新每个顶点的前三个float,并且忽略后三个float,只需要把步长参数改成float大小的6倍就行了。

所有光照的计算都在片段着色器进行,需要将法向量由顶点着色器传递到片段着色器。
out vec3 Normal;
void main()
{
gl_position=projection*view*model*vec4(aPos,1.0);
Nornal=aNormal;
}

在片段着色器中定义相应的输入变量:
in vec3 Normal;

计算漫反射光照

现在每个顶点都有了法向量,我们需要光源的位置向量和片段的位置向量。
uniform vec3 lightPos;

在渲染环境中更新uniform,我们使用前面声明的lightpos向量作为光源位置:
lightingShade.setVec3("lightPos",lightPos);

最后,需要片段的位置。
在事件空间中进行所有的光照计算,需要一个在世界空间中的顶点位置。
out vec3 FlagPos;
out vec3 Normal;

void main()
{
gl_position=projection*view*model*vec4(aPos,1.0);
FragPos=vec3(model*vec4(aPos,1.0));
Normal=aNormal;
}

最后 在片段着色器中添加相应的输入变量
in vec3 FragPos;

第一件事:计算光源和片段位置之间的方向向量;
光的方向向量:光源位置向量与片段位置向量之间的向量差。

vec3 norm=normalize(Normal);
vec3 lightDir=normalize(lightPos-FragPos);

下一步:对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。
结果值再*光的颜色,得到漫反射分量。两个向量之间角度越大,漫反射分量就越小;
float diff=max(dot(norm,lightDir),0.0);
vec3 diffuse=diff*lightColor;

当两个向量之间角度>90,点乘就会变成负数,使用max函数返回保证漫反射分量不会变成负数。
现在有了环境光分量和漫反射分量,将其相加,再乘以物体的颜色,来得到片段最后的输出颜色。
vec3 result=(ambient+diffuse)*objectColor;
FragColor=vec4(result,1.0);

材质:

现实生活中,每个物体对光产生不同的反应。要想在openGL中模拟多种类型的物体,就必须针对每种表面定义不同的材质

描述一个表面时,分别为三个光照分量定义一个材质颜色:环境光照,漫反射光照和镜面光照。再添加一个反光度分量。
#version 330 core
struct Material {
vec3 ambient; //定义环境光照下表面反射的颜色
vec3 diffuse; //定义漫反射关照下表面的颜色
vec3 specular;//设置的是表面上镜面高光的颜色
float shininess; //影响镜面高光的散射/半径
};
uniform Material material;

设置材质:

可以从uniform变量material中访问它们:
void main()
{
//环境光
vec3 ambient=lightColor*material.ambient;

//漫反射
vec3 norm=normalize(Normal);
vec3 lightDir=normalize(lightPos-FragPos);
float diff=max(dot(norm,lightDir),0.0);
vec3 diffuse=lightColor*(diff*material.diffuse);

//镜面光
vec3 viewDir=normalize(viewPos-FragPos);
vec3 reflextDir=reflect(-lightDir,norm);
float spec=pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
vec3 spectular=lightColor*(spec*material.specular);

vec3 result=ambient+diffuse+specular;
FragColor=vec4(result,1.0);
}

lightShader.setVec3("material.ambient",1.0f,0.5f,0.31f);
lightShader.setVec3("material.diffuse",1.0f,0.5f,0.31f);
lightShader.setVec3("material.specular",0.5f,0.5f,0.5f);
lightShader.setFloat("material.shininess",32.0f);

以上的操作会导致物体过亮,原因:环境光,漫反射和镜面光这三个颜色对任何一个光源都全力反射。
所以我们将光源设置为一个小一点的值,从而限制环境光颜色:
vec3 ambient=vec3(0.1)*material.ambient;

光照贴图:
上面是把整个物体的材质定义为一个整体,但现实生活中是由多种材质所组成。
所以上一个材质系统是不够的,需要引入漫反射和镜面光贴图。

漫反射贴图:

投光物

将光投射到物体的光源叫做投光物。
先讨论定向光,再点光源,最后聚光。

平行光:光源来自很远,每条光线近似于互相平行,被称为定向光,它所有的光线都是一致的。

struct Light {
vec3 direction;
material.ambient
};

void main()
{
vec3 lightDir=normalize(-light.direction);//定义一个指向光源的方向向量
}

为展示定向光对多个物体具有相同的影响,先定义十个不同的箱子位置,并对其生成不同的模型矩阵

for(unsigned int i=0;i<10;i++)
{
glm:mat4 model;
model=glm::translate(model,cubePosition[i]);
float angle=20.0f*i;
model=glm::rotate(model,glm::radians(angle),glm::vec3(1.0f,0.3f,0.5f));
lightingShader.setMat4("model",model);

glDrawArrays(GL_TRANGLES,0,36);
}

更新光源方向:
lightingShader.setVec3("light.direction",-0.2f,-1.0f,-0.3f);

点光源

点光源:处于世界中某一个位置的光源,会朝着所有方向发光,但光线会随着距离逐渐衰减。可将投光物想象成灯泡和火把。

衰减:
随着光线传播距离的增长逐渐削减光的强度叫做衰减

在这里插入图片描述

实现衰减:
为了实现衰减,需要额外加3个值:公式的常数项,一次项和二次项。

struct Light {
 vec3 position;  

 vec3 ambient;
 vec3 diffuse;
 vec3 specular;
 
 float constant;
 float linear;
 float quadratic;
}

在opengl中设置这些项:希望光源能够覆盖50的距离,设置常数项,一次项和二次项:
lighterShader.setFloat("light.constant",1.0f);
lighterShader.setFloat("light.linear",0.09f);
lighterShader.setFloat("light.quadratic",0.032f);

根据公式计算衰减值,之后再乘以环境光,漫反射和镜面光分量。
float distance=length(light.position-FragPos);
float attenuation=1.0/(light.constant+light.linesr*distance+light.quadratic*(distance*distance));

ambient*=attenuation;
diffuse*=attenuation;
specular*=attenuation;

聚光

聚光是位于环境中某一个位置的光源,只朝一个特定的方向而不是所有方向照射光线。
结果:只有特定半径内的物体才会被照亮。

opengl中聚光:世界空间位置,一个方向和一个切光角来表示。切光角指定了聚光的半径。

在这里插入图片描述

LightDir:从片段指向光源的向量
SpotDir:聚光多指向的方向
Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。

我们要做的就是计算LightDir和SpotDir向量之间的点积,并与切光角ϕ值对比。

手电筒

位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。手电筒就是普通的聚光,它的位置和方向会随着玩家的位置和朝向不断更新。

片段着色器中需要的值:聚光的位置向量,聚光的方向向量和一个切光角。存在Light结构体中:
struct Light {
vec3 position;
vec3 direction;
float cutoff;
...
}

将合适的值传到着色器中:
lightingShader.setVec3("light.position",camera.Position);
lightingShader.setVec3("light.direction",camera.Front);
lightingShader.setVec3("light.cutoff",glm::cos(glm::radian(12.5f)));

计算θ值,并将它和切光角ϕ比较,来决定是否在聚光的内部:
float theta=dot(lightDir,normalize(-light.direction));

if(theta>light.cufOff)
{
//执行光照计算
}
else //否则,使用环境光,让场景在聚光外不至于黑暗
{
color=vec4(light.ambient*vec3(texture(material.diffuse,TexCoords)),1.0)
}

平滑/软化边缘

上面灯光会有些假,因为聚光有一圈硬边,到边缘时会完全变暗,没有一点平滑的过渡,一个真实的聚光是会在边缘
逐渐减少亮度。

解决:需要模拟聚光有一个内圆锥和外圆锥。将内圆锥设置为上一部分的那个圆锥,再使用外圆锥,来让光从内圆锥
逐渐减暗。

创建外圆锥:定义一个余弦值来代表聚光方向向量和外圆锥向量的夹角。然后,如果片段处于内外圆锥之间,将计算一个0.01.0之间的强度值。在内圆锥之内,强度为1.0,外就是0.0.
公式:

在这里插入图片描述
现在有一个聚光灯外是负的,在内圆锥内大于1.0的,在边缘处于两者之间的强度值。正确约束(Clamp)这个值,就不需要if-else了,计算出来的强度值*光照分量:


float theta =dot(lightDir,normalize(-light.direction));
float epsilon=light.cutOff-light.outerCutOff;
float intensity=clamp((theta-light.outerCutOff)/epsilon,0.0,1.0);
...
//将不对环境光做出影响,让它能有一点光
diffuse *=intensity;
specular *=intensity;

使用clamp函数,把第一个参数约束(clamp)在了0.01.0之间。

多光源

关于Opengl中光照知识,包括:冯氏着色,材质,光照贴图以及不同种类的投光物。
这一节中,结合所学的所有知识,创建一个包含6个光源的场景,模拟一个类似于太阳的定向光源,四个分散在场景中的点光源,以及一个手电筒。

使用多个光源时,采样方法:需要有一个单独颜色向量代表片段的输出颜色,对于每个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上。

out vec4 FragColor;

void main()
{
//定义一个输出颜色值
vec3 output;
//将定向光的贡献加到输出中
output+=someFunctionToCalculateDirectionalLight();
//对所有的点光源也做相同的事情
for(int i=0;i<nr_of_point_lights;i++)
output+=someFunctionToCalculateDirectionalLight();

//也加上其他的光源(比如聚光)
output+=someFunctionToCalculateDirectionalLight();

FragColor=vec4(output,1.0);
}
定义几个函数,来计算每个光源的影响,并将最后的结果颜色加到输出颜色向量上。

定向光:

首先,定义一个定向光源最少所需要的变量。
struct DirLight {
vec3 direction;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;

接下来将dirLight传入一个有着以下原型的函数:
vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir);

函数实现:
vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir)
{
vec3 lightDir=normalize(-light.direction);
//漫反射着色
float diff=max(dot(normal,lightDir),0.0);
//镜面光着色
vec3 reflectDir=reflect(-lightDir,normal);
float spec=pow(max(dot(viewDir,reflectDir),0,0),material.shininess);
//合并结果
vec3 ambient=light.ambient*vec3(texture(material.diffuse,TexCoods));
vec3 diffuse=light.diffuse*diff*vec3(texture(material.diffuse,TexCoods));
vec3 specular=light.specular*spec*vec3(texture(material.specular,TexCoods));
return (ambient+diffuse+specular);
}

点光源

和定向光一样,定义一个计算点光源对相应片段贡献:
struct PointLight {
vec3 position;

float constant;
float linear;
float quadratic;

vec3 ambient;
vec3 diffuse;
vec3 specular;
};
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];

点光源的原型:
vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir);

函数实现:
vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir)
{
vec3 lightDir=normalize(light.position-fragPos);

//漫反射着色
float diff=max(dot(normal,lightDir),0.0);
//镜面光着色
vec3 reflectDir=reflect(-lightDir,normal);
float spec=pow(max(dot(viewDir,reflectDir),0.0),material.shiness);

//衰减
float distance=length(light.position-flagPos);
float attenuation=1.0/(light.constant+light.linear*distance+light.quadratic*distance*distance);

//合并结果
vec3 ambient=light.ambient*vec3(texture(material.diffuse,TexCoods));
vec3 diffuse=light.diffuse*diff*vec3(texture(material.diffuse,TexCoods));
vec3 specular=light.specular*spec*vec3(texture(material.diffuse,TexCoods));
ambient*=attenuation;
diffuse*=attenuation;
specular*=attenuation;
return (ambient+diffuse+specular);
}

合并结果

void main()
{
//属性
vec3 norm=normalize(Normal);
vec3 viewDir=normalize(viewPos-FragPos);

//第一阶段:定向光照
vec3 result=CalcDirLight(dirLight,norm,viewDir);
//第二阶段:点光源
for(int i=0;i<NR_POINT_LIGHTS;i++)
result+=CalcPointLight(pointLights[i],norm,FragPos,viewDir);
//第三阶段:聚光
//result+=CalcSpotLight(spotLight,norm,FragPos,viewDir);

FragColor=vec4(result,1.0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值