├── .gitignore ├── LICENSE ├── README.md ├── helper_functions.h ├── screenshots ├── frustum_radius.png ├── shadow_mapping.png ├── shadow_mapping_advanced.png ├── shadow_mapping_cascade.png ├── shadow_mapping_cascade_advanced.png ├── shadow_mapping_cascade_horizontal_and_vertical.png ├── shadow_mapping_cascade_texture_array.png ├── shadow_mapping_pcf.png ├── variance_shadow_mapping.png └── variance_shadow_mapping_MSM4.png ├── shadow_mapping.c ├── shadow_mapping_advanced.c ├── shadow_mapping_bidimensional_texture.c ├── shadow_mapping_cascade.c ├── shadow_mapping_cascade_advanced.c ├── shadow_mapping_cascade_horizontal.c ├── shadow_mapping_cascade_horizontal_and_vertical.c ├── shadow_mapping_cascade_texture_array.c ├── shadow_mapping_pcf.c ├── shadow_mapping_sESM1.c ├── shadow_mapping_with_dof.c ├── variance_shadow_mapping.c ├── variance_shadow_mapping_EVSM2.c ├── variance_shadow_mapping_EVSM4.c └── variance_shadow_mapping_MSM4.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | *.pro 55 | *.user 56 | *.sh 57 | *.ini 58 | *.autosave 59 | *.qmake.stash 60 | *.stash 61 | code/ 62 | code 63 | reps/ 64 | shadow_mapping 65 | shadow_mapping_advanced 66 | shadow_mapping_bidimensional_texture 67 | shadow_mapping_pcf 68 | shadow_mapping_cascade 69 | shadow_mapping_cascade_horizontal 70 | shadow_mapping_cascade_horizontal_and_vertical 71 | shadow_mapping_cascade_advanced 72 | shadow_mapping_cascade_texture_array 73 | shadow_mapping_sESM 74 | shadow_mapping_sESM1 75 | shadow_mapping_sESM2 76 | shadow_mapping_with_dof 77 | variance_shadow_mapping 78 | variance_shadow_mapping_EVSM 79 | variance_shadow_mapping_EVSM2 80 | variance_shadow_mapping_EVSM4 81 | variance_shadow_mapping_MSM 82 | variance_shadow_mapping_MSM2 83 | variance_shadow_mapping_MSM4 84 | Makefile 85 | 86 | # Other Stuff 87 | **/reps/ 88 | reps/ 89 | **/*.sh 90 | Tests/freeglut.dll 91 | Tests/glut.dll 92 | Tests/glut32.dll 93 | Tests/html/ 94 | docs/ 95 | reps/ 96 | Tests/reps/ 97 | *.pro 98 | *.user 99 | *.cfg 100 | *.ini 101 | Tests/Makefile 102 | *.exe 103 | Tests/test_im* 104 | Tests/test_shadows 105 | Tests/test_teapot 106 | Tests/test_matrix_stack 107 | Tests/test_im 108 | Tests/test_sdf 109 | glImmediateMode.h 110 | obj.h 111 | Sdf/ 112 | screenshots/frustum_section_radius.png 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Flix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny-OpenGL-Shadow-Mapping-Examples 2 | Compact OpenGL Shadow Mapping Examples in a single compilation unit. 3 | 4 | 5 | ## Features 6 | * A single .c file per example. 7 | * No extern math library used. No math structs used. Just plain float arrays. 8 | * Helper functions (mainly math stuff) grouped in the header helper_functions.h, for easier inclusion into user code. 9 | * Only a single directional light is used in all the examples. 10 | * Currently all the examples are flicker-free while the camera moves and rotates (on static objects). Please see the implementation section below for further info. 11 | 12 | 13 | ## Dependencies 14 | * glut (or freeglut) 15 | * glew (Windows only) 16 | 17 | 18 | ## How to compile 19 | Compilation instructions are at the top of each .c file. 20 | All the demos have been tested on Linux using an **NVIDIA graphic card** (AMD/ATI might need some glsl tweaking). 21 | 22 | ## Help needed 23 | The demos: shadow_mapping_sESM1.c, variance_shadow_mapping.c, variance_shadow_mapping_EVSM2.c and variance_shadow_mapping_EVSM4.c are all **WRONG**! The shadows should look blurry and instead are sharp! If you can help to fix these (and other) issues, please post a message in the *"Issues"* or *"Pull requests"* sections. Thank you in advance! 24 | 25 | ## Screenshots 26 | ![shadow_mapping.c](./screenshots/shadow_mapping.png) 27 | ![shadow_mapping_advanced.c](./screenshots/shadow_mapping_advanced.png) 28 | ![shadow_mapping_cascade.c](./screenshots/shadow_mapping_cascade.png) 29 | ![shadow_mapping_cascade_advanced.c](./screenshots/shadow_mapping_cascade_advanced.png) 30 | ![shadow_mapping_cascade_texture_array.c](./screenshots/shadow_mapping_cascade_texture_array.png) 31 | ![shadow_mapping_cascade_horizontal_and_vertical.c](./screenshots/shadow_mapping_cascade_horizontal_and_vertical.png) 32 | ![shadow_mapping_pcf.c](./screenshots/shadow_mapping_pcf.png) 33 | ![variance_shadow_mapping.c](./screenshots/variance_shadow_mapping.png) 34 | ![variance_shadow_mapping_MSM4.c](./screenshots/variance_shadow_mapping_MSM4.png) 35 | 36 | ## Implmentation 37 | All the examples in this repository implement "Stable Shadow Mapping". 38 | That means that static shadows should not flicker when the camera moves or rotates, at the expense of the shadow resolution, that is usually only a half of the resolution that can be used in any "Non-Stable Shadow Mapping" technique. 39 | 40 | 41 | In addition, the light matrices calculated by the "Stable Shadow Mapping" algorithm are not optimal for frustum culling usage, and if we want to filter the shadow map (like in VSM), we waste a lot of resources, unless we manage to calculate a tighter texture viewport somehow (we address this issue in **shadow_mapping_advanced.c** and **shadow_mapping_cascade_advanced.c**). 42 | 43 | 44 | The demos **shadow_mapping.c**,**shadow_mapping_cascade.c** and **shadow_mapping_cascade_texture_array.c** can be compiled with the optional definition **USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE** so that we can compare the two techniques. 45 | 46 | 47 | The "Stable Shadow Mapping" algorithm calculates the minimum sphere that encloses the camera frustum, and further encloses it in a light ortho projection matrix. 48 | 49 | ### Calculation of the camera frustum center and radius 50 | The picture below is valid for every frustum field of view (fovx, fovy and fovd), but if we use the diagonal fov, then the points at **tn** and **tf** are real frustum corners. 51 | (**n** is the near camera frustum clipping plane and **f** is the far camera frustum clipping plane) 52 | 53 | ![frustum_radius](./screenshots/frustum_radius.png) 54 |
55 | θ = (fovd/2);   // half diagonal fov
56 | t = tan(θ);
57 | tSquared = (1.0+ar*ar)*tan(0.5*fovy)*tan(0.5*fovy); // ar = camera aspect ratio
58 | h = 0.5*(f+n) ;  // half way between near and far plane
59 | d = h*(1.0+tSquared);   // Found after all the math passages from the picture above
60 | if (d>f) d=f;           // Clamping to save some shadow resolution
61 | r = sqrt((tSquared*f*f) + (f-d)*(f-d));
62 | 
63 | 64 | 65 | ## Useful Links 66 | [opengl-cascaded-shadow-maps with code at the bottom](https://johanmedestrom.wordpress.com/2016/03/18/opengl-cascaded-shadow-maps/) 67 | 68 | [graphics-tech-shadow-maps-part-1 (explains why shadow resolution gets wasted)](http://the-witness.net/news/2010/03/graphics-tech-shadow-maps-part-1/) 69 | 70 | [graphics-tech-shadow-maps-part-2 (proposes a method to save GPU memory)](http://the-witness.net/news/2010/04/graphics-tech-shadow-maps-part-2-save-25-texture-memory-and-possibly-much-more/) 71 | 72 | [A sampling of shadow techniques](https://mynameismjp.wordpress.com/2013/09/10/shadow-maps/) 73 | 74 | [GPU Gems 3 - Chapter 10. Parallel-Split Shadow Maps on Programmable GPUs](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html) 75 | 76 | [variance_shadow_mapping.c is greatly based on this Fabien Sanglard's work](http://fabiensanglard.net/shadowmappingVSM/) 77 | 78 | [glsl-fast-gaussian-blur: my blur filters are based on this Github repository](https://github.com/Jam3/glsl-fast-gaussian-blur/) 79 | 80 | [Github repository containing Direct3D implementations of many modern shadow mapping techniques](https://github.com/TheRealMJP/Shadows) 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /screenshots/frustum_radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/frustum_radius.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_advanced.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_cascade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_cascade.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_cascade_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_cascade_advanced.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_cascade_horizontal_and_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_cascade_horizontal_and_vertical.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_cascade_texture_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_cascade_texture_array.png -------------------------------------------------------------------------------- /screenshots/shadow_mapping_pcf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/shadow_mapping_pcf.png -------------------------------------------------------------------------------- /screenshots/variance_shadow_mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/variance_shadow_mapping.png -------------------------------------------------------------------------------- /screenshots/variance_shadow_mapping_MSM4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples/c511f1ea909d7e87a1854bf43893dc590c89ee74/screenshots/variance_shadow_mapping_MSM4.png -------------------------------------------------------------------------------- /shadow_mapping.c: -------------------------------------------------------------------------------- 1 | // https://github.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples 2 | /** License 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | * SOFTWARE. 18 | */ 19 | 20 | // DEPENDENCIES: 21 | /* 22 | -> glut or freeglut (the latter is recommended) 23 | -> glew (Windows only) 24 | */ 25 | 26 | // HOW TO COMPILE: 27 | /* 28 | // LINUX: 29 | gcc -O2 -std=gnu89 -no-pie shadow_mapping.c -o shadow_mapping -I"../" -lglut -lGL -lX11 -lm 30 | // WINDOWS (here we use the static version of glew, and glut32.lib, that can be replaced by freeglut.lib): 31 | cl /O2 /MT /Tc shadow_mapping.c /D"GLEW_STATIC" /I"../" /link /out:shadow_mapping.exe glut32.lib glew32s.lib opengl32.lib gdi32.lib Shell32.lib comdlg32.lib user32.lib kernel32.lib 32 | 33 | 34 | // IN ADDITION: 35 | By default the source file assumes that every OpenGL-related header is in "GL/". 36 | But you can define in the command-line the correct paths you use in your system 37 | for glut.h, glew.h, etc. with something like: 38 | -DGLUT_PATH=\"Glut/glut.h\" 39 | -DGLEW_PATH=\"Glew/glew.h\" 40 | (this syntax works on Linux, don't know about Windows) 41 | */ 42 | 43 | //#define USE_GLEW // By default it's only defined for Windows builds (but can be defined in Linux/Mac builds too) 44 | 45 | 46 | #define PROGRAM_NAME "shadow_mapping" 47 | #define VISUALIZE_DEPTH_TEXTURE 48 | #define SHADOW_MAP_RESOLUTION 1024 //1024 49 | #define SHADOW_MAP_CLAMP_MODE GL_CLAMP_TO_EDGE // GL_CLAMP or GL_CLAMP_TO_EDGE or GL_CLAMP_TO_BORDER 50 | // GL_CLAMP; // sampling outside of the shadow map gives always shadowed pixels 51 | // GL_CLAMP_TO_EDGE; // sampling outside of the shadow map can give shadowed or unshadowed pixels (it depends on the edge of the shadow map) 52 | // GL_CLAMP_TO_BORDER; // sampling outside of the shadow map gives always non-shadowed pixels (if we set the border color correctly) 53 | #define SHADOW_MAP_FILTER GL_LINEAR // GL_LINEAR or GL_NEAREST (GL_LINEAR is more useful with a sampler2DShadow, that cannot be used with esponential shadow mapping) 54 | 55 | //#define USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE // Better resolution, but shadow-swimming as the camera rotates (on static objects). Please see README.md about it. 56 | // in camera ortho3d mode [F1], the resolution is further better, but shadow-swimming appears when zooming in-out too. 57 | 58 | // These path definitions can be passed to the compiler command-line 59 | #ifndef GLUT_PATH 60 | # define GLUT_PATH "GL/glut.h" // Mandatory 61 | #endif //GLEW_PATH 62 | #ifndef FREEGLUT_EXT_PATH 63 | # define FREEGLUT_EXT_PATH "GL/freeglut_ext.h" // Optional (used only if glut.h comes from the freeglut library) 64 | #endif //GLEW_PATH 65 | #ifndef GLEW_PATH 66 | # define GLEW_PATH "GL/glew.h" // Mandatory for Windows only 67 | #endif //GLEW_PATH 68 | 69 | #ifdef _WIN32 70 | # include "windows.h" 71 | # define USE_GLEW 72 | #endif //_WIN32 73 | 74 | #ifdef USE_GLEW 75 | # include GLEW_PATH 76 | #else //USE_GLEW 77 | # define GL_GLEXT_PROTOTYPES 78 | #endif //USE_GLEW 79 | 80 | #include GLUT_PATH 81 | #ifdef __FREEGLUT_STD_H__ 82 | # include FREEGLUT_EXT_PATH 83 | #endif //__FREEGLUT_STD_H__ 84 | 85 | #define STR_MACRO(s) #s 86 | #define XSTR_MACRO(s) STR_MACRO(s) 87 | 88 | #include "helper_functions.h" // please search this .c file for "Helper_": 89 | // only very few of its functions are used. 90 | 91 | #include 92 | #include 93 | #include 94 | 95 | 96 | // Config file handling: basically there's an .ini file next to the 97 | // exe that you can tweak. (it's just an extra) 98 | const char* ConfigFileName = PROGRAM_NAME".ini"; 99 | typedef struct { 100 | int fullscreen_width,fullscreen_height; 101 | int windowed_width,windowed_height; 102 | int fullscreen_enabled; 103 | int show_fps; 104 | int use_camera_ortho3d_projection_matrix; 105 | } Config; 106 | void Config_Init(Config* c) { 107 | c->fullscreen_width=c->fullscreen_height=0; 108 | c->windowed_width=960;c->windowed_height=540; 109 | c->fullscreen_enabled=0; 110 | c->show_fps = 0; 111 | c->use_camera_ortho3d_projection_matrix = 0; 112 | } 113 | int Config_Load(Config* c,const char* filePath) { 114 | FILE* f = fopen(filePath, "rt"); 115 | char ch='\0';char buf[256]=""; 116 | size_t nread=0; 117 | int numParsedItem=0; 118 | if (!f) return -1; 119 | while ((ch = fgetc(f)) !=EOF) { 120 | buf[nread]=ch; 121 | nread++; 122 | if (nread>255) { 123 | nread=0; 124 | continue; 125 | } 126 | if (ch=='\n') { 127 | buf[nread]='\0'; 128 | if (nread<2 || buf[0]=='[' || buf[0]=='#') {nread = 0;continue;} 129 | if (nread>2 && buf[0]=='/' && buf[1]=='/') {nread = 0;continue;} 130 | // Parse 131 | switch (numParsedItem) { 132 | case 0: 133 | sscanf(buf, "%d %d", &c->fullscreen_width,&c->fullscreen_height); 134 | break; 135 | case 1: 136 | sscanf(buf, "%d %d", &c->windowed_width,&c->windowed_height); 137 | break; 138 | case 2: 139 | sscanf(buf, "%d", &c->fullscreen_enabled); 140 | break; 141 | case 3: 142 | sscanf(buf, "%d", &c->show_fps); 143 | break; 144 | case 4: 145 | sscanf(buf, "%d", &c->use_camera_ortho3d_projection_matrix); 146 | break; 147 | } 148 | nread=0; 149 | ++numParsedItem; 150 | } 151 | } 152 | fclose(f); 153 | if (c->windowed_width<=0) c->windowed_width=720; 154 | if (c->windowed_height<=0) c->windowed_height=405; 155 | return 0; 156 | } 157 | int Config_Save(Config* c,const char* filePath) { 158 | FILE* f = fopen(filePath, "wt"); 159 | if (!f) return -1; 160 | fprintf(f, "[Size In Fullscreen Mode (zero means desktop size)]\n%d %d\n",c->fullscreen_width,c->fullscreen_height); 161 | fprintf(f, "[Size In Windowed Mode]\n%d %d\n",c->windowed_width,c->windowed_height); 162 | fprintf(f, "[Fullscreen Enabled (0 or 1) (CTRL+RETURN)]\n%d\n", c->fullscreen_enabled); 163 | fprintf(f, "[Show FPS (0 or 1) (F2)]\n%d\n", c->show_fps); 164 | fprintf(f, "[Use camera ortho3d projection matrix (0 or 1) (F1)]\n%d\n", c->use_camera_ortho3d_projection_matrix); 165 | fprintf(f,"\n"); 166 | fclose(f); 167 | return 0; 168 | } 169 | 170 | Config config; 171 | 172 | // glut has a special fullscreen GameMode that you can toggle with CTRL+RETURN (not in WebGL) 173 | int windowId = 0; // window Id when not in fullscreen mode 174 | int gameModeWindowId = 0; // window Id when in fullscreen mode 175 | 176 | // Now we can start with our program 177 | 178 | // camera data: 179 | float targetPos[3]; // please set it in resetCamera() 180 | float cameraYaw; // please set it in resetCamera() 181 | float cameraPitch; // please set it in resetCamera() 182 | float cameraDistance; // please set it in resetCamera() 183 | float cameraPos[3]; // Derived value (do not edit) 184 | float vMatrix[16]; // view matrix 185 | float cameraSpeed = 0.5f; // When moving it 186 | 187 | // light data 188 | float lightYaw = M_PI*0.425f,lightPitch = M_PI*0.235f; // must be copied to resetLight() too 189 | float lightDirection[4] = {0,1,0,0}; // Derived value (do not edit) [lightDirection[3]==0] 190 | 191 | // pMatrix data: 192 | float pMatrix[16],pMatrixInverse[16]; // projection matrix (pMatrixInverse is used only when USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE is defined) 193 | const float pMatrixFovyDeg = 45.f; // smaller => better shadow resolution 194 | const float pMatrixNearPlane = 0.5f; // bigger => better shadow resolution 195 | const float pMatrixFarPlane = 20.f; // smaller => better shadow resolution 196 | 197 | float instantFrameTime = 16.2f; 198 | 199 | // Optional (to speed up Helper_GlutDrawGeometry(...) a bit) 200 | GLuint gDisplayListBase = 0;GLuint* pgDisplayListBase = &gDisplayListBase; // Can be set to 0 as a fallback. 201 | 202 | 203 | static const char* ShadowPassVertexShader[] = { 204 | " void main() {\n" 205 | " gl_Position = ftransform();\n" 206 | " }\n" 207 | }; 208 | static const char* ShadowPassFragmentShader[] = { 209 | " void main() {\n" 210 | " //gl_FragColor = gl_Color;\n" 211 | " }\n" 212 | }; 213 | typedef struct { 214 | GLuint fbo; 215 | GLuint textureId; 216 | GLuint program; 217 | } ShadowPass; 218 | 219 | ShadowPass shadowPass; 220 | void InitShadowPass(ShadowPass* sp) { 221 | sp->program = Helper_LoadShaderProgramFromSource(*ShadowPassVertexShader,*ShadowPassFragmentShader); 222 | 223 | // create depth texture 224 | glGenTextures(1, &sp->textureId); 225 | glBindTexture(GL_TEXTURE_2D, sp->textureId); 226 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, SHADOW_MAP_FILTER); 227 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, SHADOW_MAP_FILTER); 228 | # ifndef __EMSCRIPTEN__ 229 | glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_RESOLUTION, SHADOW_MAP_RESOLUTION, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); 230 | # else //__EMSCRIPTEN__ 231 | glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_RESOLUTION, SHADOW_MAP_RESOLUTION, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0); 232 | # undef SHADOW_MAP_CLAMP_MODE 233 | # define SHADOW_MAP_CLAMP_MODE GL_CLAMP_TO_EDGE 234 | # endif //__EMSCRIPTEN__ 235 | if (SHADOW_MAP_CLAMP_MODE==GL_CLAMP_TO_BORDER) { 236 | const GLfloat border[] = {1.0f,1.0f,1.0f,0.0f }; 237 | glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); 238 | } 239 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SHADOW_MAP_CLAMP_MODE ); 240 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SHADOW_MAP_CLAMP_MODE ); 241 | glBindTexture(GL_TEXTURE_2D, 0); 242 | 243 | // create depth fbo 244 | glGenFramebuffers(1, &sp->fbo); 245 | glBindFramebuffer(GL_FRAMEBUFFER, sp->fbo); 246 | # ifndef __EMSCRIPTEN__ 247 | glDrawBuffer(GL_NONE); // Instruct openGL that we won't bind a color texture with the currently bound FBO 248 | glReadBuffer(GL_NONE); 249 | # endif //__EMSCRIPTEN__ 250 | glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,sp->textureId, 0); 251 | { 252 | //Does the GPU support current FBO configuration? 253 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 254 | if (status!=GL_FRAMEBUFFER_COMPLETE) printf("glCheckFramebufferStatus(...) FAILED for shadowPass.fbo.\n"); 255 | } 256 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 257 | } 258 | void DestroyShadowPass(ShadowPass* sp) { 259 | if (sp->program) {glDeleteProgram(sp->program);sp->program=0;} 260 | if (sp->fbo) {glDeleteBuffers(1,&sp->fbo);sp->fbo=0;} 261 | if (sp->textureId) {glDeleteTextures(1,&sp->textureId);} 262 | } 263 | 264 | static const char* DefaultPassVertexShader[] = { 265 | "uniform mat4 u_biasedShadowMvpMatrix;\n" // (*) Actually it's already multiplied with vMatrixInverse (in C code, so that the multiplication can be easily done with doubles) 266 | "varying vec4 v_shadowCoord;\n" 267 | "varying vec4 v_diffuse;\n" 268 | "\n" 269 | "void main() {\n" 270 | " gl_Position = ftransform();\n" 271 | "\n" 272 | " vec3 normal = gl_NormalMatrix * gl_Normal;\n" 273 | " vec3 lightVector = gl_LightSource[0].position.xyz\n;// - gl_Vertex.xyz;\n" 274 | " float nxDir = max(0.0, dot(normal, lightVector));\n" 275 | " v_diffuse = gl_LightSource[0].diffuse * nxDir; \n" 276 | "\n" 277 | " gl_FrontColor = gl_Color;\n" 278 | "\n" 279 | " v_shadowCoord = u_biasedShadowMvpMatrix*(gl_ModelViewMatrix*gl_Vertex);\n" // (*) We don't pass a 'naked' mMatrix in shaders (not robust to double precision usage). We dress it in a mvMatrix. So here we're passing a mMatrix from camera space to light space (through a mvMatrix). 280 | "}\n" // (the bias just converts clip space to texture space) 281 | }; 282 | static const char* DefaultPassFragmentShader[] = { 283 | "uniform sampler2D u_shadowMap;\n" 284 | "uniform vec2 u_shadowDarkening;\n" // .x = fDarkeningFactor [10.0-80.0], .y = min value clamp [0.0-1.0] 285 | "varying vec4 v_shadowCoord;\n" 286 | "varying vec4 v_diffuse;\n" 287 | "\n" 288 | "void main() {\n" 289 | " float shadowFactor = 1.0;\n" 290 | " vec4 shadowCoordinateWdivide = v_shadowCoord/v_shadowCoord.w;\n" 291 | " shadowFactor = clamp(exp(u_shadowDarkening.x*(texture2D(u_shadowMap,(shadowCoordinateWdivide.st)).r - shadowCoordinateWdivide.z)),u_shadowDarkening.y,1.0);\n" 292 | "// shadowFactor = clamp( exp(u_shadowDarkening.x*texture2D(u_shadowMap,(shadowCoordinateWdivide.st)).r) * exp(-u_shadowDarkening.x*shadowCoordinateWdivide.z),u_shadowDarkening.y,1.0);\n" 293 | " gl_FragColor = gl_LightSource[0].ambient + (v_diffuse * vec4(gl_Color.rgb*shadowFactor,1.0));\n" 294 | "}\n" 295 | }; 296 | typedef struct { 297 | GLuint program; 298 | GLint uniform_location_biasedShadowMvpMatrix; 299 | GLint uniform_location_shadowMap; 300 | GLint uniform_location_shadowDarkening; 301 | } DefaultPass; 302 | 303 | DefaultPass defaultPass; 304 | void InitDefaultPass(DefaultPass* dp) { 305 | dp->program = Helper_LoadShaderProgramFromSource(*DefaultPassVertexShader,*DefaultPassFragmentShader); 306 | dp->uniform_location_biasedShadowMvpMatrix = glGetUniformLocation(dp->program,"u_biasedShadowMvpMatrix"); 307 | dp->uniform_location_shadowMap = glGetUniformLocation(dp->program,"u_shadowMap"); 308 | dp->uniform_location_shadowDarkening = glGetUniformLocation(dp->program,"u_shadowDarkening"); 309 | 310 | glUseProgram(dp->program); 311 | glUniform1i(dp->uniform_location_shadowMap,0); 312 | glUniform2f(dp->uniform_location_shadowDarkening,80.0,0.45); // Default values are (40.0f,0.75f) in [0-80] and [0-1] 313 | //glUniformMatrix4fv(dp->uniform_location_biasedShadowMvpMatrix, 1 /*only setting 1 matrix*/, GL_FALSE /*transpose?*/, Matrix); 314 | glUseProgram(0); 315 | } 316 | void DestroyDefaultPass(DefaultPass* dp) { 317 | if (dp->program) {glDeleteProgram(dp->program);dp->program=0;} 318 | } 319 | 320 | float current_width=0,current_height=0,current_aspect_ratio=1; // Not sure when I've used these... 321 | void ResizeGL(int w,int h) { 322 | current_width = (float) w; 323 | current_height = (float) h; 324 | if (current_height!=0) current_aspect_ratio = current_width/current_height; 325 | if (h>0) { 326 | // We set our pMatrix 327 | if (!config.use_camera_ortho3d_projection_matrix) 328 | Helper_Perspective(pMatrix,pMatrixFovyDeg,(float)w/(float)h,pMatrixNearPlane,pMatrixFarPlane); 329 | else 330 | Helper_Ortho3D(pMatrix,cameraDistance,pMatrixFovyDeg,(float)w/(float)h,pMatrixNearPlane,pMatrixFarPlane); 331 | 332 | glMatrixMode(GL_PROJECTION);glLoadMatrixf(pMatrix);glMatrixMode(GL_MODELVIEW); 333 | 334 | # ifdef USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 335 | Helper_InvertMatrix(pMatrixInverse,pMatrix); // pMatrixInverse is used only when USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE is defined 336 | # endif //USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 337 | } 338 | 339 | 340 | if (w>0 && h>0 && !config.fullscreen_enabled) { 341 | // On exiting we'll like to save these data back 342 | config.windowed_width=w; 343 | config.windowed_height=h; 344 | } 345 | 346 | glViewport(0,0,w,h); // This is what people often call in ResizeGL() 347 | 348 | } 349 | 350 | 351 | void InitGL(void) { 352 | 353 | // These are important, but often overlooked OpenGL calls 354 | glEnable(GL_DEPTH_TEST); 355 | glEnable(GL_CULL_FACE); 356 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Otherwise transparent objects are not displayed correctly 357 | glClearColor(0.3f, 0.6f, 1.0f, 1.0f); 358 | glEnable(GL_TEXTURE_2D); // Only needed for ffp, when VISUALIZE_DEPTH_TEXTURE is defined 359 | 360 | 361 | // ffp stuff 362 | glEnable(GL_LIGHTING); 363 | glEnable(GL_LIGHT0); 364 | glEnable(GL_COLOR_MATERIAL); 365 | glEnable(GL_NORMALIZE); 366 | 367 | // New 368 | InitShadowPass(&shadowPass); 369 | InitDefaultPass(&defaultPass); 370 | 371 | // Please note that after InitGL(), this implementation calls ResizeGL(...,...). 372 | // If you copy/paste this code you can call it explicitly... 373 | } 374 | 375 | void DestroyGL() { 376 | // New 377 | DestroyShadowPass(&shadowPass); 378 | DestroyDefaultPass(&defaultPass); 379 | // 40 display lists are generated by Helper_GlutDrawGeometry(...) if pgDisplayListBase!=0 380 | if (pgDisplayListBase && *pgDisplayListBase) {glDeleteLists(*pgDisplayListBase,40);*pgDisplayListBase=0;} 381 | } 382 | 383 | 384 | 385 | void DrawGL(void) 386 | { 387 | // All the things about time are just used to display FPS (F2) 388 | // or to move objects around (NOT for shadow) 389 | static unsigned begin = 0; 390 | static unsigned cur_time = 0; 391 | unsigned elapsed_time,delta_time; 392 | float elapsedMs;float cosAlpha,sinAlpha; // used to move objects around 393 | 394 | // These two instead are necessary for shadow mapping 395 | static float vMatrixInverse[16]; // view Matrix inverse (it's the camera matrix). 396 | static float lvpMatrix[16]; // = light_pMatrix*light_vMatrix 397 | 398 | // Just some time stuff here 399 | if (begin==0) begin = glutGet(GLUT_ELAPSED_TIME); 400 | elapsed_time = glutGet(GLUT_ELAPSED_TIME) - begin; 401 | delta_time = elapsed_time - cur_time; 402 | instantFrameTime = (float)delta_time*0.001f; 403 | cur_time = elapsed_time; 404 | 405 | elapsedMs = (float)elapsed_time; 406 | cosAlpha = cos(elapsedMs*0.0005f); 407 | sinAlpha = sin(elapsedMs*0.00075f); 408 | 409 | 410 | // view Matrix 411 | Helper_LookAt(vMatrix,cameraPos[0],cameraPos[1],cameraPos[2],targetPos[0],targetPos[1],targetPos[2],0,1,0); 412 | glLoadMatrixf(vMatrix); 413 | glLightfv(GL_LIGHT0,GL_POSITION,lightDirection); // Important: the ffp must recalculate internally lightDirectionEyeSpace based on vMatrix [=> every frame] 414 | 415 | // view Matrix inverse (it's the camera matrix). Used twice below (and very important to keep in any case). 416 | Helper_InvertMatrixFast(vMatrixInverse,vMatrix); // We can use Helper_InvertMatrixFast(...) instead of Helper_InvertMatrix(...) here [No scaling inside and no projection matrix] 417 | 418 | 419 | // Draw to Shadow Map------------------------------------------------------------------------------------------ 420 | { 421 | # ifndef USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 422 | Helper_GetLightViewProjectionMatrix(lvpMatrix, 423 | vMatrixInverse,pMatrixNearPlane,pMatrixFarPlane,pMatrixFovyDeg,current_aspect_ratio, 424 | lightDirection,1.0f/(float)SHADOW_MAP_RESOLUTION); 425 | # else //USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 426 | Helper_GetLightViewProjectionMatrixExtra(0, 427 | vMatrixInverse,pMatrixNearPlane,pMatrixFarPlane,pMatrixFovyDeg,current_aspect_ratio,config.use_camera_ortho3d_projection_matrix?cameraDistance:0, // in camera ortho3d mode, we can still pass zero as last arg here to avoid shadow-flickering when zooming in-out, at the expense of the further boost in shadow resolution that ortho mode can give us 428 | lightDirection,1.0f/(float)SHADOW_MAP_RESOLUTION, 429 | 0,0, 430 | pMatrixInverse, // Mandatory when we need to retrieve arguments that follow it 431 | 0,0, 432 | lvpMatrix); // Technically this was provided as an 'lvpMatrix for optimal frustum culling usage' in the 'Stable Shadow Mapping' case (but can be used to replace it too) 433 | # endif //USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 434 | 435 | // Draw to shadow map texture 436 | glMatrixMode(GL_PROJECTION);glPushMatrix();glLoadIdentity();glMatrixMode(GL_MODELVIEW); // We'll set the combined light view-projection matrix in GL_MODELVIEW (do you know that it's the same?) 437 | glBindFramebuffer(GL_FRAMEBUFFER, shadowPass.fbo); 438 | glViewport(0, 0, SHADOW_MAP_RESOLUTION,SHADOW_MAP_RESOLUTION); 439 | glClear(GL_DEPTH_BUFFER_BIT); 440 | glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 441 | glCullFace(GL_FRONT); 442 | glEnable(GL_DEPTH_CLAMP); 443 | glUseProgram(shadowPass.program); // we can just use glUseProgram(0) here 444 | glPushMatrix();glLoadMatrixf(lvpMatrix); // we load both (light) projection and view matrices here (it's the same after all) 445 | Helper_GlutDrawGeometry(elapsedMs,cosAlpha,sinAlpha,targetPos,pgDisplayListBase); // Done SHADOW_MAP_NUM_CASCADES times! 446 | glPopMatrix(); 447 | glUseProgram(0); 448 | glDisable(GL_DEPTH_CLAMP); 449 | glCullFace(GL_BACK); 450 | glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 451 | glBindFramebuffer(GL_FRAMEBUFFER,0); 452 | glMatrixMode(GL_PROJECTION);glPopMatrix();glMatrixMode(GL_MODELVIEW); 453 | 454 | } 455 | 456 | // Draw world 457 | { 458 | // biasedShadowMvpMatrix is used only in the DefaultPass: 459 | static float bias[16] = {0.5,0,0,0, 0,0.5,0,0, 0,0,0.5,0, 0.5,0.5,0.5,1}; // Moving from unit cube in NDC [-1,1][-1,1][-1,1] to [0,1][0,1][0,1] (x and y are texCoords; z is the depth range, [0,1] by default in window coordinates) 460 | static float biasedShadowMvpMatrix[16]; // multiplied per vMatrixInverse 461 | Helper_MultMatrix(biasedShadowMvpMatrix,bias,lvpMatrix); 462 | Helper_MultMatrix(biasedShadowMvpMatrix,biasedShadowMvpMatrix,vMatrixInverse); // We do this, so that when in the vertex shader we multiply it with the camera mvMatrix, we get: biasedShadowMvpMatrix * mMatrix (using mMatrices directly in the shaders prevents the usage of double precision matrices: mvMatrices are good when converted to float to feed the shader, mMatrices are bad) 463 | 464 | // Draw to world 465 | glViewport(0, 0, current_width,current_height); 466 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 467 | glBindTexture(GL_TEXTURE_2D,shadowPass.textureId); 468 | glUseProgram(defaultPass.program); 469 | glUniformMatrix4fv(defaultPass.uniform_location_biasedShadowMvpMatrix, 1 /*only setting 1 matrix*/, GL_FALSE /*transpose?*/,biasedShadowMvpMatrix); 470 | Helper_GlutDrawGeometry(elapsedMs,cosAlpha,sinAlpha,targetPos,pgDisplayListBase); // Done SHADOW_MAP_NUM_CASCADES times! 471 | glUseProgram(0); 472 | glBindTexture(GL_TEXTURE_2D,0); 473 | } 474 | 475 | 476 | if (config.show_fps && instantFrameTime>0) { 477 | if ((elapsed_time/1000)%2==0) { 478 | printf("FPS=%1.0f\n",1.f/instantFrameTime);fflush(stdout); 479 | config.show_fps=0; 480 | } 481 | } 482 | 483 | 484 | # ifdef VISUALIZE_DEPTH_TEXTURE 485 | { 486 | glDisable(GL_DEPTH_TEST); 487 | glDisable(GL_CULL_FACE); 488 | glDepthMask(GL_FALSE); 489 | 490 | glMatrixMode(GL_PROJECTION); 491 | glPushMatrix(); 492 | glLoadIdentity(); 493 | 494 | glMatrixMode(GL_MODELVIEW); 495 | glPushMatrix(); 496 | glLoadIdentity(); 497 | 498 | glColor3f(1,1,1); 499 | glDisable(GL_LIGHTING); 500 | glEnable(GL_BLEND); 501 | glBindTexture(GL_TEXTURE_2D,shadowPass.textureId); 502 | glColor4f(1,1,1,0.9f); 503 | glBegin(GL_QUADS); 504 | glTexCoord2f(0,0);glVertex2f(-1, -1); 505 | glTexCoord2f(1,0);glVertex2f(-0.25*current_aspect_ratio, -1); 506 | glTexCoord2f(1,1);glVertex2f(-0.25*current_aspect_ratio, -0.25/current_aspect_ratio); 507 | glTexCoord2f(0,1);glVertex2f(-1, -0.25/current_aspect_ratio); 508 | glEnd(); 509 | glBindTexture(GL_TEXTURE_2D,0); 510 | glDisable(GL_BLEND); 511 | glEnable(GL_LIGHTING); 512 | 513 | glPopMatrix(); 514 | glMatrixMode(GL_PROJECTION); 515 | glPopMatrix(); 516 | glMatrixMode(GL_MODELVIEW); 517 | 518 | glEnable(GL_DEPTH_TEST); 519 | glEnable(GL_CULL_FACE); 520 | glDepthMask(GL_TRUE); 521 | } 522 | # endif //VISUALIZE_DEPTH_TEXTURE 523 | 524 | 525 | } 526 | 527 | static void GlutDestroyWindow(void); 528 | static void GlutCreateWindow(); 529 | 530 | void GlutCloseWindow(void) {Config_Save(&config,ConfigFileName);} 531 | 532 | void GlutNormalKeys(unsigned char key, int x, int y) { 533 | const int mod = glutGetModifiers(); 534 | switch (key) { 535 | case 27: // esc key 536 | Config_Save(&config,ConfigFileName); 537 | GlutDestroyWindow(); 538 | # ifdef __FREEGLUT_STD_H__ 539 | glutLeaveMainLoop(); 540 | # else 541 | exit(0); 542 | # endif 543 | break; 544 | case 13: // return key 545 | { 546 | if (mod&GLUT_ACTIVE_CTRL) { 547 | config.fullscreen_enabled = gameModeWindowId ? 0 : 1; 548 | GlutDestroyWindow(); 549 | GlutCreateWindow(); 550 | } 551 | } 552 | break; 553 | } 554 | 555 | } 556 | 557 | static void updateCameraPos() { 558 | const float distanceY = sin(cameraPitch)*cameraDistance; 559 | const float distanceXZ = cos(cameraPitch)*cameraDistance; 560 | cameraPos[0] = targetPos[0] + sin(cameraYaw)*distanceXZ; 561 | cameraPos[1] = targetPos[1] + distanceY; 562 | cameraPos[2] = targetPos[2] + cos(cameraYaw)*distanceXZ; 563 | } 564 | 565 | static void updateDirectionalLight() { 566 | const float distanceY = sin(lightPitch); 567 | const float distanceXZ = cos(lightPitch); 568 | lightDirection[0] = sin(lightYaw)*distanceXZ; 569 | lightDirection[1] = distanceY; 570 | lightDirection[2] = cos(lightYaw)*distanceXZ; 571 | Helper_Vector3Normalize(lightDirection); 572 | lightDirection[3]=0.f; 573 | } 574 | 575 | static void resetCamera() { 576 | // You can set the initial camera position here through: 577 | targetPos[0]=0; targetPos[1]=0; targetPos[2]=0; // The camera target point 578 | cameraYaw = 2*M_PI; // The camera rotation around the Y axis 579 | cameraPitch = M_PI*0.125f; // The camera rotation around the XZ plane 580 | cameraDistance = 5; // The distance between the camera position and the camera target point 581 | 582 | updateCameraPos(); 583 | if (config.use_camera_ortho3d_projection_matrix) ResizeGL(current_width,current_height); // Needed because in Helper_Orho3D(...) cameraTargetDistance changes 584 | } 585 | 586 | static void resetLight() { 587 | lightYaw = M_PI*0.425f; 588 | lightPitch = M_PI*0.235f; 589 | updateDirectionalLight(); 590 | } 591 | 592 | void GlutSpecialKeys(int key,int x,int y) 593 | { 594 | const int mod = glutGetModifiers(); 595 | if (!(mod&GLUT_ACTIVE_CTRL) && !(mod&GLUT_ACTIVE_SHIFT)) { 596 | switch (key) { 597 | case GLUT_KEY_LEFT: 598 | case GLUT_KEY_RIGHT: 599 | cameraYaw+= instantFrameTime*cameraSpeed*(key==GLUT_KEY_LEFT ? -4.0f : 4.0f); 600 | if (cameraYaw>M_PI) cameraYaw-=2*M_PI; 601 | else if (cameraYaw<=-M_PI) cameraYaw+=2*M_PI; 602 | updateCameraPos(); break; 603 | case GLUT_KEY_UP: 604 | case GLUT_KEY_DOWN: 605 | cameraPitch+= instantFrameTime*cameraSpeed*(key==GLUT_KEY_UP ? 2.f : -2.f); 606 | if (cameraPitch>M_PI-0.001f) cameraPitch=M_PI-0.001f; 607 | else if (cameraPitch<-M_PI*0.05f) cameraPitch=-M_PI*0.05f; 608 | updateCameraPos(); 609 | break; 610 | case GLUT_KEY_PAGE_UP: 611 | case GLUT_KEY_PAGE_DOWN: 612 | cameraDistance+= instantFrameTime*cameraSpeed*(key==GLUT_KEY_PAGE_DOWN ? 25.0f : -25.0f); 613 | if (cameraDistance<1.f) cameraDistance=1.f; 614 | updateCameraPos(); 615 | if (config.use_camera_ortho3d_projection_matrix) ResizeGL(current_width,current_height); // Needed because in Helper_Orho3D(...) cameraTargetDistance changes 616 | break; 617 | case GLUT_KEY_F2: 618 | config.show_fps = !config.show_fps; 619 | //printf("showFPS: %s.\n",config.show_fps?"ON":"OFF");fflush(stdout); 620 | break; 621 | case GLUT_KEY_F1: 622 | config.use_camera_ortho3d_projection_matrix = !config.use_camera_ortho3d_projection_matrix; 623 | //printf("camera ortho mode: %s.\n",config.use_camera_ortho3d_projection_matrix?"ON":"OFF");fflush(stdout); 624 | ResizeGL(current_width,current_height); 625 | break; 626 | case GLUT_KEY_HOME: 627 | // Reset the camera 628 | resetCamera(); 629 | break; 630 | } 631 | } 632 | else if (mod&GLUT_ACTIVE_CTRL) { 633 | switch (key) { 634 | case GLUT_KEY_LEFT: 635 | case GLUT_KEY_RIGHT: 636 | case GLUT_KEY_UP: 637 | case GLUT_KEY_DOWN: 638 | { 639 | // Here we move targetPos and cameraPos at the same time 640 | 641 | // We must find a pivot relative to the camera here (ignoring Y) 642 | float forward[3] = {targetPos[0]-cameraPos[0],0,targetPos[2]-cameraPos[2]}; 643 | float up[3] = {0,1,0}; 644 | float left[3]; 645 | 646 | Helper_Vector3Normalize(forward); 647 | Helper_Vector3Cross(left,up,forward); 648 | { 649 | float delta[3] = {0,0,0};int i; 650 | if (key==GLUT_KEY_LEFT || key==GLUT_KEY_RIGHT) { 651 | float amount = instantFrameTime*cameraSpeed*(key==GLUT_KEY_RIGHT ? -25.0f : 25.0f); 652 | for (i=0;i<3;i++) delta[i]+=amount*left[i]; 653 | } 654 | else { 655 | float amount = instantFrameTime*cameraSpeed*(key==GLUT_KEY_DOWN ? -25.0f : 25.0f); 656 | for ( i=0;i<3;i++) delta[i]+=amount*forward[i]; 657 | } 658 | for ( i=0;i<3;i++) { 659 | targetPos[i]+=delta[i]; 660 | cameraPos[i]+=delta[i]; 661 | } 662 | } 663 | } 664 | break; 665 | case GLUT_KEY_PAGE_UP: 666 | case GLUT_KEY_PAGE_DOWN: 667 | // We use world space coords here. 668 | targetPos[1]+= instantFrameTime*cameraSpeed*(key==GLUT_KEY_PAGE_DOWN ? -25.0f : 25.0f); 669 | if (targetPos[1]<-50.f) targetPos[1]=-50.f; 670 | else if (targetPos[1]>500.f) targetPos[1]=500.f; 671 | updateCameraPos(); 672 | if (config.use_camera_ortho3d_projection_matrix) ResizeGL(current_width,current_height); // Needed because in Helper_Orho3D(...) cameraTargetDistance changes 673 | break; 674 | } 675 | } 676 | else if (mod&GLUT_ACTIVE_SHIFT) { 677 | switch (key) { 678 | case GLUT_KEY_LEFT: 679 | case GLUT_KEY_RIGHT: 680 | lightYaw+= instantFrameTime*cameraSpeed*(key==GLUT_KEY_LEFT ? -4.0f : 4.0f); 681 | if (lightYaw>M_PI) lightYaw-=2*M_PI; 682 | else if (lightYaw<=-M_PI) lightYaw+=2*M_PI; 683 | updateDirectionalLight(); 684 | break; 685 | case GLUT_KEY_UP: 686 | case GLUT_KEY_DOWN: 687 | case GLUT_KEY_PAGE_UP: 688 | case GLUT_KEY_PAGE_DOWN: 689 | lightPitch+= instantFrameTime*cameraSpeed*( (key==GLUT_KEY_UP || key==GLUT_KEY_PAGE_UP) ? 2.f : -2.f); 690 | if (lightPitch>M_PI-0.001f) lightPitch=M_PI-0.001f; 691 | else if (lightPitch<-M_PI*0.05f) lightPitch=-M_PI*0.05f; 692 | updateDirectionalLight(); 693 | break; 694 | case GLUT_KEY_HOME: 695 | // Reset the light 696 | resetLight(); 697 | break; 698 | } 699 | } 700 | } 701 | 702 | void GlutMouse(int a,int b,int c,int d) { 703 | 704 | } 705 | 706 | // Note that we have used GlutFakeDrawGL() so that at startup 707 | // the calling order is: InitGL(),ResizeGL(...),DrawGL() 708 | // Also note that glutSwapBuffers() must NOT be called inside DrawGL() 709 | static void GlutDrawGL(void) {DrawGL();glutSwapBuffers();} 710 | static void GlutIdle(void) {glutPostRedisplay();} 711 | static void GlutFakeDrawGL(void) {glutDisplayFunc(GlutDrawGL);} 712 | void GlutDestroyWindow(void) { 713 | if (gameModeWindowId || windowId) { 714 | DestroyGL(); 715 | 716 | if (gameModeWindowId) { 717 | glutLeaveGameMode(); 718 | gameModeWindowId = 0; 719 | } 720 | if (windowId) { 721 | glutDestroyWindow(windowId); 722 | windowId=0; 723 | } 724 | } 725 | } 726 | void GlutCreateWindow() { 727 | GlutDestroyWindow(); 728 | if (config.fullscreen_enabled) { 729 | char gms[16]=""; 730 | if (config.fullscreen_width>0 && config.fullscreen_height>0) { 731 | sprintf(gms,"%dx%d:32",config.fullscreen_width,config.fullscreen_height); 732 | glutGameModeString(gms); 733 | if (glutGameModeGet (GLUT_GAME_MODE_POSSIBLE)) gameModeWindowId = glutEnterGameMode(); 734 | else config.fullscreen_width=config.fullscreen_height=0; 735 | } 736 | if (gameModeWindowId==0) { 737 | const int screenWidth = glutGet(GLUT_SCREEN_WIDTH); 738 | const int screenHeight = glutGet(GLUT_SCREEN_HEIGHT); 739 | sprintf(gms,"%dx%d:32",screenWidth,screenHeight); 740 | glutGameModeString(gms); 741 | if (glutGameModeGet (GLUT_GAME_MODE_POSSIBLE)) gameModeWindowId = glutEnterGameMode(); 742 | } 743 | } 744 | if (!gameModeWindowId) { 745 | char windowTitle[1024] = PROGRAM_NAME".c\t"; 746 | # ifdef USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 747 | strcat(windowTitle,"[Unstable]\t"); 748 | # endif //USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 749 | strcat(windowTitle,"("XSTR_MACRO(SHADOW_MAP_RESOLUTION)")"); 750 | config.fullscreen_enabled = 0; 751 | glutInitWindowPosition(100,100); 752 | glutInitWindowSize(config.windowed_width,config.windowed_height); 753 | windowId = glutCreateWindow(windowTitle); 754 | } 755 | 756 | glutKeyboardFunc(GlutNormalKeys); 757 | glutSpecialFunc(GlutSpecialKeys); 758 | glutMouseFunc(GlutMouse); 759 | glutIdleFunc(GlutIdle); 760 | glutReshapeFunc(ResizeGL); 761 | glutDisplayFunc(GlutFakeDrawGL); 762 | # ifdef __FREEGLUT_STD_H__ 763 | glutWMCloseFunc(GlutCloseWindow); 764 | # endif //__FREEGLUT_STD_H__ 765 | 766 | #ifdef USE_GLEW 767 | { 768 | GLenum err = glewInit(); 769 | if( GLEW_OK != err ) { 770 | fprintf(stderr, "Error initializing GLEW: %s\n", glewGetErrorString(err) ); 771 | return; 772 | } 773 | } 774 | #endif //USE_GLEW 775 | 776 | InitGL(); 777 | 778 | } 779 | 780 | 781 | int main(int argc, char** argv) 782 | { 783 | 784 | glutInit(&argc, argv); 785 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); 786 | //glutInitContextFlags(GLUT_FORWARD_COMPATIBLE); 787 | #ifdef __FREEGLUT_STD_H__ 788 | glutSetOption ( GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION ) ; 789 | #endif //__FREEGLUT_STD_H__ 790 | 791 | Config_Init(&config); 792 | Config_Load(&config,ConfigFileName); 793 | 794 | GlutCreateWindow(); 795 | 796 | //OpenGL info 797 | printf("\nGL Vendor: %s\n", glGetString( GL_VENDOR )); 798 | printf("GL Renderer : %s\n", glGetString( GL_RENDERER )); 799 | printf("GL Version (string) : %s\n", glGetString( GL_VERSION )); 800 | printf("GLSL Version : %s\n", glGetString( GL_SHADING_LANGUAGE_VERSION )); 801 | //printf("GL Extensions:\n%s\n",(char *) glGetString(GL_EXTENSIONS)); 802 | 803 | printf("\nKEYS:\n"); 804 | printf("AROW KEYS + PAGE_UP/PAGE_DOWN:\tmove camera (optionally with CTRL down)\n"); 805 | printf("HOME KEY:\t\t\treset camera\n"); 806 | printf("ARROW KEYS + SHIFT:\tmove directional light\n"); 807 | printf("CTRL+RETURN:\t\ttoggle fullscreen on/off\n"); 808 | printf("F2:\t\t\tdisplay FPS\n"); 809 | printf("F1:\t\t\ttoggle camera ortho mode on and off\n"); 810 | printf("\n"); 811 | 812 | resetCamera(); // Mandatory 813 | resetLight(); // Mandatory 814 | 815 | glutMainLoop(); 816 | 817 | 818 | return 0; 819 | } 820 | 821 | void DrawGeometry(float elapsedMs,float cosAlpha,float sinAlpha) { 822 | int i,j; 823 | 824 | // ground 825 | glColor3f(0.2,0.4,0.2); 826 | glPushMatrix(); 827 | 828 | glTranslatef(0,-0.5,0); 829 | 830 | glPushMatrix(); 831 | glScalef(11.f,0.5f,14.f); 832 | glutSolidCube(1.0); 833 | glPopMatrix(); 834 | 835 | glPopMatrix(); 836 | 837 | // sphere 838 | glColor3f(0.8,0.8,0); 839 | glPushMatrix(); 840 | 841 | glTranslatef(-1+2.5*cosAlpha,0.25,-1+sinAlpha); 842 | 843 | glPushMatrix(); 844 | glRotatef(-elapsedMs*0.05f,0,1,0); 845 | glScalef(1.f,1.f,1.f); 846 | glutSolidSphere(0.5,16,16); 847 | glPopMatrix(); 848 | 849 | glPopMatrix(); 850 | 851 | // tours 852 | glColor3f(0.4,0.4,0.8); 853 | glPushMatrix(); 854 | 855 | glTranslatef(-0.5+2.5*cosAlpha,0.5,2-sinAlpha); 856 | 857 | glPushMatrix(); 858 | glRotatef(elapsedMs*0.05f,0,1,0); 859 | glScalef(1.f,1.f,1.f); 860 | glutSolidTorus(0.25,0.5,16,16); 861 | glPopMatrix(); 862 | 863 | glPopMatrix(); 864 | 865 | // teapot 866 | glColor3f(0.4,0.2,0.0); 867 | glPushMatrix(); 868 | 869 | glTranslatef(-0.4,0.1,-4); 870 | 871 | glPushMatrix(); 872 | glRotatef(elapsedMs*0.1f,0,1,0); 873 | glScalef(1.f,1.f,1.f); 874 | glFrontFace(GL_CW);glutSolidTeapot(0.5);glFrontFace(GL_CCW); 875 | //glutSolidCube(0.75); 876 | glPopMatrix(); 877 | 878 | glPopMatrix(); 879 | 880 | // cube 881 | glColor3f(0.5,0.5,1.0); 882 | glPushMatrix(); 883 | glTranslatef(-2,0.3,0.25); 884 | glScalef(0.5f,1.5f,0.5f); 885 | glutSolidCube(0.75); 886 | glPopMatrix(); 887 | 888 | 889 | // columns 890 | glColor3f(0.35,0.35,0.35); 891 | for (j=0;j<2;j++) { 892 | for (i=0;i<=10;i++) { 893 | glPushMatrix(); 894 | 895 | glTranslatef(4.75-j*2.0,2.7+0.31,-5.f+(float)i*1.0); 896 | glPushMatrix(); 897 | glRotatef(90,1,0,0); 898 | 899 | glPushMatrix(); 900 | glScalef(0.2f,0.2f,2.8f); 901 | 902 | // central part 903 | glutSolidCylinder(0.5,1.0,8,8); 904 | 905 | // higher part 906 | glutSolidCone(0.8f,0.1f,8,8); 907 | glTranslatef(0.f,0.f,-0.025f); 908 | glutSolidCylinder(0.8,0.025,8,8); 909 | 910 | // lower part 911 | glTranslatef(0.f,0.f,1.05); 912 | glFrontFace(GL_CW);glutSolidCone(0.8f,-0.1f,8,8);glFrontFace(GL_CCW); 913 | glTranslatef(0.f,0.f,0.0f); 914 | glutSolidCylinder(0.8,0.025,8,8); 915 | 916 | glPopMatrix(); 917 | 918 | 919 | glPopMatrix(); 920 | 921 | glPopMatrix(); 922 | } 923 | } 924 | 925 | 926 | // column plane under roof 927 | glColor3f(0.8,0.8,0.8); 928 | glPushMatrix(); 929 | glTranslatef(3.75,3.16,0.f); 930 | glScalef(2.5f,0.155f,10.75f); 931 | glutSolidCube(1.0); 932 | glPopMatrix(); 933 | 934 | // column roof 935 | glColor3f(0.4,0.0,0.0); 936 | glPushMatrix(); 937 | glTranslatef(3.75,3.02+0.48,-10.75*0.5); 938 | glScalef(0.825f,0.3f,1.f); 939 | glRotatef(90,0,0,1); 940 | glutSolidCylinder(1.75,10.75,3,3); 941 | glPopMatrix(); 942 | 943 | // column base 944 | glColor3f(0.2,0.2,0.2); 945 | glPushMatrix(); 946 | 947 | glTranslatef(3.75,-0.01f,0.f); 948 | glScalef(2.8f,0.155f,10.5f); 949 | glutSolidCube(1.0); 950 | 951 | glTranslatef(0,-1,0); 952 | glScalef(1.15f,1,1.05f); 953 | glutSolidCube(1.0); 954 | 955 | glPopMatrix(); 956 | 957 | // camera target pos: 958 | // Center 959 | glColor3f(0,0,0); 960 | glPushMatrix(); 961 | glTranslatef(targetPos[0],targetPos[1],targetPos[2]); 962 | glPushMatrix(); 963 | glutSolidSphere(0.04,8,8); 964 | 965 | // X Axis 966 | glPushMatrix(); 967 | glColor3f(1,0,0); 968 | glRotatef(90,0,1,0); 969 | glutSolidCylinder(0.04,0.25,8,8); 970 | glTranslatef(0,0,0.25); 971 | glutSolidCone(0.06,0.1,8,8); 972 | glPopMatrix(); 973 | 974 | // Y Axis 975 | glPushMatrix(); 976 | glColor3f(0,1,0); 977 | glRotatef(-90,1,0,0); 978 | glutSolidCylinder(0.04,0.25,8,8); 979 | glTranslatef(0,0,0.25); 980 | glutSolidCone(0.06,0.1,8,8); 981 | glPopMatrix(); 982 | 983 | // Z Axis 984 | glPushMatrix(); 985 | glColor3f(0,0,1); 986 | glutSolidCylinder(0.04,0.25,8,8); 987 | glTranslatef(0,0,0.25); 988 | glutSolidCone(0.06,0.1,8,8); 989 | glPopMatrix(); 990 | 991 | glPopMatrix(); 992 | glPopMatrix(); 993 | // End camera target position 994 | 995 | //# define DBG_BIG_OCCLUDING_WALL 996 | # ifdef DBG_BIG_OCCLUDING_WALL 997 | // big occluding wall 998 | glColor3f(0.5,0.1,0.1); 999 | glPushMatrix(); 1000 | 1001 | glTranslatef(10,0,0); 1002 | 1003 | glPushMatrix(); 1004 | glScalef(0.25f,2.f*pMatrixFarPlane,2.f*pMatrixFarPlane); 1005 | glutSolidCube(1.0); 1006 | glPopMatrix(); 1007 | 1008 | glPopMatrix(); 1009 | # endif // DBG_BIG_OCCLUDING_WALL 1010 | 1011 | } 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | -------------------------------------------------------------------------------- /shadow_mapping_cascade.c: -------------------------------------------------------------------------------- 1 | // https://github.com/Flix01/Tiny-OpenGL-Shadow-Mapping-Examples 2 | /** License 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | * SOFTWARE. 18 | */ 19 | 20 | // DEPENDENCIES: 21 | /* 22 | -> glut or freeglut (the latter is recommended) 23 | -> glew (Windows only) 24 | */ 25 | 26 | // HOW TO COMPILE: 27 | /* 28 | // LINUX: 29 | gcc -O2 -std=gnu89 -no-pie shadow_mapping_cascade.c -o shadow_mapping_cascade -I"../" -lglut -lGL -lX11 -lm 30 | // WINDOWS (here we use the static version of glew, and glut32.lib, that can be replaced by freeglut.lib): 31 | cl /O2 /MT /Tc shadow_mapping_cascade.c /D"GLEW_STATIC" /I"../" /link /out:shadow_mapping_cascade.exe glut32.lib glew32s.lib opengl32.lib gdi32.lib Shell32.lib comdlg32.lib user32.lib kernel32.lib 32 | 33 | 34 | // IN ADDITION: 35 | By default the source file assumes that every OpenGL-related header is in "GL/". 36 | But you can define in the command-line the correct paths you use in your system 37 | for glut.h, glew.h, etc. with something like: 38 | -DGLUT_PATH=\"Glut/glut.h\" 39 | -DGLEW_PATH=\"Glew/glew.h\" 40 | (this syntax works on Linux, don't know about Windows) 41 | */ 42 | 43 | //#define USE_GLEW // By default it's only defined for Windows builds (but can be defined in Linux/Mac builds too) 44 | 45 | 46 | #define PROGRAM_NAME "shadow_mapping_cascade" 47 | #define VISUALIZE_DEPTH_TEXTURE 48 | //#define VISUALIZE_CASCADE_SPLITS 49 | #define SHADOW_MAP_HEIGHT 512 //SHADOW_MAP_WIDTH = SHADOW_MAP_NUM_CASCADES*SHADOW_MAP_HEIGHT 50 | #define SHADOW_MAP_NUM_CASCADES 4 51 | #define SHADOW_MAP_CASCADE_LAMBDA 0.7 // in [0=uniform splits,1=logarithmic splits] logarithmic splits put higher resolution near the camera 52 | #define SHADOW_MAP_CLAMP_MODE GL_CLAMP_TO_EDGE // GL_CLAMP or GL_CLAMP_TO_EDGE or GL_CLAMP_TO_BORDER 53 | // GL_CLAMP; // sampling outside of the shadow map gives always shadowed pixels 54 | // GL_CLAMP_TO_EDGE; // sampling outside of the shadow map can give shadowed or unshadowed pixels (it depends on the edge of the shadow map) 55 | // GL_CLAMP_TO_BORDER; // sampling outside of the shadow map gives always non-shadowed pixels (if we set the border color correctly) 56 | #define SHADOW_MAP_FILTER GL_LINEAR // GL_LINEAR or GL_NEAREST (GL_LINEAR is more useful with a sampler2DShadow, that cannot be used with esponential shadow mapping) 57 | 58 | //#define USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE // Better resolution, but shadow-swimming as the camera rotates (on static objects). Please see README.md about it. 59 | 60 | // Warning: This is one of the demos that supports switching from perspective camera view to ortho camera view [F1 key]. 61 | // However please note that in ortho view the shadow mapping algorithm is currently ALWAYS a bit unstable (even if USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE is not defined). 62 | // 63 | // The reason is that I've tried just using the 'perspective' version of the shadow map algorithm and it does not work correctly for cascaded shadow maps (it worked well in non-cascaded shadow_mapping.c). 64 | // That's why Helper_GetLightViewProjectionMatrices(...) takes one more argument than Helper_GetLightViewProjectionMatrix(...): 'cameraTargetDistanceForOrtho3DModeOnly_or_zero'. 65 | // 66 | // Ideally it should be called 'cameraTargetDistanceForUnstableOrtho3DModeOnly_or_zero', but in the case of cascaded shadow maps if we set it to zero (= we use the same algo used in perspective mode for ortho mode) 67 | // it does not work. That's why ortho mode is currently always a bit 'unstable' for cascaded shadow maps (well, less unstable than defining USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE, that works as expected). 68 | // 69 | // Hope this point is clear enough... 70 | 71 | 72 | // These path definitions can be passed to the compiler command-line 73 | #ifndef GLUT_PATH 74 | # define GLUT_PATH "GL/glut.h" // Mandatory 75 | #endif //GLEW_PATH 76 | #ifndef FREEGLUT_EXT_PATH 77 | # define FREEGLUT_EXT_PATH "GL/freeglut_ext.h" // Optional (used only if glut.h comes from the freeglut library) 78 | #endif //GLEW_PATH 79 | #ifndef GLEW_PATH 80 | # define GLEW_PATH "GL/glew.h" // Mandatory for Windows only 81 | #endif //GLEW_PATH 82 | 83 | #ifdef _WIN32 84 | # include "windows.h" 85 | # define USE_GLEW 86 | #endif //_WIN32 87 | 88 | #ifdef USE_GLEW 89 | # include GLEW_PATH 90 | #else //USE_GLEW 91 | # define GL_GLEXT_PROTOTYPES 92 | #endif //USE_GLEW 93 | 94 | #include GLUT_PATH 95 | #ifdef __FREEGLUT_STD_H__ 96 | # include FREEGLUT_EXT_PATH 97 | #endif //__FREEGLUT_STD_H__ 98 | 99 | // These derived definitions can't be touched [XSTR_MACRO(...) is used to insert an 'integer definition' between double quotes] 100 | #define STR_MACRO(s) #s 101 | #define XSTR_MACRO(s) STR_MACRO(s) 102 | #define SHADOW_MAP_NUM_CASCADES_STRING XSTR_MACRO(SHADOW_MAP_NUM_CASCADES) 103 | #define SHADOW_MAP_WIDTH (SHADOW_MAP_HEIGHT*SHADOW_MAP_NUM_CASCADES) // Fixed 104 | 105 | 106 | #include "helper_functions.h" // please search this .c file for "Helper_": 107 | // only very few of its functions are used. 108 | 109 | #include 110 | #include 111 | #include 112 | 113 | 114 | // Config file handling: basically there's an .ini file next to the 115 | // exe that you can tweak. (it's just an extra) 116 | const char* ConfigFileName = PROGRAM_NAME".ini"; 117 | typedef struct { 118 | int fullscreen_width,fullscreen_height; 119 | int windowed_width,windowed_height; 120 | int fullscreen_enabled; 121 | int show_fps; 122 | int use_camera_ortho3d_projection_matrix; 123 | } Config; 124 | void Config_Init(Config* c) { 125 | c->fullscreen_width=c->fullscreen_height=0; 126 | c->windowed_width=960;c->windowed_height=540; 127 | c->fullscreen_enabled=0; 128 | c->show_fps = 0; 129 | c->use_camera_ortho3d_projection_matrix = 0; 130 | } 131 | int Config_Load(Config* c,const char* filePath) { 132 | FILE* f = fopen(filePath, "rt"); 133 | char ch='\0';char buf[256]=""; 134 | size_t nread=0; 135 | int numParsedItem=0; 136 | if (!f) return -1; 137 | while ((ch = fgetc(f)) !=EOF) { 138 | buf[nread]=ch; 139 | nread++; 140 | if (nread>255) { 141 | nread=0; 142 | continue; 143 | } 144 | if (ch=='\n') { 145 | buf[nread]='\0'; 146 | if (nread<2 || buf[0]=='[' || buf[0]=='#') {nread = 0;continue;} 147 | if (nread>2 && buf[0]=='/' && buf[1]=='/') {nread = 0;continue;} 148 | // Parse 149 | switch (numParsedItem) { 150 | case 0: 151 | sscanf(buf, "%d %d", &c->fullscreen_width,&c->fullscreen_height); 152 | break; 153 | case 1: 154 | sscanf(buf, "%d %d", &c->windowed_width,&c->windowed_height); 155 | break; 156 | case 2: 157 | sscanf(buf, "%d", &c->fullscreen_enabled); 158 | break; 159 | case 3: 160 | sscanf(buf, "%d", &c->show_fps); 161 | break; 162 | case 4: 163 | sscanf(buf, "%d", &c->use_camera_ortho3d_projection_matrix); 164 | break; 165 | } 166 | nread=0; 167 | ++numParsedItem; 168 | } 169 | } 170 | fclose(f); 171 | if (c->windowed_width<=0) c->windowed_width=720; 172 | if (c->windowed_height<=0) c->windowed_height=405; 173 | return 0; 174 | } 175 | int Config_Save(Config* c,const char* filePath) { 176 | FILE* f = fopen(filePath, "wt"); 177 | if (!f) return -1; 178 | fprintf(f, "[Size In Fullscreen Mode (zero means desktop size)]\n%d %d\n",c->fullscreen_width,c->fullscreen_height); 179 | fprintf(f, "[Size In Windowed Mode]\n%d %d\n",c->windowed_width,c->windowed_height); 180 | fprintf(f, "[Fullscreen Enabled (0 or 1) (CTRL+RETURN)]\n%d\n", c->fullscreen_enabled); 181 | fprintf(f, "[Show FPS (0 or 1) (F2)]\n%d\n", c->show_fps); 182 | fprintf(f, "[Use camera ortho3d projection matrix (0 or 1) (F1)]\n%d\n", c->use_camera_ortho3d_projection_matrix); 183 | fprintf(f,"\n"); 184 | fclose(f); 185 | return 0; 186 | } 187 | 188 | Config config; 189 | 190 | // glut has a special fullscreen GameMode that you can toggle with CTRL+RETURN (not in WebGL) 191 | int windowId = 0; // window Id when not in fullscreen mode 192 | int gameModeWindowId = 0; // window Id when in fullscreen mode 193 | 194 | // Now we can start with our program 195 | 196 | // camera data: 197 | float targetPos[3]; // please set it in resetCamera() 198 | float cameraYaw; // please set it in resetCamera() 199 | float cameraPitch; // please set it in resetCamera() 200 | float cameraDistance; // please set it in resetCamera() 201 | float cameraPos[3]; // Derived value (do not edit) 202 | float vMatrix[16]; // view matrix 203 | float cameraSpeed = 0.5f; // When moving it 204 | 205 | // light data 206 | float lightYaw = M_PI*0.425f,lightPitch = M_PI*0.235f; // must be copied to resetLight() too 207 | float lightDirection[4] = {0,1,0,0}; // Derived value (do not edit) [lightDirection[3]==0] 208 | 209 | // pMatrix data: 210 | float pMatrix[16]; // projection matrix 211 | const float pMatrixFovyDeg = 45.f; 212 | const float pMatrixNearPlane = 0.5f; 213 | const float pMatrixFarPlane = 20.0f; 214 | 215 | // we calculate these in ResizeGL(...) 216 | float gCascadeNearAndFarClippingPlanes[SHADOW_MAP_NUM_CASCADES+1]; // Array of the clipping planes of each cascade (gCascadeNearAndFarClippingPlanes[0]==pMatrixNearPlane and gCascadeNearAndFarClippingPlanes[SHADOW_MAP_NUM_CASCADES]==pMatrixFarPlane) 217 | #ifdef USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 218 | float gCascadePMatricesInv[16*SHADOW_MAP_NUM_CASCADES]; // One inverse pMatrix per cascade (calculated in ResizeGL(...)) 219 | #endif //USE_UNSTABLE_SHADOW_MAPPING_TECHNIQUE 220 | 221 | 222 | float instantFrameTime = 16.2f; 223 | 224 | // Optional (to speed up Helper_GlutDrawGeometry(...) a bit) 225 | GLuint gDisplayListBase = 0;GLuint* pgDisplayListBase = &gDisplayListBase; // Can be set to 0 as a fallback. 226 | 227 | 228 | static const char* ShadowPassVertexShader[] = { 229 | " void main() {\n" 230 | " gl_Position = ftransform();\n" 231 | " }\n" 232 | }; 233 | static const char* ShadowPassFragmentShader[] = { 234 | " void main() {\n" 235 | " //gl_FragColor = gl_Color;\n" 236 | " }\n" 237 | }; 238 | typedef struct { 239 | GLuint fbo; 240 | GLuint textureId; 241 | GLuint program; 242 | } ShadowPass; 243 | 244 | ShadowPass shadowPass; 245 | void InitShadowPass(ShadowPass* sp) { 246 | sp->program = Helper_LoadShaderProgramFromSource(*ShadowPassVertexShader,*ShadowPassFragmentShader); 247 | 248 | // create depth texture 249 | glGenTextures(1, &sp->textureId); 250 | glBindTexture(GL_TEXTURE_2D, sp->textureId); 251 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, SHADOW_MAP_FILTER); 252 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, SHADOW_MAP_FILTER); 253 | # ifndef __EMSCRIPTEN__ 254 | glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); 255 | # else //__EMSCRIPTEN__ 256 | glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0); 257 | # undef SHADOW_MAP_CLAMP_MODE 258 | # define SHADOW_MAP_CLAMP_MODE GL_CLAMP_TO_EDGE 259 | # endif //__EMSCRIPTEN__ 260 | if (SHADOW_MAP_CLAMP_MODE==GL_CLAMP_TO_BORDER) { 261 | const GLfloat border[] = {1.0f,1.0f,1.0f,0.0f }; 262 | glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); 263 | } 264 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SHADOW_MAP_CLAMP_MODE ); 265 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SHADOW_MAP_CLAMP_MODE ); 266 | glBindTexture(GL_TEXTURE_2D, 0); 267 | 268 | // create depth fbo 269 | glGenFramebuffers(1, &sp->fbo); 270 | glBindFramebuffer(GL_FRAMEBUFFER, sp->fbo); 271 | # ifndef __EMSCRIPTEN__ 272 | glDrawBuffer(GL_NONE); // Instruct openGL that we won't bind a color texture with the currently bound FBO 273 | glReadBuffer(GL_NONE); 274 | # endif //__EMSCRIPTEN__ 275 | glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,sp->textureId, 0); 276 | { 277 | //Does the GPU support current FBO configuration? 278 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 279 | if (status!=GL_FRAMEBUFFER_COMPLETE) printf("glCheckFramebufferStatus(...) FAILED for shadowPass.fbo.\n"); 280 | } 281 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 282 | } 283 | void DestroyShadowPass(ShadowPass* sp) { 284 | if (sp->program) {glDeleteProgram(sp->program);sp->program=0;} 285 | if (sp->fbo) {glDeleteBuffers(1,&sp->fbo);sp->fbo=0;} 286 | if (sp->textureId) {glDeleteTextures(1,&sp->textureId);} 287 | } 288 | 289 | 290 | static const char* DefaultPassVertexShader[] = { 291 | "varying vec4 v_diffuse;\n" 292 | "varying vec4 v_vertexModelViewSpace;\n" 293 | "varying float v_vertexModelViewSpaceDepth;\n" // A bit redundant (we can calculate it in the fragment shader using v_vertexModelViewSpace) 294 | "\n" 295 | "void main() {\n" 296 | " gl_Position = ftransform();\n" 297 | "\n" 298 | " vec3 normal = gl_NormalMatrix * gl_Normal;\n" 299 | " vec3 lightVector = gl_LightSource[0].position.xyz\n;// - gl_Vertex.xyz;\n" 300 | " float nxDir = max(0.0, dot(normal, lightVector));\n" 301 | " v_diffuse = gl_LightSource[0].diffuse * nxDir; \n" 302 | "\n" 303 | " gl_FrontColor = gl_Color;\n" 304 | "\n" 305 | " v_vertexModelViewSpace = gl_ModelViewMatrix*gl_Vertex;\n" 306 | " v_vertexModelViewSpaceDepth = -v_vertexModelViewSpace.z/v_vertexModelViewSpace.w;\n" // Negative, because camera looks in the -z axis (and u_cascadeNearAndFarClippingPlanes[...] are all positive quantities) 307 | "}\n" 308 | }; 309 | static const char* DefaultPassFragmentShader[] = { 310 | # ifdef VISUALIZE_CASCADE_SPLITS 311 | "#version 120\n" 312 | "#extension GL_EXT_gpu_shader4 : enable\n" 313 | "#define NUM_CASCADES "SHADOW_MAP_NUM_CASCADES_STRING"\n" 314 | "const vec3 dbgSplitColors[4] = vec3[4](vec3(0.5,0.0,0.0),vec3(0.0,0.5,0.0),vec3(0.0,0.0,0.5),vec3(0.5,0.5,0.0));\n" 315 | # else //VISUALIZE_CASCADE_SPLITS 316 | "#define NUM_CASCADES "SHADOW_MAP_NUM_CASCADES_STRING"\n" 317 | # endif //VISUALIZE_CASCADE_SPLITS 318 | "\n" 319 | "uniform sampler2D u_shadowMap;\n" 320 | "uniform vec2 u_shadowDarkening;\n" // .x = fDarkeningFactor [10.0-80.0], .y = min value clamp [0.0-1.0] 321 | "uniform float u_cascadeNearAndFarClippingPlanes[NUM_CASCADES+1];\n" 322 | "uniform mat4 u_biasedShadowMvpMatrix[NUM_CASCADES];\n" // Actually they are: (u_biasedShadowMvpMatrix[NUM_CASCADES] * vMatrixInverseCamera) please see the code. 323 | "\n" 324 | "varying vec4 v_diffuse;\n" 325 | "varying vec4 v_vertexModelViewSpace;\n" 326 | "varying float v_vertexModelViewSpaceDepth;\n" 327 | "\n" 328 | "float CalcShadowFactor(int CascadeIndex, vec4 shadowCoord) {\n" 329 | " vec4 shadowCoordinateWdivide = shadowCoord/shadowCoord.w;\n" 330 | " shadowCoordinateWdivide.x+= float(CascadeIndex);shadowCoordinateWdivide.x/= float(NUM_CASCADES);\n" 331 | " return clamp(exp(u_shadowDarkening.x*(texture2D(u_shadowMap,(shadowCoordinateWdivide.st)).r - shadowCoordinateWdivide.z)),u_shadowDarkening.y,1.0);\n" 332 | " }\n" 333 | "\n" 334 | "void main() {\n" 335 | " // Figure out which cascade to sample from\n" 336 | " float cascadeIdxFloat = float(NUM_CASCADES-1);\n" 337 | " for(int i=1;i