Hi guys !
I’m working on a small program, a viewer for 3D cube objects more precisely.
The data I get is a 1D array of floats and a header for describing the dimension of the cube.
First, I plotted each points in the graphic space, and each points are black with an alpha value related to the 1D array corresponding value. Of course, OpenGL hates plotting points.
So I finally used Vertex array and the function glDrawArrays to draw quads. I stack these quads and apply to each quads a RGBA texture. I got the wanted rendering but the perfs are too bad : 1 FPS, and I wanted to reach 10 FPS.
But I don’t have any idea to improve the performance of my volume rendering.
Here is the rendering to see what I want to get (and what I got): (It’s a tangerine from a commercial X-ray scanner)
Here’s my code…
Everything is in here, except how to read the .r3d file but you don’t need.
The volume is typically 350350500 of size. (500 slices, with 350*350 textures).
Also, I apologise for the lack of comments but this program was not mean to but a long and taugh work.
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include "resource.h"
#include "FichierR3D.h"
HWND g_hWnd = NULL;
HDC g_hDC = NULL;
HGLRC g_hRC = NULL;
GLuint *g_textureID = NULL;
bool g_bBlendOutColorKey = true;
float g_fSpinX = 0.0f;
float g_fSpinY = 0.0f;
float g_zoom = -10.0f;
CFichierR3D fich;
R3D_HEADER header;
float* volume;
unsigned char *pImage_RGBA = NULL;
struct Vertex
{
float tu, tv;
float x, y, z;
};
Vertex *g_quadVertices;
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void loadTexture(void);
void init(void);
void render(void);
void shutDown(void);
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
WNDCLASSEX winClass;
MSG uMsg;
memset(&uMsg,0,sizeof(uMsg));
winClass.lpszClassName = "MY_WINDOWS_CLASS";
winClass.cbSize = sizeof(WNDCLASSEX);
winClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
winClass.lpfnWndProc = WindowProc;
winClass.hInstance = hInstance;
winClass.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_OPENGL_ICON);
winClass.hIconSm = LoadIcon(hInstance, (LPCTSTR)IDI_OPENGL_ICON);
winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winClass.lpszMenuName = NULL;
winClass.cbClsExtra = 0;
winClass.cbWndExtra = 0;
if( !RegisterClassEx(&winClass) )
return E_FAIL;
g_hWnd = CreateWindowEx( NULL, "MY_WINDOWS_CLASS",
"OpenGL - Color Key Transparency",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0, 0, 640, 480, NULL, NULL, hInstance, NULL );
if( g_hWnd == NULL )
return E_FAIL;
ShowWindow( g_hWnd, nCmdShow );
UpdateWindow( g_hWnd );
init();
while( uMsg.message != WM_QUIT )
{
if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &uMsg );
DispatchMessage( &uMsg );
}
else
render();
}
shutDown();
UnregisterClass( "MY_WINDOWS_CLASS", hInstance );
return uMsg.wParam;
}
LRESULT CALLBACK WindowProc( HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam )
{
static POINT ptLastMousePosit;
static POINT ptCurrentMousePosit;
static bool bMousing;
switch( msg )
{
case WM_KEYDOWN:
{
switch( wParam )
{
case VK_ESCAPE:
PostQuitMessage(0);
break;
case VK_F1:
g_bBlendOutColorKey = !g_bBlendOutColorKey;
break;
case VK_UP:
g_zoom = g_zoom + 0.5f;
break;
case VK_DOWN:
g_zoom = g_zoom - 0.5f;
break;
}
}
break;
case WM_LBUTTONDOWN:
{
ptLastMousePosit.x = ptCurrentMousePosit.x = LOWORD (lParam);
ptLastMousePosit.y = ptCurrentMousePosit.y = HIWORD (lParam);
bMousing = true;
}
break;
case WM_LBUTTONUP:
{
bMousing = false;
}
break;
case WM_MOUSEMOVE:
{
ptCurrentMousePosit.x = LOWORD (lParam);
ptCurrentMousePosit.y = HIWORD (lParam);
if( bMousing )
{
g_fSpinX -= (ptCurrentMousePosit.x - ptLastMousePosit.x);
g_fSpinY -= (ptCurrentMousePosit.y - ptLastMousePosit.y);
}
ptLastMousePosit.x = ptCurrentMousePosit.x;
ptLastMousePosit.y = ptCurrentMousePosit.y;
}
break;
case WM_SIZE:
{
int nWidth = LOWORD(lParam);
int nHeight = HIWORD(lParam);
glViewport(0, 0, nWidth, nHeight);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 45.0, (GLdouble)nWidth / (GLdouble)nHeight, 0.1, 100.0);
}
break;
case WM_CLOSE:
{
PostQuitMessage(0);
}
case WM_DESTROY:
{
PostQuitMessage(0);
}
break;
default:
{
return DefWindowProc( hWnd, msg, wParam, lParam );
}
break;
}
return 0;
}
void loadTexture(int s)
{
int i,j;
for( i = 0, j = 0 ; i < header.sliceHeight * header.sliceWidth*4; i += 4, j++)
{
pImage_RGBA[i] = 0;
pImage_RGBA[i+1] = 0;
pImage_RGBA[i+2] = 0;
pImage_RGBA[i+3] = (unsigned char)(volume[s * header.sliceHeight * header.sliceWidth + j]*10);
}
glBindTexture( GL_TEXTURE_2D, g_textureID[s] );
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, header.sliceHeight, header.sliceWidth, 0,
GL_RGBA, GL_UNSIGNED_BYTE, pImage_RGBA);
}
void init( void )
{
GLuint PixelFormat;
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 16;
pfd.cDepthBits = 16;
g_hDC = GetDC( g_hWnd );
PixelFormat = ChoosePixelFormat( g_hDC, &pfd );
SetPixelFormat( g_hDC, PixelFormat, &pfd);
g_hRC = wglCreateContext( g_hDC );
wglMakeCurrent( g_hDC, g_hRC );
//--------------------------------------------------
g_textureID = new GLuint[header.numberOfSlices];
glGenTextures( header.numberOfSlices, g_textureID );
char *FileName = "C:/mandarine.r3d";
fich.Open(FileName);
fich.GetHeader(&header);
volume = new float[header.numberOfSlices * header.sliceHeight * header.sliceWidth];
fich.ReadVolume(volume);
pImage_RGBA = new unsigned char[header.sliceHeight * header.sliceWidth * 4];
float min = volume[0], max = volume[0];
for (int k = 1; k < header.numberOfSlices * header.sliceHeight * header.sliceWidth; k++)
{
if (min > volume[k])
min = volume[k];
else if (max < volume[k])
max = volume[k];
}
for (int k = 0; k < header.numberOfSlices * header.sliceHeight * header.sliceWidth; k++)
volume[k] = (volume[k] - min) / (max - min);
//--------------------------------------------------
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
glEnable(GL_TEXTURE_2D);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 45.0f, 640.0f / 480.0f, 0.1f, 100.0f);
int i,j;
g_quadVertices = new Vertex[4*header.numberOfSlices];
for (i = 0 , j = 0 ; j < header.numberOfSlices ; i += 4, j++)
{
g_quadVertices[i].tu = 0.0f;
g_quadVertices[i].tv = 0.0f;
g_quadVertices[i].x = -1.0f;
g_quadVertices[i].y = -1.0f;
g_quadVertices[i].z = (float)(-header.numberOfSlices/2 + j)/120.0f;
g_quadVertices[i+1].tu = 1.0f;
g_quadVertices[i+1].tv = 0.0f;
g_quadVertices[i+1].x = 1.0f;
g_quadVertices[i+1].y = -1.0f;
g_quadVertices[i+1].z = (float)(-header.numberOfSlices/2 + j)/120.0f;
g_quadVertices[i+2].tu = 1.0f;
g_quadVertices[i+2].tv = 1.0f;
g_quadVertices[i+2].x = 1.0f;
g_quadVertices[i+2].y = 1.0f;
g_quadVertices[i+2].z = (float)(-header.numberOfSlices/2 + j)/120.0f;
g_quadVertices[i+3].tu = 0.0f;
g_quadVertices[i+3].tv = 1.0f;
g_quadVertices[i+3].x = -1.0f;
g_quadVertices[i+3].y = 1.0f;
g_quadVertices[i+3].z = (float)(-header.numberOfSlices/2 + j)/120.0f;
}
}
void shutDown( void )
{
glDeleteTextures( header.numberOfSlices, g_textureID );
delete[] g_quadVertices;
delete[] pImage_RGBA;
if( g_hRC != NULL )
{
wglMakeCurrent( NULL, NULL );
wglDeleteContext( g_hRC );
g_hRC = NULL;
}
if( g_hDC != NULL )
{
ReleaseDC( g_hWnd, g_hDC );
g_hDC = NULL;
}
}
void render( void )
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0f, 0.0f, g_zoom );
glRotatef( -g_fSpinY, 1.0f, 0.0f, 0.0f );
glRotatef( -g_fSpinX, 0.0f, 1.0f, 0.0f );
if( g_bBlendOutColorKey == true )
{
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
glInterleavedArrays( GL_T2F_V3F, 0, g_quadVertices );
int s;
for (s = 0 ; s < header.numberOfSlices ; s ++)
{
loadTexture(s);
glBindTexture( GL_TEXTURE_2D, g_textureID[s] );
glDrawArrays( GL_QUADS, s*4, 4 );
}
glDisable(GL_BLEND);
SwapBuffers( g_hDC );
}
The intereting part is in render and loadtexture.
Edit:// Of course, I tried to load all the texture at the same time and stored all the pointers to these textures in g_textureID[s].
But I need 350350500*4 bytes = 245 MB on the graphic card, which I don’t have (128 MB on the device). Actually, the program crash after having loaded the 200th slice (and I’m 100% sure it’s because memory’s full).
Any idea ? I heard about pixel shader or pixel buffer, but I don’t know if it will help me.