Three.js: scene snapshot excluding a particular object - three.js

I have a three.js scene. There are some objects including a watermark object. I need to take a scene snapshot but it should not include the watermark object. But at the same time a user should not see the scene without watermark on his screen so he could not take a screenshot.
Is it possible and how?
Thank you!

HERE is a fiddle that shows how to take a screenshot.
HERE is a version that hides the mesh before the screenshot is taken.
Original function.
function takeScreenshot() {
var w = window.open('', '');
w.document.title = "Screenshot";
var img = new Image();
img.src = renderer.domElement.toDataURL();
w.document.body.appendChild(img);
}
Altered function to hide mesh.
function takeScreenshot() {
var w = window.open('', '');
w.document.title = "Screenshot";
var img = new Image();
mesh.visible = false;
renderer.render(scene, camera);
img.src = renderer.domElement.toDataURL();
mesh.visible = true;
w.document.body.appendChild(img);
}
I just set the mesh to visible = false, render the scene to take the screenshot, then set mesh.visible back to true.

Related

How to use Texture.clone in Three.js?

My problem is very basic : I have a texture object, I want to clone it but Texture.clone doesn't seem to work as expected
My code is as basic as my problem :
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 512;
canvas.getContext("2d").fillStyle = "#ff0000";
canvas.getContext("2d").fillRect(0,0,512,512);
var texture = new THREE.Texture(canvas);
texture.needsUpdate= true;
var material = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(100,100), material);
scene.add(mesh)
//please, don't focus on "scene" and the webglrenderer object,
//it's define on the top of my code but it's not important here.
This code works as expected.
BUT if I change the line containing the material definition by
var material = new THREE.MeshBasicMaterial({map:texture.clone() });
nothing appear on the screen !
Why ?!
EDIT : Thanks to "TheJim01" , I realized that I didn't apply the "needsUpdate = true" on my cloned-texture.
With
var cloneTexture = texture.clone();
cloneTexture.needsUpdate = true;
var material = new THREE.MeshBasicMaterial({map:cloneTexture });
Everything works as expected.
Thank you
I haven't dug into the renderer code, so I don't know how it uses this information, but Texture.needsUpdate increments the "version" of the texture. CanvasTexture sets this right away, causing the version value to be 1 on the first render.
Texture.clone doesn't perpetuate the version information, and instead re-calls its constructor. Because you aren't setting needsUpdate after the clone, you are not following the same steps.
// from your code:
var texture = new THREE.Texture(canvas);
texture.needsUpdate= true;
// texture.version === 1
// but...
var material = new THREE.MeshBasicMaterial({ map:texture.clone() });
//material.map.version === 0
So the clone you're passing into the new material has a version of 0, which is apparently no good if you're using a canvas as the source.
This should resolve the issue:
var material = new THREE.MeshBasicMaterial({ map:texture.clone() });
material.map.needsUpdate = true;

Sprite not showing

I have some code at https://jsfiddle.net/72mnd2yt/1/ that doesn't display the sprite I'm trying to draw. I tried to follow the code over at https://stemkoski.github.io/Three.js/Sprite-Text-Labels.html and read it line by line, but I'm not sure where I went wrong. would someone mind taking a look at it?
Here is some relevant code:
// picture
var getPicture = function(message){
var canvas = document.createElement('canvas');
canvas.width = "100%";
canvas.height = "100%";
var context = canvas.getContext('2d');
context.font = "10px";
context.fillText(message, 0, 10);
var picture = canvas.toDataURL();
// return picture;
return canvas;
};
// let there be light and so forth...
var getScene = function(){
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
70,
$('body').width(),
$('body').height(),
1,
1000
);
var light = new THREE.PointLight(0xeeeeee);
scene.add(camera);
scene.add(light);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xefefef);
renderer.setSize($('body').width(), $('body').height());
camera.position.z = -10;
camera.lookAt(new THREE.Vector3(0, 0, 0));
return [scene, renderer, camera];
};
// now for the meat
var getLabel = function(message){
var texture = new THREE.Texture(getPicture(message));
var spriteMaterial = new THREE.SpriteMaterial(
{map: texture }
);
var sprite = new THREE.Sprite(spriteMaterial);
//sprite.scale.set(100, 50, 1.0);
return sprite
};
var setup = function(){
var scene;
var renderer;
var camera;
[scene, renderer, camera] = getScene();
$('body').append(renderer.domElement);
var label = getLabel("Hello, World!");
scene.add(label);
var animate = function(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
};
setup();
A few points:
1) canvas.width = "100%" should be canvas.width = "100" (canvas sizes are assumed to be px). Same with canvas.height.
2) $('body').height() is 0, so the renderer canvas is not visible (you can check this out in dev tools, it's in the element tree but squashed to 0px high). I know nothing about jQuery, so not sure why this is, but I would recommend using window.innerHeight and window.innerWidth instead anyways. So renderer.setSize(window.innerWidth, window.innerHeight). You'll also want to make this change in the camera initialization.
3) Speaking of the camera initialization, you are passing in width and height as separate arguments, when there should only be an aspect ratio argument. See the docs. So
var camera = new THREE.PerspectiveCamera(70, window.innerWidth, window.innerHeight, 1, 1000)
becomes
var camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 1, 1000)
4) Because textures are assumed to be static, you need to add something to this part:
var texture = new THREE.Texture(getPicture(message))
texture.needsUpdate = true // this signals that the texture has changed
That's a one-time flag, so you need to set it every time the canvas changes if you want a dynamic texture. You don't have to make a new THREE.Texture each time, just add texture.needsUpdate in the render loop (or in an event that only fires when you want the texture to change, if you're going for efficiency). See the docs, under .needsUpdate.
At this point, it should work. Here are some further things to consider:
5) Instead of using Texture you could use CanvasTexture, which sets .needsUpdate for you. The fiddle you posted is using Three.js r71, which doesn't have it, but newer versions do. That would look like this:
var texture = new THREE.CanvasTexture(getPicture(message));
// no needsUpdate necessary
6) It looks like you were on this path already based on the commented out return picture, but you can use either canvas element, or a data url generated from the canvas for a texture. If getPicture now returns a data url, try this:
var texture = new THREE.TextureLoader().load(getPicture(message))
You can also indirectly use a data url with Texture:
var img = document.createElement('img')
var img = new Image()
img.src = getPicture(message)
var texture = new THREE.Texture(img);
texture.needsUpdate = true
If not, just stick with Texture or CanvasTexture, both will take a canvas element. Texture can indirectly take a url by passing in an image element whose src is set to the data url.
Fiddle with the outlined fixes:
https://jsfiddle.net/jbjw/x0uL1kbh/

