three.js - Trying to include a subwindow into main window - javascript

I try to include, into a main window (representing sphere), a subwindow representing a zoomed view of this sphere.
For this moment, I can display a right-bottom subwindow containing the three axes of main scene. These axes are rotating the same way I make rotate the sphere with mouse.
Now, I would like to display, into this subwindow, a zoomed view of the sphere instead of having the subwindow with 3D axes.
From Can multiple WebGLRenderers render the same scene?, we can't use a second time the THREE.WebGLRenderer() for the subwindow.
From How to render the same scene with different cameras in three.js? ,a solution may be to use setViewport function but I don't know if I can display the zoom of sphere in this subwindow.
I tried to do in render() function :
function render() {
controls.update();
requestAnimationFrame(render);
zoomCamera.position.copy(camera.position);
zoomCamera.position.sub(controls.target);
zoomCamera.position.setLength(500);
zoomCamera.lookAt(zoomScene.position );
// Add Viewport for zoomScene
renderer.setViewport(0, 0, width, height);
renderer.render(scene, camera);
zoomRenderer.setViewport(0, 0, 200 , 200);
zoomRenderer.render(zoomScene, zoomCamera);
}
From your advices, is it possible technically to have 2 object in the same time (one on main window and the other on right-bottom subwindow) ?
Can I solve my issue with setViewport ?
And if someone could give documentation on setViewport, this would be great.
Thanks in advance.
UPDATE :
#WestLangley , thanks, I did your modifications (with a unique scene) but the content of inset does not appear.
I think that issue comes from the fact that I don't know how to make the link between the container of inset and the drawing of this inset.
For example, into the link above, I tried :
...
// Add sphere to main scene
scene.add(sphere);
camera.position.z = 10;
var controls = new THREE.TrackballControls(camera);
// If I include these two lines below, nothing appears
// zoomContainer = document.getElementById('zoomContainer');
// zoomContainer.appendChild(renderer.domElement);
// Zoom camera
zoomCamera = new THREE.PerspectiveCamera(50, zoomWidth, zoomHeight, 1, 1000);
zoomCamera.position.z = 20;
zoomCamera.up = camera.up; // important!
// Call rendering function
render();
function render() {
controls.update();
requestAnimationFrame(render);
zoomCamera.position.copy(camera.position);
zoomCamera.position.sub(controls.target);
zoomCamera.position.setLength(camDistance);
zoomCamera.lookAt(scene.position);
// Add zoomCamera to main scene
scene.add(zoomCamera);
// render scene
renderer.clear();
renderer.setViewport(0, 0, width, height);
renderer.render(scene, camera);
// render inset
renderer.clearDepth(); // important!
renderer.setViewport(10, height - zoomHeight - 10, zoomWidth, zoomHeight);
renderer.render(scene, zoomCamera);
}
Given that I have only one renderer, I don't knwo how to assign the "inset" container to the renderer of Viewport (i.e with the good renderer.setViewport)
Would you have got a workaround ?

You want to render another view of your scene in an inset window.
Create one scene, and two cameras.
Make sure you set autoClear to false when you instantiate the renderer.
renderer.autoClear = false;
Then, in your render loop, use this pattern
// render scene
renderer.clear();
renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight );
renderer.render( scene, camera );
// render inset
renderer.clearDepth(); // important!
renderer.setViewport( 10, window.innerHeight - insetHeight - 10, insetWidth, insetHeight );
renderer.render( scene, camera2 );
three.js r.75

Related

Problem with mouse-camera raycasting in threejs

