本文正在参与「金石方案」

前面几篇文章咱们解说了OpenGL中的一些入门根底包含:着色器的运用,纹路的运用,立方体的制作等内容,今天咱们来解说下OpenGL中的光照

OpenGLES光照模型根底

咱们知道现实生活中的光线是比较杂乱的,而且受许多因素的影响,在现有的核算机处理才能中是没有彻底模仿的,在OpenGL处理过程中,一般运用的是一些简化模型,比方冯氏光照模型。冯氏光照模型主要包含3部分:

环境光,散射光,镜面光。

  • 环境光照(ambient):环境光照是为了模仿在漆黑环境下物体的发光射,现实生活中就算在黑夜环境下也是会有色彩的。
  • 散射光照(diffuse):模仿光照在不同片段上的亮度,这部分是光照最明显的部分,面向光源那面亮度最大。
  • 镜面光照(specular):模仿有光泽物体上面呈现的亮点。镜面光照的色彩,比较于物体的色彩更倾向于光的色彩。

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

运用冯氏模型的三种光照类型,能够模仿呈现实生活中的大部分光照场景。

环境光照

环境光照能够简略了解为在无光环境下物体的一种色彩状况。

环境光照的核算办法比较简略:运用光的色彩乘以一个很小的常量因子,最终乘以物体的色彩即可

大致如下:

 //设置环境光照
float ambientStrength = 0.2f;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
color = vec4(result, 1.0f);

这儿咱们运用第二章节解说的正方形为比如,假如你的代码没问题,应该会得到下面这种作用。

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

能够看到这是一个非常暗的状况,到这儿现已你完成了冯氏光照的第一阶段。

下面看第二阶段散射光照。

散射光照

散射光照是指物体上与光线排布越近的片段越能从光源处取得更多的亮度。这对物体会发生比较明显的光线作用。

散射光照模型如下

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

左上角有一个光源,他照耀在物体上每个片段的间隔是不相同的,咱们需求丈量这个光线与它所触摸片段之间的视点。这儿引入了一个法向量N,图中黄色部分:

法向量简略了解便是垂直于当时片段的一个单位向量

由于当时咱们的光线是照耀在一个正方形上面,法向量能够和极点坐标相同界说,关于杂乱图形,就需求运用特别东西获取。

散射光照核算办法

  • 1.核算出当时被照耀的片段到光源的向量:lightDir。核算办法:光源向量(lightPos)- 片段向量(FragPos)。
  • 2.核算lightDir和法向量N的夹角巨细,夹角越小,阐明光线到片段越挨近垂直,光线越强。核算办法:运用向量的点乘获取到的是夹角的cos值,cos值越大,阐明夹角越小,光线越强
  • 3.运用2中获取的夹角cos值乘以光线色彩向量得到散射光照强度,最终乘以

对应代码

/*
设置慢反射光照
1.先经过灯源pos和片段pos找到片段到灯源的向量:运用lightPos-FragPos
2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
*/
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos-FragPos);
float diff = max(dot(norm,lightDir),0.0f);
vec3 diffuse = diff*lightColor;

一切顺利的话,能够看到下面作用:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

下面进入冯氏光照的第三阶段:镜面光照。

镜面光照

镜面光照便是反射光线离观察者越近,光线就越强,模仿现实生活中更实在的场景:

模型如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

镜面光照核算办法:

  • 1.核算片段到光源的向量lightDir:核算办法通散射光照。
  • 2.核算片段到光源的反射向量R:reflectDir,核算办法运用glsl的reflect办法。
  • 3.核算片段到观察者的向量ViewDir。核算办法:原点到观察者的向量(viewPos)-原点到片段的向量(FragPos)。
  • 4.核算向量reflectDir和viewDir的夹角巨细,核算办法:经过点乘获取夹角的cos值,cos值越大,阐明夹角越小,夹角越小,光线越亮。

对应代码如下

/*设置镜面高光
1.核算片段到观察者的向量:运用viewPos-FragPos得到
2.核算灯源的反射向量,运用reflect得到
3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈*/
​
float specularStrength = 0.5f;
//1
vec3 viewDir = normalize(viewPos-FragPos);
//2
vec3 reflectDir = reflect(-lightDir,norm);
//3
float spec = specularStrength*pow(max(dot(viewDir,reflectDir),0.0f),64.0f);

