Dynamic text or texture on curved object - three.js

I'm a newcomer to three.js and am looking for what approaches are possible to achieve an effect like this:
For a cola can like object as in the image below (minus condensation), I want to change independent bits of text on the surface of the can based on user interaction. The variants of text are fairly arbitrary, too many for pre-baked full can textures. For instance I might want to:
change "Euro 2012" to arbitrary text
change the nutritional stats on the back of the can
show or hide one of the individual music notes
I'm sure it's possible, just looking for what concepts I need to employ. Is it difficult to have multiple textures on the same object? Or to generate arbitrary text and position it on an object and wrap it to the shape of the object?
Any pointers helpful!

You can use image created in a separate canvas as a Three.js texture. Instead of trying to mix and blend multiple textures in Three.js (possible, but tricky and limited control), I think the best solution would be to create the dynamic texture in 2D, totally out of Three.js then just apply the full texture to the can.
You can create your canvas image manually or using canvas image manipulation library of your choice (some possibilities: https://docs.google.com/spreadsheet/ccc?key=0Aqj_mVmuz3Y8dHNhUVFDYlRaaXlyX0xYSTVnalV5ZlE#gid=0 ). Or you can have your template as SVG and modify that (should be quite simple), render that to canvas, then use it as texture.
Using canvas as a texture is very simple:
var canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
var context = canvas.getContext('2d');
// drawing something here....
context.font = "Bold 20px Helvetica";
context.lineWidth = 4;
context.strokeStyle = 'rgba(255,255,255,.8)';
context.fillStyle = "rgba(0,0,0,1)";
context.strokeText("Testing", 4, 22);
context.fillText("Testing", 4, 22);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;

Related

How to scale a camera?

I'm trying to make a 3D scene where you can walk around the world, but my objects currently look way too small, and my camera's perspective is like from a Godzilla's. I tried to scale the camera on XYZ axis, but the view got distorted, also FOV doesn't help too.
Is there a solution to this? The only option I could think of now is to scale all of the objects instead. The problem is, there are over 100 objects, and I would have to change their parameters individually.
You can scale the scene
scene.scale.x = scene.scale.y = scene.scale.z = 10;
Or add objects to a group and scale that group:
const group = new THREE.Group();
// I guess that you're using some kind of loop for adding your objects?
group.add(objectA);
group.add(ObjectB);
// scale the group
group.scale.x = group.scale.y = group.scale.z = 10;
scene.add(group);

SpriteMaterial without sizeAttenuation in Three.js

i need a (nameplate) sprite without sizeAttenuation in Three.js and it should looks like this example:
//load image with 256x128px 32bit png
var spriteMap = new THREE.TextureLoader().load("tex/plates/mytest.png");
var spriteMat = new THREE.SpriteMaterial({map:spriteMap, transparent:true, sizeAttenuation:false});
var sprite = new THREE.Sprite(spriteMat);
...
spriteMat.map.minFilter = THREE.NearestFilter;
The result is not blurry, but its not correctly and hav some artefacts what i dont want:
Here is my second try with (THREE.LinearFilter), also not what i want cos its too blurry:
Renderer size: 1200x800px (not full window)
So, i dont know if its a problem with the filter, position or scaling. The first example seems like a bitmap text, but i really dont know how i can solve this problem. I also dont need a 2d sprite in screenspace. I need the nameplate in 3d worldspace behind other objects. I have really no idea how i can solve this, pls anyone can help me with this problem?

Dynamic change a lot of textures is very low performance

I am a newbie in Three js. In my project, I need draw a ground with a lot of texture. The ground has many layers, every layer has 4 textures and textures in different layers are different size. Below picture describe the ground:
Ground is one mesh has multiple material:
this.mesh = new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
Suppose I have a car it always at center of ground, in other word, center of all layers. When it move, the ground will translate to make sure the car always at center. So everytime ground translate, I need to update texture in new position.
The picture draw 3 layers for illustration, but in my project is 6 layers. So everytime all texture change, that means need to change 6 * 4 = 24 textures, and that cause low fps in my program.
This is my function to load texture from indexed DB every time a texture change:
Ground.prototype.loadTextureFromIndexedDB = function (url, materialIndex) {
var loader = new THREE.TextureLoader();
loader.crossOrigin = '';
loader.load(url,
function (texture) {
var groundMaterial = ground.mesh.material.materials[materialIndex];
groundMaterial.map.dispose();
groundMaterial.map = texture;
groundMaterial.map.anisotropy = ground.maxAnisotropy;
groundMaterial.map.minFilter = THREE.LinearFilter;
groundMaterial.map.needsUpdate = true;
img = null;
window.URL.revokeObjectURL(url);
});
}
I have tried many solutions. One of them is make a mesh with a BufferGeometry and MultiMaterial with array of ShaderMaterial. As what I known, it is the best for performance in THREE JS, isn't it? If it is then maybe THREE JS is not powerful as I thinked. Should I change to another API for my project?
Anyone suggest me any solution to make higher performance in my program? Thanks a lot!

three.js - Objects farther away from camera gets jagged textures

I'm struggeling with textures on objects that are a bit farther back in the scene. The textures become very jagged, and creates a disturbing effect as the camera moves. I've tried changing the anisotropy, and I've tried changing the min and mag filters, but nothing seems to help at all.
Code I'm using to load textures (all textures are 1024px by 1024px):
var texture = new THREE.Texture();
var texloader = new THREE.ImageLoader(manager);
texloader.load('static/3d/' + name + '.jpg', function (image) {
texture.image = image;
texture.needsUpdate = true;
texture.anisotropy = 1;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearMipmapLinearFilter;
});
You can see it in action here: http://www.90595.websys.sysedata.no/
gaitat is wrong, you do want the mipmaps.
The problem with your code is that they are not generated.
Using the console, I found that while "generateMipmaps" in your textures is set to "true", mipmaps are not generated, as seen in this screenshot: http://imgur.com/hAUEaur.
I looked at your textures, and I believe the mipmaps weren't generated due to your textures not being a power of 2 (e.g. 128x128, 256x256, 512x512). Try making your textures of width and height that are powers of 2 and I think the mipmaps will be generated and they won't look jagged anymore.
As objects move further away from the camera webgl uses textures automatically generated called mipmaps. These are of lower resolution. If you don't like them disable them by:
texture.generateMipmaps = false;
Okay. So I thought I'd tried all the different mipmap filters, but apparently no. So this is what ended up doing the trick:
texture.minFilter = THREE.NearestMipMapNearestFilter;
texture.magFilter = THREE.LinearMipMapLinearFilter;
Didn't need the anisotropy at all.

Drawing UI elements directly to the WebGL area with Three.js

In Three.js, is it possible to draw directly to the WebGL area (for a heads-up display or UI elements, for example) the way you could with a regular HTML5 canvas element?
If so, how can you get the context and what drawing commands are available?
If not, is there another way to accomplish this, through other Three.js or WebGL-specific drawing commands that would cooperate with Three.js?
My backup plan is to use HTML divs as overlays, but I think there should be a better solution.
Thanks!
You can't draw directly to the WebGL canvas in the same way you do with with regular canvas. However, there are other methods, e.g.
Draw to a hidden 2D canvas as usual and transfer that to WebGL by using it as a texture to a quad
Draw images using texture mapped quads (e.g. frames of your health box)
Draw paths (and shapes) by putting their vertices to a VBO and draw that with the appropriate polygon type
Draw text by using a bitmap font (basically textured quads) or real geometry (three.js has examples and helpers for this)
Using these usually means setting up a an orthographic camera.
However, all this is quite a bit of work and e.g. drawing text with real geometry can be expensive. If you can make do with HTML divs with CSS styling, you should use them as it's very quick to set up. Also, drawing over the WebGL canvas, perhaps using transparency, should be a strong hint to the browser to GPU accelerate its div drawing if it doesn't already accelerate everything.
Also remember that you can achieve quite much with CSS3, e.g. rounded corners, alpha transparency, even 3d perspective transformations as demonstrated by Anton's link in the question's comment.
I had exactly the same issue. I was trying to create a HUD (Head-up display) without DOM and I ended up creating this solution:
I created a separate scene with orthographic camera.
I created a canvas element and used 2D drawing primitives to render my graphics.
Then I created an plane fitting the whole screen and used 2D canvas element as a texture.
I rendered that secondary scene on top of the original scene
That's how the HUD code looks like:
// We will use 2D canvas element to render our HUD.
var hudCanvas = document.createElement('canvas');
// Again, set dimensions to fit the screen.
hudCanvas.width = width;
hudCanvas.height = height;
// Get 2D context and draw something supercool.
var hudBitmap = hudCanvas.getContext('2d');
hudBitmap.font = "Normal 40px Arial";
hudBitmap.textAlign = 'center';
hudBitmap.fillStyle = "rgba(245,245,245,0.75)";
hudBitmap.fillText('Initializing...', width / 2, height / 2);
// Create the camera and set the viewport to match the screen dimensions.
var cameraHUD = new THREE.OrthographicCamera(-width/2, width/2, height/2, -height/2, 0, 30 );
// Create also a custom scene for HUD.
sceneHUD = new THREE.Scene();
// Create texture from rendered graphics.
var hudTexture = new THREE.Texture(hudCanvas)
hudTexture.needsUpdate = true;
// Create HUD material.
var material = new THREE.MeshBasicMaterial( {map: hudTexture} );
material.transparent = true;
// Create plane to render the HUD. This plane fill the whole screen.
var planeGeometry = new THREE.PlaneGeometry( width, height );
var plane = new THREE.Mesh( planeGeometry, material );
sceneHUD.add( plane );
And that's what I added to my render loop:
// Render HUD on top of the scene.
renderer.render(sceneHUD, cameraHUD);
You can play with the full source code here:
http://codepen.io/jaamo/pen/MaOGZV
And read more about the implementation on my blog:
http://www.evermade.fi/pure-three-js-hud/

Resources