Explore the Geometry Painter: Transforming 3D Graphics with Real-Time Interaction and Procedural Techniques
Aug 11, 2026
832 views
Introduction to Procedural Geometry
When diving into the world of interactive 3D graphics, few technologies shine as brightly as Three.js combined with WebGPU. This duo is currently transforming how developers approach procedural geometry, opening new avenues for artistic expression and interactivity. The featured work by Chiro Visuals stands as a testament to this transformation, showcasing how procedural systems can create breathtaking visual experiences. In this piece, we’re not just focusing on the technical specifications; instead, we’re unpacking the creative methodologies behind building a Geometry Painter that employs real-time controls, custom shaders, and advanced lighting to produce stunning results. What’s truly remarkable about this tutorial is its focus on the intricacies of creating a user-friendly interface that translates complex concepts into accessible interactions. By bringing together surface picking and procedural generation techniques, Chiro Visuals offers readers an opportunity to explore how a simple brushstroke can morph into stunning visuals—think glowing crystals and bioluminescent reefs. This is more than a technical guide; it’s an invitation to reimagine how we engage with 3D environments.A Look at the Geometry Painter
At its core, the Geometry Painter invites users to interact with a floating sphere, allowing them to paint various effects onto its surface in real-time. This includes crystalline veins that shimmer, molten fissures that ooze, and vibrant bioluminescent patterns. The tutorial emphasizes not only the visual appeal but also smart architectural decisions that streamline the painting process—essentially, how to build it in a way that allows new features to be plugged in without disrupting the existing codebase. The concept mirrors that of a geode: an unassuming exterior that opens up to reveal a dazzling interior. In this case, the relatively boring canvas—designed to be matte and dark—serves to highlight the brilliant effects that users can create. Importantly, this design choice drives the codebase significantly, influencing how light and texture are applied. The tutorials are structured in a way that allows experimentation, with every technique being demonstrated through live demos, letting users grasp how the mechanics function in real time. Chiro Visuals also discusses the evolution of their painting tool, highlighting an early version that focused solely on vegetation—showcasing trees along a stroke. Over time, they realized the artistic potential was not just in the visual but in the interaction that happens at the surface level. The newly developed modes—crystals, molten fissures, aurora silk, and bioluminescent reefs—allow for a richer story to unfold with each stroke. Whether you’re a seasoned developer seeking innovative ways to enhance user experience or a curious creator exploring 3D graphics, the Geometry Painter project serves as both inspiration and a comprehensive technical guide. It embodies the heart of procedural geometry: creating engaging and visually captivating environments that enhance interaction and creativity. Explore the tutorial [here](https://tympanus.net/Tutorials/GeometryPainterThreeJS) and delve into the code repository on [GitHub](https://github.com/achrefelouafi/GeometryPainterThreeJS) to see how these concepts are brought to life.Stable Ranks and Material Property Layers
Understanding stable ranks is critical to the mechanics at play. By adjusting the density slider, the system consistently generates the same crystals in a predetermined order. This approach ensures that existing clusters remain static, providing a predictable output that feels like “more of this” rather than introducing completely new elements. The same concept applies to the clear-quartz combination, where the underlying mechanism guarantees the same crystals are produced consistently. The routine `inst.clearRnd < s.clearMix` is essential because it enables uniform generation of crystals every time. When toggling between clear and tinted forms, the process takes it one step further by assigning each crystal a position within two `InstancedMesh` instances—one showcasing the palette material, and the other embodying clear refractive quartz. Notably, though only one receives a genuine transformation, the other is rendered with a zero-scale matrix to maintain the illusion of presence without visual clutter.
Take a slider that dictates “35% of these should be clear quartz,” for instance. This acts as a dynamic switch for materials per instance, a flexibility not typically afforded by instancing alone. The creation of five shape variants, each combined with two sets of materials, results in ten `InstancedMesh` instances with each stroke. A stroke measuring roughly two-thirds across a sphere generates 537 crystals, of which 119 are actively rendered while the additional 418 remain in a zero scale buffer—waiting their turn. Yet, regardless of how many get rendered, it maintains a consistent ten draw calls.
Animation Mechanics: Growth as a Distance
Every mode of growth is defined not by timing but by the distance traveled along the stroke. Forget about timelines or external tween libraries—the focus here lies in two primary numbers that govern the visual transitions. - **`birth`** represents the start point along the stroke where the instance was initiated, established during the initial generation phase. - **`grown`** tracks how far the front has progressed, updated frame by frame based on the calculation of `dt * growthSpeed`.
The heart of the animation lies in the difference between these two variables. If the value of `t`, derived from `(this.grown - inst.birth) / GROW_WINDOW`, is less than or equal to zero, the instance remains dormant—its matrix sits at zero scale. Conversely, as `t` approaches or exceeds one, further growth is calculated to grant the instances their final shapes.
Set at 0.45 world units, `GROW_WINDOW` determines a window behind the growth front where crystals are in various stages of development. The growth speed, adjustable via a live slider, influences how quickly `grown` advances. The uniqueness here is that replaying the animation can simply be achieved by resetting `grown` to zero, while snapping to fully grown requires a value of `total + window + 1`, highlighting the absence of a complex state management system.
At the core of this visual transformation are two particular details. First, the use of `easeOutBack` creates an overshooting effect that adds realism, making it feel as though a crystal materializes rather than merely appearing. The second critical aspect involves the scaling of width during growth—the formula `(0.6 + 0.4 * k)` adjusts width while height remains static. This mimics the natural growth of minerals, preventing the animation from seeming mechanical and uniform.
Add to this the efficient update system, which ceases to process instances once they exceed `t = 1`. A finished stroke incurs no additional frame costs, enabling creators to populate the sphere efficiently without performance degradation.Final Thoughts
Reflecting on the intricacies of this project unveils a larger lesson in technical and creative restraint. The emphasis on maintaining performance by avoiding any allocations while user interactions occur isn't just a guideline; it evolves into a foundational approach to design. This constraint encourages a synergy between stability and aesthetic, allowing variations in appearance to emerge naturally from set parameters and random seeds. Here's the kicker: when design elements like live editing and undo functionalities seamlessly blend into your workflow, they shift from being perceived as separate features to becoming integral attributes of the project. This level of integration reflects both a maturity in your approach and a keen understanding of user experience. If you're considering building something similar, there are a few innovative paths to explore:- Create a new mode: Implement the
createStrokefunction and enrich the registry with your creativity—think textures like mushrooms or circuits. Your samples won’t mind. - Change the canvas: The current code doesn’t bindingly dictate a spherical representation. Modify it to accommodate different meshes; leveraging the
indexForRaycastsfunction could unlock new potentials. Only the fissure walker might require some adjustments for various geometries. - Expand the pulse effect: Drawing from the reef’s world-space wave, consider a shared dynamic element—like wind or light—that influences multiple modes concurrently. A cohesive theme can transform unrelated effects into a unified experience with minimal code.