/* * Copyright © 2009 Eric Anholt * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authors: * Eric Anholt * */ #include #include #include #include #include #include #include #include #include #include #include "glass.h" extern int no_multi_draw_arrays; extern GLUvec4 eye_world; static GLUmat4 world_to_light_eye; GLUmat4 world_to_light_ndc, world_to_shadow_texcoords; GLuint shadow_tex = 0, shadow_fbo; static void set_world_to_shadow_transform(void) { GLUmat4 temp; /* Setup the viewport translation to map light NDC to texcoords. */ memcpy(&temp, &gluIdentityMatrix, sizeof(gluIdentityMatrix)); temp.col[0].values[0] = (1.0 - 0.0) / 2.0; temp.col[3].values[0] = temp.col[0].values[0] + 0.0; temp.col[1].values[1] = (1.0 - 0.0) / 2.0; temp.col[3].values[1] = temp.col[1].values[1] + 0.0; temp.col[2].values[2] = (1.0 - 0.0) / 2.0; temp.col[3].values[2] = (1.0 - 0.0) / 2.0; gluMult4m_4m(&world_to_shadow_texcoords, &temp, &world_to_light_ndc); } static void calculate_light_projection(void) { GLUvec4 light_up; GLUmat4 temp; GLUvec4 light_to_rings; /* Wrong. So wrong. */ light_up.values[0] = 1.0; light_up.values[1] = 0.0; light_up.values[2] = 0.0; light_up.values[3] = 1.0; /* print_GLUvec4("light_world", &light_world); print_GLUvec4("ring_center", &ring_bounding_sphere_center_world); print_GLUvec4("light_up ", &light_up); */ /* Our shadow mapping can't handle lights inside the frustum of the * shadow casters. */ gluSub4v_4v(&light_to_rings, &ring_bounding_sphere_center_world, &light_world); /* Transform for looking from they eye's point of view at the pile of * rings. */ gluLookAt4v(&world_to_light_eye, &light_world, &ring_bounding_sphere_center_world, &light_up); make_sphere_frustum(&temp, &light_to_rings, ring_bounding_sphere_radius); /*print_GLUmat4("frustum", &temp);*/ gluMult4m_4m(&world_to_light_ndc, &temp, &world_to_light_eye); } /* The ground plane is located in world coordinates, so no need to do any * object_to_world transformation. */ static void install_shadow_transform(int instance) { GLUmat4 mvp; gluMult4m_4m(&mvp, &world_to_light_ndc, &ring_obj_to_world[instance]); glUniformMatrix4fv(shadow_uniforms[SHADOW_UNIFORM_MVP].location, 1, 0, (float *)&mvp); } void generate_rings_shadowmap(void) { int instance; int shadow_width = 1024, shadow_height = 1024; GLboolean first_time = GL_FALSE; if (shadow_prog == 0) return; glUseProgram(shadow_prog); calculate_light_projection(); if (shadow_tex == 0) { glGenTextures(1, &shadow_tex); glBindTexture(GL_TEXTURE_2D, shadow_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadow_width, shadow_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glGenFramebuffersEXT(1, &shadow_fbo); glBindFramebuffer(GL_FRAMEBUFFER_EXT, shadow_fbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, shadow_tex, 0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); first_time = GL_TRUE; } glBindFramebuffer(GL_FRAMEBUFFER_EXT, shadow_fbo); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); /* Move the faces away from the light, so that if they're compared * for being lit, they'll tend to pass. */ glPolygonOffset(2, 2); glEnable(GL_POLYGON_OFFSET_FILL); if (first_time) { GLenum status; status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) fprintf(stderr, "shadow map FBO incomplete: 0x%08x\n", status); } glViewport(0, 0, shadow_width, shadow_height); /* Clear to the material color. */ glClear(GL_DEPTH_BUFFER_BIT); /* Draw the rings in black. */ glBindVertexArray(ring.shadow_array_obj); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, ring.vbo); for (instance = 0; instance < NUM_RINGS; instance++) { install_shadow_transform(instance); do_ring_drawelements(); } glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDisable(GL_POLYGON_OFFSET_FILL); set_world_to_shadow_transform(); }