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)
}