Getting Started With Three.js

Neo Fang

Neo Fang

· 4 min read
Three.js React-three-fiber

🎛 OpenGL/OpenGL ES: Basic Concepts

OpenGL is generally considered to be a cross-platform graphics API for high-performance 2D and 3D graphics rendering. In fact, OpenGL is not an API. It is a specification developed and maintained by the Khronos organization. The specification strictly defines how each function should be executed and what their outputs should be. How each function is implemented internally is up to the developer.

Applications and Features:

  • Primarily used for computer graphics and game development, but also used in CAD, virtual reality, scientific visualization, etc.
  • Provides underlying rendering capabilities, allowing direct interaction with the GPU
  • Supports a wide range of hardware and operating systems, including Windows, macOS, Linux, etc.

OpenGL ES is a subset of OpenGL designed specifically for mobile devices and embedded systems.OpenGL ES introduces a more simplified shader model while adding more rendering features such as 3D texturing, multi-target rendering, and more.

Applications & Features:

  • Widely used in smartphones, tablets, handheld gaming devices, automotive dashboards, etc.
  • OpenGL ES has been simplified and optimized for devices with more limited hardware resources.
  • Removed some lesser-used features and added features for mobile devices, such as more efficient energy management.

📟 WebGL, Three.js, and RTF: Hierarchies in the 3D graphics technology stack

The relationship between WebGL, Three.js, and React-three-fiber can be understood as an incremental hierarchy of technology stacks, each of which is providing different levels of functionality and simplicity for creating 3D graphics and interactive experiences.

1. WebGL(Web Graphics Library):

  • WebGL is a JavaScript API for rendering 2D and 3D graphics in any compatible browser without the use of plugins.
  • It is based on a subset of the OpenGL ES API and allows developers to draw complex graphics and animations directly on H5 <canvas> elements.
  • High-performance graphics rendering is possible with WebGL, but programming is relatively complex and low-level.

2. Three.js :

  • Three.js is a JavaScript-based 3D graphics library built on top of WebGL.
  • It provides a higher-level API that simplifies the creation and rendering of 3D graphics, making it easier for developers to create 3D scenes, lighting, materials, textures, animations, and more!
  • Three.js abstracts the complexity of WebGL, making it easier and faster to develop 3D applications and games.

3. WebGL(Web Graphics Library):

  • React-three-fiber is a library that combines Three.js with the React framework.
  • It allows developers to create and control 3D graphics declaratively in React applications. This means that 3D scenes can be handled using React's component and state management, making 3D graphics development more integrated into the modern web development process.
  • React-three-fiber leverages the React ecosystem and tools such as Hooks and Context to simplify the creation and management of 3D graphics.

In a nutshell, WebGL provides the basic ability to render graphics in the browser, Three.js builds on that with easier-to-use tools for developing 3D graphics, and React-three-fiber integrates the capabilities of Three.js into the React ecosystem to make developing 3D graphics in React apps even more efficient and streamlined.

Comparison: Drawing a 3D Rotating Cube

3D rotating cube is a good example of different ways to achieve the same goal using WebGL, Three.js, and React-three-fiber, and the following shows how to implement this example using each of these three techniques:

Image

WebGL

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error('Shader error: ' + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error('link error: ' + gl.getProgramInfoLog(program));
    gl.deleteProgram(program);
    return null;
  }
  return program;
}

function main() {
  var canvas = document.getElementById('myCanvas');
  var gl = canvas.getContext('webgl');
  if (!gl) {
    console.error('Browser not support.');
    return;
  }

  var vertexShaderSource = `
        attribute vec4 aPosition;
        uniform mat4 uModelViewMatrix;
        uniform mat4 uProjectionMatrix;
        void main() {
            gl_Position = uProjectionMatrix * uModelViewMatrix * aPosition;
        }
    `;

  var fragmentShaderSource = `
        void main() {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
    `;

  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

  var program = createProgram(gl, vertexShader, fragmentShader);

  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  var positions = [
    -1.0, -1.0,  1.0,
    1.0, -1.0,  1.0,
    1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0, -1.0, -1.0,
    -1.0,  1.0, -1.0,
    1.0,  1.0, -1.0,
    1.0, -1.0, -1.0,
    -1.0,  1.0, -1.0,
    -1.0,  1.0,  1.0,
    1.0,  1.0,  1.0,
    1.0,  1.0, -1.0,
    -1.0, -1.0, -1.0,
    1.0, -1.0, -1.0,
    1.0, -1.0,  1.0,
    -1.0, -1.0,  1.0,
    1.0, -1.0, -1.0,
    1.0,  1.0, -1.0,
    1.0,  1.0,  1.0,
    1.0, -1.0,  1.0,
    -1.0, -1.0, -1.0,
    -1.0, -1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0,  1.0, -1.0,
  ];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  var positionAttributeLocation = gl.getAttribLocation(program, 'aPosition');
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

  var uModelViewMatrix = gl.getUniformLocation(program, 'uModelViewMatrix');
  var uProjectionMatrix = gl.getUniformLocation(program, 'uProjectionMatrix');

  var modelViewMatrix = mat4.create();
  var projectionMatrix = mat4.create();

  mat4.perspective(projectionMatrix, 45 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 100.0);
  mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);

  var then = 0;

  function render(now) {
    now *= 0.001;  
    var deltaTime = now - then;
    then = now;

    mat4.rotate(modelViewMatrix, modelViewMatrix, deltaTime, [0, 1, 1]);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.useProgram(program);

    gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);
    gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);

    gl.drawArrays(gl.TRIANGLES, 0, 6 * 6);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();

Three.js

import * as THREE from 'three';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}

animate();

React-three-fiber (Demo)

import React, { useRef } from "react";
import "./styles.css";
import { Canvas, useFrame } from "@react-three/fiber";

import { CameraControls } from "./CameraControls";

function Arc1() {
  const ref = useRef();
  useFrame(() => {
    ref.current.rotation.x += 0.01;
    ref.current.rotation.y += 0.01;
  });
  return (
    <mesh ref={ref}>
      <boxBufferGeometry attach="geometry" args={[10, 10, 10]} />
      <meshBasicMaterial attach="material" args={[{ color: 0x663333 }]} />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas
      style={{
        height: window.innerHeight + "px",
        width: window.innerWidth + "px",
      }}
      gl={{ alpha: false, antialias: false, logarithmicDepthBuffer: true }}
      camera={{ fov: 75, position: [0, 0, 40] }}
      onCreated={({ gl }) => {
        gl.setClearColor("white");
      }}
    >
      <CameraControls />
      <Arc1 />
    </Canvas>
  );
}

This article introduces the basic concepts related to Three.js, as well as some comparative examples, I hope readers can use this article to achieve the purpose of a quick start.

Neo Fang

About Neo Fang

Neo is a Front-end technology expert at Alibaba Cloud, as well as being a co-founder of Tarspace.

Copyright © 2024 Terpampas. All rights reserved.
Powered by Vercel