OpenGL实现三种光源

上一篇实现了简单光照模型,使用的光照都来自于空间中的一个点

但现实世界中,我们有很多种类的光照,比如:
1.定向光(Directional Light)
2.点光源(Point Light)
3.聚光(Spotlight)

本文意图就是分别实现这几种光源。


1.定向光(也叫平行光)
例子:太阳光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行
因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的
可以定义一个光线方向向量 而不是位置向量来模拟一个定向光

数据结构

struct DirLight {
    vec3 direction; //方向

    vec3 ambient;   //ambient强度
    vec3 diffuse;   //diffuse强度
    vec3 specular;  //specular强度
};

光照公式

//计算平行光颜色
vec3 CalcDirLight(DirLight light)
{
	//贴图
    vec3 diffuseCol = texture(material.diffuse, uv).rgb;
    vec3 specularCol = texture(material.specular, uv).rgb;

    //环境光
    vec3 ambient = light.ambient * diffuseCol;

    //漫反射
    vec3 lightDir = (light.direction);
    vec3 normalDir = normalize(normalWS);
    vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

    //高光反射
    vec3 viewDir = normalize(cameraPos - positionWS);
    vec3 halfDir = normalize(viewDir + lightDir);
    vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

    vec3 result = ambient + diffuse + specular;
    return result;
}

2.点光源
例子:灯泡
分散在场景中的点,会朝着所有方向发光,会随着距离逐渐衰减

衰减:随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)
衰减公式:1 / (常数项 + 一次项*距离 + 二次项 + 距离的平方)

一般根据希望覆盖的距离设置,可以查表:
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=-Point+Light+Attenuation

数据结构

struct PointLight{
    vec3 position; //位置

    vec3 ambient;   //ambient强度
    vec3 diffuse;   //diffuse强度
    vec3 specular;  //specular强度

    float constant; //衰减公示常数项
    float linear;   //衰减公式一次项
    float quadratic;//衰减公式二次项

};

光照公式

//计算点光源颜色
vec3 CalcPointLight(PointLight light)
{
    //计算和光源的距离
    float distance = length(light.position - positionWS);

    //衰减公式
    float attenuation = 1.0  / 
                            ( light.constant 
                            + light.linear * distance 
                            + light.quadratic * (distance * distance) );

	//贴图
    vec3 diffuseCol = texture(material.diffuse, uv).rgb;
    vec3 specularCol = texture(material.specular, uv).rgb;

    //环境光
    vec3 ambient = light.ambient * diffuseCol;

    //漫反射
    vec3 lightDir = normalize(light.position - positionWS);
    vec3 normalDir = normalize(normalWS);
    vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

    //高光反射
    vec3 viewDir = normalize(cameraPos - positionWS);
    vec3 halfDir = normalize(viewDir + lightDir);
    vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

    vec3 result = (ambient + diffuse + specular) * attenuation;
    return result;
}

3.聚光灯
位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线
用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的

原理:计算LightDir向量和SpotDir向量之间的点积,并将它与切光角值对比

数据结构

struct SpotLight {
    vec3  position;
    vec3  direction;  //方向

    vec3 ambient;   //ambient强度
    vec3 diffuse;   //diffuse强度
    vec3 specular;  //specular强度

    float cutOff;     //半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
    float outerCutOff;//更大的切光角,用来做渐变
};

计算光照

//计算聚光灯颜色
vec3 CalcSpotLight(SpotLight light)
{
    //指向聚光灯的方向
    vec3 lightDir = normalize(light.position - positionWS);

    //和光源方向做点乘(取反保证方向一致)
    float theta = dot(-lightDir, normalize(light.direction));
    
    //余弦值比较为了性能
    //余弦值越接近1.0,它的角度就越小
    if (theta > light.cutOff)
    {
	    //贴图
        vec3 diffuseCol = texture(material.diffuse, uv).rgb;
        vec3 specularCol = texture(material.specular, uv).rgb;

        //环境光
        vec3 ambient = light.ambient * diffuseCol;

        //漫反射
        vec3 normalDir = normalize(normalWS);
        vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;

        //高光反射
        vec3 viewDir = normalize(cameraPos - positionWS);
        vec3 halfDir = normalize(viewDir + lightDir);
        vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;

        vec3 result = (ambient + diffuse + specular);

        //为了创建一种看起来边缘平滑的聚光
        //  需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)
        float intensity = clamp((theta - light.cutOff) / (light.cutOff - light.outerCutOff), 0.0, 1.0);  
        result *= intensity;

        return vec3(result);
    }
    else
    {
        //不在区域内,目前是黑色
        return vec3(0.0);
    }
}

在一个shader里面声明这些,可以叠加所有光照

void main()
{
    vec3 dirLightResult = CalcDirLight(dirLight);

    vec3 result = dirLightResult;
    for(int i = 0; i < NR_POINT_LIGHTS; i++)
    {
        vec3 pointLightResult = CalcPointLight(pointLights[i]);
        result += pointLightResult;
    }
    
    vec3 spotLightResult = CalcSpotLight(spotLight);
    result += spotLightResult;

    FragColor = vec4(result * objectColor, 1.0);
}

效果如图:

在这里插入图片描述

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值