Suppose I have 10 objects and I want for each object to be given a texture picked for me randomly out of a pool of 10 textures. How do I go about that for this mesh object?
for(var int = 0; int <= 10 ; int++)
{ var loader = new THREE.TextureLoader();
var testMat = new THREE.MeshPhongMaterial({ map: loader.load('images/image1') });
var testGeo = new THREE.SphereGeometry(50, 50, 50);
testSphere = new THREE.Mesh(testGeo, testMat);
testSphere.position.set(distance, 100, 100);
scene.add(testSphere); }
Assuming all of your texture-images are named in a numeric/sequential manner, you could do:
...
var testMat = new THREE.MeshPhongMaterial({ map: loader.load('images/image' + THREE.Math.randInt(1, 10) ) });
...
If not, then you'd make a list of filenames in a manner similar to this, and choose a random value from the list:
var texturesList = [
'images/image1',
'images/some-other-image',
'images/yet-another-image',
...
'images/10th-image'
];
...
...
var randIndex = THREE.Math.randInt(0, texturesList.length - 1);
var randTexture = loader.load(texturesList[randIndex]);
var testMat = new THREE.MeshPhongMaterial({ map: randTexture });
...
Today I've been experimenting with building my first ever skybox in three.js. I've read a lot of tutorials and the code I've ended up with is based on this one: http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/
I did make a few changes in order to allow for the images to load first, and to make it compatible with the version of three.js which I am using.
I've overcome a lot of small problems to get to the point I am currently at, but cannot find any answer to my current issue despite having searched quite hard. My problem is that despite using purpose-built skybox textures downloaded from the internet, it is glaringly obvious that my skybox is a cube with corners and edges. The textures appear heavily distorted and are not at all convincing.
Here is a screenshot of how my skybox looks:
And here is a link to the site from which I downloaded the images:
http://www.humus.name/index.php?page=Cubemap&item=Yokohama3
As you can see, in their preview it looks much better.
I've tried this with a few different downloaded textures and every time it is very obvious that you are looking at the inside of a cube.
Here's my code (I'm including all my code, not just the section which creates the skybox):
var scene;
var camera;
var renderer;
function createRenderer () {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMapEnabled = true;
//renderer.shadowCameraNear = 0.5;
//renderer.shadowCameraFar = 500;
}
function createCamera () {
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth/window.innerHeight,
0.1, 1000
);
camera.position.x = 50;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(scene.position);
}
function createPlane () {
var material = new THREE.MeshLambertMaterial({
color: 0xcccccc,
})
var geometry = new THREE.PlaneGeometry(40, 40)
var plane = new THREE.Mesh(geometry, material)
plane.receiveShadow = true;
plane.rotation.x = -Math.PI/2
plane.position.y = -6;
scene.add(plane)
}
function createLight () {
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set( 0, 50, 20 );
spotLight.shadowCameraVisible = true;
spotLight.shadowDarkness = 0.5
spotLight.shadowCameraNear = 0;
spotLight.shadowCameraFar = 100;
spotLight.shadowCameraLeft = -50;
spotLight.shadowCameraRight = 50;
spotLight.shadowCameraTop = 50;
spotLight.shadowCameraBottom = -50;
spotLight.castShadow = true;
scene.add(spotLight);
}
function createSkyboxAndSphere () {
var urlPrefix = "Yokohama3/";
var urls = [ urlPrefix + "posx.jpg", urlPrefix + "negx.jpg",
urlPrefix + "posy.jpg", urlPrefix + "negy.jpg",
urlPrefix + "posz.jpg", urlPrefix + "negz.jpg" ];
var textureCube = THREE.ImageUtils.loadTextureCube( urls , undefined, function () {;
var shader = THREE.ShaderLib["cube"];
var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
shader.uniforms['tCube'].value = textureCube; // textureCube has been init before
var material = new THREE.ShaderMaterial({
fragmentShader : shader.fragmentShader,
vertexShader : shader.vertexShader,
uniforms : shader.uniforms,
depthWrite : false,
side: THREE.BackSide,
});
var geometry = new THREE.BoxGeometry(100, 100, 100)
var skybox = new THREE.Mesh(geometry, material)
scene.add(skybox)
var material = new THREE.MeshPhongMaterial({
color: "red",
envMap: textureCube,
reflectivity: 0.3,
})
var geometry = new THREE.SphereGeometry(6, 30, 15)
var sphere = new THREE.Mesh(geometry, material)
sphere.castShadow = true;
sphere.receiveShadow = true;
scene.add(sphere)
});
}
function init () {
scene = new THREE.Scene();
createRenderer();
createCamera();
createLight();
createPlane ();
createSkyboxAndSphere ();
document.getElementById("container").appendChild(renderer.domElement)
render ()
}
function render () {
renderer.render(scene, camera)
requestAnimationFrame(render);
}
window.onload = function () {
init ();
}
I suspect I am fundamentally misunderstanding something about how cubemapping and skyboxes work - I am very new to this in particular and javascript in general and am aware of huge gaps in my knowledge.
My apologies if the answer to this is obvious and/or the question has been asked before, and a pre-emptive thanks for your help!
Your camera needs to be in the center of the skybox -- or at least near the center.
So either move your camera very close to the box center, or update the box position every frame to match the camera position.
Or make the skybox much bigger relative to the camera offset from the origin.
Or place the skybox in a separate scene and have two cameras and two render passes, as in this example.
three.js r.74
I am adding a plane and a grid using Gridhelper to my 3d scene:
// PLANE XY static
var gridplaneSize = 20;
var color = 0xFFDCBB;
var plGeometry = new THREE.PlaneGeometry(gridplaneSize, gridplaneSize);
var plMaterial = new THREE.MeshBasicMaterial( {color:color, ambient:color, side:THREE.DoubleSide, opacity:0.5, transparent:true, depthWrite: false } );
var planeMesh_xy = new THREE.Mesh(plGeometry, plMaterial);
planeMesh_xy.rotation.x = -Math.PI/2;
scene.add(planeMesh_xy);
planeMesh_xy.receiveShadow = true;
// GRID XY static
var gridstep = 1;
var gridcolor = 0xCCCCCC;
var gridHelper_xy = new THREE.GridHelper(gridplaneSize/2, gridstep);
gridHelper_xy.position = new THREE.Vector3(0, 0, 0);
gridHelper_xy.setColors( new THREE.Color(gridcolor), new THREE.Color(gridcolor) );
scene.add(gridHelper_xy);
Then I would like to decide if the plane or the grid is visible.
This works for the plane:
planeMesh_xy.visible = false;
But not for the grid:
gridHelper_xy.visible = false; // not working
I also tried workarounds, not working:
gridHelper_xy.material.transparent = true;
gridHelper_xy.material.opacity = 0;
gridHelper_xy.parent.visible = false;
Can anyone tell me how to hide the grid then?
What should happen when you set the visible flag of an object is that all of its children should inherit the flag.
In r71 this should work out of the box (look at issue https://github.com/mrdoob/three.js/issues/1524)
In r65 you would have to traverse your hierarchy setting the flag on all the sub-parts:
object.traverse ( function (child) {
child.visible = false;
}
i'm new in three.js and have a problem by scaling a object. I already looked at the documentation (https://github.com/mrdoob/three.js/wiki/Updates), but i don't really understand it at all.
My Problem: By changing a HTML-select-element the CubeGeometry should be scaled in the x-direction. That is already working BUT the "old" Cube do not disappear. So i have 2 Cubes. But i want only one Cube with the current size. I hope you can understand my problem ;-)
So here is my Code from the View:
$(document).on('change',".configurator > form select",function(event){
// update 3D-object - is that a nice way???
$.getScript("/javascripts/3D-animation.js.erb", function(){
// here comes the update
OBJECT3D.updateObject();
});
})
And here is my 3D-animation.js.erb:
var drawing_area;
var renderer;
var camera;
var obj;
var scene;
var OBJECT3D = {};
$(function() {
// get drawing_area
drawing_area = document.getElementById("canvas_wrapper");
// initialize renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(drawing_area.clientWidth, drawing_area.clientHeight);
renderer.setClearColor( 0xffffff, 1);
// add renderer to drawing_area
drawing_area.appendChild(renderer.domElement);
// initialize camera
camera = new THREE.PerspectiveCamera(45, drawing_area.clientWidth/drawing_area.clientHeight, 1, 100000);
camera.position.z = 1000;
camera.position.y = 100;
camera.position.x = 300;//-0.78;
// create texture
var texture = THREE.ImageUtils.loadTexture( "/images/materials/texture_1.jpg" );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1, 1 );
// create object
var obj_form = new THREE.CubeGeometry(250,250,250);
var obj_material = new THREE.MeshLambertMaterial( { map: texture,ambient: 0x999999 } );
OBJECT3D.obj = new THREE.Mesh(obj_form, obj_material);
// so what do i need here?
OBJECT3D.obj.geometry.dynamic = true;
// OBJECT3D.obj.geometry.__dirtyVertices = true;
// OBJECT3D.obj.geometry.__dirtyNormals = true;
// OBJECT3D.obj.geometry.verticesNeedUpdate = true;
// create scene
scene = new THREE.Scene();
scene.add(camera);
scene.add(OBJECT3D.obj);
// create lights
pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 400;
pointLight.position.y = 200;
pointLight.position.z = 1300;
scene.add(pointLight);
ambientLight = new THREE.AmbientLight( 0xffffff);
scene.add( ambientLight );
requestAnimationFrame(render);
function render(){
requestAnimationFrame(render);
OBJECT3D.obj.rotation.y += 0.005;
OBJECT3D.obj.rotation.z += 0.005;
renderer.render(scene, camera);
};
// update object
OBJECT3D.updateObject = function () {
console.log("in update");
OBJECT3D.obj.scale.x = 2.5; // SCALE
OBJECT3D.obj.geometry.needsUpdate = true;
//render();
}
});
Sorry, if the Code is not the best one, but i'm really new in this stuff! :) hope you can help me!
Thanks!
I think it is because you are using $.getScript method. This will load your script each time again and create a new instance of OBJECT3D.
I propose that you make sure that your code in 3D-animation.js.erb will be included and called only once upon the load of a page (include it just like any other regular javascript file) and then call your update directly like this:
$(document).on('change',".configurator > form select", function(event) {
OBJECT3D.updateObject();
});
also, I believe you can drop following lines of code:
OBJECT3D.obj.geometry.needsUpdate = true;
and
OBJECT3D.obj.geometry.dynamic = true;
I am starting with THREE.js, and I am trying to draw a rectangle with a texture on it, lit by a single source of light. I think this is as simple as it gets (HTML omitted for brevity):
function loadScene() {
var world = document.getElementById('world'),
WIDTH = 1200,
HEIGHT = 500,
VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000,
renderer = new THREE.WebGLRenderer(),
camera = new THREE.Camera(VIEW_ANGLE, ASPECT, NEAR, FAR),
scene = new THREE.Scene(),
texture = THREE.ImageUtils.loadTexture('crate.gif'),
material = new THREE.MeshBasicMaterial({map: texture}),
// material = new THREE.MeshPhongMaterial({color: 0xCC0000});
geometry = new THREE.PlaneGeometry(100, 100),
mesh = new THREE.Mesh(geometry, material),
pointLight = new THREE.PointLight(0xFFFFFF);
camera.position.z = 200;
renderer.setSize(WIDTH, HEIGHT);
scene.addChild(mesh);
world.appendChild(renderer.domElement);
pointLight.position.x = 50;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.addLight(pointLight);
renderer.render(scene, camera);
}
The problem is, I cannot see anything. If I change the material and use the commented one, a square appears as I would expect. Note that
The texture is 256x256, so its sides are power of two
The function is actually called when the body is loaded; indeed it works with a different material.
It does not work even if I serve the file from a webserver, so it is not an issue of cross-domain policy not allowing to load the image.
What I am I doing wrong?
By the time the image is loaded, the renderer has already drawn the scene, hence it is too late. The solution is to change
texture = THREE.ImageUtils.loadTexture('crate.gif'),
into
texture = THREE.ImageUtils.loadTexture('crate.gif', {}, function() {
renderer.render(scene);
}),
Andrea solution is absolutely right, I will just write another implementation based on the same idea.
If you took a look at the THREE.ImageUtils.loadTexture() source you will find it uses the javascript Image object. The $(window).load event is fired after all Images are loaded ! so at that event we can render our scene with the textures already loaded...
CoffeeScript
$(document).ready ->
material = new THREE.MeshLambertMaterial(map: THREE.ImageUtils.loadTexture("crate.gif"))
sphere = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material)
$(window).load ->
renderer.render scene, camera
JavaScript
$(document).ready(function() {
material = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture("crate.gif") });
sphere = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material);
$(window).load(function() {
renderer.render(scene, camera);
});
});
Thanks...
In version r75 of three.js, you should use:
var loader = new THREE.TextureLoader();
loader.load('texture.png', function ( texture ) {
var geometry = new THREE.SphereGeometry(1000, 20, 20);
var material = new THREE.MeshBasicMaterial({map: texture, overdraw: 0.5});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
In version r82 of Three.js TextureLoader is the object to use for loading a texture.
Loading one texture (source code, demo)
Extract (test.js):
var scene = new THREE.Scene();
var ratio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,
0.1, 50);
var renderer = ...
[...]
/**
* Will be called when load completes.
* The argument will be the loaded texture.
*/
var onLoad = function (texture) {
var objGeometry = new THREE.BoxGeometry(20, 20, 20);
var objMaterial = new THREE.MeshPhongMaterial({
map: texture,
shading: THREE.FlatShading
});
var mesh = new THREE.Mesh(objGeometry, objMaterial);
scene.add(mesh);
var render = function () {
requestAnimationFrame(render);
mesh.rotation.x += 0.010;
mesh.rotation.y += 0.010;
renderer.render(scene, camera);
};
render();
}
// Function called when download progresses
var onProgress = function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
// Function called when download errors
var onError = function (xhr) {
console.log('An error happened');
};
var loader = new THREE.TextureLoader();
loader.load('texture.jpg', onLoad, onProgress, onError);
Loading multiple textures (source code, demo)
In this example the textures are loaded inside the constructor of the mesh, multiple texture are loaded using Promises.
Extract (Globe.js):
Create a new container using Object3D for having two meshes in the same container:
var Globe = function (radius, segments) {
THREE.Object3D.call(this);
this.name = "Globe";
var that = this;
// instantiate a loader
var loader = new THREE.TextureLoader();
A map called textures where every object contains the url of a texture file and val for storing the value of a Three.js texture object.
// earth textures
var textures = {
'map': {
url: 'relief.jpg',
val: undefined
},
'bumpMap': {
url: 'elev_bump_4k.jpg',
val: undefined
},
'specularMap': {
url: 'wateretopo.png',
val: undefined
}
};
The array of promises, for each object in the map called textures push a new Promise in the array texturePromises, every Promise will call loader.load. If the value of entry.val is a valid THREE.Texture object, then resolve the promise.
var texturePromises = [], path = './';
for (var key in textures) {
texturePromises.push(new Promise((resolve, reject) => {
var entry = textures[key]
var url = path + entry.url
loader.load(url,
texture => {
entry.val = texture;
if (entry.val instanceof THREE.Texture) resolve(entry);
},
xhr => {
console.log(url + ' ' + (xhr.loaded / xhr.total * 100) +
'% loaded');
},
xhr => {
reject(new Error(xhr +
'An error occurred loading while loading: ' +
entry.url));
}
);
}));
}
Promise.all takes the promise array texturePromises as argument. Doing so makes the browser wait for all the promises to resolve, when they do we can load the geometry and the material.
// load the geometry and the textures
Promise.all(texturePromises).then(loadedTextures => {
var geometry = new THREE.SphereGeometry(radius, segments, segments);
var material = new THREE.MeshPhongMaterial({
map: textures.map.val,
bumpMap: textures.bumpMap.val,
bumpScale: 0.005,
specularMap: textures.specularMap.val,
specular: new THREE.Color('grey')
});
var earth = that.earth = new THREE.Mesh(geometry, material);
that.add(earth);
});
For the cloud sphere only one texture is necessary:
// clouds
loader.load('n_amer_clouds.png', map => {
var geometry = new THREE.SphereGeometry(radius + .05, segments, segments);
var material = new THREE.MeshPhongMaterial({
map: map,
transparent: true
});
var clouds = that.clouds = new THREE.Mesh(geometry, material);
that.add(clouds);
});
}
Globe.prototype = Object.create(THREE.Object3D.prototype);
Globe.prototype.constructor = Globe;
Without Error Handeling
//Load background texture
new THREE.TextureLoader();
loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' , function(texture)
{
scene.background = texture;
});
With Error Handling
// Function called when download progresses
var onProgress = function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
// Function called when download errors
var onError = function (error) {
console.log('An error happened'+error);
};
//Function called when load completes.
var onLoad = function (texture) {
var objGeometry = new THREE.BoxGeometry(30, 30, 30);
var objMaterial = new THREE.MeshPhongMaterial({
map: texture,
shading: THREE.FlatShading
});
var boxMesh = new THREE.Mesh(objGeometry, objMaterial);
scene.add(boxMesh);
var render = function () {
requestAnimationFrame(render);
boxMesh.rotation.x += 0.010;
boxMesh.rotation.y += 0.010;
sphereMesh.rotation.y += 0.1;
renderer.render(scene, camera);
};
render();
}
//LOAD TEXTURE and on completion apply it on box
var loader = new THREE.TextureLoader();
loader.load('https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/1920px-The_Earth_seen_from_Apollo_17.jpg',
onLoad,
onProgress,
onError);
Result:
https://codepen.io/hiteshsahu/pen/jpGLpq/
Use TextureLoader to load a image as texture and then simply apply that texture to scene background.
new THREE.TextureLoader();
loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' , function(texture)
{
scene.background = texture;
});
Result:
https://codepen.io/hiteshsahu/pen/jpGLpq?editors=0011
See the Pen Flat Earth Three.JS by Hitesh Sahu (#hiteshsahu) on CodePen.