├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── data ├── mesh │ ├── plane.mtl │ └── plane.obj ├── screenshot_1.jpg ├── screenshot_2.jpg └── texture │ ├── LDR_LLL1_0.png │ ├── curlNoise.png │ └── grid.png └── src ├── .clang-format ├── CMakeLists.txt ├── main.cpp └── shader ├── atmosphere.glsl ├── clouds_fs.glsl ├── detail_noise_cs.glsl ├── mesh_fs.glsl ├── mesh_vs.glsl ├── noise.glsl ├── shape_noise_cs.glsl ├── tonemap_fs.glsl └── triangle_vs.glsl /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | 9 | # Precompiled Headers 10 | *.gch 11 | *.pch 12 | 13 | # Compiled Dynamic libraries 14 | *.so 15 | *.dylib 16 | *.dll 17 | 18 | # Fortran module files 19 | *.mod 20 | *.smod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | 33 | external/ 34 | external/* 35 | external/dwSampleFramwork 36 | bin/ 37 | bin/* 38 | build/ 39 | build/* 40 | lib/ 41 | lib/* 42 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/dw-sample-framework"] 2 | path = external/dw-sample-framework 3 | url = https://github.com/diharaw/dw-sample-framework.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project("volumetric-clouds") 4 | 5 | set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "ASSIMP_BUILD_ASSIMP_TOOLS") 6 | set(ASSIMP_BUILD_ASSIMP_VIEW OFF CACHE BOOL "ASSIMP_BUILD_ASSIMP_VIEW") 7 | set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "ASSIMP_BUILD_TESTS") 8 | set(ASSIMP_INSTALL OFF CACHE BOOL "ASSIMP_INSTALL") 9 | set(ASSIMP_INSTALL_PDB OFF CACHE BOOL "ASSIMP_INSTALL_PDB") 10 | set(ASSIMP_NO_EXPORT ON CACHE BOOL "ASSIMP_NO_EXPORT") 11 | set(BUILD_SAMPLES OFF CACHE BOOL "BUILD_SAMPLES") 12 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "GLFW_BUILD_DOCS") 13 | set(GLFW_INSTALL OFF CACHE BOOL "GLFW_INSTALL") 14 | 15 | IF(APPLE) 16 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") 17 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") 18 | ENDIF() 19 | 20 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 21 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 22 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) 23 | 24 | add_subdirectory(external/dw-sample-framework) 25 | 26 | include_directories("${DW_SAMPLE_FRAMEWORK_INCLUDES}") 27 | 28 | add_subdirectory(src) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dihara Wijetunga 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 | [![License: MIT](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://opensource.org/licenses/MIT) 2 | 3 | # Volumetric Clouds 4 | An OpenGL sample that implements volumetric clouds using ray marching and compute shaders. 5 | 6 | ## Screenshots 7 | ![volumetric-clouds](data/screenshot_1.jpg) 8 | ![volumetric-clouds](data/screenshot_2.jpg) 9 | 10 | ## Building 11 | 12 | ### Windows 13 | Tested on: Windows 10 version 21H1 14 | 15 | Prerequisites 16 | * MSVC 19.00 or higher 17 | * CMake 3.8 or higher 18 | 19 | ``` 20 | git clone --recursive https://github.com/diharaw/volumetric-clouds.git 21 | cd volumetric-clouds 22 | mkdir build 23 | cd build 24 | cmake -G .. 25 | ``` 26 | 27 | ## Dependencies 28 | * [dwSampleFramework](https://github.com/diharaw/dwSampleFramework) 29 | 30 | ## License 31 | ``` 32 | Copyright (c) 2023 Dihara Wijetunga 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 35 | associated documentation files (the "Software"), to deal in the Software without restriction, 36 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 37 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 38 | subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all copies or substantial 41 | portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 44 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 45 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 46 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | ``` 49 | -------------------------------------------------------------------------------- /data/mesh/plane.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl Material.001 5 | Ns 250.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | -------------------------------------------------------------------------------- /data/mesh/plane.obj: -------------------------------------------------------------------------------- 1 | # Blender v3.3.1 OBJ File: '' 2 | # www.blender.org 3 | mtllib plane.mtl 4 | o Plane 5 | v -10000.000000 0.000000 10000.000000 6 | v 10000.000000 0.000000 10000.000000 7 | v -10000.000000 0.000000 -10000.000000 8 | v 10000.000000 0.000000 -10000.000000 9 | vt 95.214630 95.214661 10 | vt -94.214630 95.214661 11 | vt -94.214615 -94.214661 12 | vt 95.214630 -94.214592 13 | vn 0.0000 1.0000 0.0000 14 | usemtl Material.001 15 | s off 16 | f 1/1/1 2/2/1 4/3/1 3/4/1 17 | -------------------------------------------------------------------------------- /data/screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/volumetric-clouds/96f46c73111fbc3379a5c098794e1cc23d864e65/data/screenshot_1.jpg -------------------------------------------------------------------------------- /data/screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/volumetric-clouds/96f46c73111fbc3379a5c098794e1cc23d864e65/data/screenshot_2.jpg -------------------------------------------------------------------------------- /data/texture/LDR_LLL1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/volumetric-clouds/96f46c73111fbc3379a5c098794e1cc23d864e65/data/texture/LDR_LLL1_0.png -------------------------------------------------------------------------------- /data/texture/curlNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/volumetric-clouds/96f46c73111fbc3379a5c098794e1cc23d864e65/data/texture/curlNoise.png -------------------------------------------------------------------------------- /data/texture/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/volumetric-clouds/96f46c73111fbc3379a5c098794e1cc23d864e65/data/texture/grid.png -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: 'true' 5 | AlignConsecutiveDeclarations: 'true' 6 | AlignEscapedNewlines: Left 7 | AlignOperands: 'false' 8 | AlignTrailingComments: 'true' 9 | AllowAllParametersOfDeclarationOnNextLine: 'true' 10 | AllowShortBlocksOnASingleLine: 'true' 11 | AllowShortCaseLabelsOnASingleLine: 'true' 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: 'true' 14 | AllowShortLoopsOnASingleLine: 'true' 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: 'false' 17 | AlwaysBreakTemplateDeclarations: 'true' 18 | BinPackArguments: 'false' 19 | BinPackParameters: 'false' 20 | BreakBeforeBraces: Allman 21 | BreakBeforeInheritanceComma: 'false' 22 | BreakBeforeTernaryOperators: 'false' 23 | BreakConstructorInitializers: AfterColon 24 | BreakStringLiterals: 'true' 25 | CompactNamespaces: 'true' 26 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 27 | Cpp11BracedListStyle: 'false' 28 | FixNamespaceComments: 'true' 29 | IncludeBlocks: Regroup 30 | IndentCaseLabels: 'true' 31 | IndentPPDirectives: AfterHash 32 | IndentWrappedFunctionNames: 'true' 33 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 34 | Language: Cpp 35 | NamespaceIndentation: None 36 | PointerAlignment: Left 37 | ReflowComments: 'true' 38 | SortIncludes: 'false' 39 | SortUsingDeclarations: 'true' 40 | SpaceAfterCStyleCast: 'false' 41 | SpaceBeforeAssignmentOperators: 'true' 42 | SpaceBeforeParens: ControlStatements 43 | SpaceInEmptyParentheses: 'false' 44 | SpacesInAngles: 'false' 45 | SpacesInCStyleCastParentheses: 'false' 46 | SpacesInContainerLiterals: 'true' 47 | SpacesInParentheses: 'false' 48 | SpacesInSquareBrackets: 'false' 49 | 50 | ... 51 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | find_program(CLANG_FORMAT_EXE NAMES "clang-format" DOC "Path to clang-format executable") 4 | 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 7 | 8 | set(VOLUMETRIC_CLOUDS_SOURCES ${PROJECT_SOURCE_DIR}/src/main.cpp) 9 | file(GLOB_RECURSE SHADER_SOURCES ${PROJECT_SOURCE_DIR}/src/*.glsl) 10 | 11 | if (APPLE) 12 | add_executable(volumetric-clouds MACOSX_BUNDLE ${VOLUMETRIC_CLOUDS_SOURCES} ${SHADER_SOURCES} ${ASSET_SOURCES}) 13 | set(MACOSX_BUNDLE_BUNDLE_NAME "volumetric-clouds") 14 | set_source_files_properties(${SHADER_SOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/shader) 15 | set_source_files_properties(${ASSET_SOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 16 | else() 17 | add_executable(volumetric-clouds ${VOLUMETRIC_CLOUDS_SOURCES}) 18 | endif() 19 | 20 | target_link_libraries(volumetric-clouds dwSampleFramework) 21 | 22 | if (NOT APPLE) 23 | add_custom_command(TARGET volumetric-clouds POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/shader $/shader) 24 | add_custom_command(TARGET volumetric-clouds POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/data/mesh $/mesh) 25 | add_custom_command(TARGET volumetric-clouds POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/data/texture $/texture) 26 | endif() 27 | 28 | if(CLANG_FORMAT_EXE) 29 | add_custom_target(volumetric-clouds-clang-format COMMAND ${CLANG_FORMAT_EXE} -i -style=file ${VOLUMETRIC_CLOUDS_SOURCES} ${SHADER_SOURCES}) 30 | endif() 31 | 32 | set_property(TARGET volumetric-clouds PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/$(Configuration)") -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define CAMERA_FAR_PLANE 1000.0f 16 | 17 | struct GlobalUniforms 18 | { 19 | DW_ALIGNED(16) 20 | glm::mat4 view_proj; 21 | DW_ALIGNED(16) 22 | glm::mat4 inv_view_proj; 23 | DW_ALIGNED(16) 24 | glm::vec4 cam_pos; 25 | }; 26 | 27 | class VolumetricClouds : public dw::Application 28 | { 29 | protected: 30 | // ----------------------------------------------------------------------------------------------------------------------------------- 31 | 32 | bool init(int argc, const char* argv[]) override 33 | { 34 | m_sun_angle = glm::radians(-58.0f); 35 | 36 | // Create camera. 37 | create_camera(); 38 | 39 | // Create GPU resources. 40 | if (!create_shaders()) 41 | return false; 42 | 43 | if (!create_textures()) 44 | return false; 45 | 46 | if (!create_uniform_buffer()) 47 | return false; 48 | 49 | // Load scene. 50 | if (!load_scene()) 51 | return false; 52 | 53 | // Generate noise textures. 54 | generate_shape_noise_texture(); 55 | generate_detail_noise_texture(); 56 | 57 | return true; 58 | } 59 | 60 | // ----------------------------------------------------------------------------------------------------------------------------------- 61 | 62 | void update(double delta) override 63 | { 64 | if (m_debug_gui) 65 | debug_gui(); 66 | 67 | // Update camera. 68 | update_camera(); 69 | 70 | update_uniforms(); 71 | 72 | render_scene(); 73 | render_clouds(); 74 | tonemap(); 75 | } 76 | 77 | // ----------------------------------------------------------------------------------------------------------------------------------- 78 | 79 | void debug_gui() 80 | { 81 | ImGui::SliderAngle("Sun Angle", &m_sun_angle, 0.0f, -180.0f); 82 | 83 | ImGui::InputFloat("Cloud Min Height", &m_cloud_min_height); 84 | ImGui::InputFloat("Cloud Max Height", &m_cloud_max_height); 85 | ImGui::SliderFloat("Shape Noise Scale", &m_shape_noise_scale, 0.1f, 1.0f); 86 | ImGui::SliderFloat("Detail Noise Scale", &m_detail_noise_scale, 0.0f, 100.0f); 87 | ImGui::SliderFloat("Detail Noise Modifier", &m_detail_noise_modifier, 0.0f, 1.0f); 88 | ImGui::SliderFloat("Turbulence Noise Scale", &m_turbulence_noise_scale, 0.0f, 100.0f); 89 | ImGui::SliderFloat("Turbulence Amount", &m_turbulence_amount, 0.0f, 100.0f); 90 | ImGui::SliderFloat("Cloud Coverage", &m_cloud_coverage, 0.0f, 1.0f); 91 | ImGui::SliderFloat("Precipitation", &m_precipitation, 1.0f, 2.5f); 92 | ImGui::SliderFloat("Ambient Factor", &m_ambient_light_factor, 0.0f, 1.0f); 93 | ImGui::SliderFloat("Sun Light Factor", &m_sun_light_factor, 0.0f, 10.0f); 94 | 95 | ImGui::SliderAngle("Wind Angle", &m_wind_angle, 0.0f, -180.0f); 96 | ImGui::SliderFloat("Wind Speed", &m_wind_speed, 0.0f, 200.0f); 97 | ImGui::InputFloat("Wind Shear Offset", &m_wind_shear_offset); 98 | ImGui::ColorPicker3("Sun Color", &m_sun_color.x); 99 | 100 | ImGui::InputFloat("Planet Radius", &m_planet_radius); 101 | ImGui::SliderInt("Max Num Steps", &m_max_num_steps, 16, 256); 102 | 103 | ImGui::SliderFloat("Exposure", &m_exposure, 0.0f, 10.0f); 104 | 105 | m_planet_center = glm::vec3(0.0f, -m_planet_radius, 0.0f); 106 | 107 | m_light_direction = glm::normalize(glm::vec3(0.0f, sin(m_sun_angle), cos(m_sun_angle))); 108 | m_wind_direction = glm::normalize(glm::vec3(cos(m_wind_angle), sin(m_wind_angle), 0.0f)); 109 | } 110 | 111 | // ----------------------------------------------------------------------------------------------------------------------------------- 112 | 113 | void window_resized(int width, int height) override 114 | { 115 | // Override window resized method to update camera projection. 116 | m_main_camera->update_projection(60.0f, 1.0f, CAMERA_FAR_PLANE, float(m_width) / float(m_height)); 117 | } 118 | 119 | // ----------------------------------------------------------------------------------------------------------------------------------- 120 | 121 | void key_pressed(int code) override 122 | { 123 | // Handle forward movement. 124 | if (code == GLFW_KEY_W) 125 | m_heading_speed = m_camera_speed; 126 | else if (code == GLFW_KEY_S) 127 | m_heading_speed = -m_camera_speed; 128 | 129 | // Handle sideways movement. 130 | if (code == GLFW_KEY_A) 131 | m_sideways_speed = -m_camera_speed; 132 | else if (code == GLFW_KEY_D) 133 | m_sideways_speed = m_camera_speed; 134 | 135 | if (code == GLFW_KEY_SPACE) 136 | m_mouse_look = true; 137 | 138 | if (code == GLFW_KEY_G) 139 | m_debug_gui = !m_debug_gui; 140 | } 141 | 142 | // ----------------------------------------------------------------------------------------------------------------------------------- 143 | 144 | void key_released(int code) override 145 | { 146 | // Handle forward movement. 147 | if (code == GLFW_KEY_W || code == GLFW_KEY_S) 148 | m_heading_speed = 0.0f; 149 | 150 | // Handle sideways movement. 151 | if (code == GLFW_KEY_A || code == GLFW_KEY_D) 152 | m_sideways_speed = 0.0f; 153 | 154 | if (code == GLFW_KEY_SPACE) 155 | m_mouse_look = false; 156 | } 157 | 158 | // ----------------------------------------------------------------------------------------------------------------------------------- 159 | 160 | void mouse_pressed(int code) override 161 | { 162 | // Enable mouse look. 163 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 164 | m_mouse_look = true; 165 | } 166 | 167 | // ----------------------------------------------------------------------------------------------------------------------------------- 168 | 169 | void mouse_released(int code) override 170 | { 171 | // Disable mouse look. 172 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 173 | m_mouse_look = false; 174 | } 175 | 176 | // ----------------------------------------------------------------------------------------------------------------------------------- 177 | 178 | protected: 179 | // ----------------------------------------------------------------------------------------------------------------------------------- 180 | 181 | dw::AppSettings intial_app_settings() override 182 | { 183 | dw::AppSettings settings; 184 | 185 | settings.maximized = false; 186 | settings.major_ver = 4; 187 | settings.width = 1920; 188 | settings.height = 1080; 189 | settings.title = "Volumetric Clouds"; 190 | settings.enable_debug_callback = false; 191 | 192 | return settings; 193 | } 194 | 195 | // ----------------------------------------------------------------------------------------------------------------------------------- 196 | 197 | private: 198 | // ----------------------------------------------------------------------------------------------------------------------------------- 199 | 200 | bool create_shaders() 201 | { 202 | // Create general shaders 203 | m_mesh_vs = dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/mesh_vs.glsl"); 204 | m_mesh_fs = dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/mesh_fs.glsl"); 205 | 206 | if (!m_mesh_vs || !m_mesh_fs) 207 | { 208 | DW_LOG_FATAL("Failed to create Shaders"); 209 | return false; 210 | } 211 | 212 | // Create general shader program 213 | m_mesh_program = dw::gl::Program::create({ m_mesh_vs, m_mesh_fs }); 214 | 215 | if (!m_mesh_program) 216 | { 217 | DW_LOG_FATAL("Failed to create Shader Program"); 218 | return false; 219 | } 220 | 221 | // Create clouds shaders 222 | m_triangle_vs = dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/triangle_vs.glsl"); 223 | m_clouds_fs = dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/clouds_fs.glsl"); 224 | 225 | if (!m_triangle_vs || !m_clouds_fs) 226 | { 227 | DW_LOG_FATAL("Failed to create Shaders"); 228 | return false; 229 | } 230 | 231 | // Create general shader program 232 | m_clouds_program = dw::gl::Program::create({ m_triangle_vs, m_clouds_fs }); 233 | 234 | if (!m_clouds_program) 235 | { 236 | DW_LOG_FATAL("Failed to create Shader Program"); 237 | return false; 238 | } 239 | 240 | m_tonemap_fs = dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/tonemap_fs.glsl"); 241 | 242 | if (!m_tonemap_fs) 243 | { 244 | DW_LOG_FATAL("Failed to create Shaders"); 245 | return false; 246 | } 247 | 248 | // Create general shader program 249 | m_tonemap_program = dw::gl::Program::create({ m_triangle_vs, m_tonemap_fs }); 250 | 251 | if (!m_tonemap_program) 252 | { 253 | DW_LOG_FATAL("Failed to create Shader Program"); 254 | return false; 255 | } 256 | 257 | m_shape_noise_cs = dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/shape_noise_cs.glsl"); 258 | 259 | // Create general shader program 260 | m_shape_noise_program = dw::gl::Program::create({ m_shape_noise_cs }); 261 | 262 | if (!m_shape_noise_program) 263 | { 264 | DW_LOG_FATAL("Failed to create Shader Program"); 265 | return false; 266 | } 267 | 268 | m_detail_noise_cs = dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/detail_noise_cs.glsl"); 269 | 270 | // Create general shader program 271 | m_detail_noise_program = dw::gl::Program::create({ m_detail_noise_cs }); 272 | 273 | if (!m_detail_noise_program) 274 | { 275 | DW_LOG_FATAL("Failed to create Shader Program"); 276 | return false; 277 | } 278 | 279 | return true; 280 | } 281 | 282 | // ----------------------------------------------------------------------------------------------------------------------------------- 283 | 284 | bool create_textures() 285 | { 286 | m_hdr_output_texture = dw::gl::Texture2D::create(m_width, m_height, 1, 1, 1, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); 287 | m_hdr_output_texture->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 288 | 289 | m_depth_output_texture = dw::gl::Texture2D::create(m_width, m_height, 1, 1, 1, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); 290 | m_depth_output_texture->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 291 | 292 | m_hdr_output_framebuffer = dw::gl::Framebuffer::create({ m_hdr_output_texture }, m_depth_output_texture); 293 | 294 | m_shape_noise_texture = dw::gl::Texture3D::create(128, 128, 128, -1, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); 295 | m_shape_noise_texture->set_wrapping(GL_REPEAT, GL_REPEAT, GL_REPEAT); 296 | m_shape_noise_texture->set_min_filter(GL_LINEAR_MIPMAP_LINEAR); 297 | 298 | m_detail_noise_texture = dw::gl::Texture3D::create(32, 32, 32, -1, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); 299 | m_detail_noise_texture->set_wrapping(GL_REPEAT, GL_REPEAT, GL_REPEAT); 300 | m_detail_noise_texture->set_min_filter(GL_LINEAR_MIPMAP_LINEAR); 301 | 302 | m_blue_noise_texture = dw::gl::Texture2D::create_from_file("texture/LDR_LLL1_0.png"); 303 | m_blue_noise_texture->set_wrapping(GL_REPEAT, GL_REPEAT, GL_REPEAT); 304 | 305 | m_curl_noise_texture = dw::gl::Texture2D::create_from_file("texture/curlNoise.png"); 306 | m_curl_noise_texture->set_wrapping(GL_REPEAT, GL_REPEAT, GL_REPEAT); 307 | 308 | return true; 309 | } 310 | 311 | // ----------------------------------------------------------------------------------------------------------------------------------- 312 | 313 | bool create_uniform_buffer() 314 | { 315 | // Create uniform buffer for global data 316 | m_global_ubo = dw::gl::Buffer::create(GL_UNIFORM_BUFFER, GL_MAP_WRITE_BIT, sizeof(GlobalUniforms)); 317 | 318 | return true; 319 | } 320 | 321 | // ----------------------------------------------------------------------------------------------------------------------------------- 322 | 323 | bool load_scene() 324 | { 325 | m_placeholder_texture = dw::gl::Texture2D::create_from_file("texture/grid.png", true, true); 326 | 327 | m_plane = dw::Mesh::load("mesh/plane.obj"); 328 | 329 | if (!m_plane) 330 | { 331 | DW_LOG_FATAL("Failed to load mesh: plane"); 332 | return false; 333 | } 334 | 335 | return true; 336 | } 337 | 338 | // ----------------------------------------------------------------------------------------------------------------------------------- 339 | 340 | void create_camera() 341 | { 342 | m_main_camera = std::make_unique(60.0f, 1.0f, CAMERA_FAR_PLANE, float(m_width) / float(m_height), glm::vec3(0.0f, 5.0f, 0.0f), glm::vec3(-1.0f, 0.0, 0.0f)); 343 | m_main_camera->update(); 344 | } 345 | 346 | // ----------------------------------------------------------------------------------------------------------------------------------- 347 | 348 | void generate_shape_noise_texture() 349 | { 350 | m_shape_noise_program->use(); 351 | m_shape_noise_program->set_uniform("u_Size", (int)m_shape_noise_texture->width()); 352 | 353 | m_shape_noise_texture->bind_image(0, 0, 0, GL_READ_WRITE, m_shape_noise_texture->internal_format()); 354 | 355 | const uint32_t TEXTURE_SIZE = m_shape_noise_texture->width(); 356 | const uint32_t NUM_THREADS = 8; 357 | 358 | glDispatchCompute(TEXTURE_SIZE / NUM_THREADS, TEXTURE_SIZE / NUM_THREADS, TEXTURE_SIZE / NUM_THREADS); 359 | 360 | glFinish(); 361 | 362 | m_shape_noise_texture->generate_mipmaps(); 363 | } 364 | 365 | // ----------------------------------------------------------------------------------------------------------------------------------- 366 | 367 | void generate_detail_noise_texture() 368 | { 369 | m_detail_noise_program->use(); 370 | m_detail_noise_program->set_uniform("u_Size", (int)m_detail_noise_texture->width()); 371 | 372 | m_detail_noise_texture->bind_image(0, 0, 0, GL_READ_WRITE, m_detail_noise_texture->internal_format()); 373 | 374 | const uint32_t TEXTURE_SIZE = m_detail_noise_texture->width(); 375 | const uint32_t NUM_THREADS = 8; 376 | 377 | glDispatchCompute(TEXTURE_SIZE / NUM_THREADS, TEXTURE_SIZE / NUM_THREADS, TEXTURE_SIZE / NUM_THREADS); 378 | 379 | glFinish(); 380 | 381 | m_detail_noise_texture->generate_mipmaps(); 382 | } 383 | 384 | // ----------------------------------------------------------------------------------------------------------------------------------- 385 | 386 | void render_mesh(dw::Mesh::Ptr mesh, glm::mat4 model) 387 | { 388 | if (m_mesh_program->set_uniform("s_Diffuse", 0)) 389 | m_placeholder_texture->bind(0); 390 | 391 | m_mesh_program->set_uniform("u_LightDirection", m_light_direction); 392 | m_mesh_program->set_uniform("u_Model", model); 393 | 394 | // Bind vertex array. 395 | mesh->mesh_vertex_array()->bind(); 396 | 397 | const auto& submeshes = mesh->sub_meshes(); 398 | 399 | for (uint32_t i = 0; i < submeshes.size(); i++) 400 | { 401 | const dw::SubMesh& submesh = submeshes[i]; 402 | 403 | // Issue draw call. 404 | glDrawElementsBaseVertex(GL_TRIANGLES, submesh.index_count, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * submesh.base_index), submesh.base_vertex); 405 | } 406 | } 407 | 408 | // ----------------------------------------------------------------------------------------------------------------------------------- 409 | 410 | void render_scene() 411 | { 412 | glEnable(GL_DEPTH_TEST); 413 | glDepthFunc(GL_LEQUAL); 414 | glDisable(GL_BLEND); 415 | glEnable(GL_CULL_FACE); 416 | glCullFace(GL_BACK); 417 | 418 | m_hdr_output_framebuffer->bind(); 419 | glViewport(0, 0, m_width, m_height); 420 | 421 | glClearColor(0.3f, 0.3f, 0.3f, 1.0f); 422 | glClearDepth(1.0); 423 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 424 | 425 | // Bind shader program. 426 | m_mesh_program->use(); 427 | 428 | // Bind uniform buffers. 429 | m_global_ubo->bind_base(0); 430 | 431 | // Draw scene. 432 | render_mesh(m_plane, glm::mat4(1.0f)); 433 | } 434 | 435 | // ----------------------------------------------------------------------------------------------------------------------------------- 436 | 437 | void render_clouds() 438 | { 439 | m_clouds_program->use(); 440 | 441 | if (m_clouds_program->set_uniform("s_ShapeNoise", 0)) 442 | m_shape_noise_texture->bind(0); 443 | 444 | if (m_clouds_program->set_uniform("s_DetailNoise", 1)) 445 | m_detail_noise_texture->bind(1); 446 | 447 | if (m_clouds_program->set_uniform("s_BlueNoise", 2)) 448 | m_blue_noise_texture->bind(2); 449 | 450 | if (m_clouds_program->set_uniform("s_CurlNoise", 3)) 451 | m_curl_noise_texture->bind(3); 452 | 453 | float noise_scale = 0.00001f + m_shape_noise_scale * 0.0004f; 454 | 455 | m_clouds_program->set_uniform("u_PlanetCenter", m_planet_center); 456 | m_clouds_program->set_uniform("u_PlanetRadius", m_planet_radius); 457 | m_clouds_program->set_uniform("u_CloudMinHeight", m_cloud_min_height); 458 | m_clouds_program->set_uniform("u_CloudMaxHeight", m_cloud_max_height); 459 | m_clouds_program->set_uniform("u_ShapeNoiseScale", noise_scale); 460 | m_clouds_program->set_uniform("u_DetailNoiseScale", noise_scale * m_detail_noise_scale); 461 | m_clouds_program->set_uniform("u_DetailNoiseModifier", m_detail_noise_modifier); 462 | m_clouds_program->set_uniform("u_TurbulenceNoiseScale", noise_scale * m_turbulence_noise_scale); 463 | m_clouds_program->set_uniform("u_TurbulenceAmount", m_turbulence_amount); 464 | m_clouds_program->set_uniform("u_CloudCoverage", m_cloud_coverage); 465 | m_clouds_program->set_uniform("u_WindDirection", m_wind_direction); 466 | m_clouds_program->set_uniform("u_WindSpeed", m_wind_speed); 467 | m_clouds_program->set_uniform("u_WindShearOffset", m_wind_shear_offset); 468 | m_clouds_program->set_uniform("u_Time", static_cast(glfwGetTime())); 469 | m_clouds_program->set_uniform("u_MaxNumSteps", (float)m_max_num_steps); 470 | m_clouds_program->set_uniform("u_LightStepLength", m_light_step_length); 471 | m_clouds_program->set_uniform("u_LightConeRadius", m_light_cone_radius); 472 | m_clouds_program->set_uniform("u_SunDir", -m_light_direction); 473 | m_clouds_program->set_uniform("u_SunColor", m_sun_color); 474 | m_clouds_program->set_uniform("u_CloudBaseColor", m_cloud_base_color); 475 | m_clouds_program->set_uniform("u_CloudTopColor", m_cloud_top_color); 476 | m_clouds_program->set_uniform("u_Precipitation", m_precipitation * 0.01f); 477 | m_clouds_program->set_uniform("u_AmbientLightFactor", m_ambient_light_factor); 478 | m_clouds_program->set_uniform("u_SunLightFactor", m_sun_light_factor); 479 | m_clouds_program->set_uniform("u_HenyeyGreensteinGForward", m_henyey_greenstein_g_forward); 480 | m_clouds_program->set_uniform("u_HenyeyGreensteinGBackward", m_henyey_greenstein_g_backward); 481 | 482 | glDrawArrays(GL_TRIANGLES, 0, 3); 483 | } 484 | 485 | // ----------------------------------------------------------------------------------------------------------------------------------- 486 | 487 | void tonemap() 488 | { 489 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 490 | glViewport(0, 0, m_width, m_height); 491 | 492 | glClearColor(0.3f, 0.3f, 0.3f, 1.0f); 493 | glClearDepth(1.0); 494 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 495 | 496 | m_tonemap_program->use(); 497 | 498 | if (m_tonemap_program->set_uniform("s_HDR", 0)) 499 | m_hdr_output_texture->bind(0); 500 | 501 | m_tonemap_program->set_uniform("u_Exposure", m_exposure); 502 | 503 | glDrawArrays(GL_TRIANGLES, 0, 3); 504 | } 505 | 506 | // ----------------------------------------------------------------------------------------------------------------------------------- 507 | 508 | void update_uniforms() 509 | { 510 | // Global 511 | { 512 | void* ptr = m_global_ubo->map(GL_WRITE_ONLY); 513 | memcpy(ptr, &m_global_uniforms, sizeof(GlobalUniforms)); 514 | m_global_ubo->unmap(); 515 | } 516 | } 517 | 518 | // ----------------------------------------------------------------------------------------------------------------------------------- 519 | 520 | void update_transforms(dw::Camera* camera) 521 | { 522 | // Update camera matrices. 523 | m_global_uniforms.view_proj = camera->m_projection * camera->m_view; 524 | m_global_uniforms.inv_view_proj = glm::inverse(camera->m_projection * camera->m_view); 525 | m_global_uniforms.cam_pos = glm::vec4(camera->m_position, 0.0f); 526 | } 527 | 528 | // ----------------------------------------------------------------------------------------------------------------------------------- 529 | 530 | void update_camera() 531 | { 532 | dw::Camera* current = m_main_camera.get(); 533 | 534 | float forward_delta = m_heading_speed * m_delta; 535 | float right_delta = m_sideways_speed * m_delta; 536 | 537 | current->set_translation_delta(current->m_forward, forward_delta); 538 | current->set_translation_delta(current->m_right, right_delta); 539 | 540 | m_camera_x = m_mouse_delta_x * m_camera_sensitivity; 541 | m_camera_y = m_mouse_delta_y * m_camera_sensitivity; 542 | 543 | if (m_mouse_look) 544 | { 545 | // Activate Mouse Look 546 | current->set_rotatation_delta(glm::vec3((float)(m_camera_y), 547 | (float)(m_camera_x), 548 | (float)(0.0f))); 549 | } 550 | else 551 | { 552 | current->set_rotatation_delta(glm::vec3((float)(0), 553 | (float)(0), 554 | (float)(0))); 555 | } 556 | 557 | current->update(); 558 | update_transforms(current); 559 | } 560 | 561 | // ----------------------------------------------------------------------------------------------------------------------------------- 562 | 563 | private: 564 | // General GPU resources. 565 | dw::gl::Shader::Ptr m_mesh_vs; 566 | dw::gl::Shader::Ptr m_mesh_fs; 567 | dw::gl::Shader::Ptr m_triangle_vs; 568 | dw::gl::Shader::Ptr m_clouds_fs; 569 | dw::gl::Shader::Ptr m_tonemap_fs; 570 | dw::gl::Shader::Ptr m_shape_noise_cs; 571 | dw::gl::Shader::Ptr m_detail_noise_cs; 572 | dw::gl::Program::Ptr m_mesh_program; 573 | dw::gl::Program::Ptr m_clouds_program; 574 | dw::gl::Program::Ptr m_tonemap_program; 575 | dw::gl::Program::Ptr m_shape_noise_program; 576 | dw::gl::Program::Ptr m_detail_noise_program; 577 | dw::gl::Buffer::Ptr m_global_ubo; 578 | dw::gl::Texture2D::Ptr m_hdr_output_texture; 579 | dw::gl::Texture2D::Ptr m_depth_output_texture; 580 | dw::gl::Texture2D::Ptr m_placeholder_texture; 581 | dw::gl::Texture2D::Ptr m_blue_noise_texture; 582 | dw::gl::Texture2D::Ptr m_curl_noise_texture; 583 | dw::gl::Texture3D::Ptr m_shape_noise_texture; 584 | dw::gl::Texture3D::Ptr m_detail_noise_texture; 585 | dw::gl::Framebuffer::Ptr m_hdr_output_framebuffer; 586 | 587 | int32_t m_max_num_steps = 128; 588 | float m_cloud_min_height = 1500.0f; 589 | float m_cloud_max_height = 4000.0f; 590 | float m_shape_noise_scale = 0.3f; 591 | float m_detail_noise_scale = 5.5f; 592 | float m_detail_noise_modifier = 0.5f; 593 | float m_turbulence_noise_scale = 7.44f; 594 | float m_turbulence_amount = 1.0f; 595 | float m_cloud_coverage = 0.7f; 596 | float m_wind_angle = 0.0f; 597 | float m_wind_speed = 50.0f; 598 | float m_wind_shear_offset = 500.0f; 599 | glm::vec3 m_wind_direction = glm::vec3(0.0f); 600 | float m_planet_radius = 35000.0f; 601 | glm::vec3 m_planet_center; 602 | float m_light_step_length = 64.0f; 603 | float m_light_cone_radius = 0.4f; 604 | glm::vec3 m_sun_color = glm::vec3(1.0f, 1.0f, 1.0f); 605 | glm::vec3 m_cloud_base_color = glm::vec3(0.78f, 0.86f, 1.0f); 606 | glm::vec3 m_cloud_top_color = glm::vec3(1.0f); 607 | float m_precipitation = 1.0f; 608 | float m_ambient_light_factor = 0.12f; 609 | float m_sun_light_factor = 1.0f; 610 | float m_henyey_greenstein_g_forward = 0.4f; 611 | float m_henyey_greenstein_g_backward = 0.179f; 612 | float m_exposure = 0.6f; 613 | 614 | dw::Mesh::Ptr m_plane; 615 | std::unique_ptr m_main_camera; 616 | 617 | float m_sun_angle = 0.0f; 618 | glm::vec3 m_light_direction; 619 | GlobalUniforms m_global_uniforms; 620 | 621 | // Camera controls. 622 | bool m_mouse_look = false; 623 | float m_heading_speed = 0.0f; 624 | float m_sideways_speed = 0.0f; 625 | float m_camera_sensitivity = 0.05f; 626 | float m_camera_speed = 0.05f; 627 | bool m_debug_gui = true; 628 | 629 | // Camera orientation. 630 | float m_camera_x; 631 | float m_camera_y; 632 | }; 633 | 634 | DW_DECLARE_MAIN(VolumetricClouds) -------------------------------------------------------------------------------- /src/shader/atmosphere.glsl: -------------------------------------------------------------------------------- 1 | #define PI 3.14159265359 2 | 3 | float saturatedDot( in vec3 a, in vec3 b ) 4 | { 5 | return max( dot( a, b ), 0.0 ); 6 | } 7 | 8 | vec3 YxyToXYZ( in vec3 Yxy ) 9 | { 10 | float Y = Yxy.r; 11 | float x = Yxy.g; 12 | float y = Yxy.b; 13 | 14 | float X = x * ( Y / y ); 15 | float Z = ( 1.0 - x - y ) * ( Y / y ); 16 | 17 | return vec3(X,Y,Z); 18 | } 19 | 20 | vec3 XYZToRGB( in vec3 XYZ ) 21 | { 22 | // CIE/E 23 | mat3 M = mat3 24 | ( 25 | 2.3706743, -0.9000405, -0.4706338, 26 | -0.5138850, 1.4253036, 0.0885814, 27 | 0.0052982, -0.0146949, 1.0093968 28 | ); 29 | 30 | return XYZ * M; 31 | } 32 | 33 | 34 | vec3 YxyToRGB( in vec3 Yxy ) 35 | { 36 | vec3 XYZ = YxyToXYZ( Yxy ); 37 | vec3 RGB = XYZToRGB( XYZ ); 38 | return RGB; 39 | } 40 | 41 | void calculatePerezDistribution( in float t, out vec3 A, out vec3 B, out vec3 C, out vec3 D, out vec3 E ) 42 | { 43 | A = vec3( 0.1787 * t - 1.4630, -0.0193 * t - 0.2592, -0.0167 * t - 0.2608 ); 44 | B = vec3( -0.3554 * t + 0.4275, -0.0665 * t + 0.0008, -0.0950 * t + 0.0092 ); 45 | C = vec3( -0.0227 * t + 5.3251, -0.0004 * t + 0.2125, -0.0079 * t + 0.2102 ); 46 | D = vec3( 0.1206 * t - 2.5771, -0.0641 * t - 0.8989, -0.0441 * t - 1.6537 ); 47 | E = vec3( -0.0670 * t + 0.3703, -0.0033 * t + 0.0452, -0.0109 * t + 0.0529 ); 48 | } 49 | 50 | vec3 calculateZenithLuminanceYxy( in float t, in float thetaS ) 51 | { 52 | float chi = ( 4.0 / 9.0 - t / 120.0 ) * ( PI - 2.0 * thetaS ); 53 | float Yz = ( 4.0453 * t - 4.9710 ) * tan( chi ) - 0.2155 * t + 2.4192; 54 | 55 | float theta2 = thetaS * thetaS; 56 | float theta3 = theta2 * thetaS; 57 | float T = t; 58 | float T2 = t * t; 59 | 60 | float xz = 61 | ( 0.00165 * theta3 - 0.00375 * theta2 + 0.00209 * thetaS + 0.0) * T2 + 62 | (-0.02903 * theta3 + 0.06377 * theta2 - 0.03202 * thetaS + 0.00394) * T + 63 | ( 0.11693 * theta3 - 0.21196 * theta2 + 0.06052 * thetaS + 0.25886); 64 | 65 | float yz = 66 | ( 0.00275 * theta3 - 0.00610 * theta2 + 0.00317 * thetaS + 0.0) * T2 + 67 | (-0.04214 * theta3 + 0.08970 * theta2 - 0.04153 * thetaS + 0.00516) * T + 68 | ( 0.15346 * theta3 - 0.26756 * theta2 + 0.06670 * thetaS + 0.26688); 69 | 70 | return vec3( Yz, xz, yz ); 71 | } 72 | 73 | vec3 calculatePerezLuminanceYxy( in float theta, in float gamma, in vec3 A, in vec3 B, in vec3 C, in vec3 D, in vec3 E ) 74 | { 75 | return ( 1.0 + A * exp( B / cos( theta ) ) ) * ( 1.0 + C * exp( D * gamma ) + E * cos( gamma ) * cos( gamma ) ); 76 | } 77 | 78 | vec3 calculate_sky_luminance_rgb( in vec3 s, in vec3 e, in float t ) 79 | { 80 | vec3 A, B, C, D, E; 81 | calculatePerezDistribution( t, A, B, C, D, E ); 82 | 83 | float thetaS = acos( saturatedDot( s, vec3(0,1,0) ) ); 84 | float thetaE = acos( saturatedDot( e, vec3(0,1,0) ) ); 85 | float gammaE = acos( saturatedDot( s, e ) ); 86 | 87 | vec3 Yz = calculateZenithLuminanceYxy( t, thetaS ); 88 | 89 | vec3 fThetaGamma = calculatePerezLuminanceYxy( thetaE, gammaE, A, B, C, D, E ); 90 | vec3 fZeroThetaS = calculatePerezLuminanceYxy( 0.0, thetaS, A, B, C, D, E ); 91 | 92 | vec3 Yp = Yz * ( fThetaGamma / fZeroThetaS ); 93 | 94 | return YxyToRGB( Yp ); 95 | } 96 | -------------------------------------------------------------------------------- /src/shader/clouds_fs.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define NUM_CONE_SAMPLES 6 4 | 5 | // ------------------------------------------------------------------ 6 | // OUTPUT VARIABLES ------------------------------------------------ 7 | // ------------------------------------------------------------------ 8 | 9 | out vec3 FS_OUT_Color; 10 | 11 | // ------------------------------------------------------------------ 12 | // INPUT VARIABLES ------------------------------------------------- 13 | // ------------------------------------------------------------------ 14 | 15 | in vec2 FS_IN_TexCoord; 16 | 17 | // ------------------------------------------------------------------ 18 | // STRUCTURES ------------------------------------------------------- 19 | // ------------------------------------------------------------------ 20 | 21 | struct Ray 22 | { 23 | vec3 origin; 24 | vec3 direction; 25 | }; 26 | 27 | // ------------------------------------------------------------------ 28 | // UNIFORMS --------------------------------------------------------- 29 | // ------------------------------------------------------------------ 30 | 31 | layout(std140, binding = 0) uniform GlobalUniforms 32 | { 33 | mat4 view_proj; 34 | mat4 inv_view_proj; 35 | vec4 cam_pos; 36 | }; 37 | 38 | uniform sampler3D s_ShapeNoise; 39 | uniform sampler3D s_DetailNoise; 40 | uniform sampler2D s_BlueNoise; 41 | uniform sampler2D s_CurlNoise; 42 | 43 | uniform vec3 u_PlanetCenter; 44 | uniform float u_PlanetRadius; 45 | uniform float u_CloudMinHeight; 46 | uniform float u_CloudMaxHeight; 47 | uniform float u_ShapeNoiseScale; 48 | uniform float u_DetailNoiseScale; 49 | uniform float u_DetailNoiseModifier; 50 | uniform float u_TurbulenceNoiseScale; 51 | uniform float u_TurbulenceAmount; 52 | uniform float u_CloudCoverage; 53 | uniform vec3 u_WindDirection; 54 | uniform float u_WindSpeed; 55 | uniform float u_WindShearOffset; 56 | uniform float u_Time; 57 | uniform float u_MaxNumSteps; 58 | uniform float u_LightStepLength; 59 | uniform float u_LightConeRadius; 60 | uniform vec3 u_SunDir; 61 | uniform vec3 u_SunColor; 62 | uniform vec3 u_CloudBaseColor; 63 | uniform vec3 u_CloudTopColor; 64 | uniform float u_Precipitation; 65 | uniform float u_AmbientLightFactor; 66 | uniform float u_SunLightFactor; 67 | uniform float u_HenyeyGreensteinGForward; 68 | uniform float u_HenyeyGreensteinGBackward; 69 | 70 | // ------------------------------------------------------------------ 71 | // FUNCTIONS -------------------------------------------------------- 72 | // ------------------------------------------------------------------ 73 | 74 | Ray generate_ray() 75 | { 76 | vec2 tex_coord_neg_to_pos = FS_IN_TexCoord * 2.0f - 1.0f; 77 | vec4 target = inv_view_proj * vec4(tex_coord_neg_to_pos, 0.0f, 1.0f); 78 | target /= target.w; 79 | 80 | Ray ray; 81 | 82 | ray.origin = cam_pos.xyz; 83 | ray.direction = normalize(target.xyz - ray.origin); 84 | 85 | return ray; 86 | } 87 | 88 | // ------------------------------------------------------------------ 89 | 90 | vec3 ray_sphere_intersection(in Ray ray, in vec3 sphere_center, in float sphere_radius) 91 | { 92 | vec3 l = ray.origin - sphere_center; 93 | float a = 1.0; 94 | float b = 2.0 * dot(ray.direction, l); 95 | float c = dot(l, l) - pow(sphere_radius, 2); 96 | float D = pow(b, 2) - 4.0 * a * c; 97 | 98 | if (D < 0.0) 99 | return ray.origin; 100 | else if (abs(D) - 0.00005 <= 0.0) 101 | return ray.origin + ray.direction * (-0.5 * b / a); 102 | else 103 | { 104 | float q = 0.0; 105 | if (b > 0.0) 106 | q = -0.5 * (b + sqrt(D)); 107 | else 108 | q = -0.5 * (b - sqrt(D)); 109 | 110 | float h1 = q / a; 111 | float h2 = c / q; 112 | vec2 t = vec2(min(h1, h2), max(h1, h2)); 113 | 114 | if (t.x < 0.0) 115 | { 116 | t.x = t.y; 117 | 118 | if (t.x < 0.0) 119 | return ray.origin; 120 | } 121 | 122 | return ray.origin + t.x * ray.direction; 123 | } 124 | } 125 | 126 | // ------------------------------------------------------------------ 127 | 128 | float blue_noise() 129 | { 130 | ivec2 size = textureSize(s_BlueNoise, 0); 131 | 132 | vec2 interleaved_pos = (mod(floor(gl_FragCoord.xy), float(size.x))); 133 | vec2 tex_coord = interleaved_pos / float(size.x) + vec2(0.5f / float(size.x), 0.5f / float(size.x)); 134 | 135 | return texture(s_BlueNoise, tex_coord).r * 2.0f - 1.0f; 136 | } 137 | 138 | // ------------------------------------------------------------------ 139 | 140 | float remap(float original_value, float original_min, float original_max, float new_min, float new_max) 141 | { 142 | return new_min + (((original_value - original_min) / (original_max - original_min)) * (new_max - new_min)); 143 | } 144 | 145 | // ------------------------------------------------------------------ 146 | 147 | // returns height fraction [0, 1] for point in cloud 148 | float height_fraction_for_point(vec3 _position) 149 | { 150 | return clamp((distance(_position, u_PlanetCenter) - (u_PlanetRadius + u_CloudMinHeight)) / (u_CloudMaxHeight - u_CloudMinHeight), 0.0f, 1.0f); 151 | } 152 | 153 | // ------------------------------------------------------------------ 154 | 155 | float density_height_gradient_for_point(vec3 _position, float _height_fraction) 156 | { 157 | return 1.0f; 158 | } 159 | 160 | // ------------------------------------------------------------------ 161 | 162 | float sample_cloud_density(vec3 _position, float _height_fraction, float _lod, bool _use_detail) 163 | { 164 | // Shear cloud top along wind direction. 165 | vec3 position = _position + u_WindDirection * u_WindShearOffset * _height_fraction; 166 | 167 | // Animate clouds in wind direction and add a small upward bias to the wind direction. 168 | position += (u_WindDirection + vec3(0.0f, 0.1f, 0.0f)) * u_WindSpeed * u_Time; 169 | 170 | // Read the low-frequency Perlin-Worley and Worley noises. 171 | vec4 low_frequency_noises = textureLod(s_ShapeNoise, position * u_ShapeNoiseScale, _lod); 172 | 173 | // Build an FBM out of the low-frequency Worley noises to add detail to the low-frequeny Perlin-Worley noise. 174 | float low_freq_fbm = (low_frequency_noises.g * 0.625f) + (low_frequency_noises.b * 0.25f) + (low_frequency_noises.a * 0.125f); 175 | 176 | // Define the base cloud shape by dilating it with the low-frequency FBM made of Worley noise. 177 | float base_cloud = remap(low_frequency_noises.r, (1.0f - low_freq_fbm), 1.0f, 0.0f, 1.0f); 178 | 179 | // Get the density-height gradient using the density height function. 180 | float density_height_gradient = density_height_gradient_for_point(position, _height_fraction); 181 | 182 | // Apply the height function to the base cloud shape. 183 | base_cloud *= density_height_gradient; 184 | 185 | // Fetch cloud coverage value. 186 | float cloud_coverage = u_CloudCoverage; 187 | 188 | // Remap to apply the cloud coverage attribute. 189 | float base_cloud_with_coverage = remap(base_cloud, cloud_coverage, 1.0f, 0.0f, 1.0f); 190 | 191 | // Multiply result by the cloud coverage attribute so that smaller clouds are lighter and more aesthetically pleasing. 192 | base_cloud_with_coverage *= cloud_coverage; 193 | 194 | // Exit out if base cloud density is zero. 195 | if (base_cloud_with_coverage <= 0.0f) 196 | return 0.0f; 197 | 198 | float final_cloud = base_cloud_with_coverage; 199 | 200 | if (_use_detail) 201 | { 202 | // Sample curl noise texture. 203 | vec2 curl_noise = textureLod(s_CurlNoise, position.xz * u_TurbulenceNoiseScale, 0.0f).rg; 204 | 205 | // Add some turbulence to bottom of clouds. 206 | position.xy += curl_noise * (1.0f - _height_fraction) * u_TurbulenceAmount; 207 | 208 | // Sample high-frequency noises. 209 | vec3 high_frequency_noises = textureLod(s_DetailNoise, position * u_DetailNoiseScale, _lod).rgb; 210 | 211 | // Build high-frequency Worley noise FBM. 212 | float high_freq_fbm = (high_frequency_noises.r * 0.625f) + (high_frequency_noises.g * 0.25f) + (high_frequency_noises.b * 0.125f); 213 | 214 | // Transition from wispy shapes to billowy shapes over height. 215 | float high_freq_noise_modifier = mix(1.0f - high_freq_fbm, high_freq_fbm, clamp(_height_fraction * 10.0f, 0.0f, 1.0f)); 216 | 217 | // Erode the base cloud shape with the distorted high-frequency Worley noise. 218 | final_cloud = remap(base_cloud_with_coverage, high_freq_noise_modifier * u_DetailNoiseModifier, 1.0f, 0.0f, 1.0f); 219 | } 220 | 221 | return clamp(final_cloud, 0.0f, 1.0f); 222 | } 223 | 224 | // ------------------------------------------------------------------ 225 | 226 | float sample_cloud_density_along_cone(vec3 _position, vec3 _light_dir) 227 | { 228 | const vec3 noise_kernel[6] = 229 | { 230 | { -0.6, -0.8, -0.2 }, 231 | { 1.0, -0.3, 0.0 }, 232 | { -0.7, 0.0, 0.7 }, 233 | { -0.2, 0.6, -0.8 }, 234 | { 0.4, 0.3, 0.9 }, 235 | { -0.2, 0.6, -0.8 } 236 | }; 237 | 238 | float density_along_cone = 0.0f; 239 | 240 | for (int i = 0; i < NUM_CONE_SAMPLES; i++) 241 | { 242 | // March ray forward along light direction. 243 | _position += _light_dir * u_LightStepLength; 244 | 245 | // Compute offset within the cone. 246 | vec3 random_offset = noise_kernel[i] * u_LightStepLength * u_LightConeRadius * (float(i + 1)); 247 | 248 | // Add offset to position. 249 | vec3 p = _position + random_offset; 250 | 251 | // Compute height fraction for the current position. 252 | float height_fraction = height_fraction_for_point(p); 253 | 254 | // Skipping detail noise based on accumulated density causes some banding artefacts 255 | // so only use detail noise for the first two samples. 256 | bool use_detail_noise = i < 2; 257 | 258 | // Sample the cloud density at this point within the cone. 259 | density_along_cone += sample_cloud_density(p, height_fraction, float(i) * 0.5f, use_detail_noise); 260 | } 261 | 262 | // Get one more sample further away to account for shadows from distant clouds. 263 | _position += 32.0f * u_LightStepLength * _light_dir; 264 | 265 | // Compute height fraction for the distant position. 266 | float height_fraction = height_fraction_for_point(_position); 267 | 268 | // Sample the cloud density for the distant position. 269 | density_along_cone += sample_cloud_density(_position, height_fraction, 2.0f, false) * 3.0f; 270 | 271 | return density_along_cone; 272 | } 273 | 274 | // ------------------------------------------------------------------ 275 | 276 | float beer_lambert_law(float _density) 277 | { 278 | return exp(-_density * u_Precipitation); 279 | } 280 | 281 | // ------------------------------------------------------------------ 282 | 283 | float beer_law(float density) 284 | { 285 | float d = -density * u_Precipitation; 286 | return max(exp(d), exp(d * 0.5f) * 0.7f); 287 | } 288 | 289 | // ------------------------------------------------------------------ 290 | 291 | float henyey_greenstein_phase(float cos_angle, float g) 292 | { 293 | float g2 = g * g; 294 | return ((1.0f - g2) / pow(1.0f + g2 - 2.0f * g * cos_angle, 1.5f)) / 4.0f * 3.1415f; 295 | } 296 | 297 | // ------------------------------------------------------------------ 298 | 299 | float powder_effect(float _density, float _cos_angle) 300 | { 301 | float powder = 1.0f - exp(-_density * 2.0f); 302 | return mix(1.0f, powder, clamp((-_cos_angle * 0.5f) + 0.5f, 0.0f, 1.0f)); 303 | } 304 | 305 | // ------------------------------------------------------------------ 306 | 307 | float calculate_light_energy(float _density, float _cos_angle, float _powder_density) 308 | { 309 | float beer_powder = 2.0f * beer_law(_density) * powder_effect(_powder_density, _cos_angle); 310 | float HG = max(henyey_greenstein_phase(_cos_angle, u_HenyeyGreensteinGForward), henyey_greenstein_phase(_cos_angle, u_HenyeyGreensteinGBackward)) * 0.07f + 0.8f; 311 | return beer_powder * HG; 312 | } 313 | 314 | // ------------------------------------------------------------------ 315 | 316 | vec4 ray_march(vec3 _ray_origin, vec3 _ray_direction, float _cos_angle, float _step_size, float _num_steps) 317 | { 318 | vec3 position = _ray_origin; 319 | float step_increment = 1.0f; 320 | float accum_transmittance = 1.0f; 321 | vec3 accum_scattering = vec3(0.0f); 322 | float alpha = 0.0f; 323 | 324 | vec3 sun_color = u_SunColor; 325 | 326 | for (float i = 0.0f; i < _num_steps; i+= step_increment) 327 | { 328 | float height_fraction = height_fraction_for_point(position); 329 | float density = sample_cloud_density(position, height_fraction, 0.0f, true); 330 | float step_transmittance = beer_lambert_law(density * _step_size); 331 | 332 | accum_transmittance *= step_transmittance; 333 | 334 | if (density > 0.0f) 335 | { 336 | alpha += (1.0f - step_transmittance) * (1.0f - alpha); 337 | 338 | float cone_density = sample_cloud_density_along_cone(position, u_SunDir); 339 | 340 | vec3 in_scattered_light = calculate_light_energy(cone_density * _step_size, _cos_angle, density * _step_size) * sun_color * u_SunLightFactor * alpha; 341 | vec3 ambient_light = mix(u_CloudBaseColor, u_CloudTopColor, height_fraction) * u_AmbientLightFactor; 342 | 343 | accum_scattering += (ambient_light + in_scattered_light) * accum_transmittance * density; 344 | } 345 | 346 | position += _ray_direction * _step_size * step_increment; 347 | } 348 | 349 | return vec4(accum_scattering, alpha); 350 | } 351 | 352 | // ------------------------------------------------------------------ 353 | // MAIN ------------------------------------------------------------- 354 | // ------------------------------------------------------------------ 355 | 356 | void main() 357 | { 358 | // Generate a camera ray to the current fragment. 359 | Ray ray = generate_ray(); 360 | 361 | // Figure out where our ray will intersect the sphere that represents the beginning of the cloud layer. 362 | vec3 ray_start = ray_sphere_intersection(ray, u_PlanetCenter, u_PlanetRadius + u_CloudMinHeight); 363 | 364 | // Similarly, figure out where the ray intersects the end of the cloud layer. 365 | vec3 ray_end = ray_sphere_intersection(ray, u_PlanetCenter, u_PlanetRadius + u_CloudMaxHeight); 366 | 367 | // Get a random number that we'll use to jitter our ray. 368 | const float rng = blue_noise(); 369 | 370 | // The maximum number of ray march steps to use. 371 | const float max_steps = u_MaxNumSteps; 372 | 373 | // The minimum number of ray march steps to use with an added offset to prevent banding. 374 | const float min_steps = (u_MaxNumSteps * 0.5f) + (rng * 2.0f); 375 | 376 | // The number of ray march steps is determined depending on how steep the viewing angle is. 377 | float num_steps = mix(max_steps, min_steps, ray.direction.y); 378 | 379 | // Using the number of steps we can determine the step size of our ray march. 380 | float step_size = length(ray_start - ray_end) / num_steps; 381 | 382 | // Jitter the ray to prevent banding. 383 | ray_start += step_size * ray.direction * rng; 384 | 385 | float cos_angle = dot(ray.direction, u_SunDir); 386 | vec4 clouds = ray_march(ray_start, ray.direction, cos_angle, step_size, num_steps); 387 | vec4 sky = vec4(calculate_sky_luminance_rgb(u_SunDir, ray.direction, 2.0f) * 0.05f, 1.0f); 388 | 389 | FS_OUT_Color = clouds.rgb + (1.0f - clouds.a) * sky.rgb; 390 | } 391 | 392 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/detail_noise_cs.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // ------------------------------------------------------------------ 4 | // INPUTS ----------------------------------------------------------- 5 | // ------------------------------------------------------------------ 6 | 7 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in; 8 | 9 | // ------------------------------------------------------------------ 10 | // UNIFORMS --------------------------------------------------------- 11 | // ------------------------------------------------------------------ 12 | 13 | layout(binding = 0, rgba16f) uniform image3D i_Noise; 14 | 15 | uniform int u_Size; 16 | 17 | // ------------------------------------------------------------------ 18 | // MAIN ------------------------------------------------------------- 19 | // ------------------------------------------------------------------ 20 | 21 | void main() 22 | { 23 | vec3 tex_coord = (vec3(gl_GlobalInvocationID) + vec3(0.5f)) / float(u_Size); 24 | 25 | float freq = 8.0f; 26 | 27 | float worley0 = worley_fbm(tex_coord, freq); 28 | float worley1 = worley_fbm(tex_coord, freq * 2.0f); 29 | float worley2 = worley_fbm(tex_coord, freq * 4.0f); 30 | 31 | vec4 worley = vec4(worley0, worley1, worley2, 0.0f); 32 | 33 | imageStore(i_Noise, ivec3(gl_GlobalInvocationID), worley); 34 | } 35 | 36 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/mesh_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUT VARIABLES ------------------------------------------------ 3 | // ------------------------------------------------------------------ 4 | 5 | out vec3 FS_OUT_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUT VARIABLES ------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | in vec3 FS_IN_WorldPos; 12 | in vec3 FS_IN_Normal; 13 | in vec2 FS_IN_UV; 14 | in vec4 FS_IN_NDCFragPos; 15 | 16 | // ------------------------------------------------------------------ 17 | // UNIFORMS --------------------------------------------------------- 18 | // ------------------------------------------------------------------ 19 | 20 | layout(std140, binding = 0) uniform GlobalUniforms 21 | { 22 | mat4 view_proj; 23 | mat4 inv_view_proj; 24 | vec4 cam_pos; 25 | }; 26 | 27 | uniform sampler2D s_Diffuse; 28 | uniform vec3 u_LightDirection; 29 | 30 | // ------------------------------------------------------------------ 31 | // MAIN ------------------------------------------------------------- 32 | // ------------------------------------------------------------------ 33 | 34 | void main() 35 | { 36 | vec3 albedo = texture(s_Diffuse, FS_IN_UV).rgb; 37 | vec3 N = normalize(FS_IN_Normal); 38 | vec3 L = -u_LightDirection; // FragPos -> LightPos vector 39 | 40 | FS_OUT_Color = albedo * clamp(dot(N, L), 0.0, 1.0); 41 | } 42 | 43 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/mesh_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUT VARIABLES -------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | layout(location = 0) in vec3 VS_IN_Position; 6 | layout(location = 1) in vec2 VS_IN_UV; 7 | layout(location = 2) in vec3 VS_IN_Normal; 8 | layout(location = 3) in vec3 VS_IN_Tangent; 9 | layout(location = 4) in vec3 VS_IN_Bitangent; 10 | 11 | // ------------------------------------------------------------------ 12 | // OUTPUT VARIABLES ------------------------------------------------- 13 | // ------------------------------------------------------------------ 14 | 15 | out vec3 FS_IN_WorldPos; 16 | out vec3 FS_IN_Normal; 17 | out vec2 FS_IN_UV; 18 | out vec4 FS_IN_NDCFragPos; 19 | 20 | // ------------------------------------------------------------------ 21 | // UNIFORMS --------------------------------------------------------- 22 | // ------------------------------------------------------------------ 23 | 24 | layout(std140, binding = 0) uniform GlobalUniforms 25 | { 26 | mat4 view_proj; 27 | mat4 inv_view_proj; 28 | vec4 cam_pos; 29 | }; 30 | 31 | uniform mat4 u_Model; 32 | 33 | // ------------------------------------------------------------------ 34 | // MAIN ------------------------------------------------------------- 35 | // ------------------------------------------------------------------ 36 | 37 | void main() 38 | { 39 | vec4 world_pos = u_Model * vec4(VS_IN_Position, 1.0f); 40 | FS_IN_WorldPos = world_pos.xyz; 41 | FS_IN_Normal = normalize(normalize(mat3(u_Model) * VS_IN_Normal)); 42 | FS_IN_UV = VS_IN_UV; 43 | FS_IN_NDCFragPos = view_proj * world_pos; 44 | 45 | gl_Position = FS_IN_NDCFragPos; 46 | } 47 | 48 | // ------------------------------------------------------------------ 49 | -------------------------------------------------------------------------------- /src/shader/noise.glsl: -------------------------------------------------------------------------------- 1 | // Hash by David_Hoskins 2 | #define UI0 1597334673U 3 | #define UI1 3812015801U 4 | #define UI2 uvec2(UI0, UI1) 5 | #define UI3 uvec3(UI0, UI1, 2798796415U) 6 | #define UIF (1.0 / float(0xffffffffU)) 7 | 8 | vec3 hash_33(vec3 p) 9 | { 10 | uvec3 q = uvec3(ivec3(p)) * UI3; 11 | q = (q.x ^ q.y ^ q.z)*UI3; 12 | return -1. + 2. * vec3(q) * UIF; 13 | } 14 | 15 | float remap(float x, float a, float b, float c, float d) 16 | { 17 | return (((x - a) / (b - a)) * (d - c)) + c; 18 | } 19 | 20 | // Gradient noise by iq (modified to be tileable) 21 | float gradient_noise(vec3 x, float freq) 22 | { 23 | // grid 24 | vec3 p = floor(x); 25 | vec3 w = fract(x); 26 | 27 | // quintic interpolant 28 | vec3 u = w * w * w * (w * (w * 6. - 15.) + 10.); 29 | 30 | // gradients 31 | vec3 ga = hash_33(mod(p + vec3(0., 0., 0.), freq)); 32 | vec3 gb = hash_33(mod(p + vec3(1., 0., 0.), freq)); 33 | vec3 gc = hash_33(mod(p + vec3(0., 1., 0.), freq)); 34 | vec3 gd = hash_33(mod(p + vec3(1., 1., 0.), freq)); 35 | vec3 ge = hash_33(mod(p + vec3(0., 0., 1.), freq)); 36 | vec3 gf = hash_33(mod(p + vec3(1., 0., 1.), freq)); 37 | vec3 gg = hash_33(mod(p + vec3(0., 1., 1.), freq)); 38 | vec3 gh = hash_33(mod(p + vec3(1., 1., 1.), freq)); 39 | 40 | // projections 41 | float va = dot(ga, w - vec3(0., 0., 0.)); 42 | float vb = dot(gb, w - vec3(1., 0., 0.)); 43 | float vc = dot(gc, w - vec3(0., 1., 0.)); 44 | float vd = dot(gd, w - vec3(1., 1., 0.)); 45 | float ve = dot(ge, w - vec3(0., 0., 1.)); 46 | float vf = dot(gf, w - vec3(1., 0., 1.)); 47 | float vg = dot(gg, w - vec3(0., 1., 1.)); 48 | float vh = dot(gh, w - vec3(1., 1., 1.)); 49 | 50 | // interpolation 51 | return va + 52 | u.x * (vb - va) + 53 | u.y * (vc - va) + 54 | u.z * (ve - va) + 55 | u.x * u.y * (va - vb - vc + vd) + 56 | u.y * u.z * (va - vc - ve + vg) + 57 | u.z * u.x * (va - vb - ve + vf) + 58 | u.x * u.y * u.z * (-va + vb + vc - vd + ve - vf - vg + vh); 59 | } 60 | 61 | // Tileable 3D worley noise 62 | float worley_noise(vec3 uv, float freq) 63 | { 64 | vec3 id = floor(uv); 65 | vec3 p = fract(uv); 66 | 67 | float min_dist = 10000.; 68 | for (float x = -1.; x <= 1.; ++x) 69 | { 70 | for(float y = -1.; y <= 1.; ++y) 71 | { 72 | for(float z = -1.; z <= 1.; ++z) 73 | { 74 | vec3 offset = vec3(x, y, z); 75 | vec3 h = hash_33(mod(id + offset, vec3(freq))) * .5 + .5; 76 | h += offset; 77 | vec3 d = p - h; 78 | min_dist = min(min_dist, dot(d, d)); 79 | } 80 | } 81 | } 82 | 83 | // inverted worley noise 84 | return 1. - min_dist; 85 | } 86 | 87 | // Fbm for Perlin noise based on iq's blog 88 | float perlin_fbm(vec3 p, float freq, int octaves) 89 | { 90 | float G = exp2(-.85); 91 | float amp = 1.; 92 | float noise = 0.; 93 | for (int i = 0; i < octaves; ++i) 94 | { 95 | noise += amp * gradient_noise(p * freq, freq); 96 | freq *= 2.; 97 | amp *= G; 98 | } 99 | 100 | return noise; 101 | } 102 | 103 | // Tileable Worley fbm inspired by Andrew Schneider's Real-Time Volumetric Cloudscapes 104 | // chapter in GPU Pro 7. 105 | float worley_fbm(vec3 p, float freq) 106 | { 107 | return worley_noise(p*freq, freq) * .625 + 108 | worley_noise(p*freq*2., freq*2.) * .25 + 109 | worley_noise(p*freq*4., freq*4.) * .125; 110 | } 111 | -------------------------------------------------------------------------------- /src/shader/shape_noise_cs.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // ------------------------------------------------------------------ 4 | // INPUTS ----------------------------------------------------------- 5 | // ------------------------------------------------------------------ 6 | 7 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in; 8 | 9 | // ------------------------------------------------------------------ 10 | // UNIFORMS --------------------------------------------------------- 11 | // ------------------------------------------------------------------ 12 | 13 | layout(binding = 0, rgba16f) uniform image3D i_Noise; 14 | 15 | uniform int u_Size; 16 | 17 | // ------------------------------------------------------------------ 18 | // MAIN ------------------------------------------------------------- 19 | // ------------------------------------------------------------------ 20 | 21 | void main() 22 | { 23 | vec3 tex_coord = (vec3(gl_GlobalInvocationID) + vec3(0.5f)) / float(u_Size); 24 | 25 | float perlin = mix(1.0f, perlin_fbm(tex_coord, 4.0f, 7), 0.5f); 26 | perlin = abs(perlin * 2. - 1.); // billowy perlin noise 27 | 28 | float freq = 4.0f; 29 | 30 | float worley0 = worley_fbm(tex_coord, freq); 31 | float worley1 = worley_fbm(tex_coord, freq * 2.0f); 32 | float worley2 = worley_fbm(tex_coord, freq * 4.0f); 33 | 34 | float perlin_worley = remap(perlin, 0.0f, 1.0f, worley0, 1.0f); // perlin-worley 35 | 36 | vec4 cloud = vec4(perlin_worley, worley0, worley1, worley2); 37 | 38 | imageStore(i_Noise, ivec3(gl_GlobalInvocationID), cloud); 39 | } 40 | 41 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/tonemap_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUT VARIABLES ------------------------------------------------ 3 | // ------------------------------------------------------------------ 4 | 5 | out vec3 FS_OUT_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUT VARIABLES ------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | in vec2 FS_IN_TexCoord; 12 | 13 | // ------------------------------------------------------------------ 14 | // UNIFORMS --------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | uniform sampler2D s_HDR; 18 | 19 | uniform float u_Exposure; 20 | 21 | // ------------------------------------------------------------------ 22 | // FUNCTIONS -------------------------------------------------------- 23 | // ------------------------------------------------------------------ 24 | 25 | // ACES tone mapping curve fit to go from HDR to LDR 26 | //https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ 27 | vec3 aces_film(vec3 x) 28 | { 29 | float a = 2.51f; 30 | float b = 0.03f; 31 | float c = 2.43f; 32 | float d = 0.59f; 33 | float e = 0.14f; 34 | return clamp((x*(a*x + b)) / (x*(c*x + d) + e), 0.0f, 1.0f); 35 | } 36 | 37 | // ------------------------------------------------------------------ 38 | // MAIN ------------------------------------------------------------- 39 | // ------------------------------------------------------------------ 40 | 41 | void main() 42 | { 43 | vec3 color = texture(s_HDR, FS_IN_TexCoord).rgb; 44 | 45 | // Apply exposure 46 | color *= u_Exposure; 47 | 48 | // Tone mapping 49 | color = aces_film(color); 50 | 51 | FS_OUT_Color = color; 52 | } 53 | 54 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/triangle_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUT VARIABLES ------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | out vec2 FS_IN_TexCoord; 6 | 7 | // ------------------------------------------------------------------ 8 | // MAIN ------------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | void main() 12 | { 13 | FS_IN_TexCoord = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); 14 | gl_Position = vec4(FS_IN_TexCoord * 2.0f - 1.0f, 1.0f, 1.0f); 15 | } 16 | 17 | // ------------------------------------------------------------------ 18 | --------------------------------------------------------------------------------