I try to make a very simple program with a clickable 3d object in Threejs. My code is based on
https://threejs.org/examples/#webgl_interactive_cubes
It works when I click on the object (although the resulting array contains the object twice, I assume because the ray intersects it when entering it and when exiting it).
But when I click in an area just surrounding the object raycaster.intersectObjects returns the object although it should return an empty array.
What am I doing wrong? Why is the object also intersected when I click next to it and not on it? And is an object always included twice in the intersect array because of the ray entering and exiting it?
A working example of the code is here:
https://codepen.io/ettir_deul/pen/PoGLNZx
(open the console to see the intersect array after you clicked on the screen)
And the code looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="libs/three.min.js.r116.1"></script>
</head>
<body>
<div id="threejsCanvas"></div>
<script>
let scene, center, camera, renderer, raycaster;
const mouse = new THREE.Vector2();
const threejsCanvas = document.getElementById("threejsCanvas");
init3d();
function init3d(){
scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAEE);
center = new THREE.Vector3(0, 0, 0);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(center.x, center.y, center.z+1);
camera.lookAt(center);
buttonMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0.2), new THREE.MeshBasicMaterial({map : null, color: 0x008844, wireframe: false, side: THREE.DoubleSide}));
buttonMesh.position.set(center.x, center.y, center.z);
scene.add(buttonMesh);
buttonMesh.objId = "buttonMesh";
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
threejsCanvas.appendChild(renderer.domElement);
renderer.render(scene, camera);
raycaster = new THREE.Raycaster();
document.addEventListener('click', onMouseClick, false);
}
function onMouseClick(event){
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
console.log(intersects);
}
</script>
</body>
</html>
the reason for getting the object twice is the
side: THREE.DoubleSide
in the material. You can use THREE.FrontSide
The reason for picking the object when the mouse is close is the canvas and window dimensions not being equal.
If you add :
body{
padding: 0;
margin: 0;
}
in the CSS it is fixed.
Here is a codepen that works.
Two results - is OK. You got 2 faces of mesh. Each one intersects ray. Check result intersection properties: face, faceIndex, point.
Raycaster is not precise and pretty slow. You can use its 'params' to change precision (see https://threejs.org/docs/#api/en/core/Raycaster). I suggest using GPUPicker. It is precise and super fast. Check here:
https://github.com/brianxu/GPUPicker
Edit: Yes you can change material to avoid 2 intersections. But it is often it is not acceptable for surfaces.
Edit: Yes precision settings affects only points and line picking. But GPUPicker can pick exact rendering result of points (shader effects, symbols with transparency). But standard raycaster - can't.

How to create hotspots in 3d enviroment using threejs?

I'm a beginner to three.js.I'm trying to build something similar to this https://virtualshowroom.nissan.in/car-selected.html?selectedCar=ext360_deep_blue_pearl. I built everything using three.js, but I'm not able to figure out how to create a hotspot(like the red dot in the above link) and show pop up when you click on it. below is my project code, let me know if anything else is required.
<html>
<head>
<title>My first three.js app</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<h1></h1>
<script src="./three.js"></script>
<script type="module">
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
var renderer,scene,camera;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xfff6e6)
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var loader = new GLTFLoader();
var hlight = new THREE.AmbientLight(0x404040, 100)
scene.add(hlight)
var directionalLight = new THREE.DirectionalLight(0xffffff, 100)
directionalLight.position.set(0,1,0)
directionalLight.castShadow = true
scene.add(directionalLight)
var light = new THREE.PointLight(0xffffff, 10)
light.position.set(0, 300, 500)
scene.add(light)
var light2 = new THREE.PointLight(0xffffff, 10)
light2.position.set(500, 100, 0)
scene.add(light2)
var light3 = new THREE.PointLight(0xffffff, 10)
light3.position.set(0, 100, -500)
scene.add(light3)
var light4 = new THREE.PointLight(0xffffff, 10)
light4.position.set(-5000, 300, 0)
scene.add(light4)
var controls = new OrbitControls(camera, renderer.domElement);
document.body.appendChild(renderer.domElement)
var loader = new GLTFLoader();
loader.load( './scene.gltf', function ( gltf )
{
scene.add( gltf.scene );
}, undefined, function ( error ) { console.error( error ); } );
// load a image resource
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Those “hotspots” as you call them are Annotations where the annotation content is basically pure HTML.
The tutorial in the link is probably the best step-by-step readiness you can follow to learn how to do it in your scene.
I can give a walkthrough on the steps required to get the desired effect since I have done it a few times myself.
define a 3d point in your scene where the hotspot should be. You can optionally nest this in a an other Object3D to make sure it scales, moves and rotates with the model / parent.
Add a plane to this point load a image texture to this plane. and there you have your visible hotspot
update the hotspots to make sure they are always looking at the camera by using the lookAt function.
when the user clicks the screen cast a raycast against all the hotspots you have in your scene. Easiest way to do this is by storing all your hotspots in an array.
When the raycast hits a hotspot get the location either of the hitpoint or the hotspots location. Transform that to screen coordinates. Search on stackoverflow how to do this. I am sure there is a post about this.
Final step display your html on the correct location you obtained from the previous step.
The advantage of this method is that the hotspot will integrate nicely with the model in your scene. Since html based hotspots will always be on top of the scene.
That is about all that is to it. Let me know if you need any further clarification!

Changing sites as continous animation with Three.js

I was looking at this website called Garden-Eight and they have this very beautiful transition when you change from "Home" to "About Us". I am really interested in how they made it so smooth and continuous without loading screens or any loading time at all.
It looks like it has to be one scene, but watching the URL one can watch a change in the address so there must be some kind of change in the site location.
I am not necessarily looking at this kind of highly complex transitions.
It would be great if I could create some kind of seemingly continuous camera movement along the x axis that changes as a starting point.
For example, I made this very short animation with this JavaScript:
var camera, scene, renderer, geometry, material, mesh, geometryNew, materialNew, meshNew;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0,0,0);
scene.add(mesh);
geometryNew = new THREE.CubeGeometry(200, 200, 200);
materialNew = new THREE.MeshNormalMaterial();
meshNew = new THREE.Mesh( geometryNew, materialNew);
meshNew.position.set(800,0,0);
scene.add(meshNew);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
meshNew.rotation.x -= 0.01;
meshNew.rotation.y -= 0.02;
if(camera.position.x < 800)
camera.position.x += 3
renderer.render(scene, camera);
}
You can watch the animation in this JSFiddle.
What I want to achieve is that the first cube is on the landing page and the second cube is on another page. Can this be done by some kind of asynchronous loading or is there a built-in method, that helps to switch from one canvas to the other?
This is a single-page-app, (SPA), which means it's all one HTML page, regardless what the URL updates to. Some frameworks, like React, have a very complex methodology to implement this, and it would take an entire tutorial to teach.
For simplicity's sake, I recommend you look into the window.onhashchange event to do a fake "change address" without loading a new page. For instance, going from site.com/ to site.com/#about would trigger the event, and then you can start your animation on the same canvas:
window.onhashchange = function() {
if (location.hash === '#about') {
// animate camera to about section
} else if (location.hash === '#work') {
// animate camera to work section
} else if (location.hash === '') {
// animate camera to home section
}
}
Then you can set the hash to change the URL and trigger the event above:
// Adds #about hash to URL, and triggers event listener
location.hash = "#about";
// Removes any hash, and triggers event listener
location.hash = "";

