Quantcast
Channel: 3D – Codrops
Viewing all 120 articles
Browse latest View live

Coding a Simple Raymarching Scene with Three.js

$
0
0

Editor’s note: We want to share more of the web dev and design community directly here on Codrops, so we’re very happy to start featuring Yuriy’s newest live coding sessions!

In this live stream of ALL YOUR HTML, we will be coding a Raymarching demo in Three.js from scratch. We’ll be using some cool Matcap textures, and add a wobbly animation to the scene, and all will be done as math functions. The scene is inspired by this demo made by Luigi De Rosa.

This coding session was streamed live on December 6, 2020.

Check out the live demo.

The Art of Code channel: https://www.youtube.com/c/TheArtofCodeIsCool/

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Coding a Simple Raymarching Scene with Three.js appeared first on Codrops.


Coding a 3D Lines Animation with Three.js

$
0
0

Editor’s note: We want to share more of the web dev and design community directly here on Codrops, so we’re very happy to start featuring Yuriy’s newest live coding sessions!

In this live stream of ALL YOUR HTML, we will be recreating the cool 3D lines animation seen on the Voice of Racism website by Assembly using Three.jsDepthTexture.

This coding session was streamed live on December 13, 2020.

Check out the live demo.

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Coding a 3D Lines Animation with Three.js appeared first on Codrops.

Infinite Hyperbolic Helicoid Animation with Three.js

$
0
0

In this ALL YOUR HTML coding session, you’ll learn how to create a beautiful shape with parametric functions, a hyperbolic helicoid, inspired by J. Miguel Medina’s artwork. We’ll be upgrading MeshPhysicalMaterial with some custom shader code, making an infinite animation.

This coding session was streamed live on January 10, 2021.

Check out the live demo.

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Infinite Hyperbolic Helicoid Animation with Three.js appeared first on Codrops.

Recreating Frontier Development Lab’s Sun in Three.js

Rotating Loading Animation of 3D Shapes with Three.js

$
0
0

All things we see around are 3D shapes. Some of them are nice looking, some of them you’d like to touch and squeeze. But because it’s still quarantine time, and I don’t have many things around, I decided to create some virtual things to twist around. 🙂 Here’s what I ended up with:

How to do that?

To render everything I used Three.js, just like Mario Carillo in his amazing demo, go check it out! Three.js is the best and most popular library to do things with WebGL at the moment. And if you are just starting your 3D path, it is definitely the easiest way to get something working.

If you just rotate the shape, its nothing special, its just rotating.

Interesting things start to happen when you rotate different parts of the shape with different speeds. Let’s say I want the bottom part of the object to rotate first, and than the rest, let’s see whats going to happen.

To do something like that, you would need to use a Vertex shader. I used a little bit of math and rotated parts of the object depending on the Y or Z coordinate. The simplified code looks like this:

vec3 newposition = rotate(oldPosition, angle*Y_COORDINATE);

So with a coordinate change, the rotation would change as well. The rotate function itself includes a lot of Sine-Cosine’s and matrices. All the math you need to rotate a point around some axis. To give it a more natural feel, I also change the easing function, so it looks like elastic rubber.

Visuals, MatCap

To create a nice look for objects, we could have used lights and shadows and complicated materials. But because there’s not so much in the scene, we got away with the most simple kind of material: MatCaps. So instead of making complicated calculations, you just use a simple image to reference the lighting and color. Here is how it looks like:

And here is how the torus shape looks like with this texture applied to it:

Amazing, isn’t it? And so simple.

So combining Matcaps and a rotation method, we can create a lot of cool shape animations that look great, and have this rubber feeling.

Go and create your shape with this technique and make sure to share it with us! Hope this simple technique will inspire you to create your own animation! Have a good day!

The post Rotating Loading Animation of 3D Shapes with Three.js appeared first on Codrops.

Magnetic 3D Grid Interaction with Content Preview

$
0
0

Today I’d like to share another thumbnail to full view animation with you. This time we have an initial grid with thumbnails that move with the mouse in 3D and have a magnetic effect including a little tooltip on hover. Once clicked, all items animate out, giving way to a content preview with more details.

The grid interaction looks as follows:

Here’s how the grid to content animation looks like:

I really hope you find this useful! Thanks for visiting!

The post Magnetic 3D Grid Interaction with Content Preview appeared first on Codrops.

Rotated 3D Letters and Image Hover Effect

$
0
0

Some months ago, I wrote about how to achieve a hover effect for a menu where an image would appear for each item. Then we saw how to apply this in Exploring Animations for Menu Hover Effects. When I discovered the fantastic site of One up Studio I wanted to combine their cool 3D-ish menu hover effect with that previous animation, so this is a little fusion between the two.

The 3D motion is slightly different and an image is shown in an angle, creating a playful look.

I really hope you like this little experiment and find it useful. Thanks for visiting!

The post Rotated 3D Letters and Image Hover Effect appeared first on Codrops.

Creating 3D Characters in Three.js

$
0
0

Three.js is a JavaScript library for drawing in 3D with WebGL. It enables us to add 3D objects to a scene, and manipulate things like position and lighting. If you’re a developer used to working with the DOM and styling elements with CSS, Three.js and WebGL can seem like a whole new world, and perhaps a little intimidating! This article is for developers who are comfortable with JavaScript but relatively new to Three.js. Our goal is to walk through building something simple but effective with Three.js — a 3D animated figure — to get a handle on the basic principles, and demonstrate that a little knowledge can take you a long way!

Setting the scene

In web development we’re accustomed to styling DOM elements, which we can inspect and debug in our browser developer tools. In WebGL, everything is rendered in a single <canvas> element. Much like a video, everything is simply pixels changing color, so there’s nothing to inspect. If you inspected a webpage rendered entirely with WebGL, all you would see is a <canvas> element. We can use libraries like Three.js to draw on the canvas with JavaScript.

Basic principles

First we’re going to set up the scene. If you’re already comfortable with this you can skip over this part and jump straight to the section where we start creating our 3D character.

We can think of our Three.js scene as a 3D space in which we can place a camera, and an object for it to look at.

Drawing of a transparent cube, with a smaller cube inside, showing the x, y and z axis and center co-ordinates
We can picture our scene as a giant cube, with objects placed at the center. In actual fact, it extends infinitely, but there is a limit to how much we can see.

First of all we need to create the scene. In our HTML we just need a <canvas> element:

<canvas data-canvas></canvas>

Now we can create the scene with a camera, and render it on our canvas in Three.js:

const canvas = document.querySelector('[data-canvas]')

// Create the scene
const scene = new THREE.Scene()

// Create the camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000)
scene.add(camera)

// Create the renderer
const renderer = new THREE.WebGLRenderer({ canvas })

// Render the scene
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.render(scene, camera)

For brevity, we won’t go into the precise details of everything we’re doing here. The documentation has much more detail about creating a scene and the various camera attributes. However, the first thing we’ll do is move the position of our camera. By default, anything we add to the scene is going to be placed at co-ordinates (0, 0, 0) — that is, if we imagine the scene itself as a cube, our camera will be placed right in the center. Let’s place our camera a little further out, so that our camera can look at any objects placed in the center of the scene.

Showing the camera looking towards the center of the scene
Moving the camera away from the center allows us to see the objects placed in the center of the scene.

We can do this by setting the z position of the camera:

camera.position.z = 5

We won’t see anything yet, as we haven’t added any objects to the scene. Let’s add a cube to the scene, which will form the basis of our figure.

3D shapes

Objects in Three.js are known as meshes. In order to create a mesh, we need two things: a geometry and a material. Geometries are 3D shapes. Three.js has a selection of geometries to choose from, which can be manipulated in different ways. For the purpose of this tutorial — to see what interesting scenes we can make with just some basic principles — we’re going to limit ourselves to only two geometries: cubes and spheres.

Let’s add a cube to our scene. First we’ll define the geometry and material. Using Three.js BoxGeometry, we pass in parameters for the x, y and z dimensions.

// Create a new BoxGeometry with dimensions 1 x 1 x 1
const geometry = new THREE.BoxGeometry(1, 1, 1)

For the material we’ll choose MeshLambertMaterial, which reacts to light and shade but is more performant than some other materials.

// Create a new material with a white color
const material = new THREE.MeshLambertMaterial({ color: 0xffffff })

Then we create the mesh by combining the geometry and material, and add it to the scene:

const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

Unfortunately we still won’t see anything! That’s because the material we’re using depends on light in order to be seen. Let’s add a directional light, which will shine down from above. We’ll pass in two arguments: 0xffffff for the color (white), and the intensity, which we’ll set to 1.

const lightDirectional = new THREE.DirectionalLight(0xffffff, 1)
scene.add(lightDirectional)
Light shining down from above at position 0, 1, 0
By default, the light points down from above

If you’ve followed all the steps so far, you still won’t see anything! That’s because the light is pointing directly down at our cube, so the front face is in shadow. If we move the z position of the light towards the camera and off-center, we should now see our cube.

const lightDirectional = new THREE.DirectionalLight(0xffffff, 1)
scene.add(lightDirectional)

// Move the light source towards us and off-center
lightDirectional.position.x = 5
lightDirectional.position.y = 5
lightDirectional.position.z = 5
Light position at 5, 5, 5
Moving the light gives us a better view

We can alternatively set the position on the x, y and z axis simultaneously by calling set():

lightDirectional.position.set(5, 5, 5)

We’re looking at our cube straight on, so only one face can be seen. If we give it a little bit of rotation, we can see the other faces. To rotate an object, we need to give it a rotation angle in [radians](). I don’t know about you, but I don’t find radians very easy to visualize, so I prefer to use a JS function to convert from degrees:

const degreesToRadians = (degrees) => {
	return degrees * (Math.PI / 180)
}

mesh.rotation.x = degreesToRadians(30)
mesh.rotation.y = degreesToRadians(30)

We can also add some ambient light (light that comes from all directions) with a color tint, which softens the effect slightly end ensures the face of the cube turned away from the light isn’t completely hidden in shadow:

const lightAmbient = new THREE.AmbientLight(0x9eaeff, 0.2)
scene.add(lightAmbient)

Now that we have our basic scene set up, we can start to create our 3D character. To help you get started I’ve created a boilerplate which includes all the set-up work we’ve just been through, so that you can jump straight to the next part if you wish.

Creating a class

The first thing we’ll do is create a class for our figure. This will make it easy to add any number of figures to our scene by instantiating the class. We’ll give it some default parameters, which we’ll use later on to position our character in the scene.

class Figure {
	constructor(params) {
		this.params = {
			x: 0,
			y: 0,
			z: 0,
			ry: 0,
			...params
		}
	}
}

Groups

In our class constructor, let’s create a Three.js group and add it to our scene. Creating a group allows us to manipulate several geometries as one. We’re going to add the different elements of our figure (head, body, arms, etc.) to this group. Then we can position, scale or rotate the figure anywhere in our scene without having to concern ourselves with individually positioning those parts individually every time.

class Figure {
	constructor(params) {
		this.params = {
			x: 0,
			y: 0,
			z: 0,
			ry: 0,
			...params
		}
		
		this.group = new THREE.Group()
		scene.add(this.group)
	}
}

Creating the body parts

Next let’s write a function to render the body of our figure. It’ll be much the same as the way we created a cube earlier, except, we’ll make it a little taller by increasing the size on the y axis. (While we’re at it, we can remove the lines of code where we created the cube earlier, to start with a clear scene.) We already have the material defined in our codebase, and don’t need to define it within the class itself.

Instead of adding the body to the scene, we instead add it to the group we created.

const material = new THREE.MeshLambertMaterial({ color: 0xffffff })

class Figure {
	constructor(params) {
		this.params = {
			x: 0,
			y: 0,
			z: 0,
			ry: 0,
			...params
		}
		
		this.group = new THREE.Group()
		scene.add(this.group)
	}
	
	createBody() {
		const geometry = new THREE.BoxGeometry(1, 1.5, 1)
		const body = new THREE.Mesh(geometry, material)
		this.group.add(body)
	}
}

We’ll also write a class method to initialize the figure. So far it will call only the createBody() method, but we’ll add others shortly. (This and all subsequent methods will be written inside our class declaration, unless otherwise specified.)

createBody() {
	const geometry = new THREE.BoxGeometry(1, 1.5, 1)
	const body = new THREE.Mesh(geometry, material)
	this.group.add(body)
}
	
init() {
	this.createBody()
}

Adding the figure to the scene

At this point we’ll want to render our figure in our scene, to check that everything’s working. We can do that by instantiating the class.

const figure = new Figure()
figure.init()

Next we’ll write a similar method to create the head of our character. We’ll make this a cube, slightly larger than the width of the body. We’ll also need to adjust the position so it’s just above the body, and call the function in our init() method:

createHead() {
	const geometry = new THREE.BoxGeometry(1.4, 1.4, 1.4)
	const head = new THREE.Mesh(geometry, material)
	this.group.add(head)
	
	// Position it above the body
	head.position.y = 1.65
}

init() {
	this.createBody()
	this.createHead()
}

You should now see a narrower cuboid (the body) rendered below the first cube (the head).

Adding the arms

Now we’re going to give our character some arms. Here’s where things get slightly more complex. We’ll add another method to our class called createArms(). Again, we’ll define a geometry and a mesh. The arms will be long, thin cuboids, so we’ll pass in our desired dimensions for these.

As we need two arms, we’ll create them in a for loop.

createArms() {
	for(let i = 0; i < 2; i++) {
		const geometry = new THREE.BoxGeometry(0.25, 1, 0.25)
		const arm = new THREE.Mesh(geometry, material)
		
		this.group.add(arm)
	}
}

We don’t need to create the geometry in the for loop, as it will be the same for each arm.

Don’t forget to call the function in our init() method:

init() {
	this.createBody()
	this.createHead()
	this.createArms()
}

We’ll also need to position each arm either side of the body. I find it helpful here to create a variable m (for multiplier). This helps us position the left arm in the opposite direction on the x axis, with minimal code. (We’ll also use it rotate the arms in a moment too.)

createArms() {
	for(let i = 0; i < 2; i++) {
		const geometry = new THREE.BoxGeometry(0.25, 1, 0.25)
		const arm = new THREE.Mesh(geometry, material)
		const m = i % 2 === 0 ? 1 : -1
		
		this.group.add(arm)
		
		arm.position.x = m * 0.8
		arm.position.y = 0.1
	}
}

Additionally, we can rotate the arms in our for loop, so they stick out at a more natural angle (as natural as a cube person can be!):

arm.rotation.z = degreesToRadians(30 * m)
Figure with co-ordinate system overlaid
If our figure is placed in the center, the arm on the left will be positioned at the negative equivalent of the x-axis position of the arm on the right

Pivoting

When we rotate the arms you might notice that they rotate from a point of origin in the center. It can be hard to see with a static demo, but try moving the slider in this example.

See the Pen
ThreeJS figure arm pivot example (default pivot from center)
by Michelle Barker (@michellebarker)
on CodePen.0

We can see that the arms don’t move naturally, at an angle from the shoulder, but instead the entire arm rotates from the center. In CSS we would simply set the transform-origin. Three.js doesn’t have this option, so we need to do things slightly differently.

Two figures, the leftmost with an arm that pivots from the center, the rightmost with an arm that pivots from the top left
The figure on the right has arms that rotate from the top, for a more natural effect

Our steps are as follows for each arm:

  1. Create a new Three.js group.
  2. Position the group at the “shoulder” of our figure (or the point from which we want to rotate).
  3. Create a new mesh for the arm and position it relative to the group.
  4. Rotate the group (instead of the arm).

Let’s update our createArms() function to follow these steps. First we’ll create the group for each arm, add the arm mesh to the group, and position the group roughly where we want it:

createArms() {
	const geometry = new THREE.BoxGeometry(0.25, 1, 0.25)
	
	for(let i = 0; i < 2; i++) {
		const arm = new THREE.Mesh(geometry, material)
		const m = i % 2 === 0 ? 1 : -1
		
		// Create group for each arm
		const armGroup = new THREE.Group()
		
		// Add the arm to the group
		armGroup.add(arm)
		
		// Add the arm group to the figure
		this.group.add(armGroup)
		
		// Position the arm group
		armGroup.position.x = m * 0.8
		armGroup.position.y = 0.1
	}
}

To assist us with visualizing this, we can add one of Three.js’s built-in helpers to our figure. This creates a wireframe showing the bounding box of an object. It’s useful to help us position the arm, and once we’re done we can remove it.

// Inside the `for` loop:
const box = new THREE.BoxHelper(armGroup, 0xffff00)
this.group.add(box)

To set the transform origin to the top of the arm rather than the center, we then need to move the arm (within the group) downwards by half of its height. Let’s create a variable for height, which we can use when creating the geometry:

createArms() {
	// Set the variable
	const height = 1
	const geometry = new THREE.BoxGeometry(0.25, height, 0.25)
	
	for(let i = 0; i < 2; i++) {
		const armGroup = new THREE.Group()
		const arm = new THREE.Mesh(geometry, material)
		
		const m = i % 2 === 0 ? 1 : -1
		
		armGroup.add(arm)
		this.group.add(armGroup)
		
		// Translate the arm (not the group) downwards by half the height
		arm.position.y = height * -0.5
		
		armGroup.position.x = m * 0.8
		armGroup.position.y = 0.6
		
		// Helper
		const box = new THREE.BoxHelper(armGroup, 0xffff00)
		this.group.add(box)
	}
}

Then we can rotate the arm group.

// In the `for` loop
armGroup.rotation.z = degreesToRadians(30 * m)

In this demo, we can see that the arms are (correctly) being rotated from the top, for a more realistic effect. (The yellow is the bounding box.)

See the Pen
ThreeJS figure arm pivot example (using group)
by Michelle Barker (@michellebarker)
on CodePen.0

Eyes

Next we’re going to give our figure some eyes, for which we’ll use the Sphere geometry in Three.js. We’ll need to pass in three parameters: the radius of the sphere, and the number of segments for the width and height respectively (defaults shown here).

const geometry = new THREE.SphereGeometry(1, 32, 16)

As our eyes are going to be quite small, we can probably get away with fewer segments, which is better for performance (fewer calculations needed).

Let’s create a new group for the eyes. This is optional, but it helps keep things neat. If we need to reposition the eyes later on, we only need to reposition the group, rather than both eyes individually. Once again, let’s create the eyes in a for loop and add them to the group. As we want the eyes to be a different color from the body, we can define a new material:

createEyes() {
	const eyes = new THREE.Group()
	const geometry = new THREE.SphereGeometry(0.15, 12, 8)
	
	// Define the eye material
	const material = new THREE.MeshLambertMaterial({ color: 0x44445c })
	
	for(let i = 0; i < 2; i++) {
		const eye = new THREE.Mesh(geometry, material)
		const m = i % 2 === 0 ? 1 : -1
		
		// Add the eye to the group
		eyes.add(eye)
		
		// Position the eye
		eye.position.x = 0.36 * m
	}
}

We could add the eye group directly to the figure. However, if we decide we want to move the head later on, it would be better if the eyes moved with it, rather than being positioned entirely independently! For that, we need to modify our createHead() method to create another group, comprising both the main cube of the head, and the eyes:

createHead() {
	// Create a new group for the head
	this.head = new THREE.Group()
	
	// Create the main cube of the head and add to the group
	const geometry = new THREE.BoxGeometry(1.4, 1.4, 1.4)
	const headMain = new THREE.Mesh(geometry, material)
	this.head.add(headMain)
	
	// Add the head group to the figure
	this.group.add(this.head)
	
	// Position the head group
	this.head.position.y = 1.65
	
	// Add the eyes by calling the function we already made
	this.createEyes()
}

In the createEyes() method we then need to add the eye group to the head group, and position them to our liking. We’ll need to position them forwards on the z axis, so they’re not hidden inside the cube of the head:

// in createEyes()
this.head.add(eyes)

// Move the eyes forwards by half of the head depth - it might be a good idea to create a variable to do this!
eyes.position.z = 0.7

Legs

Lastly, let’s give our figure some legs. We can create these in much the same way as the eyes. As they should be positioned relative to the body, we can create a new group for the body in the same way that we did with the head, then add the legs to it:

createLegs() {
	const legs = new THREE.Group()
	const geometry = new THREE.BoxGeometry(0.25, 0.4, 0.25)
	
	for(let i = 0; i < 2; i++) {
		const leg = new THREE.Mesh(geometry, material)
		const m = i % 2 === 0 ? 1 : -1
		
		legs.add(leg)
		leg.position.x = m * 0.22
	}
	
	this.group.add(legs)
	legs.position.y = -1.15
	
	this.body.add(legs)
}

Positioning in the scene

If we go back to our constructor, we can position our figure group according to the parameters:

class Figure {
	constructor(params) {
		this.params = {
			x: 0,
			y: 0,
			z: 0,
			ry: 0,
			...params
		}
		
		this.group.position.x = this.params.x
		this.group.position.y = this.params.y
		this.group.position.z = this.params.z
		this.group.rotation.y = this.params.ry
	}
}

Now, passing in different parameters enables us to position it accordingly. For example, we can give it a bit of rotation, and adjust its x and y position:

const figure = new Figure({ 
	x: -4,
	y: -2,
	ry: degreesToRadians(35)
})
figure.init()

Alternatively, if we want to center the figure within the scene, we can use the Three.js Box3 function, which computes the bounding box of the figure group. This line will center the figure horizontally and vertically:

new THREE.Box3().setFromObject(figure.group).getCenter(figure.group.position).multiplyScalar(-1)

Making it generative

At the moment our figure is all one color, which doesn’t look particularly interesting. We can add a bit more color, and take the extra step of making it generative, so we get a new color combination every time we refresh the page! To do this we’re going to use a function to randomly generate a number between a minimum and a maximum. This is one I’ve borrowed from George Francis, which allows us to specify whether we want an integer or a floating point value (default is an integer).

const random = (min, max, float = false) => {
  const val = Math.random() * (max - min) + min

  if (float) {
    return val
  }

  return Math.floor(val)
}

Let’s define some variables for the head and body in our class constructor. Using the random() function, we’ll generate a value for each one between 0 and 360:

class Figure {
	constructor(params) {
		this.headHue = random(0, 360)
		this.bodyHue = random(0, 360)
	}
}

I like to use HSL when manipulating colors, as it gives us a fine degree of control over the hue, saturation and lightness. We’re going to define the material for the head and body, generating different colors for each by using template literals to pass the random hue values to the hsl color function. Here I’m adjusting the saturation and lightness values, so the body will be a vibrant color (high saturation) while the head will be more muted:

class Figure {
	constructor(params) {
		this.headHue = random(0, 360)
		this.bodyHue = random(0, 360)
		
		this.headMaterial = new THREE.MeshLambertMaterial({ color: `hsl(${this.headHue}, 30%, 50%` })
		this.bodyMaterial = new THREE.MeshLambertMaterial({ color: `hsl(${this.bodyHue}, 85%, 50%)` })
	}
}

Our generated hues range from 0 to 360, a full cycle of the color wheel. If we want to narrow the range (for a limited color palette), we could select a lower range between the minimum and maximum. For example, a range between 0 and 60 would select hues in the red, orange and yellow end of the spectrum, excluding greens, blues and purples.

We could similarly generate values for the lightness and saturation if we choose to.

Now we just need to replace any reference to material with this.headMaterial or this.bodyMaterial to apply our generative colors. I’ve chosen to use the head hue for the head, arms and legs.

See the Pen
ThreeJS figure (generative)
by Michelle Barker (@michellebarker)
on CodePen.0

We could use generative parameters for much more than just the colors. In this demo I’m generating random values for the size of the head and body, the length of the arms and legs, and the size and position of the eyes.

See the Pen
ThreeJS figure random generated
by Michelle Barker (@michellebarker)
on CodePen.0

Animation

Part of the fun of working with 3D is having our objects move in a three-dimensional space and behave like objects in the real world. We can add a bit of animation to our 3D figure using the Greensock animation library (GSAP).

GSAP is more commonly used to animate elements in the DOM. As we’re not animating DOM elements in this case, it requires a different approach. GSAP doesn’t require an element to animate — it can animate JavaScript objects. As one post in the GSAP forum puts it, GSAP is just “changing numbers really fast”.

We’ll let GSAP do the work of changing the parameters of our figure, then re-render our figure on each frame. To do this, we can use GSAP’s ticker method, which uses requestAnimationFrame. First, let’s animate the ry value (our figure’s rotation on the y axis). We’ll set it to repeat infinitely, and the duration to 20 seconds:

gsap.to(figure.params, {
	ry: degreesToRadians(360),
	repeat: -1,
	duration: 20
})

We won’t see any change just yet, as we aren’t re-rendering our scene. Let’s now trigger a re-render on every frame:

gsap.ticker.add(() => {
	// Update the rotation value
	figure.group.rotation.y = this.params.ry
	
	// Render the scene
	renderer.setSize(window.innerWidth, window.innerHeight)
	renderer.render(scene, camera)
})

Now we should see the figure rotating on its y axis in the center of the scene. Let’s give him a little bounce action too, by moving him up and down and rotating the arms. First of all we’ll set his starting position on the y axis to be a little further down, so he’s not bouncing off screen. We’ll set yoyo: true on our tween, so that the animation repeats in reverse (so our figure will bounce up and down):

// Set the starting position
gsap.set(figure.params, {
	y: -1.5
})

// Tween the y axis position and arm rotation
gsap.to(figure.params, {
	y: 0,
	armRotation: degreesToRadians(90),
	repeat: -1,
	yoyo: true,
	duration: 0.5
})

As we need to update a few things, let’s create a method called bounce() on our Figure class, which will handle the animation. We can use it to update the values for the rotation and position, then call it within our ticker, to keep things neat:

/* In the Figure class: */
bounce() {
	this.group.rotation.y = this.params.ry
	this.group.position.y = this.params.y
}

/* Outside of the class */
gsap.ticker.add(() => {
	figure.bounce()
	
	// Render the scene
	renderer.setSize(window.innerWidth, window.innerHeight)
	renderer.render(scene, camera)
})

To make the arms move, we need to do a little more work. In our class constructor, let’s define a variable for the arms, which will be an empty array:

class Figure {
	constructor(params) {
		this.arms = []
	}
}

In our createArms() method, in addition to our code, we’ll push each arm group to the array:

createArms() {
	const height = 0.85
	
	for(let i = 0; i < 2; i++) {
		/* Other code for creating the arms.. */
		
		// Push to the array
		this.arms.push(armGroup)
	}
}

Now we can add the arm rotation to our bounce() method, ensuring we rotate them in opposite directions:

bounce() {
	// Rotate the figure
	this.group.rotation.y = this.params.ry
	
	// Bounce up and down
	this.group.position.y = this.params.y
	
	// Move the arms
	this.arms.forEach((arm, index) => {
		const m = index % 2 === 0 ? 1 : -1
		arm.rotation.z = this.params.armRotation * m
	})
}

Now we should see our little figure bouncing, as if on a trampoline!

See the Pen
ThreeJS figure with GSAP
by Michelle Barker (@michellebarker)
on CodePen.0

Wrapping up

There’s much, much more to Three.js, but we’ve seen that it doesn’t take too much to get started building something fun with just the basic building blocks, and sometimes limitation breeds creativity! If you’re interested in exploring further, I recommend the following resources.

Resources

The post Creating 3D Characters in Three.js appeared first on Codrops.


Creating the Effect of Transparent Glass and Plastic in Three.js

$
0
0

In a recent release of Three.js (r129 and beyond) some fabulous new features to MeshPhysicalMaterial were merged. The new features allow us to create convincing transparent, glass-like and plastic-like materials that refract and diffuse the content behind them, and are as easy-to-use as adding a couple of material properties!

About this article

This article explores some advanced properties of materials. While the results are very technically impressive, the new features that enable them are simple to use! Some experience with three and an intermediate understanding of the concept of “materials” in 3D graphics is ideal. Code examples are written for brevity, so it’s best to dive into the sandbox code (provided with each screenshot) if you’re interested in the gritty implementation details.

The physics of optics, light, reflection and refraction are not discussed in-depth here. This article approaches these effects through an aesthetic lens: aiming for convincing and visually pleasing results, even if they are not scientifically accurate.

Rather than introducing new concepts, this is primarily a walkthrough of features that exist within three and its MeshPhysicalMaterial class. I’d like to gush and shower praise upon the contributors and maintainers of three. It continues to be a core pillar of 3D in the browser. It has a vibrant community and extremely talented contributors who continue to push the boundaries of what’s possible on a humble web page.

Prior Art

Creating transparent materials, especially with texture and diffusion, has for a long time required deep technical expertise and creative problem solving. Some projects have achieved an impressive and convincing effect in WebGL through bespoke techniques:

A screenshot from Make Me Pulse’s 2018 Wishes, showcasing frosted glass materials in WebGL
The Jam3 FWA 100 project, showcasing glass-like orbs

Jesper Vos published an incredible tutorial here on Codrops: Real-time Multiside Refraction in Three Steps, which includes some great insights into the science and simulation of refraction.

In addition, these excellent technical examples provided the inspiration for writing this article, and further exploring what’s possible with these new features.

Three.js

three is an open-source javascript library for rendering 3D graphics in the browser. It provides a friendly API and abstractions that make working with WebGL more palatable and expressive. three has been around since 2010, is extremely well battle-tested, and is the de-facto standard for rendering 3D content on the internet. See the list of case studies on the home page, docs, examples, or source.

MeshPhysicalMaterial

MeshPhysicalMaterial is a relatively recent Physically-Based Rendering (PBR) built-in material for three. It’s an evolution and extension of the already impressive MeshStandardMaterial, providing additional features to pump the photo-realism.

This visual fidelity comes at a cost, from the docs:  As a result of these complex shading features, MeshPhysicalMaterial has a higher performance cost, per pixel, than other Three.js materials. Most effects are disabled by default, and add cost as they are enabled.

Beyond the properties offered in MeshStandardMaterial, it introduces some new ones:

Transmission

transmission is the key to transparent glass-like and plastic-like effects. Traditionally when we adjust the opacity of an element to make it transparent, its visual presence is diluted as a whole. The object appears ghostly, uniformly transparent, and not realistic as a see-through object. In the real-world, transparent objects reflect light and show glare. They have a physical presence even though they may be perfectly clear.

Reflectivity properties

MeshPhysicalMaterial includes some properties that estimate refraction through the transmissible object: thickness, ior (Index-of-refraction) and reflectivity. We’ll mostly ignore ior and reflectivity (which changes ior too, but is mapped to a 0-1 range) as the defaults work great!

thickness is the magic here, as we’ll see shortly.

Clearcoat

Like a layer of lacquer, clearcoat provides an additional thin reflective layer on the surface of objects. Previously this would require a second version of the object, with a separate material, and with different parameters.

Other

There are some other additional properties on MeshPhysicalMaterial like sheen and attenuationTint which I won’t be touching on in this article.

We can expect to see more and more features added to this material in future releases.


First steps

First things first, let’s create a scene and pop something in it! We’ll start with an Icosahedron because hey, they just look cool.

I’m skipping the basic scene setup stuff here, I recommend diving into the sandbox source or three docs if you’re unfamiliar with this.

const geometry = new THREE.IcosahedronGeometry(1, 0);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh);

An Icosahedron with normal shading in a blank scene
View on CodeSandbox

Looks like an Icosahedron! Let’s apply our MeshPhysicalMaterial:

const material = new THREE.MeshPhysicalMaterial({
  metalness: 0,  
  roughness: 0
});

The options metalness and roughness are the two primary handles with PBR materials (they are on MeshStandardMaterial too). They can be used to set the stage for how our material responds to lighting and environment. Having both set at zero describes something like “A non-metallic object with a highly polished surface”.


View on CodeSandbox

Doesn’t look like much! Physically-based materials need light to reflect, so let’s add some light:

const light = new THREE.DirectionalLight(0xfff0dd, 1);
light.position.set(0, 5, 10);
scene.add(light);


View on CodeSandbox

Cool, there it is again… Now let’s make it transparent!

Call the Glazier

The transmission option is responsible for applying our transparency. It makes the “fill” or “body” of the object transparent, while leaving all lighting and reflections on the surface in-tact.

Note that we’re not using the opacity option, which applies a uniform transparency to the material as a whole. We also don’t need to include the transparent option on the material for it to appear transparent through transmission.

const material = new THREE.MeshPhysicalMaterial({  
  roughness: 0,  
  transmission: 1, // Add transparency
});


View on CodeSandbox

I think that’s transparent, we can see the background colour through it. Let’s pop something else behind it to be sure. We’ll add a textured plane as our “backdrop”:

const bgTexture = new THREE.TextureLoader().load("src/texture.jpg");
const bgGeometry = new THREE.PlaneGeometry(5, 5);
const bgMaterial = new THREE.MeshBasicMaterial({ map: bgTexture });
const bgMesh = new THREE.Mesh(bgGeometry, bgMaterial);
bgMesh.position.set(0, 0, -1);
scene.add(bgMesh);


View on CodeSandbox

It’s transparent! It’s lacking something though. There’s nothing but a tiny flicker of movement on the corners of our geometry; as if our material is made from the most delicate and fragile of super-thin, super-clear glass.

Now here’s the magic part!

const material = new THREE.MeshPhysicalMaterial({  
  roughness: 0,  
  transmission: 1,  
  thickness: 0.5, // Add refraction!
});


View on CodeSandbox

By adding a single option: thickness to our material, we’ve now been given the gift of refraction through our object! Our background plane, which is a completely separate object, simply sitting behind our Icosahedron in the scene, now gets refracted.

This is incredible! Previous methods of achieving this required much more work and intense technical understanding. This has immediately democratised refractive materials in WebGL.

The effect is especially impressive when viewed in motion, and from an angle:

Swooping around our glass object to see it refracting the rest of the scene

Have a play by dragging around in this sandbox:

Diverse objects

While the sharp facets of our Icosahedron show a nice “cut-gem” style of refraction, we rarely see such precisely cut glass objects at any size other than tiny. This effect is greatly enhanced when geometries with smoother edges are used.
Let’s increase the detail level of our Icosahedron to form a sphere:

const geometry = new THREE.IcosahedronGeometry(1, 15);


View on CodeSandbox

This shows some optical distortion in addition to the refraction based on the shape of the geometry!


Hot tip: with all of the PolyhedronGeometry types in three, any detail level above zero is rendered as a sphere, rather than a faceted polyhedron as far as transmission is concerned.


You may notice that the distorted content is a little pixelated, this is due to the material upscaling what’s transmitted through it to perform the distortion. We can mitigate this a bit with some other effects which we’ll cover later.

Let’s explore adding some texture to glass material:

const material = new THREE.MeshPhysicalMaterial({  
  roughness: 0.7,   
  transmission: 1,  
  thickness: 1
});

The roughness option on our transmissible material provides us with a “frosting” level, making light that passes through the material more diffuse.


View in CodeSandbox


This becomes immediately recognisable as a frosted glass object, with a fine powdery texture.

Notes on roughness:

  • The middle of the roughness range can display some quite noticeably pixelated transmitted content (at the time of writing). In my experience the best results are found in the low (0-0.15) and higher (0.65+) ends of the range. This can also be quite successfully mitigated with some of the things we’ll add shortly.
  • The distance of the transmissible object from the camera affects how roughness is rendered. It’s best to tweak the roughness parameter once you’ve established your scene

Hot tip: Using a small amount of roughness (0.05 – 0.15) can help soften aliasing on the transmitted content at the cost of a bit of sharpness.


For the rest of our examples we’ll include two additional geometries for reference: a RoundedBoxGeometry and a 3D model of a dragon (loaded as a GLTF, but only used for the geometry):


View on CodeSandbox

Through the lens

While the transmission effect is already appealing, there’s so much more we can do to make this appear truer-to-life.

The next thing we’ll do is add an environment map. It’s recommended that you always include an envMap when using MeshPhysicalMaterial, as per the docs: For best results, always specify an environment map when using this material.

Highly reflective objects show reflections, and glare, and glimpses of their surrounding environment reflected off their surface. It’s unusual for a shiny object to be perfectly unreflective; as they have been in our examples so far.

We’ll use a high quality High Dynamic Range Image (HDRI) environment map. I’ve chosen this one for its bright fluorescent overhead lighting:

const hdrEquirect = new THREE.RGBELoader().load(
  "src/empty_warehouse_01_2k.hdr",  
  () => { 
    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping; 
  }
);
const material = new THREE.MeshPhysicalMaterial({  
  ...  
  envMap: hdrEquirect
});


View on CodeSandbox

NICE! Now that looks more realistic. The objects glint and shimmer in our bright environment; much more like the lighting challenges faced by a photographer of shiny things.

This is where our rounded geometries really shine too. Their smoother curves and edges catch light differently, really amplifying the effect of a highly polished surface.


Hot tip: Adding an envMap texture of some sort helps to resolve some rendering artifacts of this material. This is why it’s always recommended to include one (beyond the fact that it looks great!).


If you adjust the roughness level upward, you’ll notice that the reflections are diffused by the rougher frosted texture of the surface; however, we may want an object that’s semi-transparent while still having a shiny surface.

The clearcoat options allow us to include an additional reflective layer on the surface of our object (think lacquered wood, powder coatings, or plastic films). In the case of our transparent objects, we can make them from semi-transparent glass or plastic which still has a polished and reflective surface.


View on CodeSandbox

Adjusting the clearcoatRoughness option adjusts how highly polished the surface is; visually spanning the range from highly-polished frosted glass through to semi-gloss and matte frosted plastics. This effect is pretty convincing! You can almost feel the tack and texture of these objects.

So far we’ve been exploring objects with perfectly smooth surfaces. To really bring some texture to them, we can add a normal map:

const textureLoader = new THREE.TextureLoader();
const normalMapTexture = textureLoader.load("src/normal.jpg");
normalMapTexture.wrapS = THREE.RepeatWrapping;
normalMapTexture.wrapT = THREE.RepeatWrapping;
const material = new THREE.MeshPhysicalMaterial({  
  ...  
  normalMap: normalMapTexture,  
  clearcoatNormalMap: normalMapTexture,
});


View on CodeSandbox

The interplay between the normalMap and clearcoatNormalMap is interesting. By setting the normalMap we affect the transmission through the object, adding a textured frosting that refracts light differently. By setting the clearcoatNormalMap we affect the finish on the surface of the object.


Hot tip: The additional texture added by the normalMap greatly reduces the visible pixelation on the transmitted content, effectively solving this issue for us.


As a final touch, we’ll add a post-processing pass to apply bloom to our scene. Bloom adds that extra little bit of photographic appeal by simulating volumetric glare from the bright overhead lighting bathing our objects.

I’ll leave information around implementation post-processing within three to the docs and examples. In this sandbox I’ve included the UnrealBloomPass.

Bloom always looks good.

There we have it! Convincingly transparent, textured and reflective 3D objects, rendered in real-time, and without much effort. This deserves to be celebrated, what an empowering experience it is playing with MeshPhysicalMaterial.

Drippin ice

Just for fun, let’s crank the dial on this by rendering many of our transparent objects using three‘s InstancedMesh (link).


View on CodeSandbox

OOOUF! YES.
Instances can’t be seen through each other, which is a general limitation of transmission on MeshPhysicalMaterial (current as of r133); but in my opinion the effect is still very cool.

Explore for yourself

Finally, here’s our dragon model with a bunch of material options enabled in the GUI:

Have a play, check out metalness, play around with color to explore colourful tinted glass, tweak the ior to change our glass into crystal!

Sign-off

I’ve really only scratched the surface of what can be achieved with MeshPhysicalMaterial. There are even more options available within this material, sheen, roughnessMap, transmissionMap, attenuationTint and all sorts of other things provide inroads to many more effects. Dig deep into the docs and source if you’re interested!

This is an enabler, given the creative vision for a transparent object you can use these tools to work towards a convincing result. Transparent materials in three are here, you can start using them in your projects today.

Attributions

© 2021 Kelly Milligan

The post Creating the Effect of Transparent Glass and Plastic in Three.js appeared first on Codrops.

Animated 3D Ribbons with Three.js

Teleportation Transition with Three.js

Pixelated Distortion Effect with Three.js

$
0
0

In this ALL YOUR HTML stream and coding session we’ll be recreating the interactive pixel distortion effect seen on the website for the music video covers of “Infinite Bad Guy” made as an AI Experiment at Google and YouTube. We’ll be using Three.js and datatexture to achieve the look.

This coding session was streamed live on December 12, 2021.

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Pixelated Distortion Effect with Three.js appeared first on Codrops.

Three.js Animation with K-d (Christmas) Tree Algorithm

How to Add More Fun to a Game: Extending “The Aviator”

$
0
0

If you like cute little games you will love Karim Maaloul’s “The Aviator” — as a pilot you steer your aircraft across a round little ocean world, evading red “enemies” and collecting blue energy tokens to avoid crashing into the water. It runs entirely in the browser so make sure to quickly play a round to better understand what we are about to do in this tutorial.

By the way, Karim co-founded the Belgian creative agency Epic. His style is unique in its adorableness and his animation craftmanship is astonishing as you can also see in his series of WebGL experiments.

Karim thankfully wrote about the making of and open sourced the code and while it is a fun little game there is still a lot of potential to get even more out of it. In this article we will explore some hands-on changes on how to bring the most fun based on the foundation we have here, a small browser game using Three.js.

This tutorial requires some knowledge of JavaScript and Three.js.

What makes a game fun?

While there obviously is no definitive recipe there are a few key mechanics that will maximize your chances of generating fun. There is a great compilation on gamedesigning.org, so let’s see which items apply already:

✅ Great controls
✅ An interesting theme and visual style
🚫 Excellent sound and music
🚫 Captivating worlds
🤔 Fun gameplay
🚫 Solid level design
🚫 An entertaining story & memorable characters
🤔 Good balance of challenge and reward
✅ Something different

We can see there’s lots to do, too much for a single article of course, so we will get to the general game layout, story, characters and balance later. Now we will improve the gameplay and add sounds — let’s go!

Adding weapons

Guns are always fun! Some games like Space Invaders consist entirely of shooting and it is a great mechanic to add visual excitement, cool sound effects and an extra dimension to the skill requirement so we not only have the up and down movement of the aircraft.

Let’s try some simple gun designs:

The “Simple gun” (top) and the “Better gun” (bottom).

These 3D models consist of only 2–3 cylinders of shiny metal material:

const metalMaterial = new THREE.MeshStandardMaterial({
    color: 0x222222,
    flatShading: true,
    roughness: 0.5,
    metalness: 1.0
})

class SimpleGun {
    static createMesh() {
        const BODY_RADIUS = 3
        const BODY_LENGTH = 20

        const full = new THREE.Group()
        const body = new THREE.Mesh(
            new THREE.CylinderGeometry(BODY_RADIUS, BODY_RADIUS, BODY_LENGTH),
            metalMaterial,
        )
        body.rotation.z = Math.PI/2
        full.add(body)

        const barrel = new THREE.Mesh(
            new THREE.CylinderGeometry(BODY_RADIUS/2, BODY_RADIUS/2, BODY_LENGTH),
            metalMaterial,
        )
        barrel.rotation.z = Math.PI/2
        barrel.position.x = BODY_LENGTH
        full.add(barrel)

        return full
    }
}

We will have 3 guns: A SimpleGun, then the DoubleGun as just two of those and then the BetterGun which has just a bit different proportions and another cylinder at the tip.

Guns mounted to the airplane

Positioning the guns on the plane was done by simply experimenting with the positional x/y/z values.

The shooting mechanic itself is straight forward:

class SimpleGun {
  downtime() {
    return 0.1
  }

  damage() {
    return 1
  }

  shoot(direction) {
    const BULLET_SPEED = 0.5
    const RECOIL_DISTANCE = 4
    const RECOIL_DURATION = this.downtime() / 1.5

    const position = new THREE.Vector3()
    this.mesh.getWorldPosition(position)
    position.add(new THREE.Vector3(5, 0, 0))
    spawnProjectile(this.damage(), position, direction, BULLET_SPEED, 0.3, 3)

    // Little explosion at exhaust
    spawnParticles(position.clone().add(new THREE.Vector3(2,0,0)), 1, Colors.orange, 0.2)

    // Recoil of gun
    const initialX = this.mesh.position.x
    TweenMax.to(this.mesh.position, {
      duration: RECOIL_DURATION,
      x: initialX - RECOIL_DISTANCE,
      onComplete: () => {
        TweenMax.to(this.mesh.position, {
          duration: RECOIL_DURATION,
          x: initialX,
        })
      },
    })
  }
}

class Airplane {
  shoot() {
    if (!this.weapon) {
      return
    }

    // rate-limit shooting
    const nowTime = new Date().getTime() / 1000
    if (nowTime-this.lastShot < this.weapon.downtime()) {
      return
    }
    this.lastShot = nowTime

    // fire the shot
    let direction = new THREE.Vector3(10, 0, 0)
    direction.applyEuler(airplane.mesh.rotation)
    this.weapon.shoot(direction)

    // recoil airplane
    const recoilForce = this.weapon.damage()
    TweenMax.to(this.mesh.position, {
      duration: 0.05,
      x: this.mesh.position.x - recoilForce,
    })
  }
}

// in the main loop
if (mouseDown[0] || keysDown['Space']) {
  airplane.shoot()
}

Now the collision detection with the enemies, we just check whether the enemy’s bounding box intersects with the bullet’s box:

class Enemy {
	tick(deltaTime) {
		...
		const thisAabb = new THREE.Box3().setFromObject(this.mesh)
		for (const projectile of allProjectiles) {
			const projectileAabb = new THREE.Box3().setFromObject(projectile.mesh)
			if (thisAabb.intersectsBox(projectileAabb)) {
				spawnParticles(projectile.mesh.position.clone(), 5, Colors.brownDark, 1)
				projectile.remove()
				this.hitpoints -= projectile.damage
			}
		}
		if (this.hitpoints <= 0) {
			this.explode()
		}
	}

	explode() {
		spawnParticles(this.mesh.position.clone(), 15, Colors.red, 3)
		sceneManager.remove(this)
	}
}

Et voilá, we can shoot with different weapons and it’s super fun!

Changing the energy system to lives and coins

Currently the game features an energy/fuel bar that slowly drains over time and fills up when collecting the blue pills. I feel like this makes sense but a more conventional system of having lives as health, symbolized by hearts, and coins as goodies is clearer to players and will allow for more flexibility in the gameplay.

In the code the change from blue pills to golden coins is easy: We changed the color and then the geometry from THREE.TetrahedronGeometry(5,0) to THREE.CylinderGeometry(4, 4, 1, 10).

The new logic now is: We start out with three lives and whenever our airplane crashes into an enemy we lose one. The amount of collected coins show in the interface. The coins don’t yet have real impact on the gameplay but they are great for the score board and we can easily add some mechanics later: For example that the player can buy accessoires for the airplane with their coins, having a lifetime coin counter or we could design a game mode where the task is to not miss a single coin on the map.

Adding sounds

This is an obvious improvement and conceptually simple — we just need to find fitting, free sound bites and integrate them.

Luckily on https://freesound.org and https://www.zapsplat.com/ we can search for sound effects and use them freely, just make sure to attribute where required.

Example of a gun shot sound: https://freesound.org/people/LeMudCrab/sounds/163456/.

We load all 24 sound files at the start of the game and then to play a sound we code a simple audioManager.play(‘shot-soft’). Repetitively playing the same sound can get boring for the ears when shooting for a few seconds or when collecting a few coins in a row, so we make sure to have several different sounds for those and just select randomly which one to play.

Be aware though that browsers require a page interaction, so basically a mouse click, before they allow a website to play sound. This is to prevent websites from annoyingly auto-playing sounds directly after loading. We can simply require a click on a “Start” button after page load to work around this.

Adding collectibles

How do we get the weapons or new lives to the player? We will spawn “collectibles” for that, which is the item (a heart or gun) floating in a bubble that the player can catch.

We already have the spawning logic in the game, for coins and enemies, so we can adopt that easily.

class Collectible {
	constructor(mesh, onApply) {
		this.mesh = new THREE.Object3D()
		const bubble = new THREE.Mesh(
			new THREE.SphereGeometry(10, 100, 100),
			new THREE.MeshPhongMaterial({
				color: COLOR_COLLECTIBLE_BUBBLE,
				transparent: true,
				opacity: .4,
				flatShading: true,
			})
		)
		this.mesh.add(bubble)
		this.mesh.add(mesh)
		...
	}


	tick(deltaTime) {
		rotateAroundSea(this, deltaTime, world.collectiblesSpeed)

		// rotate collectible for visual effect
		this.mesh.rotation.y += deltaTime * 0.002 * Math.random()
		this.mesh.rotation.z += deltaTime * 0.002 * Math.random()

		// collision?
		if (utils.collide(airplane.mesh, this.mesh, world.collectibleDistanceTolerance)) {
			this.onApply()
			this.explode()
		}
		// passed-by?
		else if (this.angle > Math.PI) {
			sceneManager.remove(this)
		}
	}


	explode() {
		spawnParticles(this.mesh.position.clone(), 15, COLOR_COLLECTIBLE_BUBBLE, 3)
		sceneManager.remove(this)
		audioManager.play('bubble')

		// animation to make it very obvious that we collected this item
		TweenMax.to(...)
	}
}


function spawnSimpleGunCollectible() {
	const gun = SimpleGun.createMesh()
	gun.scale.set(0.25, 0.25, 0.25)
	gun.position.x = -2

	new Collectible(gun, () => {
		airplane.equipWeapon(new SimpleGun())
	})
}

And that’s it, we have our collectibles:

The only problem is that I couldn’t for the life of me create a heart model from the three.js primitives so I resorted to a free, low-poly 3D model from the platform cgtrader.

Defining the spawn-logic on the map in a way to have a good balance of challenge and reward requires sensible refining so after some experimenting this felt nice: Spawn the three weapons after a distance of 550, 1150 and 1750 respectively and spawn a life a short while after losing one.

Some more polish

  • The ocean’s color gets darker as we progress through the levels
  • Show more prominently when we enter a new level
  • Show an end game screen after 5 levels
  • Adjusted the code for a newer version of the Three.js library
  • Tweaked the color theme

More, more, more fun!

We went from a simple fly-up-and-down gameplay to being able to collect guns and shoot the enemies. The sounds add to the atmosphere and the coins mechanics sets us up for new features later on.

Make sure to play our result here! Collect the weapons, have fun with the guns and try to survive until the end of level 5.

If you are interested in the source code, you find it here on GitHub.

How to proceed from here? We improved on some key mechanics and have a proper basis but this is not quite a finalized, polished game yet.

As a next step we plan to dive more into game design theory. We will look at several of the most popular games of the endless runner genre to get insights into their structure and mechanics and how they keep their players engaged. The aim would be to learn more about the advanced concepts and build them into The Aviator.

Subway Surfer, the most successful “endless runner” game.

Stay tuned, so long!

The post How to Add More Fun to a Game: Extending “The Aviator” appeared first on Codrops.

Getting Creative with Infinite Loop Scrolling

$
0
0

Infinite scrolling is a web design technique that allows users to scroll through a never-ending list of content by automatically loading new items as the user reaches the bottom of the page. Instead of having to click through to a new page to see more content, the content is automatically loaded and appended to the bottom of the page as the user scrolls. This can create a seamless and engaging browsing experience for users, as they can easily access a large amount of content without having to wait for new pages to load. Forget about reaching the footer, though!

Looping the scroll of a page refers to the process of automatically taking users back to the top of the page once they reach the end of the scroll. This means that they will be able to continuously scroll through the same content over and over again, rather than being presented with new content as they scroll down the page.

In this article, we will show some examples creative loop scrolling, and then reimplement the effect seem on Bureau DAM. We will be using Lenis by Studio Freight to implement the looping effect and GSAP for the animations.

What is loop scrolling?

When your content is limited, you can do creative things with loop scrolling, which is basically infinite scrolling with repeating content. A great example for such a creative use is the effect seen on Bureau DAM:

Bureau DAM employs a fun looping scroll effect

Some great ideas how looping the scroll can be used in a creative way and add value to a website:

  • It can create an immersive experience for users. By continuously scrolling through the same content, users can feel more immersed in the website and engaged with the content. This can be particularly useful for websites that want to create a strong emotional connection with their audience.
  • It can be used to create a game or interactive experience. By adding interactive elements to the content that is being looped, developers can create a game or interactive experience for users. For example, a website could use looping the scroll to create a scrolling game or to allow users to interact with the content in a novel way.
  • It can be used to create a “time loop” effect. By looping the scroll in a particular way, developers can create the illusion of a “time loop,” where the content appears to repeat itself in a continuous loop. This can be particularly effective for websites that want to convey a sense of continuity or timelessness.

A simple example

Let’s create a simple example. We’ll set up a grid of 6 images that repeat on scroll. How can we achieve this? It’s simple: we need to repeat the content in a way that when reaching the end, we can simply reset the scroll to the top without anybody noticing! We’ve previously explored this concept in our CSS-only marquee effect. In another demo we also show how to play with this kind of infinite animation.

So, for our first example we have the following markup:

<div class="grid">
	<div class="grid__item" style="background-image:url(img/1.jpg);"></div>
	<div class="grid__item" style="background-image:url(img/4.jpg);"></div>
	<div class="grid__item" style="background-image:url(img/3.jpg);"></div>
	<div class="grid__item" style="background-image:url(img/5.jpg);"></div>
	<div class="grid__item" style="background-image:url(img/2.jpg);"></div>
	<div class="grid__item" style="background-image:url(img/6.jpg);"></div>
</div>

Our styles will create a 3×3 grid:

.grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 5vh;
}

.grid__item {
	height: 47.5vh; /* 50vh minus half of the gap */
	background-size: cover;
	background-position: 50% 20%;
}

.grid__item:nth-child(3n-2) {
	border-radius: 0 2rem 2rem 0;
}

.grid__item:nth-child(3n) {
	border-radius: 2rem 0 0 2rem;
}

.grid__item:nth-child(3n-1) {
	border-radius: 2rem;
}

Lenis comes with a handy option for making the scroll infinite. In our script we’ll make sure to repeat the visible grid items (in this case it’s 6):

const lenis = new Lenis({
    smooth: true,
    infinite: true,
});

function raf(time) {
    lenis.raf(time);
    requestAnimationFrame(raf);
}

requestAnimationFrame(raf);

// repeat first six items by cloning them and appending them to the .grid
const repeatItems = (parentEl, total = 0) => {
    const items = [...parentEl.children];
    for (let i = 0; i <= total-1; ++i) {
        var cln = items[i].cloneNode(true);
        parentEl.appendChild(cln);
    }
};
repeatItems(document.querySelector('.grid'), 6);

The result is a grid that repeats on scroll:

A simple example of loop scrolling. View the demo

We can also change the direction of the scroll and go sideways!

View the demo

Playing with animations

While we scroll, we can add some fancy animations to our grid items. Paired with switching the transform origin in the right time, we can get something playful like this:

Scale animation while scrolling. View the demo

We can also play with the scale and opacity values to create something like a vanishing effect:

View the demo

Or, add some extreme stretchiness:

View the demo

We can also play with a 3D animation and an additional filter effect:

Adding a filter effect and perspective. View the demo

The Bureau DAM example

Now, let’s see how we can remake the Bureau DAM animation. As we don’t have a grid here, things get a bit simpler. Just like them, we’ll use an SVG for the typography element, as we want to stretch it over the screen:

<div class="grid">
	<div class="grid__item grid__item--stack">
		<svg class="grid__item-logo" width="100%" height="100%" viewBox="0 0 503 277" preserveAspectRatio="none">
		<path d="M56.3 232.3 56.3 193.8C56.3 177.4 54.7 174.1 48.5 165.9 35.4 148.8 17.6 133 8.5 120.8.7 110.3.1 103.7.1 85.6L.1 45.2C.1 14.9 13.5.5 41 .5 68.8.5 79.1 15.3 79.1 45.2L79.1 94.5 56.9 94.5 56.9 48.5C56.9 35 53.5 25.8 40.7 25.8 29.8 25.8 24.1 32.4 24.1 45.2L24.1 85.3C24.1 96.8 25.1 100.1 29.8 106.3 41 121.8 59.1 137.6 68.8 150.4 77.2 161.6 80 169.8 80 193.5L80 232.3C80 260.9 68.8 277 40.4 277 12.3 277 .1 261.5.1 232.3L.1 174.7 22.9 174.7 22.9 228.7C22.9 243.1 26.9 252.3 40.1 252.3 51.6 252.3 56.3 245.1 56.3 232.3ZM176.5 277 101.5 277 101.5.5 127.1.5 127.1 251.8 176.5 251.8 176.5 277ZM290 277 264.5 277 258.4 230.6 217.1 230.6 211 277 186.2 277 224.1.5 254.1.5 290 277ZM218.1 207.1 253.4 207.1C247.7 159.7 241.6 114 236.3 65.3 230.5 114 224.5 159.7 218.1 207.1ZM399.6 277 374 277 326.3 75.1C326.6 117.1 326.6 155.7 326.6 197.7L326.6 277 304.5 277 304.5.5 335 .5 377.4 203.1C377 165.1 377 129.2 377 91.2L377 .5 399.6.5 399.6 277ZM471.5 277 446.3 277 446.3 26.3 415.3 26.3 415.3.5 502.4.5 502.4 26.3 471.5 26.3 471.5 277Z" id="SLANT" fill="#fff"></path>
		</svg>
		<p class="grid__item-text credits">An infinite scrolling demo based on <a href="https://www.bureaudam.com/">Bureau DAM</a></p>
	</div>
	<div class="grid__item">
		<div class="grid__item-inner">
			<div class="grid__item-img" style="background-image:url(img/1.jpg);"></div>
			<p class="grid__item-text"><a href="#">View all projects →</a></p>
		</div>
	</div>
</div>

In our CSS we set a couple of styles that will make sure the items are stretched to full screen:

.grid {
    display: flex;
    flex-direction: column;
    gap: 5vh;
}

.grid__item {
    height: 100vh; 
	place-items: center;
    display: grid;
}

.grid__item-inner {
	display: grid;
	gap: 1rem;
	place-items: center;
	text-align: center;
}

.grid__item--stack {
	display: grid;
	gap: 2rem;
	grid-template-rows: 1fr auto;
}

.grid__item-logo {
	padding: 8rem 1rem 0;
}

.grid__item-img {
	background-size: cover;
    background-position: 50% 50%;
	height: 70vh;
	aspect-ratio: 1.5;
}

.grid__item-text {
	margin: 0;
}

A proper setting for the transform origins ensures that we get the right visual effect:

gsap.registerPlugin(ScrollTrigger);

// repeat first three items by cloning them and appending them to the .grid
const repeatItems = (parentEl, total = 0) => {
    const items = [...parentEl.children];
    for (let i = 0; i <= total-1; ++i) {
        var cln = items[i].cloneNode(true);
        parentEl.appendChild(cln);
    }
};

const lenis = new Lenis({
    smooth: true,
    infinite: true
});

lenis.on('scroll',()=>{
    ScrollTrigger.update() // Thank you Clément!
})

function raf(time) {
    lenis.raf(time);
    requestAnimationFrame(raf);
}

imagesLoaded( document.querySelectorAll('.grid__item'), { background: true }, () => {

    document.body.classList.remove('loading');

    repeatItems(document.querySelector('.grid'), 1);

    const items = [...document.querySelectorAll('.grid__item')];

    // first item
    const firtsItem = items[0];
    gsap.set(firtsItem, {transformOrigin: '50% 100%'})
    gsap.to(firtsItem, {
        ease: 'none',
        startAt: {scaleY: 1},
        scaleY: 0,
        scrollTrigger: {
            trigger: firtsItem,
            start: 'center center',
            end: 'bottom top',
            scrub: true,
            fastScrollEnd: true,
            onLeave: () => {
                gsap.set(firtsItem, {scaleY: 1,})
            },
        }
    });

    // last item  
    const lastItem = items[2];
    gsap.set(lastItem, {transformOrigin: '50% 0%', scaleY: 0})
    gsap.to(lastItem, {
        ease: 'none',
        startAt: {scaleY: 0},
        scaleY: 1,
        scrollTrigger: {
            trigger: lastItem,
            start: 'top bottom',
            end: 'bottom top',
            scrub: true,
            fastScrollEnd: true,
            onLeaveBack: () => {
                gsap.set(lastItem, {scaleY: 1})
            }
        }
    });
    
    // in between
    let ft;
    let st;
    const middleItem = items[1];
        
    ft = gsap.timeline()
    .to(middleItem, {
        ease: 'none',
        onStart: () => {
            if (st) st.kill()
        },
        startAt: {scale: 0},
        scale: 1,
        scrollTrigger: {
            trigger: middleItem,
            start: 'top bottom',
            end: 'center center',
            scrub: true,
            onEnter: () => gsap.set(middleItem, {transformOrigin: '50% 0%'}),
            onEnterBack: () => gsap.set(middleItem, {transformOrigin: '50% 0%'}),
            onLeave: () => gsap.set(middleItem, {transformOrigin: '50% 100%'}),
            onLeaveBack: () => gsap.set(middleItem, {transformOrigin: '50% 100%'}),
        },
    });

    st = gsap.timeline()
    .to(middleItem, {
        ease: 'none',
        onStart: () => {
            if (ft) ft.kill()
        },
        startAt: {scale: 1},
        scale: 0,
        scrollTrigger: {
            trigger: middleItem,
            start: 'center center',
            end: 'bottom top',
            scrub: true,
            onEnter: () => gsap.set(middleItem, {transformOrigin: '50% 100%'}),
            onEnterBack: () => gsap.set(middleItem, {transformOrigin: '50% 100%'}),
            onLeave: () => gsap.set(middleItem, {transformOrigin: '50% 0%'}),
            onLeaveBack: () => gsap.set(middleItem, {transformOrigin: '50% 0%'}),
        },
    });
    
    requestAnimationFrame(raf);
    
    const refresh = () => {
        ScrollTrigger.clearScrollMemory();
        window.history.scrollRestoration = 'manual';
        ScrollTrigger.refresh(true);
    }

    refresh();
    window.addEventListener('resize', refresh);

});

The result is a squashy and squeezy sequence of joy:

Check out the demo

Update: Thanks to Clément’s comment and help on keeping Lenis and GSAP in sync, it’s now much smoother!

And that’s it! Hope you had some fun and that you got some inspiration for your next projects.


Rotating Twisted 3D Typography with Three.js and Shaders

$
0
0
A brief exploration into how to twist and rotate text in 3D using Three.js and Shader magic.

Crafting a Dice Roller with Three.js and Cannon-es

$
0
0
This tutorial will guide you through the process of creating a 3D dice roller using Three.js and Cannon-es.

On-Scroll Perspective Grid Animations

$
0
0
On-scroll animations for perspective image grids with different layouts.

3D Glass Portal Card Effect with React Three Fiber and Gaussian Splatting

$
0
0
Explore the creation of a 3D glass portal with React Three Fiber, with optimized rendering using Gaussian Splatting and integrating real-world objects.

Magnetic 3D Grid Interaction with Content Preview

$
0
0
A grid layout with a magnetic 3D interaction effect and a content preview animation.
Viewing all 120 articles
Browse latest View live