I created a canvas with an id of 'canvas' which I provided as an argument to the WebGLRenderer of Three.js. However, nothing is showing up on that canvas. If I append the domElement to the document, the canvas shows up on the bottom but I would like to draw on my existing canvas. Is there an extra setting I have to change?
I am using this example code to start off with:
ctx = $('canvas').getContext('2d');
var canvasElm = $('canvas');
canvasWidth = parseInt(canvasElm.width);
canvasHeight = parseInt(canvasElm.height);
canvasTop = parseInt(canvasElm.style.top);
canvasLeft = parseInt(canvasElm.style.left);
var scene = new THREE.Scene(); // Create a Three.js scene object.
var camera = new THREE.PerspectiveCamera(75, canvasWidth / canvasHeight, 0.1, 1000); // Define the perspective camera's attributes.
var renderer = window.WebGLRenderingContext ? new THREE.WebGLRenderer(canvasElm) : new THREE.CanvasRenderer(); // Fallback to canvas renderer, if necessary.
renderer.setSize(canvasWidth, canvasHeight); // Set the size of the WebGL viewport.
//document.body.appendChild(renderer.domElement); // Append the WebGL viewport to the DOM.
var geometry = new THREE.CubeGeometry(20, 20, 20); // Create a 20 by 20 by 20 cube.
var material = new THREE.MeshBasicMaterial({ color: 0x0000FF }); // Skin the cube with 100% blue.
var cube = new THREE.Mesh(geometry, material); // Create a mesh based on the specified geometry (cube) and material (blue skin).
scene.add(cube); // Add the cube at (0, 0, 0).
camera.position.z = 50; // Move the camera away from the origin, down the positive z-axis.
var render = function () {
cube.rotation.x += 0.01; // Rotate the sphere by a small amount about the x- and y-axes.
cube.rotation.y += 0.01;
renderer.render(scene, camera); // Each time we change the position of the cube object, we must re-render it.
requestAnimationFrame(render); // Call the render() function up to 60 times per second (i.e., up to 60 animation frames per second).
};
render(); // Start the rendering of the animation frames.
I am using Chrome 56.0.2924.87 (64-bit) if that helps.
Your jquery selector is wrong (I am assuming it is jquery).
var canvasElm = $('canvas'); creates a new canvas element.
If you want to select a canvas that has the id of 'canvas', use..
var canvasElm = $('#canvas');
But this then gets a jquery object / list, so to get the actual canvas (first item in the list) you could use..
var canvasElm = $('#canvas')[0];
eg.
var canvasElm = $('#canvas')[0];
renderer = new THREE.WebGLRenderer( { canvas: canvasElm } );
You would probably be better just using js without jquery.
eg.
canvasElm = document.getElementById('canvas');
renderer = new THREE.WebGLRenderer( { canvas: canvasElm } );
Related
I am currently trying to create some smooth terrain using the PlaneBufferGeometry of three.js from a height map I got from Google Images:
https://forums.unrealengine.com/filedata/fetch?id=1192062&d=1471726925
but the result is kinda choppy..
(Sorry, this is my first question and evidently I need 10 reputation to post images, otherwise I would.. but here's an even better thing: a live demo! left click + drag to rotate, scroll to zoom)
I want, like i said, a smooth terrain, so am I doing something wrong or is this just the result and i need to smoothen it afterwards somehow?
Also here is my code:
const IMAGE_SRC = 'terrain2.png';
const SIZE_AMPLIFIER = 5;
const HEIGHT_AMPLIFIER = 10;
var WIDTH;
var HEIGHT;
var container = jQuery('#wrapper');
var scene, camera, renderer, controls;
var data, plane;
image();
// init();
function image() {
var image = new Image();
image.src = IMAGE_SRC;
image.onload = function() {
WIDTH = image.width;
HEIGHT = image.height;
var canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
var context = canvas.getContext('2d');
console.log('image loaded');
context.drawImage(image, 0, 0);
data = context.getImageData(0, 0, WIDTH, HEIGHT).data;
console.log(data);
init();
}
}
function init() {
// initialize camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 100000);
camera.position.set(0, 1000, 0);
// initialize scene
scene = new THREE.Scene();
// initialize directional light (sun)
var sun = new THREE.DirectionalLight(0xFFFFFF, 1.0);
sun.position.set(300, 400, 300);
sun.distance = 1000;
scene.add(sun);
var frame = new THREE.SpotLightHelper(sun);
scene.add(frame);
// initialize renderer
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.append(renderer.domElement);
// initialize controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = .05;
controls.rotateSpeed = .1;
// initialize plane
plane = new THREE.PlaneBufferGeometry(WIDTH * SIZE_AMPLIFIER, HEIGHT * SIZE_AMPLIFIER, WIDTH - 1, HEIGHT - 1);
plane.castShadow = true;
plane.receiveShadow = true;
var vertices = plane.attributes.position.array;
// apply height map to vertices of plane
for(i=0, j=2; i < data.length; i += 4, j += 3) {
vertices[j] = data[i] * HEIGHT_AMPLIFIER;
}
var material = new THREE.MeshPhongMaterial({color: 0xFFFFFF, side: THREE.DoubleSide, shading: THREE.FlatShading});
var mesh = new THREE.Mesh(plane, material);
mesh.rotation.x = - Math.PI / 2;
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
plane.computeFaceNormals();
plane.computeVertexNormals();
scene.add(mesh);
animate();
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update();
}
The result is jagged because the height map has low color depth. I took the liberty of coloring a portion of the height map (Paint bucket in Photoshop, 0 tolerance, non-continuous) so you can see for yourself how large are the areas which have the same color value, i.e. the same height.
The areas of the same color will create a plateau in your terrain. That's why you have plateaus and sharp steps in your terrain.
What you can do is either smooth out the Z values of the geometry or use a height map which utilizes 16bits or event 32bits for height information. The current height map only uses 8bits, i.e. 256 values.
One thing you could do to smooth things out a bit is to sample more than just a single pixel from the heightmap. Right now, the vertex indices directly correspond to the pixel position in the data-array. And you just update the z-value from the image.
for(i=0, j=2; i < data.length; i += 4, j += 3) {
vertices[j] = data[i] * HEIGHT_AMPLIFIER;
}
Instead you could do things like this:
get multiple samples with certain offsets along the x/y axes
compute an (weighted) average value from the samples
That way you would get some smoothing at the borders of the same-height areas.
The second option is to use something like a blur-kernel (gaussian blur is horribly expensive, but maybe something like a fast box-blur would work for you).
As you are very limited in resolution due to just using a single byte, you should convert that image to float32 first:
const highResData = new Float32Array(data.length / 4);
for (let i = 0; i < highResData.length; i++) {
highResData[i] = data[4 * i] / 255;
}
Now the data is in a format that allows for far higher numeric resolution, so we can smooth that now. You could either adjust something like the StackBlur for the float32 use-case, use ndarrays and ndarray-gaussian-filter or implement something simple yourself. The basic idea is to find an average value for all the values in those uniformly colored plateaus.
Hope that helps, good luck :)
As you can see below, I added a mesh to the scene but everytime I try to scale it or set position, I get an error that the mesh is null.
mesh2.position.set(0,0,-5);
mesh2.scale.set(0.2, 0.3, 0.2);
Uncaught TypeError: Cannot read property 'position' of null
check it out at testing2.site44.com
Please help as I have wasted hours trying to get this to work.
function init()
{
var scene = new THREE.Scene();//CREATE NEW THREE JS SCENE
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
var renderer = new THREE.WebGLRenderer({antialias:true}); //INIT NEW THREE JS RENDERER
renderer.setPixelRatio( window.devicePixelRatio ); //SET PIXEL RATIO FOR MOBILE DEVICES
renderer.setClearColor(new THREE.Color('#005b96'), 1) //SET BG COLOR
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); // SET SIZE
document.body.appendChild( renderer.domElement ); //APPLY CANVAS TO BODY
renderer.domElement.id = "canvas_threeJS";//ADD ID TO CANVAS
//ADD CAMERA TO THE SCENE
var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR =1000;
var camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(0,3,8); //SET CAMERA POSITION
camera.lookAt(new THREE.Vector3(0,0,-5));
scene.add(camera);
//ADD AMBIENT LIGHT
var ambientLight = new THREE.AmbientLight("#6497b1");
scene.add(ambientLight);
//HANDLE WINDOW RESIZE
//ADD MAIN LIGHT
var light = new THREE.PointLight("#b3cde0",.6);
light.position.set(-5,13,-1);
scene.add(light);
var mesh2 = null;
var loader = new THREE.JSONLoader();
loader.load('assets/models/spaceship001.json', function(geometry) {
mesh2 = new THREE.Mesh(geometry);
scene.add(mesh2);
console.log("done loading model");
});
mesh2.position.set(0,0,-5);
// mesh2.scale.set(0.2, 0.3, 0.2);
//START POSITION OF A LEVEL GROUP
var levelSpawn = -100;
//GET VISIBLE WIDTH AT levelSpawn POSITION
var vFOV = camera.fov * Math.PI / 180; // convert vertical fov to radians
var height = 2 * Math.tan( vFOV / 2 )*levelSpawn; // visible height
var aspect = window.innerWidth / window.innerHeight;
var width = height * aspect;// visible width
//START RENDER
update();
function update()
{
//UPDATE 3D SCENE
renderer.render( scene, camera );
//KEEP RENDERING
requestAnimationFrame( update );
}
}
The problem is that javascript is an asynchronous language. Depending on your background you should read up on that. Try putting a console.log("setting scale and postion") just under or above those two lines. What you'll notice is that the position and scale is set before the mesh is loaded.
What you need to do is set the position and scale inside the callback function that you pass to the load function, just under the console.log("done loading model"); line.
You can also read up on javascript Promises to understand it better.
I have a Website with WebGL content. For this I have a div-element for showing the WebGL.
Now I am trying to get this content in multiple divs on the same page. The content should be exactly the same. If it is possible the animation should be shown on all divs.
I have tried to create a second renderer and tried to add this to the second div but this seems not to work.
How can I get the same WebGL content in multiple divs on the same page?
This is my code for creating the WebGL content. renderer1 was my try to append to the second div but this didnt work.
<div id="WebGLCanvas"/>
<script>
var scene;
var camera;
var controls;
var geometryArray;
initializeScene();
animateScene();
function initializeScene(){
if(!Detector.webgl){
Detector.addGetWebGLMessage();
return;
}
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setClearColorHex(0x000000, 1);
renderer1 = new THREE.WebGLRenderer({antialias:true});
renderer1.setClearColorHex(0x000000, 1);
canvasWidth = window.innerWidth;
canvasHeight = window.innerHeight;
renderer.setSize(canvasWidth, canvasHeight);
renderer1.setSize(canvasWidth, canvasHeight);
document.getElementById("WebGLCanvas").appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 1, 100);
camera.position.set(0, 0, 6);
camera.lookAt(scene.position);
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
axisSystem = new AxisSystem(camera, controls);
geometryArray = new Object();
var loader = new THREE.JSONLoader();
for(var i = 0; i < jsonFileNames.length; i++){
var layerName = jsonFileNames[i].split("/")[2].split(".")[0];
loader.load(jsonFileNames[i], function(geometry, layerName){
mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({vertexColors: THREE.FaceColors}));
mesh.scale.set(0.003, 0.003, 0.003);
mesh.doubleSided = true;
scene.add(mesh);
geometryArray[layerName] = mesh;
}, layerName);
}
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position = camera.position;
scene.add(directionalLight);
}
function animateScene(){
controls.update();
axisSystem.animate();
requestAnimationFrame(animateScene);
renderScene();
}
function renderScene(){
renderer.render(scene, camera);
axisSystem.render();
}
</script>
EDIT:
I have tried to add the renderer to a second div element but the scene than only appears on the last added div element and not on both.
This is the code I have tried. A simple example of what I am trying is that I want a left div element and a right div element. On both I want the same content. That means if I move the 3D object on my left element it should also move on the right element.
container = document.getElementById("webglcanvas");
container2 = document.getElementById("webglcanvas2");
containerWidth = container.clientWidth;
containerHeight = container.clientHeight;
renderer = new THREE.WebGLRenderer({antialias:true, alpha:true});
renderer.setSize(containerWidth, containerHeight);
container.appendChild(renderer.domElement);
container2.appendChild(renderer.domElement);
It's possible, take a look at this example. You just have to set the same camera not four different:
http://stemkoski.github.io/Three.js/Viewports-Quad.html
Did you need something more?
I created a small scene with 3 spheres and a triangle connecting the 3 centers of the spheres, i.e. the triangle vertex positions are the same variables as the sphere positions.
Now I expected that if i change the position of one of the spheres, the triangle vertex should be moved together with it (since it's the same position object) and therefore still connect the three spheres.
However, if I do this coordinate change AFTER the renderer was called, the triangle is NOT changed. (Though it does change if I move the sphere BEFORE the renderer is called.)
This seems to indicate that the renderer doesnt use the original position objects but a clone of them.
Q: Is there a way to avoid this cloning behaviour (or whatever is the reason for the independent positions) so I can still change two objects with one variable change? Or am I doing something wrong?
The code:
var width = window.innerWidth;
var height = window.innerHeight;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene;
var camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 10000);
camera.position=new THREE.Vector3(50,50,50);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
var pointLight = new THREE.PointLight(0xffffff);
pointLight.position=camera.position;
scene.add(pointLight);
var sphere=[];
var sphereGeometry = new THREE.SphereGeometry(1,8,8);
var sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });
var triGeom = new THREE.Geometry();
for (var i=0; i<3; i++) {
sphere[i] = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere[i].position=new THREE.Vector3(10*i,20+5*(i-1)^2,0);
scene.add(sphere[i]);
triGeom.vertices.push(sphere[i].position);
}
triGeom.faces.push( new THREE.Face3( 0, 1, 2 ) );
triGeom.computeFaceNormals();
var tri= new THREE.Mesh( triGeom, new THREE.MeshLambertMaterial({side:THREE.DoubleSide, color: 0x00ff00}) );
scene.add(tri);
sphere[0].position.x+=10; // this changes both sphere and triangle vertex
renderer.render(scene, camera);
sphere[1].position.x+=10; // this changes only the sphere
renderer.render(scene, camera);
This is probably because of geometry caching feature. You will have to set triGeom.verticesNeedUpdate = true every time you change vertex position.
Like we are changing height/width/depth of 3D cube at run time in this Fiddle:
http://jsfiddle.net/EtSf3/4/
How can we change the Radius and Length at runtime of Cylinder created using Three.js
Here is my code:
HTML:
<script src="http://www.html5canvastutorials.com/libraries/three.min.js"></script>
<div id="container"></div>
JS
//Script for 3D Cylinder
// revolutions per second
var angularSpeed = 0.2;
var lastTime = 0;
var cylinder = null;
// this function is executed on each animation frame
function animate() {
// update
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
cylinder.rotation.x += angleChange;
cylinder.rotation.z += angleChange;
lastTime = time;
// render
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function () {
animate();
});
}
// renderer
var container = document.getElementById("container");
var renderer = new THREE.WebGLRenderer();
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 700;
// scene
var scene = new THREE.Scene();
// cylinder
// API: THREE.CylinderGeometry(bottomRadius, topRadius, height, segmentsRadius, segmentsHeight)
cylinder = new THREE.Mesh(new THREE.CylinderGeometry(150, 150, 500, 100, 100, false), new THREE.MeshPhongMaterial({
// light
specular: '#cccccc',
// intermediate
color: '#666666',
// dark
emissive: '#444444',
shininess: 100
}));
cylinder.overdraw = true;
cylinder.rotation.x = Math.PI * 0.2;
//cylinder.rotation.y = Math.PI * 0.5;
scene.add(cylinder);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0x444444);
scene.add(ambientLight);
// directional lighting
var directionalLight = new THREE.DirectionalLight(0xcccccc);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// start animation
animate();
Here is the Fiddle for the same: http://jsfiddle.net/dpPjD/
Let me know if you need any other information.
Please suggest.
Once the object geometry is added to the mesh, it is converted to face/vertex/UV/normals and stored as part of the mesh. For example, the cylinder shape you have specified is tessellated (divided) by Three.js into triangles with a vertex count of more than 10,000.
Hence while the global mesh properties like transforms can be updated, updating the individual geometries is as good as creating a new geometry every animation-frame. If you happen to know precisely the vertices you need to modify, you can update it directly using the geometry.vertices property. But if not, I do not think there is a way.
You can try overwriting the geometry of an object by doing something like this:
cylinder.geometry.dispose();
cylinder.geometry = new THREE.CylinderGeometry(botRad, topRad, height, 32);
where: botRad, topRad and height are new values from initial or a variable that is constantly changing if you are planning on oscillating cylinder height.
The dispose makes sure that it deletes the previous geometry of the object before overwriting a new geometry to it.