Git Product home page Git Product logo

four's Introduction

Size Version Downloads

four

Minimal three.js alternative.

Table of Contents

Installation

To install, use your preferred package manager or CDN:

npm install four@npm:fourwastaken
yarn add four@npm:fourwastaken
pnpm add four@npm:fourwastaken
<script type="module">
  import * as FOUR from 'https://unpkg.com/fourwastaken'
</script>

Note: Vite may have issues consuming WebGPU code which relies on top-level await via ESM. This is well supported since 2021, but you may need to use vite-plugin-top-level-await to use this library with vite.optimizeDeps.

Getting Started

The following creates a renderer, camera, and renders a red cube:

Show WebGL example
import { WebGLRenderer, PerspectiveCamera, Geometry, Material, Mesh } from 'four'

const renderer = new WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.z = 5

const geometry = new Geometry({
  position: {
    size: 3,
    data: new Float32Array([
      0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
      0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
      0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
      -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
      -0.5, -0.5, -0.5, 0.5, -0.5,
    ]),
  },
})
const material = new Material({
  vertex: /* glsl */ `#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */ `#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `,
})
const mesh = new Mesh(geometry, material)

renderer.render(mesh, camera)
Show WebGPU example
import { WebGPURenderer, PerspectiveCamera, Geometry, Material, Mesh } from 'four'

const renderer = new WebGPURenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.z = 5

const geometry = new Geometry({
  position: {
    size: 3,
    data: new Float32Array([
      0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
      0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
      0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
      -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
      -0.5, -0.5, -0.5, 0.5, -0.5,
    ]),
  },
})
const material = new Material({
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
})
const mesh = new Mesh(geometry, material)

renderer.render(mesh, camera)

Object3D

An Object3D represents a basic 3D object and its transforms. Objects are linked via their parent and children properties, constructing a rooted scene-graph.

const object = new Object3D()
object.add(new Object3D(), new Object3D())
object.traverse((node) => {
  if (node !== object) object.remove(node)
  if (!node.visible) return true
})

Vector3

A Vector3 represents a three-dimensional (x, y, z) vector and describes local position in Object3D.position. It is also used to control local scale in Object3D.scale.

object.position.set(1, 2, 3)
object.position.x = 4
object.position[0] = 5

Quaternion

A Quaternion represents a four-dimensional vector with a rotation axis (x, y, z) and magnitude (w) and describes local orientation in Object3D.quaternion.

object.quaternion.set(0, 0, 0, 1)
object.quaternion.fromEuler(Math.PI / 2, 0, 0)
object.quaternion.x *= -1
object.quaternion[0] *= -1

Matrix4

A Matrix4 represents a 4x4 transformation matrix and describes world transforms in Object3D.matrix.

object.matrix.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1)
object.matrix[12] = 4
object.matrix.invert()
object.matrix.identity()

Mesh

A Mesh contains a Geometry and Material to describe visual behavior, and can be manipulated in 3D as an Object3D.

const geometry = new Geometry({ ... })
const material = new Material({ ... })
const mesh = new Mesh(geometry, material)

Geometry

A Geometry contains an Attribute list of vertex or storage buffer data, with a GPU buffer allocated for each Attribute.

const geometry = new Geometry({
  position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) },
  uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) },
  index: { size: 1, data: new Uint16Array([0, 1, 2]) },
})

A DrawRange can also be configured to control rendering without submitting vertex data. This is useful for GPU-computed geometry or vertex pulling, as demonstrated in the fullscreen demos.

const geometry = new Geometry()
geometry.drawRange = { start: 0, count: 3 } // renders 3 vertices at starting index 0

Attribute

An Attribute defines a data view, its per-vertex size, and an optional per-instance divisor (see instancing).

// Creates a 4x4 instance matrix for 2 instances
{
  data: new Float32Array([
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
  ]),
  size: 16,
  divisor: 1,
}

Material

A Material describes a program or shader interface for rasterization and compute (see compute), defining a vertex and fragment or compute shader, respectively.

Show WebGL example
const material = new Material({
  vertex: /* glsl */ `#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */ `#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `,
  side: 'front',
  transparent: false,
  depthTest: true,
  depthWrite: true,
})
Show WebGPU example
const material = new Material({
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
  side: 'front',
  transparent: false,
  depthTest: true,
  depthWrite: true,
})

Uniforms

The following uniforms are built-in and will be automatically populated when specified:

