nuclear@4: #include nuclear@0: #include nuclear@4: #include nuclear@0: #include "scene.h" nuclear@0: #include "image.h" nuclear@0: #include "rend.h" nuclear@0: #include "opengl.h" nuclear@0: #include "glsdr.h" nuclear@0: nuclear@0: enum { nuclear@0: TEX_RAYDIR, nuclear@0: TEX_SPHERES, nuclear@0: TEX_PLANES, nuclear@0: TEX_BOXES, nuclear@4: TEX_CONES, nuclear@0: TEX_TEXTURES, nuclear@0: TEX_ENV, nuclear@0: TEX_XFORM, nuclear@0: nuclear@0: NUM_SDR_TEXTURES nuclear@0: }; nuclear@0: nuclear@0: bool reload_shader(); nuclear@0: void gen_ray_texture(unsigned int tex, int xsz, int ysz, float vfov); nuclear@0: static Vector3 get_primary_ray_dir(int x, int y, int w, int h, float vfov_deg); nuclear@0: static int round_pow2(int x); nuclear@0: nuclear@0: static GPUScene *scn; nuclear@0: nuclear@0: static unsigned int sdr; nuclear@0: static unsigned int textures[NUM_SDR_TEXTURES]; nuclear@0: nuclear@0: bool init_renderer(GPUScene *s, int xsz, int ysz) nuclear@0: { nuclear@0: scn = s; nuclear@0: nuclear@4: int max_tex_units; nuclear@4: glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_tex_units); nuclear@4: printf("maximum texture units: %d\n", max_tex_units); nuclear@4: if(NUM_SDR_TEXTURES > max_tex_units) { nuclear@4: fprintf(stderr, "not enough texture units available!\n"); nuclear@4: return false; nuclear@4: } nuclear@4: nuclear@0: glGenTextures(1, textures + TEX_RAYDIR); nuclear@0: glBindTexture(GL_TEXTURE_2D, textures[TEX_RAYDIR]); nuclear@0: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); nuclear@0: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); nuclear@0: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); nuclear@0: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); nuclear@0: nuclear@0: if(!(s->create_textures())) { nuclear@0: fprintf(stderr, "failed to create scene textures\n"); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: textures[TEX_SPHERES] = s->get_texture(GPUScene::TEX_SPHERE); nuclear@0: textures[TEX_PLANES] = s->get_texture(GPUScene::TEX_PLANE); nuclear@0: textures[TEX_BOXES] = s->get_texture(GPUScene::TEX_BOX); nuclear@4: textures[TEX_CONES] = s->get_texture(GPUScene::TEX_CONE); nuclear@0: textures[TEX_TEXTURES] = s->get_texture(GPUScene::TEX_TEXTURE); nuclear@0: textures[TEX_ENV] = s->get_texture(GPUScene::TEX_ENV); nuclear@0: textures[TEX_XFORM] = s->get_texture(GPUScene::TEX_XFORM); nuclear@0: nuclear@0: if(!reload_shader()) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: resize_renderer(xsz, ysz); nuclear@0: nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: bool reload_shader() nuclear@0: { nuclear@0: if(sdr) { nuclear@0: free_program(sdr); nuclear@0: } nuclear@0: nuclear@0: printf("loading shader...\n"); nuclear@0: nuclear@0: if(!(sdr = create_program_load("sdr/vertex.glsl", "sdr/rt.glsl"))) { nuclear@0: return false; nuclear@0: } nuclear@0: set_uniform_int(sdr, "tex_raydir", TEX_RAYDIR); nuclear@0: set_uniform_int(sdr, "tex_spheres", TEX_SPHERES); nuclear@0: set_uniform_int(sdr, "tex_planes", TEX_PLANES); nuclear@0: set_uniform_int(sdr, "tex_boxes", TEX_BOXES); nuclear@4: set_uniform_int(sdr, "tex_cones", TEX_CONES); nuclear@0: set_uniform_int(sdr, "tex_megatex", TEX_TEXTURES); nuclear@0: set_uniform_int(sdr, "tex_env", TEX_ENV); nuclear@0: set_uniform_int(sdr, "tex_xforms", TEX_XFORM); nuclear@0: nuclear@0: set_uniform_int(sdr, "num_lights", scn->get_light_count()); nuclear@0: nuclear@0: for(int i=0; iget_light_count(); i++) { nuclear@0: const Light *lt = scn->get_light(i); nuclear@0: nuclear@0: char name[64]; nuclear@0: sprintf(name, "lights[%d].pos", i); nuclear@0: set_uniform_float3(sdr, name, lt->pos.x, lt->pos.y, lt->pos.z); nuclear@0: nuclear@0: sprintf(name, "lights[%d].color", i); nuclear@0: set_uniform_float3(sdr, name, lt->color.x, lt->color.y, lt->color.z); nuclear@0: } nuclear@0: nuclear@0: Vector2 fog; nuclear@0: scn->get_fog(&fog.x, &fog.y); nuclear@0: if(fog.x < 0.0 && fog.y < 0.0) { nuclear@0: fog.x = 0.0; nuclear@0: fog.y = 100000.0; nuclear@0: } nuclear@0: set_uniform_float2(sdr, "fog", fog.x, fog.y); nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: void destroy_renderer() nuclear@0: { nuclear@0: free_program(sdr); nuclear@0: sdr = 0; nuclear@0: } nuclear@0: nuclear@0: void resize_renderer(int xsz, int ysz) nuclear@0: { nuclear@0: gen_ray_texture(textures[TEX_RAYDIR], xsz, ysz, 45.0f); nuclear@0: } nuclear@0: nuclear@0: float *render_frame(long msec) nuclear@0: { nuclear@0: scn->prepare_xform(msec); nuclear@0: scn->update_xform_texture(); nuclear@0: nuclear@0: Camera *cam = scn->get_camera(); nuclear@0: glMatrixMode(GL_MODELVIEW); nuclear@0: glLoadIdentity(); nuclear@0: Vector3 cpos = cam->get_position(); nuclear@0: glTranslatef(cpos.x, cpos.y, cpos.z); nuclear@0: Matrix4x4 cmat = cam->get_matrix(); nuclear@0: glMultTransposeMatrixf(cmat[0]); nuclear@0: nuclear@0: for(int i=0; i> 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@0: nuclear@0: #if 0 nuclear@0: Color trace_ray(const Scene *scn, const Ray &ray, int rdepth) nuclear@0: { nuclear@0: HitPoint hit; nuclear@0: nuclear@0: if(scn->intersect(ray, &hit)) { nuclear@0: float t; nuclear@0: if(scn->fog_start >= 0.0 && (t = (hit.dist - scn->fog_start) / (scn->fog_end - scn->fog_start)) > 0.0) { nuclear@0: return lerp(shade(scn, ray, hit, rdepth), scn->env_color(ray), t > 1.0 ? 1.0 : t); nuclear@0: } nuclear@0: return shade(scn, ray, hit, rdepth); nuclear@0: } nuclear@0: nuclear@0: return scn->env_color(ray); nuclear@0: } nuclear@0: nuclear@0: Color shade(const Scene *scn, const Ray &ray, const HitPoint &hit, int rdepth) nuclear@0: { nuclear@0: const Material *mat = &hit.obj->material; nuclear@0: nuclear@0: // if we're leaving the object, we need to invert the normal (and ior) nuclear@0: Vector3 normal; nuclear@0: bool entering; nuclear@0: if(dot_product(hit.normal, ray.dir) <= 0.0) { nuclear@0: normal = hit.normal; nuclear@0: entering = true; nuclear@0: } else { nuclear@0: normal = -hit.normal; nuclear@0: entering = false; nuclear@0: } nuclear@0: nuclear@0: Vector3 vdir = -ray.dir; nuclear@0: nuclear@0: Color diffuse_color = mat->diffuse; nuclear@0: Color tex_color{1, 1, 1}; nuclear@0: if(mat->tex) { nuclear@0: tex_color *= mat->tex->sample(hit); nuclear@0: diffuse_color *= tex_color; nuclear@0: } nuclear@0: nuclear@0: Color color = mat->emission * tex_color; nuclear@0: nuclear@0: // image-based lighting nuclear@0: if(scn->envmap_conv) { nuclear@0: // pick a random direction and create a sampling ray nuclear@0: Ray envray; nuclear@0: envray.origin = hit.pos; nuclear@0: rand_dir(&envray.dir.x, &envray.dir.y, &envray.dir.z, (unsigned int*)ray.user); nuclear@0: if(dot_product(envray.dir, normal) < 0.0) { nuclear@0: envray.dir = -envray.dir; nuclear@0: } nuclear@0: nuclear@0: HitPoint env_hit; nuclear@0: if(!scn->intersect(envray, &env_hit)) { nuclear@0: Vector3 dir = envray.dir; nuclear@0: color += scn->envmap_conv->sample(dir.x, dir.y, dir.z) * diffuse_color; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: for(Light *lt: scn->lights) { nuclear@0: nuclear@0: /* construct a shadow ray to determine if there is an uninterrupted nuclear@0: * path between the intersection point and the light source nuclear@0: */ nuclear@0: Ray shadow_ray = ray; nuclear@0: shadow_ray.origin = hit.pos; nuclear@0: shadow_ray.dir = lt->pos - hit.pos; nuclear@0: nuclear@0: /* the interval [0, 1] represents the part of the ray from the origin nuclear@0: * to the light. We don't care about intersections behind the origin nuclear@0: * of the shadow ray (behind the surface of the object), or after the nuclear@0: * light source. We only care if there's something in between hiding the nuclear@0: * light. nuclear@0: */ nuclear@0: HitPoint shadow_hit; nuclear@0: if(scn->intersect(shadow_ray, &shadow_hit) && shadow_hit.dist < 1.0f) { nuclear@0: continue; // skip this light, it's hidden from view nuclear@0: } nuclear@0: nuclear@0: // calculate the light direction nuclear@0: Vector3 ldir = shadow_ray.dir.normalized(); nuclear@0: // calculate the reflected light direction nuclear@0: Vector3 lref = ldir.reflection(normal); nuclear@0: nuclear@0: float diffuse = std::max(dot_product(ldir, normal), 0.0f); nuclear@0: float specular = pow(std::max(dot_product(lref, vdir), 0.0f), mat->shininess); nuclear@0: nuclear@0: color += (diffuse_color * diffuse + mat->specular * specular) * lt->color; nuclear@0: } nuclear@0: nuclear@0: Color spec_col; nuclear@0: nuclear@0: if(mat->reflectivity > 0.001f && rdepth < MAX_RAY_DEPTH) { nuclear@0: Ray refl_ray{ray}; nuclear@0: refl_ray.origin = hit.pos; nuclear@0: refl_ray.dir = -ray.dir.reflection(normal); nuclear@0: nuclear@0: spec_col += trace_ray(scn, refl_ray, rdepth + 1) * mat->reflectivity; nuclear@0: } nuclear@0: nuclear@0: if(mat->transparency > 0.001f && rdepth < MAX_RAY_DEPTH) { nuclear@0: float from_ior = entering ? 1.0 : mat->ior; nuclear@0: float to_ior = entering ? mat->ior : 1.0; nuclear@0: nuclear@0: Ray refr_ray{ray}; nuclear@0: refr_ray.origin = hit.pos; nuclear@0: refr_ray.dir = ray.dir.refraction(normal, from_ior / to_ior); nuclear@0: nuclear@0: Color tcol = trace_ray(scn, refr_ray, rdepth + 1) * mat->transparency; nuclear@0: nuclear@0: float fres = fresnel(ray.dir, refr_ray.dir, normal, from_ior, to_ior); nuclear@0: spec_col = spec_col * fres + tcol * (1.0 - fres); nuclear@0: } nuclear@0: nuclear@0: return color + spec_col; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: static void rand_dir(float *x, float *y, float *z, unsigned int *seedp) nuclear@0: { nuclear@0: float u = (float)rand_r(seedp) / RAND_MAX; nuclear@0: float v = (float)rand_r(seedp) / RAND_MAX; nuclear@0: nuclear@0: float theta = 2.0 * M_PI * u; nuclear@0: float phi = acos(2.0 * v - 1.0); nuclear@0: nuclear@0: *x = cos(theta) * sin(phi); nuclear@0: *y = sin(theta) * sin(phi); nuclear@0: *z = cos(phi); nuclear@0: } nuclear@0: nuclear@0: static float fresnel(const Vector3 &inc, const Vector3 &trans, const Vector3 &norm, float ior_inc, float ior_trans) nuclear@0: { nuclear@0: float cos_inc = dot_product(-inc, norm); nuclear@0: float cos_trans = dot_product(-trans, norm); nuclear@0: nuclear@0: return fresnel(cos_inc, cos_trans, ior_inc, ior_trans); nuclear@0: } nuclear@0: nuclear@0: static float fresnel(float cos_inc, float cos_trans, float ior_inc, float ior_trans) nuclear@0: { nuclear@0: float r0 = ((ior_trans * cos_inc) - (ior_inc * cos_trans)) / nuclear@0: ((ior_trans * cos_inc) + (ior_inc * cos_trans)); nuclear@0: float r1 = ((ior_inc * cos_inc) - (ior_trans * cos_trans)) / nuclear@0: ((ior_inc * cos_inc) + (ior_trans * cos_trans)); nuclear@0: return (r0 * r0 + r1 * r1) * 0.5f; nuclear@0: } nuclear@0: #endif