Create a gist now

Instantly share code, notes, and snippets.

@bsergean /README.md
Last active Oct 13, 2017

What would you like to do?
offscreen rendering with three.js and headless-gl, in coffee-script

Getting the code

git clone https://gist.github.com/6780d7cc0cabb1b4d6c8.git

Executing the code

$ npm install # maybe npm start will take care of it but just in case
$ npm start && open out.png

> offscreen-sample@1.0.0 start /Users/bsergean/src/offscreen_sample
> coffee offscreen_sample.coffee

THREE.WebGLRenderer 71
THREE.WebGLRenderer: TypeError: Object #<Object> has no method 'addEventListener'
THREE.WebGLRenderer: OES_texture_float extension not supported.
THREE.WebGLRenderer: OES_texture_float_linear extension not supported.
THREE.WebGLRenderer: OES_texture_half_float extension not supported.
THREE.WebGLRenderer: OES_texture_half_float_linear extension not supported.
THREE.WebGLRenderer: OES_standard_derivatives extension not supported.
THREE.WebGLRenderer: OES_element_index_uint extension not supported.
THREE.WebGLRenderer: EXT_texture_filter_anisotropic extension not supported.
Image written: out.png

Those warnings are harmless for our test case, but might be problematic for some folks. Support for extension is planned and coming -> https://github.com/stackgl/headless-gl/issues/5

If you are on Linux you will need Xvfb. One way to do it:

$ xvfb-run -s "-ac -screen 0 1280x1024x24” node_modules/.bin/coffee cmd_antialias.coffee -i test_aliased.png -o out.png

More infos here -> https://github.com/stackgl/headless-gl#how-can-headless-gl-be-used-on-a-headless-linux-machine

Inspecting the output

Tada ! You just created an image thanks to OpenGL and many awesome libraries. How cool is that. Now open the output image. On a Mac you can just do that:

open out.png

I can't figure out how to add a .png to a gist. I've updaloaded the very non-impressive image here -> http://imgur.com/Vq4FnN9

Bummer, the sample is in coffee-script

npm run compile

This will compile the .coffee file to javascript and print it in your terminal. It's almost the same as the .coffee.

# The required node modules
THREE = require('three')
PNG = require('pngjs').PNG
gl = require("gl")()
fs = require('fs')
# Parameters (the missing one is the camera position, see below)
width = 600
height = 400
path = 'out.png'
png = new PNG({ width: width, height: height })
# THREE.js business starts here
scene = new THREE.Scene()
# camera attributes
VIEW_ANGLE = 45
ASPECT = width / height
NEAR = 0.1
FAR = 100
# set up camera
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR)
scene.add(camera)
camera.position.set(0, 2, 2)
camera.lookAt(scene.position)
# mock object, not used in our test case, might be problematic for some workflow
canvas = new Object()
# The width / height we set here doesn't matter
renderer = new THREE.WebGLRenderer({
antialias: true,
width: 0,
height: 0,
canvas: canvas, # This parameter is usually not specified
context: gl # Use the headless-gl context for drawing offscreen
})
# add some geometry
geometry = new THREE.BoxGeometry( 1, 1, 1 )
# add a material; it has to be a ShaderMaterial with custom shaders for now
# this is a work in progress, some related link / issues / discussions
#
# https://github.com/stackgl/headless-gl/issues/26
# https://github.com/mrdoob/three.js/pull/7136
# https://github.com/mrdoob/three.js/issues/7085
material = new THREE.ShaderMaterial()
vec4 = new THREE.Vector4( 1.0, 0.0, 0.0, 1.0 ) # red
material.vertexShader = '''
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
'''
material.fragmentShader = '''
uniform vec4 solidColor;
void main() {
gl_FragColor = solidColor;
}
'''
material.uniforms = { solidColor: { type: "v4", value: vec4 } }
# Create the mesh and add it to the scene
cube = new THREE.Mesh(geometry, material)
scene.add(cube)
# Let's create a render target object where we'll be rendering
rtTexture = new THREE.WebGLRenderTarget(
width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
})
# render
renderer.render(scene, camera, rtTexture, true)
# read render texture into buffer
gl = renderer.getContext()
# create a pixel buffer of the correct size
pixels = new Uint8Array(4 * width * height)
# read back in the pixel buffer
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
# lines are vertically flipped in the FBO / need to unflip them
for j in [0...height]
for i in [0...width]
k = j * width + i
r = pixels[4*k]
g = pixels[4*k + 1]
b = pixels[4*k + 2]
a = pixels[4*k + 3]
m = (height - j + 1) * width + i
png.data[4*m] = r
png.data[4*m + 1] = g
png.data[4*m + 2] = b
png.data[4*m + 3] = a
# Now write the png to disk
stream = fs.createWriteStream(path)
png.pack().pipe stream
stream.on 'close', () ->
# We're done !!
console.log("Image written: #{ path }")
{
"name": "offscreen-sample",
"version": "1.0.0",
"scripts": {
"start": "coffee offscreen_sample.coffee",
"compile": "coffee -c -b offscreen_sample.coffee && cat offscreen_sample.js"
},
"dependencies": {
"three": "latest",
"pngjs": "latest",
"gl": "latest"
},
"devDependencies": {
"coffee-script": "latest"
}
}

