Im currently working on my portfolio in A-frame. I'm trying to change the material of the objects to a wireframe material, i'm using the wireframe material (MeshBasicMaterial) of THREE.js to do that. If I put this material on objects like "a-box" or "a-sphere" it works, but i need to put this wireframe material on my 3D gltf model. Is this possible?
This is my script to call the material:
AFRAME.registerComponent('wireframe', {
dependencies: ['material'],
init: function () {
this.el.components.material.material.wireframe = true;
}
});
<a-box position="-1 0.5 -3" rotation="0 45 0" material="color: blue" wireframe></a-box>
It is possible to modify the material on a gltf.
One way to do it is to drill into the THREEjs level of the mesh inside the gltf, and create a new material and assign it a wireframe property.
AFRAME.registerComponent('tree-manager', {
init: function () {
let el = this.el;
let comp = this;
let data = this.data;
comp.scene = el.sceneEl.object3D;
comp.counter = 0;
comp.treeModels = [];
comp.modelLoaded = false;
// After gltf model has loaded, modify it materials.
el.addEventListener('model-loaded', function(ev){
let mesh = el.getObject3D('mesh');
if (!mesh){return;}
//console.log(mesh);
mesh.traverse(function(node){
if (node.isMesh){
let mat = new THREE.MeshStandardMaterial;
let color = new THREE.Color(0xaa5511);
mat.color = color;
mat.wireframe = true;
node.material = mat;
}
});
comp.modelLoaded = true;
});
}
});
<a-entity id="tree" gltf-model="#tree-gltf" scale="5 5 5" tree-manager></a-entity>
Here is a glitch that shows how.
Related
I have a .glb model which has multiple objects and materials. I'm updating the children that have a certain material at runtime, but as there are nearly 1,000 children with this material it's choking a little. I'm looking for a more elegant solution if it exists.
AFRAME.registerComponent('apply-texture', {
schema: {
texture: { type: 'string' }
},
init: function() {
var data = this.data;
var el = this.el;
var loader = new THREE.TextureLoader();
var alphamap = loader.load("./images/PETAL-DATA.jpg");
el.object3D.traverse(function(child) {
if (child.isMesh && child.material.name == "Material_to_update") {
var texture = loader.load(data.texture,
function ( texture ) {
child.material.map = texture;
child.material.alphaMap = alphamap;
child.material.map.encoding = THREE.sRGBEncoding;
child.material.side = THREE.DoubleSide;
child.material.color.set("#FFFFFF");
child.material.alphaTest = 0.5;
child.material.opacity = 0.8;
child.material.needsUpdate = true;
},
function ( xhr ) {
console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
},
function ( xhr ) {
console.log( 'An error happened' );
}
);
}
As all the child objects are using the same material I'm hoping there's a way of just updating the material itself rather than the 1,000 children.
Instead of creating 1000 image loaders, use a system which will provide the loaded texture to each component:
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script>
// register system
AFRAME.registerSystem("image-provider", {
init: function() {
// load some external image
this.image = new THREE.TextureLoader().load(
"https://i.imgur.com/wjobVTN.jpg"
);
},
getImage() {
// the components will grab the image via this function
return this.image;
}
});
// register the component
AFRAME.registerComponent("image-provider", {
init: function() {
// grab the system reference
const system = this.system;
// wait until the model is loaded
this.el.addEventListener("model-loaded", e => {
// grab the mesh
const mesh = this.el.getObject3D("mesh");
// apply the texture from the system
mesh.traverse(node => {
if (node.material) {
node.material.map = system.getImage();
node.material.needsUpdate = true;
}
});
});
}
});
</script>
<body>
<div style="z-index: 99999; position: fixed; top: 2.5%">
<p>
Model by Kelli Ray
</p>
</div>
<a-scene background="color: #FAFAFA">
<a-assets>
<a-asset-item id="model" src="https://cdn.glitch.com/994b27e1-a801-47b8-9b52-9547b8f25fc1%2Fgoogle%20poly.gltf"></a-asset-item>
<a-mixin id="fish" gltf-model="#model" scale="0.001 0.001 0.001" image-provider></a-mixin>
</a-assets>
<a-text position="-1.5 2 -3" color="black" value="original"></a-text>
<a-entity gltf-model="#model" position="-1 1 -3" scale="0.001 0.001 0.001"></a-entity>
<a-entity position="0 1 -3" mixin="fish"></a-entity>
<a-entity position="1 1 -3" mixin="fish"></a-entity>
<a-entity position="2 1 -3" mixin="fish"></a-entity>
<a-entity position="3 1 -3" mixin="fish"></a-entity>
</a-scene>
If You have multiple identical objects, you should look into Instanced Meshes - which allow you to place multiple objects while creating only one draw call. Check out this SO thread. You can check it out here (source)
"I guess what I'm trying to do is avoid having to traverse the model and just make an update to 1 material in the model, which in turn updates all objects that have that material applied"
If multiple mesh are actually sharing the same exact material (meaning they have the same uuid), if you update a property of the material then it'll update on all mesh using that material. I'm guessing they're not all sharing the same material though.
From what you've said, I think you just need to make your updates to the first mesh's material, create a variable referencing that material, then traverse your model and apply that material to every applicable mesh. For memory sake, it would also be good to .dispose() the materials that your replacing.
I've got a textured model in three.js and I want to be able to swap out the texture defined in the .gltf file when the page loads. I've looked here for inspiration.
"images": [
{
"uri": "flat_baseColor.png"
},
// etc
So to update the texture I do
var images = [
"./textures/01.jpg",
// "./textures/01.jpg",
];
var texture = new THREE.TextureLoader().load( images[0] );
var my_material = new THREE.MeshBasicMaterial({map: texture});
// load the model
var loader = new GLTFLoader().setPath( 'models/gltf/' ); // trex
loader.load( 'creature_posed.gltf', function ( gltf )
{
gltf.scene.traverse( function ( child )
{
if ( child.isMesh )
{
// The textures go for a double flip, I have no idea why
// Texture compensation
texture.flipX = false;
texture.flipY = false;
child.material = my_material;
texture.needsUpdate = true;
}
} );
var model = gltf.scene;
Only the texture is considerably pale. :(
I've tested it against itself, so it's not the texture). What have a missed out?
When loading textures you'll need to pay attention to colorspace: if the texture has color data (like .map or .emissiveMap) it's probably sRGB.
texture.encoding = THREE.sRGBEncoding;
See GLTFLoader docs and color management in three.js. This assumes that renderer.outputEncoding = THREE.sRGBEncoding as well.
three.js r113
I am trying to apply a texture to my 3D model with Three JS.
I tried a lot of ways from the Three JS Examples but for any reasons nothing is working.
Here is how I apply the texture to my model:
//LOADING TEXTURE
var textureLoader = new THREE.TextureLoader();
var diffuse = textureLoader.load( "models/Carbon.png" );
diffuse.encoding = THREE.sRGBEncoding;
diffuse.wrapS = THREE.RepeatWrapping;
diffuse.wrapT = THREE.RepeatWrapping;
diffuse.repeat.x = 1;
diffuse.repeat.y = 1;
var normalMap = textureLoader.load( "models/Carbon_Normal.png" );
normalMap.wrapS = THREE.RepeatWrapping;
normalMap.wrapT = THREE.RepeatWrapping;
var material = new THREE.MeshPhysicalMaterial({
roughness: 0.5,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
map: diffuse,
normalMap: normalMap
});
// LOADING MODEL
var loader = new THREE.GLTFLoader();
loader.load("models/stairs.gltf", function(gltf){
model = gltf.scene
model.traverse((newModel) => {
if (newModel.isMesh){
newModel.material = material;
newModel.castShadow = true;
newModel.receiveShadow = true;
}
});
scene.add(model);
});
I appreciate any help!
When loading a texture for a glTF model, you have to set Texture.flipY to false to satisfy the uv convention of glTF.
diffuse.flipY = false;
normalMap.flipY = false;
Besides, you always have to ensure that your model has texture coordinates. You can do this by checking if the geometries of your meshes have an uv attribute. If this attribute is missing, I suggest you generate texture coordinates in a DCC tool like Blender.
three.js R112
I'm doing a student project at involves a gift box where users can change how it looks.
I started learning what to do by making a cube, importing a texture and setting a gui.dat control to allow the user to change the texture.
I'm now trying to replace the cube with a blender model of a gift box but I'm having trouble changing the texture.
EDIT: The full code is on github here:
https://github.com/GitKiwi/GiftBox/blob/master/Workspace/Proto%208c%20Changing%20textures%20on%20giftbox.html
The coding for the working cube model is:
`// add cube with texture
var cubeGeometry = new THREE.CubeGeometry(4,4,4);
var cubeMaterial = new THREE.MeshLambertMaterial({ map:
THREE.ImageUtils.loadTexture("birthday.jpg") });
var cube = new THREE.Mesh(cubeGeometry,cubeMaterial);
cube.position.set (0,0,0);
cube.rotation.set (0,-1.2,0);
cube.receiveShadow = true;
// add the cube to scene
scene.add(cube); `
//gui texture change
`var controls = new function()
{ this.changeTexture = "birthday";
this.changeTexture = function (e){
var texture = THREE.ImageUtils.loadTexture
("../assets/textures/general/" + e + ".jpg");
cube.material.map = texture; }`
//gui control
var gui = new dat.GUI();
gui.add(controls, "changeTexture", ['christmas', 'valentine', 'birthday']).onChange(controls.changeTexture);
I'm loading the gift box in four parts and I'm just trying to get the first part, the box, to change texture. I load it with:
var box;
var loaderOne = new THREE.JSONLoader();
loaderOne.load('../assets/models/box.js', function (geometry)
{
var material = new THREE.MeshLambertMaterial({color: 0xffff00});
box = new THREE.Mesh(geometry, material);
box.position.set (5,0,5);
box.scale.set (1,1,1);
//box.name = "mybox";
scene.add(box);
});
I can't get it to change texture with the gui control. I've tried changing the "cube" to "box" in the gui texture change code and I've tried naming the box and calling it(commented out in the code above) but those didn't work. I've searched for answers to this in a number of places but I'm just really stuck. I feel I'm perhaps missing something obvious?
Any help to would really be appreciated.
The code wasn't working because there were no texture maps for the model I was importing.
What I did was go back to Blender and create a model with two textures that could each be applied to the whole model. The exported JSON file then had the model geometry and the two textures (with their texture maps).
In three.js I loaded it:
// load in geometry and textures
var loader = new THREE.JSONLoader();
loader.load('../models/two textures on cube.js', function (geometry, material)
{
matOne = new THREE.MeshLambertMaterial(material[1]);
matTwo = new THREE.MeshLambertMaterial(material[2]);
box = new THREE.Mesh(geometry, matOne);
//position, scale
box.position.set (0,0,0);
box.rotation.set (0,-1.2,0);
box.scale.set (2,2,2);
box.receiveShadow = true;
scene.add(box);
}, '../models');
and then used this code to switch the textures:
//gui control panel
var controls = new function()
{
//changing the texture
this.changeTexture = function (e)
{
switch (e)
{
case "birthday":
box.material = matOne;
break;
case "christmas":
box.material = matTwo;
break;
}
}
}
with this code for the gui.dat controls:
//gui control panel
var gui = new dat.GUI();
gui.add(controls, "changeTexture", ['birthday', 'christmas']).onChange(controls.changeTexture);
I've loaded a Blender model using the three.js library and want to allow the users to change the texture of some faces through an input field in a form. I don't have any problem when I use the WebGLRenderer, and it works fine in Chrome, but it doesn't work with the canvas renderer when the texture coming from the input is in data:image... format. Seems if I load a full path image from the server it works fine. Does anybody know if there's a way to load textures this way and render them with the canvasrenderer?
Thank you.
I add here the code after I set the camera, lights and detect it the browswer detects webgl or not to use the WebGLRenderer or the CanvasRenderer.
First I load the model from blender:
var loader = new THREE.JSONLoader();
loader.load('assets/models/mimaquina9.js', function (geometry, mat) {
//I set the overdraw property to 1 for each material like i show here for 16
mat[16].overdraw = 1;
mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(mat) );
mesh.scale.x = 5;
mesh.scale.y = 5;
mesh.scale.z = 5;
scene.add(mesh);
}, 'assets/images');
render();
//To render, I try to make an animation in case WebGL is available and just render one frame in case of using the canvas renderer.
function render() {
if(webgl){
if (mesh) {
mesh.rotation.y += 0.02;
}
// render using requestAnimationFrame
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
else if(canvas){
camera.position.x = 30;
camera.position.y = 20;
camera.position.z = 40;
camera.lookAt(new THREE.Vector3(0, 10, 0));
setTimeout(function (){
//something you want delayed
webGLRenderer.render(scene, camera);
}, 1000);
}
}
$('#datafile').change(function(e)
{
e.preventDefault();
var f = e.target.files[0];
if(f && window.FileReader)
{
var reader = new FileReader();
reader.onload = function(evt) {
console.log(evt);
mesh.material.materials[16].map = THREE.ImageUtils.loadTexture(evt.target.result);
if(canvas && !webgl){
//I read that might be a problem of using Lambert materials, so I tried this commented line without success
//mesh.material.materials[16] = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture(evt.target.result)});
//If I uncomment the next line, it displays the texture fine when rendering after.
//mesh.material.materials[16].map = THREE.ImageUtils.loadTexture("assets/images/foto.jpg");
render();
}
}
reader.readAsDataURL(f);
}
});
Thanks once more.
evt.target.result is a DataURL so you should assign that to a image.src. Something like this should work:
var image = document.createElement( 'img' );
image.src = evt.target.result;
mesh.material.materials[16].map = new THREE.Texture( image );