Make a scene a 3D texture in THREE.js (3D Render Targets) - javascript

I'm looking at the THREE.js example located here and wondering how to prevent the 'flattening' of scenes rendered as textures. In other words, the scene loses the illusion of having depth when set as a WebGLRenderTarget.
I have looked everywhere, including in THREE.js documentation, and have found no mention of this kind of functionality, probably because it would put a significant load on the user's processor unnecessarily (except for in very particular cases). Perhaps this is possible in pure WebGL, though?
EDIT: Downvoters - why is this question poor? I have done significant research into this matter, but since I'm new to WebGL, I can't exactly spout senseless code... How do I improve my query?

I think you want to use screenspace projections instead of UV projections if that makes sense. Given your tv example, the screen would have the UV points that do get transformed as you move the camera around. You want something that stays put, ie. no matter how much you move, you're looking at the same thing. I'm not sure how this is done without shaders, but in fragment shaders you have gl_FragCoord

Because THREE.js "flattens" every scene it renders, all that's needed is a change of perspective (relative to the main camera of the main scene) to maintain the illusion of depth in render targets. Here's a skeleton of something that would do that:
var scene = new THREE.Scene(),
rtScene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera( ..... ),
rtCamera = new THREE.PerspectiveCamera( ..... ),
rtCube = new THREE.Mesh(new THREE.CubeGeometry(1,1,1), new THREE.MeshSimpleMaterial({ color: 0x0000ff }),
rtTexture = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat }),
material = new THREE.MeshBasicMaterial({ map: rtTexture }),
cube = new THREE.Mesh(new THREE.CubeGeometry(1,1,1), material);
function init() {
//manipulate cameras
//add any textures, lighting, etc
rtScene.add( rtCube );
scene.add( cube );
}
function update() {
//some function of cube.rotation & cube.position
//that changes the rtCamera rotation & position,
//depending on the desired effect.
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
renderer.clear();
update();
renderer.render( rtScene, rtCamera, rtTexture, true );
renderer.render( scene, camera );
}
init();
animate();
I assumed in my code that camera would remain stationary while cube rotates around the y axis. Each face has its own updating instance of material. The update() function for each face is a bunch of trigonometric gibberish that can be derived easily with the law of cosines. I will post a jsFiddle example as soon as I have my local working properly.

Related

Lighting glb models in Three JS with minimal shadows

Not sure if I worded my title right but I'm getting my feet wet with three JS. Right now I have a simple glb model that I would like to import into my scene, but I can't get the lighting right. The image below is what I want to accomplish.
But when I import my glb into my scene and add some lighting this is what it looks like
The model is quite dark and I can't get it to light up ideally. I've tried adding ambient lights top-down, point lights as a child to the camera instance, hemisphere lights, etc. but I just can't get it to look right. Below is the code for the current lighting; I'm trying to achieve the look by using point lights atm.
var light = new THREE.PointLight( 0xffffff, 10 );
light.position.z = 10
camera.add(light)
var light2 = new THREE.PointLight( 0xffffff, 10 );
light2.position.set(0, -20, 30)
scene.add(light2)
If anyone could give me some insights as to what is the proper way to achieve what I am desiring that will be great.
So I did some digging in, and it turns out that Blender includes this thing called an environment map
https://discourse.threejs.org/t/exporting-blender-scene-lighting-issues/11887/8
So I had to recreate the environment in my scene as well.
After importing RoomEnvironment like so:
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment';
I created the room environment:
const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator( renderer );
scene.environment = pmremGenerator.fromScene( environment ).texture;
Then I added the following attributes to my scene object:
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
renderer.outputEncoding = THREE.sRGBEncoding;
After that, it lights up just as fine! I honestly don't really know what these toneMapping stuff do at the moment, but for now this solves my problem.

three.js progressive rendering of large mesh

