前言
这篇文章主要是为了了解在OpenGL ES中,光照的一些基本概念以及该如何进行光照计算,方便以后在用到光照时,能够理解每一步是如何计算出来的。
什么是光照?
现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是以目前我们所拥有的处理能力无法模拟的。
- OpenGL的光照仅仅使用了简化的模型并基于对现实的估计来进行模拟,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。
-
其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面
如上图所示
环境光照(Ambient Lighting):即使在黑暗的情况下,世界上也仍然有一些光亮(月亮、一个来自远处的光),所以物体永远不会是完全黑暗的。我们使用环境光照来模拟这种情况,也就是无论如何永远都给物体一些颜色。
漫反射光照(Diffuse Lighting):模拟一个发光物对物体的方向性影响(Directional Impact)。它是冯氏光照模型最显著的组成部分。面向光源的一面比其他面会更亮。
镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色,相比于物体的颜色更倾向于光的颜色。
光照计算
1.环境光的计算
环境光 = 光源的环境光颜色 * 物体的材质颜⾊
环境光的GLSL代码实现如下:
varying vec3 objectColor;
void main()
{
//⾄少有%10的光找到物体所有⾯
float ambientStrength = 0.1;
//环境光颜⾊
vec3 ambient = ambientStrength * lightColor;
//最终颜⾊色 = 环境光颜⾊ * 物体颜色
vec3 result = ambient * objectColor;
gl_FragColor = vec4(result, 1.0);
}
2.发射光的计算
发射颜色 = 物体的反射材质颜色
3.漫反射光照计算
光照强度是光本身强度和光线与物体表面法线夹角cos的乘积.
如下图所示,有效的光照方向是与物体表面法线夹角在0~90度之间的
漫反射颜色 = 光源的漫反射颜色* 物体的漫反射材质颜色 * 漫反射因子(DiffuseFactor)
漫反射因子(DiffuseFactor) = max(0,dot(N,L) //是光线 与顶点法线向量的点积
GLSL代码如下所示:
uniform vec3 lightColor;
uniform vec3 lightPo;
uniform vec3 objectColor;
uniform vec3 viewPo;
varying vec3 outNormal;
//确保法线为单位向量,做归一化处理
vec3 norm = normalize(outNormal); //顶点指向光源 单位向量
vec3 lightDir = normalize(lightPo - FragPo); //得到两向量的cos值 ⼩于0则为0
float diff = max(dot(norm, lightDir),0.0); //得到漫反射的光源向量
vec3 diffuse = diff * lightColor;//光源的漫反射颜色*漫反射因子
vec3 result = diffuse * objectColor;//上一步的结果*物体的漫反射材质颜色
gl_FragColor = vec4(result,1.0);
4.镜面光计算
镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * 镜面因子
镜面因子(SpecularFactor) = power(max(0,dot(N,H)),shininess)
- H :视线向量E 与 光线向量L 的半向量
- dot(N,H):H,N的点积⼏几何意义,平⽅方线与法线夹角的cos值
- shiniess : ⾼光的反光度
-
如下图所示:
GLSL代码如下:
//镜⾯强度
float specularStrength = 0.5;
//顶点指向观察点的单位向量
vec3 viewDir = normalize(viewPo - FragPo);
//求得光线 在 顶点的反射线(传⼊光源指向顶点的向量)
vec3 reflectDir = reflect(-lightDir ,outNormal);
// 求得夹角cos值 取256次幂 注意 pow(float,float)函数参数类型
float spec = pow(max(dot(viewDir, reflectDir),0.0),256.0);
vec3 specular = specularStrength * spec * lightColor;
5.衰减因子计算
attenuation = 1.0 / (1.0 + a * dist + b * dist * dist)
衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平⽅)
环境光,漫反射光和镜⾯光的强度都会受距离的增⼤而衰减,只有发射光和全局环境光的强度不会受影响
GLSL代码如下:
//距离衰减常量
float constantPara = 1.0f;
//线性衰减常量
float linearPara = 0.09f;
//⼆次衰减因子
float quadraticPara = 0.032f;
//距离
float LFDistance = length(lightPo - FragPo);
//衰减因⼦
float lightWeakPara = 1.0/(constantPara + linearPara
* LFDistance + quadraticPara * (LFDistance*LFDistance));
聚光灯因子
首先计算聚光灯夹角的cos值
聚光灯夹⻆cos值 = power(max(0,dot(单位光源位置,单位光线向量)),聚光灯指数);
- 单位光线向量是从光源指向顶点的单位向量
- 聚光灯指数,表示聚光灯的亮度程度
- 公式解读:单位光源位置 * 单位光线向量点积 的 聚光灯指数次⽅。
聚光灯分为无过渡处理和有过渡处理。如下图所示:
增加过渡计算的聚光灯因子公式:
聚光灯因⼦ = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯⻆度cos值)/ (外环的聚光灯角度cos值- 内环聚光灯的⻆度的cos值),0,1);
可以从下图进行理解这个公式:
GLSL代码实现:
//(⼀些复杂的计算操作 应该让CPU做,提⾼效率,不变的量也建议外部传输,避免重复计算)
//内锥⻆cos值
float inCutOff = cos(radians(10.0f)); //外锥⻆cos值
float outCutOff = cos(radians(15.0f)); //聚光朝向
vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f);
//光源指向物体的向量 和 聚光朝向的 cos值
float theta = dot(lightDir ,normalize(-spotDir)); //内外锥⻆cos差值
float epsilon = inCutOff - outCutOff;
//clamp(a,b,c);若b<a<c 则函数返回值为a 若不是,则返回值最小为b 最⼤ 为c
// (theta - outCutOff)/epsilon 若theta的⻆度⼩于内锥角 则其值 >=1 若theta的角度大于外锥⻆ 则其值<=0 这样光线就在内外锥角之间平滑变化.
float intensity = clamp((theta - outCutOff)/epsilon,0.0,1.0);
6.光照计算综合公式
基本公式:
光照颜色 = (环境颜色 + 漫反射颜色 + 镜面反射颜色) * 衰减因子
终极公式:
光照颜⾊ = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜⾊ + 镜⾯反射颜⾊色) * 聚光灯效果 * 衰减因⼦
分为平面光和点光源
平面光计算
如下图所示为平面光示意图:
平面光计算的GLSL代码实现:
//环境因⼦
float ambientStrength = 0.3;
//镜⾯强度
float specularStrength = 2.0;
//反射强度
float reflectance = 256.0;
//平行光方向
vec3 paraLightDir = normalize(vec3(-0.2,-1.0,-0.3));
//环境光
vec3 ambient = ambientStrength * texture(Texture ,outTexCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
//当前顶点⾄光源的单位向量
vec3 lightDir = normalize(lightPo - FragPo);
float diff = max(dot(norm ,paraLightDir),0.0);
vec3 diffuse = diff *lightColor*texture(Texture ,outTexCoord).rgb;
//镜面反射
vec3 viewDir = normalize(viewPo - FragPo);
vec3 reflectDir = reflect(-paraLightDir ,outNormal);
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
vec3 specular = specularStrength * spec * texture(specularTexture ,outTexCoord).rgb;
//最终光照颜色
vec3 res = ambient + diffuse + specular;
FragColor = vec4(res,1.0);
点光源计算
如下图所示为点光源示意图:
点光源计算的GLSL代码实现:
//环境因⼦
float ambientStrength = 0.3;
//镜⾯强度
float specularStrength = 2.0;
//反射强度
float reflectance = 256.0;
//常量
float constantPara = 1.0f;
//线性部分因数
float linearPara = 0.09f;
//二次项部分因数
float quadraticPara = 0.032f;
//环境光
vec3 ambient = ambientStrength * texture(Texture ,outTexCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
vec3 lightDir = normalize(lightPo - FragPo); //当前顶点⾄光源的单位向量
//点光源
float diff = max(dot(norm ,lightDir),0.0); //光源与法线夹⻆
vec3 diffuse = diff * lightColor * texture(Texture ,outTexCoord).rgb;
//镜⾯反射
vec3 viewDir = normalize(viewPo - FragPo);
vec3 reflectDir = reflect(-lightDir ,outNormal);
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
vec3 specular = specularStrength * spec * texture(specularTexture ,outTexCoord).rgb;
//光线衰弱
float LFDistance = length(lightPo - FragPo);
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
vec3 res = (ambient + diffuse + specular)*lightWeakPara;
FragColor = vec4(res,1.0);
总结
以上就是按步骤来,然后最终完成光照计算的所有过程。在OpenGL ES中光照计算是很复杂的一部分内容了,重点是需要理解每一个步骤,然后能够在使用到光照的时候能够灵活计算就可以了。
GLKit 实现光照效果Demo
GLSL 实现光照效果Demo:待更新