作用如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

以上便是关于冯氏光照三阶段的简略处理:

完整的片段着色器代码如下:

#version 300 es
precision mediump float;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
​
out vec4 fragColor;
uniform sampler2D textureColor;
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
​
void main()
{
  //设置环境光照
  float ambientStrength = 0.2f;
  vec3 ambient = ambientStrength * lightColor;
  /*
  设置慢反射光照
  1.先经过灯源pos和片段pos找到片段到灯源的向量:运用lightPos-FragPos
  2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
  */
  vec3 norm = normalize(Normal);
  vec3 lightDir = normalize(lightPos-FragPos);
  float diff = max(dot(norm,lightDir),0.0f);
  vec3 diffuse = diff*lightColor;
​
  /*设置镜面高光
  1.核算片段到观察者的向量:运用viewPos-FragPos得到
  2.核算灯源的反射向量,运用reflect得到
  3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈*/
​
  float specularStrength = 0.5f;
  //1
  vec3 viewDir = normalize(viewPos-FragPos);
  //2
  vec3 reflectDir = reflect(-lightDir,norm);
  //3
  float spec = specularStrength*pow(max(dot(viewDir,reflectDir),0.0f),64.0f);
​
  vec3 result = (ambient+diffuse+spec)*objectColor;
  fragColor = vec4(result, 1.0f);
}

材质

其实大部分情况下,咱们是不会直接运用单独的色彩来表明物体的色彩,而是运用纹路:

咱们会将物体的色彩:包含环境光照下以及散射光照下或者镜面光照下都会运用不同的纹路来核算,会封装在一个Material结构体中。

struct Material
{
//漫反射光照下物体色彩
  sampler2D diffuse;
//反射一个物体特定的镜面高光色彩
  sampler2D specular;
//放射光贴图
  sampler2D emission;
//影响镜面高光的散射/半径 如2,4,8,16,32,64,128,256
  float shininess;
};

投光物

现实生活中的投光物是许多很杂乱的,咱们这儿来讨论三种比较常见的:定向光,点光,聚光

定向光

望文生义便是光线的方向是固定的,关于足够远的光,咱们就称之为定向光,如太阳光

模型如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

已然方向是固定的,那么核算散射光照的时分,就不需求核算夹角了,而是只需求知道光线的方向就能够。

这儿咱们将光线封装到一个Light结构体中:

struct Light
{
  //定向光线照耀的办法
  vec3 direction;
  //光线在环境光照下的强度
  vec3 ambient;
  //光线在散射光照下的强度
  vec3 diffuse;
  //光线在镜面高光光照下的强度
  vec3 specular;
};

完整片段着色器代码如下:

#version 300 es
precision mediump float;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
​
out vec4 fragColor;
uniform vec3 viewPos;
struct Light
{
  //定向光线照耀的办法
  vec3 direction;
  //光线在环境光照下的强度
  vec3 ambient;
  //光线在散射光照下的强度
  vec3 diffuse;
  //光线在镜面高光光照下的强度
  vec3 specular;
};
uniform Light light;
​
struct Material
{
//漫反射光照下物体色彩
  sampler2D diffuse;
//反射一个物体特定的镜面高光色彩
  sampler2D specular;
//放射光贴图
  sampler2D emission;
//影响镜面高光的散射/半径 如2,4,8,16,32,64,128,256
  float shininess;
};
uniform Material material;
void main()
{
  //设置环境光照
  vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoord));
  /*
  设置慢反射光照
  1.定向光方向固定
  2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
  */
  vec3 norm = normalize(Normal);
  vec3 lightDir = normalize(-light.direction);
  float diff = max(dot(norm,lightDir),0.0f);
  vec3 diffuse = light.diffuse*diff*vec3(texture(material.diffuse,TexCoord));
