当前位置:C++技术网 > 精选软件 > 绘图技术的闪烁原因探究3:深入分析绘图闪烁原因和详细解释解决闪烁办法

绘图技术的闪烁原因探究3:深入分析绘图闪烁原因和详细解释解决闪烁办法

更新时间:2016-04-22 00:49:26浏览次数:1+次

    通过《绘图技术的闪烁原因探究2:实验研究找绘图闪烁原因》文章,我们找到了广为流传的绘图闪烁的原因。然而,这些原因不能从根本上解释闪烁原因。所以,从这些原因着手解决闪烁,是治标不治本的,效果不明显。
    我们进一步分析一下,刷新的频率是时间上的反映,绘制的面积则是空间上的反映。这两个因素只是表现因素,它们是量变,不是质变。只有在数量上达到一定程度,才会凸显出问题。而当你出现这个闪烁问题的时候,也就表示,你在量上面是无法降低了。因此在这两方面是无法解决问题的。
    然而,从造成闪烁的必要条件即颜色反差来着手,也是没有意义。难道我们将所有的图形都画成一个颜色,或者相近的颜色来降低闪烁的强度吗?
    那么研究了这么久,实际上并没有找到问题的根本原因,所以我们也没有有效的办法来解决闪烁。要真正解决闪烁问题,我们还需要了解更多知识。下面听我慢慢讲。
    闪烁是在绘图产生的,闪烁也是针对人眼产生的。如果你不绘图,或者你不看屏幕,就不会觉得闪烁。绘图是必须要的,人眼也是必须看屏幕的,所以,两者都不能少。为了更深入了解这个问题,我们要从这两个方面下手。
    在人眼方面,我们无法改变什么,我们无法调节眼睛来适应屏幕。不过我们可以了解人眼的特性来帮助我们理解问题所在。
    人眼之所以能看到东西,而且看到的画面可以持久留存,并不是因为画面永久刻入了眼睛,而是有不断的刺激,也就保持了画面的连续。然而你闭上眼睛时,之前的画面并没有立即消失,而是有一点存留时间。这个时间叫做人眼的影像残留时间。一般在50毫秒到200毫秒之间。
    如果两次不同的画面切换低于人眼影像残留时间,也就是画面切换的太快,人眼就无法捕捉到这个变化,当人眼看到画面时已经是第二个画面了。所以此时我们看不到闪烁。闪烁其实就是两次画面的快速切换,然而切换的过程被人眼识别到了。如果一个人的眼睛很迟钝,影像残留时间很长,那么他的眼睛对于闪烁敏感度也就低很多,稍微快一点就感觉不到了。这样,敏锐度高的人看到了闪烁,敏锐度低的人却看不到。
    不管怎么样,我们只要计算机在绘制的时候,保证画面切换足够快,就不会出现闪烁了。从这里也就知道闪烁的一个必要条件,即多次绘制的画面切换间隔偏长。
    也就是说,人眼是感知闪烁的,计算机绘图是产生闪烁的。对于人眼我们是无法改变的,而对于计算机,正是我们要改变的。计算机是闪烁产生的源头,也是我们能够控制的一方。
    难道计算机绘图还不够快吗?还会让人眼感觉到画面的切换吗?是的。既然说到了计算机方面的原因,我们就深入探讨一下。
    现在的计算机运行速度确实很快,然而这个只是你的一个感性的认识罢了。计算机中每一个程序的执行,都是公平的轮流执行的。计算机再快,也不是为你一个程序而准备的。在计算机中有好多好多的线程,绘图的也就是其中一个线程,很可能你这个绘图的线程是一千个的一个,按照公平的轮流算法,平均执行概率为1/1000。只不过每一个线程的执行都会执行一小段时间,然后换为下一个线程执行。你一个动作的执行可能要执行很多次的中断。所以,计算机是快,但是也是所有线程共享的。这就好比中国很有钱,然而平均下来,人均又有多少钱呢?我们的绘图线程一样是这样,真正得到的执行时间并不会很多。
    而我们知道,闪烁的原因就是有颜色反差的连续两次的画面的切换被人眼捕捉到了,从而感受到了闪烁。实际上,知道这个根本原因,我们也只有一个办法来解决问题。那就是降低画面切换所需的时间。如果想绝对提高线程的执行速度是不可能的,这个由硬件决定。不过只要线程在执行,处理速度是可以满足的。问题就是在于线程的轮流切换执行,这个会导致线程总体执行变慢,导致多次绘制界面时间隔变长。然而线程的轮流是系统调度的事,我们无法左右。
    那么如何解决闪烁呢?答案是确定的,保证多次绘制间隔时间足够短即可。前面我们知道,刷新频率太快,或者绘制的面积太大,都会增加闪烁的几率。问题就在于,频率太高,会产生大量的刷新次数,高频率的环境下,总有一些时候会因为CPU忙不过来而耽误了你的线程的执行,从而让绘制的间隔时间变长。我们不能提高CPU的运行频率来解决这个问题,而只有从绘图本身出发。
    我们在绘图时,基本上是直接向窗口绘图。每一次GDI的绘图,都会直接输出到屏幕,也就是说,每一次GDI绘图,都会产生一次界面的刷新。如果一个界面有很多内容,那么一个画面绘制下来,可能会绘制好多次才完成一个画面的绘制。比如第一次刷一下背景,然后再涂一个颜色,然后在很多地方单独输出文字。而在计时器不停的刷新输出时,每一次计时器执行,都会将整个窗口绘制一次,而这一次实际上暗含了很多次的GDI绘图。而高频率的计时器再加上很多次的GDI输出,无疑加倍了输出的频率,这样往往就加重了CPU的负担,也就导致绘图线程经常得不到足够的时间绘图,也就导致多次连续的绘制时间间隔变长而产生严重的闪烁。这个原因是很明显的。
    同时,如果是大面积的绘制,每一个绘制操作都要处理比较多的面积,在高频率的情况下,这也是加重了CPU的负担,导致线程处理不及时。
    再者,因为一个窗口的画面的形成是经过了很多次的GDI绘图,这些绘图的多个画面又是有颜色反差的,所以闪烁的两个必要条件即颜色反差和画面切换时间太长都满足了,这样就产生了闪烁。
    所以,降低频率和减少绘图的面积都是辅助,并不能根本性解决问题。最好的解决办法是在颜色反差上着手。虽然我们无法将所有的反差消除,也不能消除,但是我们可以尽量将反差效果降低。通过将反差消除到最低,这样即使绘图变慢,因为两次的画面相差不大,所以也感觉不到闪烁。进一步的方法就是在需要变化的地方才绘制,不变化的地方不绘制,因为减少了绘画的面积,同时没有变化的内容不重复绘制,这样就大大消除了多次绘制的颜色反差效果,同时加快了绘制的速度也是画面切换的间隔非常低,人眼无法时间中间的绘制切换过程。
    那么如何降低画面的反差效果呢?当然不是用同一个颜色或者相近的颜色绘制。我们一个窗口的画面,基本上都是由很多次的绘制堆积而成的。每一次绘制都在前一次的画面上绘画,每一次绘画都会产生画面的切换,这样你看到的一个窗口的画面实际上是多次绘画的结果。这样多次绘画,就产生了画面色差的切换,产生闪烁。如果我们事先将窗口的所有绘制过程都画在内存中,然后再一次性的按照像素点一对一的复制到屏幕,这样在屏幕上只有一次绘制就显示了所有的内容。那么此时的连续的计时器刷新,也只是一个完整的画面的多次重复。在大部分的位置,基本都是一样的,只有局部的内容是变化的,所以在画面的颜色反差上就只有局部的内容了,这些内容也就是我们要看到变化的地方,所以看到了局部的内容的变化如数据的变化,我们理解为正常的变化,而不是闪烁。虽然从某种角度来说也是闪烁,但是我们一般不将这种变化称为闪烁。同时,如果我们将内存的像素数据直接一比一的复制到屏幕,中间没有任何的转换,所以速度极快。这样不仅在画面的颜色反差上降到最小了,而且在绘制的速度上也是大大提高了。这样处理之后,正常情况下不会再出现闪烁了。如果还想继续提高绘制的速度,可以在内存中形成整个画面后,只拷贝其中变化的一部分,这样复制到屏幕的内容变少了,速度也就因此提高了。
    我们这里提到的在内存中将多次绘制的内容存储起来,然后一次性贴到屏幕上,这个技术就是双缓冲技术。双缓冲技术更加准确的理解是间接一次绘图技术,当然这个名字是我说的,只是帮助理解。双缓冲的意思是将内存作为一级缓冲,在画面显示到屏幕时会先将数据复制到显卡缓存里,因此就出现了两次缓冲。我们直接在屏幕上绘制,也就是直接将数据放入显卡缓存,也就是一次缓冲。
    我想,到了这里,应该对于绘图闪烁原因和深入分析解决绘图闪烁有了全面的理解了。至于具体的编程实现双缓冲绘图将在下一篇文章讲解。
    本文对于绘图技术的学习非常有用,如果一遍没有看懂或者没有完全明白,请再看一遍,如果有任何问题,请留言提问。