Skip to content

Instantly share code, notes, and snippets.

@bsergean bsergean/README.md
Last active Aug 30, 2018

Embed
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"
}
}
@robhawkes

This comment has been minimized.

Copy link

commented Sep 18, 2015

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)

@bsergean

This comment has been minimized.

Copy link
Owner Author

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

@bsergean

This comment has been minimized.

Copy link
Owner Author

commented Oct 19, 2015

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

@xiaotian-tan

This comment has been minimized.

Copy link

commented Nov 18, 2016

It always get below errors:

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

This comment has been minimized.

Copy link

commented Nov 25, 2016

@xiaotian-tan i got same errors!

@trakout

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

commented Mar 17, 2017

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

@Jontem

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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.)

@sid3007

This comment has been minimized.

Copy link

commented Feb 22, 2018

@trakout I did that but it still isn't working for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.