From 0379c81c2c9631ae3fbe5da3799462369122af3d Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Thu, 13 Jul 2023 03:10:49 -0500 Subject: [PATCH] feat: Geometry.drawRange --- README.md | 152 ++------------------------------- examples/webgl-cube.ts | 2 +- examples/webgl-fullscreen.ts | 22 ++--- examples/webgl-mrt.ts | 4 +- examples/webgl-transparency.ts | 2 +- examples/webgpu-fullscreen.ts | 24 ++---- src/Geometry.ts | 13 +++ src/WebGLRenderer.ts | 12 ++- src/WebGPURenderer.ts | 7 +- 9 files changed, 56 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index cb99998..a858ce9 100644 --- a/README.md +++ b/README.md @@ -155,151 +155,6 @@ renderer.render(mesh, camera) -
- -In four, a camera is optional and not needed for fullscreen effects. The following renders a red fullscreen triangle: - -
- -Show WebGL example - -```ts -import { WebGLRenderer, Geometry, Material, Mesh } from 'four' - -const renderer = new WebGLRenderer() -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.canvas) - -const geometry = new Geometry({ - position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }, -}) -const material = new Material({ - vertex: /* glsl */ `#version 300 es - in vec3 position; - void main() { - gl_Position = 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) -``` - -
- -
- -Show WebGPU example - -```ts -import { WebGPURenderer, Geometry, Material, Mesh } from 'four' - -const renderer = new WebGPURenderer() -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.canvas) - -const geometry = new Geometry({ - position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }, -}) -const material = new Material({ - vertex: /* wgsl */ ` - @vertex - fn main(@location(0) position: vec3) -> @builtin(position) vec4 { - return vec4(position, 1); - } - `, - fragment: /* wgsl */ ` - @fragment - fn main() -> @location(0) vec4 { - return vec4(1, 0, 0, 1); - } - `, -}) -const mesh = new Mesh(geometry, material) - -renderer.render(mesh) -``` - -
- -
- -Vertex data or geometry are also optional for drawing. The following renders 3 vertices and computes everything in shaders: - -
- -Show WebGL example - -```ts -import { WebGLRenderer, Material, Mesh } from 'four' - -const renderer = new WebGLRenderer() -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.canvas) - -const material = new Material({ - vertex: /* glsl */ `#version 300 es - void main() { - const vec2 position[3] = vec2[](vec2(-1), vec2(3, -1), vec2(-1, 3)); - gl_Position = vec4(position[gl_VertexID], 0, 1); - } - `, - fragment: /* glsl */ `#version 300 es - out lowp vec4 color; - void main() { - color = vec4(1, 0, 0, 1); - } - `, -}) -const mesh = new Mesh() -mesh.material = material - -renderer.render(mesh) -``` - -
- -
- -Show WebGPU example - -```ts -import { WebGPURenderer, Material, Mesh } from 'four' - -const renderer = new WebGPURenderer() -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.canvas) - -const material = new Material({ - vertex: /* wgsl */ ` - @vertex - fn main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { - const position = array, 3>(vec2(-1), vec2(3, -1), vec2(-1, 3)); - return vec4(position[i], 0, 1); - } - `, - fragment: /* wgsl */ ` - @fragment - fn main() -> @location(0) vec4 { - return vec4(1, 0, 0, 1); - } - `, -}) -const mesh = new Mesh() -mesh.material = material - -renderer.render(mesh) -``` - -
- ## 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. @@ -367,6 +222,13 @@ const geometry = new Geometry({ }) ``` +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. + +```ts +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](#instancing)). diff --git a/examples/webgl-cube.ts b/examples/webgl-cube.ts index 680e14e..bf379e4 100644 --- a/examples/webgl-cube.ts +++ b/examples/webgl-cube.ts @@ -64,7 +64,7 @@ const material = new Material({ } `, fragment: /* glsl */ `#version 300 es - precision highp float; + precision lowp float; uniform vec3 color; in vec3 vNormal; diff --git a/examples/webgl-fullscreen.ts b/examples/webgl-fullscreen.ts index f945037..cd977fa 100644 --- a/examples/webgl-fullscreen.ts +++ b/examples/webgl-fullscreen.ts @@ -1,32 +1,26 @@ -import { WebGLRenderer, Geometry, Material, Mesh, Texture } from 'four' +import { WebGLRenderer, Material, Mesh, Texture } from 'four' const renderer = new WebGLRenderer() renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.canvas) -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]) }, -}) - const material = new Material({ uniforms: { time: 0, color: new Texture(await createImageBitmap(new ImageData(new Uint8ClampedArray([76, 51, 128, 255]), 1, 1))), }, vertex: /* glsl */ `#version 300 es - in vec2 uv; - in vec3 position; - out vec2 vUv; + + const vec2 triangle[3] = vec2[](vec2(-1), vec2(3, -1), vec2(-1, 3)); void main() { - vUv = uv; - gl_Position = vec4(position, 1); + gl_Position = vec4(triangle[gl_VertexID], 0, 1); + vUv = abs(gl_Position.xy) - 1.0; } `, fragment: /* glsl */ `#version 300 es - precision highp float; + precision lowp float; uniform float time; uniform sampler2D color; @@ -40,7 +34,9 @@ const material = new Material({ `, }) -const mesh = new Mesh(geometry, material) +const mesh = new Mesh() +mesh.material = material +mesh.geometry.drawRange.count = 3 window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight) diff --git a/examples/webgl-mrt.ts b/examples/webgl-mrt.ts index 573a8a9..67acd3e 100644 --- a/examples/webgl-mrt.ts +++ b/examples/webgl-mrt.ts @@ -20,7 +20,7 @@ const compute = new Mesh( } `, fragment: /* glsl */ `#version 300 es - precision highp float; + precision lowp float; layout(location = 0) out vec4 color0; layout(location = 1) out vec4 color1; @@ -64,7 +64,7 @@ const composite = new Mesh( } `, fragment: /* glsl */ `#version 300 es - precision highp float; + precision lowp float; uniform sampler2D texture0; uniform sampler2D texture1; diff --git a/examples/webgl-transparency.ts b/examples/webgl-transparency.ts index b2f2238..6ba1b5a 100644 --- a/examples/webgl-transparency.ts +++ b/examples/webgl-transparency.ts @@ -55,7 +55,7 @@ const material = new Material({ } `, fragment: /* glsl */ `#version 300 es - precision highp float; + precision lowp float; in vec3 vColor; out vec4 pc_fragColor; diff --git a/examples/webgpu-fullscreen.ts b/examples/webgpu-fullscreen.ts index a29e7a0..3f6e242 100644 --- a/examples/webgpu-fullscreen.ts +++ b/examples/webgpu-fullscreen.ts @@ -4,11 +4,6 @@ const renderer = new WebGPURenderer() renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.canvas) -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]) }, -}) - const material = new Material({ uniforms: { time: 0, @@ -20,23 +15,20 @@ const material = new Material({ }; @group(0) @binding(0) var uniforms: Uniforms; - struct VertexIn { - @location(0) position: vec3, - @location(1) uv: vec2, - }; - struct VertexOut { @builtin(position) position: vec4, @location(0) color: vec4, @location(1) uv: vec2, }; + const triangle = array, 3>(vec2(-1), vec2(3, -1), vec2(-1, 3)); + @vertex - fn main(input: VertexIn) -> VertexOut { + fn main(@builtin(vertex_index) i: u32) -> VertexOut { var out: VertexOut; - out.position = vec4(input.position, 1.0); - out.color = vec4(0.5 + 0.3 * cos(vec3(input.uv, 0.0) + uniforms.time), 0.0); - out.uv = input.uv; + out.position = vec4(triangle[i], 0, 1); + out.uv = abs(out.position.xy) - 1.0; + out.color = vec4(0.5 + 0.3 * cos(vec3(out.uv, 0.0) + uniforms.time), 0.0); return out; } `, @@ -62,7 +54,9 @@ const material = new Material({ `, }) -const mesh = new Mesh(geometry, material) +const mesh = new Mesh() +mesh.material = material +mesh.geometry.drawRange.count = 3 window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight) diff --git a/src/Geometry.ts b/src/Geometry.ts index eea6d2b..8d63e90 100644 --- a/src/Geometry.ts +++ b/src/Geometry.ts @@ -33,10 +33,23 @@ export interface Attribute { needsUpdate?: boolean } +/** + * Specifies the visible range of vertices or indices to draw when rendering. + */ +export interface DrawRange { + start: number + count: number +} + /** * Constructs a geometry object. Used to store program attributes. */ export class Geometry { + /** + * Configures the geometry's {@link DrawRange}. + */ + drawRange: DrawRange = { start: 0, count: Infinity } + constructor( /** * Geometry program attributes. diff --git a/src/WebGLRenderer.ts b/src/WebGLRenderer.ts index fb06da0..28c182b 100644 --- a/src/WebGLRenderer.ts +++ b/src/WebGLRenderer.ts @@ -605,9 +605,17 @@ export class WebGLRenderer { const mode = this.gl[node.mode.toUpperCase() as Uppercase] const { index, position } = node.geometry.attributes + const { start, count } = node.geometry.drawRange if (index) - this.gl.drawElementsInstanced(mode, index.data.length / index.size, getDataType(index.data)!, 0, node.instances) - else if (position) this.gl.drawArraysInstanced(mode, 0, position.data.length / position.size, node.instances) + this.gl.drawElementsInstanced( + mode, + min(count, index.data.length / index.size), + getDataType(index.data)!, + start, + node.instances, + ) + else if (position) + this.gl.drawArraysInstanced(mode, start, min(count, position.data.length / position.size), node.instances) else this.gl.drawArraysInstanced(mode, 0, 3, node.instances) } } diff --git a/src/WebGPURenderer.ts b/src/WebGPURenderer.ts index 23102ae..79a3594 100644 --- a/src/WebGPURenderer.ts +++ b/src/WebGPURenderer.ts @@ -4,7 +4,7 @@ import type { Camera } from './Camera' import { Mesh, type Mode } from './Mesh' import type { Object3D } from './Object3D' import { type RenderTarget } from './RenderTarget' -import { Compiled } from './_utils' +import { Compiled, min } from './_utils' import type { Attribute, AttributeData, Geometry } from './Geometry' import type { Material, Side, Uniform } from './Material' import { Texture } from './Texture' @@ -623,8 +623,9 @@ export class WebGPURenderer { // Alternate drawing for indexed and non-indexed children const { index, position } = node.geometry.attributes - if (index) this._passEncoder.drawIndexed(index.data.length / index.size, node.instances) - else if (position) this._passEncoder.draw(position.data.length / position.size, node.instances) + const { start, count } = node.geometry.drawRange + if (index) this._passEncoder.drawIndexed(min(count, index.data.length / index.size), node.instances, start) + else if (position) this._passEncoder.draw(min(count, position.data.length / position.size), node.instances, start) else this._passEncoder.draw(3, node.instances) }