How to convert render from three.js to .png file?

How would I convert a render to a .png image?
I've been looking around for awhile but nothing has worked.
Here is a function I use and a fiddle that shows it working.
function takeScreenshot() {
// For screenshots to work with WebGL renderer, preserveDrawingBuffer should be set to true.
// open in new window like this
var w = window.open('', '');
w.document.title = "Screenshot";
//w.document.body.style.backgroundColor = "red";
var img = new Image();
img.src = renderer.domElement.toDataURL();
w.document.body.appendChild(img);
// download file like this.
//var a = document.createElement('a');
//a.href = renderer.domElement.toDataURL().replace("image/png", "image/octet-stream");
//a.download = 'canvas.png'
//a.click();
}

Three.js: Do I have to set up the anisotropic texture filtering for each texture?

Do I have to set up the anisotropic texture filtering for each texture?
I think it would be cool If you can set the anisotropic level for a material or even for the global scene. Any Ideas?
maxA = renderer.getMaxAnisotropy();
var texture1 = THREE.ImageUtils.loadTexture('models/folder/m67/o1.jpg');
texture1.anisotropy = maxA;
var texture2 = THREE.ImageUtils.loadTexture('models/folder/m67/o2.jpg');
texture2.anisotropy = maxA;
var texture3 = THREE.ImageUtils.loadTexture('models/folder/m67/c1.jpg');
texture3.anisotropy = maxA;
var texture4 = THREE.ImageUtils.loadTexture('models/folder/m67/c2.jpg');
texture4.anisotropy = maxA;
var material = {
"logoprint": new THREE.MeshPhongMaterial({
map: texture1
}),
}
you can wrap the load into a function, imageutils just gives you another utility function
function loadTexture( url ){
var tex = THREE.ImageUtils.loadTexture(url);
tex.anisotropy = MAXANISO;
return tex;
}
To clarify what's happening here, and what to do about the ImageUtils depreciation warning:
var tl = new THREE.TextureLoader();
//calling load on it immediately returns a THREE.Texture object
var myTexture = tl.load( 'name.jpg' );
//you can also pass a callback
function onTextureLoaded( tex ){
//tex.image contains data, because textureLoader updated it, before passing it to the callback
handleTexture( tex )...
}
var myTexture = tl.load( 'name.jpg' , onTextureLoaded );
// myTexture is now an instance of THREE.Texture
// myTexture.image contains no data
myTexture.aniso = ...; //texture is configured , but probably waiting for data
The texture object itself holds all these 3d properties like wrapping, mapping type and filtering. The image data itself has nothing to do with this, it stays the same regardless of how you use it.
When you call load on the texture loader, it gives you back a proxy - a reference to an object that can be configured, manipulated, and passed to other objects (like materials) to be used.
A material can render an empty texture. When the image data is loaded, the texture is updated and the very next frame will reflect that.
var tl = new THREE.TextureLoader()
function myCustomLoad( url ){
var newTex = tl.load(url); //make the proxy
newTex.prop = ... ;//config
return newTex; //return configured texture, it will update itself
}

text in three.js: transparency lost when rotating

I'm trying the second approach to use Text in three.js, drawing on a canvas and using the result as a texture. It basically works, except for the following problem -don't know if it's a bug:
I create two texts, with transparent background, and overlap then. They show ok, but when I rotate one of them, the transparency is messed up.
I create the text objects with following (excerpt) function
function createText(text, ...){
var textHolder = document.createElement( 'canvas' );
var ctext = textHolder.getContext('2d');
...
var tex = new THREE.Texture(textHolder);
var mat = new THREE.MeshBasicMaterial( { map: tex, overdraw: true});
mat.transparent = true;
mat.map.needsUpdate = true;
var textBoard = new THREE.Mesh(new THREE.PlaneGeometry(textHolder.width, textHolder.height),mat);
textBoard.dynamic = true;
textBoard.doubleSided = true;
return textBoard;
}
and add them to the scene.
See demonstration with full code in jsfiddle
Transparency is tricky in webGL.
The best solution in your case is to do the following for your transparent text material:
mat.depthTest = false;
updated fiddle: http://jsfiddle.net/SXA8n/4/
three.js r.55

Resources