Created
February 21, 2021 01:58
-
-
Save leereilly/6200e7c4e034069b5c9700be2377d9a9 to your computer and use it in GitHub Desktop.
Advanced Synthwave Scene
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Synthwave world | |
// WebGL Version is coming soon | |
const c = document.createElement('canvas').getContext('2d') // There will party | |
const canvas = c.canvas | |
const postctx = document.body.appendChild(document.createElement('canvas')).getContext('2d') // For post-processing | |
// This is for TV-effect | |
const redFilter = document.createElement('canvas').getContext('2d') | |
const greenFilter = document.createElement('canvas').getContext('2d') | |
const blueFilter = document.createElement('canvas').getContext('2d') | |
// Common | |
let frame = 0 | |
const polygons = [] | |
const meshes = [] | |
// Properties | |
const polygonCount = 16 | |
const polygonSize = 48 | |
const perspective = 70 | |
const cameraY = 32 | |
const colorDistortionSize = 2 | |
const resolutionDivider = 4 // Image quality | |
const bloom = true | |
const { sin, cos, PI } = Math | |
const drawSun = (x, y, r) => { | |
c.fillStyle = c.createLinearGradient(x, y - r, x, y + r) | |
c.fillStyle.addColorStop(0.1, "#fdce74") | |
c.fillStyle.addColorStop(0.8, "#d60066") | |
c.beginPath() | |
c.arc(x, y, r, 0, Math.PI * 2) | |
c.fill() | |
// splitting the sun by lines | |
for (let i = 0; i < 30; i++) { | |
c.fillStyle = "#000" | |
c.fillRect(x - r, y + i * 10, r * 2, i) | |
} | |
} | |
// Generation model of mountain | |
const mountainMesh = function (x1, z1) { | |
let mesh = [] | |
let s = 10 | |
let cliffFunction = (vertex) => { | |
let x = vertex[0] | |
let y = vertex[1] | |
let z = vertex[2] | |
let dist = Math.sqrt((x1 - x) ** 2 + (z1 - z) ** 2) | |
if (dist < 400) { | |
vertex[1] -= 400 - dist | |
vertex[1] += cos(x) * 32 + sin(z) * 32 | |
} | |
} | |
// Generation floor | |
for (let i = 0; i < s ** 2;i++) { | |
let x = i % s | |
let z = i / s >> 0 | |
let y = 128 | |
// Set origin of plane to center | |
x -= s / 2 | |
z -= s / 2 | |
// Scaling to polygonSize | |
x *= polygonSize | |
z *= polygonSize | |
// Translation to coord x1, z1 | |
x += x1 | |
z += z1 | |
let p1 = [ | |
[x, y, z], | |
[x, y, z + polygonSize], | |
[x + polygonSize, y, z], | |
[x, y, z] | |
] | |
let p2 = [ | |
[x + polygonSize, y, z + polygonSize], | |
[x, y, z + polygonSize], | |
[x + polygonSize, y, z], | |
[x + polygonSize, y, z + polygonSize], | |
] | |
p1.forEach(cliffFunction) | |
p2.forEach(cliffFunction) | |
mesh.push(p1) | |
mesh.push(p2) | |
} | |
meshes.push(mesh) | |
} | |
const loop = function () { | |
let rad = cos(frame / 40) * Math.PI / 20 // Camera rotation | |
frame++ | |
// Resizing canvas | |
if (postctx.canvas.width !== postctx.canvas.offsetWidth || postctx.canvas.height !== postctx.canvas.offsetHeight) { | |
postctx.canvas.width = | |
canvas.width = | |
redFilter.canvas.width = | |
greenFilter.canvas.width = | |
blueFilter.canvas.width = | |
postctx.canvas.offsetWidth / resolutionDivider | |
postctx.canvas.height = | |
canvas.height = | |
redFilter.canvas.height = | |
greenFilter.canvas.height = | |
blueFilter.canvas.height = | |
postctx.canvas.offsetHeight / resolutionDivider | |
} | |
c.fillRect(0, 0, canvas.width, canvas.height) | |
c.strokeStyle = "#00e9ff" | |
c.save() | |
// 0,0 is center of the canvas | |
c.translate(canvas.width / 2, canvas.height / 2) | |
// Drawing sun with transformation | |
drawSun(sin(rad) * 150, -64, 64) | |
// Drawing Models | |
c.fillStyle = "#28144B" | |
c.strokeStyle = "#CC3B2F" | |
meshes.forEach(p => { | |
p.forEach((polygon, i) => { | |
c.beginPath() | |
polygon.forEach((vertex, j) => { | |
let x = vertex[0] | |
let y = vertex[1] | |
let z = vertex[2] | |
let dist = Math.abs(x) | |
let tx = x | |
let ty = y | |
let tz = z | |
// Transformation Rotate Y | |
tx = x * cos(rad) + z * sin(rad) | |
tz = -x * sin(rad) + z * cos(rad) | |
x = tx | |
y = ty | |
z = tz | |
// Translate by camera | |
y += cameraY + cos(frame / 5) * 1 | |
y -= (sin(frame / 10 + z / 6) + cos(frame / 10 + x / 6)) * 8 | |
if ( z < 1 ) z = 1 | |
// Transform | |
x /= z / perspective | |
y /= z / perspective | |
c[j === 0 ? 'moveTo' : 'lineTo'](x, y) | |
}) | |
c.fill() | |
c.stroke() | |
}) | |
}) | |
// Drawing road | |
c.fillStyle = "#511445" | |
polygons.forEach((polygon, i) => { | |
c.beginPath() | |
polygon.forEach((vertex, j) => { | |
let x = vertex[0] | |
let y = vertex[1] | |
let z = vertex[2] | |
let tx, ty, tz; | |
let dist = Math.abs(x) | |
// Make ground move | |
z -= frame % polygonSize | |
// Waveform | |
if (dist > 256) { // This line makes cliffs in right and left | |
y -= dist - 128 + (sin(frame / 10 + z / 6) + cos(frame / 10 + x / 6)) * 8 | |
} | |
// Translate by camera | |
y += cameraY + cos(frame / 5) * 1 | |
tx = x | |
ty = y | |
tz = z | |
// Transform RotationY | |
tx = x * cos(rad) + z * sin(rad) | |
tz = -x * sin(rad) + z * cos(rad) | |
// Apply transformation | |
x = tx | |
y = ty | |
z = tz | |
if ( z < 1 ) z = 1 | |
x /= z / perspective | |
y /= z / perspective | |
// if current vertex is first then calling 'moveTo' function else calling 'lineTo' | |
c[j === 0 ? 'moveTo' : 'lineTo'](x, y) | |
}) | |
c.strokeStyle = `hsl(${polygon[0][2] / 10 + 250 - frame}deg, 100%, 50%)` | |
c.fillStyle = `hsl(${polygon[0][2] / 10 + 200 - frame}deg, 100%, 10%)` | |
c.fill() | |
c.stroke() | |
}) | |
c.restore() | |
// Bloom | |
if (bloom) { | |
c.globalCompositeOperation = 'screen' | |
c.filter = 'blur(8px)' | |
c.drawImage(canvas, 0, 0) | |
c.filter = 'blur(0px)' | |
} | |
// Color Correction | |
c.fillStyle = c.createLinearGradient(0, 0, 0, canvas.height) | |
c.fillStyle.addColorStop(0, "#AB4FE7") | |
c.fillStyle.addColorStop(0.4, "#28144B") | |
c.globalCompositeOperation = 'overlay' | |
c.fillRect(0, 0, canvas.width, canvas.height) | |
c.globalCompositeOperation = 'source-over' | |
// Post-processing | |
// Getting only red colors from canvas | |
redFilter.drawImage(canvas, 2, 0) | |
redFilter.globalCompositeOperation = 'multiply' | |
redFilter.fillStyle = "#f00" | |
redFilter.fillRect(0, 0, canvas.width, canvas.height) | |
redFilter.globalCompositeOperation = 'source-over' | |
// Getting only green colors from canvas | |
greenFilter.drawImage(canvas, 2, 0) | |
greenFilter.globalCompositeOperation = 'multiply' | |
greenFilter.fillStyle = "#0f0" | |
greenFilter.fillRect(0, 0, canvas.width, canvas.height) | |
greenFilter.globalCompositeOperation = 'source-over' | |
// Getting only blue colors from canvas | |
blueFilter.drawImage(canvas, 2, 0) | |
blueFilter.globalCompositeOperation = 'multiply' | |
blueFilter.fillStyle = "#00f" | |
blueFilter.fillRect(0, 0, canvas.width, canvas.height) | |
blueFilter.globalCompositeOperation = 'source-over' | |
// Making TV-effect | |
// Combine all filter in one with bloom effect and color shifting | |
postctx.clearRect(0, 0, canvas.width, canvas.height) | |
postctx.globalCompositeOperation = 'screen' | |
postctx.filter = 'blur(0.5px)' | |
postctx.drawImage(redFilter.canvas, 0, 0) | |
postctx.drawImage(greenFilter.canvas, colorDistortionSize, 0) | |
postctx.drawImage(blueFilter.canvas, -colorDistortionSize, 0) | |
postctx.globalCompositeOperation = 'source-over' | |
requestAnimationFrame(loop) | |
} | |
mountainMesh(0, 500, 15) | |
// Generating Ground | |
for (let i = 0; i < polygonCount ** 2;i++) { | |
let x = i % polygonCount | |
let z = i / polygonCount >> 0 | |
let y = 0 | |
// Set x-origin to center of plane | |
x -= polygonCount / 2 | |
// Scaling to polygon Size | |
x *= polygonSize | |
z *= polygonSize | |
// Creating two polygons to make rectangle | |
polygons.push([ | |
[x, y, z], | |
[x, y, z + polygonSize], | |
[x + polygonSize, y, z], | |
[x, y, z] | |
]) | |
polygons.push([ | |
[x + polygonSize, y, z + polygonSize], | |
[x, y, z + polygonSize], | |
[x + polygonSize, y, z], | |
[x + polygonSize, y, z + polygonSize] | |
]) | |
} | |
loop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
canvas { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment