├── README.md ├── library.frag ├── main.frag ├── render_engine.py ├── sdf_engine.c ├── setup.py ├── shader_sdf.vert ├── stb_image.h └── uniforms.frag /README.md: -------------------------------------------------------------------------------- 1 | # SDFEngine 2 | 3 | Install python-dev and glfw 4 | 5 | To Build the C extension 6 | 7 | python setup.py build 8 | 9 | Will add more details soon (And maybe examples??? If enough people need)!!! 10 | -------------------------------------------------------------------------------- /library.frag: -------------------------------------------------------------------------------- 1 | 2 | 3 | //////////////////////////////////////////////////////////////// 4 | // 5 | // HELPER FUNCTIONS/MACROS 6 | // 7 | //////////////////////////////////////////////////////////////// 8 | 9 | #define PI 3.14159265 10 | #define TAU (2*PI) 11 | #define PHI (sqrt(5)*0.5 + 0.5) 12 | #define saturate(x) clamp(x, 0, 1) 13 | 14 | float sgn(float x) { 15 | return (x<0)?-1.:1.; 16 | } 17 | 18 | vec2 sgn(vec2 v) { 19 | return vec2((v.x<0)?-1:1, (v.y<0)?-1:1); 20 | } 21 | 22 | float square (float x) { 23 | return x*x; 24 | } 25 | 26 | vec2 square (vec2 x) { 27 | return x*x; 28 | } 29 | 30 | vec3 square (vec3 x) { 31 | return x*x; 32 | } 33 | 34 | float lengthSqr(vec3 x) { 35 | return dot(x, x); 36 | } 37 | 38 | 39 | float vmax(vec2 v) { 40 | return max(v.x, v.y); 41 | } 42 | 43 | float vmax(vec3 v) { 44 | return max(max(v.x, v.y), v.z); 45 | } 46 | 47 | float vmax(vec4 v) { 48 | return max(max(v.x, v.y), max(v.z, v.w)); 49 | } 50 | 51 | float vmin(vec2 v) { 52 | return min(v.x, v.y); 53 | } 54 | 55 | float vmin(vec3 v) { 56 | return min(min(v.x, v.y), v.z); 57 | } 58 | 59 | float vmin(vec4 v) { 60 | return min(min(v.x, v.y), min(v.z, v.w)); 61 | } 62 | 63 | 64 | 65 | 66 | //////////////////////////////////////////////////////////////// 67 | // 68 | // PRIMITIVE DISTANCE FUNCTIONS 69 | // 70 | //////////////////////////////////////////////////////////////// 71 | 72 | 73 | float sdfSphere(vec3 p, float r) { 74 | return length(p) - r; 75 | } 76 | 77 | // Plane with normal n (n is normalized) at some distance from the origin 78 | float sdfPlane(vec3 p, vec3 n, float distanceFromOrigin) { 79 | return dot(p, n) + distanceFromOrigin; 80 | } 81 | 82 | // Cheap Box: distance to corners is overestimated 83 | float sdfBoxCheap(vec3 p, vec3 b) { //cheap box 84 | return vmax(abs(p) - b); 85 | } 86 | 87 | // Box: correct distance to corners 88 | float sdfBox(vec3 p, vec3 b) { 89 | vec3 d = abs(p) - b; 90 | return length(max(d, vec3(0))) + vmax(min(d, vec3(0))); 91 | } 92 | 93 | // Same as above, but in two dimensions (an endless box) 94 | float sdfBox2Cheap(vec2 p, vec2 b) { 95 | return vmax(abs(p)-b); 96 | } 97 | 98 | float sdfBox2(vec2 p, vec2 b) { 99 | vec2 d = abs(p) - b; 100 | return length(max(d, vec2(0))) + vmax(min(d, vec2(0))); 101 | } 102 | 103 | 104 | // Endless "corner" 105 | float sdfCorner (vec2 p) { 106 | return length(max(p, vec2(0))) + vmax(min(p, vec2(0))); 107 | } 108 | 109 | // Blobby ball object. You've probably seen it somewhere. This is not a correct distance bound, beware. 110 | float sdfBlob(vec3 p) { 111 | p = abs(p); 112 | if (p.x < max(p.y, p.z)) p = p.yzx; 113 | if (p.x < max(p.y, p.z)) p = p.yzx; 114 | float b = max(max(max( 115 | dot(p, normalize(vec3(1, 1, 1))), 116 | dot(p.xz, normalize(vec2(PHI+1, 1)))), 117 | dot(p.yx, normalize(vec2(1, PHI)))), 118 | dot(p.xz, normalize(vec2(1, PHI)))); 119 | float l = length(p); 120 | return l - 1.5 - 0.2 * (1.5 / 2)* cos(min(sqrt(1.01 - b / l)*(PI / 0.25), PI)); 121 | } 122 | 123 | // Cylinder standing upright on the xz plane 124 | float sdfCylinder(vec3 p, float r, float height) { 125 | float d = length(p.xz) - r; 126 | d = max(d, abs(p.y) - height); 127 | return d; 128 | } 129 | 130 | // Capsule: A Cylinder with round caps on both sides 131 | float sdfCapsule(vec3 p, float r, float c) { 132 | return mix(length(p.xz) - r, length(vec3(p.x, abs(p.y) - c, p.z)) - r, step(c, abs(p.y))); 133 | } 134 | 135 | // Distance to line segment between and , used for fCapsule() version 2below 136 | float sdfLineSegment(vec3 p, vec3 a, vec3 b) { 137 | vec3 ab = b - a; 138 | float t = saturate(dot(p - a, ab) / dot(ab, ab)); 139 | return length((ab*t + a) - p); 140 | } 141 | 142 | // Capsule version 2: between two end points and with radius r 143 | float sdfCapsule(vec3 p, vec3 a, vec3 b, float r) { 144 | return sdfLineSegment(p, a, b) - r; 145 | } 146 | 147 | // Torus in the XZ-plane 148 | float sdfTorus(vec3 p, float smallRadius, float largeRadius) { 149 | return length(vec2(length(p.xz) - largeRadius, p.y)) - smallRadius; 150 | } 151 | 152 | // A circle line. Can also be used to make a torus by subtracting the smaller radius of the torus. 153 | float sdfCircle(vec3 p, float r) { 154 | float l = length(p.xz) - r; 155 | return length(vec2(p.y, l)); 156 | } 157 | 158 | // A circular disc with no thickness (i.e. a cylinder with no height). 159 | // Subtract some value to make a flat disc with rounded edge. 160 | float sdfDisc(vec3 p, float r) { 161 | float l = length(p.xz) - r; 162 | return l < 0 ? abs(p.y) : length(vec2(p.y, l)); 163 | } 164 | 165 | // Hexagonal prism, circumcircle variant 166 | float sdfHexagonCircumcircle(vec3 p, vec2 h) { 167 | vec3 q = abs(p); 168 | return max(q.y - h.y, max(q.x*sqrt(3)*0.5 + q.z*0.5, q.z) - h.x); 169 | //this is mathematically equivalent to this line, but less efficient: 170 | //return max(q.y - h.y, max(dot(vec2(cos(PI/3), sin(PI/3)), q.zx), q.z) - h.x); 171 | } 172 | 173 | // Hexagonal prism, incircle variant 174 | float sdfHexagonIncircle(vec3 p, vec2 h) { 175 | return sdfHexagonCircumcircle(p, vec2(h.x*sqrt(3)*0.5, h.y)); 176 | } 177 | 178 | // Cone with correct distances to tip and base circle. Y is up, 0 is in the middle of the base. 179 | float sdfCone(vec3 p, float radius, float height) { 180 | vec2 q = vec2(length(p.xz), p.y); 181 | vec2 tip = q - vec2(0, height); 182 | vec2 mantleDir = normalize(vec2(height, radius)); 183 | float mantle = dot(tip, mantleDir); 184 | float d = max(mantle, -q.y); 185 | float projected = dot(tip, vec2(mantleDir.y, -mantleDir.x)); 186 | 187 | // distance to tip 188 | if ((q.y > height) && (projected < 0)) { 189 | d = max(d, length(tip)); 190 | } 191 | 192 | // distance to base ring 193 | if ((q.x > radius) && (projected > length(vec2(height, radius)))) { 194 | d = max(d, length(q - vec2(radius, 0))); 195 | } 196 | return d; 197 | } 198 | 199 | // 200 | // "Generalized Distance Functions" by Akleman and Chen. 201 | // see the Paper at https://www.viz.tamu.edu/faculty/ergun/research/implicitmodeling/papers/sm99.pdf 202 | // 203 | // This set of constants is used to construct a large variety of geometric primitives. 204 | // Indices are shifted by 1 compared to the paper because we start counting at Zero. 205 | // Some of those are slow whenever a driver decides to not unroll the loop, 206 | // which seems to happen for fIcosahedron und fTruncatedIcosahedron on nvidia 350.12 at least. 207 | // Specialized implementations can well be faster in all cases. 208 | // 209 | 210 | const vec3 GDFVectors[19] = vec3[]( 211 | normalize(vec3(1, 0, 0)), 212 | normalize(vec3(0, 1, 0)), 213 | normalize(vec3(0, 0, 1)), 214 | 215 | normalize(vec3(1, 1, 1 )), 216 | normalize(vec3(-1, 1, 1)), 217 | normalize(vec3(1, -1, 1)), 218 | normalize(vec3(1, 1, -1)), 219 | 220 | normalize(vec3(0, 1, PHI+1)), 221 | normalize(vec3(0, -1, PHI+1)), 222 | normalize(vec3(PHI+1, 0, 1)), 223 | normalize(vec3(-PHI-1, 0, 1)), 224 | normalize(vec3(1, PHI+1, 0)), 225 | normalize(vec3(-1, PHI+1, 0)), 226 | 227 | normalize(vec3(0, PHI, 1)), 228 | normalize(vec3(0, -PHI, 1)), 229 | normalize(vec3(1, 0, PHI)), 230 | normalize(vec3(-1, 0, PHI)), 231 | normalize(vec3(PHI, 1, 0)), 232 | normalize(vec3(-PHI, 1, 0)) 233 | ); 234 | 235 | // Version with variable exponent. 236 | // This is slow and does not produce correct distances, but allows for bulging of objects. 237 | float sdfGDF(vec3 p, float r, float e, int begin, int end) { 238 | float d = 0; 239 | for (int i = begin; i <= end; ++i) 240 | d += pow(abs(dot(p, GDFVectors[i])), e); 241 | return pow(d, 1/e) - r; 242 | } 243 | 244 | // Version with without exponent, creates objects with sharp edges and flat faces 245 | float sdfGDF(vec3 p, float r, int begin, int end) { 246 | float d = 0; 247 | for (int i = begin; i <= end; ++i) 248 | d = max(d, abs(dot(p, GDFVectors[i]))); 249 | return d - r; 250 | } 251 | 252 | // Primitives follow: 253 | 254 | float sdfOctahedron(vec3 p, float r, float e) { 255 | return sdfGDF(p, r, e, 3, 6); 256 | } 257 | 258 | float sdfDodecahedron(vec3 p, float r, float e) { 259 | return sdfGDF(p, r, e, 13, 18); 260 | } 261 | 262 | float sdfIcosahedron(vec3 p, float r, float e) { 263 | return sdfGDF(p, r, e, 3, 12); 264 | } 265 | 266 | float sdfTruncatedOctahedron(vec3 p, float r, float e) { 267 | return sdfGDF(p, r, e, 0, 6); 268 | } 269 | 270 | float sdfTruncatedIcosahedron(vec3 p, float r, float e) { 271 | return sdfGDF(p, r, e, 3, 18); 272 | } 273 | 274 | float sdfOctahedron(vec3 p, float r) { 275 | return sdfGDF(p, r, 3, 6); 276 | } 277 | 278 | float sdfDodecahedron(vec3 p, float r) { 279 | return sdfGDF(p, r, 13, 18); 280 | } 281 | 282 | float sdfIcosahedron(vec3 p, float r) { 283 | return sdfGDF(p, r, 3, 12); 284 | } 285 | 286 | float sdfTruncatedOctahedron(vec3 p, float r) { 287 | return sdfGDF(p, r, 0, 6); 288 | } 289 | 290 | float sdfTruncatedIcosahedron(vec3 p, float r) { 291 | return sdfGDF(p, r, 3, 18); 292 | } 293 | 294 | 295 | //////////////////////////////////////////////////////////////// 296 | // 297 | // DOMAIN MANIPULATION OPERATORS 298 | // 299 | //////////////////////////////////////////////////////////////// 300 | // 301 | // Conventions: 302 | // 303 | // Everything that modifies the domain is named pSomething. 304 | // 305 | // Many operate only on a subset of the three dimensions. For those, 306 | // you must choose the dimensions that you want manipulated 307 | // by supplying e.g. or 308 | // 309 | // is always the first argument and modified in place. 310 | // 311 | // Many of the operators partition space into cells. An identifier 312 | // or cell index is returned, if possible. This return value is 313 | // intended to be optionally used e.g. as a random seed to change 314 | // parameters of the distance functions inside the cells. 315 | // 316 | // Unless stated otherwise, for cell index 0,

