|
16 | 16 | <meta property="og:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/gn-sdf-remesh-hero.webp" /> |
17 | 17 | <meta property="og:image:width" content="1280" /> |
18 | 18 | <meta property="og:image:height" content="720" /> |
19 | | - <meta property="og:image:alt" content="A glossy red torus with visibly blocky voxel faceting from the SDF remesh." /> |
| 19 | + <meta property="og:image:alt" content="Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid." /> |
20 | 20 | <meta property="og:site_name" content="Blender Developer Tools" /> |
21 | 21 | <meta name="twitter:card" content="summary_large_image" /> |
22 | 22 | <meta name="twitter:title" content="gn-sdf-remesh — Examples — Blender Developer Tools" /> |
23 | 23 | <meta name="twitter:description" content="A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh." /> |
24 | 24 | <meta name="twitter:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/gn-sdf-remesh-hero.webp" /> |
25 | | - <meta name="twitter:image:alt" content="A glossy red torus with visibly blocky voxel faceting from the SDF remesh." /> |
| 25 | + <meta name="twitter:image:alt" content="Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid." /> |
26 | 26 | <style> |
27 | 27 | /* fonts are deployed by the landing build (docs/fonts/) */ |
28 | 28 | @font-face { font-family: 'Barlow Condensed'; font-weight: 600; font-display: swap; |
@@ -268,8 +268,8 @@ <h1>gn-sdf-remesh</h1> |
268 | 268 | <p>A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh.</p> |
269 | 269 | </header> |
270 | 270 | <main id="main"> |
271 | | - <button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: A glossy red torus with visibly blocky voxel faceting from the SDF remesh."> |
272 | | - <img src="../assets/gn-sdf-remesh-hero.webp" alt="A glossy red torus with visibly blocky voxel faceting from the SDF remesh." width="1280" height="720" /> |
| 271 | + <button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid."> |
| 272 | + <img src="../assets/gn-sdf-remesh-hero.webp" alt="Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid." width="1280" height="720" /> |
273 | 273 | </button> |
274 | 274 | <p class="zoom-hint">Rendered headless by the example itself — click to zoom.</p> |
275 | 275 | <div class="callout"><span class="tag">witnesses</span> An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.</div> |
@@ -377,10 +377,56 @@ <h2>Source</h2> |
377 | 377 | wall.rotation_euler = (<span class="n">1.5708</span>, <span class="n">0</span>, <span class="n">0</span>); bpy.context.collection.objects.link(wall) |
378 | 378 | w = bpy.data.worlds.new(<span class="s">"W"</span>); w.use_nodes = <span class="k">True</span> |
379 | 379 | w.node_tree.nodes[<span class="s">"Background"</span>].inputs[<span class="n">0</span>].default_value = (<span class="n">0.02</span>, <span class="n">0.021</span>, <span class="n">0.025</span>, <span class="n">1</span>); sc.world = w |
380 | | - aim = bpy.data.objects.new(<span class="s">"Aim"</span>, <span class="k">None</span>); aim.location = (<span class="n">0</span>, <span class="n">0</span>, <span class="n">0.5</span>); bpy.context.collection.objects.link(aim) |
381 | | - <span class="c"># off-axis and low: the remesh facets are the API evidence, and they only</span> |
382 | | - <span class="c"># read when a raking key skims them from the side</span> |
383 | | - cam = bpy.data.objects.new(<span class="s">"cam"</span>, bpy.data.cameras.new(<span class="s">"cam"</span>)); cam.location = (<span class="n">2.6</span>, -<span class="n">5.2</span>, <span class="n">1.5</span>) |
| 380 | + <span class="c"># Input beside output. Alone, the remeshed torus read as a glossy inner</span> |
| 381 | + <span class="c"># tube with blotchy highlights: nothing said "remeshed". The source torus</span> |
| 382 | + <span class="c"># (a modifier-free copy of the same datablock) on the left and the SDF</span> |
| 383 | + <span class="c"># result on the right, each under a cage of its own edges, show the</span> |
| 384 | + <span class="c"># thing the check measures: UV-ring topology in, voxel-grid topology</span> |
| 385 | + <span class="c"># out. Render-path scaffolding only; the check ran before this.</span> |
| 386 | + obj.location.x = <span class="n">1.85</span> |
| 387 | + src = bpy.data.objects.new(<span class="s">"SourceTorus"</span>, obj.data.copy()) |
| 388 | + src.location = (-<span class="n">1.85</span>, obj.location.y, obj.location.z) |
| 389 | + bpy.context.collection.objects.link(src) |
| 390 | + <span class="c"># build() centres the torus at z=0.55 with a 0.5 minor radius, so both</span> |
| 391 | + <span class="c"># hovered 5 cm over the floor. Ground each on its own measured bottom:</span> |
| 392 | + <span class="c"># the SDF surface is not exactly the source surface.</span> |
| 393 | + dg0 = bpy.context.evaluated_depsgraph_get() |
| 394 | + em = obj.evaluated_get(dg0).to_mesh() |
| 395 | + obj.location.z -= obj.location.z + min(v.co.z <span class="k">for</span> v <span class="k">in</span> em.vertices) |
| 396 | + obj.evaluated_get(dg0).to_mesh_clear() |
| 397 | + src.location.z = -min(v.co.z <span class="k">for</span> v <span class="k">in</span> src.data.vertices) |
| 398 | + cage_mat = bpy.data.materials.new(<span class="s">"Cage"</span>); cage_mat.use_nodes = <span class="k">True</span> |
| 399 | + cb = cage_mat.node_tree.nodes.get(<span class="s">'Principled BSDF'</span>) |
| 400 | + cb.inputs[<span class="s">'Base Color'</span>].default_value = (<span class="n">0.92</span>, <span class="n">0.82</span>, <span class="n">0.62</span>, <span class="n">1</span>) |
| 401 | + cb.inputs[<span class="s">'Roughness'</span>].default_value = <span class="n">0.4</span> |
| 402 | + cages = [] |
| 403 | + dg = bpy.context.evaluated_depsgraph_get() |
| 404 | + <span class="k">for</span> host, remeshed <span class="k">in</span> ((src, <span class="k">False</span>), (obj, <span class="k">True</span>)): |
| 405 | + <span class="c"># The remeshed cage is the evaluated result frozen into a plain mesh:</span> |
| 406 | + <span class="c"># a live copy of the tree would re-apply its Set Material and paint</span> |
| 407 | + <span class="c"># the cage crimson.</span> |
| 408 | + data = (bpy.data.meshes.new_from_object(obj.evaluated_get(dg)) |
| 409 | + <span class="k">if</span> remeshed <span class="k">else</span> obj.data.copy()) |
| 410 | + cage = bpy.data.objects.new(host.name + <span class="s">"Cage"</span>, data) |
| 411 | + cage.data.materials.clear(); cage.data.materials.append(cage_mat) |
| 412 | + <span class="k">for</span> poly <span class="k">in</span> cage.data.polygons: |
| 413 | + poly.material_index = <span class="n">0</span> |
| 414 | + cage.location = host.location |
| 415 | + wire = cage.modifiers.new(<span class="s">"cage"</span>, <span class="s">'WIREFRAME'</span>) |
| 416 | + wire.thickness = <span class="n">0.012</span>; wire.offset = <span class="n">1.0</span>; wire.use_even_offset = <span class="k">True</span> |
| 417 | + wire.material_offset = <span class="n">0</span> |
| 418 | + bpy.context.collection.objects.link(cage) |
| 419 | + cages.append(cage) |
| 420 | + <span class="c"># Softer than 0.16: at that gloss every voxel facet threw its own hard</span> |
| 421 | + <span class="c"># glint and the highlight broke into blocks that read as artifacts.</span> |
| 422 | + <span class="k">for</span> m <span class="k">in</span> obj.data.materials: |
| 423 | + <span class="k">if</span> m <span class="k">is</span> <span class="k">not</span> <span class="k">None</span> <span class="k">and</span> m.node_tree <span class="k">is</span> <span class="k">not</span> <span class="k">None</span>: |
| 424 | + bsdf = m.node_tree.nodes.get(<span class="s">'Principled BSDF'</span>) |
| 425 | + <span class="k">if</span> bsdf <span class="k">is</span> <span class="k">not</span> <span class="k">None</span>: |
| 426 | + bsdf.inputs[<span class="s">'Roughness'</span>].default_value = <span class="n">0.32</span> |
| 427 | + aim = bpy.data.objects.new(<span class="s">"Aim"</span>, <span class="k">None</span>); aim.location = (<span class="n">0</span>, <span class="n">0</span>, <span class="n">0.45</span>); bpy.context.collection.objects.link(aim) |
| 428 | + <span class="c"># Raised so both donut holes read, and pulled back to hold the pair.</span> |
| 429 | + cam = bpy.data.objects.new(<span class="s">"cam"</span>, bpy.data.cameras.new(<span class="s">"cam"</span>)); cam.location = (<span class="n">1.5</span>, -<span class="n">10.8</span>, <span class="n">5.5</span>) |
384 | 430 | bpy.context.collection.objects.link(cam); sc.camera = cam |
385 | 431 | c = cam.constraints.new(<span class="s">'TRACK_TO'</span>); c.target = aim; c.track_axis = <span class="s">'TRACK_NEGATIVE_Z'</span>; c.up_axis = <span class="s">'UP_Y'</span> |
386 | 432 | <span class="c"># low raking warm key so every facet catches a distinct glint, faint cool</span> |
@@ -466,7 +512,7 @@ <h2>Source</h2> |
466 | 512 | </main> |
467 | 513 | <dialog class="lightbox" id="lightbox" aria-label="Full-size render"> |
468 | 514 | <button class="lightbox-close" type="button" autofocus>Close</button> |
469 | | - <img src="../assets/gn-sdf-remesh-hero.webp" alt="A glossy red torus with visibly blocky voxel faceting from the SDF remesh." /> |
| 515 | + <img src="../assets/gn-sdf-remesh-hero.webp" alt="Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid." /> |
470 | 516 | </dialog> |
471 | 517 | <footer> |
472 | 518 | <div class="statusbar"> |
|
0 commit comments