How to use async/await when loading a material texture image - three.js

I have a .obj, and .mtl with texture image that I want to render.
The scene is static so I want to render by explicitly calling the renderer.render() command once (and not use animate()), after the assets are loaded, and see the texture.
But the texture is not seen.
To wait until the texture is loaded, I:
call loadAsync() instead of load(), when loading the .mtl and the .obj files
add await before some of the calls (mtlLoader.loadAsync, materials.preload, objLoader.loadAsync)
change the file MTLLoader, OBJLoader slightly, compared to the reference (from threejs/releases/three.js-r120/examples/jsm/loaders/MTLLoader.js)
(the changes are to accommodate the async/await changes)
The changes work, but don't fully understand why, and would like to know.
Specifically, I have a question regarding placing async/await in one of the functions (MTLLoader.MaterialCreator::create)
In example13_MTLLoader.js, this is the program flow:
MTLLoader.MaterialCreator::preload
MTLLoader.MaterialCreator::create
MTLLoader.MaterialCreator::createMaterial_
in example13_OBJLoader.js, this is the program flow:
OBJLoader::parse
MTLLoader.MaterialCreator::create
I placed async/await in the MTLLoader.MaterialCreator functions: preload, create, createMaterial_.
The rational was to wait for the texture to be loaded before continuing the program.
UseCase1
In this case:
The texture is loaded before the program continues. This can be seen in the log where:
the material is defined
objLoader.materials.materials {test2: MeshPhongMaterial}
the map image is defined
objLoader.materials.materials.test2.map.image <img crossorigin=​"anonymous" src=​"http:​/​/​127.0.0.1:​8080/​./​test2.jpg">​
But the texture is not displayed
The code for this case can be seen here
UseCase2
If I take-out the async/await from MTLLoader.MaterialCreator::create
The texture is NOT loaded before the program continues. This can be seen in the log where:
the materials list is empty
objLoader.materials.materials {}
the material test2 is undefined
objLoader.materials.materials.test2 undefined
But the texture is displayed...
The code for this case can be seen here
(for some reason I need to click Run in jsfiddle multiple times, to see the texture, otherwise only a white patch is seen)
The only change between the 2 cases is in the value of MTLLoader.MaterialCreator::doUseCreateWithAsync = false/true
(the behaviour for the 2 cases is the same for single render or animate())
Why when placing async/await in MTLLoader.MaterialCreator::create, the texture is not seen?
Thanks

Related

Cache scene in Three.js

PIXI.js has Container#cacheAsBitmap which causes the container to "render" itself to an image, save that, render the image instead of its children and when a child is added or removed or updated, the cache is updated.
What's the alternative for Three.js (but instead of an image it would be a mesh)?
I may not be understanding your question properly, but your reply to Sabee's answer was helpful. It sounds like you're looking to either merge multiple geometries into a single mesh or implement a form of model instancing, with the goal of reducing draw calls.
There is more than one way to accomplish this, depending on your requirements. You can merge multiple geometries into a single geometry object, and provide either one material or an array of materials (where each index corresponds to one of the merged geometries). You can also use GPU-accelerated instancing to achieve a similar effect with only a single copy of the geometry.
I'll refer to Dusan Bosnjak's excellent Medium series on instancing, which starts here: https://medium.com/#pailhead011/instancing-with-three-js-36b4b62bc127
As well, here are the three.js examples regarding instancing: https://threejs.org/examples/?q=instanc#webgl_buffergeometry_instancing_dynamic
Pixi.js is a 2D javascript library, using WebGL to render the images(frames) into html5 canvas. Three.js allows the creation of Graphical Processing Unit (GPU)-accelerated 3D animations using WebGL.
The browser cannot store rendered 3D frames, this work allows the GPU Accelerated Render Cache, which depends on what hardware's they run. Helpful post understanding what's going on behind the scenes.
But you can cahce your assets in browser like images, json objects of 3D models and etc.
In Three.js Cache class is a global object, used by assets loaders (TextureLoader, ImageLoader, AudioLoader ...), by default is disabled (false). To enable it you can set THREE.Cache.enabled = true ;
I think by default the browser should cache the textures for performance reasons, but if you want to be sure simply enable the cache by force code it in Three.js. Also the creator of Three.js answered this question.

