feat: remove float render targets altogether

This commit is contained in:
Himadri Bhattacharjee
2025-01-20 21:30:26 +05:30
parent a9728f83c4
commit 5ccb33e33c

View File

@@ -99,24 +99,19 @@ void main() {
}
}
</script>
<script type="module">
import * as THREE from 'three';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
var rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
var renderer = new THREE.WebGLRenderer({antialias: true});
var width = document.querySelector('#stage').offsetWidth;
var height = Math.round(9/16 * width);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(width, height);
document.querySelector('#stage').appendChild(renderer.domElement);
const gl = renderer.getContext();
gl.getExtension("EXT_color_buffer_float");
gl.getExtension("EXT_float_blend");
function get(path) {
return document.querySelector(path).textContent;
return document.querySelector('#' + path).textContent;
};
const scene = new THREE.Scene();
@@ -132,8 +127,8 @@ void main() {
solidScene.add(light);
camera.position.set(6,8,14);
var buffer = new THREE.WebGLRenderTarget(width, height, {format: THREE.RGBAFormat, type: THREE.FloatType})
var outlineBuffer = new THREE.WebGLRenderTarget(width, height, {format: THREE.RGBAFormat, type: THREE.FloatType})
var buffer = new THREE.WebGLRenderTarget(width, height, {format: THREE.RGBAFormat})
var outlineBuffer = new THREE.WebGLRenderTarget(width, height, {format: THREE.RGBAFormat})
const orbit = new OrbitControls(camera, renderer.domElement);
orbit.update();
@@ -143,30 +138,30 @@ void main() {
const shadowMesh = new THREE.Mesh(geometry);
const uniforms = {
gbufferMask: { value: buffer.texture },
viewportSize: { value: new THREE.Vector2(width, height) },
gbufferMask: { value: buffer.texture },
viewportSize: { value: new THREE.Vector2(width, height) },
}
const material = new THREE.ShaderMaterial({
vertexShader: get('#outline-vert'),
fragmentShader: get('#outline-frag'),
uniforms,
transparent: true,
vertexShader: get('outline-vert'),
fragmentShader: get('outline-frag'),
uniforms,
transparent: true,
});
const solidMesh = new THREE.Mesh(
geometry,
new THREE.MeshPhysicalMaterial({
color: 0xaaeadb,
metalness: 0.8,
clearcoat: 0.4,
clearcoatRoughness: 0.1,
}),
geometry,
new THREE.MeshPhysicalMaterial({
color: 0xaaeadb,
metalness: 0.8,
clearcoat: 0.4,
clearcoatRoughness: 0.1,
}),
);
const mesh = new THREE.Mesh(
geometry,
material
geometry,
material
);
solidScene.add(solidMesh);
scene.add(mesh);
@@ -174,95 +169,95 @@ void main() {
const evoUniforms = {
initBufferMask: { value: null },
initBufferMask: { value: null },
}
const evoMaterial = new THREE.ShaderMaterial({
// this is a copy shader
vertexShader: get('#evolve-vert'),
fragmentShader: get('#evolve-frag'),
uniforms: evoUniforms,
transparent: true,
// this is a copy shader
vertexShader: get('evolve-vert'),
fragmentShader: get('evolve-frag'),
uniforms: evoUniforms,
transparent: true,
});
const evolveMesh = new THREE.Mesh(
geometry,
evoMaterial
geometry,
evoMaterial
);
evolveScene.add(evolveMesh)
function continuity(bitmap, width, height) {
const visited = Array.from({length: height}, () => Array(width).fill(false));
const visited = Array.from({length: height}, () => Array(width).fill(false));
function valid(row, col) {
return row >= 0 && row < height && col >= 0 && col <= width
}
function valid(row, col) {
return row >= 0 && row < height && col >= 0 && col <= width
}
// for a point to be on the screen edge, it must have at least three
// of its neighbors invalid
function isSentinel(row, col) {
return (
Number(valid(row - 1, col - 1)) +
Number(valid(row - 1, col)) +
Number(valid(row - 1, col + 1)) +
// for a point to be on the screen edge, it must have at least three
// of its neighbors invalid
function isSentinel(row, col) {
return (
Number(valid(row - 1, col - 1)) +
Number(valid(row - 1, col)) +
Number(valid(row - 1, col + 1)) +
Number(valid(row, col - 1)) +
Number(valid(row, col + 1)) +
Number(valid(row, col - 1)) +
Number(valid(row, col + 1)) +
Number(valid(row + 1, col - 1)) +
Number(valid(row + 1, col)) +
Number(valid(row + 1, col + 1))
) < 6
}
Number(valid(row + 1, col - 1)) +
Number(valid(row + 1, col)) +
Number(valid(row + 1, col + 1))
) < 6
}
var sentinels = [];
var cyclic = [];
var sentinels = [];
var cyclic = [];
function dfs(row, col, rootRow, rootCol, steps) {
function dfs(row, col, rootRow, rootCol, steps) {
const pointIsRoot = row == rootRow && col == rootCol;
const pointIsRoot = row == rootRow && col == rootCol;
if (steps != 0 && pointIsRoot) {
cyclic.push([row, col]);
return;
}
if (steps != 0 && pointIsRoot) {
cyclic.push([row, col]);
return;
}
if (!valid(row, col) || visited[row][col]) {
return;
}
if (!valid(row, col) || visited[row][col]) {
return;
}
// is this point switched off?
var point = 4 * (row * width + col);
if (bitmap[point+0] == 0 && bitmap[point+1] == 0 && bitmap[point+2] == 0) {
return;
}
// is this point switched off?
var point = 4 * (row * width + col);
if (bitmap[point+0] == 0 && bitmap[point+1] == 0 && bitmap[point+2] == 0) {
return;
}
visited[row][col] = true;
dfs(row - 1, col, rootRow, rootCol, steps + 1)
dfs(row + 1, col, rootRow, rootCol, steps + 1)
dfs(row, col - 1, rootRow, rootCol, steps + 1)
dfs(row, col + 1, rootRow, rootCol, steps + 1)
visited[row][col] = true;
dfs(row - 1, col, rootRow, rootCol, steps + 1)
dfs(row + 1, col, rootRow, rootCol, steps + 1)
dfs(row, col - 1, rootRow, rootCol, steps + 1)
dfs(row, col + 1, rootRow, rootCol, steps + 1)
if (isSentinel(row, col) && steps != 0) {
sentinels.push([row, col]);
}
return;
}
if (isSentinel(row, col) && steps != 0) {
sentinels.push([row, col]);
}
return;
}
for (let row = 0; row < height; row++) {
for(let col=0; col < width; col++) {
if (visited[row][col]) {
continue;
}
var point = 4 * (row * width + col);
if (bitmap[point] == 1 && bitmap[point+1] == 1 && bitmap[point+2] == 1) {
dfs(row, col, row, col, 0);
}
}
}
for (let row = 0; row < height; row++) {
for(let col=0; col < width; col++) {
if (visited[row][col]) {
continue;
}
var point = 4 * (row * width + col);
if (bitmap[point] == 1 && bitmap[point+1] == 1 && bitmap[point+2] == 1) {
dfs(row, col, row, col, 0);
}
}
}
return cyclic.concat(sentinels);
return cyclic.concat(sentinels);
}
var durationInSeconds = 5;
@@ -275,103 +270,116 @@ void main() {
//
// This way we can quickly zero out all the pixels below a
// threshold when timing the animation.
function dijkstraNumber(points, allThePixels) {
points.forEach((point) => {
dijkstraPropagate(point, allThePixels, 2)
})
function dijkstraNumber(points, buf) {
points.forEach((point) => {
dijkstraPropagate(point, buf, 2)
})
}
function dijkstraPropagate(point, allThePixels, value) {
let row = point[0]
let col = point[1]
let pos = 4 * (row * buffer.width + col);
if (allThePixels[pos] != 1) {
return
}
// propagate
allThePixels[pos] = value;
allThePixels[pos+1] = value;
allThePixels[pos+2] = value;
allThePixels[pos+3] = value;
function dijkstraPropagate(point, buf, value) {
let row = point[0]
let col = point[1]
let pos = 4 * (row * buffer.width + col);
if (buf[pos] != 1) {
return
}
// propagate
buf[pos] = value;
buf[pos+1] = value;
buf[pos+2] = value;
buf[pos+3] = value;
dijkstraPropagate([row - 1, col], allThePixels, value+1);
dijkstraPropagate([row + 1, col], allThePixels, value+1);
dijkstraPropagate([row, col - 1], allThePixels, value+1);
dijkstraPropagate([row, col + 1], allThePixels, value+1);
dijkstraPropagate([row - 1, col - 1], allThePixels, value+1);
dijkstraPropagate([row + 1, col + 1], allThePixels, value+1);
dijkstraPropagate([row + 1, col - 1], allThePixels, value+1);
dijkstraPropagate([row - 1, col + 1], allThePixels, value+1);
dijkstraPropagate([row - 1, col], buf, value+1);
dijkstraPropagate([row + 1, col], buf, value+1);
dijkstraPropagate([row, col - 1], buf, value+1);
dijkstraPropagate([row, col + 1], buf, value+1);
dijkstraPropagate([row - 1, col - 1], buf, value+1);
dijkstraPropagate([row + 1, col + 1], buf, value+1);
dijkstraPropagate([row + 1, col - 1], buf, value+1);
dijkstraPropagate([row - 1, col + 1], buf, value+1);
}
const clock = new THREE.Clock();
var longestPixelStrand = 0;
var init = true;
const allThePixels = new Float32Array( buffer.width * buffer.height * 4);
const allThePixels = new Uint8Array(buffer.width * buffer.height * 4);
const dijkstraBuffer = new Float32Array(buffer.width * buffer.height * 4);
function animate() {
if (init) {
renderer.setRenderTarget(buffer);
renderer.render(maskScene, camera);
if (init) {
renderer.setRenderTarget(buffer);
renderer.render(maskScene, camera);
renderer.setRenderTarget(outlineBuffer);
renderer.render(scene, camera);
renderer.setRenderTarget(outlineBuffer);
renderer.render(scene, camera);
renderer.readRenderTargetPixels(outlineBuffer, 0, 0, buffer.width, buffer.height, allThePixels);
renderer.readRenderTargetPixels(outlineBuffer, 0, 0, buffer.width, buffer.height, allThePixels);
for (let i = 0; i < allThePixels.length; i++) {
dijkstraBuffer[i] = allThePixels[i] == 255 ? 1 : 0;
}
let points = continuity(allThePixels, width, height)
let dijkstraBuffer = dijkstraNumber(points, allThePixels)
let points = continuity(dijkstraBuffer, width, height)
dijkstraNumber(points, dijkstraBuffer)
for (let row = 0; row < height; row++) {
for(let col = 0; col<width; col++) {
var point = 4 * (row * width + col);
longestPixelStrand = Math.max(longestPixelStrand, dijkstraBuffer[point])
}
}
for (let row = 0; row < height; row++) {
for(let col = 0; col<width; col++) {
var point = 4 * (row * width + col);
longestPixelStrand = Math.max(longestPixelStrand, allThePixels[point])
}
}
let initBuffer = new Uint8Array(buffer.width * buffer.height * 4);
points.forEach((point) => {
let pos = 4 * (point[0] * buffer.width + point[1]);
initBuffer[pos] = 255;
initBuffer[pos+1] = 255;
initBuffer[pos+2] = 255;
initBuffer[pos+3] = 255;
})
let initBuffer = new Uint8Array(buffer.width * buffer.height * 4);
points.forEach((point) => {
let pos = 4 * (point[0] * buffer.width + point[1]);
initBuffer[pos] = 255;
initBuffer[pos+1] = 255;
initBuffer[pos+2] = 255;
initBuffer[pos+3] = 255;
})
let ephemeralTex = new THREE.DataTexture(initBuffer, width, height);
ephemeralTex.needsUpdate = true;
evoUniforms.initBufferMask.value = ephemeralTex;
init = false;
}
let fractionAnimated = (clock.getElapsedTime() % durationInSeconds) / durationInSeconds;
let pixelsAnimated = Math.round(longestPixelStrand * fractionAnimated);
let initBuffer = new Uint8Array(buffer.width * buffer.height * 4);
for (let row = 0; row < height; row++) {
for(let col=0; col<width; col++) {
var point = 4 * (row * width + col);
if (dijkstraBuffer[point] > 1 && dijkstraBuffer[point] < pixelsAnimated) {
initBuffer[point] = 255;
initBuffer[point+1] = 255;
initBuffer[point+2] = 255;
initBuffer[point+3] = 255;
}
}
}
let ephemeralTex = new THREE.DataTexture(initBuffer, width, height);
ephemeralTex.needsUpdate = true;
evoUniforms.initBufferMask.value = ephemeralTex;
let ephemeralTex = new THREE.DataTexture(initBuffer, width, height);
ephemeralTex.needsUpdate = true;
evoUniforms.initBufferMask.value = ephemeralTex;
init = false;
}
let fractionAnimated = (clock.getElapsedTime() % durationInSeconds) / durationInSeconds;
let pixelsAnimated = Math.round(longestPixelStrand * fractionAnimated);
let initBuffer = new Uint8Array(buffer.width * buffer.height * 4);
for (let row = 0; row < height; row++) {
for(let col=0; col<width; col++) {
var point = 4 * (row * width + col);
if (allThePixels[point] > 1 && allThePixels[point] < pixelsAnimated) {
initBuffer[point] = 255;
initBuffer[point+1] = 255;
initBuffer[point+2] = 255;
initBuffer[point+3] = 255;
}
}
}
let ephemeralTex = new THREE.DataTexture(initBuffer, width, height);
ephemeralTex.needsUpdate = true;
evoUniforms.initBufferMask.value = ephemeralTex;
renderer.setRenderTarget(null);
renderer.render(solidScene, camera);
renderer.autoClear = false;
renderer.clearDepth();
renderer.render(evolveScene, camera);
renderer.autoClear = true;
renderer.setRenderTarget(null);
renderer.render(solidScene, camera);
renderer.autoClear = false;
renderer.clearDepth();
renderer.render(evolveScene, camera);
renderer.autoClear = true;
}
renderer.setAnimationLoop(animate);
window.addEventListener('resize', function() {
width = window.innerWidth
height = window.innerHeight
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
init = true;
});
orbit.addEventListener('change', function() {
init = true;
init = true;
})
</script>