none
MFCクラスライブラリの描画APIとOpenGL描画APIの同居について RRS feed

  • 質問

  • いつもお世話になっております。

    VS2015 MFCアプリについて相談です。

    現在、アプリの描画部はMFCクラスライブラリを使用しております。

    ただ、一部サーフェスモデル化する必要が出てきたので、OpenGLが使えるか検討しています。

    下記のサイトを参考に色々と試して、MFC描画→OpenGL描画という流れにしようと思っているのですが、

    OpenGLの「glClear()」 でMFCで描画されたものも含めて、背景が全て塗り潰されてしまいます。

    「glClearColor()」でalpha値を0にしているので透明になるのでは?と思っているのですが、問題の箇所が分かる方、お願い致します。

    モデルビューワを作る(OpenGL+MFC) (Visual C++ 6.0版)

    void CBaseOpenGLView::OnDraw(CDC* pDC)
    {
       CBaseOpenGLDoc* pDoc = GetDocument();
       ASSERT_VALID(pDoc);
    
       if (!m_pDC) return;
       //*******************************
       // MFCで描画
       //*******************************
       CPen myPen, *pOldPen;
       myPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
       pOldPen = pDC->SelectObject(&myPen);
       pDC->MoveTo(10, 100);
       pDC->LineTo(100, 10);
       pDC->SelectObject(pOldPen);
    
       //*******************************
       // OpenGLで描画
       //*******************************
       ::wglMakeCurrent( m_pDC->GetSafeHdc(), m_hRC );
       ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // ここで背景が全て黒に塗り潰される
       ::glPushMatrix();
       // 描画処理
       RenderStockScene();
       ::glPopMatrix();
       ::glFinish();
       ::SwapBuffers( m_pDC->GetSafeHdc() );
    }
    
    // OpenGL初期化
    bool CBaseOpenGLView::InitializeOpenGL()
    {
       // デバイスコンテキストの取得、設定
       m_pDC = new CClientDC( this );
       if( NULL == m_pDC ) {
          ASSERT( !"m_pDC is NULL" );
          return false;
       }
       // OpenGL pixel format の設定
       static PIXELFORMATDESCRIPTOR pfd =
       {
          sizeof(PIXELFORMATDESCRIPTOR),
          1,
          PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
          PFD_TYPE_RGBA,
          24,
          0, 0, 0, 0, 0, 0,
          0,
          0,
          0,
          0, 0, 0, 0,
          32,
          0,
          0,
          PFD_MAIN_PLANE,
          0,
          0, 0, 0
       };
    
       int iPixelFormat;
       if ((iPixelFormat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd)) == 0) {
          ASSERT(!"ChoosePixelFormat is failed");
          return false;
       }
       if (::SetPixelFormat(m_pDC->GetSafeHdc(), iPixelFormat, &pfd) != TRUE) {
          ASSERT(!"SetPixelFormat is failed");
          return false;
       }
       // レンダリングコンテキストの作成
       if ((m_hRC = ::wglCreateContext(m_pDC->GetSafeHdc())) == 0) {
          ASSERT(!"wglCreateContext failed");
          return false;
       }
       // レンダリングコンテキストをカレントのデバイスコンテキストに設定
       if (::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC) != TRUE) {
          ASSERT(!"wglMakeCurrent failed");
          return false;
       }
       // クリアカラーの設定(alphaに0を指定)
       ::glClearColor( 0.0f, 0.0f, 0.0f, 0.0f);
       // デプスバッファのクリア
       ::glClearDepth( 1.0f );
       // デプステスト
       ::glEnable( GL_DEPTH_TEST );	// デプステスト
       //デプスファンク(同じか、手前にあるもので上描いていく)
       ::glDepthFunc( GL_LEQUAL );
    
       return true;
    }
    
    

    2017年5月22日 2:38

回答

  • 一つの案ですが、GDI を使った描画はビットマップへ行い。OpenGL の glClear の後に、glDrawPixels を使ってビットマップを画面に転送するのはどうでしょうか?以下サンプルです。(ビットマップの生成と破棄をOnDrawの中で行っていますが、速度が気になる場合は、生成と破棄を他の適切な場所に移動させてください。)

    void CBaseOpenGLView::OnDraw(CDC* pDC)
    {
    	COpengGL_GDI_mixed2Doc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	HDC hMemDC = NULL;
    	HBITMAP hBitmap = NULL;
    	HBITMAP hOldBitmap = NULL;
    	BITMAP bitmap = { 0 };
    
    	//*******************************
    	// GDI 描画用 ビットマップの生成
    	//*******************************
    	{
    		HDC hdc = ::GetDC(0);
    		hMemDC = CreateCompatibleDC(hdc);
    		BITMAPINFO bmpInfo = { 0 };
    		LPDWORD lpPixel = 0;
    		bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    		bmpInfo.bmiHeader.biWidth = 256; // GDIで描画する範囲(幅)を指定する
    		bmpInfo.bmiHeader.biHeight = 256; // GDIで描画する範囲(高さ)を指定する
    		bmpInfo.bmiHeader.biPlanes = 1;
    		bmpInfo.bmiHeader.biBitCount = 24;
    		bmpInfo.bmiHeader.biCompression = BI_RGB;
    		hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
    		hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
    		::ReleaseDC(0, hdc);
    
    		GetObject(hBitmap, sizeof(BITMAP), &bitmap);
    	}
    
    	//*******************************
    	// MFC で ビットマップ へ描画
    	//*******************************
    	{
    		CDC* pMemDC = CDC::FromHandle(hMemDC);
    		pMemDC->PatBlt(0, 0, bitmap.bmWidth, bitmap.bmHeight, WHITENESS);
    		CPen myPen, *pOldPen;
    		myPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    		pOldPen = pMemDC->SelectObject(&myPen);
    		pMemDC->MoveTo(10, 100);
    		pMemDC->LineTo(100, 10);
    		pMemDC->SelectObject(pOldPen);
    	}
    
    	//*******************************
    	// OpenGLで描画
    	//*******************************
    	RECT rect;
    	GetClientRect(&rect);
    	::glViewport(0, 0, rect.right, rect.bottom);
    
    	::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC);
    	::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ここで背景が全て黒に塗り潰される
    
    	// 上記で作成したビットマップを描画
    	::glPushMatrix();
    	::glLoadIdentity();
    	::glOrtho(0, rect.right, 0, rect.bottom, -1, 1);
    	::glRasterPos2i(0, rect.bottom - bitmap.bmHeight);
    	::glDrawPixels(bitmap.bmWidth, bitmap.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, bitmap.bmBits);
    	::glPopMatrix();
    
    	::glPushMatrix();
    	// 描画処理
    	RenderStockScene();
    	::glPopMatrix();
    	::glFinish();
    	::SwapBuffers(m_pDC->GetSafeHdc());
    
    	//*******************************
    	// ビットマップの破棄
    	//*******************************
    	SelectObject(hMemDC, hOldBitmap);
    	DeleteObject(hBitmap);
    	DeleteDC(hMemDC);
    }
    • 回答としてマーク dd_123 2017年5月22日 10:57
    2017年5月22日 9:29
  • CreateDCRenderTarget()を使ったのですが、
    このレンダはバインドされたHDCの全体を塗りつぶすので、それまでHDCに描画された内容は消えます。
    つまり、レンダ::BeginDraw();とEndDraw();間の内容が描画される全てとなります。
    この条件はOpenGLでも大差ないとの感想です。

    描画対象が図形のみであれば問題は無かったのですが、前景に文字列を描画する部分も多かったため、
    EndDraw()後、当初のバインド元になったHDCにGDIで文字列を描画することにしました。

    もしGDIの描画を背景にしたい場合は、一旦ビットマップに描画しておいて、
    レンダの描画開始後にこのビットマップを描画した後に、本来のレンダリングを行う
    というような方法を探ったであろうと考えます。
    しかし、実際にはやってみていません。あくまで可能性ということですね。

    • 回答としてマーク dd_123 2017年5月22日 10:57
    2017年5月22日 9:33

