I started working with this example where both, a CSS2D Renderer as well as a WebGL Renderer is used - it works fine.
Now, I want to load object files and add CSS Labels to it. I started with the OBJ-Loader Example. There, the renderer is created as follows (all the following code snippets refer to the loader example):
initGL: function () {
this.renderer = new THREE.WebGLRenderer( {
canvas: this.canvas,
antialias: true,
autoClear: true
} ); .....
Thre renderer is called as follows:
render: function () {
if ( ! this.renderer.autoClear ) this.renderer.clear();
this.controls.update();
this.renderer.render( this.scene, this.camera );
}
My problem is that I do not know how to integrate the CSS2 Renderer. I simply tried to add the new labelrenderer to the same positions of the WebGLRenderer but it seems not to work:
initGL: function () {
this.renderer = new THREE.WebGLRenderer( {
canvas: this.canvas,
antialias: true,
autoClear: true
} ); //same code as before
this.labelRenderer = new THREE.CSS2DRenderer();
document.getElementById( 'example' ).appendChild(this.labelRenderer.domElement );....
I tried to update the render also at the same position of the WebGL Renderer:
render: function () {
if ( ! this.renderer.autoClear ) this.renderer.clear();
this.controls.update();
this.renderer.render( this.scene, this.camera );
//same code as before
this.labelRenderer.render(this.scene,this.camera);
}
The result: the WebGL renderer seem to work (the model is loaded) but the webgl renderer is not working... I do not get any error message but I simply do not see any label. What do I wrong? I create the label as follows:
var earthDiv = document.createElement( 'div' );
earthDiv.className = 'label';
earthDiv.textContent = 'MYLABEL IS NOT SHOWN';
earthDiv.style.marginTop = '-1em';
var earthLabel = new THREE.CSS2DObject( earthDiv );
earthLabel.position.set( 0, 3, 0 );
scope.scene.add(earthLabel );
I found the solution. The problem was that the CCS2 Object was added to the wrong div:
document.getElementById( 'example' ).appendChild(this.labelRenderer.domElement );
Of course, both, the canvas from the WebGL Renderer and the dom of the label Renderer have to be added to the same parent (the element example was the dom of the webgl renderer)
Thank you soo much for your support!
Related
Hello I'm trying to add canvas with a sense using three.js I have
<div id="earth3D" bind:this={earth3DCanvas} />
and in order to add canvase in my js section, I do
let earth3DCanvas;
onMount(() => {
const child = document.createElement(renderer.domElement);
earth3DCanvas.appendChild(child);
});
but for some reason I'm getting this error and I have no idea what it means.
VM2002:1 Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('[object HTMLCanvasElement]') is not a valid name.
at HTMLDocument.createElement (<anonymous>:1:1536)
at http://localhost:5000/build/bundle.js:51519:30
at run (http://localhost:5000/build/bundle.js:14:16)
at Array.map (<anonymous>)
at http://localhost:5000/build/bundle.js:430:49
at flush (http://localhost:5000/build/bundle.js:245:21)
at init (http://localhost:5000/build/bundle.js:520:13)
at new App (http://localhost:5000/build/bundle.js:51598:7)
at http://localhost:5000/build/bundle.js:51609:17
at http://localhost:5000/build/bundle.js:51618:3
Bind a container first then onMount add the renderer to this container
<script>
import { onMount } from 'svelte';
let container;
onMount(async () => {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(renderer.domElement);
});
</script>
<div id="container" bind:this={container} />
Here is a working repl
https://svelte.dev/repl/446c6476fc0444b19ec95c0044564b96?version=3.44.2
here is the fix if you want to use a conditional statement
<script>
const initContainer = (node) => {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = THREE.sRGBEncoding;
node.appendChild(renderer.domElement);
}
</script>
{#if condition}
<div id="container" use:initContainer />
{/if}
here is a working repl using this approach
https://svelte.dev/repl/446c6476fc0444b19ec95c0044564b96?version=3.44.2
Document.createElement() expects a tag name as parameter (so a string like 'canvas' or 'div'), but you're passing it an HTMLCanvasElement object instead.
It's like you were attempting to recreate an already existing canvas.
I am not sure what your actual goal is, but if you want to create and append a brand new canvas, then you would do:
onMount(() => {
const child = document.createElement('canvas');
earth3DCanvas.appendChild(child);
});
If you instead want to append the canvas referred to by renderer.domElement to your container div:
onMount(() => {
earth3DCanvas.appendChild(renderer.domElement);
});
Note that if the canvas was already part of the DOM (which seems to be the case given the renderer.domElement name?), then it will be moved, not duplicated.
I've dug around looking for a solve for this - but couldn't find the answer on here. I have a gltf/glb model loading into three js as such:
createScene: function() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true
});
let container = document.getElementById('container');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(new Three.Color('#fff'));
this.renderer.setClearAlpha( 0 );
//this.render.shadowMap.type = Three.PCFSoftShadowMap;
container.appendChild(this.renderer.domElement);
},
createCamera: function() {
this.camera = new Three.PerspectiveCamera(6, container.clientWidth/container.clientHeight, 1, 1000);
this.camera.position.set(0, 0, 40);
},
createShape: function() {
let me = this;
const loader = new GLTFLoader();
loader.load( 'model/fbf3.glb', function ( gltf ) {
me.bottle = gltf.scene
me.scene.add( gltf.scene );
gltf.scene.traverse(function(obj) { obj.frustumCulled = false; });
me.pivot = new Three.Group();
me.pivot.position.set( 0.0, 0.0, 0 );
me.bottle.add( me.pivot );
console.log(gltf.scene);
console.log(gltf.scene.children[1]);
//me.pivot.add( me.bottle );
me.animate();
}, undefined, function ( error ) {
console.error( error );
});
},
I found a post that said to loop through the scene and be sure to add frustumCulled to false - so that's added in ( and when I log the child, that objects frustumCulled is set to false ). In order to have a label on my object easy to map and also take on a different material/glossiness i've created another object in the group that is just slightly in front of my other object on the yaxis. When it is facing the camera, it works well - however, it is when the object rotates is where it disappears. Working:
Rotate enough, and gone:
Is there a setting in threejs that I need to add to be sure the render order is correct? Or is it something wrong with my object set up in Blender? Ideally I wouldn't have to UV wrap the whole bottle as one object and add the label to the bottle texture because I want the label to have less specularity ( that a word? ). Any help would be appreciated.
Solved - the tutorial is slightly old ( 2+years ) and now Blender ( the latest version 2.9+ ) has the Principled BSDF Shader baked in. Instead of using the glTF-Blender-Exporter-Master pbr appended shaders from the listed tutorial - I instead used the Principled BSDF Shader - linked my material to it's Base Color and linked that to the Material Output ( all in the node/now Shader Editor. No issues anymore.
I have a THREE.Group(); in my scene and at runtime i want to add more to this group but it doesn't get updatet or better i cannot see the sprite that i added on runtime. The sprite is in the group but not rendered.
How can i update a THREE.Group();?
Example Code to clarify my problem. Not really running i know.
var systemGroup = new THREE.Group();
init();
animate();
function init() {
//add something to systemGroup like multiple meshes
systemGroup.add(mesh);
scene.add(systemGroup);
}
function render() {
scene.updateMatrixWorld();
renderer.render( scene, camera );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function addMore(x,y,z){
var map = THREE.ImageUtils.loadTexture( "sprite.png" );
var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
var sprite = new THREE.Sprite( material );
sprite.position.set(x,y,z);
systemGroup.add(sprite);
//Here how do i update the group so that the mesh is in the scene?
}
Thank you.
The code you are showing is adding the same mesh over and over again.
As the mesh is already in the group, it doesn't change anything.
What you seem to want is to clone() the mesh and add it at a different position.
Like this:
var group = new THREE.Group()
scene.add(group);
document.body.addEventListener('click', function(evt) {
var clone = mesh.clone()
clone.position.set(-100 + Math.random()*200, 0, 0)
group.add(mesh)
})
No need to update the group with anything else.
When I load obj+mtl+jpg data with Three.js, the material of the object is set to MeshLambertMaterial according to three.js document. If I want to change the material to another one such as MeshBasicMaterial, how can I do that?
The following code loads obj+mtl+jpg from my server and display the data with MeshLambertMaterial. I tried to apply MeshBasicMaterial with the following code (commented), but it failed and the object with displayed in white.
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"></head>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="http://threejs.org/examples/js/loaders/OBJMTLLoader.js"></script>
<body>
<div id="canvas_frame"></div>
<script>
var canvasFrame, scene, renderer, camera;
canvasFrame = document.getElementById('canvas_frame');
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
canvasFrame.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(50,50,50);
camera.lookAt( {x: 0, y: 0, z: 0} );
var ambient = new THREE.AmbientLight(0xFFFFFF);
scene.add(ambient);
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
function loadObjMtl(objUrl, mtlUrl, url, x, y, z){
var loader = new THREE.OBJMTLLoader();
loader.crossOrigin = 'anonymous';
loader.load( objUrl, mtlUrl,
function ( object ) {
object.url = url;
object.position.set(x,y,z);
object.traverse( function( node ) {
if( node.material ) {
////This doesn't work. How can I apply material for loaded obj?
//var material = new THREE.MeshBasicMaterial();
//if ( node instanceof THREE.Mesh ) {
// node.material = material;
//}
}
});
scene.add ( object );
});
}
objUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.obj";
mtlUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.mtl";
loadObjMtl(objUrl,mtlUrl,"",0,0,0);
animate();
</script>
</body>
</html>
You see it totally white because you are not specifying any color for MeshBasicMaterial.
Try this:
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
You'll see all red: it is working.
I think that you probably want to distinguish the different meshes, or apply any texture, do you?
I know this is an old question but I was looking to do something similar and did it this way:
As of today Three.js r143 , the MTLLoader loads the materials as MeshPhongMaterial as seen in
MTLLoader.js 495: this.materials[ materialName ] = new MeshPhongMaterial( params );
Because I didn't want any reflections from my lighting. So I set the materials to
MeshLambertMaterial (see the docs) by just changing that line to:
MTLLoader.js 495: this.materials[ materialName ] = new MeshLambertMaterial( params );
And that's it!.
I know this approach will change the way every other model is loaded but in my case that's how I wanted it.
Hope this helps
Blender export obj don't export double sided objects. How can I render objects in double sided mode. I tried this without succes:
var loader = new THREE.OBJMTLLoader();
loader.load('models/test.obj');
loader.addEventListener( 'load', function ( event ) {
objects = event.content;
objects.position.set(0,5,0);
objects.scale.set(1.5,1.5,1.5);
objects.mesh.doubleSided = true;
scene.add(objects);
});
In your case, you add the following to your callback function:
objects.traverse( function( node ) {
if( node.material ) {
node.material.side = THREE.DoubleSide;
}
});
The doubleSided property of Mesh is deprecated. It was replaced with the side property of Material
Also, it is best to learn from three.js examples that work with the current version of the library.
three.js r.57
2022
Future readers! Just add the side option to the material constructor:
const material = new THREE.MeshLambertMaterial({
color: 0xff0000,
side: THREE.DoubleSide
// ^ this line here
});