Files
ByteDiceWeb/synthwaveEffect.js
T

403 lines
9.4 KiB
JavaScript

let usePostProcessing = true
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ alpha: true })
const composer = new THREE.EffectComposer(renderer)
composer.setSize(window.innerWidth, window.innerHeight)
const renderPass = new THREE.RenderPass(scene, camera)
composer.addPass(renderPass)
const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
type: THREE.UNSIGNED_BYTE
})
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 edgeColor;
uniform vec3 centerColor;
uniform sampler2D tDiffuse;
uniform float pxDensity;
uniform vec2 resolution;
uniform float aspectRatio;
float brightness(vec3 c) {
return (c.r + c.g + c.b) / 3.0;
}
void main() {
vec2 uv = vUv;
vec2 screenPxCount = vec2(resolution.x / pxDensity, resolution.y / pxDensity);
screenPxCount = vec2(256.0, 164.0);
vec2 p = floor(uv * screenPxCount) / screenPxCount;
vec4 color = texture2D(tDiffuse, p);
float b = brightness(color.rgb);
float centerB = brightness(centerColor);
float alpha = floor(color.a);
if (b > centerB) {
gl_FragColor = vec4(edgeColor, color.a);
}
else if (color == vec4(0.0)) {
gl_FragColor = vec4(0.0);
}
else {
gl_FragColor = vec4(centerColor, color.a);
}
//gl_FragColor = color;
}
`,
uniforms: {
edgeColor: { value: new THREE.Color(0x00b4f0) },
centerColor: { value: new THREE.Color(0x323232) },
tDiffuse: { value: null },
pxDensity: { value: pxDensity },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
aspectRatio: { value: camera.aspect }
}
})
const shaderPass = new THREE.ShaderPass(shaderMaterial)
composer.addPass(shaderPass)
shaderPass.material.uniforms.tDiffuse.value = renderTarget.texture
// TODO: add pixelation shader
if (!renderer) {
alert("Renderer failed to load, using static images instead.")
// TODO: add image logic
throw new Error("Renderer initialization failed.")
}
renderer.setSize(window.innerWidth, window.innerHeight)
const mainCanvas = document.getElementById("canvasContainer")
mainCanvas.appendChild(renderer.domElement)
renderer.setClearColor(0x000000, 0)
const geometry = new THREE.BufferGeometry()
let meshVerts = [
-1, 0, 1,
-1, 1, -1,
1, 1, 1,
1, 0, -1
]
let meshIndices = [
0, 1, 2,
1, 2, 3
]
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(meshVerts), 3))
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(meshIndices), 1))
geometry.computeVertexNormals()
geometry.computeBoundingBox()
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: false, side: THREE.DoubleSide })
const mesh = new THREE.Mesh(geometry, material)
//scene.add(mesh)
// "toward sun"
/* camera.position.z = 5
camera.position.y = 2 */
// far away "toward sun"
camera.position.z = 0
camera.position.y = 1
camera.rotation.x = 0.2
// top down
/* camera.position.y = 5
camera.lookAt(0, 0, 0) */
debugPrint(
"Camera transform",
`Pos: [x: ${camera.position.x}, y: ${camera.position.y}, z: ${camera.position.z}]
Rot: [x: ${camera.rotation.x}, y: ${camera.rotation.y}, z: ${camera.rotation.z}]`
)
function hillCurve(x, width) {
return (Math.sin(Math.PI * (x + 1)) + 1) ** width
}
function generateHill(width, length, segments, maxHeight, posOffsetZ = 0, prevHillVerts = []) {
let hillIndices = []
let startPos = -width / 2
let triCount = segments + 1
let prevHillHeights = prevHillVerts.filter((_, index) => (index - 1) % 3 === 0)
let hillData = generateHillVerts(
triCount,
maxHeight,
width,
startPos,
prevHillHeights,
segments,
length,
posOffsetZ
)
let hillVerts = hillData.verts
let hillUVs = hillData.uvs
for (let i = 0; i < segments; i++) {
hillIndices.push(i * 2, i * 2 + 2, i * 2 + 1)
hillIndices.push(i * 2 + 1, i * 2 + 2, i * 2 + 3)
}
const geometry = new THREE.BufferGeometry()
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(hillVerts), 3))
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(hillUVs), 2))
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(hillIndices), 1))
geometry.computeVertexNormals()
geometry.computeBoundingBox()
const material = generateHillMat(segments, width, length)
const newMesh = new THREE.Mesh(geometry, material)
newMesh.position.z = posOffsetZ
return {mesh: newMesh, verts: hillVerts, indices: hillIndices}
}
function generateHillVerts(
triCount,
maxHeight,
width,
startPos,
prevHillHeights,
segments,
length
) {
let hillVerts = []
let hillUVs = []
for (let i = 0; i < triCount; i++) {
let height = randomFloat(0, maxHeight) * hillCurve(i / triCount, 1.2)
hillVerts.push(i / (triCount - 1) * width + startPos)
if (prevHillHeights.length / 2 != segments + 1) { hillVerts.push(0) }
else { hillVerts.push(prevHillHeights[i * 2 + 1]) }
hillVerts.push(length / 2)
hillUVs.push(i / (triCount - 1), 0)
hillVerts.push(
i / (triCount - 1) * width + startPos,
height,
-length / 2
)
hillUVs.push(i / (triCount - 1), 1)
}
return {verts: hillVerts, uvs: hillUVs}
}
function generateHillMat(segments, width, length) {
//return new THREE.MeshBasicMaterial({ color: 0x00FF00, wireframe: false })
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
const fragmentShader = `
varying vec2 vUv;
uniform vec3 edgeColor;
uniform vec3 centerColor;
uniform int segmentCount;
uniform vec2 resolution;
void main() {
vec2 uv = vUv;
uv.x = fract(uv.x * float(segmentCount));
float edgeThresholdX = 0.075;
float edgeThresholdY = 0.075;
if (resolution.x > resolution.y) {
edgeThresholdX *= resolution.y / resolution.x;
}
else {
edgeThresholdY *= resolution.x / resolution.y;
}
float isEdge = 0.0;
if (uv.y < edgeThresholdY || uv.y > (1.0 - edgeThresholdY)) {
isEdge = 1.0;
}
if (uv.x < edgeThresholdX || uv.x > (1.0 - edgeThresholdX)) {
isEdge = 1.0;
}
vec3 color = mix(centerColor, edgeColor, isEdge);
gl_FragColor = vec4(color, 1.0);
}
`
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
edgeColor: { value: new THREE.Color(0x00b4f0) },
centerColor: { value: new THREE.Color(0x323232) },
segmentCount: { value: segments },
resolution: { value: new THREE.Vector2(width / segments, length) }
}
})
return material
}
let allHillMeshes = []
function addHillMesh(mesh) {
allHillMeshes.push(mesh)
scene.add(mesh)
}
const hillLength = 1.2
let hillSpawnOffset = -25
let lastHillVerts = []
let hasPreAnimated = false
function preAnimateSynthwave(times) {
for (let i = 0; i < times; i++) {
let hillOffset = -(i * hillLength) + 0.02
let newHill = generateHill(
15, //camera.aspect * 10,
hillLength,
25,
4,
hillOffset,
lastHillVerts
)
addHillMesh(newHill.mesh)
lastHillVerts = newHill.verts
hillSpawnOffset = hillOffset
}
hasPreAnimated = true
debugPrint("Pre-animated synthwave complete")
}
let lastTime = 0
const tickRate = 12
let tickMultiplier = 1
let distSinceLastHill = 0
function animateSynthwave(timestamp) {
let deltaTime = (timestamp - lastTime) / 1000
lastTime = timestamp
let currentTickRate = 1 / deltaTime
tickMultiplier = tickRate / currentTickRate
if (!hasPreAnimated) { return }
if (!isAnimating) {
render()
return
}
const moveDist = 0.0035 //0.05 * tickMultiplier
distSinceLastHill += moveDist
for (let hill of allHillMeshes) {
hill.position.z += moveDist
const outOfBoundsCoord = camera.aspect * 75
if (hill.position.z - hill.geometry.boundingBox.min.y >= outOfBoundsCoord) {
scene.remove(hill)
}
}
if (distSinceLastHill >= hillLength) {
let hillZOffset = hillLength - distSinceLastHill
let newHill = generateHill(
15, //camera.aspect * 10,
hillLength,
25,
4,
hillZOffset + 0.02 + hillSpawnOffset,
lastHillVerts
)
addHillMesh(newHill.mesh)
distSinceLastHill = -hillZOffset
lastHillVerts = newHill.verts
}
render()
}
function render() {
if (usePostProcessing) {
renderer.setRenderTarget(renderTarget)
renderer.render(scene, camera)
renderer.setRenderTarget(null)
composer.render()
requestAnimationFrame(animateSynthwave)
}
else {
renderer.setRenderTarget(null)
renderer.render(scene, camera)
requestAnimationFrame(animateSynthwave)
}
}
function onResizeSynthwave() {
renderer.setSize(window.innerWidth, window.innerHeight)
composer.setSize(window.innerWidth, window.innerHeight)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
}