const leavesMaterial = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
side: THREE.DoubleSide
});
const instanceNumber = 100000;
const grass = new THREE.Object3D();
const geometry = new THREE.PlaneGeometry( 3, 6, 1, 4 );
const instancedMesh = new THREE.InstancedMesh( geometry, leavesMaterial, instanceNumber );
instancedMesh.name = "shader grass"
scene.add( instancedMesh );
const r = ZONE_RADIUS.GARDEN
for ( let i=0 ; i<instanceNumber ; i++ ) {
// https://stackoverflow.com/questions/5837572/generate-a-random-point-within-a-circle-uniformly
const t = 2*Math.PI*Math.random()
const u = getRandomArbitrary(0, r) + getRandomArbitrary(0, r)
let radius;
if(u > r) {
radius = r*2 - u
} else {
radius = u
}
grass.position.set(radius * Math.cos(t), 0, radius * Math.sin(t))
grass.scale.setScalar( 0.5 + Math.random() * 0.5 );
grass.rotation.y = Math.random() * Math.PI;
grass.updateMatrix();
instancedMesh.setMatrixAt( i, grass.matrix );
}
instancedMesh.translateX(position.x)
instancedMesh.translateZ(position.z)
Rose Algorithm
Define class and Animating flower petals
constructor(params) {
const { numerator, denominator, angleGap, size } = params;
// ...
this.n = numerator || 4;
this.d = denominator || 7;
this.angle = angleGap || 0.5;
this.k = this.n/this.d;
this.amplitude = 30;
this.rotationY = Math.PI/2.0;
this.scaleNum = size;
}
renderPetal(){
//...
for(let i = 0; i < Math.PI * 2.0 * this.d; i += this.angle) {
let radial = this.amplitude * Math.cos(this.k * i)
let x = radial * Math.cos(i)
let y = radial * Math.sin(i)
var m = mesh.clone()
m.rotateZ(i * Math.PI/4.0)
m.position.set(x, y, 0)
this.add(m)
}
}
tick(){
let petalN = Math.abs(Math.cos(time/3000)*4) + 1
let petalD = Math.abs(Math.sin(time/3000)*9) + 1
this.n = petalN
this.d = petalD
this.renderPetal()
}
Used to decorate each ZONE's bottom circular planes and materials for Main Tree, each Zone's bottom plane (metallics kaleidoscope, coffee colored liquid visual)
Referred to shadertoy and The Book of Shaders.
Vertex shader: Here's a quick summary of how threejs uses vertex shader and coordinates.
In Three.js, the vertex shader is responsible for transforming the vertices of a 3D object from their local coordinate space to the final position on the screen. This transformation involves applying a series of matrix operations to each vertex.
The reason for calculating matrices in the vertex shader is to ensure that the vertices are transformed correctly based on the current state of the scene and the camera. The matrices used in the vertex shader include modelMatrix (local space), viewMatrix (camera space), projectionMatrix (3D space to 2D).
vertex position => model matrix => view matrix => projection matrix => Final position in 3D space & Camera view
if(branchRadius < 0.24) {
branchMesh = new THREE.Mesh(petalClone, randomMat);
} else {
branchMesh = new THREE.Mesh(branchCylinder, material);
if(material.name === "shader") branchMesh.name = "shader"
}
if (axiom.charAt(i) === "f") {
topPoint = branchInsert(
branchLength * (1 - j * lengthReductionFactor),
branchRadius * (1 - j * radiusReductionFactor),
topPoint,
angle * rightX + preXAngle,
angle * rightY + preYAngle,
angle * rightZ + preZAngle);
j += 1;
preXAngle += angle * rightX;
preYAngle += angle * rightY;
preZAngle += angle * rightZ;
rightX = 0;
rightY = 0;
rightZ = 0;
}
function tree(xpos, len) {
push()
translate(xpos, height);
line(0, 0, 0, -100);
branch(len)
pop()
}
function branch(len) {
line(0, 0, 0, -len)
translate(0, -len)
len *= 0.7;
if(len > 1) {
push()
rotate(PI/theta1)
branch(len)
pop()
push()
rotate(-PI/theta2)
branch(len)
pop()
}
}
// Zone 1
const centerX1 = ZONE_POS.ONE.x
const centerZ1 = ZONE_POS.ONE.z
const radius1 = ZONE_RADIUS.ONE
const dx1 = Math.abs(currentPos.x - centerX1)
const dz1 = Math.abs(currentPos.z - centerZ1)
let inZone1 = dx1*dx1 + dz1*dz1 <= radius1*radius1
let shutterBB_left, shutterBB_right, shutterBB_back;
{
// x: 6051 ~ 7061, z: -50
// x: 6051 ~ 7061, z: 50
const shutterGeometry = new THREE.BoxGeometry( 1000, 100, 20 );
const shutterMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
const _leftShutterBBMesh = new THREE.Mesh( shutterGeometry, shutterMaterial );
_leftShutterBBMesh.position.set(6051 + 500, 50, 50)
// scene.add( _leftShutterBBMesh );
// Update mesh matrix world
_leftShutterBBMesh.updateMatrixWorld();
// Update shutter bounding box
_leftShutterBBMesh.geometry.computeBoundingBox();
shutterBB_left = new THREE.Box3();
shutterBB_left.copy(_leftShutterBBMesh.geometry.boundingBox).applyMatrix4(_leftShutterBBMesh.matrixWorld);
}
function tick() {
//...
let currentPos = pointerControls.getObject().position
if(shutterBB_left){
const dist = shutterBB_left.distanceToPoint(currentPos)
if(dist < 20){
console.log("close to shutter left")
// velocity.x += velocity.x * 0.005;
// pointerControls.moveRight(velocity.x);
pointerControls.getObject().position.z -= 6;
}
}
// ...
}
// init NodePostProcessing
blurScreen = new Nodes.BlurNode( new Nodes.ScreenNode() );
blurScreen.size = new THREE.Vector2( size.width, size.height );
nodepost.output = blurScreen;
blurScreen.radius.x = 0;
blurScreen.radius.y = 0;
// check energy progress
let energyPercent = ((window.ACC_STEPS/window.STEP_LIMIT)*100)
if( energyPercent < 30 ) {
warnLowEnergy(scene)
gradientBlurScreen(0.005)
} else if ( energyPercent > 30 ) {
retrieveEnergy(scene)
gradientBlurScreen(-0.005)
}
function gradientBlurScreen(delta) {
if(delta < 0) { // to CLEAR
while(blurScreen.radius.x >= 0) {
blurScreen.radius.x += delta
blurScreen.radius.y += delta
}
return;
}
if(delta > 0) { // to BLUR
while(blurScreen.radius.x <= 5) {
blurScreen.radius.x += delta
blurScreen.radius.y += delta
}
}
}
Animating Static Geometries
Animating Skinned Mesh
Threejs Animation System
Animaiton Mixer
Character Assets
Euljiro district (Seoul, South Korea) stands as a melting pot of generations, a fusion of historical manufacturers and modern merchants, a juxtaposition of vintage charm and hipster allure, all underscored by the palpable tension of gentrification. However, in my observations, a crucial element appeared absent: a natural public space where its eclectic inhabitants could indirectly connect and interact. This prompted me to conceptualize a virtual park. Within this digital realm, users can only recharge their 'energy' within the park, which is then expended as they explore the broader environment.
This work was featured in the group exhibition 'Folding of the City' at Eulji Art Center and drew inspiration from Jingfang Hao's science fiction novel, "Folding Beijing."
The artwork delves into the multifaceted aspects of Euljiro, portraying the dichotomies of young versus old, time-honored manufacturing outlets versus contemporary cafes, and the looming shadows of gentrification amidst a lack of nature. This piece is my sci-fi vision of Euljiro's potential evolution.
Special gratitude to 서울문화재단 (Seoul Foundation of Arts and Culture), 을지예술센터 (Eulji Art Center), 이유준 PD, and 김자현, a renowned Composer and Sound Artist, for their invaluable support and contribution.