Nice, thanks! This is going to be useful for multiple aspects of ViziCities – visual tests, screenshot generation, video creation. I can think of all sorts of cool uses for being able to render on the server.

Btw, you can output the image like this…

![](http://i.imgur.com/Vq4FnN9.png)

Owner

bsergean commented Oct 19, 2015

Excellent, glad it will be useful for you @robhawkes / right now I'm trying to make an example that is using a texture / I've been trying to make a code base where the browser code is shared with the node code as much as possible. To share the resources (images, shaders, ...) I'm using brfs and browserify. I should try to make some extra gist to show others how I did it.

Thanks for the tip btw on how to embed an image.

ps: I think I used to work with a Kyle ViziCities

Owner

bsergean commented Oct 19, 2015

Published the other gist with texturing here -> https://gist.github.com/bsergean/08be90a2f21205062ccc

It always get below errors:

THREE.WebGLRenderer: TypeError: _canvas.getContext is not a function
TypeError: Cannot read property 'getExtension' of undefined
@ghost

ghost commented Nov 25, 2016

@xiaotian-tan i got same errors!

trakout commented Dec 22, 2016

@xiaotian-tan @jefurry Try using gl version 3.0.6. This fixed it for me (still getting extension warnings, but at least everything works through to rendering). gl versions >= 4.0.0 cause this break.

joeloyj commented Mar 17, 2017

line 101 should be m = (height - j - 1) * width + i

Jontem commented May 30, 2017

@xiaotian-tan @ghost @trakout you need to pass in width and height to the gl creator function: https://gist.github.com/bsergean/6780d7cc0cabb1b4d6c8#file-offscreen_sample-coffee-L5

Otherwise it will return null:
https://github.com/stackgl/headless-gl/blob/master/node_index.js#L19

If the context is null it will try and use a canvas which is not available:
https://github.com/mrdoob/three.js/blob/dev/src/renderers/WebGLRenderer.js#L234

hassaananjum commented Jul 18, 2017

On a linux system, running
xvfb-run -s "-ac -screen 0 1280x1024x24” node_modules/.bin/coffee offscreen_sample.coffee -i test_aliased.png -o out.png
gives me the error "window is not defined". Following is the stack trace:
ReferenceError: window is not defined at new WebVRManager (/home/server/6780d7cc0cabb1b4d6c8/node_modules/three/build/three.js:19882:25) at new WebGLRenderer (/home/server/6780d7cc0cabb1b4d6c8/node_modules/three/build/three.js:20556:12) at Object. (/home/server/6780d7cc0cabb1b4d6c8/offscreen_sample.coffee:34:12) at Object. (/home/server/6780d7cc0cabb1b4d6c8/offscreen_sample.coffee:3:1) at Module._compile (module.js:556:32) at Object.exports.run (/home/server/6780d7cc0cabb1b4d6c8/node_modules/coffee-script/lib/coffee-script/coffee-script.js:173:23) at compileScript (/home/server/6780d7cc0cabb1b4d6c8/node_modules/coffee-script/lib/coffee-script/command.js:224:29) at compilePath (/home/server/6780d7cc0cabb1b4d6c8/node_modules/coffee-script/lib/coffee-script/command.js:174:14) at Object.exports.run (/home/server/6780d7cc0cabb1b4d6c8/node_modules/coffee-script/lib/coffee-script/command.js:98:20) at Object. (/home/server/6780d7cc0cabb1b4d6c8/node_modules/coffee-script/bin/coffee:15:45) at Module._compile (module.js:556:32) at Object.Module._extensions..js (module.js:565:10) at Module.load (module.js:473:32) at tryModuleLoad (module.js:432:12) at Function.Module._load (module.js:424:3) at Module.runMain (module.js:590:10) at run (bootstrap_node.js:394:7) at startup (bootstrap_node.js:149:9) at bootstrap_node.js:509:3

Any idea how to solve this?

kevinw commented Oct 13, 2017

@hassaananjum one quick way to solve the missing window problem is to add this to the top of offscreen_sample.coffee:

global.window = {
    addEventListener: () ->
    removeEventListener: () ->
}

(It seems the newer versions of THREE.js have a web VR component that checks for things on the window object.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment