OpenGL ES旅行·在路上摄影

OpenGL ES光照计算

2019-06-20  本文已影响6人  聪莞

光照基础

  1. 环境光照:利用环境光可以描述一块区域的亮度,通常在场景中,环境光的颜色是一个常量
  2. 漫反射光照:光线向所有方向均匀的散射
  3. 镜面光照:反射的路线与入射方向和表面法线形成的反射方向保持一致,镜面反射依赖于观察者所处的位置.当反射方向指向观察者时,光线最强,当反射光线离开观察者时,光线强度呈指数下降
image.png

光照特性

  1. 发射光:物体本身发光
  2. 环境光:在环境中散射的光,无法判断方向
  3. 漫反射光:光线来着某个方向,在物体各个方向反射
  4. 镜面高光:光线来着特定的方向,在物体表面以特定的方向反射

在物理学中,光线如果射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。光线如果射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。现实生活中的物体在反射光线时,并不是绝对的镜面反射或漫反射,但可以看成是这两种反射的叠加。对于光源发出的光线,可以分别设置其经过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也可以分别设置光线经过镜面反射和漫反射后的光线强度。这些因素综合起来,就形成了最终的光照效果。

材质属性

  1. 泛射材质
  2. 漫反射材质
  3. 镜面泛射材质
  4. 发射材质

材质是指接受光照的各种物体的表面,由于物体如何反射光线只由物体表面决定(OpenGL中没有考虑光的折射),材质特点就决定了物体反射光线的特点。

光照计算

环境光计算

环境光 = 光源的环境光颜色 * 物体的材质颜色

varying vec3 objectColor;
void main() {
//设定有%20的光到达物体表面
float ambientStrength = 0.2;
//环境光颜色
vec3 ambient = ambientStrength * lightColor;
//最终颜色
vec3 result = ambient * objectColor;
gl_FragColor = vec4(result,1.0);
}

发射光计算

发射颜色 = 物体的反射材质颜色

漫反射光照计算

image.png

光照的方向(顶点指向灯泡的向量) 和 表面法线会有一个角度Θ,根据自然现象,Θ越大,光照越弱,角度越小,光照越强。
假设:Ld表示光源漫反射颜色,Od表示某个物体的漫反射颜色,那么漫反射公式就是 Ld * Kd * cos(Θ),而cos(Θ)又等于s和n的点乘,所以漫反射的计算公式为:(DiffuseFactor为漫反射因子)

漫反射颜色 = 光源的漫反射颜色 * 物体漫反射材质颜色 *DiffuseFactor
DiffuseFactor = max(0,dot(s,n))
uniform vec3 lightColor;    //光源颜色
uniform vec3 lightPo;      //光源位置
uniform vec3 objectColor;    //物体颜色
uniform vec3 viewPo;      //物体位置
uniform vec3 outNormal;  //当前顶点平面的法向量

//确保法线为单位向量
vec3 norm = normalize(outNormal);
//顶点指向光源的单位向量
vec3 lightDir = normalize(lightPo - viewPo);
//得到cos值,小于0则为0
float diff = max(dot(norm,lightDir),0);

vec3 result = lightColor * objectColor * diff;
gl_FragColor = vec4(result,1.0);

镜面光计算

反射的方向跟观察者有一个夹角Θ,假设镜面指数为reflectance,那么镜面反射因子计算为:
SpecularFactor = power(max(dot(camera_direction,reflected_light),0),reflectance);
Power(Number,Power)。其中Number表示底数,Power表示幂值。
如2的10次方,可以写为:POWER(2,10)。

假设specularStrength为镜面强度,镜面光计算为:

result = specularStrength *SpecularFactor * objectColor * lightColor
//镜面强度
float specularStrength = 0.5;
//顶点指向观察者的单位向量
vec3 viewDir = normalize(viewPo - FragPo);
//求反射线(光源基于法线为outNormal的平面产生的反射线)
vec3 reflectDir = reflect(-lightDir,outNormal);
//求得cos值,取256次幂
flost spec = pow(max(dot(viewDir,reflectDir),0),256);
//求得镜面光照
vec3 result = specularStrenth * spec * objectColor *lightColor

gl_FragColor = vec4(result,1.0);

但是这种算法的每一次计算,都涉及到反射光线, 但实际中,反射光线可能有无数条, 计算量太大, 能否有一种简化模型呢?
一种办法是, 在光源位置向量与观察者向量, 取一个中间向量, 叫做平分向量H, 如图所示:


image.png

当H和法线N重合时, 反射到观察者的光线最近, 当H和法线N角度变大时,光线减弱。
所以我们可以调整夹角为 H和N的角度 。
法线计算公式:


image.png
平分向量H的计算:

vec3 H = normalize(L + V)

光照计算

光照颜色 = (环境颜色 + 漫反射颜色 + 镜面反射颜色) * 衰减因子

衰减因子计算公式

image.png

衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平方)
距离衰减常量,线性衰减常量和二次衰减常量均为常量值。

注意!环境光,漫反射光和镜⾯光的强度都会受距离的增大而衰减,只有发射光和全局环境光的强度不会受影响。

float constantPara = 1.0f;    //距离衰减常量
float linearPara = 0.09f;    //线性衰减常量
float quadraticPara = 0.032;  //二次衰减因子
float distance = length(lightPo - FragPo); //距离

//衰减因子
float lightWeakPara = 1.0/(constantPara + linearPara * distance + quadraticPara *  distance * distance);

聚光灯因子(聚光灯 可以想象成手电筒这样的光源)

聚光灯夹角cos值 = power(max(0,dot(单位光源位置,单位光线向量量)),聚光灯指数);
单位光线向量是从光源指向顶点的单位向量、
聚光灯指数,表示聚光灯的亮度成都、
公式解读:
单位光源位置 * 单位光线向量点积的聚光灯指数次⽅

聚光灯过渡处理:


image.png

增加过渡计算:
聚光灯因子 = clamp((外环的聚光灯⻆度cos值 - 当前顶点的聚光灯⻆度cos值) / (外环的聚光灯角度cos值- 内环聚光灯的角度的cos值),0,1);

clamp()为区间指定函数,将结果控制在 0 - 1,即小于0为0,大于1为1.

    //聚光灯切角 (一些复杂的计算操作 应该让CPU做,提高效率,不变的量也建议外部传输,避免重复计算)
    float inCutOff = cos(radians(10.0f));
    float outCutOff = cos(radians(15.0f));
    vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f);
    
    //聚光灯因子 = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值)/(外环的聚光灯角度cos值- 内环聚光灯的角度的cos值),0,1);
    float theta = dot(lightDir,normalize(-spotDir));
    //(外环的聚光灯角度cos值- 内环聚光灯的角度的cos值)
    float epsilon  = inCutOff - outCutOff;
    //(外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值) / (外环的聚光灯角度cos值- 内环聚光灯的角度的cos值)
    float intensity = clamp((theta - outCutOff)/epsilon,0.0,1.0);

光照最终计算公式:

光照颜色 = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜色 + 镜面反射颜色) * 聚光灯效果 * 衰减因⼦

光源分多种,不容的光源类型(点光源、聚光灯、平行光),计算方式略有不同。开发中需要根据实际场景进行合适的选择和综合计算。
上一篇下一篇

猜你喜欢

热点阅读