envMap and MeshPhong Material in three.js - reflection

I tried to add an environmental map to my PhongMaterial, but when I do so my geometry disappears. Here goes my code:
var reflection = THREE.ImageUtils.loadTextureCube( [ 'textures/hdr/pos-x.png', 'textures/hdr/neg-x.png', 'textures/hdr/pos-y.png', 'textures/hdr/neg-y.png', 'textures/hdr/pos-z.png', 'textures/hdr/neg-z.png' ] );
material = new THREE.MeshPhongMaterial(
{
map: textures.color,
normalMap: textures.normal,
specularMap: textures.specular,
envMap: reflection,
combine: THREE.MixOperation,
reflectivity: 0.25,
specular: 0xffffff,
}
);
If I change the Phong to a Lambert material, I can see thee geometry and the reflection. Do you have any idea what I did wrong?
Update: I have found out that the normal and the envMap don't work together. So the envMap works if I don't you use a normal Map and the normalMap only works without the envMap. Is this a known issue and is there any way I can add both maps to my mehsphong material?

EnvMap and normalMap can work together - example. You must have spotlight on the scene . Otherwise you will get "'vWorldPosition' : undeclared identifier" error.
Seems to be the reason is in phong shader:
"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
"varying vec3 vWorldPosition;",
"#endif"

Related

Change opacity of glb 3D model with aframe

I am trying to make a web app where I can use world tracking to view a 3D model. I am trying to change the opacity of the model using the material property of the a-entity tag, however it does not seem to be working. any idea on how to change the opacity of my 3D object in order to make it translucent?
<a-entity
id="model"
gltf-model="#3dmodel"
class="cantap"
geometry="primitive: box"
scale="0.5 0.5 0.5"
material="transparent: true; opacity 0.1"
animation-mixer="clip: idle; loop: repeat"
hold-drag two-finger-spin pinch-scale>
</a-entity>
GLTF models come with their own materials included in the model file. These are handled by THREE.js rather than AFrame, so in order to access their properties you have to search through the elements object3D, using something like this:
this.el.object3D.traverse((child) => {
if (child.type === 'Mesh') {
const material = child.material;
// Do stuff with the material
material.opacity = 0.1;
}
})
See the THREE.js documentation on Materials and Object3D for more detail.
Also, I noticed you have both a geometry primitive and a gltf-model on that entity. Not sure how well those two things can co-exist, if you want both a box and the model you should probably make one a child of the other.
E Purwanto's answer above works for me with an addition of setting transparency true:
this.el.object3D.traverse((child) => {
if (child.type === 'Mesh') {
const material = child.material;
// Do stuff with the material
material.transparent = true; // enable to modify opacity correctly
material.opacity = 0.1;
}
})

Aframe gltf-model demo with envmap

It's very convenient to load GLTF- model in aframe, but no case is found that contains envmap texture. I'd like to see that the official can provide the same case as three official. pmremGenerator.fromEquirectangular(texture) function is used to make gltf model produce real reflection effect
https://threejs.org/examples/#webgl_loader_gltf
https://threejs.org/examples/#webgl_materials_envmaps_hdr
One way would be creating a custom component, which will:
wait until the model is loaded
traverse through the object's children
if they have a material property - apply the envMap
The envmap needs to be a CubeTexture - which adds another level of complication, when you want to use a panorama. You can use a the WebGLRenderTargetCube - It's an object which provides a texture from a Cube Camera 'watching' the panorama.
Overall The component code could look like this:
// create the 'cubecamera' objct
var targetCube = new THREE.WebGLRenderTargetCube(512, 512);
var renderer = this.el.sceneEl.renderer;
// wait until the model is loaded
this.el.addEventListener("model-loaded", e => {
let mesh = this.el.getObject3D("mesh");
// load the texture
var texture = new THREE.TextureLoader().load( URL,
function() {
// create a cube texture from the panorama
var cubeTex = targetCube.fromEquirectangularTexture(renderer, texture);
mesh.traverse(function(node) {
// if a node has a material attribute - it can have a envMap
if (node.material) {
node.material.envMap = cubeTex.texture;
node.material.envMap.intensity = 3;
node.material.needsUpdate = true;
}
});
}
Check it out in this glitch.
I was having the same issue and i found that cube-env-map from a-frame-extras works like a charm.
View component on GitHub
Its docs describe it as:
Applies a CubeTexture as the envMap of an entity, without otherwise
modifying the preset materials
And the code is super simple:
yarn add aframe-extras
import 'aframe-extras'
<a-entity
gltf-model="src: url('/path/to/file.glb')"
cube-env-map="path: /cubeMapFolder/;
extension: jpg;
reflectivity: 0.9;">
</a-entity>
In THREE demo, I remember that WebGLRenderTargetCube was used to produce envmap, but recently it was found thatPMREMGenerator was basically used to generate envmap texture with mipmap. It also supports HDR image format, making gltf model better than JPG texture.
I don't know how these JS modules PMREMGenerator and RGBELoader are used together with the components of Aframe. Can someone provide such an example in Aframe ,Thanks
That's the same High dynamic range (RGBE) Image-based Lighting (IBL) using run-time generated pre-filtered roughness mipmaps (PMREM)

reference to normalMap in material returning undefined

I am attempting to make a simple demo, using dat.GUI to manipulate a material on an Aframe primitive . On the entity tag, I create a material, an assign a texture to the normal slot.
<a-entity id="circle" geometry="primitive:circle; radius: 2; segments: 96"
rotation="-90 0 0"
material="color: #335500; transparent: true; opacity: 0.9;
normalMap: #watertex; side: double"
water ></a-entity>
Then, in a custom component, I access its THREEjs mesh and material.
I am able to get a reference to the material, but when I try to access the normalMap, it returns undefined.
I added a listener, to wait for the "loaded" event. I am not using gltf, so "model-loaded" is unnecessary.
I used getObject3D("mesh"), and traversed the mesh, but still, the normal map still returns undefined. The puzzling part is that in the console, I log the material, and can clearly see the normalMap. But when log it, it returns undefined.
AFRAME.registerComponent("water",{
init: function(){
let el = this.el;
//console.log(el);
let comp = this;
this.counter=0;
el.addEventListener("loaded", function(ev){
console.log('model loaded');
let mesh = el.getObject3D('mesh');
if (!mesh){return;}
mesh.traverse(function(node){
if (node.isMesh){
console.log(node);
console.log(node.material);
console.log(node.material.normalMap); <-- returns
undefined
}
});
});
},
});
<a-entity id="circle" geometry="primitive:circle; radius: 2; segments: 96"
rotation="-90 0 0"
material="color: #335500; transparent: true; opacity: 0.9;
normalMap: #watertex; side: double"
water ></a-entity>
I am surprised that a reference to a material shows the normal map
console.log(node.material);
but a direct reference to the normal map fails
console.log(node.material.normalMap);
I suspect that perhaps something is strange with texture maps made with aframe not being accessible from Threejs, but I don't know why or how to test this.
Ultimately I want to use datGUI to control params in the materials.
I notice that AFrame material component does not expose all the parameters of threejs materials. Also accessing some aframe material parameters with datGUI seem a bit buggy. So this is why I want to access threejs directly.
Here is a link to the project on my server, where you can see the console logs.
http://sensorium.love/experiments/demos/water/waterDemo1.html
and here is a glitch if you want to play with the code
https://glitch.com/~water-demo
It seems to be a race, where you try to access the normalMap just before it's applied.
There is a materialtextureloaded event (docs) which seems to do the trick:
el.addEventListener('materialtextureloaded', e => {
// nomalMap is accessible here
})
Otherwise you can also use a timeout or interval to determine when the normalMap isn't null.
I've seen ar.js do:
let timer = setInterval(e => {
if (!someVal) return;
clearInterval(timer);
// here someVal isn't null or undefined
}, 500)
Glitch here.
I'm pretty sure you could see the normalMap in the console, because the expression gets evaluated once you click the arrow in the browser console. On chrome there's a blue "i" which says "value below was evaluated just now".

How do I use the setting "Park (Day)" for the environment under Lighting from the gltf viewer to A-Frame?

I'm trying to load a gltf model (link) to A-frame, but it appears dark, I've checked it using the link and the difference is that in the gltf viewer, under lighting, there's a field called environment which is set to "Park (Day)".
with environment set to: None
with environment set to: Park (Day)
How do I apply this setting to my model in A-Frame?
The other difference is the gammeOutput property which I have already fixed with "colorManagement: true" in the a-scene renderer.
Currently using version 0.9.0 of A-Frame
If you see an image being reflected by the model, it's an environment map. Its used when you want your object to reflect its surroundings or any other environment.
You can set it on primitives with either envMap (cubemap) or sphericalEnvMap (360 image) property:
<a-sphere material="roughness:0; sphericalEnvMap: #myImage>
Check it out in this fiddle.
With models, you'd need to dig in a bit deeper. You'd need to traverse the model, and set each mesh envMap property:
let texture = THREE.TextureLoader()
const mesh = element.getObject3D('mesh');
const envMap = texture;
if (!mesh) return;
mesh.traverse(function (node) {
if (node.material && 'envMap' in node.material) {
node.material.envMap = envMap;
node.material.needsUpdate = true;
}
});

What's the easiest way to render a 2D shape in an A-Frame scene?

Say, a shape of a human outline.
Ideally it could be converted to 3d by extruding, but even if it has no depth, that's fine for my use case.
I think the easiest way would be taking a transparent png image (the human outline), and use it as a source for an a-plane
<a-plane material="src: img.png; transparent: true"></a-plane>
Glitch here.
....but if you want to create a geometry with a custom shape, which will be helpful for extrusion, then check this out:
Creating a simple shape with the underlying THREE.js
First you need an array of 2D points:
let points = [];
points.push(new THREE.Vector2(0, 0));
// and so on for as many as you want
Create a THREE.Shape object which vertices will be created from the array
var shape = new THREE.Shape(points);
Create a mesh with the shape geometry, and any material, and add it to the scene, or entity
var geometry = new THREE.ShapeGeometry(shape);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
var mesh = new THREE.Mesh(geometry, material);
entity.object3D.add(mesh);
More on:
1) THREE.Shape
2) THREE.ShapeGeometry
3) THREE.Mesh
Extrusion
Instead of the ShapeGeometry you can use the ExtrudeGeometry object:
var extrudedGeometry = new THREE.ExtrudeGeometry(shape, {amount: 5, bevelEnabled: false});
Where the amount is basically the "thickness". More on Three.ExtrudeGeometry here.
Usage with AFRAME
I'd recommend creating an AFRAME custom component:
js
AFRAME.registerComponent('foo', {
init: function() {
// mesh creation
this.el.object3D.add(mesh);
}
})
HTML
<a-entity foo></a-entity>
2D shape here.
Extruded 2D shape here.
Three.js examples here. They are quite more complicated than my polygons :)
There are also a couple of pre-built A-Frame components you could use to help with extrusion.
https://github.com/JosePedroDias/aframe-extrude-and-lathe
https://github.com/luiguild/aframe-svg-extruder
You can find examples of usage in each of those repos.

Resources