The Three.js tag has really bad questions

The distribution of&nbsp;<a href="https://stackoverflow.com/questions/tagged/three.js" target="_blank">three.js</a>&nbsp;tagged question is something like this:

The distribution of three.js tagged question is something like this:

1 x

super clear, graphics / WebGL / GLSL / JavaScript related question for the library, that makes sense, is properly formatted and helps the community.

19 x

confusing, lazy, ill-formatted questions that make no sense, and are often not even remotely three.js related (basic JavaScript or DOM)

What can be done to remedy this?

I notice a lot of the questions simply go unanswered. Some do get downvoted though.

I find myself writing more comments than answers lately. Sometimes I link to MCVE, and sometimes I explain why the question is not three.js-related and remove the tags.

Other times, users accept answers that are wrong. They ask one question, but within there's a different problem hidden. The accepted answer usually focuses on the problem, rather than the question. This is when I usually suggest to edit the question, but some times I urged for the different answers to be accepted.

It doesn't seem that there are too many people responding to three.js, and I'm also under the impression that a lot of the questions are asked by people who register specifically to ask that question.

Is it possible to somehow urge the users to read MCVE before posting a question with this tag?

Build a 3D Application with React and Three.js

Build a 3D Application with React and Three.js

A beginner’s guide to using Three.js, React and WebGL to build a 3D Application with interaction

A beginner’s guide to using Three.js, React and WebGL to build a 3D Application with interaction

What if we want to touch the world of 3D and leverage the power of WebGL with Three.js?

Probably we want to use it together with React. The common pattern of starting anything with React is to type “react + anything” into a Google to find some wrappers. But that’s not the case with Three.js. Let’s find out why.

React wrappers for Three.js have 2 issues:

  • They could easily become stale at some moment of time: for example, 2 most popular React wrappers by date (react-three and react-three-renderer) are outdated and only work with React 15
  • They don’t use plain Three.js code: you should translate it into declarative React components that might sound like a good idea in general but not for our case because you can’t just copy-paste the code from StackOverflow or Three.js examples that match your use case.

So, let’s try to avoid above-mentioned downsides and start the React integration with pure Three.js without any additional npm packages. I will try to use as little code as possible to keep things short and simple.

Stage 1: Import Three.js into the app

import React, { Component } from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
class App extends Component {
  render() {
    return (
      
    )
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(, rootElement);

We have just imported the Three.js npm package that is named “three” but have nothing to show except for a blank page. Stand by.

Step 2: Copy-paste pure Three.js code into componentDidMount method

import React, { Component } from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
class App extends Component {
  componentDidMount() {
    // === THREE.JS CODE START ===
    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
    var cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
    camera.position.z = 5;
    var animate = function () {
      requestAnimationFrame( animate );
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      renderer.render( scene, camera );
    };
    animate();
    // === THREE.JS EXAMPLE CODE END ===
  }
  render() {
    return (
      
    )
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(, rootElement);

Voila! At this stage we already have a green cube on the screen:

Build a 3D Application with React 16 and Three.js

360-degree rotation cube: Click here for a live demo

The magic here is just a copy-paste of Three.js sample code from an official “Creating a scene” tutorial script without any modifications into a componentDidMount() method of the App Component.

It looks great but if we check the page markup it becomes obvious that the canvas is actually mounted at the document body and not inside of React. That happens because of the document.body.appendChild( renderer.domElement ) line in the example code. Let’s fix that.

Step 3: Add a ref to tell where Three.js should mount the scene

import React, { Component } from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
class App extends Component {
  componentDidMount() {
    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    // document.body.appendChild( renderer.domElement );
    // use ref as a mount point of the Three.js scene instead of the document.body
    this.mount.appendChild( renderer.domElement );
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
    var cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
    camera.position.z = 5;
    var animate = function () {
      requestAnimationFrame( animate );
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      renderer.render( scene, camera );
    };
    animate();
  }
  render() {
    return (
       (this.mount = ref)} />
    )
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(, rootElement);

We simply attach the Three.js renderer to the React Element utilizing a ref. If we check the page markup, we’ll see that Three.js canvas is inside of the React App so everything is fine now.

Here is a link to the final result

Code: https://codesandbox.io/s/81qjyxonm8

Live demo: https://81qjyxonm8.codesandbox.io/

Congratulations! We have successfully completed a basic Three.js integration that works with the latest React and allows to experiment with any plain Three.js example in 3 easy steps. I hope you enjoyed the post, stay tuned!

Creating a custom shader in Three.js

3D stuff in the browser is awesome. After playing around with threejs for some time and making a&nbsp;<a href="https://dodge.imanidap.nl/" target="_blank">mini-game</a>&nbsp;at school I started to like it a lot. A classmate that is really into graphics programming told me a little bit about WebGL and shaders. It seemed really cool and I promised myself I would make my own shader. Of course some other shiny thing caught my attention and I forgot about it but, from today on I can finally say that I have created a shader and used it within threejs.

3D stuff in the browser is awesome. After playing around with threejs for some time and making a mini-game at school I started to like it a lot. A classmate that is really into graphics programming told me a little bit about WebGL and shaders. It seemed really cool and I promised myself I would make my own shader. Of course some other shiny thing caught my attention and I forgot about it but, from today on I can finally say that I have created a shader and used it within threejs.

Three JS

Before going all in on shaders it is probably a good idea to explain what three js is. Threejs is a javascript library to ease the process of creating 3D scenes on a canvas. Other popular solutions like a-frame and whitestorm jsare build on top of it. If you have ever played around with those but want even more control definitely try it out! (If you are a TypeScript lover, three js has type definitions 😉).

The most popular intro to this library is creating a cube and making it spin. There is a written tutorial in the threejs documentation and a brilliant youtube tutorial by CJ Gammon that is part of his 'diving in: three js' series.


Creating this cube is a basically preparing a film set and placing it inside of that set. You create a scene and a camera and pass these to a renderer to say: "hey this is my movie set". Then you can place mesh, which is basically an object, within the scene. This mesh consists of a geometry (the shape of the object) and a material (the color, behavior towards light and more). Based on the material you have chosen, you might want to add different kinds of lights to the scene. In order to animate the object and actually display everything you create a loop. Within this loop you tell the renderer to show the scene. Your code might look like this:

window.addEventListener('load', init)
let scene
let camera
let renderer
let sceneObjects = []

function init() {
scene = new THREE.Scene()

camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5

renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)

document.body.appendChild(renderer.domElement)
adjustLighting()
addBasicCube()
animationLoop()
}

function adjustLighting() {
let pointLight = new THREE.PointLight(0xdddddd)
pointLight.position.set(-5, -3, 3)
scene.add(pointLight)

let ambientLight = new THREE.AmbientLight(0x505050)
scene.add(ambientLight)

}

function addBasicCube() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
let material = new THREE.MeshLambertMaterial()

let mesh = new THREE.Mesh(geometry, material)
mesh.position.x = -2
scene.add(mesh)
sceneObjects.push(mesh)
}

function animationLoop() {
renderer.render(scene, camera)

for(let object of sceneObjects) {
object.rotation.x += 0.01
object.rotation.y += 0.03
}

requestAnimationFrame(animationLoop)
}

Shaders

Shaders are basically functions or small scripts that are executed by the GPU. This is where WebGL and GLSL (OpenGL Shading Language) come into play. WebGL is a browser API that allows javascript to run code on the GPU. This can increase the performance of certain scripts because your GPU is optimized for doing graphics related calculations. WebGL even allows us to write code that will be executed directly by the GPU in the GLSL language. These pieces of GLSL code are our shaders and since threejs has a WebGL renderer we can write shaders to modify our mesh. In threejs you can create custom material by using the 'shader material'. This material accepts two shaders, a vertex shader and a fragment shader. Let's try to make 'gradient material'.

Vertex Shader

A vertex shader is a function that is applied on every vertex (point) of a mesh. It is usually used to distort or animate the shape of a mesh. Within our script it looks something like this:

function vertexShader() {
return `
varying vec3 vUv;

void main() {
  vUv = position; 

  vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * modelViewPosition; 
}

} </pre><p>The first thing that you probably notice is that all our GLSL code is in a string. We do this because WebGL will pass this piece of code to our GPU and we have to pass the code to WebGL within javascript. The second thing you might notice is that we are using variables that we did not create. This is because threejs passes those variables to the GPU for us.</p><p>Within this piece of code we calculate where the points of our mesh should be placed. We do this by calculating where the points are in the scene by multiplying the position of the mesh in the scene (modelViewMatrix) and the position of the point. After that we multiply this value with the camera's relation to the scene (projectionMatrix) so the camera settings within threejs are respected by our shader. The gl_Position is the value that the GPU takes to draw our points.</p><p>Right now this vertex shader doesn't change anything about our shape. So why even bother creating this at all? We will need the positions of parts of our mesh to create a nice gradient. By creating a 'varying' variable we can pass the position to another shader.</p><h2>Fragment shader</h2><p>A fragment shader is a function that is applied on every fragment of our mesh. A fragment is a result of a process called rasterization which turns the entire mesh into a collection of triangles. For every pixel that is covered by our mesh there will be at least one fragment. The fragment shader is usually used to do color transformations on pixels. Our fragment shader looks like this:</p><pre class="ql-syntax" spellcheck="false"> return
uniform vec3 colorA;
uniform vec3 colorB;
varying vec3 vUv;

  void main() {
    gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
  }

`
}

As you can see we take the value of the position that was passed by the vertex shader. We want to apply a mix of the colors A and B based on the position of the fragment on the z axis of our mesh. But where do the colors A and B come from? These are 'uniform' variables which means they are passed into the shader from the outside. The mix function will calculate the RGB value we want to draw for this fragment. This color and an additional value for the opacity are passed to gl_FragColor. Our GPU will set the color of a fragment to this color.

Creating the material

Now that we've created the shaders we can finally build our threejs mesh with a custom material.

function addExperimentalCube() {
let uniforms = {
colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
}

let geometry = new THREE.BoxGeometry(1, 1, 1)
let material = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader(),
vertexShader: vertexShader(),
})

let mesh = new THREE.Mesh(geometry, material)
mesh.position.x = 2
scene.add(mesh)
sceneObjects.push(mesh)
}

This is where everything comes together. Our 'uniforms' colorA and colorB are created and passed along with the vertex shader and fragment shader into the shader material. The material and geometry are used to create a mesh and the mesh is added to the scene.



I build this in glitch. A friend recommended it and it is great! Some add blockers block you loading the embed though, so here is a direct link just in case.

The left cube is a cube using mesh lambert material, the right cube uses our own 'gradient material'. As you can see our material looks pretty sweet but ignores the light settings in the scene. This is because we didn't do the math in our fragment shader to take the light into account. This is hopefully something I figure out soon 😝.

Resources

It took some time to figure this out and if you liked this you should really check out the sources I have used to learn and understand this:


SceneJS vs Three.JS vs others

Pros and cons anyone? Couldn’t find a complete feature set for three.js library. Though I do know it’s popular and in active development. Maybe someone have done some R&amp;D on WebGL engines and can suggest which benefits each library gives?

Pros and cons anyone? Couldn’t find a complete feature set for three.js library. Though I do know it’s popular and in active development. Maybe someone have done some R&D on WebGL engines and can suggest which benefits each library gives?

Learning WebGL and three.js

I'm new and starting to learn about 3D computer graphics in web browsers. I'm interested in making 3D games in a browser. For anyone who has learned both WebGL and three.js...

I'm new and starting to learn about 3D computer graphics in web browsers. I'm interested in making 3D games in a browser. For anyone who has learned both WebGL and three.js...

  1. Is knowledge of WebGL required to use three.js?
  2. What are the advantages of using three.js vs. WebGL?


Getting Started With Three.js

Getting Started With Three.js

In this video we will look at the Three JS animation library using the WebGL renderer.