上一篇根底光照论述了opengl中色彩的界说,运用以及冯氏光照模型。本篇继续讲和光照相关的原料、光照贴图、投光物等知识。
1、原料
实际世界里,不同的原料在光照下有不同的表现,比方,金属制品在阳光下闪闪发光,但木头就不会,所以咱们需求界说物体原料做区分
依据冯氏光照模型,原料也会用以下三个重量来界说:
- 环境光照
- 漫反射光照
- 镜面光照
再加上反光度,这样就能够界说原料了:
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
在片段着色器中界说如上结构体,并且界说结构体uniform类型变量,那该怎样给这种类型的uniform变量赋值呢?
objectShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
2、光照
已然原料都已经按冯氏光照模型三个维度界说了,光照也是需求按冯氏模型维度界说的:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
在核算色彩输出时,需求光源的方位信息,所以光源结构体中也增加了光源的方位信息。
这样在核算终究色彩输出时,比方漫反射输出,则用原料的漫反射色彩乘以光源的漫反射色彩即可,一般写法为:
void main()
{
vec3 ambient = light.ambient * material.ambient;
vec3 norm = normalize(normal);
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse);
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
3、光照贴图
现在光照作用只运用了色彩,假如要运用纹路怎样办呢?由于实在场景中,肯定是纹路运用得更多,更实在
纹路代表着一个buf,这个buf或许代表着一张图片,或许一段视频中的yuv某个重量数据,能够以为就是无数色彩的合集,那原料能用色彩,也能够用纹路,所以原料能够这么界说:
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
uniform Material material;
所以,现在需求咱们给uniform变量赋值,然后再来核算终究色彩输出:
//设置原料纹路id,纹路id就是0,1这类
objectShader.setInt("material.diffuse", 0);
objectShader.setInt("material.specular", 1);
//然后核算终究色彩输出,运用texture函数核算色彩值
void main()
{
vec3 ambient = light.ambient * (texture(material.diffuse, texCoords)).rgb;
vec3 norm = normalize(normal);
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * (texture(material.diffuse, texCoords)).rgb;
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * (texture(material.specular, texCoords)).rgb;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
4、平行光
光源有多种类型,平行光是其间一种。
- 平行光,比方说太阳,光照强度满足强,间隔满足远
- 点光源,比方说灯泡,理论上点光源只能照亮点光源附近的当地,无法照亮远处
- 聚光,类似于手电筒,在光的规模内可照亮物体,规模之外则无法照亮物体
还记得上一节中,光源结构体有个position变量,平行光是不需求光源的方位的,咱们只需求光的方向,所以针对平行光,position变direction
struct Light {
// vec3 position;
// 运用定向光就不再需求了
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//核算光广告,也需求改一改
vec3 lightDir = normalize(-light.direction);
5、点光源
一开始学习的示例就是点光源,但有一个特性,点光源没有考虑到。就是衰减,理论上离点光源方位越远,就会越暗,那怎样核算间隔,并且核算光亮的程度呢?
glsl中有函数,能够核算间隔:
float distance = length(light.position - fragPos);
衰减系数如上述公式,d既是间隔,公式里这几个参数一般按如下方式挑选:
间隔 | 常数项 | 一次项 | 二次项 |
---|---|---|---|
7 | 1.0 | 0.7 | 1.8 |
13 | 1.0 | 0.35 | 0.44 |
20 | 1.0 | 0.22 | 0.20 |
32 | 1.0 | 0.14 | 0.07 |
50 | 1.0 | 0.09 | 0.032 |
65 | 1.0 | 0.07 | 0.017 |
100 | 1.0 | 0.045 | 0.0075 |
160 | 1.0 | 0.027 | 0.0028 |
200 | 1.0 | 0.022 | 0.0019 |
325 | 1.0 | 0.014 | 0.0007 |
600 | 1.0 | 0.007 | 0.0002 |
3250 | 1.0 | 0.0014 | 0.000007 |
综上,点光源结构体应该这么界说:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
//下边3个成员别离是常量、一次项系数、二次项系数
float constant;
float linear;
float quadratic;
};
float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * distance * distance);
间隔衰减系数核算如上,最后把漫反射以及镜面成果别离自乘这个系数即可。
6、聚光
还有一种光源像手电筒相同,它能射出一定规模内的光线。
-
LightDir
:从片段指向光源的向量。 -
SpotDir
:聚光所指向的方向。 -
Phi
: 指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。 -
Theta
:LightDir
向量和SpotDir
向量之间的夹角。在聚光内部的话值应该比值小
所以,光源结构体就要这么界说了:
struct Light {
vec3 position;
vec3 direction;
float cutOff;
float outCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
position是光源方位,direction是聚光源的指向,上图就是正向下
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 履行光照核算
}
else // 不然,运用环境光,让场景在聚光之外时不至于完全漆黑
color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
但这么做会有个问题,很突兀,比方这样:
所以,为了解决这个问题,需求做边际滑润作业。
为了创立一种看起来边际滑润的聚光,咱们需求模仿聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。咱们能够将内圆锥设置为上一部分中的那个圆锥,但咱们也需求一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
为了创立一个外圆锥,咱们只需求再界说一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,假如一个片段处于表里圆锥之间,将会给它核算出一个0.0到1.0之间的强度值。假如片段在内圆锥之内,它的强度就是1.0,假如在外圆锥之外强度值就是0.0。
咱们能够用下面这个公式来核算这个值:
void main()
{
vec3 lightDir = normalize(light.position - fragPos);
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outCutOff;
float intensity = clamp((theta - light.outCutOff)/epsilon, 0.0, 1.0);
float distance = length(light.position - fragPos);
float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * distance * distance);
vec3 ambient = light.ambient * (texture(material.diffuse, texCoords)).rgb;
// ambient = ambient * attenuation;
vec3 norm = normalize(normal);
//假如传的是方向值 ,就直接核算光方向的单位向量
// vec3 lightDir = normalize(-light.direction);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * (texture(material.diffuse, texCoords)).rgb;
// diffuse = diffuse * attenuation;
diffuse *= intensity;
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * (texture(material.specular, texCoords)).rgb;
// specular = specular * attenuation;
specular *= intensity;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}