├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── data ├── screenshot_1.JPG └── screenshot_2.JPG └── src ├── .clang-format ├── CMakeLists.txt ├── imgui_color_gradient.cpp ├── imgui_color_gradient.h ├── imgui_curve_editor.cpp ├── imgui_curve_editor.h ├── main.cpp └── shader ├── curl_noise.glsl ├── depth_fs.glsl ├── depth_prepass_fs.glsl ├── mesh_fs.glsl ├── mesh_vs.glsl ├── particle_emission_cs.glsl ├── particle_fs.glsl ├── particle_initialize_cs.glsl ├── particle_simulation_cs.glsl ├── particle_update_kickoff_cs.glsl ├── particle_vs.glsl ├── random.glsl └── simplex_noise.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 | bin/ 36 | bin/* 37 | build/ 38 | build/* 39 | lib/ 40 | lib/* 41 | 42 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dwSampleFramework"] 2 | path = dwSampleFramework 3 | url = https://github.com/diharaw/dwSampleFramework.git 4 | [submodule "external/dwSampleFramework"] 5 | path = external/dwSampleFramework 6 | url = https://github.com/diharaw/dwSampleFramework.git 7 | [submodule "external/ImGuizmo"] 8 | path = external/ImGuizmo 9 | url = https://github.com/CedricGuillemet/ImGuizmo.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project("GPUParticleSystem") 4 | 5 | IF(APPLE) 6 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") 7 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") 8 | ENDIF() 9 | 10 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 11 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 12 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) 13 | 14 | add_subdirectory(external/dwSampleFramework) 15 | 16 | include_directories("${DW_SAMPLE_FRAMEWORK_INCLUDES}" 17 | "${CMAKE_SOURCE_DIR}/external/dwSampleFramework/extras" 18 | "${CMAKE_SOURCE_DIR}/external/ImGuizmo") 19 | 20 | add_subdirectory(src) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | # GPU Particle System 2 | An OpenGL GPU accelerated particle system using Compute shaders and Indirect rendering. 3 | 4 | ## Screenshots 5 | 6 | ![GPUParticleSystem](data/screenshot_1.JPG) 7 | ![GPUParticleSystem](data/screenshot_2.JPG) 8 | 9 | ## Dependencies 10 | * [dwSampleFramework](https://github.com/diharaw/dwSampleFramework) 11 | 12 | ## License 13 | ``` 14 | Copyright (c) 2020 Dihara Wijetunga 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 17 | associated documentation files (the "Software"), to deal in the Software without restriction, 18 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 19 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all copies or substantial 23 | portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 26 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | ``` -------------------------------------------------------------------------------- /data/screenshot_1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/gpu-particle-system/fca08cb4cc0f1b46bde81496f66c1f5076b19065/data/screenshot_1.JPG -------------------------------------------------------------------------------- /data/screenshot_2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/gpu-particle-system/fca08cb4cc0f1b46bde81496f66c1f5076b19065/data/screenshot_2.JPG -------------------------------------------------------------------------------- /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(GPU_PARTICLE_SYSTEM_SOURCES ${PROJECT_SOURCE_DIR}/src/main.cpp 9 | ${PROJECT_SOURCE_DIR}/src/imgui_curve_editor.h 10 | ${PROJECT_SOURCE_DIR}/src/imgui_curve_editor.cpp 11 | ${PROJECT_SOURCE_DIR}/src/imgui_color_gradient.h 12 | ${PROJECT_SOURCE_DIR}/src/imgui_color_gradient.cpp 13 | ${PROJECT_SOURCE_DIR}/external/ImGuizmo/ImGuizmo.h 14 | ${PROJECT_SOURCE_DIR}/external/ImGuizmo/ImGuizmo.cpp 15 | ${PROJECT_SOURCE_DIR}/external/dwSampleFramework/extras/shadow_map.cpp 16 | ${PROJECT_SOURCE_DIR}/external/dwSampleFramework/extras/shadow_map.h 17 | ${PROJECT_SOURCE_DIR}/external/dwSampleFramework/extras/shadow_map.cpp 18 | ${PROJECT_SOURCE_DIR}/external/dwSampleFramework/extras/bruneton_sky_model.h 19 | ${PROJECT_SOURCE_DIR}/external/dwSampleFramework/extras/bruneton_sky_model.cpp) 20 | file(GLOB_RECURSE SHADER_SOURCES ${PROJECT_SOURCE_DIR}/src/*.glsl) 21 | 22 | if (APPLE) 23 | add_executable(GPUParticleSystem MACOSX_BUNDLE ${GPU_PARTICLE_SYSTEM_SOURCES} ${SHADER_SOURCES} ${ASSET_SOURCES}) 24 | set(MACOSX_BUNDLE_BUNDLE_NAME "GPUParticleSystem") 25 | set_source_files_properties(${SHADER_SOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/shader) 26 | set_source_files_properties(${ASSET_SOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 27 | else() 28 | add_executable(GPUParticleSystem ${GPU_PARTICLE_SYSTEM_SOURCES}) 29 | endif() 30 | 31 | target_link_libraries(GPUParticleSystem dwSampleFramework) 32 | 33 | if (NOT APPLE) 34 | add_custom_command(TARGET GPUParticleSystem POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/shader $/shader) 35 | endif() 36 | 37 | if(CLANG_FORMAT_EXE) 38 | add_custom_target(GPUParticleSystem-clang-format COMMAND ${CLANG_FORMAT_EXE} -i -style=file ${GPU_PARTICLE_SYSTEM_SOURCES} ${SHADER_SOURCES}) 39 | endif() 40 | 41 | set_property(TARGET GPUParticleSystem PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/$(Configuration)") -------------------------------------------------------------------------------- /src/imgui_color_gradient.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // imgui_color_gradient.cpp 3 | // imgui extension 4 | // 5 | // Created by David Gallardo on 11/06/16. 6 | 7 | #include "imgui_color_gradient.h" 8 | #include 9 | 10 | static const float GRADIENT_BAR_WIDGET_HEIGHT = 25; 11 | static const float GRADIENT_BAR_EDITOR_HEIGHT = 40; 12 | static const float GRADIENT_MARK_DELETE_DIFFY = 40; 13 | 14 | ImGradient::ImGradient() 15 | { 16 | addMark(0.0f, ImColor(0.0f, 0.0f, 0.0f)); 17 | addMark(1.0f, ImColor(1.0f, 1.0f, 1.0f)); 18 | } 19 | 20 | ImGradient::~ImGradient() 21 | { 22 | for (ImGradientMark* mark : m_marks) 23 | { 24 | delete mark; 25 | } 26 | } 27 | 28 | void ImGradient::addMark(float position, ImColor const color) 29 | { 30 | position = ImClamp(position, 0.0f, 1.0f); 31 | ImGradientMark* newMark = new ImGradientMark(); 32 | newMark->position = position; 33 | newMark->color[0] = color.Value.x; 34 | newMark->color[1] = color.Value.y; 35 | newMark->color[2] = color.Value.z; 36 | newMark->color[3] = color.Value.w; 37 | 38 | m_marks.push_back(newMark); 39 | 40 | refreshCache(); 41 | } 42 | 43 | void ImGradient::removeMark(ImGradientMark* mark) 44 | { 45 | m_marks.remove(mark); 46 | refreshCache(); 47 | } 48 | 49 | void ImGradient::getColorAt(float position, float* color) const 50 | { 51 | position = ImClamp(position, 0.0f, 1.0f); 52 | int cachePos = (position * 255); 53 | cachePos *= 3; 54 | color[0] = m_cachedValues[cachePos + 0]; 55 | color[1] = m_cachedValues[cachePos + 1]; 56 | color[2] = m_cachedValues[cachePos + 2]; 57 | color[3] = m_cachedValues[cachePos + 3]; 58 | } 59 | 60 | void ImGradient::computeColorAt(float position, float* color) const 61 | { 62 | position = ImClamp(position, 0.0f, 1.0f); 63 | 64 | ImGradientMark* lower = nullptr; 65 | ImGradientMark* upper = nullptr; 66 | 67 | for (ImGradientMark* mark : m_marks) 68 | { 69 | if (mark->position < position) 70 | { 71 | if (!lower || lower->position < mark->position) 72 | { 73 | lower = mark; 74 | } 75 | } 76 | 77 | if (mark->position >= position) 78 | { 79 | if (!upper || upper->position > mark->position) 80 | { 81 | upper = mark; 82 | } 83 | } 84 | } 85 | 86 | if (upper && !lower) 87 | { 88 | lower = upper; 89 | } 90 | else if (!upper && lower) 91 | { 92 | upper = lower; 93 | } 94 | else if (!lower && !upper) 95 | { 96 | color[0] = color[1] = color[2] = color[3] = 0; 97 | return; 98 | } 99 | 100 | if (upper == lower) 101 | { 102 | color[0] = upper->color[0]; 103 | color[1] = upper->color[1]; 104 | color[2] = upper->color[2]; 105 | color[3] = upper->color[3]; 106 | } 107 | else 108 | { 109 | float distance = upper->position - lower->position; 110 | float delta = (position - lower->position) / distance; 111 | 112 | //lerp 113 | color[0] = ((1.0f - delta) * lower->color[0]) + ((delta)*upper->color[0]); 114 | color[1] = ((1.0f - delta) * lower->color[1]) + ((delta)*upper->color[1]); 115 | color[2] = ((1.0f - delta) * lower->color[2]) + ((delta)*upper->color[2]); 116 | color[3] = ((1.0f - delta) * lower->color[3]) + ((delta)*upper->color[3]); 117 | } 118 | } 119 | 120 | void ImGradient::refreshCache() 121 | { 122 | m_marks.sort([](const ImGradientMark* a, const ImGradientMark* b) { return a->position < b->position; }); 123 | 124 | for (int i = 0; i < 256; ++i) 125 | { 126 | computeColorAt(i / 255.0f, &m_cachedValues[i * 3]); 127 | } 128 | } 129 | 130 | namespace ImGui 131 | { 132 | static void DrawGradientBar(ImGradient* gradient, 133 | struct ImVec2 const& bar_pos, 134 | float maxWidth, 135 | float height) 136 | { 137 | ImVec4 colorA = { 1, 1, 1, 1 }; 138 | ImVec4 colorB = { 1, 1, 1, 1 }; 139 | float prevX = bar_pos.x; 140 | float barBottom = bar_pos.y + height; 141 | ImGradientMark* prevMark = nullptr; 142 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 143 | 144 | draw_list->AddRectFilled(ImVec2(bar_pos.x - 2, bar_pos.y - 2), 145 | ImVec2(bar_pos.x + maxWidth + 2, barBottom + 2), 146 | IM_COL32(100, 100, 100, 255)); 147 | 148 | if (gradient->getMarks().size() == 0) 149 | { 150 | draw_list->AddRectFilled(ImVec2(bar_pos.x, bar_pos.y), 151 | ImVec2(bar_pos.x + maxWidth, barBottom), 152 | IM_COL32(255, 255, 255, 255)); 153 | } 154 | 155 | ImU32 colorAU32 = 0; 156 | ImU32 colorBU32 = 0; 157 | 158 | for (auto markIt = gradient->getMarks().begin(); markIt != gradient->getMarks().end(); ++markIt) 159 | { 160 | ImGradientMark* mark = *markIt; 161 | 162 | float from = prevX; 163 | float to = prevX = bar_pos.x + mark->position * maxWidth; 164 | 165 | if (prevMark == nullptr) 166 | { 167 | colorA.x = mark->color[0]; 168 | colorA.y = mark->color[1]; 169 | colorA.z = mark->color[2]; 170 | } 171 | else 172 | { 173 | colorA.x = prevMark->color[0]; 174 | colorA.y = prevMark->color[1]; 175 | colorA.z = prevMark->color[2]; 176 | } 177 | 178 | colorB.x = mark->color[0]; 179 | colorB.y = mark->color[1]; 180 | colorB.z = mark->color[2]; 181 | 182 | colorAU32 = ImGui::ColorConvertFloat4ToU32(colorA); 183 | colorBU32 = ImGui::ColorConvertFloat4ToU32(colorB); 184 | 185 | if (mark->position > 0.0) 186 | { 187 | draw_list->AddRectFilledMultiColor(ImVec2(from, bar_pos.y), 188 | ImVec2(to, barBottom), 189 | colorAU32, 190 | colorBU32, 191 | colorBU32, 192 | colorAU32); 193 | } 194 | 195 | prevMark = mark; 196 | } 197 | 198 | if (prevMark && prevMark->position < 1.0) 199 | { 200 | draw_list->AddRectFilledMultiColor(ImVec2(prevX, bar_pos.y), 201 | ImVec2(bar_pos.x + maxWidth, barBottom), 202 | colorBU32, 203 | colorBU32, 204 | colorBU32, 205 | colorBU32); 206 | } 207 | 208 | ImGui::SetCursorScreenPos(ImVec2(bar_pos.x, bar_pos.y + height + 10.0f)); 209 | } 210 | 211 | static void DrawGradientMarks(ImGradient* gradient, 212 | ImGradientMark*& draggingMark, 213 | ImGradientMark*& selectedMark, 214 | struct ImVec2 const& bar_pos, 215 | float maxWidth, 216 | float height) 217 | { 218 | ImVec4 colorA = { 1, 1, 1, 1 }; 219 | ImVec4 colorB = { 1, 1, 1, 1 }; 220 | float barBottom = bar_pos.y + height; 221 | ImGradientMark* prevMark = nullptr; 222 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 223 | ImU32 colorAU32 = 0; 224 | ImU32 colorBU32 = 0; 225 | 226 | for (auto markIt = gradient->getMarks().begin(); markIt != gradient->getMarks().end(); ++markIt) 227 | { 228 | ImGradientMark* mark = *markIt; 229 | 230 | if (!selectedMark) 231 | { 232 | selectedMark = mark; 233 | } 234 | 235 | float to = bar_pos.x + mark->position * maxWidth; 236 | 237 | if (prevMark == nullptr) 238 | { 239 | colorA.x = mark->color[0]; 240 | colorA.y = mark->color[1]; 241 | colorA.z = mark->color[2]; 242 | } 243 | else 244 | { 245 | colorA.x = prevMark->color[0]; 246 | colorA.y = prevMark->color[1]; 247 | colorA.z = prevMark->color[2]; 248 | } 249 | 250 | colorB.x = mark->color[0]; 251 | colorB.y = mark->color[1]; 252 | colorB.z = mark->color[2]; 253 | 254 | colorAU32 = ImGui::ColorConvertFloat4ToU32(colorA); 255 | colorBU32 = ImGui::ColorConvertFloat4ToU32(colorB); 256 | 257 | draw_list->AddTriangleFilled(ImVec2(to, bar_pos.y + (height - 6)), 258 | ImVec2(to - 6, barBottom), 259 | ImVec2(to + 6, barBottom), 260 | IM_COL32(100, 100, 100, 255)); 261 | 262 | draw_list->AddRectFilled(ImVec2(to - 6, barBottom), 263 | ImVec2(to + 6, bar_pos.y + (height + 12)), 264 | IM_COL32(100, 100, 100, 255), 265 | 1.0f, 266 | 1.0f); 267 | 268 | draw_list->AddRectFilled(ImVec2(to - 5, bar_pos.y + (height + 1)), 269 | ImVec2(to + 5, bar_pos.y + (height + 11)), 270 | IM_COL32(0, 0, 0, 255), 271 | 1.0f, 272 | 1.0f); 273 | 274 | if (selectedMark == mark) 275 | { 276 | draw_list->AddTriangleFilled(ImVec2(to, bar_pos.y + (height - 3)), 277 | ImVec2(to - 4, barBottom + 1), 278 | ImVec2(to + 4, barBottom + 1), 279 | IM_COL32(0, 255, 0, 255)); 280 | 281 | draw_list->AddRect(ImVec2(to - 5, bar_pos.y + (height + 1)), 282 | ImVec2(to + 5, bar_pos.y + (height + 11)), 283 | IM_COL32(0, 255, 0, 255), 284 | 1.0f, 285 | 1.0f); 286 | } 287 | 288 | draw_list->AddRectFilledMultiColor(ImVec2(to - 3, bar_pos.y + (height + 3)), 289 | ImVec2(to + 3, bar_pos.y + (height + 9)), 290 | colorBU32, 291 | colorBU32, 292 | colorBU32, 293 | colorBU32); 294 | 295 | ImGui::SetCursorScreenPos(ImVec2(to - 6, barBottom)); 296 | ImGui::InvisibleButton("mark", ImVec2(12, 12)); 297 | 298 | if (ImGui::IsItemHovered()) 299 | { 300 | if (ImGui::IsMouseClicked(0)) 301 | { 302 | selectedMark = mark; 303 | draggingMark = mark; 304 | } 305 | } 306 | 307 | prevMark = mark; 308 | } 309 | 310 | ImGui::SetCursorScreenPos(ImVec2(bar_pos.x, bar_pos.y + height + 20.0f)); 311 | } 312 | 313 | bool GradientButton(ImGradient* gradient) 314 | { 315 | if (!gradient) return false; 316 | 317 | ImVec2 widget_pos = ImGui::GetCursorScreenPos(); 318 | // ImDrawList* draw_list = ImGui::GetWindowDrawList(); 319 | 320 | float maxWidth = ImMax(250.0f, ImGui::GetContentRegionAvailWidth() - 100.0f); 321 | bool clicked = ImGui::InvisibleButton("gradient_bar", ImVec2(maxWidth, GRADIENT_BAR_WIDGET_HEIGHT)); 322 | 323 | DrawGradientBar(gradient, widget_pos, maxWidth, GRADIENT_BAR_WIDGET_HEIGHT); 324 | 325 | return clicked; 326 | } 327 | 328 | bool GradientEditor(const char* label, 329 | ImGradient* gradient, 330 | ImGradientMark*& draggingMark, 331 | ImGradientMark*& selectedMark) 332 | { 333 | if (!gradient) return false; 334 | 335 | bool modified = false; 336 | 337 | ImVec2 bar_pos = ImGui::GetCursorScreenPos(); 338 | bar_pos.x += 10; 339 | ImVec2 textSize = ImGui::CalcTextSize(label); 340 | float maxWidth = ImGui::GetContentRegionAvailWidth() - textSize.x; 341 | float barBottom = bar_pos.y + GRADIENT_BAR_EDITOR_HEIGHT; 342 | 343 | ImGui::InvisibleButton("gradient_editor_bar", ImVec2(maxWidth, GRADIENT_BAR_EDITOR_HEIGHT)); 344 | 345 | if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) 346 | { 347 | float pos = (ImGui::GetIO().MousePos.x - bar_pos.x) / maxWidth; 348 | 349 | float newMarkCol[4]; 350 | gradient->getColorAt(pos, newMarkCol); 351 | 352 | gradient->addMark(pos, ImColor(newMarkCol[0], newMarkCol[1], newMarkCol[2])); 353 | } 354 | 355 | ImGui::SameLine(); 356 | ImGui::Text(label); 357 | 358 | DrawGradientBar(gradient, bar_pos, maxWidth, GRADIENT_BAR_EDITOR_HEIGHT); 359 | DrawGradientMarks(gradient, draggingMark, selectedMark, bar_pos, maxWidth, GRADIENT_BAR_EDITOR_HEIGHT); 360 | 361 | if (!ImGui::IsMouseDown(0) && draggingMark) 362 | { 363 | draggingMark = nullptr; 364 | } 365 | 366 | if (ImGui::IsMouseDragging(0) && draggingMark) 367 | { 368 | float increment = ImGui::GetIO().MouseDelta.x / maxWidth; 369 | bool insideZone = (ImGui::GetIO().MousePos.x > bar_pos.x) && (ImGui::GetIO().MousePos.x < bar_pos.x + maxWidth); 370 | 371 | if (increment != 0.0f && insideZone) 372 | { 373 | draggingMark->position += increment; 374 | draggingMark->position = ImClamp(draggingMark->position, 0.0f, 1.0f); 375 | gradient->refreshCache(); 376 | modified = true; 377 | } 378 | 379 | float diffY = ImGui::GetIO().MousePos.y - barBottom; 380 | 381 | if (diffY >= GRADIENT_MARK_DELETE_DIFFY) 382 | { 383 | gradient->removeMark(draggingMark); 384 | draggingMark = nullptr; 385 | selectedMark = nullptr; 386 | modified = true; 387 | } 388 | } 389 | 390 | if (!selectedMark && gradient->getMarks().size() > 0) 391 | { 392 | selectedMark = gradient->getMarks().front(); 393 | } 394 | 395 | if (selectedMark) 396 | { 397 | bool colorModified = ImGui::ColorPicker4("Color", selectedMark->color); 398 | 399 | if (selectedMark && colorModified) 400 | { 401 | modified = true; 402 | gradient->refreshCache(); 403 | } 404 | } 405 | 406 | return modified; 407 | } 408 | }; // namespace ImGui -------------------------------------------------------------------------------- /src/imgui_color_gradient.h: -------------------------------------------------------------------------------- 1 | // 2 | // imgui_color_gradient.h 3 | // imgui extension 4 | // 5 | // Created by David Gallardo on 11/06/16. 6 | 7 | /* 8 | 9 | Usage: 10 | 11 | ::GRADIENT DATA:: 12 | ImGradient gradient; 13 | 14 | ::BUTTON:: 15 | if(ImGui::GradientButton(&gradient)) 16 | { 17 | //set show editor flag to true/false 18 | } 19 | 20 | ::EDITOR:: 21 | static ImGradientMark* draggingMark = nullptr; 22 | static ImGradientMark* selectedMark = nullptr; 23 | 24 | bool updated = ImGui::GradientEditor(&gradient, draggingMark, selectedMark); 25 | 26 | ::GET A COLOR:: 27 | float color[3]; 28 | gradient.getColorAt(0.3f, color); //position from 0 to 1 29 | 30 | ::MODIFY GRADIENT WITH CODE:: 31 | gradient.getMarks().clear(); 32 | gradient.addMark(0.0f, ImColor(0.2f, 0.1f, 0.0f)); 33 | gradient.addMark(0.7f, ImColor(120, 200, 255)); 34 | 35 | ::WOOD BROWNS PRESET:: 36 | gradient.getMarks().clear(); 37 | gradient.addMark(0.0f, ImColor(0xA0, 0x79, 0x3D)); 38 | gradient.addMark(0.2f, ImColor(0xAA, 0x83, 0x47)); 39 | gradient.addMark(0.3f, ImColor(0xB4, 0x8D, 0x51)); 40 | gradient.addMark(0.4f, ImColor(0xBE, 0x97, 0x5B)); 41 | gradient.addMark(0.6f, ImColor(0xC8, 0xA1, 0x65)); 42 | gradient.addMark(0.7f, ImColor(0xD2, 0xAB, 0x6F)); 43 | gradient.addMark(0.8f, ImColor(0xDC, 0xB5, 0x79)); 44 | gradient.addMark(1.0f, ImColor(0xE6, 0xBF, 0x83)); 45 | 46 | */ 47 | 48 | #pragma once 49 | 50 | #include 51 | #include 52 | 53 | struct ImGradientMark 54 | { 55 | float color[4]; 56 | float position; //0 to 1 57 | }; 58 | 59 | class ImGradient 60 | { 61 | public: 62 | ImGradient(); 63 | ~ImGradient(); 64 | 65 | void getColorAt(float position, float* color) const; 66 | void addMark(float position, ImColor const color); 67 | void removeMark(ImGradientMark* mark); 68 | void refreshCache(); 69 | std::list& getMarks() { return m_marks; } 70 | 71 | private: 72 | void computeColorAt(float position, float* color) const; 73 | std::list m_marks; 74 | float m_cachedValues[256 * 3]; 75 | }; 76 | 77 | namespace ImGui 78 | { 79 | bool GradientButton(ImGradient* gradient); 80 | 81 | bool GradientEditor(const char* label, 82 | ImGradient* gradient, 83 | ImGradientMark*& draggingMark, 84 | ImGradientMark*& selectedMark); 85 | 86 | } // namespace ImGui -------------------------------------------------------------------------------- /src/imgui_curve_editor.cpp: -------------------------------------------------------------------------------- 1 | #define IMGUI_DEFINE_MATH_OPERATORS 2 | #include 3 | #include 4 | #include 5 | 6 | namespace ImGui 7 | { 8 | template 9 | void bezier_table(ImVec2 P[4], ImVec2 results[steps + 1]) 10 | { 11 | static float C[(steps + 1) * 4], *K = 0; 12 | if (!K) 13 | { 14 | K = C; 15 | for (unsigned step = 0; step <= steps; ++step) 16 | { 17 | float t = (float)step / (float)steps; 18 | C[step * 4 + 0] = (1 - t) * (1 - t) * (1 - t); // * P0 19 | C[step * 4 + 1] = 3 * (1 - t) * (1 - t) * t; // * P1 20 | C[step * 4 + 2] = 3 * (1 - t) * t * t; // * P2 21 | C[step * 4 + 3] = t * t * t; // * P3 22 | } 23 | } 24 | for (unsigned step = 0; step <= steps; ++step) 25 | { 26 | ImVec2 point = { 27 | K[step * 4 + 0] * P[0].x + K[step * 4 + 1] * P[1].x + K[step * 4 + 2] * P[2].x + K[step * 4 + 3] * P[3].x, 28 | K[step * 4 + 0] * P[0].y + K[step * 4 + 1] * P[1].y + K[step * 4 + 2] * P[2].y + K[step * 4 + 3] * P[3].y 29 | }; 30 | results[step] = point; 31 | } 32 | } 33 | 34 | float BezierValue(float dt01, float P[4]) 35 | { 36 | enum 37 | { 38 | STEPS = 256 39 | }; 40 | ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } }; 41 | ImVec2 results[STEPS + 1]; 42 | bezier_table(Q, results); 43 | return results[(int)((dt01 < 0 ? 0 : dt01 > 1 ? 1 : dt01) * STEPS)].y; 44 | } 45 | 46 | int Bezier(const char* label, float P[5]) 47 | { 48 | // visuals 49 | enum 50 | { 51 | SMOOTHNESS = 64 52 | }; // curve smoothness: the higher number of segments, the smoother curve 53 | enum 54 | { 55 | CURVE_WIDTH = 4 56 | }; // main curved line width 57 | enum 58 | { 59 | LINE_WIDTH = 1 60 | }; // handlers: small lines width 61 | enum 62 | { 63 | GRAB_RADIUS = 8 64 | }; // handlers: circle radius 65 | enum 66 | { 67 | GRAB_BORDER = 2 68 | }; // handlers: circle border width 69 | enum 70 | { 71 | AREA_CONSTRAINED = true 72 | }; // should grabbers be constrained to grid area? 73 | enum 74 | { 75 | AREA_WIDTH = 128 76 | }; // area width in pixels. 0 for adaptive size (will use max avail width) 77 | 78 | // curve presets 79 | static struct 80 | { 81 | const char* name; 82 | float points[4]; 83 | } presets[] = { 84 | { "Linear", 0.000f, 0.000f, 1.000f, 1.000f }, 85 | 86 | { "In Sine", 0.470f, 0.000f, 0.745f, 0.715f }, 87 | { "In Quad", 0.550f, 0.085f, 0.680f, 0.530f }, 88 | { "In Cubic", 0.550f, 0.055f, 0.675f, 0.190f }, 89 | { "In Quart", 0.895f, 0.030f, 0.685f, 0.220f }, 90 | { "In Quint", 0.755f, 0.050f, 0.855f, 0.060f }, 91 | { "In Expo", 0.950f, 0.050f, 0.795f, 0.035f }, 92 | { "In Circ", 0.600f, 0.040f, 0.980f, 0.335f }, 93 | { "In Back", 0.600f, -0.28f, 0.735f, 0.045f }, 94 | 95 | { "Out Sine", 0.390f, 0.575f, 0.565f, 1.000f }, 96 | { "Out Quad", 0.250f, 0.460f, 0.450f, 0.940f }, 97 | { "Out Cubic", 0.215f, 0.610f, 0.355f, 1.000f }, 98 | { "Out Quart", 0.165f, 0.840f, 0.440f, 1.000f }, 99 | { "Out Quint", 0.230f, 1.000f, 0.320f, 1.000f }, 100 | { "Out Expo", 0.190f, 1.000f, 0.220f, 1.000f }, 101 | { "Out Circ", 0.075f, 0.820f, 0.165f, 1.000f }, 102 | { "Out Back", 0.175f, 0.885f, 0.320f, 1.275f }, 103 | 104 | { "InOut Sine", 0.445f, 0.050f, 0.550f, 0.950f }, 105 | { "InOut Quad", 0.455f, 0.030f, 0.515f, 0.955f }, 106 | { "InOut Cubic", 0.645f, 0.045f, 0.355f, 1.000f }, 107 | { "InOut Quart", 0.770f, 0.000f, 0.175f, 1.000f }, 108 | { "InOut Quint", 0.860f, 0.000f, 0.070f, 1.000f }, 109 | { "InOut Expo", 1.000f, 0.000f, 0.000f, 1.000f }, 110 | { "InOut Circ", 0.785f, 0.135f, 0.150f, 0.860f }, 111 | { "InOut Back", 0.680f, -0.55f, 0.265f, 1.550f }, 112 | 113 | // easeInElastic: not a bezier 114 | // easeOutElastic: not a bezier 115 | // easeInOutElastic: not a bezier 116 | // easeInBounce: not a bezier 117 | // easeOutBounce: not a bezier 118 | // easeInOutBounce: not a bezier 119 | }; 120 | 121 | // preset selector 122 | 123 | bool reload = 0; 124 | //ImGui::PushID(label); 125 | //if (ImGui::ArrowButton("##lt", ImGuiDir_Left)) 126 | //{ // ImGui::ArrowButton(ImGui::GetCurrentWindow()->GetID("##lt"), ImGuiDir_Left, ImVec2(0, 0), 0) 127 | // if (--P[4] >= 0) 128 | // reload = 1; 129 | // else 130 | // ++P[4]; 131 | //} 132 | //ImGui::SameLine(); 133 | 134 | //if (ImGui::Button("Presets")) 135 | //{ 136 | // ImGui::OpenPopup("!Presets"); 137 | //} 138 | //if (ImGui::BeginPopup("!Presets")) 139 | //{ 140 | // for (int i = 0; i < IM_ARRAYSIZE(presets); ++i) 141 | // { 142 | // if (i == 1 || i == 9 || i == 17) ImGui::Separator(); 143 | // if (ImGui::MenuItem(presets[i].name, NULL, P[4] == i)) 144 | // { 145 | // P[4] = i; 146 | // reload = 1; 147 | // } 148 | // } 149 | // ImGui::EndPopup(); 150 | //} 151 | //ImGui::SameLine(); 152 | 153 | //if (ImGui::ArrowButton("##rt", ImGuiDir_Right)) 154 | //{ // ImGui::ArrowButton(ImGui::GetCurrentWindow()->GetID("##rt"), ImGuiDir_Right, ImVec2(0, 0), 0) 155 | // if (++P[4] < IM_ARRAYSIZE(presets)) 156 | // reload = 1; 157 | // else 158 | // --P[4]; 159 | //} 160 | //ImGui::SameLine(); 161 | //ImGui::PopID(); 162 | 163 | if (reload) 164 | { 165 | memcpy(P, presets[(int)P[4]].points, sizeof(float) * 4); 166 | } 167 | 168 | // bezier widget 169 | 170 | const ImGuiStyle& Style = GetStyle(); 171 | const ImGuiIO& IO = GetIO(); 172 | ImDrawList* DrawList = GetWindowDrawList(); 173 | ImGuiWindow* Window = GetCurrentWindow(); 174 | if (Window->SkipItems) 175 | return false; 176 | 177 | // header and spacing 178 | int changed = 0; //SliderFloat4(label, P, 0, 1, "%.3f", 1.0f); 179 | int hovered = IsItemActive() || IsItemHovered(); // IsItemDragged() ? 180 | Dummy(ImVec2(0, 3)); 181 | 182 | // prepare canvas 183 | const float avail = GetContentRegionAvailWidth(); 184 | const float dim = AREA_WIDTH > 0 ? AREA_WIDTH : avail; 185 | ImVec2 Canvas(dim, dim); 186 | 187 | ImRect bb(Window->DC.CursorPos, Window->DC.CursorPos + Canvas); 188 | ItemSize(bb); 189 | if (!ItemAdd(bb, NULL)) 190 | return changed; 191 | 192 | const ImGuiID id = Window->GetID(label); 193 | hovered |= 0 != ItemHoverable(ImRect(bb.Min, bb.Min + ImVec2(avail, dim)), id); 194 | 195 | RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, Style.FrameRounding); 196 | 197 | // background grid 198 | for (int i = 0; i <= Canvas.x; i += (Canvas.x / 4)) 199 | { 200 | DrawList->AddLine( 201 | ImVec2(bb.Min.x + i, bb.Min.y), 202 | ImVec2(bb.Min.x + i, bb.Max.y), 203 | GetColorU32(ImGuiCol_TextDisabled)); 204 | } 205 | for (int i = 0; i <= Canvas.y; i += (Canvas.y / 4)) 206 | { 207 | DrawList->AddLine( 208 | ImVec2(bb.Min.x, bb.Min.y + i), 209 | ImVec2(bb.Max.x, bb.Min.y + i), 210 | GetColorU32(ImGuiCol_TextDisabled)); 211 | } 212 | 213 | // eval curve 214 | ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } }; 215 | ImVec2 results[SMOOTHNESS + 1]; 216 | bezier_table(Q, results); 217 | 218 | // control points: 2 lines and 2 circles 219 | { 220 | // handle grabbers 221 | ImVec2 mouse = GetIO().MousePos, pos[2]; 222 | float distance[2]; 223 | 224 | for (int i = 0; i < 2; ++i) 225 | { 226 | pos[i] = ImVec2(P[i * 2 + 0], 1 - P[i * 2 + 1]) * (bb.Max - bb.Min) + bb.Min; 227 | distance[i] = (pos[i].x - mouse.x) * (pos[i].x - mouse.x) + (pos[i].y - mouse.y) * (pos[i].y - mouse.y); 228 | } 229 | 230 | int selected = distance[0] < distance[1] ? 0 : 1; 231 | if (distance[selected] < (4 * GRAB_RADIUS * 4 * GRAB_RADIUS)) 232 | { 233 | SetTooltip("(%4.3f, %4.3f)", P[selected * 2 + 0], P[selected * 2 + 1]); 234 | 235 | if (/*hovered &&*/ (IsMouseClicked(0) || IsMouseDragging(0))) 236 | { 237 | float& px = (P[selected * 2 + 0] += GetIO().MouseDelta.x / Canvas.x); 238 | float& py = (P[selected * 2 + 1] -= GetIO().MouseDelta.y / Canvas.y); 239 | 240 | if (AREA_CONSTRAINED) 241 | { 242 | px = (px < 0 ? 0 : (px > 1 ? 1 : px)); 243 | py = (py < 0 ? 0 : (py > 1 ? 1 : py)); 244 | } 245 | 246 | changed = true; 247 | } 248 | } 249 | } 250 | 251 | // if (hovered || changed) DrawList->PushClipRectFullScreen(); 252 | 253 | // draw curve 254 | { 255 | ImColor color(GetStyle().Colors[ImGuiCol_PlotLines]); 256 | for (int i = 0; i < SMOOTHNESS; ++i) 257 | { 258 | ImVec2 p = { results[i + 0].x, 1 - results[i + 0].y }; 259 | ImVec2 q = { results[i + 1].x, 1 - results[i + 1].y }; 260 | ImVec2 r(p.x * (bb.Max.x - bb.Min.x) + bb.Min.x, p.y * (bb.Max.y - bb.Min.y) + bb.Min.y); 261 | ImVec2 s(q.x * (bb.Max.x - bb.Min.x) + bb.Min.x, q.y * (bb.Max.y - bb.Min.y) + bb.Min.y); 262 | DrawList->AddLine(r, s, color, CURVE_WIDTH); 263 | } 264 | } 265 | 266 | // draw preview (cycles every 1s) 267 | static clock_t epoch = clock(); 268 | ImVec4 white(GetStyle().Colors[ImGuiCol_Text]); 269 | for (int i = 0; i < 3; ++i) 270 | { 271 | double now = ((clock() - epoch) / (double)CLOCKS_PER_SEC); 272 | float delta = ((int)(now * 1000) % 1000) / 1000.f; 273 | delta += i / 3.f; 274 | if (delta > 1) delta -= 1; 275 | int idx = (int)(delta * SMOOTHNESS); 276 | float evalx = results[idx].x; // 277 | float evaly = results[idx].y; // ImGui::BezierValue( delta, P ); 278 | ImVec2 p0 = ImVec2(evalx, 1 - 0) * (bb.Max - bb.Min) + bb.Min; 279 | ImVec2 p1 = ImVec2(0, 1 - evaly) * (bb.Max - bb.Min) + bb.Min; 280 | ImVec2 p2 = ImVec2(evalx, 1 - evaly) * (bb.Max - bb.Min) + bb.Min; 281 | DrawList->AddCircleFilled(p0, GRAB_RADIUS / 2, ImColor(white)); 282 | DrawList->AddCircleFilled(p1, GRAB_RADIUS / 2, ImColor(white)); 283 | DrawList->AddCircleFilled(p2, GRAB_RADIUS / 2, ImColor(white)); 284 | } 285 | 286 | // draw lines and grabbers 287 | float luma = IsItemActive() || IsItemHovered() ? 0.5f : 1.0f; 288 | ImVec4 pink(1.00f, 0.00f, 0.75f, luma), cyan(0.00f, 0.75f, 1.00f, luma); 289 | ImVec2 p1 = ImVec2(P[0], 1 - P[1]) * (bb.Max - bb.Min) + bb.Min; 290 | ImVec2 p2 = ImVec2(P[2], 1 - P[3]) * (bb.Max - bb.Min) + bb.Min; 291 | DrawList->AddLine(ImVec2(bb.Min.x, bb.Max.y), p1, ImColor(white), LINE_WIDTH); 292 | DrawList->AddLine(ImVec2(bb.Max.x, bb.Min.y), p2, ImColor(white), LINE_WIDTH); 293 | DrawList->AddCircleFilled(p1, GRAB_RADIUS, ImColor(white)); 294 | DrawList->AddCircleFilled(p1, GRAB_RADIUS - GRAB_BORDER, ImColor(pink)); 295 | DrawList->AddCircleFilled(p2, GRAB_RADIUS, ImColor(white)); 296 | DrawList->AddCircleFilled(p2, GRAB_RADIUS - GRAB_BORDER, ImColor(cyan)); 297 | 298 | // if (hovered || changed) DrawList->PopClipRect(); 299 | ImGui::SameLine(); 300 | ImGui::Text(label); 301 | 302 | return changed; 303 | } 304 | 305 | void ShowBezierDemo() 306 | { 307 | { 308 | static float v[5] = { 0.950f, 0.050f, 0.795f, 0.035f }; 309 | Bezier("easeInExpo", v); 310 | } 311 | } 312 | } // namespace ImGui -------------------------------------------------------------------------------- /src/imgui_curve_editor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ImGui 4 | { 5 | extern float BezierValue(float dt01, float P[4]); 6 | extern int Bezier(const char* label, float P[5]); 7 | extern void ShowBezierDemo(); 8 | } // namespace ImGui -------------------------------------------------------------------------------- /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 | #include 15 | #include 16 | #include "imgui_curve_editor.h" 17 | #include "imgui_color_gradient.h" 18 | 19 | #undef min 20 | #undef max 21 | #define CAMERA_FAR_PLANE 1000.0f 22 | #define MAX_PARTICLES 1000000 23 | #define GRADIENT_SAMPLES 32 24 | #define LOCAL_SIZE 32 25 | 26 | struct GlobalUniforms 27 | { 28 | DW_ALIGNED(16) 29 | glm::mat4 view_proj; 30 | }; 31 | 32 | enum EmissionShape 33 | { 34 | EMISSION_SHAPE_SPHERE, 35 | EMISSION_SHAPE_BOX, 36 | EMISSION_SHAPE_CONE 37 | }; 38 | 39 | enum DirectionType 40 | { 41 | DIRECTION_TYPE_SINGLE, 42 | DIRECTION_TYPE_OUTWARDS 43 | }; 44 | 45 | enum PropertyChangeType 46 | { 47 | PROPERTY_CONSTANT, 48 | PROPERTY_OVER_TIME 49 | }; 50 | 51 | class GPUParticleSystem : public dw::Application 52 | { 53 | protected: 54 | // ----------------------------------------------------------------------------------------------------------------------------------- 55 | 56 | bool init(int argc, const char* argv[]) override 57 | { 58 | // Create GPU resources. 59 | if (!create_shaders()) 60 | return false; 61 | 62 | load_mesh(); 63 | create_buffers(); 64 | create_textures(); 65 | create_framebuffers(); 66 | 67 | // Create camera. 68 | create_camera(); 69 | particle_initialize(); 70 | 71 | glEnable(GL_MULTISAMPLE); 72 | 73 | m_debug_draw.set_distance_fade(true); 74 | m_debug_draw.set_depth_test(true); 75 | m_debug_draw.set_fade_start(5.0f); 76 | m_debug_draw.set_fade_end(10.0f); 77 | 78 | m_color_gradient.getMarks().clear(); 79 | m_color_gradient.addMark(0.0f, ImColor(1.0f, 0.0f, 0.0f)); 80 | m_color_gradient.addMark(0.225f, ImColor(1.0f, 1.0f, 0.0f)); 81 | m_color_gradient.addMark(0.4f, ImColor(0.086f, 0.443f, 0.039f)); 82 | m_color_gradient.addMark(0.6f, ImColor(0.0f, 0.983f, 0.77f)); 83 | m_color_gradient.addMark(0.825f, ImColor(0.0f, 0.011f, 0.969f)); 84 | m_color_gradient.addMark(1.0f, ImColor(0.939f, 0.0f, 1.0f)); 85 | 86 | update_color_over_time_texture(); 87 | update_size_over_time_texture(); 88 | 89 | m_generator = std::mt19937(m_random()); 90 | 91 | m_sky_model.initialize(); 92 | m_shadow_map.initialize(2048); 93 | 94 | m_sky_model.set_sun_angle(glm::radians(-30.0f)); 95 | m_shadow_map.set_direction(m_sky_model.direction()); 96 | m_shadow_map.set_extents(12.0f); 97 | 98 | glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); 99 | 100 | m_position_transform = glm::translate(m_position_transform, glm::vec3(0.0f, 3.0f, 0.0f)); 101 | 102 | return true; 103 | } 104 | 105 | // ----------------------------------------------------------------------------------------------------------------------------------- 106 | 107 | void update(double delta) override 108 | { 109 | ImGuizmo::BeginFrame(); 110 | 111 | m_accumulator += float(m_delta_seconds); 112 | 113 | std::uniform_real_distribution<> distribution(1.0f, 10000.0f); 114 | 115 | m_seeds = glm::vec3(distribution(m_generator), distribution(m_generator), distribution(m_generator)); 116 | m_max_active_particles = m_max_lifetime * m_emission_rate; 117 | 118 | if (m_debug_gui) 119 | debug_gui(); 120 | 121 | ImGuizmo::SetRect(0, 0, m_width, m_height); 122 | 123 | ImGuizmo::Manipulate(&m_main_camera->m_view[0][0], &m_main_camera->m_projection[0][0], ImGuizmo::TRANSLATE, ImGuizmo::WORLD, &m_position_transform[0][0], NULL, NULL); 124 | 125 | m_position = m_position_transform * glm::vec4(1.0f); 126 | m_position -= glm::vec3(1.0f); 127 | 128 | // Update camera. 129 | update_camera(); 130 | 131 | render_depth_prepass(); 132 | 133 | particle_kickoff(); 134 | particle_emission(); 135 | particle_simulation(); 136 | 137 | m_sky_model.update_cubemap(); 138 | render_shadow_map(); 139 | render_lit_scene(); 140 | 141 | m_sky_model.render_skybox(0, 0, m_width, m_height, m_main_camera->m_view, m_main_camera->m_projection, nullptr); 142 | 143 | if (m_show_grid) 144 | m_debug_draw.grid(m_main_camera->m_view_projection, 1.0f, 10.0f); 145 | 146 | m_debug_draw.render(nullptr, m_width, m_height, m_main_camera->m_view_projection, m_main_camera->m_position); 147 | 148 | m_pre_sim_idx = m_pre_sim_idx == 0 ? 1 : 0; 149 | m_post_sim_idx = m_post_sim_idx == 0 ? 1 : 0; 150 | } 151 | 152 | // ----------------------------------------------------------------------------------------------------------------------------------- 153 | 154 | void shutdown() override 155 | { 156 | m_shadow_map.shutdown(); 157 | m_sky_model.shutdown(); 158 | } 159 | 160 | // ----------------------------------------------------------------------------------------------------------------------------------- 161 | 162 | void window_resized(int width, int height) override 163 | { 164 | // Override window resized method to update camera projection. 165 | m_main_camera->update_projection(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height)); 166 | } 167 | 168 | // ----------------------------------------------------------------------------------------------------------------------------------- 169 | 170 | void key_pressed(int code) override 171 | { 172 | // Handle forward movement. 173 | if (code == GLFW_KEY_W) 174 | m_heading_speed = m_camera_speed; 175 | else if (code == GLFW_KEY_S) 176 | m_heading_speed = -m_camera_speed; 177 | 178 | // Handle sideways movement. 179 | if (code == GLFW_KEY_A) 180 | m_sideways_speed = -m_camera_speed; 181 | else if (code == GLFW_KEY_D) 182 | m_sideways_speed = m_camera_speed; 183 | 184 | if (code == GLFW_KEY_SPACE) 185 | m_mouse_look = true; 186 | 187 | if (code == GLFW_KEY_G) 188 | m_debug_gui = !m_debug_gui; 189 | } 190 | 191 | // ----------------------------------------------------------------------------------------------------------------------------------- 192 | 193 | void key_released(int code) override 194 | { 195 | // Handle forward movement. 196 | if (code == GLFW_KEY_W || code == GLFW_KEY_S) 197 | m_heading_speed = 0.0f; 198 | 199 | // Handle sideways movement. 200 | if (code == GLFW_KEY_A || code == GLFW_KEY_D) 201 | m_sideways_speed = 0.0f; 202 | 203 | if (code == GLFW_KEY_SPACE) 204 | m_mouse_look = false; 205 | } 206 | 207 | // ----------------------------------------------------------------------------------------------------------------------------------- 208 | 209 | void mouse_pressed(int code) override 210 | { 211 | // Enable mouse look. 212 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 213 | m_mouse_look = true; 214 | } 215 | 216 | // ----------------------------------------------------------------------------------------------------------------------------------- 217 | 218 | void mouse_released(int code) override 219 | { 220 | // Disable mouse look. 221 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 222 | m_mouse_look = false; 223 | } 224 | 225 | // ----------------------------------------------------------------------------------------------------------------------------------- 226 | 227 | protected: 228 | // ----------------------------------------------------------------------------------------------------------------------------------- 229 | 230 | dw::AppSettings intial_app_settings() override 231 | { 232 | dw::AppSettings settings; 233 | 234 | settings.resizable = true; 235 | settings.maximized = false; 236 | settings.refresh_rate = 60; 237 | settings.major_ver = 4; 238 | settings.width = 1920; 239 | settings.height = 1080; 240 | settings.title = "GPU Particle System (c) 2020 Dihara Wijetunga"; 241 | settings.enable_debug_callback = false; 242 | 243 | return settings; 244 | } 245 | 246 | // ----------------------------------------------------------------------------------------------------------------------------------- 247 | 248 | private: 249 | // ----------------------------------------------------------------------------------------------------------------------------------- 250 | 251 | void debug_gui() 252 | { 253 | std::string active_count = "Max Active Particles: " + std::to_string(m_max_active_particles); 254 | 255 | ImGui::Text(active_count.c_str()); 256 | ImGui::InputFloat3("Position", &m_position.x); 257 | ImGui::InputInt("Emission Rate (Particles/Second)", &m_emission_rate); 258 | ImGui::InputFloat("Min Lifetime", &m_min_lifetime); 259 | ImGui::InputFloat("Max Lifetime", &m_max_lifetime); 260 | ImGui::InputFloat("Min Initial Speed", &m_min_initial_speed); 261 | ImGui::InputFloat("Max Initial Speed", &m_max_initial_speed); 262 | ImGui::InputFloat3("Constant Velocity", &m_constant_velocity.x); 263 | ImGui::InputFloat("Viscosity", &m_viscosity); 264 | ImGui::Checkbox("Affected by Gravity", &m_affected_by_gravity); 265 | ImGui::Checkbox("Depth Buffer Collision", &m_depth_buffer_collision); 266 | if (m_depth_buffer_collision) 267 | ImGui::SliderFloat("Restitution", &m_restitution, 0.0f, 1.0f); 268 | ImGui::SliderFloat("Sphere Radius", &m_sphere_radius, 0.1f, 25.0f); 269 | 270 | if (ImGui::InputFloat("Start Size", &m_start_size)) 271 | update_size_over_time_texture(); 272 | 273 | if (ImGui::InputFloat("End Size", &m_end_size)) 274 | update_size_over_time_texture(); 275 | 276 | if (ImGui::Bezier("Size Over Time", m_size_curve)) 277 | update_size_over_time_texture(); 278 | 279 | if (ImGui::GradientEditor("Color Over Time:", &m_color_gradient, m_dragging_mark, m_selected_mark)) 280 | update_color_over_time_texture(); 281 | 282 | ImGui::Checkbox("Show Grid", &m_show_grid); 283 | float sun_angle = m_sky_model.sun_angle(); 284 | ImGui::SliderAngle("Sun Angle", &sun_angle, 0.0f, -180.0f); 285 | m_sky_model.set_sun_angle(sun_angle); 286 | m_shadow_map.set_direction(m_sky_model.direction()); 287 | ImGui::InputFloat("Shadow Bias", &m_shadow_bias); 288 | } 289 | 290 | // ----------------------------------------------------------------------------------------------------------------------------------- 291 | 292 | void render_particles(std::unique_ptr& program, glm::mat4 view, glm::mat4 projection) 293 | { 294 | glEnable(GL_DEPTH_TEST); 295 | 296 | program->use(); 297 | 298 | program->set_uniform("u_Rotation", glm::radians(m_rotation)); 299 | program->set_uniform("u_View", view); 300 | program->set_uniform("u_Proj", projection); 301 | 302 | if (program->set_uniform("s_ColorOverTime", 0)) 303 | m_color_over_time->bind(0); 304 | 305 | if (program->set_uniform("s_SizeOverTime", 1)) 306 | m_size_over_time->bind(1); 307 | 308 | m_particle_data_ssbo->bind_base(0); 309 | m_alive_indices_ssbo[m_post_sim_idx]->bind_base(1); 310 | 311 | glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_draw_indirect_args_ssbo->handle()); 312 | 313 | glDrawArraysIndirect(GL_TRIANGLES, 0); 314 | } 315 | 316 | // ----------------------------------------------------------------------------------------------------------------------------------- 317 | 318 | void render_mesh(dw::Mesh* mesh, glm::mat4 model, std::unique_ptr& program) 319 | { 320 | program->set_uniform("u_Model", model); 321 | 322 | // Bind vertex array. 323 | mesh->mesh_vertex_array()->bind(); 324 | 325 | for (uint32_t i = 0; i < mesh->sub_mesh_count(); i++) 326 | { 327 | dw::SubMesh& submesh = mesh->sub_meshes()[i]; 328 | 329 | program->set_uniform("u_Color", glm::vec3(0.7f)); 330 | program->set_uniform("u_Direction", m_sky_model.direction()); 331 | program->set_uniform("u_LightColor", m_shadow_map.color()); 332 | 333 | // Issue draw call. 334 | glDrawElementsBaseVertex(GL_TRIANGLES, submesh.index_count, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * submesh.base_index), submesh.base_vertex); 335 | } 336 | } 337 | 338 | // ----------------------------------------------------------------------------------------------------------------------------------- 339 | 340 | void render_scene(std::unique_ptr& program) 341 | { 342 | // Bind shader program. 343 | program->use(); 344 | 345 | program->set_uniform("u_LightViewProj", m_shadow_map.projection() * m_shadow_map.view()); 346 | program->set_uniform("u_ViewProj", m_main_camera->m_view_projection); 347 | program->set_uniform("u_Bias", m_shadow_bias); 348 | 349 | if (program->set_uniform("s_ShadowMap", 0)) 350 | m_shadow_map.texture()->bind(0); 351 | 352 | // Draw scene. 353 | render_mesh(m_playground.get(), glm::mat4(1.0f), program); 354 | } 355 | 356 | // ----------------------------------------------------------------------------------------------------------------------------------- 357 | 358 | void render_lit_scene() 359 | { 360 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 361 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 362 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 363 | glViewport(0, 0, m_width, m_height); 364 | 365 | render_particles(m_particle_program, m_main_camera->m_view, m_main_camera->m_projection); 366 | render_scene(m_mesh_lit_program); 367 | } 368 | 369 | // ----------------------------------------------------------------------------------------------------------------------------------- 370 | 371 | void render_shadow_map() 372 | { 373 | m_shadow_map.begin_render(); 374 | 375 | render_particles(m_particle_depth_program, m_shadow_map.view(), m_shadow_map.projection()); 376 | 377 | m_mesh_depth_program->use(); 378 | m_mesh_depth_program->set_uniform("u_ViewProj", m_shadow_map.projection() * m_shadow_map.view()); 379 | render_mesh(m_playground.get(), glm::mat4(1.0f), m_mesh_depth_program); 380 | 381 | m_shadow_map.end_render(); 382 | } 383 | 384 | // ----------------------------------------------------------------------------------------------------------------------------------- 385 | 386 | void render_depth_prepass() 387 | { 388 | m_scene_depth_fbo->bind(); 389 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 390 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 391 | glViewport(0, 0, m_width, m_height); 392 | 393 | render_scene(m_depth_prepass_program); 394 | } 395 | 396 | // ----------------------------------------------------------------------------------------------------------------------------------- 397 | 398 | void particle_initialize() 399 | { 400 | m_particle_initialize_program->use(); 401 | 402 | m_dead_indices_ssbo->bind_base(0); 403 | m_counters_ssbo->bind_base(1); 404 | 405 | m_particle_initialize_program->set_uniform("u_MaxParticles", MAX_PARTICLES); 406 | 407 | glDispatchCompute(ceil(float(MAX_PARTICLES) / float(LOCAL_SIZE)), 1, 1); 408 | 409 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 410 | } 411 | 412 | // ----------------------------------------------------------------------------------------------------------------------------------- 413 | 414 | void particle_kickoff() 415 | { 416 | m_particle_update_kickoff_program->use(); 417 | 418 | m_emission_delta = 1.0f / float(m_emission_rate); // In seconds 419 | 420 | m_particles_per_frame = 0; 421 | 422 | while (m_accumulator >= m_emission_delta) 423 | { 424 | m_accumulator -= m_emission_delta; 425 | m_particles_per_frame++; 426 | } 427 | 428 | m_particle_update_kickoff_program->set_uniform("u_ParticlesPerFrame", m_particles_per_frame); 429 | m_particle_update_kickoff_program->set_uniform("u_PreSimIdx", m_pre_sim_idx); 430 | m_particle_update_kickoff_program->set_uniform("u_PostSimIdx", m_post_sim_idx); 431 | 432 | m_particle_data_ssbo->bind_base(0); 433 | m_dispatch_emission_indirect_args_ssbo->bind_base(1); 434 | m_dispatch_simulation_indirect_args_ssbo->bind_base(2); 435 | m_draw_indirect_args_ssbo->bind_base(3); 436 | m_counters_ssbo->bind_base(4); 437 | 438 | glDispatchCompute(1, 1, 1); 439 | 440 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 441 | } 442 | 443 | // ----------------------------------------------------------------------------------------------------------------------------------- 444 | 445 | void particle_emission() 446 | { 447 | m_particle_emission_program->use(); 448 | 449 | m_particle_emission_program->set_uniform("u_Seeds", m_seeds); 450 | m_particle_emission_program->set_uniform("u_Position", m_position); 451 | m_particle_emission_program->set_uniform("u_MinInitialSpeed", m_min_initial_speed); 452 | m_particle_emission_program->set_uniform("u_MaxInitialSpeed", m_max_initial_speed); 453 | m_particle_emission_program->set_uniform("u_MinLifetime", m_max_lifetime); 454 | m_particle_emission_program->set_uniform("u_MaxLifetime", m_max_lifetime); 455 | m_particle_emission_program->set_uniform("u_EmissionShape", int(m_emission_shape)); 456 | m_particle_emission_program->set_uniform("u_DirectionType", int(m_direction_type)); 457 | m_particle_emission_program->set_uniform("u_Direction", m_direction); 458 | m_particle_emission_program->set_uniform("u_SphereRadius", m_sphere_radius); 459 | m_particle_emission_program->set_uniform("u_PreSimIdx", m_pre_sim_idx); 460 | 461 | m_particle_data_ssbo->bind_base(0); 462 | m_dead_indices_ssbo->bind_base(1); 463 | m_alive_indices_ssbo[m_pre_sim_idx]->bind_base(2); 464 | m_counters_ssbo->bind_base(3); 465 | 466 | glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, m_dispatch_emission_indirect_args_ssbo->handle()); 467 | 468 | glDispatchComputeIndirect(0); 469 | 470 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 471 | } 472 | 473 | // ----------------------------------------------------------------------------------------------------------------------------------- 474 | 475 | void particle_simulation() 476 | { 477 | m_particle_simulation_program->use(); 478 | 479 | m_particle_simulation_program->set_uniform("u_DeltaTime", float(m_delta_seconds)); 480 | m_particle_simulation_program->set_uniform("u_Viscosity", m_viscosity); 481 | m_particle_simulation_program->set_uniform("u_PreSimIdx", m_pre_sim_idx); 482 | m_particle_simulation_program->set_uniform("u_PostSimIdx", m_post_sim_idx); 483 | m_particle_simulation_program->set_uniform("u_ConstantVelocity", m_constant_velocity); 484 | m_particle_simulation_program->set_uniform("u_AffectedByGravity", (int)m_affected_by_gravity); 485 | m_particle_simulation_program->set_uniform("u_DepthBufferCollision", (int)m_depth_buffer_collision); 486 | m_particle_simulation_program->set_uniform("u_Restitution", m_restitution); 487 | m_particle_simulation_program->set_uniform("u_ViewProj", m_main_camera->m_view_projection); 488 | 489 | if (m_particle_simulation_program->set_uniform("s_Depth", 0)) 490 | m_scene_depth_rt->bind(0); 491 | 492 | if (m_particle_simulation_program->set_uniform("s_Normals", 1)) 493 | m_scene_normals_rt->bind(1); 494 | 495 | m_particle_data_ssbo->bind_base(0); 496 | m_dead_indices_ssbo->bind_base(1); 497 | m_alive_indices_ssbo[m_pre_sim_idx]->bind_base(2); 498 | m_alive_indices_ssbo[m_post_sim_idx]->bind_base(3); 499 | m_draw_indirect_args_ssbo->bind_base(4); 500 | m_counters_ssbo->bind_base(5); 501 | 502 | glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, m_dispatch_simulation_indirect_args_ssbo->handle()); 503 | 504 | glDispatchComputeIndirect(0); 505 | 506 | glMemoryBarrier(GL_ALL_BARRIER_BITS); 507 | } 508 | 509 | // ----------------------------------------------------------------------------------------------------------------------------------- 510 | 511 | void load_mesh() 512 | { 513 | m_playground = dw::Mesh::load("Particle_Playground.obj"); 514 | } 515 | 516 | // ----------------------------------------------------------------------------------------------------------------------------------- 517 | 518 | bool create_shaders() 519 | { 520 | { 521 | // Create general shaders 522 | m_particle_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/particle_vs.glsl")); 523 | m_particle_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/particle_fs.glsl")); 524 | m_particle_initialize_cs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/particle_initialize_cs.glsl")); 525 | m_particle_update_kickoff_cs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/particle_update_kickoff_cs.glsl")); 526 | m_particle_emission_cs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/particle_emission_cs.glsl")); 527 | m_particle_simulation_cs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/particle_simulation_cs.glsl")); 528 | m_mesh_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/mesh_vs.glsl")); 529 | m_mesh_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/mesh_fs.glsl")); 530 | m_depth_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/depth_fs.glsl")); 531 | m_depth_prepass_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/depth_prepass_fs.glsl")); 532 | 533 | { 534 | if (!m_particle_vs || !m_particle_fs) 535 | { 536 | DW_LOG_FATAL("Failed to create Shaders"); 537 | return false; 538 | } 539 | 540 | // Create general shader program 541 | dw::gl::Shader* shaders[] = { m_particle_vs.get(), m_particle_fs.get() }; 542 | m_particle_program = std::make_unique(2, shaders); 543 | 544 | if (!m_particle_program) 545 | { 546 | DW_LOG_FATAL("Failed to create Shader Program"); 547 | return false; 548 | } 549 | } 550 | 551 | { 552 | if (!m_particle_vs || !m_depth_fs) 553 | { 554 | DW_LOG_FATAL("Failed to create Shaders"); 555 | return false; 556 | } 557 | 558 | // Create general shader program 559 | dw::gl::Shader* shaders[] = { m_particle_vs.get(), m_depth_fs.get() }; 560 | m_particle_depth_program = std::make_unique(2, shaders); 561 | 562 | if (!m_particle_depth_program) 563 | { 564 | DW_LOG_FATAL("Failed to create Shader Program"); 565 | return false; 566 | } 567 | } 568 | 569 | { 570 | if (!m_mesh_vs || !m_mesh_fs) 571 | { 572 | DW_LOG_FATAL("Failed to create Shaders"); 573 | return false; 574 | } 575 | 576 | // Create general shader program 577 | dw::gl::Shader* shaders[] = { m_mesh_vs.get(), m_mesh_fs.get() }; 578 | m_mesh_lit_program = std::make_unique(2, shaders); 579 | 580 | if (!m_mesh_lit_program) 581 | { 582 | DW_LOG_FATAL("Failed to create Shader Program"); 583 | return false; 584 | } 585 | } 586 | 587 | { 588 | if (!m_mesh_vs || !m_depth_prepass_fs) 589 | { 590 | DW_LOG_FATAL("Failed to create Shaders"); 591 | return false; 592 | } 593 | 594 | // Create general shader program 595 | dw::gl::Shader* shaders[] = { m_mesh_vs.get(), m_depth_prepass_fs.get() }; 596 | m_depth_prepass_program = std::make_unique(2, shaders); 597 | 598 | if (!m_depth_prepass_program) 599 | { 600 | DW_LOG_FATAL("Failed to create Shader Program"); 601 | return false; 602 | } 603 | } 604 | 605 | { 606 | if (!m_mesh_vs || !m_depth_fs) 607 | { 608 | DW_LOG_FATAL("Failed to create Shaders"); 609 | return false; 610 | } 611 | 612 | // Create general shader program 613 | dw::gl::Shader* shaders[] = { m_mesh_vs.get(), m_depth_fs.get() }; 614 | m_mesh_depth_program = std::make_unique(2, shaders); 615 | 616 | if (!m_mesh_depth_program) 617 | { 618 | DW_LOG_FATAL("Failed to create Shader Program"); 619 | return false; 620 | } 621 | } 622 | 623 | { 624 | if (!m_particle_initialize_cs) 625 | { 626 | DW_LOG_FATAL("Failed to create Shaders"); 627 | return false; 628 | } 629 | 630 | // Create general shader program 631 | dw::gl::Shader* shaders[] = { m_particle_initialize_cs.get() }; 632 | m_particle_initialize_program = std::make_unique(1, shaders); 633 | 634 | if (!m_particle_initialize_program) 635 | { 636 | DW_LOG_FATAL("Failed to create Shader Program"); 637 | return false; 638 | } 639 | } 640 | 641 | { 642 | if (!m_particle_update_kickoff_cs) 643 | { 644 | DW_LOG_FATAL("Failed to create Shaders"); 645 | return false; 646 | } 647 | 648 | // Create general shader program 649 | dw::gl::Shader* shaders[] = { m_particle_update_kickoff_cs.get() }; 650 | m_particle_update_kickoff_program = std::make_unique(1, shaders); 651 | 652 | if (!m_particle_update_kickoff_program) 653 | { 654 | DW_LOG_FATAL("Failed to create Shader Program"); 655 | return false; 656 | } 657 | } 658 | 659 | { 660 | if (!m_particle_emission_cs) 661 | { 662 | DW_LOG_FATAL("Failed to create Shaders"); 663 | return false; 664 | } 665 | 666 | // Create general shader program 667 | dw::gl::Shader* shaders[] = { m_particle_emission_cs.get() }; 668 | m_particle_emission_program = std::make_unique(1, shaders); 669 | 670 | if (!m_particle_emission_program) 671 | { 672 | DW_LOG_FATAL("Failed to create Shader Program"); 673 | return false; 674 | } 675 | } 676 | 677 | { 678 | if (!m_particle_simulation_cs) 679 | { 680 | DW_LOG_FATAL("Failed to create Shaders"); 681 | return false; 682 | } 683 | 684 | // Create general shader program 685 | dw::gl::Shader* shaders[] = { m_particle_simulation_cs.get() }; 686 | m_particle_simulation_program = std::make_unique(1, shaders); 687 | 688 | if (!m_particle_simulation_program) 689 | { 690 | DW_LOG_FATAL("Failed to create Shader Program"); 691 | return false; 692 | } 693 | } 694 | } 695 | 696 | return true; 697 | } 698 | 699 | // ----------------------------------------------------------------------------------------------------------------------------------- 700 | 701 | bool create_buffers() 702 | { 703 | struct Particle 704 | { 705 | glm::vec4 lifetime; 706 | glm::vec4 velocity; 707 | glm::vec4 position; 708 | glm::vec4 color; 709 | }; 710 | 711 | m_draw_indirect_args_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * 4, nullptr); 712 | m_dispatch_emission_indirect_args_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * 3, nullptr); 713 | m_dispatch_simulation_indirect_args_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * 3, nullptr); 714 | m_particle_data_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(Particle) * MAX_PARTICLES, nullptr); 715 | m_alive_indices_ssbo[0] = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * MAX_PARTICLES, nullptr); 716 | m_alive_indices_ssbo[1] = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * MAX_PARTICLES, nullptr); 717 | m_dead_indices_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * MAX_PARTICLES, nullptr); 718 | m_counters_ssbo = std::make_unique(GL_STATIC_DRAW, sizeof(int32_t) * 5, nullptr); 719 | 720 | return true; 721 | } 722 | 723 | // ----------------------------------------------------------------------------------------------------------------------------------- 724 | 725 | void create_framebuffers() 726 | { 727 | m_scene_depth_rt = std::make_unique(m_width, m_height, 1, 1, 1, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); 728 | m_scene_normals_rt = std::make_unique(m_width, m_height, 1, 1, 1, GL_RGB32F, GL_RGB, GL_FLOAT); 729 | 730 | m_scene_depth_fbo = std::make_unique(); 731 | m_scene_depth_fbo->attach_render_target(0, m_scene_normals_rt.get(), 0, 0); 732 | m_scene_depth_fbo->attach_depth_stencil_target(m_scene_depth_rt.get(), 0, 0); 733 | } 734 | 735 | // ----------------------------------------------------------------------------------------------------------------------------------- 736 | 737 | void create_textures() 738 | { 739 | m_color_over_time = std::make_unique(GRADIENT_SAMPLES, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE); 740 | m_size_over_time = std::make_unique(GRADIENT_SAMPLES, 1, 1, GL_R32F, GL_RED, GL_FLOAT); 741 | 742 | m_color_over_time->set_min_filter(GL_NEAREST); 743 | m_size_over_time->set_min_filter(GL_NEAREST); 744 | 745 | m_color_over_time->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 746 | m_size_over_time->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 747 | } 748 | 749 | // ----------------------------------------------------------------------------------------------------------------------------------- 750 | 751 | void update_color_over_time_texture() 752 | { 753 | float delta = 1.0f / float(GRADIENT_SAMPLES); 754 | float x = 0.0f; 755 | 756 | std::vector samples; 757 | 758 | for (uint32_t i = 0; i < GRADIENT_SAMPLES; i++) 759 | { 760 | glm::vec4 color; 761 | m_color_gradient.getColorAt(x, &color.x); 762 | 763 | samples.push_back(color.x * 255.0f); 764 | samples.push_back(color.y * 255.0f); 765 | samples.push_back(color.z * 255.0f); 766 | samples.push_back(color.w * 255.0f); 767 | 768 | x += delta; 769 | } 770 | 771 | m_color_over_time->set_data(0, 0, samples.data()); 772 | } 773 | 774 | // ----------------------------------------------------------------------------------------------------------------------------------- 775 | 776 | void update_size_over_time_texture() 777 | { 778 | float delta = 1.0f / float(GRADIENT_SAMPLES); 779 | float x = 0.0f; 780 | float size_diff = m_end_size - m_start_size; 781 | 782 | std::vector samples; 783 | 784 | for (uint32_t i = 0; i < GRADIENT_SAMPLES; i++) 785 | { 786 | float size = m_start_size + ImGui::BezierValue(x, m_size_curve) * size_diff; 787 | samples.push_back(size); 788 | 789 | x += delta; 790 | } 791 | 792 | m_size_over_time->set_data(0, 0, samples.data()); 793 | } 794 | 795 | // ----------------------------------------------------------------------------------------------------------------------------------- 796 | 797 | void create_camera() 798 | { 799 | m_main_camera = std::make_unique(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height), glm::vec3(10.0f, 5.0f, 5.0f), glm::vec3(-1.0f, 0.0, 0.0f)); 800 | m_main_camera->set_rotatation_delta(glm::vec3(0.0f, -90.0f, 0.0f)); 801 | m_main_camera->update(); 802 | } 803 | 804 | // ----------------------------------------------------------------------------------------------------------------------------------- 805 | 806 | void update_transforms(dw::Camera* camera) 807 | { 808 | // Update camera matrices. 809 | m_global_uniforms.view_proj = camera->m_projection * camera->m_view; 810 | } 811 | 812 | // ----------------------------------------------------------------------------------------------------------------------------------- 813 | 814 | void update_camera() 815 | { 816 | dw::Camera* current = m_main_camera.get(); 817 | 818 | float forward_delta = m_heading_speed * m_delta; 819 | float right_delta = m_sideways_speed * m_delta; 820 | 821 | current->set_translation_delta(current->m_forward, forward_delta); 822 | current->set_translation_delta(current->m_right, right_delta); 823 | 824 | m_camera_x = m_mouse_delta_x * m_camera_sensitivity; 825 | m_camera_y = m_mouse_delta_y * m_camera_sensitivity; 826 | 827 | if (m_mouse_look) 828 | { 829 | // Activate Mouse Look 830 | current->set_rotatation_delta(glm::vec3((float)(m_camera_y), 831 | (float)(m_camera_x), 832 | (float)(0.0f))); 833 | } 834 | else 835 | { 836 | current->set_rotatation_delta(glm::vec3((float)(0), 837 | (float)(0), 838 | (float)(0))); 839 | } 840 | 841 | current->update(); 842 | update_transforms(current); 843 | } 844 | 845 | // ----------------------------------------------------------------------------------------------------------------------------------- 846 | 847 | private: 848 | std::unique_ptr m_particle_vs; 849 | std::unique_ptr m_particle_fs; 850 | std::unique_ptr m_particle_initialize_cs; 851 | std::unique_ptr m_particle_update_kickoff_cs; 852 | std::unique_ptr m_particle_emission_cs; 853 | std::unique_ptr m_particle_simulation_cs; 854 | std::unique_ptr m_mesh_vs; 855 | std::unique_ptr m_mesh_fs; 856 | std::unique_ptr m_depth_fs; 857 | std::unique_ptr m_depth_prepass_fs; 858 | 859 | std::unique_ptr m_particle_program; 860 | std::unique_ptr m_particle_initialize_program; 861 | std::unique_ptr m_particle_update_kickoff_program; 862 | std::unique_ptr m_particle_emission_program; 863 | std::unique_ptr m_particle_simulation_program; 864 | std::unique_ptr m_mesh_lit_program; 865 | std::unique_ptr m_mesh_depth_program; 866 | std::unique_ptr m_particle_depth_program; 867 | std::unique_ptr m_depth_prepass_program; 868 | 869 | std::unique_ptr m_draw_indirect_args_ssbo; 870 | std::unique_ptr m_dispatch_emission_indirect_args_ssbo; 871 | std::unique_ptr m_dispatch_simulation_indirect_args_ssbo; 872 | std::unique_ptr m_particle_data_ssbo; 873 | std::unique_ptr m_alive_indices_ssbo[2]; 874 | std::unique_ptr m_dead_indices_ssbo; 875 | std::unique_ptr m_counters_ssbo; 876 | 877 | std::unique_ptr m_scene_depth_rt; 878 | std::unique_ptr m_scene_normals_rt; 879 | std::unique_ptr m_scene_depth_fbo; 880 | 881 | std::unique_ptr m_size_over_time; 882 | std::unique_ptr m_color_over_time; 883 | 884 | std::unique_ptr m_main_camera; 885 | 886 | dw::BrunetonSkyModel m_sky_model; 887 | dw::ShadowMap m_shadow_map; 888 | dw::Mesh::Ptr m_playground; 889 | 890 | GlobalUniforms m_global_uniforms; 891 | 892 | // Camera controls. 893 | bool m_debug_gui = true; 894 | bool m_mouse_look = false; 895 | bool m_show_grid = true; 896 | float m_heading_speed = 0.0f; 897 | float m_sideways_speed = 0.0f; 898 | float m_camera_sensitivity = 0.05f; 899 | float m_camera_speed = 0.005f; 900 | 901 | // Camera orientation. 902 | float m_camera_x; 903 | float m_camera_y; 904 | 905 | // Particle settings 906 | int32_t m_max_active_particles = 0; // Max Lifetime * Emission Rate 907 | int32_t m_emission_rate = 250; // Particles per second 908 | float m_min_lifetime = 2.0f; // Seconds 909 | float m_max_lifetime = 2.5f; // Seconds 910 | float m_min_initial_speed = 1.0f; 911 | float m_max_initial_speed = 4.0f; 912 | float m_start_size = 0.01f; // Seconds 913 | float m_end_size = 0.005f; // Seconds 914 | bool m_affected_by_gravity = true; 915 | bool m_depth_buffer_collision = true; 916 | glm::vec3 m_position = glm::vec3(0.0f); 917 | glm::mat4 m_position_transform = glm::mat4(1.0f); 918 | glm::vec3 m_direction = glm::vec3(0.0f, 1.0f, 0.0f); 919 | glm::vec3 m_constant_velocity = glm::vec3(0.0f); 920 | float m_rotation = 0.0f; 921 | int32_t m_pre_sim_idx = 0; 922 | int32_t m_post_sim_idx = 1; 923 | float m_accumulator = 0.0f; 924 | float m_emission_delta = 0.0f; 925 | float m_viscosity = 0.0f; 926 | float m_restitution = 0.5f; 927 | int32_t m_particles_per_frame = 0; 928 | EmissionShape m_emission_shape = EMISSION_SHAPE_SPHERE; 929 | DirectionType m_direction_type = DIRECTION_TYPE_OUTWARDS; 930 | float m_sphere_radius = 0.1f; 931 | float m_shadow_bias = 0.00001f; 932 | 933 | // Random 934 | glm::vec3 m_seeds = glm::vec4(0.0f); 935 | std::random_device m_random; 936 | std::mt19937 m_generator; 937 | 938 | // UI 939 | ImGradient m_color_gradient; 940 | float m_size_curve[5] = { 0.000f, 0.000f, 1.000f, 1.000f, 0.0f }; 941 | ImGradientMark* m_dragging_mark = nullptr; 942 | ImGradientMark* m_selected_mark = nullptr; 943 | }; 944 | 945 | DW_DECLARE_MAIN(GPUParticleSystem) -------------------------------------------------------------------------------- /src/shader/curl_noise.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | vec3 curl_noise(vec3 coord) 4 | { 5 | vec3 dx = vec3(EPSILON, 0.0, 0.0); 6 | vec3 dy = vec3(0.0, EPSILON, 0.0); 7 | vec3 dz = vec3(0.0, 0.0, EPSILON); 8 | 9 | vec3 dpdx0 = vec3(snoise(coord - dx)); 10 | vec3 dpdx1 = vec3(snoise(coord + dx)); 11 | vec3 dpdy0 = vec3(snoise(coord - dy)); 12 | vec3 dpdy1 = vec3(snoise(coord + dy)); 13 | vec3 dpdz0 = vec3(snoise(coord - dz)); 14 | vec3 dpdz1 = vec3(snoise(coord + dz)); 15 | 16 | float x = dpdy1.z - dpdy0.z + dpdz1.y - dpdz0.y; 17 | float y = dpdz1.x - dpdz0.x + dpdx1.z - dpdx0.z; 18 | float z = dpdx1.y - dpdx0.y + dpdy1.x - dpdy0.x; 19 | 20 | return vec3(x, y, z) / EPSILON * 2.0; 21 | } -------------------------------------------------------------------------------- /src/shader/depth_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // MAIN ------------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | void main() 6 | { 7 | } 8 | 9 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/depth_prepass_fs.glsl: -------------------------------------------------------------------------------- 1 | out vec3 FS_OUT_Normal; 2 | 3 | in vec3 FS_IN_Normal; 4 | 5 | // ------------------------------------------------------------------ 6 | // MAIN ------------------------------------------------------------- 7 | // ------------------------------------------------------------------ 8 | 9 | void main() 10 | { 11 | FS_OUT_Normal = FS_IN_Normal; 12 | } 13 | 14 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /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 | 14 | // ------------------------------------------------------------------ 15 | // UNIFORMS --------------------------------------------------------- 16 | // ------------------------------------------------------------------ 17 | 18 | uniform mat4 u_LightViewProj; 19 | uniform vec3 u_LightColor; 20 | uniform vec3 u_Color; 21 | uniform vec3 u_Direction; 22 | uniform float u_Bias; 23 | uniform sampler2D s_ShadowMap; 24 | 25 | // ------------------------------------------------------------------ 26 | 27 | float shadow_occlussion(vec3 p) 28 | { 29 | // Transform frag position into Light-space. 30 | vec4 light_space_pos = u_LightViewProj * vec4(p, 1.0); 31 | 32 | vec3 proj_coords = light_space_pos.xyz / light_space_pos.w; 33 | // transform to [0,1] range 34 | proj_coords = proj_coords * 0.5 + 0.5; 35 | // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) 36 | float closest_depth = texture(s_ShadowMap, proj_coords.xy).r; 37 | // get depth of current fragment from light's perspective 38 | float current_depth = proj_coords.z; 39 | // check whether current frag pos is in shadow 40 | float bias = u_Bias; 41 | float shadow = current_depth - bias > closest_depth ? 1.0 : 0.0; 42 | 43 | return 1.0 - shadow; 44 | } 45 | 46 | // ------------------------------------------------------------------ 47 | // MAIN ------------------------------------------------------------- 48 | // ------------------------------------------------------------------ 49 | 50 | void main() 51 | { 52 | float shadow = shadow_occlussion(FS_IN_WorldPos); 53 | 54 | vec3 L = normalize(-u_Direction); 55 | vec3 N = normalize(FS_IN_Normal); 56 | 57 | FS_OUT_Color = u_Color * clamp(dot(N, L), 0.0, 1.0) * shadow + u_Color * 0.1; 58 | } 59 | 60 | // ------------------------------------------------------------------ 61 | -------------------------------------------------------------------------------- /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 | 18 | // ------------------------------------------------------------------ 19 | // UNIFORMS --------------------------------------------------------- 20 | // ------------------------------------------------------------------ 21 | 22 | uniform mat4 u_ViewProj; 23 | uniform mat4 u_Model; 24 | 25 | // ------------------------------------------------------------------ 26 | // MAIN ------------------------------------------------------------- 27 | // ------------------------------------------------------------------ 28 | 29 | void main() 30 | { 31 | vec4 world_pos = u_Model * vec4(VS_IN_Position, 1.0f); 32 | FS_IN_WorldPos = world_pos.xyz; 33 | FS_IN_Normal = normalize(normalize(mat3(u_Model) * VS_IN_Normal)); 34 | 35 | gl_Position = u_ViewProj * world_pos; 36 | } 37 | 38 | // ------------------------------------------------------------------ 39 | -------------------------------------------------------------------------------- /src/shader/particle_emission_cs.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // ------------------------------------------------------------------ 4 | // CONSTANTS --------------------------------------------------------- 5 | // ------------------------------------------------------------------ 6 | 7 | #define LOCAL_SIZE 32 8 | #define EMISSION_SHAPE_SPHERE 0 9 | #define EMISSION_SHAPE_BOX 1 10 | #define EMISSION_SHAPE_CONE 2 11 | #define DIRECTION_TYPE_SINGLE 0 12 | #define DIRECTION_TYPE_OUTWARD 1 13 | 14 | // ------------------------------------------------------------------ 15 | // INPUTS ----------------------------------------------------------- 16 | // ------------------------------------------------------------------ 17 | 18 | layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in; 19 | 20 | // ------------------------------------------------------------------ 21 | // UNIFORMS --------------------------------------------------------- 22 | // ------------------------------------------------------------------ 23 | 24 | struct Particle 25 | { 26 | vec2 lifetime; 27 | vec3 velocity; 28 | vec3 position; 29 | vec4 color; 30 | }; 31 | 32 | uniform vec3 u_Seeds; 33 | uniform vec3 u_Position; 34 | uniform float u_MinInitialSpeed; 35 | uniform float u_MaxInitialSpeed; 36 | uniform float u_MinLifetime; 37 | uniform float u_MaxLifetime; 38 | uniform vec3 u_Direction; 39 | uniform int u_EmissionShape; 40 | uniform int u_DirectionType; 41 | uniform float u_SphereRadius; 42 | uniform int u_PreSimIdx; 43 | 44 | layout(std430, binding = 0) buffer ParticleData_t 45 | { 46 | Particle particles[]; 47 | } 48 | ParticleData; 49 | 50 | layout(std430, binding = 1) buffer ParticleDeadIndices_t 51 | { 52 | uint indices[]; 53 | } 54 | DeadIndices; 55 | 56 | layout(std430, binding = 2) buffer ParticleAlivePreSimIndices_t 57 | { 58 | uint indices[]; 59 | } 60 | AliveIndicesPreSim; 61 | 62 | layout(std430, binding = 3) buffer Counters_t 63 | { 64 | uint dead_count; 65 | uint alive_count[2]; 66 | uint simulation_count; 67 | uint emission_count; 68 | } 69 | Counters; 70 | 71 | // ------------------------------------------------------------------ 72 | // FUNCTIONS -------------------------------------------------------- 73 | // ------------------------------------------------------------------ 74 | 75 | uint pop_dead_index() 76 | { 77 | uint index = atomicAdd(Counters.dead_count, -1); 78 | return DeadIndices.indices[index - 1]; 79 | } 80 | 81 | void push_alive_index(uint index) 82 | { 83 | uint insert_idx = atomicAdd(Counters.alive_count[u_PreSimIdx], 1); 84 | AliveIndicesPreSim.indices[insert_idx] = index; 85 | } 86 | 87 | // ------------------------------------------------------------------ 88 | // MAIN ------------------------------------------------------------- 89 | // ------------------------------------------------------------------ 90 | 91 | void main() 92 | { 93 | uint index = gl_GlobalInvocationID.x; 94 | 95 | if (index < Counters.emission_count) 96 | { 97 | uint particle_index = pop_dead_index(); 98 | 99 | vec3 position = u_Position; 100 | 101 | if (u_EmissionShape == EMISSION_SHAPE_SPHERE) 102 | position += randomPointOnSphere(rand(u_Seeds.xyz / (index + 1)), rand(u_Seeds.yzx / (index + 1)), u_SphereRadius * rand(u_Seeds.zyx / (index + 1))); 103 | else if (u_EmissionShape == EMISSION_SHAPE_BOX) 104 | position += vec3(0.0); 105 | else if (u_EmissionShape == EMISSION_SHAPE_CONE) 106 | position += vec3(0.0); 107 | 108 | vec3 direction = u_Direction; 109 | 110 | if (u_DirectionType == DIRECTION_TYPE_OUTWARD) 111 | direction = normalize(position - u_Position); 112 | 113 | float initial_speed = u_MinInitialSpeed + (u_MaxInitialSpeed - u_MinInitialSpeed) * rand(u_Seeds.xzy / (index + 1)); 114 | float lifetime = u_MinLifetime + (u_MaxLifetime - u_MinLifetime) * rand(u_Seeds.zyx / (index + 1)); 115 | 116 | ParticleData.particles[particle_index].position.xyz = position; 117 | ParticleData.particles[particle_index].velocity.xyz = direction * initial_speed; 118 | ParticleData.particles[particle_index].lifetime.xy = vec2(0.0, lifetime); 119 | 120 | push_alive_index(particle_index); 121 | } 122 | } -------------------------------------------------------------------------------- /src/shader/particle_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUTS ----------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | in vec4 FS_IN_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // OUTPUTS ---------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | out vec4 FS_OUT_FragColor; 12 | 13 | // ------------------------------------------------------------------ 14 | // MAIN ------------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | void main() 18 | { 19 | FS_OUT_FragColor = FS_IN_Color; 20 | } 21 | 22 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/particle_initialize_cs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // CONSTANTS --------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | #define LOCAL_SIZE 32 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUTS ----------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in; 12 | 13 | // ------------------------------------------------------------------ 14 | // UNIFORMS --------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | layout(std430, binding = 0) buffer ParticleDeadIndices_t 18 | { 19 | uint indices[]; 20 | } 21 | DeadIndices; 22 | 23 | layout(std430, binding = 1) buffer ParticleCounters_t 24 | { 25 | uint dead_count; 26 | uint alive_count[2]; 27 | uint simulation_count; 28 | uint emission_count; 29 | } 30 | Counters; 31 | 32 | uniform int u_MaxParticles; 33 | 34 | // ------------------------------------------------------------------ 35 | // MAIN ------------------------------------------------------------- 36 | // ------------------------------------------------------------------ 37 | 38 | void main() 39 | { 40 | uint index = gl_GlobalInvocationID.x; 41 | 42 | if (index == 0) 43 | { 44 | // Initialize counts 45 | Counters.dead_count = u_MaxParticles; 46 | Counters.alive_count[0] = 0; 47 | Counters.alive_count[1] = 0; 48 | } 49 | 50 | if (index < u_MaxParticles) 51 | DeadIndices.indices[index] = index; 52 | } -------------------------------------------------------------------------------- /src/shader/particle_simulation_cs.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // ------------------------------------------------------------------ 4 | // CONSTANTS --------------------------------------------------------- 5 | // ------------------------------------------------------------------ 6 | 7 | #define LOCAL_SIZE 32 8 | #define CAMERA_NEAR_PLANE 0.1 9 | #define CAMERA_FAR_PLANE 1000.0 10 | #define MIN_THICKNESS 0.001 11 | 12 | // ------------------------------------------------------------------ 13 | // INPUTS ----------------------------------------------------------- 14 | // ------------------------------------------------------------------ 15 | 16 | layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in; 17 | 18 | // ------------------------------------------------------------------ 19 | // UNIFORMS --------------------------------------------------------- 20 | // ------------------------------------------------------------------ 21 | 22 | struct Particle 23 | { 24 | vec4 lifetime; 25 | vec4 velocity; 26 | vec4 position; 27 | vec4 color; 28 | }; 29 | 30 | layout(std430, binding = 0) buffer ParticleData_t 31 | { 32 | Particle particles[]; 33 | } 34 | ParticleData; 35 | 36 | layout(std430, binding = 1) buffer ParticleDeadIndices_t 37 | { 38 | uint indices[]; 39 | } 40 | DeadIndices; 41 | 42 | layout(std430, binding = 2) buffer ParticleAlivePreSimIndices_t 43 | { 44 | uint indices[]; 45 | } 46 | AliveIndicesPreSim; 47 | 48 | layout(std430, binding = 3) buffer ParticleAlivePostSimIndices_t 49 | { 50 | uint indices[]; 51 | } 52 | AliveIndicesPostSim; 53 | 54 | layout(std430, binding = 4) buffer ParticleDrawArgs_t 55 | { 56 | uint count; 57 | uint instance_count; 58 | uint first; 59 | uint base_instance; 60 | } 61 | ParticleDrawArgs; 62 | 63 | layout(std430, binding = 5) buffer ParticleCounters_t 64 | { 65 | uint dead_count; 66 | uint alive_count[2]; 67 | uint simulation_count; 68 | uint emission_count; 69 | } 70 | Counters; 71 | 72 | uniform mat4 u_ViewProj; 73 | uniform float u_DeltaTime; 74 | uniform int u_PreSimIdx; 75 | uniform int u_PostSimIdx; 76 | uniform float u_Viscosity; 77 | uniform float u_Restitution; 78 | uniform vec3 u_ConstantVelocity; 79 | uniform int u_AffectedByGravity; 80 | uniform int u_DepthBufferCollision; 81 | 82 | uniform sampler2D s_Depth; 83 | uniform sampler2D s_Normals; 84 | 85 | // ------------------------------------------------------------------ 86 | // FUNCTIONS -------------------------------------------------------- 87 | // ------------------------------------------------------------------ 88 | 89 | void push_dead_index(uint index) 90 | { 91 | uint insert_idx = atomicAdd(Counters.dead_count, 1); 92 | DeadIndices.indices[insert_idx] = index; 93 | } 94 | 95 | uint pop_dead_index() 96 | { 97 | uint index = atomicAdd(Counters.dead_count, -1); 98 | return DeadIndices.indices[index - 1]; 99 | } 100 | 101 | void push_alive_index(uint index) 102 | { 103 | uint insert_idx = atomicAdd(Counters.alive_count[u_PostSimIdx], 1); 104 | AliveIndicesPostSim.indices[insert_idx] = index; 105 | } 106 | 107 | uint pop_alive_index() 108 | { 109 | uint index = atomicAdd(Counters.alive_count[u_PreSimIdx], -1); 110 | return AliveIndicesPreSim.indices[index - 1]; 111 | } 112 | 113 | float exp_01_to_linear_01_depth(float z, float n, float f) 114 | { 115 | float z_buffer_params_y = f / n; 116 | float z_buffer_params_x = 1.0 - z_buffer_params_y; 117 | 118 | return 1.0 / (z_buffer_params_x * z + z_buffer_params_y); 119 | } 120 | 121 | 122 | // ------------------------------------------------------------------ 123 | // MAIN ------------------------------------------------------------- 124 | // ------------------------------------------------------------------ 125 | 126 | void main() 127 | { 128 | uint index = gl_GlobalInvocationID.x; 129 | 130 | if (index < Counters.simulation_count) 131 | { 132 | // Consume an Alive particle index 133 | uint particle_index = pop_alive_index(); 134 | 135 | Particle particle = ParticleData.particles[particle_index]; 136 | 137 | // Is it dead? 138 | if (particle.lifetime.x >= particle.lifetime.y) 139 | { 140 | // If dead, just append into the DeadIndices list 141 | push_dead_index(particle_index); 142 | } 143 | else 144 | { 145 | // If still alive, increment lifetime and run simulation 146 | particle.lifetime.x += u_DeltaTime; 147 | 148 | if (u_AffectedByGravity == 1) 149 | particle.velocity.xyz += vec3(0.0, -9.8, 0.0) * u_DeltaTime; 150 | 151 | if (u_DepthBufferCollision == 1) 152 | { 153 | vec4 position = u_ViewProj * vec4(particle.position.xyz, 1.0); 154 | position.xyz /= position.w; 155 | 156 | vec2 tex_coord = position.xy * 0.5 + vec2(0.5); 157 | 158 | vec3 surface_normal = normalize(texture(s_Normals, tex_coord).rgb); 159 | 160 | float g_buffer_depth = exp_01_to_linear_01_depth(texture(s_Depth, tex_coord).r, CAMERA_NEAR_PLANE, CAMERA_FAR_PLANE); 161 | float particle_depth = exp_01_to_linear_01_depth(position.z * 0.5 + 0.5, CAMERA_NEAR_PLANE, CAMERA_FAR_PLANE); 162 | 163 | if ((particle_depth > g_buffer_depth) && (particle_depth - g_buffer_depth) < MIN_THICKNESS) 164 | { 165 | if (dot(particle.velocity.xyz, surface_normal) < 0.0) 166 | particle.velocity.xyz = reflect(particle.velocity.xyz, surface_normal) * u_Restitution; 167 | } 168 | } 169 | 170 | if (u_Viscosity != 0.0) 171 | particle.velocity.xyz += (curl_noise(particle.position.xyz) - particle.velocity.xyz) * u_Viscosity * u_DeltaTime; 172 | 173 | particle.position.xyz += (particle.velocity.xyz + u_ConstantVelocity) * u_DeltaTime; 174 | 175 | ParticleData.particles[particle_index] = particle; 176 | 177 | // Append index back into AliveIndices list 178 | push_alive_index(particle_index); 179 | 180 | // Increment draw count 181 | atomicAdd(ParticleDrawArgs.instance_count, 1); 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /src/shader/particle_update_kickoff_cs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // CONSTANTS --------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | #define LOCAL_SIZE 32 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUTS ----------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 12 | 13 | // ------------------------------------------------------------------ 14 | // UNIFORMS --------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | struct Particle 18 | { 19 | vec4 lifetime; 20 | vec4 velocity; 21 | vec4 position; 22 | vec4 color; 23 | }; 24 | 25 | layout(std430, binding = 0) buffer ParticleData_t 26 | { 27 | Particle particles[]; 28 | } 29 | ParticleData; 30 | 31 | layout(std430, binding = 1) buffer EmissionDispatchArgs_t 32 | { 33 | uint num_groups_x; 34 | uint num_groups_y; 35 | uint num_groups_z; 36 | } 37 | EmissionDispatchArgs; 38 | 39 | layout(std430, binding = 2) buffer SimulationDispatchArgs_t 40 | { 41 | uint num_groups_x; 42 | uint num_groups_y; 43 | uint num_groups_z; 44 | } 45 | SimulationDispatchArgs; 46 | 47 | layout(std430, binding = 3) buffer ParticleDrawArgs_t 48 | { 49 | uint count; 50 | uint instance_count; 51 | uint first; 52 | uint base_instance; 53 | } 54 | ParticleDrawArgs; 55 | 56 | layout(std430, binding = 4) buffer ParticleCounters_t 57 | { 58 | uint dead_count; 59 | uint alive_count[2]; 60 | uint simulation_count; 61 | uint emission_count; 62 | } 63 | Counters; 64 | 65 | uniform int u_ParticlesPerFrame; 66 | uniform int u_PreSimIdx; 67 | uniform int u_PostSimIdx; 68 | 69 | // ------------------------------------------------------------------ 70 | // MAIN ------------------------------------------------------------- 71 | // ------------------------------------------------------------------ 72 | 73 | void main() 74 | { 75 | // Reset particle indirect draw instance count 76 | ParticleDrawArgs.count = 6; 77 | ParticleDrawArgs.instance_count = 0; 78 | ParticleDrawArgs.first = 0; 79 | ParticleDrawArgs.base_instance = 0; 80 | 81 | // We can't emit more particles than we have available 82 | Counters.emission_count = min(uint(u_ParticlesPerFrame), Counters.dead_count); 83 | 84 | EmissionDispatchArgs.num_groups_x = uint(ceil(float(Counters.emission_count) / float(LOCAL_SIZE))); 85 | EmissionDispatchArgs.num_groups_y = 1; 86 | EmissionDispatchArgs.num_groups_z = 1; 87 | 88 | // Calculate total number of particles to simulate this frame 89 | Counters.simulation_count = Counters.alive_count[u_PreSimIdx] + Counters.emission_count; 90 | 91 | SimulationDispatchArgs.num_groups_x = uint(ceil(float(Counters.simulation_count) / float(LOCAL_SIZE))); 92 | SimulationDispatchArgs.num_groups_y = 1; 93 | SimulationDispatchArgs.num_groups_z = 1; 94 | 95 | // Reset post sim alive index count 96 | Counters.alive_count[u_PostSimIdx] = 0; 97 | } -------------------------------------------------------------------------------- /src/shader/particle_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // CONSTANTS -------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | const vec3 PARTICLE_VERTICES[6] = vec3[]( 6 | vec3(-1.0, -1.0, 0.0), // 0 7 | vec3(1.0, -1.0, 0.0), // 1 8 | vec3(-1.0, 1.0, 0.0), // 2 9 | vec3(-1.0, 1.0, 0.0), // 3 10 | vec3(1.0, -1.0, 0.0), // 4 11 | vec3(1.0, 1.0, 0.0) // 5 12 | ); 13 | 14 | // ------------------------------------------------------------------ 15 | // OUTPUTS ---------------------------------------------------------- 16 | // ------------------------------------------------------------------ 17 | 18 | out vec4 FS_IN_Color; 19 | 20 | // ------------------------------------------------------------------ 21 | // UNIFORMS --------------------------------------------------------- 22 | // ------------------------------------------------------------------ 23 | 24 | uniform float u_Rotation; 25 | uniform mat4 u_View; 26 | uniform mat4 u_Proj; 27 | 28 | uniform sampler1D s_ColorOverTime; 29 | uniform sampler1D s_SizeOverTime; 30 | 31 | struct Particle 32 | { 33 | vec4 lifetime; 34 | vec4 velocity; 35 | vec4 position; 36 | vec4 color; 37 | }; 38 | 39 | layout(std430, binding = 0) buffer ParticleData_t 40 | { 41 | Particle particles[]; 42 | } 43 | ParticleData; 44 | 45 | layout(std430, binding = 1) buffer ParticleIndices_t 46 | { 47 | uint count; 48 | uint indices[]; 49 | } 50 | AliveIndicesPostSim; 51 | 52 | // ------------------------------------------------------------------ 53 | // MAIN ------------------------------------------------------------- 54 | // ------------------------------------------------------------------ 55 | 56 | void main() 57 | { 58 | Particle particle = ParticleData.particles[AliveIndicesPostSim.indices[gl_InstanceID]]; 59 | 60 | float life = particle.lifetime.x / particle.lifetime.y; 61 | float size = texture(s_SizeOverTime, life).x; 62 | FS_IN_Color = texture(s_ColorOverTime, life); 63 | 64 | vec3 quad_pos = PARTICLE_VERTICES[gl_VertexID]; 65 | 66 | // rotate the billboard: 67 | mat2 rot = mat2(cos(u_Rotation), -sin(u_Rotation), sin(u_Rotation), cos(u_Rotation)); 68 | 69 | quad_pos.xy = rot * quad_pos.xy; 70 | 71 | // scale the quad 72 | quad_pos.xy *= size; 73 | 74 | vec4 position = u_View * vec4(particle.position.xyz, 1.0); 75 | position.xyz += quad_pos; 76 | 77 | gl_Position = u_Proj * position; 78 | } 79 | 80 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/random.glsl: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | float rand(vec3 co) 4 | { 5 | return fract(sin(dot(co.xyz, vec3(12.9898, 78.233, 45.5432))) * 43758.5453); 6 | } 7 | vec3 randomPointOnSphere(float u, float v, float radius) 8 | { 9 | float theta = 2 * PI * u; 10 | float phi = acos(2 * v - 1); 11 | float x = radius * sin(phi) * cos(theta); 12 | float y = radius * sin(phi) * sin(theta); 13 | float z = radius * cos(phi); 14 | return vec3(x, y, z); 15 | } 16 | vec3 curlNoise(vec3 coord) 17 | { 18 | vec3 dx = vec3(EPSILON, 0.0, 0.0); 19 | vec3 dy = vec3(0.0, EPSILON, 0.0); 20 | vec3 dz = vec3(0.0, 0.0, EPSILON); 21 | 22 | vec3 dpdx0 = vec3(snoise(coord - dx)); 23 | vec3 dpdx1 = vec3(snoise(coord + dx)); 24 | vec3 dpdy0 = vec3(snoise(coord - dy)); 25 | vec3 dpdy1 = vec3(snoise(coord + dy)); 26 | vec3 dpdz0 = vec3(snoise(coord - dz)); 27 | vec3 dpdz1 = vec3(snoise(coord + dz)); 28 | 29 | float x = dpdy1.z - dpdy0.z + dpdz1.y - dpdz0.y; 30 | float y = dpdz1.x - dpdz0.x + dpdx1.z - dpdx0.z; 31 | float z = dpdx1.y - dpdx0.y + dpdy1.x - dpdy0.x; 32 | 33 | return vec3(x, y, z) / EPSILON * 2.0; 34 | } -------------------------------------------------------------------------------- /src/shader/simplex_noise.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // Noise Shader Library for Unity - https://github.com/keijiro/NoiseShader 3 | // 4 | // Original work (webgl-noise) Copyright (C) 2011 Ashima Arts. 5 | // Translation and modification was made by Keijiro Takahashi. 6 | // 7 | // This shader is based on the webgl-noise GLSL shader. For further details 8 | // of the original shader, please see the following description from the 9 | // original source code. 10 | // 11 | 12 | // 13 | // Description : Array and textureless GLSL 2D/3D/4D simplex 14 | // noise functions. 15 | // Author : Ian McEwan, Ashima Arts. 16 | // Maintainer : ijm 17 | // Lastmod : 20110822 (ijm) 18 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 19 | // Distributed under the MIT License. See LICENSE file. 20 | // https://github.com/ashima/webgl-noise 21 | // 22 | 23 | #define EPSILON 1e-3 24 | #define PI 3.1415926535 25 | 26 | vec3 mod289(vec3 x) 27 | { 28 | return x - floor(x / 289.0) * 289.0; 29 | } 30 | 31 | vec4 mod289(vec4 x) 32 | { 33 | return x - floor(x / 289.0) * 289.0; 34 | } 35 | 36 | vec4 permute(vec4 x) 37 | { 38 | return mod289((x * 34.0 + 1.0) * x); 39 | } 40 | 41 | vec4 taylorInvSqrt(vec4 r) 42 | { 43 | return 1.79284291400159 - r * 0.85373472095314; 44 | } 45 | 46 | float snoise(vec3 v) 47 | { 48 | const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); 49 | 50 | // First corner 51 | vec3 i = floor(v + dot(v, C.yyy)); 52 | vec3 x0 = v - i + dot(i, C.xxx); 53 | 54 | // Other corners 55 | vec3 g = step(x0.yzx, x0.xyz); 56 | vec3 l = 1.0 - g; 57 | vec3 i1 = min(g.xyz, l.zxy); 58 | vec3 i2 = max(g.xyz, l.zxy); 59 | 60 | // x1 = x0 - i1 + 1.0 * C.xxx; 61 | // x2 = x0 - i2 + 2.0 * C.xxx; 62 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 63 | vec3 x1 = x0 - i1 + C.xxx; 64 | vec3 x2 = x0 - i2 + C.yyy; 65 | vec3 x3 = x0 - 0.5; 66 | 67 | // Permutations 68 | i = mod289(i); // Avoid truncation effects in permutation 69 | vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) 70 | + i.y + vec4(0.0, i1.y, i2.y, 1.0)) 71 | + i.x + vec4(0.0, i1.x, i2.x, 1.0)); 72 | 73 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 74 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 75 | vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) 76 | 77 | vec4 x_ = floor(j / 7.0); 78 | vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) 79 | 80 | vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; 81 | vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; 82 | 83 | vec4 h = 1.0 - abs(x) - abs(y); 84 | 85 | vec4 b0 = vec4(x.xy, y.xy); 86 | vec4 b1 = vec4(x.zw, y.zw); 87 | 88 | //vec4 s0 = vec4(lessThan(b0, 0.0)) * 2.0 - 1.0; 89 | //vec4 s1 = vec4(lessThan(b1, 0.0)) * 2.0 - 1.0; 90 | vec4 s0 = floor(b0) * 2.0 + 1.0; 91 | vec4 s1 = floor(b1) * 2.0 + 1.0; 92 | vec4 sh = -step(h, vec4(0.0)); 93 | 94 | vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; 95 | vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; 96 | 97 | vec3 g0 = vec3(a0.xy, h.x); 98 | vec3 g1 = vec3(a0.zw, h.y); 99 | vec3 g2 = vec3(a1.xy, h.z); 100 | vec3 g3 = vec3(a1.zw, h.w); 101 | 102 | // Normalise gradients 103 | vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); 104 | g0 *= norm.x; 105 | g1 *= norm.y; 106 | g2 *= norm.z; 107 | g3 *= norm.w; 108 | 109 | // Mix final noise value 110 | vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); 111 | m = m * m; 112 | m = m * m; 113 | 114 | vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); 115 | return 42.0 * dot(m, px); 116 | } 117 | 118 | vec4 snoise_grad(vec3 v) 119 | { 120 | const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); 121 | 122 | // First corner 123 | vec3 i = floor(v + dot(v, C.yyy)); 124 | vec3 x0 = v - i + dot(i, C.xxx); 125 | 126 | // Other corners 127 | vec3 g = step(x0.yzx, x0.xyz); 128 | vec3 l = 1.0 - g; 129 | vec3 i1 = min(g.xyz, l.zxy); 130 | vec3 i2 = max(g.xyz, l.zxy); 131 | 132 | // x1 = x0 - i1 + 1.0 * C.xxx; 133 | // x2 = x0 - i2 + 2.0 * C.xxx; 134 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 135 | vec3 x1 = x0 - i1 + C.xxx; 136 | vec3 x2 = x0 - i2 + C.yyy; 137 | vec3 x3 = x0 - 0.5; 138 | 139 | // Permutations 140 | i = mod289(i); // Avoid truncation effects in permutation 141 | vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) 142 | + i.y + vec4(0.0, i1.y, i2.y, 1.0)) 143 | + i.x + vec4(0.0, i1.x, i2.x, 1.0)); 144 | 145 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 146 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 147 | vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) 148 | 149 | vec4 x_ = floor(j / 7.0); 150 | vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) 151 | 152 | vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; 153 | vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; 154 | 155 | vec4 h = 1.0 - abs(x) - abs(y); 156 | 157 | vec4 b0 = vec4(x.xy, y.xy); 158 | vec4 b1 = vec4(x.zw, y.zw); 159 | 160 | //vec4 s0 = vec4(lessThan(b0, 0.0)) * 2.0 - 1.0; 161 | //vec4 s1 = vec4(lessThan(b1, 0.0)) * 2.0 - 1.0; 162 | vec4 s0 = floor(b0) * 2.0 + 1.0; 163 | vec4 s1 = floor(b1) * 2.0 + 1.0; 164 | vec4 sh = -step(h, vec4(0.0)); 165 | 166 | vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; 167 | vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; 168 | 169 | vec3 g0 = vec3(a0.xy, h.x); 170 | vec3 g1 = vec3(a0.zw, h.y); 171 | vec3 g2 = vec3(a1.xy, h.z); 172 | vec3 g3 = vec3(a1.zw, h.w); 173 | 174 | // Normalise gradients 175 | vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); 176 | g0 *= norm.x; 177 | g1 *= norm.y; 178 | g2 *= norm.z; 179 | g3 *= norm.w; 180 | 181 | // Compute noise and gradient at P 182 | vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); 183 | vec4 m2 = m * m; 184 | vec4 m3 = m2 * m; 185 | vec4 m4 = m2 * m2; 186 | vec3 grad = -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; 187 | vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); 188 | return 42.0 * vec4(grad, dot(m4, px)); 189 | } --------------------------------------------------------------------------------