Why does my three.js canvas masking not work?

I'm trying to reproduce example of Szenia Zadvornykh presented here https://medium.com/#Zadvorsky/webgl-masking-composition-75b82dd4cdfd
His demo is based on three.js r80, so I referenced r101 and tried to remove most of unrelated parts, and just have scene with grid and png mask on top.
Here's my code: http://jsfiddle.net/mmalex/y2kt3Lrf/
Having commented // composer.addPass(maskPass) the grid shows up. But it does not seem like uniform sampler2D tDiffuse has the output of render pass.
I expect to see the grid helper and underlying HTML content under the canvas, where mask makes canvas transparent.
UPDATE, working now, thanks to #Mugen87: http://jsfiddle.net/mmalex/y2kt3Lrf/
There is a mismatch of files in your fiddle. If I use the latest version of three.js and the respective post processing classes, your code works fine:
http://jsfiddle.net/pk24zby7/
three.js has deprecated the renderTarget and forceClear parameter from WebGLrenderer.render() with R102. When the change was done, it was necessary to change many files in the examples directory in order to avoid warnings and even breakage. So using the latest post-processing classes with an older three.js version does not work.
Since the change is listed in the release note, I suggest you read the respective PRs for more details.

Three.js calling clipAction.play() makes animated objects vanish

In Three.js, Calling action.play() makes objects just vanish, without any error or warning on the console.
I use THREE.ObjectLoader to load a JSON file created in blender. The srt (position/scale/quaternion) animation is in the generated file. As are the morphtargets. To optimise filesize I animated the srt as a series of null objects. The morphtargets tracks are in the main object, which I clone 5 times to build the characters (balloons to be exact).
I previously did extensive testing to introduce shape/morph animation. After being succesfull I finalised all the animations. Only to be trumped by the disappearing models. The srt (position/scale/quaternion) animation was working fine before. But after refactoring the code, to be less spagettied, upon calling action.play(). The objects just vanish, exactly then. Echoeing the mixers and the array containing the clips, everything looks correct (ie I see the tracks, the names are right etc). Also examining the newly generated JSON, it seems the same and correct (also I have not changed the SRT animations, only introduced shapeanimation)
So I am lost, and think this looks more and more like a bug. From previous experience I do know it works (or has worked).
I created a jsfiddle: https://jsfiddle.net/oompol/3ya6sqed/
[edit] I turned on the action.play and call the function from the link in the div [/edit] please note I commented out calling action.play(). So you see the load and init work. See the function listed below
function playScene(scene) {
for (parentName in srtMixers) {
var clpName = "balloon1_fly";
var clp = THREE.AnimationClip.findByName(animLib, clpName);
var action = srtMixers[parentName].clipAction(clp);
action.clampWhenFinished = true;
console.log("playScene:", clpName, clp, parentName, srtMixers);
//this is when the problem happens
action.play();
}
}
This is the JSON I am loading:
https://rawgit.com/bakajin/2e3d2f6a722103ed4aefd76f6250ec08/raw/28cad35c20060d478499c0cd40a2753611993720/oomp-scene_balloons-oomp-6.9.4.json
Ok,
there was something very wrong with the scaling indeed.
The io_three JSON exporter for Blender (r87 dev) writes incorrect matrix transformation data in the geometry object (really tiny scaling values). The animation track with the scaling keys were correctly written as 1,1,1. So all the objects just scaled out of view immediately.
Hard to see because the geometry has no separate scaling value but a matrix. Seems to happen when you set "Scene" to true on export.
Worked around the problem by entering the scaling value in the keyframe tracks. But this will only work if you have no scaling animation (so the keys are all one).
Meanwhile I have extensively edited the JSON by hand. Because this is not the only incorrect data. The formatting of the animation object is also wrong. The durations for the morphTargetInfluence Keys is also incorrect. The formatting of these keys is also not always correct.
Hope this helps some other ppl

The render loop freezes temporarly at first "contact" of the camera frustum with multiple meshes

I have a basic scene into wich I am loading objects using the JSONLoader. The objects themselves have very small footprint, for example: milk carton: 560kb with textures, 34 kb json file.
When rendering, let's say 10 new objects, if I orbit the camera to bring them into view, the animation loop freezes for a second or so. After this first freeze, the camera orbits smoothly no matter how many objects. Loading dynamically the objects would be a solution, but for my specific use case, I still need to load at least 50 objects at first load.
Update - I have added the preload functions I use in my production project, and I also added 21 different models just to illustrate my specific scenario. I have tried the following solution:
preloading the json files,
reading the source path to the textures,
loading them with texture loader,
overwritting the maps of the json material objects with the preloaded textures,
finally releasing the objects into scene. The same behavior occurs again.
Try to click the setCamera link to se how laggy it is. I need to cut this lag to 0ms. Thanks for support!
Working demo: http://demo.adrianmoisa.ro/flexikom-loader/ First try to orbit the camera up and down to check it's working ok, then left and right. Any advice is much appreciated!
Looking at your code I see you are loading the same object 10 times, creating 10 meshes that are all the same. All use the same geometry and the same material. This is where your lag comes from. Both from the loading (asynchronous request to the server) and the object creation.
What you need to do is to load one object and create one material that you will assign to the object. Then you clone() the object 10 times assigning the different position that you want to each cloned object.
Gaitat is correct that you should not be loading the same object 10 times. But I think the lag is directly related to the textures.
You should load the textures outside of the loop.
How it is now, you are loading 30 textures onto the gpu when you could be just loading 2 (at least I think this is how it is working).
Profiling the page shows that texture2D is taking a lot of time.
I am almost certain that this will stop the lag.

Using a FrameBufferObject with several Color Texture attachments

I'm implementing in my program the gaussian blur effect. To do the job I need to render the first blur information (the one on Y axis) in a specific texture (let's call it tex_1) and use this same information contained in tex_1 as input information for a second render pass (for the X axis) to fill an other texture (let's call it tex_2) containing the final gaussian blur result.
A good practice should be to create 2 frame buffers (FBOs) with a texture attached for each of them and linked both to GL_COLOR_ATTACHMENT0 (for example). But I just wonder one thing:
Is it possible to fill these 2 textures using the same FBO ?
So I will have to enable GL_COLOR_ATTACHMENT0 and GL_COLOR_ATTACHMENT1 and bind the desired texture to the correct render pass as follow :
Pseudo code:
FrameBuffer->Bind()
{
FrameBuffer->GetTexture(GL_COLOR_ATTACHMENT0)->Bind(); //tex_1
{
//BIND external texture to blur
//DRAW code (Y axis blur pass) here...
//-> Write the result in texture COLOR_ATTACHEMENT0 (tex_1)
}
FrameBuffer->GetTexture(GL_COLOR_ATTACHMENT1)->Bind(); //tex_2
{
//BIND here first texture (tex_1) filled above in the first render pass
//Draw code (X axis blur pass) here...
//-> Use this texture in FS to compute the final result
//within COLOR_ATTACHEMENT1 (tex_2) -> The final result
}
}
FrameBuffer->Unbind()
But in my mind there is a problem because I need for each render pass to bind an external texture as an input in my fragment shader. Consequently, the first binding of the texture (the color_attachment) is lost!
So does it exist a way to solve my problem using one FBO or do I need to use 2 separate FBOs ?
I can think of at least 3 distinct options to do this. Where the 3rd one will actually not work in OpenGL ES, but I'll explain it anyway because you might be tempted to try it otherwise, and it is supported in desktop OpenGL.
I'm going to use pseudo-code as well to cut down on typing and improve readability.
2 FBOs, 1 attachment each
This is the most straightforward approach. You use a separate FBO for each texture. During setup, you would have:
attach(fbo1, ATTACHMENT0, tex1)
attach(fbo2, ATTACHMENT0, tex2)
Then for rendering:
bindFbo(fbo1)
render pass 1
bindFbo(fbo2)
bindTexture(tex1)
render pass 2
1 FBO, 1 attachment
In this approach, you use one FBO, and attach the texture you want to render to each time. During setup, you only create the FBO, without attaching anything yet.
Then for rendering:
bindFbo(fbo1)
attach(fbo1, ATTACHMENT0, tex1)
render pass 1
attach(fbo1, ATTACHMENT0, tex2)
bindTexture(tex1)
render pass 2
1 FBO, 2 attachments
This seems to be what you had in mind. You have one FBO, and attach both textures to different attachment points of this FBO. During setup:
attach(fbo1, ATTACHMENT0, tex1)
attach(fbo1, ATTACHMENT1, tex2)
Then for rendering:
bindFbo(fbo1)
drawBuffer(ATTACHMENT0)
render pass 1
drawBuffer(ATTACHMENT1)
bindTexture(tex1)
render pass 2
This renders to tex2 in pass 2 because it is attached to ATTACHMENT1, and we set the draw buffer to ATTACHMENT1.
The major caveat is that this does not work with OpenGL ES. In ES 2.0 (without using extensions) it's a non-starter because it only supports a single color buffer.
In ES 3.0/3.1, there is a more subtle restriction: They do not have the glDrawBuffer() call from full OpenGL, only glDrawBuffers(). The call you would try is:
GLenum bufs[1] = {GL_COLOR_ATTACHMENT1};
glDrawBuffers(bufs, 1);
This is totally valid in full OpenGL, but will produce an error in ES 3.0/3.1 because it violates the following constraint from the spec:
If the GL is bound to a draw framebuffer object, the ith buffer listed in bufs must be COLOR_ATTACHMENTi or NONE.
In other words, the only way to render to GL_COLOR_ATTACHMENT1 is to have at least two draw buffers. The following call is valid:
GLenum bufs[2] = {GL_NONE, GL_COLOR_ATTACHMENT1};
glDrawBuffers(bufs, 2);
But to make this actually work, you'll need a fragment shader that produces two outputs, where the first one will not be used. By now, you hopefully agree that this approach is not appealing for OpenGL ES.
Conclusion
For OpenGL ES, the first two approaches above will work, and are both absolutely fine to use. I don't think there's a very strong reason to choose one over the other. I would recommend the first approach, though.
You might think that using only one FBO would save resources. But keep in mind that FBOs are objects that contain only state, so they use very little memory. Creating an additional FBO is insignificant.
Most people would probably prefer the first approach. The thinking is that you can configure both FBOs during setup, and then only need glBindFramebuffer() calls to switch between them. Binding a different object is generally considered cheaper than modifying an existing object, which you need for the second approach.
Consequently, the first binding of the texture (the color_attachment)
is lost!
No, it isn't. Maybe your framebuffer class works that way, but then, it would be a very bad abstraction. The GL won't detach a texture from an FBO just because you bind this texture to some texture unit. You might get some undefined results if you create a feedback loop (rendering to a texture you are reading from).
EDIT
However, as #Reto Koradi pointed out in his excellent answer, (and his comment to this one), you can't simply render to a single color attachment in unextended GLES1/2, and need some tricks in GLES3. As a result, The fact I'm pointing out here is still true, but not really helpful for the ultimate goal you are trying to achieve.

Resources