[openGL] 高级光照-Gamma矫正与衰减

时间:2024-04-20 15:52:54

目录

一 衰减

二 衰减公式

三 使用场景

四 代码实现

4.1 部分代码

4.2 未校验的效果

4.3  Gamma校验后的效果

4.4 总结


本章节源码 点击此处

一 衰减

在之前平行光和投光物的部分中,了解了光源的衰减,对于平行光来说是不需要衰减的,并且在衰减时对是否衰减环境光也要做出判断,

  • 比如有多个光源时可能就需要来衰减环境光了,不然多个光源会对同一处的环境光进行叠加,导致场景过亮,这就需要人为来判断了

注意: 对于衰减我们这里要记得这一步

我们可以将环境光分量保持不变,让环境光照不会随着距离减少,但是如果我们使用多于一个的光源,所有的环境光分量将会开始叠加,所以在这种情况下我们也希望衰减环境光照。简单实验一下,看看什么才能在你的环境中效果最好。

二 衰减公式

  • 而在真实的物理世界中,光线的衰减减和光源的距离的平方成反比。
  • 但是这个衰减公式的衰减效果是很强烈的,光源可能只会照亮一小圈,看起来可能不是那么真实,当然我们依旧可以使用我们之前一种使用的衰减方程(它对衰减的控制更为准确)。但是这里只是为了说明衰减和Gamma之间的关系。

公式1:

float attenuation = 1.0 / (distance * distance);

  • 我们还可以使用下面这个公式,这个衰减效果是在距离在比较小的时候更加实用。当然这里不关系距离,只关心衰减公式和Gamma的关系

公式2:

float attenuation = 1.0 / distance;

三 使用场景

当我们在不进行Gamma校正的时候我们使用公式2看起来会更加真实

  • 因为我们不进行校正的时候,监视器本事就会进行一个2.2次方的衰弱,这看起来其实是和公式一的几乎相同的
  • 如果我们使用了公式1也就是线性衰减,但是没有进行Gamma校正,那么试想而知,这个衰减也太强烈了。被衰减方程处理过的光源将会变得非常暗。

当我们进行Gamma校正的时候公式1会得到更好的效果。

  • 因为Gamma校正后,本身就会对光源有一个增强效果,也就是之前说的逆Gamma(1.0/2,2),然后将,相当于这个校正把衰减公式给减弱了。
  • 你可以这样理解,原来的二次函数的衰减公式,被Gamma矫正之后,拉回到了一次线性的一次衰减公式。

总结: 这说明了什么,哪怕是衰减,也会被监视器Gamma影响他的值,也就是说所有的计算最好都是在颜色的线性空间中是最好的。

四 代码实现

4.1 部分代码

  • 首先准备两个纹理 一个是带Gamma校正的地面纹理,一个是不带的
m_NoGammaTex = new QOpenGLTexture(QImage(":/wood.png").mirrored());
m_planeNoGammaMesh = processMesh(planeVertices,6,m_NoGammaTex->textureId());
QImage wall = QImage(":/wood.png").convertToFormat(QImage::Format_RGB888);
m_planeTex = new QOpenGLTexture(QOpenGLTexture::Target2D);
glBindTexture(GL_TEXTURE_2D, m_planeTex->textureId());

glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, wall.width(), wall.height(),
             0, GL_RGB , GL_UNSIGNED_BYTE, wall.bits());
glGenerateMipmap(GL_TEXTURE_2D);

m_planeMesh=processMesh(planeVertices,6,m_planeTex->textureId());

  • 使用按钮控制Gamma属性
    if(blin == true)
        m_planeMesh->Draw(shaderProgramObject);
    else
        m_planeNoGammaMesh->Draw(shaderProgramObject);
  • 着色器中的代码实现
#version 330 core

struct Material {
    sampler2D texture_diffuse1;
    sampler2D texture_specular1;
    float shininess;
};

struct Light {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;


uniform Material material;
out vec4 FragColor;

in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;

uniform vec3 viewPos;

uniform bool Gamma;

// 首先需要的是法向量 然后是顶点向量 以及光照向量 和视野向量 以及光照颜色
vec3 BlinnPhong(vec3 normal,vec3 fragPos,vec3 lightPos,vec3 lightColor)
{
    // 先进行计算漫反射 漫反射不存在BlinPhong的问题 和原来的计算方式保持一致
    // 光照的方向向量
    vec3 lightDir = normalize(lightPos - fragPos);
    // dot 用来计算两个向量之间的点积(夹角的cos值)
    float  diff = max(dot(lightDir,normal),0.0);
    vec3  diffuse = lightColor * diff;

    // 计算环境光照
    // 计算反射角 (入射角向量 乘以 法线向量) 参数不能互调
    vec3 reflectDir = reflect(-lightDir,normal);
    // 计算观察向量
    vec3 viewDir = normalize(viewPos - fragPos);
    // 计算半程向量
    vec3 hDir = normalize(viewDir + lightDir);
    // 计算镜面光
    float spec;
    spec = pow(max(dot(viewDir,lightDir),0.0f),64.0);
    vec3 specular = spec * lightColor;
    // 开始计算衰减
    // 获取光源距离
    float distance = length(lightPos - fragPos);

    // 计算衰减
    float attenuation = 1.0 / (Gamma ? distance * distance : distance);

    diffuse *= attenuation;
    specular *= attenuation;
    return diffuse + specular;
}


void main() {
    // 拿到漫反射纹理颜色
    vec3 diffuseTexColor = vec3(texture(material.texture_diffuse1,TexCoords));

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

    // if(Gamma == true)
    // {
    //     diffuseTexColor = pow(diffuseTexColor,vec3(1.0 / 2.2));
    // }

    vec3 norm = normalize(Normal);

    vec3 result;
    for(int i = -2; i < 2; ++i){
        vec3 lightColor=(2-i)*vec3(0.25);
        result += BlinnPhong(norm,FragPos,light.position+i*vec3(2,0.0,0.0),lightColor);
    }

    if(Gamma)
        ambient = pow(ambient,vec3(2.2));
    result += ambient;
    result*=diffuseTexColor*result;
    if(Gamma) result = pow(result, vec3(1.0/2.2));
    if(gl_FrontFacing==false)
        FragColor = vec4(result, 1.0);
}
  • 上面的for循环实际是是模拟了光源位置基于x轴的变化,用这种变化,模拟出从让光源沿着x轴移动。

4.2 未校验的效果

4.3  Gamma校验后的效果

4.4 总结

未校验:

  • 对于经过Gamma校验后中间色调(尤其是暗部)实际显示出来的亮度低于它们应有的线性亮度。

校验后:

  • 图像的整体亮度相比于未校正时会显得更亮,尤其是在中间色调和暗部区域

可能你会敏锐的发现,为什么上面的图片未校验的反而更亮呢? 试想一下我们未校验使用的纹理是不是没有经过处理,也就是说他的纹理颜色是位于sRGB空间中的,而我们并没有对他进行压缩(或者说逆Gamma处理),所以它输出的肯定会更亮,如果尝试在渲染时对(校正和不校正)使用同一个纹理,那么Gamma校验后的效果肯定定更亮。