​
  /*
  设置镜面高光
  1.核算片段到观察者的向量:运用viewPos-FragPos得到
  2.核算灯源的反射向量,运用reflect得到
  3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈
  */
  //1
  vec3 viewDir = normalize(viewPos-FragPos);
  //2
  vec3 reflectDir = reflect(-lightDir,norm);
  //3
  float spec = pow(max(dot(viewDir,reflectDir),0.0f),material.shininess);
  vec3 specular = light.specular*spec*vec3(texture(material.specular,TexCoord));
  vec3 result = (ambient+diffuse+specular);fragColor = vec4(result, 1.0f);
}

定向光作用:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

点光源

点光源便是光源只是一个点,那么他和每个片段的间隔和视点便是不同的,需求核算不同片段的光源亮度。

其模型如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

关于点光源的片段色彩核算办法,在前面其实现已解说过了,需求合作当时片段的法向量进行处理,核算和法向量的夹角巨细。

代码如下:

#version 300 es
precision mediump float;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
​
out vec4 fragColor;
uniform vec3 viewPos;
struct Light
{
  vec3 position;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
  /*衰减
  constant:常数项
  linear:一次项
  quadratic:二次项
  衰减值 = 1.0f/constant+linear*distance+quadratic*distance*distance;
​
  灯光亮度要减去当时衰减值
  */
  float constant;
  float linear;
  float quadratic;
};
uniform Light light;
​
struct Material
{
//漫反射光照下物体色彩
  sampler2D diffuse;
//反射一个物体特定的镜面高光色彩
  sampler2D specular;
//放射光贴图
  sampler2D emission;
//影响镜面高光的散射/半径 如2,4,8,16,32,64,128,256
  float shininess;
};
uniform Material material;
void main()
{
  //设置环境光照
  vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoord));
  /*
  设置慢反射光照
  1.定向光方向固定
  2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
  */
  vec3 norm = normalize(Normal);
  vec3 lightDir = normalize(light.position-FragPos);
  float diff = max(dot(norm,lightDir),0.0f);
  vec3 diffuse = light.diffuse*diff*vec3(texture(material.diffuse,TexCoord));
​
  /*
  设置镜面高光
  1.核算片段到观察者的向量:运用viewPos-FragPos得到
  2.核算灯源的反射向量,运用reflect得到
  3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈
  */
  //1
  vec3 viewDir = normalize(viewPos-FragPos);
  //2
  vec3 reflectDir = reflect(-lightDir,norm);
  //3
  float spec = pow(max(dot(viewDir,reflectDir),0.0f),material.shininess);
  vec3 specular = light.specular*spec*vec3(texture(material.specular,TexCoord));
​
  /*
  核算衰减值
  */
  float distance = length(light.position-FragPos);
  float attenuation = 1.0f/(light.constant+light.linear*distance+light.quadratic*distance*distance);
​
  vec3 result = (ambient+diffuse+specular)*attenuation;
  fragColor = vec4(result, 1.0f);
}

代码里面有个核算衰减值,这个衰减值便是用来模仿:片段的间隔和光线越远,衰减的就越快,这也愈加贴合现实生活。

衰减值 = 1.0f/constant+linear*distance+quadratic*distance*distance;

点光源作用如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

聚光

聚光是一种位于环境中某处的光源,它不是向一切方向照耀,而是只朝某个方向照耀。成果是只要一个聚光照耀方向的确认半径内的物体才会被照亮,其他的都保持漆黑。聚光的好比如是路灯手电筒

OpenGL中的聚光用世界空间位置,一个方向和一个指定了聚光半径的切光角来表明。咱们核算的每个片段,假如片段在聚光的切光方向之间(便是在圆锥体内),咱们就会把片段照亮。下面的图能够让你理解聚光是如何作业的:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phi:界说聚光半径的切光角。每个落在这个视点之外的,聚光都不会照亮。
  • ThetaLightDir向量和SpotDir向量之间的视点。值应该比值小,这样才会在聚光内。

所以咱们大致要做的是,核算LightDir向量和SpotDir向量的点乘(回来两个单位向量的点乘,还记得吗?),然后在和切光角对比。现在你应该理解聚光是咱们下面将创立的手电筒的范例。

代码如下:

#version 300 es
precision mediump float;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
​
out vec4 fragColor;
uniform vec3 viewPos;
struct Light
{
  //聚光的位置
  vec3 position;
  //聚光指向的方向
  vec3 direction;
  //夹角的cos值,值大于cutoff阐明夹角越小,需求显现聚光,小余,不需求显现聚光
  float cutoff;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
​
  /*衰减
  constant:常数项
  linear:一次项
  quadratic:二次项
  衰减值 = 1.0f/constant+linear*distance+quadratic*distance*distance;
​
  灯光亮度要减去当时衰减值
  */
  float constant;
  float linear;
  float quadratic;
​
};
uniform Light light;
​
struct Material
{
//漫反射光照下物体色彩
  sampler2D diffuse;
//反射一个物体特定的镜面高光色彩
  sampler2D specular;
//放射光贴图
  sampler2D emission;
//影响镜面高光的散射/半径 如2,4,8,16,32,64,128,256
  float shininess;
};
uniform Material material;
void main()
{
​
  /*
  1.核算片段到光源的间隔lightDir
  2.核算lightDir和光源方向direction的夹角的cos
  3.判断2中的cos值和cutoff比较:值大于cutoff阐明夹角越小,需求显现聚光,小余,不需求显现聚光,运用环境光照显现
  */
  vec3 lightDir = normalize(light.position-FragPos);
  float theta = dot(lightDir,normalize(-light.direction));
  vec3 result;
  if(theta>light.cutoff){
    //设置环境光照
    vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoord));
    /*
  设置慢反射光照
  1.定向光方向固定
  2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
  */
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position-FragPos);
    float diff = max(dot(norm,lightDir),0.0f);
    vec3 diffuse = light.diffuse*diff*vec3(texture(material.diffuse,TexCoord));
​
    /*
    设置镜面高光
    1.核算片段到观察者的向量:运用viewPos-FragPos得到
    2.核算灯源的反射向量,运用reflect得到
    3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈
    */
    //1
    vec3 viewDir = normalize(viewPos-FragPos);
    //2
    vec3 reflectDir = reflect(-lightDir,norm);
    //3
    float spec = pow(max(dot(viewDir,reflectDir),0.0f),material.shininess);
    vec3 specular = light.specular*spec*vec3(texture(material.specular,TexCoord));
​
    /*
    核算衰减值
    */
    float distance = length(light.position-FragPos);
    float attenuation = 1.0f/(light.constant+light.linear*distance+light.quadratic*distance*distance);
    result = (ambient+diffuse+specular)*attenuation;
  }else {
    //设置环境光照
    result = light.ambient * vec3(texture(material.diffuse,TexCoord));
  }
​
  fragColor = vec4(result, 1.0f);
}

作用如下:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

上面的光源看起来其实是不实在的,一个实在的聚光的光会在它的鸿沟处滑润削弱的。

为创立聚光的滑润边,咱们希望去模仿的聚光有一个内圆锥和外圆锥。咱们能够把内圆锥设置为前面部分界说的圆锥,咱们希望外圆锥从内边到外边逐渐的变暗。

滑润边际作用

滑润作用界说办法:咱们简略界说另一个余弦值,它代表聚光的方向向量和外圆锥的向量(等于它的半径)的视点。然后,假如片段在内圆锥和外圆锥之间,就会给它核算出一个0.0到1.0之间的亮度。假如片段在内圆锥以内这个亮度就等于1.0,假如在外面便是0.0

咱们能够运用下面的公式核算这样的值:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

这儿是内部()和外部圆锥()(\epsilon = \phi – \gamma)的差。成果II的值是聚光在当时片段的亮度。

很难用图画描绘出这个公式是怎样作业的,所以咱们测验运用一个比如:

(视点) (内切) (视点) (外切) (视点) II
0.87 30 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.87 – 0.82 / 0.09 = 0.56
0.9 26 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.9 – 0.82 / 0.09 = 0.89
0.97 14 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.97 – 0.82 / 0.09 = 1.67
0.97 14 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.97 – 0.82 / 0.09 = 1.67
0.83 34 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.83 – 0.82 / 0.09 = 0.11
0.64 50 0.91 25 0.82 35 0.91 – 0.82 = 0.09 0.64 – 0.82 / 0.09 = -2.0
0.966 15 0.9978 12.5 0.953 17.5 0.966 – 0.953 = 0.0448 0.966 – 0.953 / 0.0448 = 0.29

