You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenGL创建科学实验用闪烁刺激视频的正确方法

问题描述

我们为开展科学实验,需要开发一款显示闪烁刺激(如屏幕闪烁方块)的程序,采用C++与OpenGL实现。方案是在display函数中跟踪帧数,偶数帧绘制方块,奇数帧不绘制,同时实现FPS统计功能。

测试环境为Windows 10系统 + NVIDIA Geforce RTX 3080 Ti GPU,在两款显示器上的测试结果:

  • 144Hz显示器未限FPS时,方块有时闪烁、多数时间固定,FPS显示144;窗口状态变化时闪烁模式改变,但FPS仍为144
  • 60Hz显示器方块持续闪烁,FPS为60
  • 限FPS为120时闪烁更稳定,但仍有时方块固定,FPS显示120

我们推测问题出在帧绘制/擦除方式及GPU、OS未考虑的配置上,恳请提供解决思路。

代码片段
#include <iostream>
#include <Windows.h>
#include <gl/GL.h>    
#include <gl/GLU.h>    
#include <gl/glut.h>
#include <time.h>

# define FPS 120

using namespace std;

bool limitFPS = false;

int initial_time = time(NULL), final_time, frame_count = 0;
int frameNum = 0;

void timer(int) {
    glutPostRedisplay();
    glutTimerFunc(1000/FPS, timer, 0);

}

void idle() {
    glutPostRedisplay();
}

void display() {
    // display callback function
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity(); // resets coordinate system

    // draw something here
    if (frameNum % 2 == 0) {
        glBegin(GL_POLYGON);
        glColor3f(1.0, 1.0, 1.0);
        glVertex3f(-5.0, 5.0, 0.0);
        glVertex3f(-5.0, -5.0, 0.0);
        glVertex3f(5.0, -5.0, 0.0);
        glVertex3f(5.0, 5.0, 0.0);
        glEnd();
    }
    glutSwapBuffers(); // presents buffer in the screen
    frame_count++;
    frameNum++;
    final_time = time(NULL);

    if (final_time - initial_time > 0) {
        cout << "FPS: " << frame_count / (final_time - initial_time) << endl;
        frame_count = 0;
        initial_time = final_time;
    }
}

void init() {
    // initializing window params
    glClearColor(0.0, 0.0, 0.0, 1.0);   
}

void reshape(int w, int h) {
    // set viewport
    glViewport(0, 0, w, h);
    // set projection   
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-10, 10, -10, 10);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char** argv){
    cout << "Starting..." << endl;

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowPosition(200, 100);
    glutInitWindowSize(500, 500);   
    glutCreateWindow("Test Window"); // first create the window, then register the callback
    glutDisplayFunc(display);
    glutReshapeFunc(reshape); // how window reshape is going to be handled
    if (limitFPS)
        glutTimerFunc(1000/FPS, timer, 0); // function to perform animation, timer func will be called after X ms
    else
        glutIdleFunc(idle);
    init();
    glutMainLoop(); // requires a display callback: a function when the window needs to be drawn

    cout << "Finishing..." << endl;
    return 0;
}
解决思路

1. 同步绘制逻辑与显示器垂直刷新

当前代码的帧计数和显示器垂直同步(VSync)完全脱节,导致绘制的帧可能被GPU/显示器丢弃或重复显示,出现闪烁异常。需要强制开启垂直同步,并基于显示器刷新周期控制闪烁,而非程序自身帧计数:

  • 在OpenGL上下文创建后调用wglSwapIntervalEXT(1)开启垂直同步,让glutSwapBuffers()严格等待显示器垂直刷新后再交换缓冲区。
  • 改用基于时间的闪烁判断:用高精度计时器(如QueryPerformanceCounter())记录每次缓冲区交换的时间,计算与显示器刷新周期的倍数关系,决定是否绘制方块。比如144Hz显示器刷新周期约为6.94ms,每经过一个周期切换一次绘制状态。

2. 修正FPS统计与帧控制逻辑

  • 当前用time(NULL)统计FPS精度仅为秒级,改用QueryPerformanceCounter()计算准确的帧间隔和FPS,避免统计误差。
  • 手动限FPS的glutTimerFunc方式精度不足(Windows定时器本身存在误差),开启垂直同步后,无需手动限FPS,让VSync自动匹配显示器刷新率,再在此基础上控制闪烁频率。

3. 避免GPU帧合并优化

部分GPU会对连续空帧(奇数帧只清屏不绘制)进行合并优化,导致实际显示的帧并非严格交替状态。可以修改奇数帧逻辑:

  • 奇数帧不只是清屏,而是绘制一个全黑的全屏多边形,确保每帧都有明确的绘制操作,避免GPU跳过帧。
  • 或直接切换glClearColor的颜色值,奇数帧清屏为白色,偶数帧清屏为黑色,简化逻辑同时避免空帧。

4. 禁用窗口 compositor 优化

Windows 10的桌面 compositor(如DWM)可能对窗口内容进行额外渲染处理,干扰帧显示时序。可以尝试:

  • 启用窗口独占全屏模式,绕过DWM的 compositor 处理。
  • 调用DwmSetWindowAttribute禁用窗口的 compositor 优化(需Windows API支持)。

内容的提问来源于stack exchange,提问作者hernandezurbina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 23:34:57