最近在写一个系统仿真程序,需要尽可能真实地模拟人眼因瞳孔而产生的聚焦效果。我不知道游戏里的景深效果一般是怎么做的,不过猜测应该是使用深度缓冲加模糊特效吧,但是我这里是不能这么用的。
于是就打算在瞳孔上采样若干点,对应这些点产生若干相机,最后将图像叠加的做法。也就是glLookAt里前三个参数根据瞳孔采样位置改变;中间三个参数,也就是对焦点不变:
gluLookAt(eye.x, eye.y, eye.z, focus.x, focus.y, focus.z, 0.0, 1.0, 0.0);
这样子,只要程序中调节focus的位置,就能对焦到不同的位置上。
于是建立一个FBO,用于得到单个采样点的渲染画面,渲染到纹理。然后循环采样次数,每次用glBlend来对采样点得到的画面进行混合:
glBlendColor(1.0, 1.0, 1.0, 1.0/pupil.samples);
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
glEnable(GL_BLEND);
用glBlendColor来计算一个平均的透明度,然后用GL_CONSTANT_ALPHA和GL_ONE的组合。实现每画一张FBO纹理就将这张纹理按屏幕大小叠到原有画面上去。
但是问题出现了。当采样点比较少时可能看不出来,但是采样点一多,比方说50个,颜色就变得很奇怪,过渡变得不均匀。想了想,应该是这么算透明度时,由于颜色是256级整数,在计算过程中除不尽的小数都被抹掉了,导致了误差累积。50次叠加之后,实际上画面颜色只有256/50=5级了。
一开始不知道怎么办,甚至想把所有采样点都建一个纹理,用多重纹理扔到glsl里去算。但是想想,要是我想设几百个采样点呢?
于是想到了浮点纹理。在建立用于FBO渲染的纹理时,用到的一句glTexImage2D函数,函数的第三个参数internalformat,之前从来都只是设成GL_RGBA,没细想过其作用。皆因显示器的32位色正好对应了GL_RGBA,这么设置本来没什么问题。但是对于我现在这种情况,需要对像素进行细分时,8位的精度根本不够,因此需要换成浮点数,以得到更准确的结果。
于是可以将这个参数从GL_RGBA换成GL_RGBA16F甚至GL_RGBA32F。这样纹理在GPU内部就以0~1的16位或者32位浮点数的形式储存,进行像素运算时更为准确。