New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to render three.js on serverside? #730

Closed
zettam opened this Issue Mar 6, 2016 · 21 comments

Comments

Projects
None yet
6 participants
@zettam

zettam commented Mar 6, 2016

I was planning to use node-canvas to render out three.js on server side, using the canvas renderer.
I didn't have any successful results so far.

Any help is appreciated.

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Mar 6, 2016

three.js uses the "webgl" canvas context, which isn't supported.

@zettam

This comment has been minimized.

zettam commented Mar 6, 2016

there is a canvas renderer as well

@zettam

This comment has been minimized.

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Mar 6, 2016

Ah, didn't realize that, thanks for the link. Can you post the code you've tried, please?

@zettam

This comment has been minimized.

zettam commented Mar 6, 2016

I've added this section to the begginning of the three.js file (maybe it's not a right thing to modify the source but oh well, I couldn't come up with any other way)

var Canvas = require('canvas');

var self = self || {}; // File:src/Three.js

var canvasWidth = 1024;
var canvasHeight = 1024;

var window = {
    innerWidth: canvasWidth,
    innerHeight: canvasHeight
};

var document = {
    createElement: function(name) {
        if (name == "canvas") {
            return new Canvas(canvasWidth, canvasHeight);
        }
    }
};

now it does not give me any errors when I do this in my app.js

var THREE = require("three");



var scene, camera, renderer;
var geometry, material, mesh;


init();

function init() {

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(75, 1, 1, 10000);
    camera.position.z = 1000;

    geometry = new THREE.BoxGeometry(200, 200, 200);
    material = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        wireframe: true
    });

    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    renderer = new THREE.CanvasRenderer();
    renderer.setSize(200, 200);

    renderer.render(scene, camera);

    //document.body.appendChild(renderer.domElement);
}

However, this is not rendering anything as you may expect.
What do you think?

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Mar 7, 2016

Started to work on your code, but turns out someone already made a node module wrapping three.js that is backed by canvas: https://github.com/nulltask/node-three.js (and http://stackoverflow.com/questions/19162510/how-do-i-render-three-js-in-node-js). Does that help?

Also came across some discussion here: mrdoob/three.js#7085

@zettam

This comment has been minimized.

zettam commented Mar 7, 2016

I've came across these three links, they're mostly out of date (or there is another problem which does not let things work).

Maybe I'm doing some mistake ~

Would love to have an up to date solution for two reasons, the main one being "to understand the logic of the fix" and the secondary one being "to have a more futureproof solution".

So if you can help me with this I'll be happy.

@zettam

This comment has been minimized.

zettam commented Mar 7, 2016

https://www.npmjs.com/package/three.js also, fyi - this module is deprecated.

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Mar 13, 2016

Sorry for the delay. The below seems to work, although CanvasRenderer lacks some WebGL features, which I think causes the geometry to render incorrectly (note that the plane below the cube intersects the cube).

Required three.js files are these -- CanvasRenderer was moved out of core three.js and it requires the projector file:
https://github.com/mrdoob/three.js/blob/master/build/three.js
https://github.com/mrdoob/three.js/blob/master/examples/js/renderers/CanvasRenderer.js
https://github.com/mrdoob/three.js/blob/master/examples/js/renderers/Projector.js

// Assign this to global so that the subsequent modules can extend it:
global.THREE = require("./three.js");
require("./three-canvasrenderer.js");
require("./three-projector.js");

var fs = require("fs");
var Canvas = require("canvas");

var w = 200;
var h = 200;

var scene = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(70, 1, 1, 10000);
camera.position.y = 150;
camera.position.z = 400;

var geometry = new THREE.BoxGeometry(200, 200, 200);
for ( var i = 0; i < geometry.faces.length; i += 2 ) {
    var hex = Math.random() * 0xffffff;
    geometry.faces[ i ].color.setHex( hex );
    geometry.faces[ i + 1 ].color.setHex( hex );
}

var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, overdraw: 0.5 } );

cube = new THREE.Mesh(geometry, material);
cube.position.y = 150;
cube.rotation.y = 45;
scene.add(cube);

var geometry = new THREE.PlaneBufferGeometry( 200, 200 );
geometry.rotateX( - Math.PI / 2 );