すべての返信

  • OpenGLは詳しくありません。手元にある(古い)マニュアルによると、glClear()の説明に
    (以下引用)
    The glClear function ignores the alpha function, blend function, logical operation, stenciling, texture mapping, and z-buffering.
    (引用終わり)

    とあるのでアルファ値は無視されるのではないでしょうか。
    この関数は描画の最初でコンテキスト全体を一色で塗りつぶすのが目的なので、まぁ仕方ないかもしれません。

    ところで、CDCの描画とOpenGLの描画の同居という事についてなのですが、

    OnDraw()の引数のCDCはCPaintDCのポインタで当該のViewのHWNDを使って::BeginPaint()して取得したものです。
    コードの通り、m_pDCのHDCは当該のViewのHWNDを使って構築したCClientDCであるので、::GetDC()して取得したものです。
    両者のHDCのバイナリ値は異なりますが、初期化時に使われたHWNDが同じなので、同じフィールドを指し示します。
    従って、m_pDCを具現化先に選択したOpenGLのコンテキストも、最終的には同じウインドウに描画されます。
    かつ、OpenGLのコンテキストはウインドウ全体を描画してしまいます。
    最も簡単なのは、GDIによる描画をOpenGLによる描画の「後で」行うことかもしれません。
    Direct2DとGDIを同居させる場合はその方法が可能でした。
    両社ともデバイス直でなくビットマップにしておいて、あとでブレンドするというのも有りかもしれません。

    2017年5月22日 6:30
  • 丁寧なご返信ありがとうございます。

    OpenGL→GDIによる描画の案ですが、サーフェスモデルを最前面に持ってくる必要があるので、難しいかと思います。

    ちなみに、Direct2Dで同居させたとのことですが、Direct2Dでも、「glClear()」のような塗り潰し関数を呼び出す必要があったのでしょうか。

    2017年5月22日 8:01
  • 一つの案ですが、GDI を使った描画はビットマップへ行い。OpenGL の glClear の後に、glDrawPixels を使ってビットマップを画面に転送するのはどうでしょうか?以下サンプルです。(ビットマップの生成と破棄をOnDrawの中で行っていますが、速度が気になる場合は、生成と破棄を他の適切な場所に移動させてください。)

    void CBaseOpenGLView::OnDraw(CDC* pDC)
    {
    	COpengGL_GDI_mixed2Doc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	HDC hMemDC = NULL;
    	HBITMAP hBitmap = NULL;
    	HBITMAP hOldBitmap = NULL;
    	BITMAP bitmap = { 0 };
    
    	//*******************************
    	// GDI 描画用 ビットマップの生成
    	//*******************************
    	{
    		HDC hdc = ::GetDC(0);
    		hMemDC = CreateCompatibleDC(hdc);
    		BITMAPINFO bmpInfo = { 0 };
    		LPDWORD lpPixel = 0;
    		bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    		bmpInfo.bmiHeader.biWidth = 256; // GDIで描画する範囲(幅)を指定する
    		bmpInfo.bmiHeader.biHeight = 256; // GDIで描画する範囲(高さ)を指定する
    		bmpInfo.bmiHeader.biPlanes = 1;
    		bmpInfo.bmiHeader.biBitCount = 24;
    		bmpInfo.bmiHeader.biCompression = BI_RGB;
    		hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
    		hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
    		::ReleaseDC(0, hdc);
    
    		GetObject(hBitmap, sizeof(BITMAP), &bitmap);
    	}
    
    	//*******************************
    	// MFC で ビットマップ へ描画
    	//*******************************
    	{
    		CDC* pMemDC = CDC::FromHandle(hMemDC);
    		pMemDC->PatBlt(0, 0, bitmap.bmWidth, bitmap.bmHeight, WHITENESS);
    		CPen myPen, *pOldPen;
    		myPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    		pOldPen = pMemDC->SelectObject(&myPen);
    		pMemDC->MoveTo(10, 100);
    		pMemDC->LineTo(100, 10);
    		pMemDC->SelectObject(pOldPen);
    	}
    
    	//*******************************
    	// OpenGLで描画
    	//*******************************
    	RECT rect;
    	GetClientRect(&rect);
    	::glViewport(0, 0, rect.right, rect.bottom);
    
    	::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC);
    	::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ここで背景が全て黒に塗り潰される
    
    	// 上記で作成したビットマップを描画
    	::glPushMatrix();
    	::glLoadIdentity();
    	::glOrtho(0, rect.right, 0, rect.bottom, -1, 1);
    	::glRasterPos2i(0, rect.bottom - bitmap.bmHeight);
    	::glDrawPixels(bitmap.bmWidth, bitmap.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, bitmap.bmBits);
    	::glPopMatrix();
    
    	::glPushMatrix();
    	// 描画処理
    	RenderStockScene();
    	::glPopMatrix();
    	::glFinish();
    	::SwapBuffers(m_pDC->GetSafeHdc());
    
    	//*******************************
    	// ビットマップの破棄
    	//*******************************
    	SelectObject(hMemDC, hOldBitmap);
    	DeleteObject(hBitmap);
    	DeleteDC(hMemDC);
    }
    • 回答としてマーク dd_123 2017年5月22日 10:57
    2017年5月22日 9:29
  • CreateDCRenderTarget()を使ったのですが、
    このレンダはバインドされたHDCの全体を塗りつぶすので、それまでHDCに描画された内容は消えます。
    つまり、レンダ::BeginDraw();とEndDraw();間の内容が描画される全てとなります。
    この条件はOpenGLでも大差ないとの感想です。

    描画対象が図形のみであれば問題は無かったのですが、前景に文字列を描画する部分も多かったため、
    EndDraw()後、当初のバインド元になったHDCにGDIで文字列を描画することにしました。

    もしGDIの描画を背景にしたい場合は、一旦ビットマップに描画しておいて、
    レンダの描画開始後にこのビットマップを描画した後に、本来のレンダリングを行う
    というような方法を探ったであろうと考えます。
    しかし、実際にはやってみていません。あくまで可能性ということですね。

    • 回答としてマーク dd_123 2017年5月22日 10:57
    2017年5月22日 9:33
  • kenjinote

    何故か、私の環境で正常に動作しないのですが、理論上、提供していただいた方法で問題は解決できるかと思います。

    もう少し、自分なりに理解しながら組んでみたいと思います。

    ありがとうございます。

    仲澤@失業者

    Direct2Dでも同じような問題があるのですね。

    手探り状態なので、貴重な情報をありがとうございます。

    ひとまず、kenjinote様の方法で、あれこれ試してみますので、解決とさせていただきます。

    皆様、ありがとうございました。

    2017年5月22日 10:57