I would like to render a static mesh that is arbitrarily large using three.js. The mesh could be 2 GB with tens of millions of polygons.
I want to stream the mesh geometry buffers into indexedDB and progressively read them out and render them to the screen, while maintaining an interactive frame rate. I will create a MemoryManager class that makes sure we do not crash the browser by loading the data into a fixed-size buffer from indexedDB. In my animation loop I will render as many geometries as I can within 16ms, until the user stops interacting, then continuously render meshes until there are no more.
That is the high-level approach I want to take, of course there are many optimizations that will need to be done. ( object pools, octree, occlusion queries, etc)
My question is this: is there a better way to do this, and has it been done before? ( with WebGL1, I know WebGL2 occlusion queries would make this much simpler)
Also, what is the best way to customize the Three.js WebGLRenderer class? There are private closure vars ( like WebGLState ) that I will need access to tweak the performance for my use case.
I don't believe WebGL2 occlusion queries will help you a whole lot here. It might be possible to use them, but doing culling on the cpu is probably a better option, especially if your scene is largely static.
If you want to progressively render in geometry while the user has stopped interacting, then you can just stop clearing the render buffer. With THREE.js you can do this by enabling preserveDrawingBuffer on the renderer and setting autoClear to false.
If the camera moves or the scene otherwise changes and you need to start over, you can clear the view again by calling renderer.clear().
Regarding streaming data in, I would use BufferGeometry and modify the necessary BufferAttributes every frame. And it sounds like you have already have some ideas around optimizing and deciding which geometry needs to be rendered.
Example
Scene redraws on the same frame over and over. Click the scene to clear it and start drawing again. The important js lines are NOTE'd
// NOT: enabling preserveDrawingBuffer
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setClearColor(0x263238);
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.clear();
document.body.appendChild( renderer.domElement );
// NOTE: disabling autoclear
renderer.autoClear = false;
// Scene setup
const scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(.4, 1, .1);
scene.add(dirLight);
// Camera setup
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000)
camera.position.z = 10;
const geom = new THREE.TorusKnotGeometry(.5, .2, 200, 100);
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial({ color: 0xE91E63 }));
scene.add(mesh);
// NOTE: Clearing on user interaction
renderer.domElement.addEventListener('click', () => renderer.clear());
(function renderLoop() {
mesh.position.y = Math.sin(performance.now() * 0.001);
renderer.render( scene, camera );
requestAnimationFrame( renderLoop );
})();
<script type="text/javascript" src="https://rawgit.com/mrdoob/three.js/r92/build/three.js"></script>
<style>
canvas {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
font-family: sans-serif;
}
div {
color: white;
position: absolute;
text-align: center;
width: 100%;
}
</style>
<div>Click to clear</div>
Hope that helps!

Three.js project crashes mobile

I've been working on a three.js project to try and learn the framework. Got a basic model floating around that works fine on the desktop browser but will crash repeatedly on mobile. I uploaded the project on my server http://threedeesneaker.404vanity.com/
Is there any way to optimize this for mobile devices? I tried both chrome and safari for iPhone and iPad.
The code it self:
(function() {
var scene, camera, renderer;
var geometry, material, mesh, sneaker;
init();
animate();
function init() {
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
camera = new THREE.PerspectiveCamera( 3, WIDTH / HEIGHT, 1, 20000 );
camera.position.z = 1000;
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
geometry = new THREE.BoxGeometry( 200, 200, 200 );
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
// prepare loader and load the model
var oLoader = new THREE.OBJMTLLoader();
oLoader.load('models/sneaker.obj', 'models/sneaker.mtl', function(object) {
object.scale.set(1, 1, 1);
object.rotation.y = 600;
object.rotation.z= 600;
sneaker = object;
scene.add(sneaker);
});
// var loader = new THREE.OBJLoader();
// loader.load('models/sneaker.obj', function(object) {
// sneaker = object;
// sneaker.scale.set(1,1,1);
// sneaker.rotation.y = 600;
// sneaker.rotation.z= 600;
// scene.add(sneaker);
// });
renderer = new THREE.WebGLRenderer();
renderer.setSize( WIDTH, HEIGHT );
renderer.setClearColor(0x333F47, 1);
var light = new THREE.PointLight(0xffffff);
light.position.set(-100,200,100);
scene.add(light);
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
sneaker.rotation.x += 0.01;
sneaker.rotation.y += 0.02;
renderer.render( scene, camera );
}
})();
First a comment on your js : check if typeof sneaker !== 'undefined' in your render loop before asking to rotate your mesh, before loading it generates errors.
Your scene crashes because you are using too detailed materials, I can see a 4096x4096 bump map for instance. It strongly increases frame rendering time on desktop and is probably the reason why the page is irresponsive on mobile : the fragment shader computations become too big.
However it would be a shame to completly delete those details you spent time on. What you can do is to add a device detector in your js. You can use that to display two different models on desktop and on mobile.
But there are further important improvements you can bring. As they are part of my original post I let them there :) :
Resize your textures. You are using two 4096 x 4096 jpg of 4.5MB, this is heavy (note that there are webgl-enabled smartphones with only 500Mo RAM that get realeased these days). Moreover you have very few details that justifies it. You could change your uv to reduce a lot the parts with no details, and probably resize the picture to 512x512. Finally, use a JPG compressor that will reduce the weight by 70-80%. Depending on your picture PNG can be a better choice also. The device's GPU memory is still something else, and if you still need to improve performance you can check in the script if the client supports .pvr or .ktx texture formats, optimized for GPU memory.
An important problem that makes your visualization unappropriate for mobile devices is that you have ... 23 render calls, because you are using 15 textures and 23 geometries.
What it means is that, for each frame, you will have to bind 23 different geometries before the final frame renders. Some mobile CPU-GPU couples cannot do that 60 times per second. Don't plan more than 10 render calls for average mobile devices. That means less geometries with less materials. Merge.
I have not inspected your .obj file in detail to understand how you get 23 geometries in the end, neither where your 13 textures come from, up to you.
A lot of 3D apps (OpenGL) on the stores have more than 23 objects of course. But stores know the apps and they know your phone so they can do the compatibility job and hide the app to low devices.
Here is the tip to check your render calls, geometries and materials in the scene : in your main function, after having set the renderer, include a pointer to it in the window object window.renderer = renderer. Now at runtime in your console, once resources have been loaded, type renderer.info. It will return those data in an object.

