Explore Generative Art with WebGPU: Crafting a Dynamic Garden Inspired by Japanese Aesthetics

Sep 09, 2026 811 views

Still: From Akira to Ink Wash, Creating a Generative Garden with WebGPU

This article provides a technical exploration of Still, showcasing how WebGPU, procedural generation, and influences from Japanese art converge to form a dynamic generative garden.

Featured Image

Editor’s Note: With the Three.js Conference just around the corner, we’re excited to present this feature as the perfect lead-up. We're thrilled to highlight Ming Jyun Hung and his work, Still, which continues to unfold his astronaut narrative. Drawing influence from Akira and traditional Japanese aesthetics, Ming Jyun combines toon shading, ink wash effects, silk textures, procedural flowers, and generative tendrils to create an immersive environment that is both meticulously designed and vibrantly alive! We hope you enjoy this insightful analysis!

🌸 Excitement builds! The inaugural Three.js Conference in Paris is almost here. If you’re in town and haven't yet committed, this is your final opportunity! Use code CODROPS to enjoy 15% off your ticket – grab yours here →

Still marks the third installment in an interactive astronaut saga that I’ve been developing on the web (check out the previous Codrops article). Each new chapter propels the narrative forward while prompting exploration of new visual and technical challenges.

The protagonist, after navigating the endless expanse of Drift, finds himself repeatedly entangled in False Earth, trapped in a cycle of perpetual motion. Exhaustion eventually sets in, leading him to a state between consciousness and dreams. Here, the boundary between reality and imagination blurs; the landscape, flowers, and his suit coalesce into one fluid vista, with his thoughts drifting back and forth between memories and the present.

Creating an Aesthetic from Japanese Art

This chapter's visual journey began with my reflections on Akira. Initially, my engagement with the film was emotional rather than technical; its distinctive aesthetics, immersive atmosphere, and evocative sound design lingered in my mind, overshadowing all else and spurring me to craft something of a similar iconic nature.

As I delved into traditional Japanese artwork, particularly folding screens, the parallels between Akira and these pieces became apparent. Both employ a particular visual logic characterized by expansive fields of color, distinct outlines, purposeful composition, and organic forms that evoke a sense of atmosphere. In this context, rhythm and stillness often take precedence over intricate detail.

I took these visual principles and translated them into a real-time 3D environment. By employing toon shading, I maintained broad, flat planes, while the ground shadow emulated an uneven ink wash. The silk weave added a unique texture, and procedural growth introduced an organic undulation to the flowers and tendrils. The aim wasn't to mimic the references exactly but to infuse their compositional sense and material depth into a vibrant, animated scene.

Woodblock Visual Influence

Evolving the artistic style, I sought harmony between the astronaut and the flora, necessitating distinct shading and outlining techniques for each. The astronaut’s suit utilized a toon shader for depth, while the flower components were constructed with a vertex-color material specifically designed for VAT instancing. This shared lighting approach created a cohesive visual output across different elements.

Employing a method that begins with N·L—the dot product of surface normals and light direction—I quantized lighting across color levels. By remapping to a range defined by thresholdLow and thresholdHigh, I focused on just two levels: shadow band and lit band, as excess variation tended to obscure the print-like clarity. Shadows and highlights were handled through tints on the base color, while minor noise was added to give the edges a less rigid appearance.

const ndl = max(dot(N, L), 0.0);
const thresholdNoise = fbm3(positionWorld.mul(thresholdNoiseScale))
  .sub(0.5)
  .mul(thresholdNoiseStrength);
const preShade = clamp(
  ndl.sub(thresholdLow.add(thresholdNoise))
    .div(thresholdHigh.sub(thresholdLow)),
  0.0,
  1.0,
);
const quantized = floor(preShade.mul(colorLevels.sub(1.0)).add(0.5))
  .div(colorLevels.sub(1.0));

The outline design mirrored this methodology, varying by the geometry of the respective asset. For the astronaut model, an inverted hull method was employed?—a secondary mesh with back faces extended outward along the normals. In contrast, petals utilize a mask shader to delineate edges directly, forgoing a separate mesh to optimize performance, especially since each VAT head is instantiated multiple times. This method allows for efficient edge definition while maintaining a cohesive visual style.

Ink-Wash Style Ground Shadow

