夏天/isummer

Sun of my life !Talk is cheap, Show me the code! 追风赶月莫停留,平芜尽处是春山~

博客园 首页 新随笔 联系 管理

OpenGL的绘图机制是

  OpenGL的绘图方式与Windows一般的绘图方式是不同的,主要区别如下:
  (1)Windows采用的是GDI(Graphy Device Interface 图形设备接口)在设备描述表DC上进行绘图
  (2)OpenGL采用的是OpenGL相关的函数(OGL的命令)在渲染描述表RC上进行绘图
  (3)OpenGL使用的是特殊的像素格式
  在Windows中使用GDI绘图时必须指定在哪个设备环境DC中绘制,同同样的在使用OpenGL函数时也必须指定一个所谓的渲染环境。正如DC要存储GDI的绘制环境信息如笔,刷和字体等,RC也必须保存OpenGL所需的渲染信息如像素格式等。

  先用OpenGL的绘图上下文Rendering Contex(渲染上下文),用GL的命令把图绘制好。在把所绘制的结果通过WinOS的SwapBuffer()函数传给WinOS的设备上下文DeviceContex,DC(绘图设备上下文)。

  一般情况下,程序运行过程中,可能有多个DC,但是OpenGL只有一个RC。所以当一个DC完成画图操作后,要立刻释放RC,这样其他的设备上下文DC也可以进行绘制。

OpenGL渲染环境RC(Render Contex)的创建相关的函数

  渲染环境主要由以下六个wgl函数来管理:

  (1)HGLRC wglCreateContext( HDC hdc ),这个创建的过程比较耗时,一般放在初始化预言创建的过程中
  该函数用来创建一个OpenGL可用的渲染环境,并没有指定此RC就是当前线程的现行的RC。hdc必须是一个合法的支持至少16位色的屏幕设备描述表DC或内存设备描述表的句柄。该函数在调用前,设备描述表必须设置好适当的像素格式。成功创建渲染描述表RC之后,hdc可以释放或删除(此时并没有把hdc与RC关联起来,需要使用其他函数)。函数返回NULL值表示失败,否则返回值为渲染上下文的句柄。
  (2)BOOL wglDeleteContex( HGLRC hglrc )
  该函数删除一个RC,一般应用程序在删除RC之前,应使它成为非现行RC,不过,删除一个现行RC也是可以得。此时,OpenGL系统冲掉等待的绘图命令并使之成为非现行RC,后删除之。注意:删除一个属于别的线程的RC时会导致失败,所以只能管理本线程的RC。
  (3)HGLRC wglGetCurrentcontext( void )
  该函数返回线程的现行RC,如果线程无现行RC则返回NULL
  (4)HDC wglGetCurrentDC( void )
  该函数返回与线程现行RC关联的DC,如果线程无现行RC则返回NULL。
  (5)BOOL wglMakeCurrent( HDC hdc, HGLRC hglrc )
  该函数把hdc和hglrc关联起来,并使hglrc成为调用线程的现行RC,如果传给hglrc的值为NULL,则函数解除关联,并置线程的现行RC为非现行RC,测试忽略hdc参数。注意:传给该函数的hdc可以不是调用wglCreateContext时使用的值,但是,他们所关联的设备必须相同并且拥有相同的像素格式,因为创建RC是指借助于DC的基本的配置创建RC,并没有在创建的时候已经指定关联DC。

  注意:

   对于线程来说,一个线程可以创建(拥有)多个RC,一个RC可以由多个线程共享,但是只有一个RC可以成为“现行RC”,“现行RC”必然与一个DC相关联,否则RC不能成为现行RC

  一个线程在拥有现行RC进行绘图时,别的现场将无法同时绘图,因为当前的现行RC被占用。在使用RC时,不应该释放或者删除与之关联的DC。如果应用程序在整个生命期内保持一个现行RC,则应用程序也一直占有一个DC资源。Windows系统只有有限的DC资源。

MFC管理RC与DC的方式:

(1)在WM_CREATE消息响应时创建RC(因为创建RC非常耗时,所以在创建创建的时候创建RC),创建后立即释放DC(解除此RC对DC的独占,造成其他绘图不能使用DC);当WM_PAINT消息到来时,程序再获取DC句柄,并与RC关联起来(只有窗口重绘的时候,关联DC与RC,即在使用的时候占有DC,是合理的),绘图完成后,立即解除RC与DC得关联并释放DC(同上上);当WM_DESTROY消息到来时,程序只需简单的删除RC(如果在窗口销毁的过程中,还有OGL命令执行,则需要再次关联,然后删除RC,以及消除此线程创建的DC,释放资源)

(2)RC在程序开始时创建并使之成为现行RC。它将保持为现行RC直至程序结束。相应的,GETDC在程序开始调用,RELEASEDC在程序结束时才调用。此中方法的好处是在相应WM_PAINT消息时,无需调用十分耗时的wglMakeCurrent函数。一般它要消耗几千个时钟周期。但是为了避免造成“RC”独占DC现象,以及多线程有多个RC绘图的影响,一般采用:创建,释放关联; 绘图:关联,释放;销毁:释放

具体的MFC管理RC与DC的程序下文有展示:

www.cmblog.com/icmzn

OpenGL与具体系统相关的几个函数:

  glFlush: 将GL命令队列中的命令发送给“显卡”,并清空命令队列。发送完毕后,立刻返回。不能保证之前的显卡对这些命令队列的命令是否执行完毕,(可能显卡还没有图形绘制完毕)。glFlush就是强制刷新,要知道OpenGL是使用一条渲染管线线性处理命令的,一般情况下,我们提交给OpenGL的指令并不是马上送到显卡驱动程序里被GPU执行的,而是放到一个缓冲区里面,等这个缓冲区满了再一次过发到驱动程序里执行;很多时候只有几条指令是填充不满那个缓冲区的,这就是说这些指令根本没有被发送到驱动里,所以我们要调用glFlush来强制把这些指令送到驱动里进行处理。

  glFinish: 将GL命令队列中的命令发送给“显卡”, 并清空命令队列。GL需要等显卡执行完命令完全执行完毕后(显卡完成绘图),再返回。

  OpenGL,建议一般在绘图命令GL比较冗长的情况下,可以分段调用glFlush,清空命令队列并发送给显卡先执行这些命令,最后调用glFinish进行同步处理。如,在某些的场景绘制的过程中,渲染器如果有多个,渲染执行从shader0,一直渲染到shaderN,如果采用所有的额命令绘制完毕后在调用glFinish,则会造成CPU等待GPU进行绘图,造成CPU没有充分利用,反映到效果上就是图形卡卡的,或者就是对CPU的其他有影响,如影响程序的时钟系统,因为程序在主线程停在了等待GPU的反馈信号,而对其他模块产生影响。understand!

  可以将上述情形优化为:每个shader完成后,调用一次glFlush,强制GPU开始执行命令队列中的GL绘图命令,这样在最后的shader渲染后,调用glFinish,完成当前帧内容的绘制并进行同步,因为GPU已经将之前的GL命令执行的差不多了,所以此方式对于GL冗长的情况执行效率比较高。

使用OpenGL渲染时,提高CPU效率的方式:

  (1)使用多线程。

  将更新和渲染作为线程A,辅助计算入AI(智能控制)策略、地图生成作为线程B。则当线程A因为glFinish阻塞时,开启线程B预先进行CPU计算。直到线程B完成后,根据B的结果做出相应的操作。

  (2)使用CPU时间预算方法。先给出一帧所需要的时间预算,然后在调用glFinish之前判断是否有更多的时间需要等待,如果有,则调用glFlush后,直接进行“辅助计算”,完成或者超过预算时间后,在调用glFinish。(此时GPU的渲染帧的所有命令已经绘制完毕,只需要一条glFinish进行同步处理)。

对于SwapBuffer的理解:

  SwapBuffer的命令指示把前台和后台的“缓冲区”指针交换了一下,即把前台的内容编程后台的内容,把后台的内容变换到了前台。但是,but,这个函数并不执行变换后的buffer清理工作。

  所以,绘制当前帧的OpenGL模式方法是:(1)每一帧的绘制之前都需要先调用一次glClear函数,然后在绘制当前帧的内容。(2)用GL命令绘制当前帧的内容,在这里已经考虑glFlush,GlFinish的各种优化,当前帧内容的GL命令的最后,需要执行glFinish以完成GL命令与当前帧绘制的所有同步工作,只须等待前后台的缓存交换执行显示。(3)在GL命令绘制后,最后调用一次SwapBuffer,进行前后台的显示。

对wglMakeCurrent的理解

  wglMakeCurrent 函数设定OpenGL当前线程的渲染环境。以后这个线程所有的OpenGL调用都是在这个hdc标识的设备上绘制。你也可以使用wglMakeCurrent 函数来改变调用线程的当前渲染环境,使之不再是当前的渲染环境。But,但是,正如上文所述,一般由多个DC,竞争使用一个RC,则在使用此函数获取一个DC后,本DC执行完毕后,需要在最后释放RC,不能独占RC