var material = new THREE.MeshBasicMaterial( { color: 0xe0e0e0, overdraw: 0.5 } );

plane = new THREE.Mesh( geometry, material );
scene.add( plane );

var canvas = new Canvas(w, h);
canvas.style = {}; // dummy shim to prevent errors during render.setSize
var renderer = new THREE.CanvasRenderer({
    canvas: canvas
});

renderer.setClearColor(0xffffff, 0);
renderer.setSize(200, 200);

renderer.render(scene, camera);

var out = fs.createWriteStream("./test-out.png");
var canvasStream = canvas.pngStream();
canvasStream.on("data", function (chunk) { out.write(chunk); });
canvasStream.on("end", function () { console.log("done"); });

test-out

@zettam

This comment has been minimized.

zettam commented Mar 19, 2016

Hm, there is already a three package
npm install three
would it be possible to make this work with it?

Thanks for the effort!

@zettam

This comment has been minimized.

zettam commented Mar 19, 2016

Error: Cannot find module './three-canvasrenderer.js'

I have all the files in the same directory with my app.js

@zettam

This comment has been minimized.

zettam commented Mar 19, 2016

Oh nevermind, looks like it worked, my bad, that I didn't replace the file names to match your script!

Thank you!

@zettam

This comment has been minimized.

zettam commented Mar 19, 2016

One final issue/question.
It's normally possible to load textures and render them via canvas renderer.
When I try to do the following:
THREE.TextureLoader().load( 'gradient.png' )
I get the following error:
ReferenceError: document is not defined

Since, earlier it was all about the "document is not defined" error (the reason I was defining document in my earlier post), and since you fixed it without making that definition anywhere, I thought maybe it would be possible to solve this somehow.

Any ideas?

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Mar 19, 2016

I haven't tested this, but I think the following will work:

global.document = {
  createElement: function (tag) {
    if (tag === "img") {
      return new Canvas.Image();
    }
  }
};

If you find more missing APIs, you might want to use node-canvas via jsdom, as that provides the document element.

@makc

This comment has been minimized.

makc commented May 1, 2016

@zbjornson much thanks for your sample code here, it appears renderer.setSize(200, 200) is critical for this to work, even if you pass the canvas that is already 200x200, otherwise the png is blank (which is the issue I was facing).

p.s. you can do renderer.setSize(200, 200, false); to skip style.xxx assignments

@makc makc referenced this issue Jan 23, 2017

Closed

Loading a texture in THREE.js using Node #10616

2 of 12 tasks complete
@dbkaplun

This comment has been minimized.

dbkaplun commented May 15, 2018

I've taken @zbjornson's idea and turned it into an NPM module! Thanks @zbjornson!

import { toPNG } from 'node-three-screenshot';

fs.writeFileSync('out.png', toPNG(scene));

screenshot

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented May 15, 2018

Awesome! :)

@zbjornson zbjornson closed this May 15, 2018

@robert-cooper-developer

This comment has been minimized.

robert-cooper-developer commented Aug 6, 2018

I know this is kinda late but...

/var/www/render/node_modules/three/build/three.js:32184
                    image.addEventListener( 'load', onImageLoad, false );
                          ^
TypeError: image.addEventListener is not a function

Getting that error when I try to use new Canvas.Image as the document images.

P.S. I'm trying to use this with OBJLoader and MTLLoader to render stuff on my server. Got everything working but the texture will not load for some reason.

@makc

This comment has been minimized.

makc commented Aug 6, 2018

I used to load it by hand:

  fs.readFile(path, function (error, data) {
    if (error) throw error;

    var image = new Image;
    image.src = data;

    var texture = new THREE.Texture(image);
    texture.needsUpdate = true;

it is probably possible to patch things to make 3js loaders work, but I did not care to do it.

@zbjornson

This comment has been minimized.

Collaborator

zbjornson commented Aug 6, 2018

Just opened #1218 for that issue.

@frankleonrose

This comment has been minimized.

frankleonrose commented Aug 23, 2018

@robert-cooper-developer Note monkey-patch in node-three.js lib at https://github.com/nulltask/node-three.js/blob/master/lib/three.js#L21 to add addEventListener to Image.

@zbjornson zbjornson added the Question label Oct 27, 2018

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