three.js - Draw custom mesh with MeshLambertMaterial and point light - javascript

I'm currently learning webGL and three.js. So, for test reasons, I tried to create a plane geometry and two cube geometries and a point light:
function initLights () {
var c = context;
var pointLight = new THREE.PointLight( 0xffffff, 1, 100 );
pointLight.position.set( 10, 10, 10 );
c.scene.add(pointLight);
}
function initObjects () {
var c = context;
/**
* Defining the materials
*/
var lambertRedMaterial = new THREE.MeshLambertMaterial({
color : 0xff0000
, side : THREE.DoubleSide
});
var lambertWhiteMaterial = new THREE.MeshLambertMaterial({
color : 0xffffff
, side : THREE.DoubleSide
});
/**
* Defining the floor
*/
var floorGeometry = new THREE.Geometry();
floorGeometry.vertices.push(new THREE.Vector3(-5.0, 0.0, -5.0));
floorGeometry.vertices.push(new THREE.Vector3( 5.0, 0.0, -5.0));
floorGeometry.vertices.push(new THREE.Vector3( 5.0, 0.0, 5.0));
floorGeometry.vertices.push(new THREE.Vector3(-5.0, 0.0, 5.0));
floorGeometry.faces.push(new THREE.Face3(2, 1, 0));
floorGeometry.faces.push(new THREE.Face3(3, 2, 0));
var floorMesh = new THREE.Mesh(floorGeometry, lambertWhiteMaterial);
floorMesh.position.set(0.0, 0.0, 0.0);
/**
* Defining a cube
*/
var cubeGeometry1 = new THREE.CubeGeometry(2.0,0.25,1);
var cube1 = new THREE.Mesh( cubeGeometry1, lambertRedMaterial );
cube1.position.set(0.0, 1.0, 0.0);
var cubeGeometry2 = new THREE.CubeGeometry(2.0,0.25,1);
var cube2 = new THREE.Mesh( cubeGeometry2, lambertRedMaterial );
cube2.position.set(0.0, 1.35, 0.0);
c.scene.add(floorMesh);
c.scene.add(cube1);
c.scene.add(cube2);
}
The context with a camera and the scene was defined before.
The weird thing is, that the cubes are displayed correctly, but the plane is not displayed.
When I set the y-position of the plane to 1.0, then I can see, that it intersects with the lower cube. Also, it's displayed when I use MeshBasicMaterial, but I want to use MeshLambertMaterial for lighting reasons.
Has anybody an idea, if I forgot something, or what the problem could be?
Many thanks in advance.

MeshLambertMaterial requires face normals or vertex normals for the lighting calculation.
Face normals are used for "flat shading" and vertex normals are used for "smooth shading".
You can compute face normals by calling geometry.computeFaceNormals();. For vertex normals, you can call geometry.computeVertexNormals();.
For visual cues, use the three.js helpers, such as this one:
scene.add( new THREE.FaceNormalsHelper( mesh ) );
Also, if you are just learning, the advice in this answer may be helpful to you.
three.js r.65

Related

Why my map not working with MeshPhongMaterial

I try to create the planet Earth with three.js. I used a texture on a MeshBasicMaterial and it worked perfectly, but when I change the material to a MeshPhongMaterial it just doesn't render the map anymore.
I want to change the material because I want to add a bump Map too.
Here's my code:
let renderer;
const earthGeometry = new THREE.SphereGeometry(5, 50, 50);
const earthMaterial = new THREE.MeshPhongMaterial({
map: new THREE.TextureLoader().load("../img/globe.jpg"),
// bumpMap: new THREE.TextureLoader().load("../img/earthbump.jpg"),
// bumpScale: 0.3,
});
const sphere = new THREE.Mesh(earthGeometry, earthMaterial);
scene.add(sphere);
And there's not a single problem in the console so I don't know what to do.
I'm also using svelte and vite, maybe it can come from here.
I used a texture on a MeshBasicMaterial and it worked perfectly, but when I change the material to a MeshPhongMaterial it just doesn't render the map anymore.
If it does work with MeshBasicMaterial but not with MeshPhongMaterial, you most likely have no lights in your scene. Try it with this basic setup:
const ambientLight = new THREE.AmbientLight( 0xffffff, 0.4 );
scene.add( ambientLight );
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
directionalLight.position.set( 100, 100, 0 );
scene.add( directionalLight );

Three.js - Cylinder inner texture

