Blinn-Phong模型光照
Phong模型可以对光照有很好的模拟,但是在镜面反射时可能会有问题。在镜面反射时我们会看到一条明显的分界线,这是因为我们一开始如果观察向量和反射向量夹角超过90度,就钳制为0,但这种情况适用于漫反射,镜面反射效果不好。
所以Blinn-Phong模型引入了一个半程向量——观察方向与反射方向的角平分线向量h,然后计算h与法线向量的点乘
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;
这样Blinn-Phong模型的高光看起来会比Phong模型大一点,并且没有明显的分界线。
伽马矫正
Gamma也叫灰度系数,每种显示设备都有自己的Gamma值,都不相同,有一个公式:设备输出亮度 = 电压的Gamma次幂,任何设备Gamma基本上都不会等于1,等于1是一种理想的线性状态,这种理想状态是:如果电压和亮度都是在0到1的区间,那么多少电压就等于多少亮度。
对于CRT显示器,Gamma通常为2.2,实际显示的通常会比预期的暗;Gamma 0.45时(2.2的倒数)就会比预期的亮。显示器所显示出来的图像和线性图像的最小亮度是相同的,它们最大的亮度也是相同的;只是中间亮度部分会被压暗。
2.2通常是是大多数显示设备的大概平均gamma值。基于gamma2.2的颜色空间叫做sRGB颜色空间
一、什么是伽马矫正
Gamma矫正就是在显示到显示器之前加一个Gamma2.2的倒数Gamma0.45进行矫正。我们在颜色显示到显示器的时候把每个颜色输出都加上这个翻转的Gamma曲线,这样应用了显示器Gamma以后最终的颜色将会变为线性的。
举个例子,我们想要将暗红色 ( 0.5 , 0.0 , 0.0 ) (0.5, 0.0, 0.0) (0.5,0.0,0.0)显示在屏幕上,线性的颜色显示出来相当于降低了2.2次幂的亮度,即 ( 0.5 , 0.0 , 0.0 ) 2.2 = ( 0.218 , 0.0 , 0.0 ) (0.5, 0.0, 0.0)^{2.2} = (0.218, 0.0, 0.0) (0.5,0.0,0.0)2.2=(0.218,0.0,0.0) ,亮度会减弱。如果进行了Gamma矫正,即 ( 0.5 , 0.0 , 0.0 ) 0.45 = ( 0.73 , 0.0 , 0.0 ) (0.5, 0.0, 0.0)^{0.45} = (0.73, 0.0, 0.0) (0.5,0.0,0.0)0.45=(0.73,0.0,0.0) ,然后屏幕再降低2.2次幂亮度时会变回线性空间 ( 0.73 , 0.0 , 0.0 ) 2.2 = ( 0.5 , 0.0 , 0.0 ) (0.73, 0.0, 0.0)^{2.2} = (0.5, 0.0, 0.0) (0.73,0.0,0.0)2.2=(0.5,0.0,0.0)
gamma校正使你可以在线性空间中进行操作。因为线性空间更符合物理世界,大多数物理公式现在都可以获得较好效果,比如真实的光的衰减。你的光照越真实,使用gamma校正获得漂亮的效果就越容易。
二、伽马矫正的方式
1. OpenGL内置的sRGB framebuffer 支持
这个方法实现简单,但是我们可控性就很少,因为是基于硬件自动完成的。我们只需要通过调用 GL_FRAMEBUFFER_SRGB 告诉OpenGL后续的绘图命令将在颜色存储到颜色缓冲区之前进行Gamma矫正
glEnable(GL_FRAMEBUFFER_SRGB);
gamma校正将把线性颜色空间转变为非线性空间,所以在最后一步进行gamma校正是极其重要的。
2. 在我们自己的片段着色器中进行
我们在片元着色器的最后应用gamma矫正
void main()
{
// do super fancy lighting
[...]
// apply gamma correction
float gamma = 2.2;
fragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}
但是我们需要在每个片元着色器中都要添加这步操作。不过我们也可以在后处理阶段直接应用到纹理中,这样只要做一次就好
三、sRGB纹理
因为监视器总是在sRGB空间中显示应用了gamma的颜色,所以我们在制作图片的时候,我们创建或编辑的图片并不是在线性空间,而是在sRGB空间中(sRGB空间定义的gamma接近于2.2)
如果我们把纹理应用到渲染应用中,因为现在我们对渲染应用也进行了Gamma矫正,所以对于那些在制作时就已经进行Gamma矫正的纹理,就会变的更亮(被两次Gamma矫正)所以我们需要将纹理勾选为sRGB纹理,这样OpenGL将自动把颜色校正到线性空间中,这样我们所使用的所有颜色值都是在线性空间中的了。
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
但并不是所有纹理都需要进行sRGB设置,漫反射纹理是在sRGB空间中,但是用于检索光照的高光贴图和法线贴图几乎总是在线性空间中的,所以这两个就不用再设置为sRGB
四、衰减 Attenuation
在真实世界中,光照衰减近似于与距离平方成反比
float attenuation = 1.0 / (distance * distance);
在应用Gamma矫正之前,显示器中这个衰减效果有点太过强烈而不真实,所以我们使用了另外一个线性衰减函数
float attenuation = 1.0 / distance;
在应用Gamma矫正之后,上面真实世界的二次衰减函数就更加真实。这种差异产生的原因是,光的衰减方程改变了亮度值,而且屏幕上显示出来的也不是线性空间,在监视器上效果最好的衰减方程,并不是符合物理的。