OpenGL ES教程——光照进阶

上一篇根底光照论述了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);

OpenGL ES教程——光照进阶

衰减系数如上述公式,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、聚光

还有一种光源像手电筒相同,它能射出一定规模内的光线。

OpenGL ES教程——光照进阶

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phi: 指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
  • ThetaLightDir向量和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);

但这么做会有个问题,很突兀,比方这样:

OpenGL ES教程——光照进阶

所以,为了解决这个问题,需求做边际滑润作业。

为了创立一种看起来边际滑润的聚光,咱们需求模仿聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。咱们能够将内圆锥设置为上一部分中的那个圆锥,但咱们也需求一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。

为了创立一个外圆锥,咱们只需求再界说一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,假如一个片段处于表里圆锥之间,将会给它核算出一个0.0到1.0之间的强度值。假如片段在内圆锥之内,它的强度就是1.0,假如在外圆锥之外强度值就是0.0。

咱们能够用下面这个公式来核算这个值:

OpenGL ES教程——光照进阶

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);
}