OpenGL ES光照计算

作者: 聪莞 | 来源:发表于2019-06-20 10:46 被阅读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);
    

    光照最终计算公式:

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

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

    相关文章

      网友评论

        本文标题:OpenGL ES光照计算

        本文链接:https://www.haomeiwen.com/subject/jtckqctx.html