Throughout my work on this chapter, I frequently revisited Akira, as exemplified by the distinctive poster I encountered. Kaneda and his motorcycle sit upon a stark white backdrop, yet it is the shadow they cast that has remained etched in my memory. The shadow’s soft, uneven edges and loose outlines resonate, reminding me of an artist's paint thinned on paper. Consequently, I sought to render the shadow in a manner aligned with this aesthetic philosophy.

Akira Poster Shadow

With a directional light already providing a shadow map, I inverted the mapping and used a smoothstep function to achieve a broad ink wash effect with soft edges. Noise was added to disturb the fill and soften the outline simultaneously, allowing for variation in the resulting shadow.

The darker contour, which utilized the shadow value, was set just outside the ink wash. This placement helps differentiate the drawn line from the shadow, ensuring it complements rather than competes with the visual flow of the scene. Further noise was introduced to break the continuity of the contour line, while fwidth was applied to maintain a steady visual width across the screen.

const shade = shadow(light).oneMinus();
const noise = fbm2(positionWorld.xz.mul(washScale));
const fill = smoothstep(washAt, washAt.add(washSoft), shade.add(noise.mul(washBleed)));
const wash = fill.mul(float(1.0).sub(noise.mul(washMottle).max(0.0)));

const wobble = mx_noise_float(positionWorld.xz.mul(contourWobbleScale)).mul(contourWobble);
const penWidth = fwidth(shade).mul(contourWidth).max(0.0001);
const line = float(1.0).sub(smoothstep(0.0, penWidth, shade.sub(contourShade.add(wobble)).abs()));
const shColor = mix(washColor, contourColor, line);
return mix(bg, shColor, max(wash.mul(washStr), line.mul(contourStr)));

In addition to the overall ground wash, I aimed for the flowers to cast shadows on the character without overshadowing themselves. This required leveraging a second shadow map specifically for the flowers, ensuring they were assigned to a separate layer. By allowing the plant-shadow light’s shadow camera to focus solely on the flowers, I could sync its position with the main light while keeping its intensity at zero, effectively recording depth without altering visible brightness.

This low-poly VAT mesh acted as a shadow-only proxy on an isolated render layer, allowing shadow passes to occur without full geometrical detail of the petals.

Silk Weaving Texture

My research included an exploration of two Japanese folding screens by artists Sakai Hōitsu and Ogata Kenzan. The intricate patterns captured my attention, particularly the textural quality of the ground: a delicate weave resembling threads, adorned with uneven accents similar to painted stains. Translating that aspect into the scene became a crucial objective.

I spread that weaving texture across the entire frame, mirroring the screens’ technique which integrates patterns directly into the foundation of the art rather than confining it to individual objects. I scaled screenUV to create thread-like cells, altered the grid to break away from a mechanical appearance, and blended together warp and weft directions to fabricate the weave. To inject authenticity, I shifted tone and layered in slower noise to resemble stains, ultimately darkening the overall frame while enhancing the scene’s visual complexity.

const coord = screenUV.mul(vec2(aspect, 1.0)).mul(threadCount);
const x = coord.x.add(hash(floor(coord.y)).sub(0.5).mul(irregularity));
const y = coord.y.add(hash(floor(coord.x)).sub(0.5).mul(irregularity));
const warp = pow(abs(sin(x.mul(PI))), sharpness);
const weft = pow(abs(sin(y.mul(PI))), sharpness);
const checker = mod(floor(x).add(floor(y)), 2.0);
const weave = mix(warp, weft, checker);
const fabric = clamp(float(1.0).sub(strength.mul(float(1.0).sub(weave))).add(threadTone), 0.0, 1.0);
const blotch = float(1.0).sub(blotchStrength.mul(smoothstep(0.45, 0.95, stain)));
const overlaid = sceneColor.mul(tint).mul(fabric).mul(blotch);

Crafting Individual Plant Elements

With the foundational elements set, I shifted focus to the smallest reproductive unit in this digital garden: a singular plant. Before constructing surrounding fields, I concentrated on perfecting the flower, stem, leaves, and their growth cycle.

Flower Animation

For animating the flower, I utilized the Blooming Flowers pack for Blender, which contained built-in Geometry Nodes that required minimal adaptation for my purposes. I maintained their detailed blooming animations while converting them into VAT, leveraging an add-on I developed for the entire VAT process in Blender.

Blender Flower Animation

