Three.Js draw linear gradient texture in webgl - three.js

With canvas renderer, I am using a function that draws a linear gradient. I would like this to work with the webgl renderer as well, but it chokes on transparency. The code is below and here is a link to a fiddle which demonstrates what I mean.
function generateTexture() {
var size = 512;
// create canvas
canvas = document.createElement( 'canvas' );
canvas.width = size;
canvas.height = size;
// get context
var context = canvas.getContext( '2d' );
// draw gradient
context.rect( 0, 0, size, size );
var gradient = context.createLinearGradient( 0, 0, size, size );
gradient.addColorStop(0, '#99ddff'); // light blue
gradient.addColorStop(1, 'transparent');
context.fillStyle = gradient;
context.fill();
return canvas;
}

For WebGLRenderer, you need to set material.transparent = true.
var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true } );
Updated fiddle: http://jsfiddle.net/FtML5/3/
three.js r.62

Related

three.js sprites seem to ignore parent's scale when using a PerspectiveCamera

I'm trying to have text sprites in the 3d scene with constant size (regardless of camera distance) using a PerspectiveCamera. In order to get non-sprites to have constant size, I make them children of a special "scaled" object which adjusts its scale as the camera distance to origin changes (see the code below). This works well to keep a general object roughly the same visual size, but when I add a sprite to the scaled object, the sprite seems to ignore its parent's scale (so it gets smaller and bigger as you zoom out and in).
Interestingly, when we switch to an orthographic camera (uncomment the appropriate line below), this special scaled object doesn't seem to affect children anymore (i.e., children don't stay a constant size). However, since we have an orthographic camera, sprites no longer scale as the camera distance changes (so they maintain a constant size), but this is independent of the scaled object.
I notice a few other similar questions and answers, including adjust the scale of the sprites themselves (it seems much easier to add all my sprites to a single scaling object), use an orthographic camera overlay to draw sprites (see also this) (but I want my sprites to be inside the 3d perspective scene).
So, my questions are: why do sprites not use scale according to their parent's scale when using a PerspectiveCamera? Also, why does my scaled object not work with the orthographic camera? Are these bugs or features of the cameras?
Thanks!
http://jsfiddle.net/LLbcs/8/
var camera, scene, renderer, geometry, material, mesh, text, controls;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000); var scenescale=1;
//camera = new THREE.OrthographicCamera( -7,7,7,-7, 1, 20 );
camera.position.z = 10;
scene.add(camera);
scaled=new THREE.Object3D();
scene.add(scaled);
var textmaterial = new THREE.SpriteMaterial( {color: 'red', useScreenCoordinates: true, map: texttexture("hi")});
text = new THREE.Sprite( textmaterial );
text.position.set( 1, 1, 0);
scaled.add(text);
var geometry = new THREE.BoxGeometry( 1, 1,1 );
var material = new THREE.MeshLambertMaterial( { color: 0xffffff } );
mesh = new THREE.Mesh( geometry, material );
mesh.position.set(0,3,0);
scaled.add(mesh);
var light = new THREE.PointLight('green');
light.position.set(10,15,10);
camera.add(light);
light = new THREE.PointLight(0x333333);
light.position.set(-10,-15,-8);
camera.add(light);
renderer = new THREE.WebGLRenderer();
controls = new THREE.OrbitControls( camera, renderer.domElement );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
var scale = camera.position.length()/10;
scaled.scale.set(scale,scale,scale);
render();
}
function render() {
renderer.render(scene, camera);
}
function texttexture(string) {
var fontFace = "Arial"
var size = "50";
var color = "white"
var squareTexture = true;
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.height = size;
var font = "Normal " + size + "px " + fontFace;
context.font = font;
var metrics = context.measureText(string);
var textWidth = metrics.width;
canvas.width = textWidth;
if (squareTexture) {
canvas.height = canvas.width;
}
var aspect = canvas.width / canvas.height;
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = color;
// Must set the font again for the fillText call
context.font = font;
context.fillText(string, canvas.width / 2, canvas.height / 2);
var t = new THREE.Texture(canvas);
t.needsUpdate = true;
return t;
}
If you want text to appear over a 3D scene and you don't care if it is static, why not try layering a div over the scene instead?
This will allow you to save graphics bandwidth and memory, improving performance of your scene and give you much better flexibility over what you display. It's also much easier to do and to maintain.

Webgl 2d circle as sprite

I am brand new to three.js and am having some difficultly creating a simple 2d circle sprite in Webgl.
I am able to do this easily with the canvas renderer:
var PI2 = Math.PI * 2;
var material = new THREE.SpriteCanvasMaterial( {
color: 0xffffff,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 2, 0, PI2, true );
context.fill();
}
});
particle = new THREE.Sprite( material );
Is their an easy way to achieve the same effect in Webgl? When reading through the documentation, I only found support for sprites as images in Webgl.
Thanks!
Here is the pattern to follow:
function generateTexture() {
var canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
var context = canvas.getContext( '2d' );
< insert drawing code here >
return canvas;
}
...
var texture = new THREE.Texture( generateTexture() );
texture.needsUpdate = true; // important!
var material = new THREE.SpriteMaterial( { map: texture } );
sprite = new THREE.Sprite( material );
three.js r.66

threejs merge plane geometries or create a texture from multiple images

I have a set of plane geometries, each having a their own texture from a url. At any point during navigation(zoom/pan) the container (THREE.Object3D) contains 26 plane geometries. How will i merge them to a single big plane so that i could apply a heighmap for all tiles in the merge geometry.
Or How could I get all the texture from the 36 images.(currently as a map property for MeshPhongMaterial) in to a single geometry?
EDIT:
Currently I create a Big geometry as suggested by Dainel. and put a texture to the geometry which is a combined texture of a set of images via canvas.
context = canvas.getContext( '2d' );
var img = Image();
img.src = url //url of the texture tile
context.drawImage(img,xpos, ypos);
This is done or every images. Each image have a url, xpos, ypos. Images are preloaded and after loading every images a callback is called which creates the geometry(plane) and add texture from the canvas.
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var material = new THREE.MeshPhongMaterial({ map : texture });
var geom = new THREE.PlaneGeometry(canvas.width, canvas.height, 57, 40);
var mesh = new THREE.Mesh(geom, material);
scene.add( mesh );
also i update the z vertex of geometry with the height values.
u may create one "big" plane and apply a merged texture to it
or u could manually set the vertex Y-values of each plane to match the corresponding part of the height map. so u have a set of separate planes that all share the same "base" height map.
regards,
daniel
Using terrainLoader class for threejs and canvas texture I was able to acheive
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, mywidth / myheight, 0.01, 10000);
camera.position.set(0, 0, 240);
scene.add(new THREE.AmbientLight(0xeeeeee));
renderer = new THREE.WebGLRenderer();
renderer.setSize(mywidth, myheight);
var group = new THREE.Object3D();
var canvas = document.createElement( 'canvas' );
canvas.width = mywidth;
canvas.height = myheight;
var ctx = canvas.getContext('2d');
/* adjust width, height, segments based on height data;*/
/* create a plane geometry */
var geom = new THREE.PlaneGeometry(800, 692, 40, 55);
/*
read height values from .bin file uing terrainLoader
and update the Z values of the plane
*/
heightdata = 'assets/heightmap.bin';
terrainLoader.load(heightdata, function(data) {
/* bin file generated from USGS SRTM tile */
for (var i = 0, l = geom.vertices.length ; i < l; i++) {
geom.vertices[i].z = data[i] / 200; /*simple scale */
}
geom.verticesNeedUpdate = true;
});
/*
create a texture from canvas created above with width and height
of the of the scene. This is to be careful as the size of texture
image may not be the same but as same ratio as height map.
Atleast for my case it was the situation. But geometry width and
texture width are in same ratio. So that i need a small array of
heightmap from bin but a much bigger texture
*/
var texture = new THREE.Texture(canvas);
var material = new THREE.MeshPhongMaterial({ map : texture });
var mesh = new THREE.Mesh(geom, material);
group.add( mesh );
scene.add(group);
Now to draw multiple images to canvas check this page
I will just duplicate the code from HTML5 Canvas Image Loader Tutorial
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg'
};
You can call the loadImages function after updating the Plane geometry from heightmap in above code.
loadImages(sources, function(images) {
//context = context of the texture put in threejs texture
context.drawImage(images.darthVader, 100, 30, 200, 137);
context.drawImage(images.yoda, 350, 55, 93, 104);
});

ThreeJS Texture is pixelated when seen from distance

I was playing with webGL and ThreeJS, then I've got the following issue:
Textures with large images gets pixelated when seen from distance.
Check the example: http://jsfiddle.net/4qTR3/1/
Below is the code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 10, 7000);
var light = new THREE.PointLight(0xffffff);
light.position.set(0, 150, 100);
scene.add(light);
var light2 = new THREE.AmbientLight(0x444444);
scene.add(light2);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.PlaneGeometry(500, 500, 10, 10);
//I use different textures in my project
var texture = new THREE.ImageUtils.loadTexture(TEST_IMAGE);
var textureBack = new THREE.ImageUtils.loadTexture(TEST_IMAGE);
textureBack.anisotropy = renderer.getMaxAnisotropy();
texture.anisotropy = renderer.getMaxAnisotropy();
//Filters
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
textureBack.magFilter = THREE.NearestFilter;
textureBack.minFilter = THREE.LinearMipMapLinearFilter;
var materials = [
new THREE.MeshLambertMaterial({
transparent: true,
map: texture,
side: THREE.FrontSide
}),
new THREE.MeshLambertMaterial({
transparent: true,
map: textureBack,
side: THREE.BackSide
})];
for (var i = 0, len = geometry.faces.length; i < len; i++) {
var face = geometry.faces[i].clone();
face.materialIndex = 1;
geometry.faces.push(face);
geometry.faceVertexUvs[0].push(geometry.faceVertexUvs[0][i].slice(0));
}
planeObject = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
planeObject.overdraw = true;
planeObject.position.z = -5000;
scene.add(planeObject);
camera.position.z = 1000;
(function render() {
requestAnimationFrame(render);
planeObject.rotation.y += 0.02;
renderer.render(scene, camera);
})();
If the image of the texture has got text in it, the text becomes very pixelated with poor quality.
How can I fix this?
In order to not get pixelated you need to use mips but WebGL can't generate mips for non-power-of-2 textures. Your texture is 800x533, neither of those is a power of 2.
a couple of options
1) Scale the picture offline to powers of 2 like 512x512 or 1024x512
2) Scale the picture at runtime before making a texture.
Load the image yourself, once loaded make a canvas that is power-of-2. call drawImage(img, 0, 0, canvas.width, canvas.height) to scale the image into the canvas. Then load the canvas into a texture.
You also probably want to change your mag filtering from NearestFilter to LinearFilter.
Note: (1) is the better option. (2) takes time on the user's machine, uses more memory, and you have no guarantee what the quality of the scaling will be.
Example here.

Three.js Rotate Texture

I have a texture applied to a mesh I can change the offset with
mesh.material.map.offset.set
I can change the scaling with
mesh.material.repeat.set
so my question is, how can I rotate texture inside a plane?
Example:
From This:
To this
Thanks.
use 2D canvas as a texture
demo:
https://dl.dropboxusercontent.com/u/1236764/temp/stackoverflow_20130525/index.html
example code
var camera, scene, renderer, mesh;
var width = window.innerWidth;
var height = window.innerHeight;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 30, width / height, 1, 1000 );
camera.position.z = 100;
renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );
var img = new Image();
img.onload = createMeshThenRender;
img.src = 'img.jpg';
function createMeshThenRender () {
var imgWidth = imgHeight = 256;
var mapCanvas = document.createElement( 'canvas' );
mapCanvas.width = mapCanvas.height = 256;
// document.body.appendChild( mapCanvas );
var ctx = mapCanvas.getContext( '2d' );
ctx.translate( imgWidth / 2, imgHeight / 2 );
ctx.rotate( Math.PI / 4 );
ctx.translate( -imgWidth / 2, -imgHeight / 2 );
ctx.drawImage( img, 0, 0, imgWidth, imgHeight );
var texture = new THREE.Texture( mapCanvas );
texture.needsUpdate = true;
mesh = new THREE.Mesh(
new THREE.PlaneGeometry( 50, 50, 1, 1 ),
new THREE.MeshBasicMaterial( {
map : texture
} )
);
scene.add( mesh );
renderer.render( scene, camera );
}
three.js does not have a UV editing utility, so you either have to edit the geometry.faceVertexUvs manually, or rotate your image in an image editing program. I'd suggest the latter.
three.js r.58
three.js r85
For those looking to actually "rotate UVs" on a Plane sitting in XY plane (default plane) using a ShapeBufferGeometry or PlaneBufferGeometry.
var planeGeo = new THREE.PlaneBufferGeometry(24,24);
var planeMesh = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({map: yourTexture}));
scene.add(planeMesh);
rotateUVonPlanarBufferGeometry(45, planeMesh);
function rotateUVonPlanarBufferGeometry(rotateInDeg, mesh){
if(rotateInDeg != undefined && mesh){
var degreeInRad = THREE.Math.degToRad(rotateInDeg);
var tempGeo = mesh.geometry.clone();
var geo;
if(tempGeo instanceof THREE.BufferGeometry){
geo = new THREE.Geometry().fromBufferGeometry(tempGeo);
}else{
console.log('regular geometry currently not supported in this method, but can be if code is modified, so use a buffer geometry');
return;
}
// rotate the geo on Z-axis
// which will rotate the vertices accordingly
geo.applyMatrix(new THREE.Matrix4().makeRotationZ(degreeInRad));
// loop through the vertices which should now have been rotated
// change the values of UVs based on the new rotated vertices
var index = 0;
geo.vertices.forEach(function(v){
mesh.geometry.attributes.uv.setXY( index, v.x, v.y );
index++;
});
mesh.geometry.attributes.uv.needsUpdate = true;
}
}
three.js r121
In the newer version of the three.js, you can directly set rotation and rotation center of texture.
var texture = new THREE.Texture( ... );
texture.rotation = Math.PI/4;
texture.center = new Vector2d(0.5, 0.5); // center of texture.
The above solutions were not good to me, a little bit old. That's simple solution worked for me (Three.js 125)
imgData = canvasCtx.getImageData(0, 0, canvasElement.width, canvasElement.height);
texture = new THREE.DataTexture( imgData.data, canvasElement.width, canvasElement.height, THREE.SRGB );
texture.rotation = Math.PI;
texture.center = new THREE.Vector2(0.5, 0.5); // center of texture.
mymaterial = new THREE.MeshBasicMaterial({
map: texture,
alphaTest: 0.5,
transparent: true,
side: THREE.DoubleSide,
});

Resources