1 /* 2 Copyright (c) 2017-2018 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 28 module dagon.graphics.clustered; 29 30 import std.stdio; 31 import std.math; 32 import std.conv; 33 import std.random; 34 35 import dlib.core.memory; 36 import dlib.math.vector; 37 import dlib.container.array; 38 import dlib.image.color; 39 import dlib.geometry.frustum; 40 41 import derelict.opengl; 42 43 import dagon.core.ownership; 44 import dagon.graphics.view; 45 import dagon.graphics.rc; 46 import dagon.logics.entity; 47 import dagon.logics.behaviour; 48 49 float clampf(float x, float mi, float ma) 50 { 51 if (x < mi) return mi; 52 else if (x > ma) return ma; 53 else return x; 54 } 55 56 struct Box 57 { 58 Vector2f pmin; 59 Vector2f pmax; 60 } 61 62 struct Circle 63 { 64 Vector2f center; 65 float radius; 66 } 67 68 Box cellBox(uint x, uint y, float cellSize, float domainWidth) 69 { 70 Box b; 71 b.pmin = Vector2f(x, y) * cellSize - domainWidth * 0.5f; 72 b.pmax = b.pmin + cellSize; 73 return b; 74 } 75 76 bool circleBoxIsec(Circle c, Box b) 77 { 78 if (c.center.x > b.pmin.x && c.center.x < b.pmax.x && 79 c.center.y > b.pmin.y && c.center.y < b.pmax.y) 80 return true; // sphere's center is inside the box 81 82 Vector2f closest = c.center; 83 for (int i = 0; i < 2; i++) 84 { 85 float v = c.center[i]; 86 if (v < b.pmin[i]) v = b.pmin[i]; 87 if (v > b.pmax[i]) v = b.pmax[i]; 88 closest[i] = v; 89 } 90 91 return (distance(c.center, closest) <= c.radius); 92 } 93 94 class LightSource 95 { 96 Vector3f position; 97 Vector3f color; 98 float radius; // max light attenuation radius 99 float areaRadius; // light's own radius 100 float energy; 101 102 this(Vector3f pos, Vector3f col, float attRadius, float areaRadius, float energy) 103 { 104 this.position = pos; 105 this.color = col; 106 this.radius = attRadius; 107 this.areaRadius = areaRadius; 108 this.energy = energy; 109 } 110 } 111 112 enum uint maxLightsPerNode = 8; 113 114 struct LightCluster 115 { 116 Box box; 117 118 uint[maxLightsPerNode] lights; 119 uint numLights = 0; 120 } 121 122 // TODO: move this to dlib.geometry.frustum 123 bool frustumIntersectsSphere(ref Frustum f, Vector3f center, float radius) 124 { 125 float d; 126 127 foreach(i, ref p; f.planes) 128 { 129 // find the signed distance to this plane 130 d = p.distance(center); 131 132 // if this distance is > sphere.radius, we are outside 133 if (d > radius) 134 return false; 135 } 136 137 return true; 138 } 139 140 class ClusteredLightManager: Owner 141 { 142 DynamicArray!LightSource lightSources; 143 LightCluster[] clusterData; 144 145 uint[] clusters; 146 Vector3f[] lights; 147 uint[] lightIndices; 148 149 Vector3f position; 150 151 Vector2f clustersPosition; 152 float sceneSize = 200.0f; 153 float invSceneSize; 154 float clusterSize; 155 uint domainSize = 100; 156 157 uint numLightAttributes = 4; 158 159 uint maxNumLights; 160 uint maxNumIndices = 2048; 161 162 uint currentlyVisibleLights = 0; 163 164 GLuint clusterTexture; 165 GLuint lightTexture; 166 GLuint indexTexture; 167 168 this(float sceneSize, uint numClusters, Owner o) 169 { 170 super(o); 171 172 position = Vector3f(0, 0, 0); 173 174 this.sceneSize = sceneSize; 175 this.domainSize = numClusters; 176 177 invSceneSize = 1.0f / sceneSize; 178 179 clusterSize = sceneSize / cast(float)domainSize; 180 clustersPosition = Vector2f(-sceneSize * 0.5f, -sceneSize * 0.5f); 181 clusterData = New!(LightCluster[])(domainSize * domainSize); 182 183 foreach(y; 0..domainSize) 184 foreach(x; 0..domainSize) 185 { 186 LightCluster* c = &clusterData[y * domainSize + x]; 187 c.box = cellBox(x, y, clusterSize, sceneSize); 188 c.numLights = 0; 189 } 190 191 clusters = New!(uint[])(domainSize * domainSize); 192 193 foreach(ref c; clusters) 194 c = 0; 195 196 maxNumLights = maxNumIndices / maxLightsPerNode; 197 198 lights = New!(Vector3f[])(maxNumLights * numLightAttributes); 199 foreach(ref l; lights) 200 l = Vector3f(0, 0, 0); 201 202 lightIndices = New!(uint[])(maxNumIndices); 203 204 // 2D texture to store light clusters 205 glGenTextures(1, &clusterTexture); 206 glActiveTexture(GL_TEXTURE0); 207 glBindTexture(GL_TEXTURE_2D, clusterTexture); 208 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 209 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 210 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 211 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 212 glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, domainSize, domainSize, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, clusters.ptr); 213 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 214 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); 215 glBindTexture(GL_TEXTURE_2D, 0); 216 217 // 2D texture to store light data 218 glGenTextures(1, &lightTexture); 219 glBindTexture(GL_TEXTURE_2D, lightTexture); 220 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 221 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 222 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 223 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 224 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, maxNumLights, numLightAttributes, 0, GL_RGB, GL_FLOAT, lights.ptr); 225 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 226 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); 227 glBindTexture(GL_TEXTURE_2D, 0); 228 229 // 1D texture to store light indices per cluster 230 glGenTextures(1, &indexTexture); 231 glBindTexture(GL_TEXTURE_1D, indexTexture); 232 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 233 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 234 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 235 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 236 glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, maxNumIndices, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, lightIndices.ptr); 237 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0); 238 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, 0); 239 glBindTexture(GL_TEXTURE_1D, 0); 240 } 241 242 ~this() 243 { 244 if (glIsTexture(clusterTexture)) 245 glDeleteTextures(1, &clusterTexture); 246 247 if (glIsTexture(lightTexture)) 248 glDeleteTextures(1, &lightTexture); 249 250 if (glIsTexture(indexTexture)) 251 glDeleteTextures(1, &indexTexture); 252 253 Delete(clusters); 254 Delete(lights); 255 Delete(lightIndices); 256 257 foreach(light; lightSources) 258 Delete(light); 259 260 lightSources.free(); 261 Delete(clusterData); 262 } 263 264 LightSource addLight(Vector3f position, Color4f color, float energy, float radius, float areaRadius = 0.0f) 265 { 266 lightSources.append(New!LightSource(position, color.rgb, radius, areaRadius, energy)); 267 268 if (lightSources.length >= maxNumLights) 269 writeln("Warning: lights number exceeds index buffer capability (", maxNumLights, ")"); 270 271 return lightSources.data[$-1]; 272 } 273 274 void bindClusterTexture() 275 { 276 glBindTexture(GL_TEXTURE_2D, clusterTexture); 277 } 278 void unbindClusterTexture() 279 { 280 glBindTexture(GL_TEXTURE_2D, 0); 281 } 282 283 void bindLightTexture() 284 { 285 glBindTexture(GL_TEXTURE_2D, lightTexture); 286 } 287 void unbindLightTexture() 288 { 289 glBindTexture(GL_TEXTURE_2D, 0); 290 } 291 292 void bindIndexTexture() 293 { 294 glBindTexture(GL_TEXTURE_1D, indexTexture); 295 } 296 void unbindIndexTexture() 297 { 298 glBindTexture(GL_TEXTURE_1D, 0); 299 } 300 301 void update(RenderingContext* rc) 302 { 303 position = rc.cameraPosition; 304 305 foreach(ref v; clusters) 306 v = 0; 307 308 foreach(ref c; clusterData) 309 { 310 c.numLights = 0; 311 } 312 313 currentlyVisibleLights = 0; 314 315 foreach(i, light; lightSources.data) 316 { 317 if (frustumIntersectsSphere(rc.frustum, light.position, light.radius)) 318 { 319 currentlyVisibleLights++; 320 321 if (currentlyVisibleLights < maxNumLights) 322 { 323 uint index = currentlyVisibleLights - 1; 324 325 lights[index] = light.position; 326 lights[maxNumLights + index] = light.color; 327 lights[maxNumLights * 2 + index] = Vector3f(light.radius, light.areaRadius, light.energy); 328 329 Vector3f lightPosLocal = light.position - position; 330 Vector2f lightPosXZ = Vector2f(lightPosLocal.x, lightPosLocal.z); 331 Circle lightCircle = Circle(lightPosXZ, light.radius); 332 333 uint x1 = cast(uint)clampf(floor((lightCircle.center.x - lightCircle.radius + sceneSize * 0.5f) / clusterSize), 0, domainSize-1); 334 uint y1 = cast(uint)clampf(floor((lightCircle.center.y - lightCircle.radius + sceneSize * 0.5f) / clusterSize), 0, domainSize-1); 335 uint x2 = cast(uint)clampf(x1 + ceil(lightCircle.radius + lightCircle.radius) + 1, 0, domainSize-1); 336 uint y2 = cast(uint)clampf(y1 + ceil(lightCircle.radius + lightCircle.radius) + 1, 0, domainSize-1); 337 338 foreach(y; y1..y2) 339 foreach(x; x1..x2) 340 { 341 Box b = cellBox(x, y, clusterSize, sceneSize); 342 if (circleBoxIsec(lightCircle, b)) 343 { 344 auto c = &clusterData[y * domainSize + x]; 345 if (c.numLights < maxLightsPerNode) 346 { 347 c.lights[c.numLights] = index; 348 c.numLights = c.numLights + 1; 349 } 350 } 351 } 352 } 353 else 354 break; 355 } 356 } 357 358 uint offset = 0; 359 foreach(ci, ref c; clusterData) 360 if (offset < maxNumIndices) 361 { 362 if (offset + c.numLights > maxNumIndices) 363 break; 364 365 if (c.numLights) 366 { 367 foreach(i; 0..c.numLights) 368 lightIndices[offset + cast(uint)i] = c.lights[i]; 369 370 clusters[ci] = offset | (c.numLights << 16); 371 372 offset += c.numLights; 373 } 374 else 375 { 376 clusters[ci] = 0; 377 } 378 } 379 380 glBindTexture(GL_TEXTURE_2D, clusterTexture); 381 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, domainSize, domainSize, GL_RED_INTEGER, GL_UNSIGNED_INT, clusters.ptr); 382 glBindTexture(GL_TEXTURE_2D, 0); 383 384 glBindTexture(GL_TEXTURE_2D, lightTexture); 385 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, maxNumLights, numLightAttributes, GL_RGB, GL_FLOAT, lights.ptr); 386 glBindTexture(GL_TEXTURE_2D, 0); 387 388 glBindTexture(GL_TEXTURE_1D, indexTexture); 389 glTexSubImage1D(GL_TEXTURE_1D, 0, 0, maxNumIndices, GL_RED_INTEGER, GL_UNSIGNED_INT, lightIndices.ptr); 390 glBindTexture(GL_TEXTURE_1D, 0); 391 } 392 } 393 394 // Attach a light to Entity 395 class LightBehaviour: Behaviour 396 { 397 LightSource light; 398 399 this(Entity e, LightSource light) 400 { 401 super(e); 402 403 this.light = light; 404 } 405 406 override void update(double dt) 407 { 408 light.position = entity.position; 409 } 410 }