nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: nuclear@0: #include nuclear@0: #ifndef __APPLE__ nuclear@0: #include nuclear@0: #else nuclear@0: #include nuclear@0: #endif nuclear@0: nuclear@0: #include nuclear@0: #include nuclear@0: #include "sdr.h" nuclear@0: nuclear@3: #define XFER_MAP_SZ 512 nuclear@3: nuclear@0: struct slice_file { nuclear@0: char *name; nuclear@0: struct slice_file *next; nuclear@0: }; nuclear@0: nuclear@5: enum { nuclear@5: UIMODE_DEFAULT, nuclear@5: UIMODE_XFER, nuclear@5: UIMODE_CURSOR nuclear@5: }; nuclear@5: nuclear@0: int init(void); nuclear@0: void disp(void); nuclear@4: void render_volume(void); nuclear@5: void draw_slice(void); nuclear@4: void draw_xfer_func(void); nuclear@0: void reshape(int x, int y); nuclear@0: void keyb(unsigned char key, int x, int y); nuclear@4: void keyb_up(unsigned char key, int x, int y); nuclear@0: void mouse(int bn, int state, int x, int y); nuclear@0: void motion(int x, int y); nuclear@0: int parse_args(int argc, char **argv); nuclear@0: nuclear@1: static void create_ray_texture(int xsz, int ysz, float vfov, vec2_t *tex_scale); nuclear@0: static vec3_t get_primary_ray_dir(int x, int y, int w, int h, float vfov_deg); nuclear@0: static int round_pow2(int x); nuclear@3: static void create_transfer_map(float mean, float sdev); nuclear@0: nuclear@0: float cam_theta = 0, cam_phi = 0, cam_dist = 4.0; nuclear@0: float cam_x, cam_y, cam_z; nuclear@0: nuclear@0: vec2_t tex_scale; nuclear@0: struct slice_file *flist; nuclear@0: int nslices; nuclear@5: unsigned int vol_sdr, slice_sdr, vol_tex, ray_tex; nuclear@0: int win_xsz, win_ysz; nuclear@1: int raytex_needs_recalc = 1; nuclear@0: nuclear@3: unsigned int xfer_tex; nuclear@7: float xfer_mean = 0.7, xfer_sdev = 0.1; nuclear@3: int xfertex_needs_recalc = 1; nuclear@3: nuclear@5: static int uimode; nuclear@7: static float cur_z = 0.0; nuclear@7: static float ray_step = 0.01; nuclear@4: nuclear@0: int main(int argc, char **argv) nuclear@0: { nuclear@0: glutInit(&argc, argv); nuclear@0: glutInitWindowSize(1280, 720); nuclear@0: glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); nuclear@0: nuclear@0: if(parse_args(argc, argv) == -1) { nuclear@0: return 1; nuclear@0: } nuclear@0: nuclear@0: glutCreateWindow("Volume Raytracer"); nuclear@0: nuclear@0: glutDisplayFunc(disp); nuclear@0: glutReshapeFunc(reshape); nuclear@0: glutKeyboardFunc(keyb); nuclear@4: glutKeyboardUpFunc(keyb_up); nuclear@0: glutMouseFunc(mouse); nuclear@0: glutMotionFunc(motion); nuclear@0: nuclear@0: glewInit(); nuclear@0: nuclear@0: if(init() == -1) { nuclear@0: return 1; nuclear@0: } nuclear@0: nuclear@0: glutMainLoop(); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: int init(void) nuclear@0: { nuclear@0: int i, vol_xsz, vol_ysz; nuclear@0: nuclear@5: if(!(vol_sdr = create_program_load("volray.v.glsl", "volray.p.glsl"))) { nuclear@1: return -1; nuclear@0: } nuclear@5: set_uniform_int(vol_sdr, "volume", 0); nuclear@5: set_uniform_int(vol_sdr, "ray_tex", 1); nuclear@5: set_uniform_int(vol_sdr, "xfer_tex", 2); nuclear@7: set_uniform_float(vol_sdr, "ray_step", ray_step); nuclear@7: set_uniform_float(vol_sdr, "zclip", cur_z); nuclear@5: nuclear@5: if(!(slice_sdr = create_program_load(0, "slice.p.glsl"))) { nuclear@5: return -1; nuclear@5: } nuclear@5: set_uniform_int(slice_sdr, "volume", 0); nuclear@5: set_uniform_int(slice_sdr, "xfer_tex", 1); nuclear@0: nuclear@0: glGenTextures(1, &vol_tex); nuclear@0: glBindTexture(GL_TEXTURE_3D, vol_tex); nuclear@0: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); nuclear@0: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); nuclear@4: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); nuclear@4: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); nuclear@4: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); nuclear@0: nuclear@0: for(i=0; inext; nuclear@0: nuclear@0: if(!(pix = img_load_pixels(sfile->name, &xsz, &ysz, IMG_FMT_RGBA32))) { nuclear@0: fprintf(stderr, "failed to load image: %s\n", sfile->name); nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: if(i == 0) { nuclear@0: /* allocate storage for the texture */ nuclear@7: glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, xsz, ysz, nslices, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); nuclear@0: nuclear@0: vol_xsz = xsz; nuclear@0: vol_ysz = ysz; nuclear@0: nuclear@0: } else { nuclear@0: if(xsz != vol_xsz || ysz != vol_ysz) { nuclear@0: fprintf(stderr, "%s: inconsistent slice size: %dx%d. expected: %dx%d\n", nuclear@0: sfile->name, xsz, ysz, vol_xsz, vol_ysz); nuclear@0: img_free_pixels(pix); nuclear@0: return -1; nuclear@0: } nuclear@0: } nuclear@0: free(sfile); nuclear@0: nuclear@0: glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, i, xsz, ysz, 1, GL_RGBA, GL_UNSIGNED_BYTE, pix); nuclear@0: img_free_pixels(pix); nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: void disp(void) nuclear@0: { nuclear@4: /* recalculate primary ray texture if needed */ nuclear@1: if(raytex_needs_recalc) { nuclear@1: create_ray_texture(win_xsz, win_ysz, 50.0, &tex_scale); nuclear@1: } nuclear@4: /* recalculate transfer function texture if needed */ nuclear@3: if(xfertex_needs_recalc) { nuclear@3: create_transfer_map(xfer_mean, xfer_sdev); nuclear@3: } nuclear@1: nuclear@4: render_volume(); nuclear@5: draw_slice(); nuclear@4: draw_xfer_func(); nuclear@4: nuclear@4: glutSwapBuffers(); nuclear@4: assert(glGetError() == GL_NO_ERROR); nuclear@4: } nuclear@4: nuclear@4: void render_volume(void) nuclear@4: { nuclear@4: /* set the camera transformation */ nuclear@0: glMatrixMode(GL_MODELVIEW); nuclear@4: glPushMatrix(); nuclear@0: glLoadIdentity(); nuclear@1: glRotatef(-90, 1, 0, 0); nuclear@0: glTranslatef(cam_x, cam_y, -cam_z); nuclear@0: glRotatef(cam_theta, 0, 1, 0); nuclear@0: glRotatef(cam_phi, 1, 0, 0); nuclear@0: glTranslatef(0, 0, -cam_dist); nuclear@0: nuclear@4: /* setup the texture matrix to map the useful part of the ray texture to [0,1] */ nuclear@0: glMatrixMode(GL_TEXTURE); nuclear@4: glPushMatrix(); nuclear@1: glLoadIdentity(); nuclear@0: glScalef(tex_scale.x, tex_scale.y, 1.0); nuclear@0: nuclear@4: /* tex unit0: volume data 3D texture */ nuclear@0: glActiveTexture(GL_TEXTURE0); nuclear@1: glBindTexture(GL_TEXTURE_3D, vol_tex); nuclear@0: glEnable(GL_TEXTURE_3D); nuclear@0: nuclear@4: /* tex unit1: primary rays in view space */ nuclear@0: glActiveTexture(GL_TEXTURE1); nuclear@1: glBindTexture(GL_TEXTURE_2D, ray_tex); nuclear@0: glEnable(GL_TEXTURE_2D); nuclear@0: nuclear@4: /* tex unit2: transfer function (1d) */ nuclear@3: glActiveTexture(GL_TEXTURE2); nuclear@3: glBindTexture(GL_TEXTURE_1D, xfer_tex); nuclear@3: glEnable(GL_TEXTURE_1D); nuclear@3: nuclear@5: bind_program(vol_sdr); nuclear@0: glBegin(GL_QUADS); nuclear@0: glColor3f(1, 1, 1); nuclear@1: glTexCoord2f(0, 1); glVertex2f(-1, -1); nuclear@1: glTexCoord2f(1, 1); glVertex2f(1, -1); nuclear@1: glTexCoord2f(1, 0); glVertex2f(1, 1); nuclear@1: glTexCoord2f(0, 0); glVertex2f(-1, 1); nuclear@0: glEnd(); nuclear@0: bind_program(0); nuclear@0: nuclear@3: glActiveTexture(GL_TEXTURE2); nuclear@3: glDisable(GL_TEXTURE_1D); nuclear@1: glActiveTexture(GL_TEXTURE1); nuclear@0: glDisable(GL_TEXTURE_2D); nuclear@0: glActiveTexture(GL_TEXTURE0); nuclear@0: glDisable(GL_TEXTURE_3D); nuclear@0: nuclear@0: glMatrixMode(GL_TEXTURE); nuclear@4: glPopMatrix(); nuclear@4: glMatrixMode(GL_MODELVIEW); nuclear@4: glPopMatrix(); nuclear@4: } nuclear@0: nuclear@5: void draw_slice(void) nuclear@5: { nuclear@5: glMatrixMode(GL_MODELVIEW); nuclear@5: glPushMatrix(); nuclear@5: glTranslatef(0.9, 0.9, 0); nuclear@5: glScalef(0.3, 0.3 * ((float)win_xsz / win_ysz), 1); nuclear@5: glTranslatef(-1, -1, 0); nuclear@5: nuclear@5: glActiveTexture(GL_TEXTURE0); nuclear@5: glBindTexture(GL_TEXTURE_3D, vol_tex); nuclear@5: glEnable(GL_TEXTURE_3D); nuclear@5: nuclear@5: glActiveTexture(GL_TEXTURE1); nuclear@5: glBindTexture(GL_TEXTURE_1D, xfer_tex); nuclear@5: glEnable(GL_TEXTURE_1D); nuclear@5: nuclear@5: bind_program(slice_sdr); nuclear@5: nuclear@5: glBegin(GL_QUADS); nuclear@5: glColor3f(1, 1, 1); nuclear@5: glTexCoord3f(0, 1, cur_z); glVertex2f(-1, -1); nuclear@5: glTexCoord3f(1, 1, cur_z); glVertex2f(1, -1); nuclear@5: glTexCoord3f(1, 0, cur_z); glVertex2f(1, 1); nuclear@5: glTexCoord3f(0, 0, cur_z); glVertex2f(-1, 1); nuclear@5: glEnd(); nuclear@5: nuclear@5: bind_program(0); nuclear@5: nuclear@5: glActiveTexture(GL_TEXTURE1); nuclear@5: glDisable(GL_TEXTURE_1D); nuclear@5: glActiveTexture(GL_TEXTURE0); nuclear@5: glDisable(GL_TEXTURE_3D); nuclear@5: glPopMatrix(); nuclear@5: } nuclear@5: nuclear@4: void draw_xfer_func(void) nuclear@4: { nuclear@4: glMatrixMode(GL_MODELVIEW); nuclear@4: glPushMatrix(); nuclear@4: glTranslatef(-0.9, -0.9, 0); nuclear@4: glScalef(0.5, 0.1, 1); nuclear@4: nuclear@4: glBindTexture(GL_TEXTURE_1D, xfer_tex); nuclear@4: glEnable(GL_TEXTURE_1D); nuclear@4: nuclear@4: glBegin(GL_QUADS); nuclear@4: glColor3f(1, 1, 1); nuclear@4: glTexCoord1f(1); nuclear@4: glVertex2f(1, 0); nuclear@4: glVertex2f(1, 1); nuclear@4: glTexCoord1f(0); nuclear@4: glVertex2f(0, 1); nuclear@4: glVertex2f(0, 0); nuclear@4: glEnd(); nuclear@4: nuclear@4: glDisable(GL_TEXTURE_1D); nuclear@4: nuclear@4: glLineWidth(2.0); nuclear@4: glBegin(GL_LINE_LOOP); nuclear@5: if(uimode == UIMODE_XFER) { nuclear@5: glColor3f(1, 0, 0); nuclear@5: } else { nuclear@5: glColor3f(0, 0, 1); nuclear@5: } nuclear@4: glVertex2f(0, 0); nuclear@4: glVertex2f(1, 0); nuclear@4: glVertex2f(1, 1); nuclear@4: glVertex2f(0, 1); nuclear@4: glEnd(); nuclear@4: nuclear@4: glPopMatrix(); nuclear@0: } nuclear@0: nuclear@0: void reshape(int x, int y) nuclear@0: { nuclear@0: glViewport(0, 0, x, y); nuclear@0: nuclear@0: if(x != win_xsz || y != win_ysz) { nuclear@1: raytex_needs_recalc = 1; nuclear@0: win_xsz = x; nuclear@0: win_ysz = y; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void keyb(unsigned char key, int x, int y) nuclear@0: { nuclear@0: switch(key) { nuclear@0: case 27: nuclear@0: exit(0); nuclear@4: nuclear@4: case 'x': nuclear@5: uimode = UIMODE_XFER; nuclear@4: glutPostRedisplay(); nuclear@4: break; nuclear@5: nuclear@5: case 'c': nuclear@5: uimode = UIMODE_CURSOR; nuclear@5: glutPostRedisplay(); nuclear@5: break; nuclear@5: nuclear@5: default: nuclear@5: break; nuclear@4: } nuclear@4: } nuclear@4: nuclear@4: void keyb_up(unsigned char key, int x, int y) nuclear@4: { nuclear@4: switch(key) { nuclear@4: case 'x': nuclear@5: if(uimode == UIMODE_XFER) { nuclear@5: uimode = UIMODE_DEFAULT; nuclear@5: glutPostRedisplay(); nuclear@5: } nuclear@5: break; nuclear@5: nuclear@5: case 'c': nuclear@5: if(uimode == UIMODE_CURSOR) { nuclear@5: uimode = UIMODE_DEFAULT; nuclear@5: glutPostRedisplay(); nuclear@5: } nuclear@5: break; nuclear@5: nuclear@5: default: nuclear@4: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static int bnstate[32]; nuclear@0: static int prev_x, prev_y; nuclear@0: nuclear@0: void mouse(int bn, int state, int x, int y) nuclear@0: { nuclear@0: bnstate[bn - GLUT_LEFT_BUTTON] = state == GLUT_DOWN; nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@0: } nuclear@0: nuclear@0: void motion(int x, int y) nuclear@0: { nuclear@0: int dx = x - prev_x; nuclear@0: int dy = y - prev_y; nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@0: nuclear@5: switch(uimode) { nuclear@5: case UIMODE_XFER: nuclear@4: if(dx || dy) { nuclear@4: xfer_mean += dx / (float)win_xsz; nuclear@4: xfer_sdev += 0.5 * dy / (float)win_ysz; nuclear@0: nuclear@4: xfer_mean = xfer_mean < 0.0 ? 0.0 : (xfer_mean > 1.0 ? 1.0 : xfer_mean); nuclear@4: xfer_sdev = xfer_sdev < 0.0 ? 0.0 : (xfer_sdev > 1.0 ? 1.0 : xfer_sdev); nuclear@0: nuclear@4: xfertex_needs_recalc = 1; nuclear@4: glutPostRedisplay(); nuclear@4: } nuclear@5: break; nuclear@0: nuclear@5: case UIMODE_CURSOR: nuclear@5: cur_z += 0.5 * dy / (float)win_ysz; nuclear@5: nuclear@5: if(cur_z < 0.0) nuclear@5: cur_z = 0.0; nuclear@5: if(cur_z > 1.0) nuclear@5: cur_z = 1.0; nuclear@7: nuclear@7: set_uniform_float(vol_sdr, "zclip", cur_z); nuclear@5: glutPostRedisplay(); nuclear@5: break; nuclear@5: nuclear@5: default: nuclear@5: /* view control */ nuclear@4: if(bnstate[0]) { nuclear@4: cam_theta += dx * 0.5; nuclear@4: cam_phi += dy * 0.5; nuclear@0: nuclear@4: if(cam_phi <= -90) cam_phi = -89; nuclear@4: if(cam_phi >= 90) cam_phi = 89; nuclear@4: nuclear@4: glutPostRedisplay(); nuclear@4: } nuclear@4: nuclear@4: if(bnstate[1]) { nuclear@4: cam_x += dx * 0.025; nuclear@4: cam_y += dy * 0.025; nuclear@4: glutPostRedisplay(); nuclear@4: } nuclear@4: nuclear@4: if(bnstate[2]) { nuclear@4: cam_dist += dy * 0.025; nuclear@4: if(cam_dist < 0.0) cam_dist = 0.0; nuclear@4: glutPostRedisplay(); nuclear@4: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: int parse_args(int argc, char **argv) nuclear@0: { nuclear@0: int i; nuclear@0: struct slice_file *tail; nuclear@3: char *endp; nuclear@0: nuclear@0: for(i=1; iname = argv[i]; nuclear@2: sfile->next = 0; nuclear@2: nuclear@2: if(!flist) { nuclear@2: flist = tail = sfile; nuclear@2: } else { nuclear@2: tail->next = sfile; nuclear@2: tail = sfile; nuclear@2: } nuclear@2: nslices++; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(!nslices) { nuclear@0: fprintf(stderr, "pass the slice filenames\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: nuclear@1: static void create_ray_texture(int xsz, int ysz, float vfov, vec2_t *tex_scale) nuclear@0: { nuclear@0: int i, j; nuclear@1: int cur_tex_xsz, cur_tex_ysz; nuclear@0: int tex_xsz = round_pow2(xsz); nuclear@0: int tex_ysz = round_pow2(ysz); nuclear@0: float *teximg, *dir; nuclear@0: nuclear@1: if(!(teximg = malloc(3 * xsz * ysz * sizeof *teximg))) { nuclear@1: return; nuclear@0: } nuclear@0: dir = teximg; nuclear@0: nuclear@1: for(i=0; i cur_tex_xsz || tex_ysz > cur_tex_ysz) { nuclear@1: glBindTexture(GL_TEXTURE_2D, ray_tex); nuclear@1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); nuclear@1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); nuclear@1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); nuclear@1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); nuclear@1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F_ARB, tex_xsz, tex_ysz, 0, GL_RGB, GL_FLOAT, 0); nuclear@1: } nuclear@1: nuclear@1: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, xsz, ysz, GL_RGB, GL_FLOAT, teximg); nuclear@0: free(teximg); nuclear@0: nuclear@0: if(tex_scale) { nuclear@0: tex_scale->x = (float)xsz / (float)tex_xsz; nuclear@0: tex_scale->y = (float)ysz / (float)tex_ysz; nuclear@0: } nuclear@1: raytex_needs_recalc = 0; nuclear@0: } nuclear@0: nuclear@0: static vec3_t get_primary_ray_dir(int x, int y, int w, int h, float vfov_deg) nuclear@0: { nuclear@0: float vfov = M_PI * vfov_deg / 180.0; nuclear@0: float aspect = (float)w / (float)h; nuclear@0: nuclear@0: float ysz = 2.0; nuclear@0: float xsz = aspect * ysz; nuclear@0: nuclear@0: float px = ((float)x / (float)w) * xsz - xsz / 2.0; nuclear@0: float py = 1.0 - ((float)y / (float)h) * ysz; nuclear@0: float pz = 1.0 / tan(0.5 * vfov); nuclear@0: nuclear@0: float mag = sqrt(px * px + py * py + pz * pz); nuclear@0: nuclear@0: return v3_cons(px / mag, py / mag, pz / mag); nuclear@0: } nuclear@0: nuclear@0: static int round_pow2(int x) nuclear@0: { nuclear@0: x--; nuclear@0: x = (x >> 1) | x; nuclear@0: x = (x >> 2) | x; nuclear@0: x = (x >> 4) | x; nuclear@0: x = (x >> 8) | x; nuclear@0: x = (x >> 16) | x; nuclear@0: return x + 1; nuclear@0: } nuclear@0: nuclear@3: static void create_transfer_map(float mean, float sdev) nuclear@3: { nuclear@3: static float map[XFER_MAP_SZ]; nuclear@3: int i; nuclear@3: nuclear@3: if(!xfer_tex) { nuclear@3: glGenTextures(1, &xfer_tex); nuclear@3: glBindTexture(GL_TEXTURE_1D, xfer_tex); nuclear@3: glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); nuclear@3: glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); nuclear@3: glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); nuclear@3: glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); nuclear@3: glTexImage1D(GL_TEXTURE_1D, 0, GL_LUMINANCE32F_ARB, XFER_MAP_SZ, 0, GL_LUMINANCE, GL_FLOAT, 0); nuclear@3: } nuclear@3: nuclear@3: for(i=0; i