The animation allowed petals to detach gradually, with each one lifting and fanning outward. This concept reflected inspiration from Flowers and People by teamLab, where a flower achieves its fullest form just before it drops. Given that the mesh was already segmented into distinct islands, I organized vertices based on connectivity and included petal IDs with corresponding pivot vertices in the vertex colors. A hashed ID helped stagger the timing and varied the trajectory of each petal's lift, while the eased value controlled both upward and lateral motions. By combining the baked VAT with procedural components, I aimed to preserve intricate, authored motions while also ensuring variation and flexibility in real-time, creating a lively experience rather than a repetitive one.

const petalId = color.g; // island id, 0..1
const pivot = sampleVAT(color.b, frame); // same vertex, current bloom frame
const startJitter = fract(sin(petalId * 127.1) * 43758.5453);
const heightJitter = fract(sin(petalId * 127.1 + 7.13) * 43758.5453);
const t = clamp((shed - startJitter * stagger) / (1.0 - stagger), 0.0, 1.0);
const ease = t * t * (3.0 - 2.0 * t);
const shrunk = pivot.add(basePos.sub(pivot).mul(1.0 - ease));
const height = 1.0 + (heightJitter - 0.5) * 2.0 * riseVariance;
const lift = rise * max(height, 0.0) * ease;
const outward = normalize(vec3(pivot.x, 0.0, pivot.z));
const fan = rotate(outward, flowerRotation) * spread * ease;

const position = rotate(shrunk, flowerRotation) + flowerPosition
  + vec3(0.0, lift, 0.0) * stemLength
  + fan * stemLength;

Stem Design

The same Blender pack also featured Geometry Nodes for stem creation; however, since those were preattached to specific flowers, I designed a customizable stem in Three.js. I shaped a tube by sweeping a curve. A seeded Catmull-Rom curve started below ground level and leaned towards the flower head while incorporating a lateral bend. I sampled this curve into rings, allowed the base to flare out, and tapered the height as it approached the flower.

const from = new THREE.Vector3(0, -BASE_BURY, 0);
const to = /* lean azimuth × stemLength */;
const bend = /* seeded sideways offset */;
const curve = new THREE.CatmullRomCurve3(
  [
    from,
    from.clone().lerp(to, 0.25).add(bend),
    from.clone().lerp(to, 0.75).add(bend),
    to,
  ],
  false,
  'centripetal',
);
const scale = (1 - (1 - radiusAttenuation) * t) + baseFlare * (1 - t) ** 3;

After constructing the tubular base, I implemented growth through a single parameter ranging from 0 to 1. The fragment shader was designed to hide any extensions beyond the current growth front while the vertex shader dynamically scaled every visible ring outward. This growth tracking was synchronized with the flower head's placement along the curve, ensuring that the bloom remained genuinely part of the stem’s development.

If(uv().x.greaterThan(growth), () => Discard());
const rScale = startScale + growth * (1.0 - startScale);
grown = center.add(positionLocal.sub(center).mul(rScale));

Leaf Development

With the stem properly positioned, I incorporated leaves as distinct modeled meshes. In the shader, I manipulated each leaf blade, starting with a tight curl and gradually allowing them to unfurl as they grow.

Final Thoughts

The journey of creating this dynamic system has revealed fundamental insights not just about visual aesthetics but about the underlying mechanics that bring a concept to life in a digital environment. The integration of procedural elements—such as plant lifecycles and tendril systems—into the astronaut's immediate surroundings isn’t merely an exercise in artistry; it’s an exploration of how interconnectedness can manifest in design. If you’ve been wrestling with how to make digital elements feel organic and responsive, this approach is a reminder that balance is key. It's fascinating to see how the meticulous structuring of nature-inspired rules can create a flourishing ecosystem, even in a simulated environment. The challenge now lies in leveraging these foundational principles while managing performance constraints. Every additional element must justify its presence—if it doesn't enhance the visual experience, it risks dragging down the performance. As technology evolves, there will undoubtedly be new avenues for pushing these boundaries, but the principle remains: richness in detail needs to harmonize with responsiveness. Looking ahead, the interplay between observation and technological innovation will only deepen. Each new project is a fresh opportunity to draw from the world’s visual complexity while refining storytelling methods. Artists and developers alike will benefit from being keen observers, translating the subtleties of their surroundings into digital narratives. The path forward is open, but one thing’s clear: the quest for a deeper connection—between art, technology, and story—will continue to shape the future of interactive experiences.
Source: Ming Jyun Hung · tympanus.net

Comments

Sign in to comment.
No comments yet. Be the first to comment.

Related Articles

Still: From Akira to Ink Wash, Building a Generative Gard...