函数原型如下:

BOOL wglMakeCurrent(
  HDC  hdc,      // device context of device that OpenGL calls are 
                 // to be drawn on,设备环境(绘图上下文)的句柄。调用这个函数的线程接下来的所有OpenGL调用都将在这个hdc所标识的设备上绘制。指定当前线程。
  HGLRC  hglrc   // OpenGL rendering context to be made the calling 
                 // thread's current rendering context 函数设定的OpenGL渲染环境的句柄,作为当前线程的渲染环境。仅限定当前线程。
);
注意:如果hglrc为NULL,函数将使调用线程的当前渲染环境不再作为当前的渲染环境,并释放这个渲染环境所使用的设备环境。这种情况下hdc参数将被忽略。
// link the Win Device Context with the OGL Rendering Context
  根据绘图设备上下文生成OGL的RC
m_hRC = wglCreateContext(m_pCDC->GetSafeHdc()); // specify the target DeviceContext (window) of the subsequent OGL calls 设置当前OGL的渲染环境RC对应的目标DC
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC); // free the target DeviceContext (window),释放DC,供其他DC使用本RC 释放DC,从而其他的DC可以使用本RC
wglMakeCurrent(NULL,NULL);

注意:(1)hdc参数的绘制表面必须是支持OpenGL的。

  它不需是wglCreateContext 创建hglrc时所传进来的hdc,但它必须是相同的设备并具有相同的像素格式。GDI转换的hdc将不被渲染环境所支持。当前的渲染环境将一直使用hdc设备环境直到它不再是当前渲染环境。

  (2)在切换到新的渲染环境之前,OpenGL将冲刷掉当前调用线程之前的所有渲染环境。

  (3)一个线程可以有一个渲染环境(RC)。所以一个多线程的进程可以有多个渲染环境。一个线程在调用任何OpenGL函数之前必须设置一个当前渲染环境(RC)。否则所有OpenGL调用都将忽略,因为当前的RC没有可以执行GL命令的DC,必然被忽略。

  (4)在某一时刻渲染环境(RC)只能属于一个线程,你不能使一个渲染环境同时属于多个线程,因为RC只有一个。

  (5)一个应用程序可以通过不同线程中不同的当渲染环境(RC)产生多个绘图,它支持各个线程都拥有自己的渲染环境的设备环境(DC)

  (6)如果有错误发生,wglMakeCurrent 函数在返回之前将使渲染环境不作为线程当前的渲染环境。避免异常情况的独自占有情况。

在视图View创建之前,需要对设备环境,与当前线程的渲染环境进行设置:此外,也需要对OGL的全局设置进行处理,如开启深度缓存,设置背景颜色清除深度缓存信息。

 1 int CGLEnabledView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
 2 {
 3     if (CView::OnCreate(lpCreateStruct) == -1) return -1;
 4 // OpenGL rendering context creation
 5     PIXELFORMATDESCRIPTOR pfd;
 6     int         n;
 7 // initialize the private member
 8     m_pCDC= new CClientDC(this);//当前线程窗口对应的设备绘制上下文即DC
 9 // choose the requested video mode
10     if (!bSetupPixelFormat()) return 0;
11 // ask the system if the video mode is supported
12     n=::GetPixelFormat(m_pCDC->GetSafeHdc());
13     ::DescribePixelFormat(m_pCDC->GetSafeHdc(),n,sizeof(pfd),&pfd);
14 // create a palette if the requested video mode has 256 colors (indexed mode)
15     CreateRGBPalette();
16 // link the Win Device Context with the OGL Rendering Context
17     m_hRC = wglCreateContext(m_pCDC->GetSafeHdc());  创建RC
18 // specify the target DeviceContext (window) of the subsequent OGL calls
19     wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);    设置当前RC对应的DC
20 // performs default setting of rendering mode,etc..
21     OnCreateGL();//OGL需要提前设置的配置属性。
22 // free the target DeviceContext (window)
23     wglMakeCurrent(NULL,NULL);               释放DC
24     return 0;
25 }
1 void CGLEnabledView::OnCreateGL()//执行的是对RC的全局设置
2 {
3 // perform hidden line/surface removal (enabling Z-Buffer)
4     glEnable(GL_DEPTH_TEST);
5 // set background color to black
6     glClearColor(0.f,0.f,0.f,1.0f );
7 // set clear Z-Buffer value
8     glClearDepth(1.0f);
9 }

 在View的OnDraw中,每次都要进行绘制处理,每次的处理流程都一样,这样用户只需要单独执行OnDrawGL();,就可以了,预设值以及善后就已经设置完毕。

