美文网首页
OpenGL学习12——光线投射器

OpenGL学习12——光线投射器

作者: 蓬篙人 | 来源:发表于2021-06-26 17:06 被阅读0次

    光线投射器(Light Casters)

    目前我们的渲染场景都只是应用单一来源的光源,现实世界中往往存在多种类型的光源。一个投射光线到物体上的光源我们称为光线投射器(light caster)。下面我们讨论几种光线投射器。

    1. 定向光源

    • 当一个光源建模为无限远,则称为定向光源(directional light),因为它的光线拥有相同的方向(平行),且独立于光源的位置。下图展示一种定向光源——太阳光:(图片取自书中
      定向光源——太阳光
    • 因为光的方向矢量保持不变,因此对于场景中的每个对象的光源计算都是相似的。我们可以通过定义光的方向矢量而不是位置矢量来建模定向光源。这样对于定向光源,在着色器中我们无需通过位置矢量计算光的方向矢量而是直接使用。
    struct Light{
        // vec3 position;  // 对于方向光源,不需要位置矢量
        vec3 direction;
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    };
    ...
    void main()
    {
        vec3 lightDir = normalize(-light.direction);  // 直接取反
        // 下面直接使用lightDir计算扩散光和镜面光
        ...
    }
    
    • 为了展示定向光源的效果,我使用坐标系统章节中产生多个立方体的方法,关键代码如下:
    for (unsigned int i = 0; i < 10; i++)
    {
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, cubePositions[i]);
        float angle = 20.0f * i;
        model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
        objectShader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    
    • 代码中直接设置光源的方向矢量。
    objectShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
    
    • 渲染效果


      定向光源效果

    2. 点光源

    • 点光源(point light)是在世界空间中指定位置向所有方向照射的光源,光线会随距离变暗(如灯光)。(图片取自书中
      点光源
    • 光线随照射距离逐渐降低强度的过程我们通常称为衰减(attenuation)。最简单的模拟衰减过程我们可以使用线性等式,但是线性等式的效果并不逼真。现实世界中,光的亮度在近距离内会快速衰减,超过一定距离之后衰减会变得缓慢很多。下面我们使用前人提出的公式来进行衰减模拟:
      F_{att}=\frac{1.0}{K_{c}+K_{l} *d+K_{q}*d^{2}}
      其中,d代表片元到光源的距离。要计算衰减值我们定义了(可配置的)3项:常数项K_{c},线性项K_{l}和二次项K_{q}
        1. 常数项一般设置为1.0,主要用于保证分母不小于1。
        1. 线性项与距离相乘,以线性的方式降低光的强度。
        1. 二次项与距离象限相乘,使光源进行二次衰减。与线性项相比,二次项在近距离的影响很小,但随着距离增加影响逐渐变大。
    • 上面公式的衰减效果。(图片取自书中
      衰减
    • 下面给出上述公式各项的常用值设置
    距离 常数项 线性项 二次项
    7 1.0 0.7 1.8
    13 1.0 0.35 0.44
    20 1.0 0.22 0.20
    32 1.0 0.14 0.07
    50 1.0 0.09 0.032
    65 1.0 0.07 0.017
    100 1.0 0.045 0.0075
    160 1.0 0.027 0.0028
    200 1.0 0.022 0.0019
    325 1.0 0.014 0.0007
    600 1.0 0.007 0.0002
    3250 1.0 0.0014 0.00007
    • 要实现光的衰减,我们在片元着色器添加公式中对应的3个项,并将原先定向光源时的方向矢量修改回位置矢量。
    struct Light{
        vec3 position;
    
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    
        float constant;
        float linear;
        float quadratic;
    };
    
    • 在片元着色器中完成片元与光源距离和衰减值的计算
    // 使用GLSL内置函数length
    float distance = length(light.position - FragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
    
    • 将计算后的衰减值分别乘以3个光分量值
    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    
    • 代码中设置各项的值(对照上方的表格,我们大概想覆盖范围为50)
    objectShader.setFloat("light.constant", 1.0f);
    objectShader.setFloat("light.linear", 0.09f);
    objectShader.setFloat("light.quadratic", 0.032f);
    
    • 渲染效果


      点光源效果

    3. 聚光灯(spotlight)

    • 聚光灯是处于场景中某个位置向指定方向投射光线的光源。结果就是只有在聚光灯特定半径范围的物体才会被照亮。
    • 在OpenGL中,聚光灯由其世界空间的位置方向和一个指定其半径的截断角度(cutoff angle)所定义。见下图展示:(图片取自书中
      聚光灯
        1. LightDir:从片元指向光源位置的矢量。
        1. SpotDir:聚光灯指向的矢量。
        1. Phi\phi:截断角度,指定了聚光灯的照射半径,处于该角度外的物体将不会被照亮。
        1. Theta\thetaLightDirSpotDir矢量之间的角度。
    • 闪光灯(flashlight)是一种处于观察者位置的聚光灯,一般指向观察者视角的正前方。闪光灯的位置和方向随着观察者的位置和朝向的变化而变化。下面我们使用闪光灯这种类型的聚光灯来展示场景的渲染效果。
    • 对于处于聚光灯下物体的片元颜色的计算,我们需要聚光灯的位置矢量,方向矢量和cutoff角度,我们在片元着色器的Light结构添加。
    struct Light{
        vec3 position;
        vec3 direction;
        float cutOff;
        ...
    };
    
    • 对于聚光灯,我们需要计算\theta角度并与cutoff\phi角度值进行比较,判断片元是否处于聚光灯的光线椎体内。注意,下面的计算使用的是\theta\phicos值进行比较。
    float theta = dot(lightDir, normalize(-light.direction));
    if(theta > light.cutOff)
    {
        // 完成光源计算
    }
    else
    {
        FragColor = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
    }
    
    • 代码中设置聚光灯参数的值。
    objectShader.setVec3("light.position", camera.Position);
    objectShader.setVec3("light.direction", camera.Front);
    objectShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
    
    • 渲染效果


      聚光灯效果

    4. 聚光灯边缘平滑

    • 从上面的渲染效果可以看出边缘界限十分清晰,现实中,聚光灯的边缘应该是逐步缓慢变暗的。要平滑聚光灯的边缘,我们需模拟一个具有内外光线椎体的聚光灯,光线从内锥体到外椎体之间逐渐变暗。
    • 对于具有内外光锥的聚光灯,我们需要再定义一个cos值来代表光线方向矢量和外光锥之间的角度。如果片元处于内外光锥之间,它的强度值应该处于1.0到0.0之间,如果处于内光锥内则为1.0,处于外光锥外则为0.0。要计算这样的值我们可以使用如下公式:
      I=\frac{\theta -\gamma}{\epsilon}
      其中,\epsilon是内光锥截断角度\phi和外光锥截断角度\gamma之差(\epsilon =\phi -\gamma)。而I就是当前片元的聚光灯强度值。
    • 对于上述公式,很难想象各个参数如何影响最终的渲染效果,下面我们给出一些样本值。
    \theta \theta角度 \phi(内cutoff) \phi角度 \gamma(外cutoff) \gamma角度 \epsilon I
    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.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.996 - 0.953 =
    0.0448
    0.966 - 0.953 /
    0.0448 = 0.29
    • 从上面的表格我们大致可以推测出聚光灯的强度值,当处于聚光灯外时值为负,当处于内光锥内时值大于1.0,而当处于内外光锥之间,取值则处于1.0到0.0之间。在片元着色器的计算中,我们使用clamp函数,将强度值限制到0.0和1.0之间,这样我们可以取消前面的if-else判断,将强度值直接应用于光分量。
    struct Light{
        vec3 position;
        vec3 direction;
        float cutOff;
        float outerCutOff;
        ...
    };
    
    int main()
    {
        float theta = dot(lightDir, normalize(-light.direction));
        float epsilon = light.cutOff - light.outerCutOff;
        float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); 
    
        ...
    
        diffuse *= intensity;
        specular *= intensity;
        ...
    }
    
    • 代码中设置cutoff值
    objectShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
    objectShader.setFloat("light.outerCutOff", glm::cos(glm::radians(17.5f)));
    
    • 渲染效果


      聚光灯边缘平滑效果

    相关文章

      网友评论

          本文标题:OpenGL学习12——光线投射器

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