目录
一 衰减
二 衰减公式
三 使用场景
四 代码实现
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校验后的效果肯定定更亮。