WebGL & Three.js - Render a scene successively in two renderers

I'm trying to render a scene in two different renderers (successively not at the same time) but it leads to the error "GL_INVALID_OPERATION".
Here is a sample script:
var scene1 = new THREE.Scene();
var camera1 = new THREE.PerspectiveCamera( ... );
var renderer1= new THREE.WebGLRenderer( ... );
var renderer2= new THREE.WebGLRenderer( ... );
var camera2 = new THREE.PerspectiveCamera( ... );
//Render scene1 in renderer1
renderer1.render( scene1, camera1 );
//[After some user event...]
//Render scene1 in renderer2
renderer2.render( scene1, camera2 ); //This fails. getError()=1282 (i.e. GL_INVALID_OPERATION)
I know it often deprecated to render a scene in two different renderers even not at the same time, but I could think of no other way of solving my issue as it is part of a very big project.
I understand there are GL data associated to scene1 that are linked to renderer1 but how can I remove those data so that I could render the scene1 again in an other renderer ???
Beware that I am not trying to render the scene in the two renderes simutaneously (which is different than https://github.com/mrdoob/three.js/issues/189).
Thanks for the help.
Regards.
The problem was related to objects/material/textures being bound to specific OpenGL buffers. The solution is thus to unbind from any buffer all the children of the object to be removed from a specific scene, before adding it to an other scene.
I'll post the code of my solution asap.
Regards.

How do I use texture on a sphere in three.js

I've downloaded a sphere example from: http://aerotwist.com/lab/getting-started-with-three-js/ and I can see the nice red sphere. I'd like to use a texture on it. I've tried this:
var texture = THREE.ImageUtils.loadTexture("ball-texture.jpg");
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
texture.repeat.set( 125, 125 );
texture.offset.set( 15, 15 );
texture.needsUpdate = true;
var sphereMaterial = new THREE.MeshBasicMaterial( { map: texture } );
var sphere = new THREE.Mesh(new THREE.Sphere(radius, segments, rings),sphereMaterial);
but I can't see anything, all is black. Does anyone have a working example for sphere texture?
You might have two problems.
First, try loading it like this:
var texture = THREE.ImageUtils.loadTexture('ball-texture.jpg', {}, function() {
renderer.render(scene, camera);
});
texture.needsUpdate = true;
Make sure that the texture size is a power of two (512x512px for IE).
Are you using Firefox? This could be a problem in your browser. Firefox uses some kind of cross-site-blocker for textures. The result is black instead. Take a look at this site for more info: http://hacks.mozilla.org/2011/06/cross-domain-webgl-textures-disabled-in-firefox-5/
Do you have a rendering loop, or did you render the scene just once?
You need to have a rendering loop so that when the THREE.ImageUtils loads the image and updates the texture, you re-render the scene with the now updated texture.
All the three.js examples seem to rely on this technique. I.e., Fire off several async operations involving a fetch of a remote resource, start rendering loop, let scene be updated as remote resources arrive.
IMHO this is Three.js's biggest gotcha for Javascript newbs (like me) who are not familiar with how async operations work.
I had this problem, but if you are loading the html as a file (i.e. locally not a webserver), many browsers (chrome for e.g.) will not allow you to load images in the standard three.js way as it is a security violation.

Categories

Resources