is unchanged and cells 317 | // are centered on the origin so objects don't have to be moved to fit. 318 | // 319 | // 320 | //////////////////////////////////////////////////////////////// 321 | 322 | 323 | 324 | // Rotate around a coordinate axis (i.e. in a plane perpendicular to that axis) by angle . 325 | // Read like this: R(p.xz, a) rotates "x towards z". 326 | // This is fast if is a compile-time constant and slower (but still practical) if not. 327 | void pR(inout vec2 p, float a) { 328 | p = cos(a)*p + sin(a)*vec2(p.y, -p.x); 329 | } 330 | 331 | // Shortcut for 45-degrees rotation 332 | void pR45(inout vec2 p) { 333 | p = (p + vec2(p.y, -p.x))*sqrt(0.5); 334 | } 335 | 336 | // Repeat space along one axis. Use like this to repeat along the x axis: 337 | // - using the return value is optional. 338 | float pMod1(inout float p, float size) { 339 | float halfsize = size*0.5; 340 | float c = floor((p + halfsize)/size); 341 | p = mod(p + halfsize, size) - halfsize; 342 | return c; 343 | } 344 | 345 | // Same, but mirror every second cell so they match at the boundaries 346 | float pModMirror1(inout float p, float size) { 347 | float halfsize = size*0.5; 348 | float c = floor((p + halfsize)/size); 349 | p = mod(p + halfsize,size) - halfsize; 350 | p *= mod(c, 2.0)*2 - 1; 351 | return c; 352 | } 353 | 354 | // Repeat the domain only in positive direction. Everything in the negative half-space is unchanged. 355 | float pModSingle1(inout float p, float size) { 356 | float halfsize = size*0.5; 357 | float c = floor((p + halfsize)/size); 358 | if (p >= 0) 359 | p = mod(p + halfsize, size) - halfsize; 360 | return c; 361 | } 362 | 363 | // Repeat only a few times: from indices to (similar to above, but more flexible) 364 | float pModInterval1(inout float p, float size, float start, float stop) { 365 | float halfsize = size*0.5; 366 | float c = floor((p + halfsize)/size); 367 | p = mod(p+halfsize, size) - halfsize; 368 | if (c > stop) { //yes, this might not be the best thing numerically. 369 | p += size*(c - stop); 370 | c = stop; 371 | } 372 | if (c = (repetitions/2)) c = abs(c); 392 | return c; 393 | } 394 | 395 | // Repeat in two dimensions 396 | vec2 pMod2(inout vec2 p, vec2 size) { 397 | vec2 c = floor((p + size*0.5)/size); 398 | p = mod(p + size*0.5,size) - size*0.5; 399 | return c; 400 | } 401 | 402 | // Same, but mirror every second cell so all boundaries match 403 | vec2 pModMirror2(inout vec2 p, vec2 size) { 404 | vec2 halfsize = size*0.5; 405 | vec2 c = floor((p + halfsize)/size); 406 | p = mod(p + halfsize, size) - halfsize; 407 | p *= mod(c,vec2(2))*2 - vec2(1); 408 | return c; 409 | } 410 | 411 | // Same, but mirror every second cell at the diagonal as well 412 | vec2 pModGrid2(inout vec2 p, vec2 size) { 413 | vec2 c = floor((p + size*0.5)/size); 414 | p = mod(p + size*0.5, size) - size*0.5; 415 | p *= mod(c,vec2(2))*2 - vec2(1); 416 | p -= size/2; 417 | if (p.x > p.y) p.xy = p.yx; 418 | return floor(c/2); 419 | } 420 | 421 | // Repeat in three dimensions 422 | vec3 pMod3(inout vec3 p, vec3 size) { 423 | vec3 c = floor((p + size*0.5)/size); 424 | p = mod(p + size*0.5, size) - size*0.5; 425 | return c; 426 | } 427 | 428 | // Mirror at an axis-aligned plane which is at a specified distance from the origin. 429 | float pMirror (inout float p, float dist) { 430 | float s = sgn(p); 431 | p = abs(p)-dist; 432 | return s; 433 | } 434 | 435 | // Mirror in both dimensions and at the diagonal, yielding one eighth of the space. 436 | // translate by dist before mirroring. 437 | vec2 pMirrorOctant (inout vec2 p, vec2 dist) { 438 | vec2 s = sgn(p); 439 | pMirror(p.x, dist.x); 440 | pMirror(p.y, dist.y); 441 | if (p.y > p.x) 442 | p.xy = p.yx; 443 | return s; 444 | } 445 | 446 | // Reflect space at a plane 447 | float pReflect(inout vec3 p, vec3 planeNormal, float offset) { 448 | float t = dot(p, planeNormal)+offset; 449 | if (t < 0) { 450 | p = p - (2*t)*planeNormal; 451 | } 452 | return sgn(t); 453 | } 454 | 455 | 456 | //////////////////////////////////////////////////////////////// 457 | // 458 | // OBJECT COMBINATION OPERATORS 459 | // 460 | //////////////////////////////////////////////////////////////// 461 | // 462 | // We usually need the following boolean operators to combine two objects: 463 | // Union: OR(a,b) 464 | // Intersection: AND(a,b) 465 | // Difference: AND(a,!b) 466 | // (a and b being the distances to the objects). 467 | // 468 | // The trivial implementations are min(a,b) for union, max(a,b) for intersection 469 | // and max(a,-b) for difference. To combine objects in more interesting ways to 470 | // produce rounded edges, chamfers, stairs, etc. instead of plain sharp edges we 471 | // can use combination operators. It is common to use some kind of "smooth minimum" 472 | // instead of min(), but we don't like that because it does not preserve Lipschitz 473 | // continuity in many cases. 474 | // 475 | // Naming convention: since they return a distance, they are called fOpSomething. 476 | // The different flavours usually implement all the boolean operators above 477 | // and are called fOpUnionRound, fOpIntersectionRound, etc. 478 | // 479 | // The basic idea: Assume the object surfaces intersect at a right angle. The two 480 | // distances and constitute a new local two-dimensional coordinate system 481 | // with the actual intersection as the origin. In this coordinate system, we can 482 | // evaluate any 2D distance function we want in order to shape the edge. 483 | // 484 | // The operators below are just those that we found useful or interesting and should 485 | // be seen as examples. There are infinitely more possible operators. 486 | // 487 | // They are designed to actually produce correct distances or distance bounds, unlike 488 | // popular "smooth minimum" operators, on the condition that the gradients of the two 489 | // SDFs are at right angles. When they are off by more than 30 degrees or so, the 490 | // Lipschitz condition will no longer hold (i.e. you might get artifacts). The worst 491 | // case is parallel surfaces that are close to each other. 492 | // 493 | // Most have a float argument to specify the radius of the feature they represent. 494 | // This should be much smaller than the object size. 495 | // 496 | // Some of them have checks like "if ((-a < r) && (-b < r))" that restrict 497 | // their influence (and computation cost) to a certain area. You might 498 | // want to lift that restriction or enforce it. We have left it as comments 499 | // in some cases. 500 | // 501 | // usage example: 502 | // 503 | // float fTwoBoxes(vec3 p) { 504 | // float box0 = fBox(p, vec3(1)); 505 | // float box1 = fBox(p-vec3(1), vec3(1)); 506 | // return fOpUnionChamfer(box0, box1, 0.2); 507 | // } 508 | // 509 | //////////////////////////////////////////////////////////////// 510 | 511 | 512 | // The "Chamfer" flavour makes a 45-degree chamfered edge (the diagonal of a square of size ): 513 | float fOpUnionChamfer(float a, float b, float r) { 514 | return min(min(a, b), (a - r + b)*sqrt(0.5)); 515 | } 516 | 517 | // Intersection has to deal with what is normally the inside of the resulting object 518 | // when using union, which we normally don't care about too much. Thus, intersection 519 | // implementations sometimes differ from union implementations. 520 | float fOpIntersectionChamfer(float a, float b, float r) { 521 | return max(max(a, b), (a + r + b)*sqrt(0.5)); 522 | } 523 | 524 | // Difference can be built from Intersection or Union: 525 | float fOpDifferenceChamfer (float a, float b, float r) { 526 | return fOpIntersectionChamfer(a, -b, r); 527 | } 528 | 529 | // The "Round" variant uses a quarter-circle to join the two objects smoothly: 530 | float fOpUnionRound(float a, float b, float r) { 531 | vec2 u = max(vec2(r - a,r - b), vec2(0)); 532 | return max(r, min (a, b)) - length(u); 533 | } 534 | 535 | float fOpIntersectionRound(float a, float b, float r) { 536 | vec2 u = max(vec2(r + a,r + b), vec2(0)); 537 | return min(-r, max (a, b)) + length(u); 538 | } 539 | 540 | float fOpDifferenceRound (float a, float b, float r) { 541 | return fOpIntersectionRound(a, -b, r); 542 | } 543 | 544 | 545 | // The "Columns" flavour makes n-1 circular columns at a 45 degree angle: 546 | float fOpUnionColumns(float a, float b, float r, float n) { 547 | if ((a < r) && (b < r)) { 548 | vec2 p = vec2(a, b); 549 | float columnradius = r*sqrt(2)/((n-1)*2+sqrt(2)); 550 | pR45(p); 551 | p.x -= sqrt(2)/2*r; 552 | p.x += columnradius*sqrt(2); 553 | if (mod(n,2) == 1) { 554 | p.y += columnradius; 555 | } 556 | // At this point, we have turned 45 degrees and moved at a point on the 557 | // diagonal that we want to place the columns on. 558 | // Now, repeat the domain along this direction and place a circle. 559 | pMod1(p.y, columnradius*2); 560 | float result = length(p) - columnradius; 561 | result = min(result, p.x); 562 | result = min(result, a); 563 | return min(result, b); 564 | } else { 565 | return min(a, b); 566 | } 567 | } 568 | 569 | float fOpDifferenceColumns(float a, float b, float r, float n) { 570 | a = -a; 571 | float m = min(a, b); 572 | //avoid the expensive computation where not needed (produces discontinuity though) 573 | if ((a < r) && (b < r)) { 574 | vec2 p = vec2(a, b); 575 | float columnradius = r*sqrt(2)/n/2.0; 576 | columnradius = r*sqrt(2)/((n-1)*2+sqrt(2)); 577 | 578 | pR45(p); 579 | p.y += columnradius; 580 | p.x -= sqrt(2)/2*r; 581 | p.x += -columnradius*sqrt(2)/2; 582 | 583 | if (mod(n,2) == 1) { 584 | p.y += columnradius; 585 | } 586 | pMod1(p.y,columnradius*2); 587 | 588 | float result = -length(p) + columnradius; 589 | result = max(result, p.x); 590 | result = min(result, a); 591 | return -min(result, b); 592 | } else { 593 | return -m; 594 | } 595 | } 596 | 597 | float fOpIntersectionColumns(float a, float b, float r, float n) { 598 | return fOpDifferenceColumns(a,-b,r, n); 599 | } 600 | 601 | // The "Stairs" flavour produces n-1 steps of a staircase: 602 | // much less stupid version by paniq 603 | float fOpUnionStairs(float a, float b, float r, float n) { 604 | float s = r/n; 605 | float u = b-r; 606 | return min(min(a,b), 0.5 * (u + a + abs ((mod (u - a + s, 2 * s)) - s))); 607 | } 608 | 609 | // We can just call Union since stairs are symmetric. 610 | float fOpIntersectionStairs(float a, float b, float r, float n) { 611 | return -fOpUnionStairs(-a, -b, r, n); 612 | } 613 | 614 | float fOpDifferenceStairs(float a, float b, float r, float n) { 615 | return -fOpUnionStairs(-a, b, r, n); 616 | } 617 | 618 | 619 | // Similar to fOpUnionRound, but more lipschitz-y at acute angles 620 | // (and less so at 90 degrees). Useful when fudging around too much 621 | // by MediaMolecule, from Alex Evans' siggraph slides 622 | float fOpUnionSoft(float a, float b, float r) { 623 | float e = max(r - abs(a - b), 0); 624 | return min(a, b) - e*e*0.25/r; 625 | } 626 | 627 | 628 | // produces a cylindical pipe that runs along the intersection. 629 | // No objects remain, only the pipe. This is not a boolean operator. 630 | float fOpPipe(float a, float b, float r) { 631 | return length(vec2(a, b)) - r; 632 | } 633 | 634 | // first object gets a v-shaped engraving where it intersect the second 635 | float fOpEngrave(float a, float b, float r) { 636 | return max(a, (a + r - abs(b))*sqrt(0.5)); 637 | } 638 | 639 | // first object gets a capenter-style groove cut out 640 | float fOpGroove(float a, float b, float ra, float rb) { 641 | return max(a, min(a + ra, rb - abs(b))); 642 | } 643 | 644 | // first object gets a capenter-style tongue attached 645 | float fOpTongue(float a, float b, float ra, float rb) { 646 | return min(a, max(a - ra, abs(b) - rb)); 647 | } 648 | 649 | 650 | 651 | float sceneSDF(vec3 p); 652 | 653 | 654 | /** 655 | * Using the gradient of the SDF, estimate the normal on the surface at point p. 656 | */ 657 | vec3 estimateNormal(vec3 p) { 658 | return normalize(vec3( 659 | sceneSDF(vec3(p.x + EPSILON, p.y, p.z)) - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)), 660 | sceneSDF(vec3(p.x, p.y + EPSILON, p.z)) - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)), 661 | sceneSDF(vec3(p.x, p.y, p.z + EPSILON)) - sceneSDF(vec3(p.x, p.y, p.z - EPSILON)) 662 | )); 663 | } 664 | -------------------------------------------------------------------------------- /main.frag: -------------------------------------------------------------------------------- 1 | /** 2 | * Return the normalized direction to march in from the eye point for a single pixel. 3 | * 4 | * fieldOfView: vertical field of view in degrees 5 | * size: resolution of the output image 6 | * fragCoord: the x,y coordinate of the pixel in the output image 7 | */ 8 | vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) { 9 | vec2 xy = fragCoord - size / 2.0; 10 | float z = size.y / tan(radians(fieldOfView) / 2.0); 11 | return normalize(vec3(xy, -z)); 12 | } 13 | 14 | 15 | /** 16 | * Return a transform matrix that will transform a ray from view space 17 | * to world coordinates, given the eye point, the camera target, and an up vector. 18 | * 19 | * This assumes that the center of the camera is aligned with the negative z axis in 20 | * view space when calculating the ray marching direction. See rayDirection. 21 | */ 22 | mat3 viewMatrix(vec3 eye, vec3 center, vec3 up) { 23 | // Based on gluLookAt man page 24 | vec3 f = normalize(center - eye); 25 | vec3 s = normalize(cross(f, up)); 26 | vec3 u = cross(s, f); 27 | return mat3(s, u, -f); 28 | } 29 | 30 | 31 | out vec4 FragColor; 32 | void main( void ) 33 | { 34 | vec4 outColor = vec4(0.0,0.0,0.0,1.0); 35 | vec3 viewDir = rayDirection(45.0, iResolution.xy, gl_FragCoord.xy); 36 | mat3 viewToWorld = viewMatrix(eye, target, up); 37 | vec3 worldDir = viewToWorld * viewDir; 38 | 39 | mainImage( outColor, gl_FragCoord.xy, eye, worldDir); 40 | FragColor = outColor; 41 | color = outColor; 42 | } 43 | -------------------------------------------------------------------------------- /render_engine.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import gpu 3 | import bgl 4 | from mathutils import Matrix, Vector 5 | from gpu_extras.batch import batch_for_shader 6 | from gpu_extras.presets import draw_circle_2d 7 | 8 | import imbuf 9 | 10 | import numpy as np 11 | import sys 12 | 13 | import sdf_engine 14 | 15 | 16 | MAX_MARCHING_STEPS = 255 17 | MIN_DIST = 0.0 18 | MAX_DIST = 100.0 19 | EPSILON = 0.0001 20 | 21 | ''' TODO resizing area error fix''' 22 | class SDFRenderEngine(bpy.types.RenderEngine): 23 | # These three members are used by blender to set up the 24 | # RenderEngine; define its internal name, visible name and capabilities. 25 | bl_idname = "SDF_ENGINE" 26 | bl_label = "SDF Engine" 27 | bl_use_preview = True 28 | 29 | # Init is called whenever a new render engine instance is created. Multiple 30 | # instances may exist at the same time, for example for a viewport and final 31 | # render. 32 | def __init__(self): 33 | self.scene_data = None 34 | self.draw_data = None 35 | 36 | # When the render engine instance is destroy, this is called. Clean up any 37 | # render engine data here, for example stopping running render threads. 38 | def __del__(self): 39 | pass 40 | 41 | def update_render_passes(self, scene=None, renderlayer=None): 42 | self.register_pass(scene, renderlayer, "Combined", 4, "RGBA", 'COLOR') 43 | self.register_pass(scene, renderlayer, "Depth", 1, "X", 'VALUE') 44 | 45 | # This is the method called by Blender for both final renders (F12) and 46 | # small preview for materials, world and lights. 47 | def render(self, depsgraph): 48 | scene = depsgraph.scene 49 | scale = scene.render.resolution_percentage / 100.0 50 | self.size_x = int(scene.render.resolution_x * scale) 51 | self.size_y = int(scene.render.resolution_y * scale) 52 | self.is_rendering = True 53 | # Fill the render result with a flat color. The framebuffer is 54 | # defined as a list of pixels, each pixel itself being a list of 55 | # R,G,B,A values. 56 | if self.is_preview: 57 | color = [0.1, 0.2, 0.1, 1.0] 58 | else: 59 | color = [0.2, 0.1, 0.1, 1.0] 60 | 61 | pixel_count = self.size_x * self.size_y 62 | rect = [color] * pixel_count 63 | 64 | m = scene.camera.matrix_world.inverted() 65 | m = np.array(m).flatten('F').tolist() 66 | 67 | eye = scene.camera.location 68 | up = scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0)) 69 | target = scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0)) 70 | time = scene.frame_current/24 71 | 72 | vertex_shader, fragment_shader = get_shaders() 73 | 74 | sdf_engine.glRenderInit(self.size_x, self.size_y) 75 | shader = sdf_engine.glCompileProgram(vertex_shader, fragment_shader) 76 | sdf_engine.glUseProgram(shader) 77 | image_path = '/home/night-queen/Videos/Youtube/noise_medium.png' 78 | vb, ebo, vid = sdf_engine.glCreateBuffers(image_path, self.size_x, self.size_y) 79 | 80 | args = {"dimension": [self.size_x, self.size_y], "eye": list(eye), "target": list(target), "up": list(up), "vbo": vb, "ebo": ebo, "matrix": m, "time": time, "vid": vid, "max_marching_steps": MAX_MARCHING_STEPS, "min_dist": MIN_DIST, "max_dist": MAX_DIST, "epsilon": EPSILON, "custom_uniforms":[{'key':'some','type':'1f','value':1.0}]} 81 | sdf_engine.glDraw(args) 82 | 83 | 84 | 85 | #TODO try parallelizing later 86 | pixels, depth = sdf_engine.glRenderResult(self.size_x, self.size_y) 87 | 88 | # Here we write the pixel values to the RenderResult 89 | result = self.begin_result(0, 0, self.size_x, self.size_y) 90 | layer = result.layers[0].passes["Combined"] 91 | layer.rect = pixels 92 | layer = result.layers[0].passes["Depth"] 93 | layer.rect = depth 94 | self.end_result(result) 95 | 96 | # For viewport renders, this method gets called once at the start and 97 | # whenever the scene or 3D viewport changes. This method is where data 98 | # should be read from Blender in the same thread. Typically a render 99 | # thread will be started to do the work while keeping Blender responsive. 100 | def view_update(self, context, depsgraph): 101 | region = context.region 102 | view3d = context.space_data 103 | scene = depsgraph.scene 104 | 105 | # Get viewport dimensions 106 | dimensions = region.width, region.height 107 | #self.draw_data = EngineDrawData(dimensions) 108 | 109 | if not self.scene_data: 110 | # First time initialization 111 | self.scene_data = [] 112 | first_time = True 113 | 114 | # Loop over all datablocks used in the scene. 115 | for datablock in depsgraph.ids: 116 | pass 117 | else: 118 | first_time = False 119 | 120 | # Test which datablocks changed 121 | for update in depsgraph.updates: 122 | print("Datablock updated: ", update.id.name) 123 | 124 | # Test if any material was added, removed or changed. 125 | if depsgraph.id_type_updated('MATERIAL'): 126 | print("Materials updated") 127 | 128 | # Loop over all object instances in the scene. 129 | if first_time or depsgraph.id_type_updated('OBJECT'): 130 | for instance in depsgraph.object_instances: 131 | pass 132 | 133 | # For viewport renders, this method is called whenever Blender redraws 134 | # the 3D viewport. The renderer is expected to quickly draw the render 135 | # with OpenGL, and not perform other expensive work. 136 | # Blender will draw overlays for selection and editing on top of the 137 | # rendered image automatically. 138 | def view_draw(self, context, depsgraph): 139 | region = context.region 140 | scene = depsgraph.scene 141 | 142 | # Get viewport dimensions 143 | dimensions = region.width, region.height 144 | if not self.draw_data or self.draw_data.dimensions != dimensions: 145 | #self.is_rendering = False 146 | self.draw_data = EngineDrawData(dimensions) 147 | self.draw_data.draw() 148 | 149 | 150 | 151 | 152 | 153 | class EngineDrawData: 154 | def __init__(self, dimensions): 155 | self.dimensions = dimensions 156 | image_path = '/home/night-queen/Videos/Youtube/noise_medium.png' 157 | vertex_shader, fragment_shader = get_shaders() 158 | self.program = sdf_engine.glCompileProgram(vertex_shader, fragment_shader) 159 | self.vb, self.ebo, self.vid = sdf_engine.glCreateBuffers(image_path, self.dimensions[0], self.dimensions[1]) 160 | 161 | 162 | def draw(self): 163 | region = bpy.context.region_data 164 | target = region.view_location 165 | view_projection_matrix = region.perspective_matrix 166 | view_rotation = region.view_rotation 167 | 168 | eye = region.view_matrix.inverted().translation 169 | up = region.view_rotation @ Vector((0.0, 1.0, 0.0)) 170 | time = bpy.context.scene.frame_current/24 171 | 172 | m = np.array(view_projection_matrix).flatten('F').tolist() 173 | 174 | 175 | sdf_engine.glUseProgram(self.program) 176 | 177 | min_dist = bpy.context.scene.sdf.min_dist 178 | max_dist = bpy.context.scene.sdf.max_dist 179 | epsilon = bpy.context.scene.sdf.epsilon 180 | max_marching_steps = bpy.context.scene.sdf.max_marching_steps 181 | 182 | args = {"dimension": list(self.dimensions), "eye": list(eye), "target": list(target), "up": list(up), "vbo": self.vb, "ebo": self.ebo, "matrix": m, "time": time, "vid": self.vid, "max_marching_steps": max_marching_steps, "min_dist": min_dist, "max_dist": max_dist, "epsilon": epsilon, "custom_uniforms":[{'key':'some','type':'1f','value':1.0}]} 183 | sdf_engine.glDraw(args) 184 | 185 | 186 | def get_shaders(): 187 | vertex_shader = bpy.data.texts['shader_sdf.vert'].as_string() 188 | try: 189 | scene_sdf = bpy.context.scene.sdf.shader_program.as_string() 190 | except: 191 | scene_sdf = '' 192 | uniforms = bpy.data.texts['uniforms.frag'].as_string() 193 | if bpy.context.scene.sdf.template_type == 'DEFAULT': 194 | library = bpy.data.texts['library.frag'].as_string() 195 | main = bpy.data.texts['main.frag'].as_string() 196 | else: 197 | library = main = '' 198 | fragment_shader = uniforms + library + scene_sdf + main 199 | return vertex_shader, fragment_shader 200 | 201 | 202 | 203 | 204 | template_items = [ 205 | ("DEFAULT", "Default", "", 1), 206 | ("UNIFORM_ONLY", "Uniforms Only", "", 2), 207 | ] 208 | 209 | def update_shader(self, context): 210 | for area in context.screen.areas: 211 | if area.type == 'VIEW_3D': 212 | space = area.spaces[0] 213 | break 214 | space.shading.type = 'SOLID' 215 | space.shading.type = 'MATERIAL' 216 | 217 | class SDFProperties(bpy.types.PropertyGroup): 218 | shader_program: bpy.props.PointerProperty(name='Shader Program', type=bpy.types.Text, update=update_shader) 219 | template_type: bpy.props.EnumProperty(name='Template', items=template_items, update=update_shader) 220 | max_marching_steps: bpy.props.IntProperty(name='Max Marching Steps', min=1, default=255) 221 | min_dist: bpy.props.FloatProperty(name='Min Distance', default=0.0, min=0) 222 | max_dist: bpy.props.FloatProperty(name='Max Distance', default=100.0, min=0) 223 | epsilon: bpy.props.FloatProperty(name='Epsilon', default=0.0001, min=0) 224 | channel_0: bpy.props.CollectionProperty(name='Channel 0', type=bpy.types.OperatorFileListElement) 225 | 226 | class SDFEnginePanel(bpy.types.Panel): 227 | """SDF Engine""" 228 | bl_label = "SDF Engine" 229 | bl_idname = "OBJECT_PT_sdf_engine" 230 | bl_space_type = 'PROPERTIES' 231 | bl_region_type = 'WINDOW' 232 | bl_context = "render" 233 | COMPAT_ENGINES = {'SDF_ENGINE'} 234 | 235 | def draw(self, context): 236 | layout = self.layout 237 | props = context.scene.sdf 238 | split = layout.split() 239 | col = split.column() 240 | col.label(text='Template') 241 | col.label(text='Shader Program') 242 | col.label(text='Max Marching Steps') 243 | col.label(text='Min Distance') 244 | col.label(text='Max Distance') 245 | col.label(text='Epsilon') 246 | col = split.column() 247 | col.prop(props, 'template_type', text='') 248 | col.prop(props, 'shader_program', text='') 249 | col.prop(props, 'max_marching_steps', text='') 250 | col.prop(props, 'min_dist', text='') 251 | col.prop(props, 'max_dist', text='') 252 | col.prop(props, 'epsilon', text='') 253 | col.prop(props, 'channel_0', text='') 254 | @classmethod 255 | def poll(cls, context): 256 | return context.engine in cls.COMPAT_ENGINES 257 | 258 | 259 | # RenderEngines also need to tell UI Panels that they are compatible with. 260 | # We recommend to enable all panels marked as BLENDER_RENDER, and then 261 | # exclude any panels that are replaced by custom panels registered by the 262 | # render engine, or that are not supported. 263 | def get_panels(): 264 | exclude_panels = { 265 | 'VIEWLAYER_PT_filter', 266 | 'VIEWLAYER_PT_layer_passes', 267 | } 268 | 269 | panels = [] 270 | for panel in bpy.types.Panel.__subclasses__(): 271 | if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: 272 | if panel.__name__ not in exclude_panels: 273 | panels.append(panel) 274 | 275 | return panels 276 | 277 | classes = [SDFRenderEngine, SDFEnginePanel, SDFProperties] 278 | 279 | def register(): 280 | # Register the RenderEngine 281 | for cls in classes: 282 | bpy.utils.register_class(cls) 283 | bpy.types.Scene.sdf = bpy.props.PointerProperty(type=SDFProperties) 284 | 285 | for panel in get_panels(): 286 | panel.COMPAT_ENGINES.add('SDF_ENGINE') 287 | 288 | def unregister(): 289 | for cls in classes: 290 | bpy.utils.unregister_class(cls) 291 | 292 | for panel in get_panels(): 293 | if 'SDF_ENGINE' in panel.COMPAT_ENGINES: 294 | panel.COMPAT_ENGINES.remove('SDF_ENGINE') 295 | 296 | 297 | if __name__ == "__main__": 298 | register() 299 | -------------------------------------------------------------------------------- /sdf_engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include "cglm/cglm.h" 10 | 11 | #define STB_IMAGE_IMPLEMENTATION 12 | #include "stb_image.h" 13 | 14 | #define p 1.f 15 | GLuint vertexArrayID, programID, vertexBuffer; 16 | 17 | GLuint channel_0, channel_1, channel_2, channel_3; 18 | 19 | // Assumes the file exists and will seg. fault otherwise. 20 | const GLchar *load_shader_source(const char *filename) { 21 | FILE *file = fopen(filename, "r"); // open 22 | fseek(file, 0L, SEEK_END); // find the end 23 | size_t size = ftell(file); // get the size in bytes 24 | GLchar *shaderSource = calloc(1, size); // allocate enough bytes 25 | rewind(file); // go back to file beginning 26 | int ret = fread(shaderSource, size, sizeof(char), file); // read each char into ourblock 27 | fclose(file); // close the stream 28 | return shaderSource; 29 | } 30 | 31 | void createTextures(const char* image_path) { 32 | int width, height, nrChannels; 33 | unsigned char *data = stbi_load(image_path, &width, &height, &nrChannels, 0); 34 | glGenTextures(1, &channel_0); 35 | glActiveTexture(GL_TEXTURE0); 36 | glBindTexture(GL_TEXTURE_2D, channel_0); 37 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); 38 | 39 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 40 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 41 | glGenerateMipmap(GL_TEXTURE_2D); 42 | } 43 | 44 | 45 | GLuint loadShaders(const char * vertexSourcePointer,const char * fragmentSourcePointer){ 46 | 47 | // Create the shaders 48 | GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER); 49 | GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); 50 | 51 | GLint result = GL_FALSE; 52 | int infoLogLength; 53 | 54 | GLchar const* files_v[] = {vertexSourcePointer}; 55 | // Compile Vertex Shader 56 | printf("Compiling Vertex Shader\n"); 57 | glShaderSource(vertexShaderID, 1, files_v , NULL); 58 | glCompileShader(vertexShaderID); 59 | 60 | // Check Vertex Shader 61 | glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result); 62 | glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); 63 | if ( infoLogLength > 0 ){ 64 | printf("Vertex Shader Compilation Failed\n"); 65 | char errorLog[512]; 66 | glGetShaderInfoLog(vertexShaderID, infoLogLength, &infoLogLength, &errorLog[0]); 67 | printf("%s\n", errorLog); 68 | printf("%d\n", result); 69 | } 70 | 71 | 72 | GLchar const* files_f[] = {fragmentSourcePointer}; 73 | // Compile Fragment Shader 74 | printf("Compiling Fragment Shader\n"); 75 | glShaderSource(fragmentShaderID, 1, files_f , NULL); 76 | glCompileShader(fragmentShaderID); 77 | 78 | // Check Fragment Shader 79 | glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result); 80 | glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); 81 | if ( infoLogLength > 0 ){ 82 | printf("Fragment Shader Compilation Failed\n"); 83 | char errorLog[512]; 84 | glGetShaderInfoLog(fragmentShaderID, infoLogLength, &infoLogLength, &errorLog[0]); 85 | printf("%s\n", errorLog); 86 | printf("%d\n", result); 87 | } 88 | 89 | 90 | 91 | // Link the program 92 | printf("Linking program\n"); 93 | GLuint programID = glCreateProgram(); 94 | glAttachShader(programID, vertexShaderID); 95 | glAttachShader(programID, fragmentShaderID); 96 | glLinkProgram(programID); 97 | 98 | // Check the program 99 | glGetProgramiv(programID, GL_LINK_STATUS, &result); 100 | glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength); 101 | if ( infoLogLength > 0 ){ 102 | printf("Shader Linking Failed\n"); 103 | char errorLog[512]; 104 | glGetProgramInfoLog(programID, infoLogLength, &infoLogLength, &errorLog[0]); 105 | printf("%s\n", errorLog); 106 | printf("%d\n", result); 107 | } 108 | 109 | 110 | glDetachShader(programID, vertexShaderID); 111 | glDetachShader(programID, fragmentShaderID); 112 | 113 | glDeleteShader(vertexShaderID); 114 | glDeleteShader(fragmentShaderID); 115 | 116 | return programID; 117 | } 118 | 119 | static PyObject* glCompileProgram(PyObject* self, PyObject* args) { 120 | GLchar *vertex_shader, *fragment_shader; 121 | if (!PyArg_ParseTuple(args, "ss", &vertex_shader, &fragment_shader)) 122 | return NULL; 123 | // Create and compile our GLSL program from the shaders 124 | //programID = loadShaders( "/home/night-queen/Downloads/of_v0.10.1_linux64gcc6_release/apps/myApps/mySketch/bin/data/SimpleVertexShader.vertexshader", "/home/night-queen/Downloads/of_v0.10.1_linux64gcc6_release/apps/myApps/mySketch/bin/data/SimpleFragmentShader.fragmentshader" ); 125 | programID = loadShaders(vertex_shader, fragment_shader); 126 | return Py_BuildValue("i", programID); 127 | } 128 | 129 | static PyObject* glProgram(PyObject* self, PyObject* args) { 130 | GLuint programID; 131 | if (!PyArg_ParseTuple(args, "i", &programID)) 132 | return NULL; 133 | glUseProgram(0); 134 | glUseProgram(programID); 135 | return Py_None; 136 | } 137 | 138 | static PyObject* glRenderInit(PyObject* self, PyObject* args) { 139 | GLuint width, height; 140 | if (!PyArg_ParseTuple(args, "ii", &width, &height)) 141 | return NULL; 142 | GLuint fb, color, depth; 143 | if (!glfwInit()) { 144 | 145 | printf("Initialising GLFW failed\n"); 146 | 147 | } 148 | 149 | GLFWwindow* window; 150 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 151 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 152 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 153 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); 154 | window = glfwCreateWindow( width, height, "Hidden Window", NULL, NULL); 155 | glfwMakeContextCurrent(window); 156 | glewExperimental = 1; 157 | if (glewInit() != GLEW_OK) { 158 | printf("GLEW initialisation failed\n"); 159 | } 160 | 161 | 162 | glGenFramebuffers(1, &fb); 163 | glGenTextures(1, &color); 164 | glGenRenderbuffers(1, &depth); 165 | 166 | glBindFramebuffer(GL_FRAMEBUFFER, fb); 167 | glActiveTexture(GL_TEXTURE0); 168 | glBindTexture(GL_TEXTURE_2D, color); 169 | glTexImage2D( GL_TEXTURE_2D, 170 | 0, 171 | GL_SRGB_ALPHA, 172 | width, height, 173 | 0, 174 | GL_RGBA, 175 | GL_FLOAT, 176 | NULL); 177 | 178 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 179 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 180 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); 181 | 182 | glBindRenderbuffer(GL_RENDERBUFFER, depth); 183 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); 184 | glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); 185 | GLenum DrawBuffers[2] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT}; 186 | glDrawBuffers(2, DrawBuffers); 187 | 188 | GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); 189 | status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); 190 | switch(status) { 191 | case GL_FRAMEBUFFER_COMPLETE: 192 | break; 193 | case GL_FRAMEBUFFER_UNSUPPORTED: 194 | break; 195 | default: 196 | fputs("Framebuffer Error\n", stderr); 197 | printf("%d\n", glGetError()); 198 | } 199 | return Py_None; 200 | } 201 | 202 | static PyObject* glRenderResult(PyObject* self, PyObject* args) { 203 | // switch back to window-system-provided framebuffer 204 | GLuint width, height; 205 | if (!PyArg_ParseTuple(args, "ii", &width, &height)) 206 | return NULL; 207 | GLfloat *pixels, *depth; 208 | pixels = (GLfloat*) malloc(width * height * 4 * sizeof(GLfloat)); 209 | depth = (GLfloat*) malloc(width * height * sizeof(GLfloat)); 210 | //glReadBuffer(GL_NONE); 211 | glReadPixels( 0,0, width, height, GL_RGBA, GL_FLOAT, pixels); 212 | glReadPixels( 0,0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); 213 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 214 | glBindTexture(GL_TEXTURE_2D, 0); 215 | 216 | int N = width * height; 217 | PyObject* pixel_list = PyList_New(N); 218 | for (int i = 0; i < N; ++i) 219 | { 220 | PyObject* pixel_val = Py_BuildValue("ffff", pixels[i*4], pixels[i*4+1], pixels[i*4+2], pixels[i*4+3]); 221 | PyList_SetItem(pixel_list, i, pixel_val); 222 | } 223 | PyObject* depth_list = PyList_New(N); 224 | for (int i = 0; i < N; ++i) 225 | { 226 | PyObject* depth_val = Py_BuildValue("(f)", depth[i]); 227 | PyList_SetItem(depth_list, i, depth_val); 228 | } 229 | glfwTerminate(); 230 | free(pixels); 231 | free(depth); 232 | PyObject *ret_val = PyList_New(2); 233 | PyList_SetItem(ret_val, 0, pixel_list); 234 | PyList_SetItem(ret_val, 1, depth_list); 235 | return ret_val; 236 | } 237 | 238 | static PyObject* glCreateBuffer(PyObject* self, PyObject* args) { 239 | GLuint width, height; 240 | GLchar* image_path; 241 | if (!PyArg_ParseTuple(args, "sii", &image_path, &width, &height)) 242 | return NULL; 243 | 244 | createTextures(image_path); 245 | 246 | 247 | glGenVertexArrays(1, &vertexArrayID); 248 | glBindVertexArray(vertexArrayID); 249 | 250 | static const GLfloat vertices[] = { 251 | p, p, 0.0f, 1.0f, 1.0f, // top right 252 | p, -p, 0.0f, 1.0f, 0.0f, // bottom right 253 | -p, -p, 0.0f, 0.0f, 0.0f, // bottom left 254 | -p, p, 0.0f, 0.0f, 1.0f // top left 255 | }; 256 | GLuint indices[] = { // note that we start from 0! 257 | 0, 1, 3, // first triangle 258 | 1, 2, 3 // second triangle 259 | }; 260 | 261 | glGenBuffers(1, &vertexBuffer); 262 | glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); 263 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 264 | 265 | unsigned int EBO; 266 | glGenBuffers(1, &EBO); 267 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 268 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 269 | 270 | return Py_BuildValue("(iii)", vertexBuffer, EBO, vertexArrayID); 271 | } 272 | 273 | static PyObject* glCreateTexture(PyObject* self, PyObject* args) { 274 | GLuint width, height; 275 | GLuint fb, color, depth; 276 | if (!PyArg_ParseTuple(args, "ii", &width, &height)) 277 | return NULL; 278 | 279 | glGenFramebuffers(1, &fb); 280 | glGenTextures(1, &color); 281 | glGenRenderbuffers(1, &depth); 282 | 283 | glBindFramebuffer(GL_FRAMEBUFFER, fb); 284 | glActiveTexture(GL_TEXTURE0); 285 | glBindTexture(GL_TEXTURE_2D, color); 286 | glTexImage2D( GL_TEXTURE_2D, 287 | 0, 288 | GL_RGBA, 289 | width, height, 290 | 0, 291 | GL_RGBA, 292 | GL_FLOAT, 293 | NULL); 294 | 295 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 296 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 297 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); 298 | 299 | //glBindRenderbuffer(GL_RENDERBUFFER, depth); 300 | //glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); 301 | //glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); 302 | GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; 303 | glDrawBuffers(1, DrawBuffers); 304 | 305 | GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); 306 | status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); 307 | switch(status) { 308 | case GL_FRAMEBUFFER_COMPLETE: 309 | break; 310 | case GL_FRAMEBUFFER_UNSUPPORTED: 311 | break; 312 | default: 313 | fputs("Framebuffer Error\n", stderr); 314 | printf("%d\n", glGetError()); 315 | } 316 | glBindTexture(GL_TEXTURE_2D, 0); 317 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 318 | return Py_BuildValue("(ii)", color, fb); 319 | } 320 | 321 | 322 | static PyObject* glDraw(PyObject* self, PyObject* args) { 323 | PyObject *float_list, *eye, *target, *up, *vbo, *ebo, *vid, *dimension, *timeObj, *custom_uniforms; 324 | int pr_length; 325 | GLfloat *pr; 326 | GLuint vertexBuffer, EBO, width, height, vertexArrayID, maxMarchingSteps; 327 | GLfloat time, minDist, maxDist, epsilon; 328 | GLfloat eyeX, eyeY, eyeZ, targetX, targetY, targetZ, upX, upY, upZ; 329 | 330 | PyObject *dict; 331 | if (!PyArg_ParseTuple(args, "O", &dict)) { 332 | return NULL; 333 | } 334 | float_list = PyDict_GetItemString(dict,"matrix"); 335 | eye = PyDict_GetItemString(dict, "eye"); 336 | target = PyDict_GetItemString(dict, "target"); 337 | up = PyDict_GetItemString(dict, "up"); 338 | vbo = PyDict_GetItemString(dict, "vbo"); 339 | ebo = PyDict_GetItemString(dict, "ebo"); 340 | vid = PyDict_GetItemString(dict, "vid"); 341 | dimension = PyDict_GetItemString(dict, "dimension"); 342 | timeObj = PyDict_GetItemString(dict, "time"); 343 | custom_uniforms = PyDict_GetItemString(dict, "custom_uniforms"); 344 | 345 | maxMarchingSteps = PyLong_AsLong(PyDict_GetItemString(dict, "max_marching_steps")); 346 | minDist = PyFloat_AsDouble(PyDict_GetItemString(dict, "min_dist")); 347 | maxDist = PyFloat_AsDouble(PyDict_GetItemString(dict, "max_dist")); 348 | epsilon = PyFloat_AsDouble(PyDict_GetItemString(dict, "epsilon")); 349 | 350 | time = PyFloat_AsDouble(timeObj); 351 | 352 | eyeX = PyFloat_AsDouble(PyList_GetItem(eye, 0)); 353 | eyeY = PyFloat_AsDouble(PyList_GetItem(eye, 1)); 354 | eyeZ = PyFloat_AsDouble(PyList_GetItem(eye, 2)); 355 | 356 | targetX = PyFloat_AsDouble(PyList_GetItem(target, 0)); 357 | targetY = PyFloat_AsDouble(PyList_GetItem(target, 1)); 358 | targetZ = PyFloat_AsDouble(PyList_GetItem(target, 2)); 359 | 360 | upX = PyFloat_AsDouble(PyList_GetItem(up, 0)); 361 | upY = PyFloat_AsDouble(PyList_GetItem(up, 1)); 362 | upZ = PyFloat_AsDouble(PyList_GetItem(up, 2)); 363 | 364 | width = PyLong_AsLong(PyList_GetItem(dimension, 0)); 365 | height = PyLong_AsLong(PyList_GetItem(dimension, 1)); 366 | 367 | vertexBuffer = PyLong_AsLong(vbo); 368 | EBO = PyLong_AsLong(ebo); 369 | vertexArrayID = PyLong_AsLong(vid); 370 | 371 | pr_length = PyObject_Length(float_list); 372 | if (pr_length < 0) 373 | return NULL; 374 | pr = (GLfloat *) malloc(sizeof(GLfloat *) * pr_length); 375 | if (pr == NULL) 376 | return NULL; 377 | for (int index = 0; index < pr_length; index++) { 378 | PyObject *item; 379 | item = PyList_GetItem(float_list, index); 380 | if (!PyFloat_Check(item)) 381 | pr[index] = 0.0; 382 | pr[index] = PyFloat_AsDouble(item); 383 | } 384 | 385 | //Get custom uniforms 386 | pr_length = PyObject_Length(custom_uniforms); 387 | for(int i = 0; i < pr_length; ++i) { 388 | PyObject *item = PyList_GetItem(custom_uniforms, i); 389 | PyObject *uniform_variable = PyDict_GetItemString(item, "key"); 390 | PyObject *uniform_value = PyDict_GetItemString(item, "value"); 391 | PyObject *uniform_type = PyDict_GetItemString(item, "type"); 392 | const char* type = PyBytes_AsString(PyUnicode_AsUTF8String(uniform_type)); 393 | const char* key = PyBytes_AsString(PyUnicode_AsUTF8String(uniform_variable)); 394 | 395 | unsigned int loc = glGetUniformLocation(programID, key); 396 | if (strcmp(type, "1f") == 0) { 397 | GLfloat value = PyFloat_AsDouble(uniform_value); 398 | glUniform1f(loc, value); 399 | } 400 | else if (strcmp(type, "2f") == 0) { 401 | glUniform2f(loc, PyFloat_AsDouble(PyList_GetItem(uniform_value, 0)), 402 | PyFloat_AsDouble(PyList_GetItem(uniform_value, 1))); 403 | } 404 | else if (strcmp(type, "3f") == 0) { 405 | glUniform3f(loc, PyFloat_AsDouble(PyList_GetItem(uniform_value, 0)), 406 | PyFloat_AsDouble(PyList_GetItem(uniform_value, 1)), 407 | PyFloat_AsDouble(PyList_GetItem(uniform_value, 1))); 408 | }else if (strcmp(type, "1i") == 0) { 409 | GLfloat value = PyLong_AsLong(uniform_value); 410 | glUniform1i(loc, value); 411 | } 412 | } 413 | 414 | glViewport(0,0, width, height); 415 | 416 | ///glClearColor(0.0f, 0.0f, 0.4f, 0.0f); 417 | glEnable(GL_BLEND); 418 | glEnable(GL_DEPTH_TEST); 419 | glDepthFunc(GL_LESS); 420 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 421 | 422 | // Clear the screen 423 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 424 | 425 | glBindTexture(GL_TEXTURE_2D, channel_0); 426 | 427 | unsigned int maxMarchingStepsLoc = glGetUniformLocation(programID, "MAX_MARCHING_STEPS"); 428 | glUniform1i(maxMarchingStepsLoc, maxMarchingSteps); 429 | 430 | unsigned int minDistLoc = glGetUniformLocation(programID, "MIN_DIST"); 431 | glUniform1f(minDistLoc, minDist); 432 | 433 | unsigned int maxDistLoc = glGetUniformLocation(programID, "MAX_DIST"); 434 | glUniform1f(maxDistLoc, maxDist); 435 | 436 | unsigned int epsilonLoc = glGetUniformLocation(programID, "EPSILON"); 437 | glUniform1f(epsilonLoc, epsilon); 438 | 439 | unsigned int viewProjectionLoc = glGetUniformLocation(programID, "modelViewProjectionMatrix"); 440 | glUniformMatrix4fv(viewProjectionLoc, 1, GL_FALSE, pr); 441 | 442 | unsigned int cameraMatrixLoc = glGetUniformLocation(programID, "tCameraMatrix"); 443 | glUniformMatrix4fv(cameraMatrixLoc, 1, GL_FALSE, pr); 444 | 445 | unsigned int resolutionLoc = glGetUniformLocation(programID, "iResolution"); 446 | glUniform3f(resolutionLoc, width, height, 1.0f); 447 | 448 | unsigned int eyeLoc = glGetUniformLocation(programID, "eye"); 449 | glUniform3f(eyeLoc, eyeX, eyeY, eyeZ); 450 | 451 | unsigned int targetLoc = glGetUniformLocation(programID, "target"); 452 | glUniform3f(targetLoc, targetX, targetY, targetZ); 453 | 454 | unsigned int upLoc = glGetUniformLocation(programID, "up"); 455 | glUniform3f(upLoc, upX, upY, upZ); 456 | 457 | unsigned int timeLoc = glGetUniformLocation(programID, "iTime"); 458 | glUniform1f(timeLoc, time); 459 | 460 | unsigned int channelLoc = glGetUniformLocation(programID, "channel"); 461 | glUniform1i(channelLoc, 0); 462 | 463 | 464 | glBindVertexArray(vertexArrayID); 465 | // 1rst attribute buffer : vertices 466 | glEnableVertexAttribArray(0); 467 | glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); 468 | glVertexAttribPointer( 469 | 0, // attribute 0. No particular reason for 0, but must match the layout in the shader. 470 | 3, // size 471 | GL_FLOAT, // type 472 | GL_FALSE, // normalized? 473 | 5 * sizeof(GLfloat), // stride 474 | (void*)0 // array buffer offset 475 | ); 476 | 477 | glEnableVertexAttribArray(1); 478 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat))); 479 | 480 | 481 | // Draw the triangle ! 482 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 483 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 484 | 485 | glDisableVertexAttribArray(1); 486 | glDisableVertexAttribArray(0); 487 | //glDisable(GL_BLEND); 488 | //glDisable(GL_DEPTH_TEST); 489 | 490 | return Py_None; 491 | } 492 | 493 | 494 | static PyMethodDef methods[] = { 495 | {"glCompileProgram", glCompileProgram, METH_VARARGS, "Init opengl context"}, 496 | {"glDraw", glDraw, METH_VARARGS, "Draw Opengl"}, 497 | {"glRenderInit", glRenderInit, METH_VARARGS, "Render initialisation"}, 498 | {"glRenderResult", glRenderResult, METH_VARARGS, "Render end"}, 499 | {"glUseProgram", glProgram, METH_VARARGS, "Use shader program"}, 500 | {"glCreateBuffers", glCreateBuffer, METH_VARARGS, "Create buffers"}, 501 | {"glCreateTexture", glCreateTexture, METH_VARARGS, "Create texture channel"}, 502 | { NULL, NULL, 0, NULL } 503 | }; 504 | 505 | // Our Module Definition struct 506 | static struct PyModuleDef sdf_engine = { 507 | PyModuleDef_HEAD_INIT, 508 | "sdf_engine", 509 | "SDF Render Engine Module", 510 | -1, 511 | methods 512 | }; 513 | 514 | // Initializes our module using our above struct 515 | PyMODINIT_FUNC PyInit_sdf_engine(void) 516 | { 517 | return PyModule_Create(&sdf_engine); 518 | } 519 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | setup(name = 'sdf_engine', version = '1.0', \ 3 | ext_modules = [Extension('sdf_engine', ['sdf_engine.c'], extra_link_args=["-lGLEW", "-lglfw"])]) -------------------------------------------------------------------------------- /shader_sdf.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | precision mediump float; 3 | layout (location = 0) in vec3 position; 4 | out vec2 texCoord; 5 | void main(){ 6 | gl_Position = vec4(position.xy, 0, 1); 7 | texCoord = (position.xy+vec2(1,1))/2.0; 8 | } -------------------------------------------------------------------------------- /uniforms.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec2 texCoord; 3 | 4 | uniform vec3 iResolution; 5 | uniform float iTime; 6 | uniform mat4 tCameraMatrix; 7 | uniform vec3 eye; 8 | uniform vec3 target; 9 | uniform vec3 up; 10 | 11 | uniform sampler2D channel; 12 | 13 | layout(location = 0) out vec4 color; 14 | 15 | uniform int MAX_MARCHING_STEPS; 16 | uniform float MIN_DIST; 17 | uniform float MAX_DIST; 18 | uniform float EPSILON; 19 | --------------------------------------------------------------------------------