void CGLEnabledView::OnDraw(CDC* pDC)
{
// prepare a semaphore 标志位
    static BOOL     bBusy = FALSE;
// use the semaphore to enter this critic section
    if(bBusy) return;
    bBusy = TRUE;
// specify the target DeviceContext of the subsequent OGL calls
    wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);//只需执行的是切换功能,不需要执行耗时的创建过程
// clear background 清空缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// call the virtual drawing procedure (to be overridden by user)
    //模板方法模式
    OnDrawGL();
// execute OGL commands (flush the OGL graphical pipeline)
    //所有的绘制命令后,必须执行此GL的glFInish同步命令
    glFinish();
// if double buffering is used it's time to swap the buffers,必须使用双缓存
    if (m_bDoubleBuffer) SwapBuffers(m_pCDC->GetSafeHdc());
// turn the semaphore "green"
    bBusy = FALSE;
// free the target DeviceContext (window),释放DC,供其他DC使用本RC
    wglMakeCurrent(NULL,NULL);
}

 View的onDestroy窗口销毁过程的动作:

void CGLEnabledView::OnDestroy() 
{
    OnDestroyGL();
// specify the target DeviceContext (window) of the subsequent OGL calls wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC); // remove all display lists for (int c=0;c<MAX_LISTS;c++) if(m_DispListVector[c]) glDeleteLists(m_DispListVector[c],1); // release definitely OGL Rendering Context if (m_hRC!=NULL) ::wglDeleteContext(m_hRC); // Select our palette out of the dc CPalette palDefault; palDefault.CreateStockObject(DEFAULT_PALETTE); m_pCDC->SelectPalette(&palDefault, FALSE); // destroy Win Device Context if(m_pCDC) delete m_pCDC; // finally call the base function CView::OnDestroy(); }

在MFC中实现视图中平移OGL中场景的思路

  OGL中的场景是客观存在的。为达到这一效果,可以通过控制视图窗口来实现。

  gllookat定义了观察坐标系VC在世界坐标系WC中的位置,其中定义了观察者的位置、观察的焦点、观察的正方向;

  glViewPort定义了视口的位置(Win窗口中的绘制区域位置,从裁剪投影体空间中【正交投影】【透视投影】)映射到窗口中的位置,像素为单位,默认窗口右下角为原点0,0,窗口的宽和高都作为默认视口值);

  glOrtho定义了正交投影的“裁剪体”的信息,在相对于观察者定义,即是在观察坐标系中的坐标。glPersertive也是观察者的坐标(相对观察者的位置)。

  (1)通过修改裁剪空间的位置,用鼠标控制,可以实现“OGL”鼠标点击平移效果

   在MFC中实现鼠标选择区域矩形,局部放大效果

  (1)鼠标左键UP相应函数中, 捕获两个点,像素位置;

  (2)转化为OGL中的世界坐标系WC中的点,获取OGL中的视图矩形VIEWRECT;

  (3)以,此VIEWRECT通过调整后,与原来的窗口视图保持相同给的横纵比,方式变形。然后将此VIEWRECT作为裁剪空间尺寸输出OGL命令。

  (4)在OnDraw中绘制,即可以显示出此效果。

  CWnd::OnSetCursor

  函数原型:afx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message );

  参数: pWnd 指定了包含光标的窗口指针。这个指针可能是临时的,不能被保存以供将来使用。 nHitTest 指定了击中测试区域代码。击中测试确定了光标的位置。 message 指定了鼠标消息。

  如果鼠标输入没有被捕获并且鼠标使光标在CWnd对象内移动,则框架调用这个成员函数。缺省的实现在处理之前调用父窗口的OnSetCursor。如果父窗口返回TRUE,则将停止进一步处理。调用父窗口使父窗口能够控制子窗口中光标的设置。如果光标不在客户区内,缺省的实现将光标设为箭头;如果是在客户区内,则将光标设为注册的类光标。

 

  最好的直接坐标系的视点位置:

  

 

  

 

end:

 

posted on 2016-08-04 02:08  夏天/isummer  阅读(1467)  评论(0编辑  收藏  举报