Type Name Description Conversion
mat4x4 modelMatrix world-space mesh transform local space => world space
mat4x4 projectionMatrix clip-space camera projection view space => clip space
mat4x4 viewMatrix inverse camera transform world space => view space
mat4x4 modelViewMatrix premultiplied model-view transform local space => view space
mat4x4 normalMatrix isotropic inverse model-view or "normal" transform local space => view space

In WebGPU, uniforms are bound to a single uniform buffer, preceded by storage buffers, and followed by sampler-texture for texture uniforms.

// Storage buffers
@group(0) @binding(0)
var<storage, read_write> data: array<vec2<f32>>;

// Uniform buffer
struct Uniforms {
  time: f32,
};
@group(0) @binding(1) var<uniform> uniforms: Uniforms;

// Texture bindings
@group(0) @binding(2) var sample: sampler;
@group(0) @binding(3) var color: texture_2d<f32>;

@group(0) @binding(4) var sample_2: sampler;
@group(0) @binding(5) var color_2: texture_2d<f32>;

Blending

By default, opaque meshes do not blend but replace values, and transparent meshes alpha blend by the following blend equation:

material.blending = {
  color: {
    operation: 'add',
    srcFactor: 'src-alpha',
    dstFactor: 'one-minus-src-alpha',
  },
  alpha: {
    operation: 'add',
    srcFactor: 'one',
    dstFactor: 'one-minus-src-alpha',
  },
}

This gets applied to the final fragment color as src * srcFactor + dst * dstFactor, assuming a premultiplied alpha.

Custom blending can be used for postprocessing and various VFX. The following are the most common configurations:

Blend Mode BlendOperation BlendFactor (src) BlendFactor (dst)
Additive add src-alpha one
Subtractive reverse-subtract src-alpha one
Multiply add dst-color zero
Screen add one-minus-src-color one
Maximize max src-alpha dst-alpha
Custom add one one
Local Additive add dst-alpha one
Disabled add one zero

Texture

A Texture transports or stores image or video data to the GPU as well as data like normals or depth.

const pixel = new Uint8ClampedArray([76, 51, 128, 255])
const image = new ImageData(pixel, 1, 1)
const texture = new Texture(image)

Sampler

A Sampler configures texel filtering and transforms for a texture, and can be used to sample a texture multiple times with different configurations in a shader.

const sampler = new Sampler({
  magFilter: 'nearest',
  minFilter: 'nearest',
  wrapS: 'clamp',
  wrapT: 'clamp',
  anisotropy: 1,
})
texture.sampler = sampler

RenderTarget

A RenderTarget constructs a frame buffer object which can be drawn to, similar to the canvas itself. Unlike the canvas, render targets can have multiple attachments or texture channels, configurable as the third argument count, enabling efficient use of techniques like deferred rendering and postprocessing.

// Create render target with 4 channels
const width = window.innerWidth
const height = window.innerHeight
const count = 4
const renderTarget = new RenderTarget(width, height, count)

// Resize with page
window.addEventListener('resize', () => {
  const width = window.innerWidth
  const height = window.innerHeight
  renderTarget.setSize(width, height)
})

// Bind and render to render target
renderer.setRenderTarget(renderTarget)
renderer.render(scene, camera)

// Unbind to canvas
renderer.setRenderTarget(null)

Camera

A Camera contains matrices and a frustum for projection transforms and queries. The type of projection is defined by Camera.projectionMatrix.

Frustum

A Frustum contains clipping planes used for frustum culling and queries, set from a projectionViewMatrix.

camera.frustum.fromMatrix4(camera.projectionViewMatrix)
if (camera.frustum.contains(mesh)) {
  // ...
}

PerspectiveCamera

A PerspectiveCamera calculates a perspective or non-linear projectionMatrix, where objects appear smaller by distance.

const fov = 75
const aspect = canvas.width / canvas.height
const near = 0.1
const far = 1000
const camera = new PerspectiveCamera(fov, aspect, near, far)

OrthographicCamera

An OrthographicCamera calculates an orthographic or linear projectionMatrix, where objects are unaffected by distance.

const near = 0.1
const far = 1000
const left = -(canvas.width / 2)
const right = canvas.width / 2
const bottom = -(canvas.height / 2)
const top = canvas.height / 2
const camera = new OrthographicCamera(near, far, left, right, bottom, top)

Rendering

Four supports WebGL 2 and WebGPU with WebGLRenderer and WebGPURenderer, respectively, and implements a shared API for rendering and compute.

const renderer = new WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

//

const renderer = new WebGPURenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

Instancing

Four instances by default, which better aligns with WebGPU. Instanced rendering rasterizes multiple vertex primitives with the same shader interface to render multiple meshes at the cost of one.

You can specify the number of instances to render with Mesh.instances and add variance or control each instance with Attribute.divisor to specify a per-instance divisor. A divisor of one will be used by a single instance, and a divisor greater than one will be used by multiple instances.

Note: Attributes can only allocate primitive types in WebGPU (gpuweb/gpuweb#1652), so you must allocate and index storage or uniform buffers via the instance_index built-in for complex types like matrices.

Show WebGL example
const geometry = new Geometry({
  position: { size: 3, data: new Float32Array([...]) },
  instanceMatrix: { divisor: 1, size: 16, data: new Float32Array([...]) }
})

const material = new Material({
  vertex: /* glsl */`#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in mat4 instanceMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */`#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `
})

const mesh = new Mesh(geometry, material)
mesh.instances = 2
Show WebGPU example
const geometry = new Geometry({
  position: { size: 3, data: new Float32Array([...]) },
})

const material = new Material({
  uniforms: {
    instanceMatrix: new Float32Array([...]),
  },
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
      instanceMatrix: array<mat4x4<f32>, 2>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(
      @builtin(instance_index) instanceID: u32,
      @location(0) position: vec3<f32>,
    ) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * uniforms.instanceMatrix[instanceID] * vec4(position, 1.0);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
})

const mesh = new Mesh(geometry, material)
mesh.instances = 2

Compute

Four supports compute for both WebGL and WebGPU via transform feedback and compute pipelines, respectively. This can be used in lieu of pixel shaders to write directly to buffer storage without any CPU reads/writes to textures. Useful for high precision compute or large simulations where texture memory is limited.

The following populates geometry buffers on the GPU, computing a fullscreen triangle geometry:

Show WebGL example
const geometry = new Geometry({
  position: { size: 2, data: new Float32Array(6) },
  uv: { size: 2, data: new Float32Array(6) },
})
const computeMaterial = new Material({
  compute: /* glsl */ `#version 300 es
    out vec2 uv;
    out vec2 position;

    void main() {
      uv = vec2(gl_VertexID << 1 & 2, gl_VertexID & 2);
      position = uv * 2.0 - 1.0;
    }
  `,
})
const mesh = new Mesh(geometry, computeMaterial)

renderer.compute(mesh)
Show WebGPU example
const geometry = new Geometry({
  position: { size: 2, data: new Float32Array(6) },
  uv: { size: 2, data: new Float32Array(6) },
})
const computeMaterial = new Material({
  compute: /* wgsl */ `
    @group(0) @binding(0)
    var<storage, read_write> position: array<vec2<f32>>;

    @group(0) @binding(1)
    var<storage, read_write> uv: array<vec2<f32>>;

    @compute @workgroup_size(64)
    fn main(@builtin(local_invocation_index) i: u32) {
      uv[i] = vec2<f32>(vec2((i << 1) & 2, i & 2));
      position[i] = uv[i] * 2 - 1;
    }
  `,
})
const mesh = new Mesh(geometry, computeMaterial)

renderer.compute(mesh)

four's People

Contributors

codyjasonbennett avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

four's Issues

[feature] custom sorting

can we have this changed to something like return renderList.sort(this.sortAlgo || ....) so people could override sorting via renderer.sortAlgo = mySuperSmartCustomSort or Idk

question about `perspective projection matrix`

The perspective projection matrix in this project is

[ f / aspect,   0,  0,   0, 
 0,  f,  0,  0, 
 0,  0, (far + near) * depth, -1, 
 0,  0, 2 * far * near * depth, 0
]

But, When I google, many answers are

[ f / aspect,   0,  0,   0, 
 0,  f,  0,  0, 
 0,  0, (far + near) * depth, 2 * far * near * depth, 
 0,  0, -1, 0
]

so, why is that?

Error with top-level await

Hi,
thank you for the lib, it looks promising !

I tried to use it for a basic WebGL test but ran into the following issue :

image

Indeed, I see that there are two top-level constants _adapter and _device defined in WebGPURenderer.ts that await a promise. The error pops up even when using WebGL, I guess because the exported file index.js bundles everythings.

I tried a quick fix, but since the request of the device is async, I don't understand how the creation of the WebGPURenderer can not be async. I could work on a PR if you have an idea of how you want to address that

Mipmap sampling for textures

Hello,

I am getting some artifacts when using Texture uniforms, which I think are due to the lack of mipmaps for the textures. I see that the Filter option only accepts nearest and linear, but no mipmaps.

Do you have any plan on implementing mipmaps ? Or do you have a workaround ? I could load the textures on the GPU using directly the native WebGL API, but then since I don't have access to the program, I don't know how to bind them to the uniforms.

Thank you !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.