I'm completely new to Three.js and use it for now only for a side feature on a website.
I'm trying to render a paper cup (or something that reminds a paper cup) with different textures on the outer side and inner side.
So far I've managed to do something like that. rotate left <--> right using keyboard arrows.
Simple Demo
How can I add a different texture for the inner side of the cup (where the red arrow points).
without a texture, the rotation looks a bit strange.
and I'll be happy to hear any suggestion to how to make it better in terms of visual
Thank you
Just a concept of how you can do it:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 3, 5);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.CylinderGeometry(2, 1.5, 4, 32, 1, true);
var materialOuter = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/758px-Canestra_di_frutta_(Caravaggio).jpg")
});
var materialInner = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/UV_Grid_Sm.jpg"),
side: THREE.BackSide
});
var meshOuter = new THREE.Mesh(geometry, materialOuter);
var meshInner = new THREE.Mesh(geometry, materialInner);
meshOuter.add(meshInner);
scene.add(meshOuter);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
As an alternative to #prisoner849's answer, you can use a custom shader material and check whether the rendered fragment is backfacing and give it its own color (texture...)
vec3 frontMaterial = vec3(1.0, 0.0, 0.0); //red
vec3 backMaterial = vec3(0.0, 1.0, 0.0); //green
//make every fragment green
gl_FragColor = vec4( frontMaterial, 1.0 );
//make fragments green when on the inner side of the object model
if (!gl_FrontFacing) gl_FragColor = vec4(backMaterial, 1.0);
Here's a live example

How do shadows work in THREE.js r75?

I'm working on a tutorial for Three.js and in an example, it presents the concepts of shadows that can be utilized through methods such as: castShadow, receiveShadow and by setting shadowMapenabled to true.
However the example code is for Three.js r69. As of the date of this question, Three.js is at r75 and this code for casting shadows does not work.
Additionally the current Three.js documentation has no information on shadowMapenabled nor shadowMap.enabled or the other methods mentioned.
Suggestions on how to resolve this?
Some of the shadow map properties have been renamed in the recent versions, but they basically work the same.
Setting up the renderer for shadow maps (and choosing the more computational expensive shadow map type):
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
Setting up the light (notice how it also works with THREE.PointLight):
var light = new THREE.PointLight( 0xffffff, 1, 100 );
light.position.set( 0, 12, 0 );
light.castShadow = true; // default false
light.shadow.mapSize.width = 1024; // default 512
light.shadow.mapSize.height = 1024; // default 512
light.shadow.camera.near = 2; // default 0.5
light.shadow.camera.far = 100; // default 500
//light.shadow.camera.left = 500 // Not sure about this one +
// right, top and bottom, Do they still do anything?
scene.add( light );
You get notified of all these API changes if you check your console when trying to use the properties specified in the current documention.
Creating you objects that casts and receives shadows is same as before:
//Creating box
var boxGeometry = new THREE.BoxGeometry( 1, 1, 1 );
var boxMaterial = new THREE.MeshPhongMaterial( { color: 0xdddddd, specular: 0x999999, shininess: 15, shading: THREE.FlatShading } );
var box = new THREE.Mesh( boxGeometry, boxMaterial );
box.castShadow = true;
scene.add( box );
creating plane
var planeGeometry = new THREE.PlaneGeometry( 20, 20, 32, 32 );
var planeMaterial = new THREE.MeshPhongMaterial( { color: 0x00dddd, specular: 0x009900, shininess: 10, shading: THREE.FlatShading } )
var plane = new THREE.Mesh( planeGeometry, planeMaterial );
plane.receiveShadow = true;
scene.add( plane );
Placing the plane under the box and it will receive a shadow.
Here is a working codepen example
EDIT
In the current version of THREE.js, the scene needs to be rendered at least twice for the shadows to show.
THREE.js r75.
Here is a the simple code for shadow in three.js .Also included the shadow camera helper
var light = new THREE.SpotLight( 0xFFAA55 );
light.castShadow = true;
light.position.set( 1, 3, 5 );
light.shadowCameraNear = 0.5;
scene.add( light );
helper = new THREE.CameraHelper( light.shadow.camera );
scene.add( helper );
code

Issue with drawing half of a sphere in three.js