就像你看到的那样咱们基本是根据在外余弦和内余弦之间插值。假如你依然不理解怎样持续,不要忧虑。你能够简略的运用这个公式核算,当你愈加老道和理解的时分再来看。

由于咱们现在有了一个亮度值,当在聚光外的时分是个负的,当在内部圆锥以内大于1。假如咱们适当地把这个值固定,咱们在片段着色器中就再不需求if-else了,咱们能够简略地用核算出的亮度值乘以光的元素:

glsl代码如下:

#version 300 es
precision mediump float;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
​
out vec4 fragColor;
uniform vec3 viewPos;
struct Light
{
  //聚光的位置
  vec3 position;
  //聚光指向的方向
  vec3 direction;
  //夹角的cos值,值大于cutoff阐明夹角越小,需求显现聚光,小余,不需求显现聚光
  float cutoff;
  float outerCutoff;
  vec3 ambient;
  vec3 diffuse;
  vec3 specular;
​
  /*衰减
  constant:常数项
  linear:一次项
  quadratic:二次项
  衰减值 = 1.0f/constant+linear*distance+quadratic*distance*distance;
​
  灯光亮度要减去当时衰减值
  */
  float constant;
  float linear;
  float quadratic;
​
};
uniform Light light;
​
struct Material
{
//漫反射光照下物体色彩
  sampler2D diffuse;
//反射一个物体特定的镜面高光色彩
  sampler2D specular;
//放射光贴图
  sampler2D emission;
//影响镜面高光的散射/半径 如2,4,8,16,32,64,128,256
  float shininess;
};
uniform Material material;
void main()
{
​
  /*
  1.核算片段到光源的间隔lightDir
  2.核算lightDir和光源方向direction的夹角的cos
  3.判断2中的cos值和cutoff比较:值大于cutoff阐明夹角越小,需求显现聚光,小余,不需求显现聚光,运用环境光照显现
  */
  vec3 lightDir = normalize(light.position-FragPos);
  float theta = dot(lightDir,normalize(-light.direction));
  float epsilon = light.cutoff - light.outerCutoff;
  //clamp函数,它把第一个参数固定在0.0和1.0之间。这确保了亮度值不会超出[0, 1]以外
  float intensity = clamp(( theta - light.outerCutoff) / epsilon,0.0, 1.0);
​
  //设置环境光照
  vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoord));
  /*
  设置慢反射光照
  1.定向光方向固定
  2.运用1中获取的片段到灯源的向量和法向量的点乘,能够获取夹角巨细的cos值,假如cos值越大,阐明夹角越小,当时片段的光越亮
  */
  vec3 norm = normalize(Normal);
  float diff = max(dot(norm,lightDir),0.0f);
  vec3 diffuse = light.diffuse*diff*vec3(texture(material.diffuse,TexCoord));
​
  /*
  设置镜面高光
  1.核算片段到观察者的向量:运用viewPos-FragPos得到
  2.核算灯源的反射向量,运用reflect得到
  3.获取1和2向量的夹角的cos值,cos值越大,夹角越小,光线越激烈
  */
  //1
  vec3 viewDir = normalize(viewPos-FragPos);
  //2
  vec3 reflectDir = reflect(-lightDir,norm);
  //3
  float spec = pow(max(dot(viewDir,reflectDir),0.0f),material.shininess);
  vec3 specular = light.specular*spec*vec3(texture(material.specular,TexCoord));
​
  /*
  核算衰减值
  */
  float distance = length(light.position-FragPos);
  float attenuation = 1.0f/(light.constant+light.linear*distance+light.quadratic*distance*distance);
  vec3 result = (ambient+diffuse*intensity+specular*intensity)*attenuation;fragColor = vec4(result, 1.0f);
}

作用:

【安卓音视频开发OpenGLES】 开发入门(四):给3D图形加上光照是一种什么体验

这样就更靠近现实生活中的光照场景啦:

好了,完整代码现已贴到github上了,我们能够自行下载查看。我是小余,咱们下期见》》》