Three.js - antialiasing, rendering, fxaa

I have a three.js project with 3d-models, a ground and a grid in it.
The 3d-models getting outlined with outlinePass (https://threejs.org/examples/?q=outl#webgl_postprocessing_outline).
I am able to move the objects with Transformcontrol (https://threejs.org/examples/?q=transf#misc_controls_transform) and i can change my camera position with Orbitcontrols (https://threejs.org/examples/?q=orbit#misc_controls_orbit)
The problem: The graphics seem kind of badly rendered, here some screenshots:
https://imgur.com/gallery/3FrZt3s
I don't really know which settings i should use here:
renderer = new THREE.WebGLRenderer();//{ antialias: true } ); With antialiasing or without?
//antialiasing is only needed when not using fxaa, right??
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.gammaOutput = true;
renderer.physicallyCorrectLights = true;
camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 0.001, 1000 );
camera.addEventListener( 'change', render ); //Is this necessary? Seems like it has no use
FXAA is probably necessary for outlinePass (also in the outlinePass-example linked above).
composer = new EffectComposer( renderer );
var renderPass = new RenderPass( scene, camera );
composer.addPass( renderPass );
effectFXAA = new ShaderPass( FXAAShader );
effectFXAA.uniforms[ 'resolution' ].value.set( 1 / window.innerWidth, 1 / window.innerHeight );
effectFXAA.renderToScreen = true;
composer.addPass( effectFXAA );
orbitControls = new OrbitControls( camera, renderer.domElement );
orbitControls.update();
orbitControls.addEventListener( 'change', render );
function render(){
renderer.render(scene, camera);
//composer.render(); // don't know if needed
}
So i have to say, i have not really a clue how i can solve the rendering issue and which settings i have to set to make the most out of my project. I'm happy for every hint and answers and maybe i can put these answers together and solve the issue.
When using post-processing with WebGL 1, you have to use FXAA for antialiasing. Passing { antialias: true } to true when creating WebGLRenderer activates MSAA but only if you render to the default framebuffer (directly to screen).
In any event, you configure the FXAA pass like so:
effectFXAA = new ShaderPass( FXAAShader );
effectFXAA.uniforms[ 'resolution' ].value.x = 1 / ( window.innerWidth * pixelRatio );
effectFXAA.uniforms[ 'resolution' ].value.y = 1 / ( window.innerHeight * pixelRatio );
composer.addPass( effectFXAA );
You have to honor the pixelRatio. Besides, setting renderToScreen to true is not necessary anymore. The last pass in the post-processing chain is automatically rendered to screen now.
When using EffectComposer, you do not call renderer.render(scene, camera);. You have to use composer.render(); instead.
camera.addEventListener( 'change', render ); can also be deleted. I'm not sure where you seen this but it has no effect.
three.js R109

How to make a menu before my three program starts executing

I´ve started programming using three.js a little time ago, and I would like to know how to make a menu on a page before it loaded the program using three itself.So, for instance I would like before running my game to have two options:"start" and "music control" being desplayed before it loaded itself.
Is this made using three, or do you use html, javascript files?(Sorry, I really don´t know how to even start it).
If possible, I would like some tutorials or even videos about it.
I would like something like this for example:
http://www.dilladimension.com/
http://lights.helloenjoy.com/
You probably do want to use html to display the menu and js to control the instantiation of your scene.
Rather than use the page load event to set up your renderer, etc, put them inside a function, and use js events to begin the function:
function start() {
var renderer = new THREE.WebGLRenderer();
...
// more setup
...
renderer.render(scene, camera);
};
Then activate this method with a button or a link:
<button onclick="start()">
Start Scene
</button>
Here's a test implementation:
function start() {
var renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 600);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
35, // Field of view
800 / 600, // Aspect ratio
0.1, // Near plane
10000 // Far plane
);
camera.position.set(-15, 10, 10);
camera.lookAt(scene.position);
var geometry = new THREE.BoxGeometry(5, 5, 5);
var material = new THREE.MeshLambertMaterial({
color: 0xFF0000
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var light = new THREE.PointLight(0xFFFF00);
light.position.set(10, 0, 10);
scene.add(light);
renderer.setClearColor(0xdddddd, 1);
renderer.render(scene, camera);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<button onclick="start()">
Start Scene
</button>

Categories