I'm creating a skydome in three.js like this:
var geometry = new THREE.SphereGeometry( 40, 32, 15, 1*Math.PI/2, 2*Math.PI, Math.PI, Math.PI);
var material = new THREE.MeshBasicMaterial( { color: 0xddddff } );
mesh = new THREE.Mesh( geometry, material );
mesh.material.side = THREE.DoubleSide;
scene.add( mesh );
This does draw half of a sphere but it also colors the part that I expected to be open (it colors the surface of my mesh that I need the skydome for). How do I solve this problem?
The thing is that even if you draw half a sphere, I doubt it will put UVs correctly, so I very recommend you to make your own skydome using custom mesh.
For skydome you can use mesh.material.side = THREE.BackSide instead of .DoubleSide, btw, unless you stay on the surface all the time.
I wasn't drawing the sphere correctly:
var geometry = new THREE.SphereGeometry( 50, 60, 60, Math.PI, Math.PI, 3*Math.PI/2);
var material = new THREE.MeshBasicMaterial( { color: 0xddddff } );
mesh = new THREE.Mesh( geometry, material );
mesh.material.side = THREE.DoubleSide;
scene.add( mesh );

Three.js Object3d cylinder rotation to align to a vector

I have searched far and wide, but can't seem to figure this pretty basic thing out. I have seen other examples on stackoverflow and elsewhere from a year or two ago, but they fail to work with the latest version of Three.js.
Here is a version of what i'm working on: http://medschoolgunners.com/sandbox/3d/.
I'm trying to get the grey cone to exactly align with the unlabeled red vector. Ie. I want the tip of the cone to be exactly aligned with the vector and point out from the origin in that direction.
Here is the code I have right now:
//FUNCTION TO CREATE A CYLINDER
function create_cylinder(radiusTop,radiusBottom, height, segmentsRadius, segmentsHeight, openEnded, color)
{
var material = new THREE.MeshLambertMaterial({
color: color, //0x0000ff
opacity: 0.2
});
var cylinder = new THREE.Mesh(new THREE.CylinderGeometry(radiusTop,radiusBottom, height, segmentsRadius, segmentsHeight, openEnded), material);
cylinder.overdraw = true;
return cylinder;
}
//ALIGN THE CYLINDER TO A GIVEN VECTOR
var alignVector=new THREE.Vector3(-50,50,50); //the vector to be aligned with
var newcylinder = create_cylinder(0.1, 10, 40, 50, 50, false, "0x0ff0f0"); // the object to be aligned with the vector above
var cylinderQuaternion = new THREE.Quaternion();
cylinderQuaternion.setFromEuler(alignVector);
newcylinder.useQuaternion = true;
newcylinder.quaternion=cylinderQuaternion;
scatterPlot.add(newcylinder);
If you have an arbitrary vector:
var vector = new THREE.Vector3(100, 60, 20);
You can align an object, such as a cylinder, to the vector like this:
var geometry = new THREE.CylinderGeometry(2, 2, vector.length(), 4, 4);
var mesh = new THREE.Mesh(geometry, someMaterial);
var axis = new THREE.Vector3(0, 1, 0);
mesh.quaternion.setFromUnitVectors(axis, vector.clone().normalize());
Where axis is the original direction of the cylinder (pointing up).
You can also move the cylinder to match the position of the vector like this:
mesh.position.copy(vector.clone().multiplyScalar(0.5));
This puts one end of the cylinder at the 0, 0, 0 and the other at 100, 60, 20, and works because I set the cylinder length to vector.length().
i know this is an old question, but in case anyone is still wondering, what worked for me was adding the vector to the mesh position and use lookAt to align it to the vector:
//Mesh to align
var material = new THREE.MeshLambertMaterial({color: 0x0000ff});
var cylinder = new THREE.Mesh(new THREE.CylinderGeometry(10, 10, 15), material);
//vector to align to
var vector = new THREE.Vector3(
5,//x
10,//y
15 //z
);
//create a point to lookAt
var focalPoint = new THREE.Vector3(
cylinder.position.x + vector.x,
cylinder.position.y + vector.y,
cylinder.position.z + vector.z
);
//all that remains is setting the up vector (if needed) and use lookAt
cylinder.up = new THREE.Vector3(0,0,1);//Z axis up
cylinder.lookAt(focalPoint);
Unfortunately I haven't worked with Quaternions, so can't help much. It seems to my that some offsetting is needed, since the cylinder's pivot is at the centre of the mesh, not at one end.
If played with matrices a bit, and I've got decent results.
Here's one way to this, using Mesh's lookAt() method:
var HALF_PI = -Math.PI * .5;
var p1 = new THREE.Vector3(Math.random()-.5,Math.random()-.5,Math.random()-.5).multiplyScalar(30);
var p2 = new THREE.Vector3(Math.random(),Math.random(),Math.random()).multiplyScalar(300);
var halfLength = diff.length() * .5;
var c = new THREE.CylinderGeometry(10, 10, halfLength * 2, 12, 1, false );
var orientation = new THREE.Matrix4();
orientation.setRotationFromEuler(new THREE.Vector3(HALF_PI,0,0));//rotate on X 90 degrees
orientation.setPosition(new THREE.Vector3(0,0,halfLength));//move half way on Z, since default pivot is at centre
c.applyMatrix(orientation);//apply transformation for geometry
var m = new THREE.Mesh( c, new THREE.MeshLambertMaterial( { color: 0x009900, wireframe: true, shading: THREE.FlatShading } ) );
scene.add(m);
m.lookAt(p2);//tell mesh to orient itself towards p2
//just for debugging - to illustrate orientation
m.add(new THREE.Axes());
//visualize p1,p2 vectors
var PI2 = Math.PI * 2;
var program = function ( context ) {
context.beginPath();
context.arc( 0, 0, 1, 0, PI2, true );
context.closePath();
context.fill();
}
particleMaterial = new THREE.ParticleCanvasMaterial( { color: 0x990000, program: program } );
var pp1 = new THREE.Particle( new THREE.ParticleCanvasMaterial( { color: 0x990000, program: program } ) );
pp1.scale.multiplyScalar(10);
pp1.position.copy(p1);
scene.add( pp1 );
var pp2 = new THREE.Particle( new THREE.ParticleCanvasMaterial( { color: 0x009900, program: program } ) );
pp2.scale.multiplyScalar(10);
pp2.position.copy(p2);
scene.add( pp2 );
This should draw a cylinder that starts at p1, ends at p2 and is oriented towards it.
Offsetting might need some tweaking, but the geometry follows the vector direction pretty close.
There's also the longer version of manually computing the matrices, as opposed to relying on the lookAt() functionality:
plane.add(getCylinderBetweenPoints(p1,p2,new THREE.MeshLambertMaterial( { color: 0x009900, wireframe: true, shading: THREE.FlatShading } )));
function getCylinderBetweenPoints(point1,point2,material){
var HALF_PI = -Math.PI * .5;
var diff = new THREE.Vector3().sub(point1,point2);//delta vector
var halfLength = diff.length() * .5;
var c = new THREE.CylinderGeometry(10, 10, halfLength * 2, 12, 1, false );
var orientation = new THREE.Matrix4();//a new orientation matrix to offset pivot
var offsetRotation = new THREE.Matrix4();//a matrix to fix pivot rotation
var offsetPosition = new THREE.Matrix4();//a matrix to fix pivot position
orientation.lookAt(point1,point2,new THREE.Vector3(0,1,0));//look at destination
offsetRotation.setRotationX(HALF_PI);//rotate 90 degs on X
offsetPosition.setPosition(new THREE.Vector3(-point1.x,diff.length()*.5+point1.z,point1.y*.5));//move by pivot offset on Y
orientation.multiplySelf(offsetRotation);//combine orientation with rotation transformations
orientation.multiplySelf(offsetPosition);//combine orientation with position transformations
c.applyMatrix(orientation);//apply the final matrix
var m = new THREE.Mesh( c, material );
m.add(new THREE.Axes());
return m;
}
var PI2 = Math.PI * 2;
var program = function ( context ) {
context.beginPath();
context.arc( 0, 0, 1, 0, PI2, true );
context.closePath();
context.fill();
}
//visualize p1,p2 vectors
particleMaterial = new THREE.ParticleCanvasMaterial( { color: 0x990000, program: program } );
var pp1 = new THREE.Particle( new THREE.ParticleCanvasMaterial( { color: 0x990000, program: program } ) );
pp1.scale.multiplyScalar(10);
pp1.position.copy(p1);
plane.add( pp1 );
var pp2 = new THREE.Particle( new THREE.ParticleCanvasMaterial( { color: 0x009900, program: program } ) );
pp2.scale.multiplyScalar(10);
pp2.position.copy(p2);
plane.add( pp2 );
This looks like me more work than using quaternion, from what I see in you're code. If the setFromEuler does the magic for orientation, the mesh's geometry still might need to move (pivot from one end rather than centre)
HTH

Categories