Created
June 5, 2017 16:19
-
-
Save mattdesl/cd13e41b39983e8c822f56fb3a7bb114 to your computer and use it in GitHub Desktop.
CPU-side "shaders" for really big image rendering
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
/* | |
This streams a CPU-side "shader" function into a really huge PNG image. | |
*/ | |
const PNGEncoder = require('png-stream/encoder'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const Readable = require('readable-stream').Readable; | |
const vec2 = require('gl-vec2'); | |
const smoothstep = require('smoothstep'); | |
const shape = [ 6000, 6000 ]; | |
const encoder = new PNGEncoder(shape[0], shape[1], { | |
colorSpace: 'rgba' | |
}); | |
const output = fs.createWriteStream(path.resolve(__dirname, 'test.png')); | |
const stream = createShader(shape, (uv) => { | |
// Write your shader function here with any cool code you want... | |
const length = vec2.length([ uv[0] - 0.5, uv[1] - 0.5 ]); | |
const d = smoothstep(0.25, 0.5, length); | |
return [ d, d, d, 1 ]; | |
}, 4); | |
stream.pipe(encoder); | |
encoder.pipe(output); | |
function createShader (size, fn, stride = 4) { | |
const width = size[0]; | |
const height = size[1]; | |
const totalPixels = width * height; | |
const chunkSize = Math.min(2048, height); | |
const totalChunks = Math.ceil(height / chunkSize); | |
let currentChunk = 0; | |
var stream = new Readable() | |
stream._read = read | |
return stream | |
function read () { | |
if (currentChunk > totalChunks - 1) { | |
return process.nextTick(() => { | |
stream.push(null) | |
}) | |
} | |
var yOffset = chunkSize * currentChunk; | |
var dataHeight = Math.min(chunkSize, height - yOffset); | |
var pixelBuffer = new Buffer(width * dataHeight * stride); | |
var pixelsInChunk = pixelBuffer.length / stride; | |
var offset = 0; | |
for (let i = 0; i < pixelsInChunk; i++) { | |
const x = Math.floor(i % width); | |
const y = Math.floor(i / width) + yOffset; | |
const uv = [ x / width, y / height ]; | |
const color = fn(uv, x, y, width, height); | |
for (let c = 0; c < stride; c++) { | |
let component; | |
if (c < color.length) { | |
component = Math.max(0, Math.min(255, Math.floor(color[c] * 255))); | |
} else { | |
component = 255; // expand any missing components, like alpha | |
} | |
pixelBuffer[offset++] = component | |
} | |
} | |
currentChunk++; | |
console.log('Chunk %d / %d', currentChunk, totalChunks) | |
stream.push(pixelBuffer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment