Published
Edited
Jun 11, 2021
Importers
1 star
Insert cell
md`# Anaglyph Pipeline in GPUjs`
Insert cell
worldController
Insert cell
renderer(pyramidGrid, parameters)
Insert cell
renderer = (scene, parameters) => {
return svg`<svg width="${width}px" height="${height}">${anaglyphRenderer(processScene(scene, parameters))}</svg>`
}
Insert cell
perspectiveController
Insert cell
colors = Object.assign({left: "red", right: "rgb(0,255,235)"})
Insert cell
anaglyphRenderer = (processedScene) => {
const leftLayer = []
const rightLayer = []
const behindProjectionPlane = (item) => {
return item.map(points => {
points.map(d => d[0][2]).find((d => d <= 0)) === undefined &&
points.map(d => d[1][2]).find((d => d <= 0)) === undefined
})

return true
}
let allLeftPath = ''
let allRightPath = ''
const svgStuff = processedScene
.filter(behindProjectionPlane)
.forEach((item) => {
if (item.length > 1) {
const leftPath = "M " + item.map(p => `${p[0][0]} ${p[0][1]}`).join(' L ')
const rightPath = "M " + item.map(p => `${p[1][0]} ${p[1][1]}`).join(' L ')
allLeftPath += ' ' + leftPath
allRightPath += ' ' + rightPath
} else {
if (item.text) {
leftLayer.push(svg`<g transform="translate(${item[0][0][0]},${item[0][0][1]})">${letterPlotter(item.text, { color: colors.left })}</g>`)
rightLayer.push(svg`<g transform="translate(${item[0][1][0]},${item[0][1][1]})">${letterPlotter(item.text, { color: colors.right })}</g>`)
} else {
leftLayer.push(svg`<circle cx="${item[0][0][0]}" cy="${item[0][0][1]}" r="1" fill="${colors.left}" />`)
rightLayer.push(svg`<circle cx="${item[0][1][0]}" cy="${item[0][1][1]}" r="1" fill="${colors.right}" />`)
}
}
})
if (allLeftPath !== '') {
leftLayer.push(svg`<path d="${allLeftPath}" fill="none" stroke="${colors.left}" stroke-width="1" style="mix-blend-mode: multiply;" />`)
}
if (allRightPath !== '') {
rightLayer.push(svg`<path d="${allRightPath}" fill="none" stroke="${colors.right}" stroke-width="1" style="mix-blend-mode: multiply;" />`)
}
return svg`<g>
<g transform="translate(${width / 2},${height / 2})">
${leftLayer}
</g>
<g transform="translate(${width / 2},${height / 2})">
${rightLayer}
</g>
</g>`
}
Insert cell
// processedScene = processScene(chunksOfStuff[0], parameters)
Insert cell
scene
Insert cell
pyramidGrid = {
const rowsOfPyramids = 35
const rangeInput = Math.floor(rowsOfPyramids / 2) * 3
const x = d3.range(-rangeInput, rangeInput + 1, 3)
const y = d3.range(-rangeInput, rangeInput + 1, 3)
const origins = d3.cross(x, y).filter((c) => {
const isMiddleX = (c[0] <= 1 && c[0] >= -1)
const isMiddleY = (c[1] <= 1 && c[1] >= -1)
return !isMiddleX || !isMiddleY
})
let list = []
origins.forEach((o, i) => {
const peak = [o[0], o[1], 2, 1]
const pcorners = [
[o[0] + 1, o[1] + 1, 0, 1],
[o[0] + 1, o[1] - 1, 0, 1],
[o[0] - 1, o[1] - 1, 0, 1],
[o[0] - 1, o[1] + 1, 0, 1]
]

const bottom = d3.range(4).map((i) => { return [pcorners[i], pcorners[(i + 1) % 4]] })
const sides = d3.range(4).map((i) => { return [peak, pcorners[(i + 1) % 4]] })

list = list.concat(bottom)
list = list.concat(sides)
})
return list
}
Insert cell
prepareForGPU(scene)
Insert cell
processScene = (scene, parameters) => {
let flattenedScene = prepareForGPU(scene)
const translateVector = [parameters.mx, parameters.my, parameters.mz, 0]
const wtMatrix = worldTransformMatrixFn(parameters)
const apMatrices = anaglyphPerspectiveMatricesFn(parameters)
const processChunk = (chunk) => {
const translationKernel = gpu.createKernel(function(points, translate) {
let sum = 0;

sum += points[this.thread.y][this.thread.x] + translate[this.thread.x]

return sum;
})
.setPipeline(true)
.setOutput([4, chunk.length]);

const worldTransformKernel = gpu.createKernel(function(points, worldTransformMatrix) {
let sum = 0;

for (let i = 0; i < 4; i++) {
sum += points[this.thread.y][i] * worldTransformMatrix[i][this.thread.x];
}

return sum;
})
.setPipeline(true)
.setOutput([4, chunk.length]);

const eyeShiftKernel = gpu.createKernel(function(points, shift, cameraOffset) {
let sideShift = (this.thread.x === 0) ? shift[this.thread.y] : 0
let cOffset = (this.thread.x === 2) ? -cameraOffset : 0
return points[this.thread.z][this.thread.x] + sideShift + cOffset
})
.setPipeline(true)
.setOutput([4, 2, chunk.length])

const anaglyphPerspectiveKernel = gpu.createKernel(function(points, anaglyphPerspectiveMatrices) {
let sum = 0;

for (let i = 0; i < 4; i++) {
sum += points[this.thread.z][this.thread.y][i] * anaglyphPerspectiveMatrices[this.thread.y][i][this.thread.x]
}

return sum;
})
.setPipeline(true)
.setOutput([4, 2, chunk.length])

const divisionKernel = gpu.createKernel(function(points, scale) {
const factor = (this.thread.x <= 1) ? scale / points[this.thread.z][this.thread.y][3] : 1

return points[this.thread.z][this.thread.y][this.thread.x] * factor
}).setOutput([3, 2, chunk.length])
return divisionKernel(anaglyphPerspectiveKernel(eyeShiftKernel(worldTransformKernel(translationKernel(chunk, translateVector), wtMatrix), [-parameters.eyeGap / 2, parameters.eyeGap / 2], parameters.cameraOffset), apMatrices), parameters.scale)
}
// Divide and Conquer
const maxTextureSize = 16384
const numOfChunks = Math.ceil(flattenedScene.length / maxTextureSize)
const chunks = d3.range(numOfChunks).map((i) => {
return flattenedScene.splice(0, maxTextureSize)
})
.map(processChunk)
let flatResults = [].concat.apply([], chunks);
// Group back into entities in the scene
const results = scene.map((entity) => {
const anaglyphPoints = flatResults.splice(0, entity.length)
Object.keys(entity).forEach((key) => {
if (!Number.isInteger(parseInt(key))) {
anaglyphPoints[key] = entity[key]
}
})
return anaglyphPoints
})
return results
}
Insert cell
prepareForGPU(pyramidGrid)
Insert cell
chunksOfStuff = {
const longArray = prepareForGPU(pyramidGrid)
const maxTextureSize = 16384
const numOfChunks = Math.ceil(longArray.length / maxTextureSize)
return d3.range(numOfChunks).map((i) => {
return longArray.splice(0, maxTextureSize)
})
}
Insert cell
prepareForGPU = (scene) => {
const flatten = [].concat.apply([], scene)
return flatten
}
Insert cell
md`# Prototyping`
Insert cell
pointsArray = {
return [
[0, 0, 0, 1],
[3, 0, 0, 1],
[0, 3, 0, 1],
[0, 0, 3, 1],
[3, 3, 3, 1],
]
}
Insert cell
one = translatePoints(pointsArray, translate)
Insert cell
two = applyWorldTransforms(one, [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
Insert cell
three = applyShiftTransform(two, [-0, 0], parameters.cameraOffset)
Insert cell
four = applyAnaglyphPerspectiveTransforms(three, anaglyphPerspectiveMatrices)
Insert cell
five = applyDivision(four, 1000)
Insert cell
applyAnaglyphPerspectiveTransforms = gpu.createKernel(function(points, anaglyphPerspectiveMatrices) {
let sum = 0;

for (let i = 0; i < 4; i++) {
sum += points[this.thread.z][this.thread.y][i] * anaglyphPerspectiveMatrices[this.thread.y][i][this.thread.x]
}

return sum;
}).setOutput([4, 2, pointsArray.length])
Insert cell
applyShiftTransform = gpu.createKernel(function(points, shift, cameraOffset) {
let sideShift = (this.thread.x === 0) ? shift[this.thread.y] : 0
let cOffset = (this.thread.x === 2) ? -cameraOffset : 0
return points[this.thread.z][this.thread.x] + sideShift + cOffset
})
.setOutput([4, 2, pointsArray.length])
Insert cell
applyDivision = gpu.createKernel(function(points, scale) {
const factor = (this.thread.x <= 1) ? scale / points[this.thread.z][this.thread.y][3] : 1

return points[this.thread.z][this.thread.y][this.thread.x] * factor
}).setOutput([3, 2, pointsArray.length])
Insert cell
applyWorldTransforms = gpu.createKernel(function(points, worldTransformMatrix) {
let sum = 0;

//sum += points[this.thread.y][this.thread.x] + translate[this.thread.x]
for (let i = 0; i < 4; i++) {
// sum += a[this.thread.y][i] * b[i][this.thread.x];
sum += points[this.thread.y][i] * worldTransformMatrix[i][this.thread.x];
}

return sum;
})
.setOutput([4, pointsArray.length]);
Insert cell
results2 = applyAnaglyphPerspectiveTransforms(pointsArray, anaglyphPerspectiveMatrices)
Insert cell
testMatrices = [
[
[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]
]
]
Insert cell
anaglyphPerspectiveMatrices = anaglyphPerspectiveMatricesFn(parameters)
Insert cell
anaglyphPerspectiveMatricesFn = ({ eyeGap, near, zRange, scale, horizontalShift, verticalShift }) => {
const far = near + zRange
const generateMatrix = (sign) => {
const hahaha = eyeGap * 0.01
const r = 1 + hahaha * sign + horizontalShift
const l = -1 + hahaha * sign + horizontalShift
const t = 1 + verticalShift
const b = -1 + verticalShift

return [
[2 * near / (r - l), 0, 0, 0],
[0, 2 * near / (t - b), 0, 0],
[(r + l) / (r - l), (t + b) / (t - b), -(far + near)/(far - near), -2 * far * near / (far - near)],
[0, 0, -1, 0]
]
}
return [
generateMatrix(1),
generateMatrix(-1)
]
}
Insert cell
translatePoints = gpu.createKernel(function(points, translate
// , worldTransformMatrix, mx, my, mz, cameraOffset, near, zRange, eyeGap,
// scale, horizontalShift, verticalShift, xprime, yprime
) {
let sum = 0;

sum += points[this.thread.y][this.thread.x] + translate[this.thread.x]
/*
for (let i = 0; i < 4; i++) {
// sum += a[this.thread.y][i] * b[i][this.thread.x];
}
*/

return sum;
})
.setOutput([4, pointsArray.length]);
Insert cell
translate = [parameters.mx, parameters.my, parameters.mz, 0]
Insert cell
worldTransformMatrix = worldTransformMatrixFn(parameters)
Insert cell
worldTransformMatrixFn = ({ rX, rY, rZ, cx, cy, cz }) => {
const rotationX = [
[ 1, 0, 0, 0],
[ 0, Math.cos(rX), -Math.sin(rX), 0],
[ 0, Math.sin(rX), Math.cos(rX), 0],
[ 0, 0, 0, 1]
]
const rotationY = [
[ Math.cos(rY), 0, Math.sin(rY), 0 ],
[ 0, 1, 0, 0 ],
[ -Math.sin(rY), 0, Math.cos(rY), 0 ],
[ 0, 0, 0, 1 ]
]
const rotationZ = [
[ Math.cos(rZ), -Math.sin(rZ), 0, 0 ],
[ Math.sin(rZ), Math.cos(rZ), 0, 0 ],
[ 0, 0, 1, 0 ],
[ 0, 0, 0, 1 ]
]
const cameraTransform =
[
[ 1, 0, 0, 0 ],
[ 0, 1, 0, 0 ],
[ 0, 0, 1, 0 ],
[ -cx, -cy, -cz, 1 ]
];
return mathjs.multiply(rotationZ, rotationY, rotationX, cameraTransform)
}
Insert cell
parameters = Object.assign({ ...worldParameters, ...perspectiveParameters })
Insert cell
gpu = new GPU.GPU();
Insert cell
import {letterPlotter} from '@tonyhschu/experiment-with-plottable-letters'
Insert cell
import { worldParameters, perspectiveParameters, perspectiveController, worldController, scene } from '@tonyhschu/anaglyph-rendering-in-svg'
Insert cell
GPU = require("gpu.js@2")
Insert cell
height = 512
Insert cell
mathjs = require("https://unpkg.com/mathjs@4.0.0/dist/math.min.js")
Insert cell
d3 = require("d3@6")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more