I am trying to achieve flat shading with the phong material. As the documentation says we have to set flatShading:true in config when creating the material. I have created a cube with these settings and created a spot light to focus at the center of the cube (not touching the vertices of the cube).
I assume the face of the cube should look same at all pixels but its not looking so. I see a reflection of the light at the center of the face. Indeed, changing the flatShading from true to false, is not affecting the light.
Here is the JS fiddle link to what i tried. https://jsfiddle.net/dj03gktb/.
Below is the code.
var camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 4;
scene.add(camera);
scene = new THREE.Scene();
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
scene.add(ambientLight);
var pointLight = new THREE.SpotLight({ color: 0xffffff, angle: Math.PI / 10, intensity: 2 });
scene.add(pointLight);
//pointLight.position.x = 5;
pointLight.position.z = 1.5;
pointLight.position.y = 0;
var geometry = new THREE.BoxGeometry(1, 1, 1);
//geometry.computeFlatVertexNormals();
//geometry.rotateY(0.5);
var material = new THREE.MeshPhongMaterial({ color: 0xff0000, flatShading: true, shininess: 100, specular: 0x00ff00 });
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
May be i have under stood flat shading wrongly or could be bug with the THREE.js or problem with the render engine.
Any help would be great.
Unfortunately, using a cube to demonstrate flat shading is a bad example since you won't see any difference. Try it with a sphere instead:
var camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 4;
scene = new THREE.Scene();
var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.2 );
scene.add( ambientLight );
var spotLight = new THREE.SpotLight({ color: 0xffffff, angle: Math.PI / 10, intensity: 2});
spotLight.position.z = 1.5;
scene.add(spotLight);
var geometry = new THREE.SphereBufferGeometry( 1, 12, 16);
var material = new THREE.MeshPhongMaterial({color: 0xff0000, flatShading: true, shininess: 100, specular: 0x00ff00});
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.118.3/build/three.js"></script>
I assume the face of the cube should look same at all pixels but its not looking so.
This is not what flat shading does. Normally vertex normals are interpolated in the fragment shader which produces the typical smooth rendering. Flat shading just ensures that the normals of a face are equal across its (flat) surface. So there is no interpolation of vertex normals. Apart from that, lighting calculations are not affected. Hence, you also see specular highlights.
Related
I was doing a ring in three.js. I've needed to add camera and lighting. Camera work is ok, but I've got the problem with lighting. Here is my code:
<script src="three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejsfundamentals.org/threejs/../3rdparty/dat.gui.module.js"></script>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(10, 10, 10);
light.target.position.set(-5, 0, 0);
scene.add(light);
scene.add(light.target);
document.body.appendChild( renderer.domElement );
let controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.rotateSpeed = 0.5;
controls.update();
camera.position.z = 5;
const geometry = new THREE.RingGeometry( 1, 3, 32 );
const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
const mesh = new THREE.Mesh( geometry, material );
mesh.receiveShadow = true;
mesh.castShadow = true
scene.add( mesh );
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
</script>
I've tried to change DirectionalLight to AmbientLight and SpotLight, but they all didn't work either. It doesn't matter for me what kind of the light it would be, I've just need to show my 3d shape.
MeshBasicMaterial is not affected by lights. You have to use a different material. Use a MeshPhongMaterial, that the Blinn-Phong model uses for light calculation:
const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
const material = new THREE.MeshPhongMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
I have simple three.js scene with sphere and directional light. On the sphere you can see that it get gradually darker. How to make the transition from light to dark faster. How to make light "sharper"?
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 3;
camera.position.z = 6;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshPhongMaterial({
color: 0x777777
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 10, 10);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.js"></script>
One way to achieve the intended affect is via toon shading. Hence, give MeshToonMaterial a try. You can also defined a gradientMap that defines the distinct lit areas of the material.
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 3;
camera.position.z = 6;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshToonMaterial({
color: 0x777777
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 10, 10);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
You can set material.shininess. See this
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 1;
camera.position.z = 2;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshPhongMaterial({
color: 0x777777,
shininess: 400,
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 10, 10);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.js"></script>
Even with a MeshToonMaterial you need to set shininess to 0 if you want no specular highlights
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 1;
camera.position.z = 2;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshToonMaterial({
color: 0x777777,
shininess: 0,
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 10, 10);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
further, the relative amount of light is hard coded unless you use a gradientMap
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 0;
camera.position.z = 2;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshToonMaterial({
color: 'red',
shininess: 0,
gradientMap: new THREE.DataTexture(
new Uint8Array([
0, // black (color away from light multiplied by color of material)
255, // white (color toward light multiplied by material
]),
2, // width of texture
1, // height of texture,
THREE.LuminanceFormat, // format of texture
),
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(50, 50, 30);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
Note the gradientMap above is a 2x1 pixel texture so the cutoff will be 50% between facing the light and not facing the light. To move the cutoff make the gradientMap bigger. For example a 5x1 pixel texture with black, black, white, white, white will be 40% black, 60% white
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 0;
camera.position.z = 2;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshToonMaterial({
color: 'red',
shininess: 0,
gradientMap: new THREE.DataTexture(
new Uint8Array([
0, // black (color away from light multiplied by color of material)
0,
255, // white (color toward light multiplied by material
255,
255,
]),
5, // width of texture
1, // height of texture,
THREE.LuminanceFormat, // format of texture
),
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(50, 50, 30);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
If you want to set the 2 colors explicitly then make the gradientMap have the colors and set the material color to white
var scene, camera, renderer, controls, sphere, geometry, material, light;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 0;
camera.position.z = 2;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(1, 32, 32);
material = new THREE.MeshToonMaterial({
color: 'white',
shininess: 0,
gradientMap: new THREE.DataTexture(
new Uint8Array([
255, 0, 255, // purple (color away from light multiplied by color of material)
0, 255, 255, // cyan (color toward light multiplied by material
]),
2, // width of texture
1, // height of texture,
THREE.RGBFormat, // format of texture
),
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(50, 50, 30);
scene.add(light);
renderer.render(scene, camera);
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
If you want more than 2 colors/divisions then put more than 2 colors in the gradientMap
I want to draw a Octahedron,but the shape of the result is below expectation.I dont know the reason and next step I want to draw a Dodecahedron, any suggestions?The result of the Octahedron
My code:
var mesh, renderer, scene, camera, controls;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(40, window.innerWidth /
window.innerHeight, 1, 10000);
camera.position.set(20, 20, 20);
// controls
controls = new THREE.OrbitControls(camera);
// ambient
scene.add(new THREE.AmbientLight(0x222222));
// light
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(20, 20, 0);
scene.add(light);
// axes
scene.add(new THREE.AxisHelper(20));
// geometry
//var geometry = new THREE.SphereGeometry(5, 12, 8);
var vertices = [
0.5,0,0, 0,0.3,0, -0.5,0,0, 0,-0.3,0, 0,0,0.3, 0,0,-0.3
];
var faces = [
0,1,4, 1,2,4, 2,3,4, 3,0,4, 0,1,5, 1,2,5, 2,3,5, 3,0,5
];
var geometry = new THREE.PolyhedronGeometry(vertices, faces, 5, 1);
// material
var material = new THREE.MeshPhongMaterial({
color: 0x00ffff,
shading: THREE.FlatShading,
wireframe:true,
transparent: true,
opacity: 0.7,
});
// mesh
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function animate() {
requestAnimationFrame(animate);
//controls.update();
renderer.render(scene, camera);
}
Use
var geometry = new THREE.OctahedronGeometry( 5, 0 ); // or DodecahedronGeometry
Read and understand the source code of OctahedronGeometry.js if you want to use PolyhedronGeometry directly.
If wireframe is true, use MeshBasicMaterial, instead.
three.js r.85
I tried to make the wireframe rotate in the scene.
The animation works when I removed the BoxHelper, but I want to animate the cube wireframe without diagonal line instead of a solid object.
Codepen demo :
Demo
Code :
var w = window.innerWidth, h = window.innerHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000),
renderer = new THREE.WebGLRenderer(),
geometry = new THREE.BoxGeometry( 1, 1, 1 ),
material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ),
mesh = new THREE.Mesh( geometry, material ),
cube = new THREE.BoxHelper(mesh);
cube.material.color.setRGB(25,25,25);
scene.add(cube);
camera.position.z = 2;
renderer.setSize(w,h);
document.body.appendChild(renderer.domElement);
function render(){
requestAnimationFrame( render );
cube.rotation.x += 1;
renderer.render(scene, camera );
}
render();
The position of your THREE.BoxHelper instance is tied to the position of the THREE.Mesh. For your code to work you will have to add the mesh to the scene and rotate the mesh. Your box helper will follow.
If you don't want to show the mesh you can simply set mesh.visible = false;
This code works:
var w = window.innerWidth, h = window.innerHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000),
renderer = new THREE.WebGLRenderer(),
geometry = new THREE.BoxGeometry( 1, 1, 1 ),
mesh = new THREE.Mesh( geometry );
camera.position.z = 2;
renderer.setSize(w,h);
document.body.appendChild(renderer.domElement);
mesh.visible = false; //<-- hide mesh
scene.add(mesh); //<-- add mesh to scene
cube = new THREE.BoxHelper(mesh);
cube.material.color.setRGB(25,25,25);
scene.add(cube);
function render(){
mesh.rotation.y += 0.01; //<-- rotate the mesh
requestAnimationFrame( render );
renderer.render( scene, camera );
}
render();
I am trying to create a very realistic scene using Three.js. So far I have implemented mouse controls, loading model from Maya and I came to applying textures. The code works however, as it can be seen on the images below the textures don't fill the box as one would expect. I assume the problem is because each face of the model is filled separately, which occurred to me when I displayed my model as in wireframe mode.
My UV values look like this :
[[0.375,0,0.625,0,0.375,0.25,0.625,0.25,0.375,0.5,0.625,0.5,0.375,0.75,0.625,0.75,0.375,1,0.625,1,0.875,0,0.875,0.25,0.125,0,0.125,0.25]]
function init() {
// renderer
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setSize(window.innerWidth, window.innerHeight);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 10000 );
camera.position.z = 5;
cameraControls = new THREE.TrackballControls(camera, renderer.domElement);
cameraControls.target.set(0, 0, 0);
scene = new THREE.Scene();
light = new THREE.AmbientLight( 0xffffff );
scene.add( light );
material = new THREE.MeshPhongMaterial( {
map: THREE.ImageUtils.loadTexture('images/box_texture.jpg')
} );
group = new THREE.Object3D();
var loader = new THREE.JSONLoader();
loader.load('models/cube_1.js', modelLoadedCallback);
window.addEventListener( 'resize', onWindowResize, false );
}
function modelLoadedCallback(geometry) {
mesh = new THREE.Mesh( geometry, material );
group.add(mesh);
// scene.add (new THREE.Mesh (geometry,
// new THREE.MeshBasicMaterial ({ color: 0x000000, wireframe: true })));
scene.add (new THREE.Mesh (geometry, material));
scene.add( group );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function animate() {
var delta = clock.getDelta();
requestAnimationFrame(animate);
cameraControls.update(delta);
renderer.render(scene, camera);
}
It can be helpful
var texture = THREE.ImageUtils.loadTexture('images/box_texture.jpg') ;
and add:
texture.flipY = false;