<input type="text" id="modelURL" placeholder="Enter GLB model URL...">
<!-- Textbox for Y-offset value -->
<input type="text" id="yOffset" placeholder="Enter Y-offset...">
<input type="text" id="rotation" placeholder="Enter X-Axis Rotation offset...">
<!-- Button to load model -->
<button onclick="loadModel()">Load Model</button>
<!-- Button to export positioned model -->
<button onclick="exportModel()">Export Model</button>
<button id = "changePosition" onclick="changePosition()">Change Position</button>
<script>
let modelName = "";
let scene, camera, renderer, model;
const ktx2Loader = new THREE.KTX2Loader();
ktx2Loader.setTranscoderPath('');
const loader = new THREE.GLTFLoader();
const dracoLoader = new THREE.DRACOLoader();
init();
animate();
function setTexture(object) {
const material = object.material;
const setTextureEncoding = (texture) => {
if (texture) {
texture.encoding = THREE.sRGBEncoding;
}
};
setTextureEncoding(material.map);
const setRMTextureEncoding = (texture) => {
if (texture) {
texture.encoding = THREE.sRGBEncoding;
texture.minFilter = THREE.LinearMipMapNearestFilter; // Ensure highest quality of filtering
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); // This will make textures look better at glancing angles
}
};
setRMTextureEncoding(material.map);
setRMTextureEncoding(material.roughnessMap);
setRMTextureEncoding(material.metalnessMap);
setRMTextureEncoding(material.aoMap);
// if (material.transparent) {
// // Adjust roughness and metalness values
// material.roughness = 0.1; // Increase roughness as desired
// material.metalness = 0.1; // Increase metalness as desired
// // Enable clearcoat and adjust clearcoat settings
// // material.clearcoat = 1.0; // Enable clearcoat
// // material.clearcoatRoughness = 0.1; // Adjust clearcoat roughness as desired
// material.envMapIntensity = 1;
// }
setRMTextureEncoding(material.alphaMap);
// if(material.transparent) {
// setRMTextureEncoding(material.alphaMap);
// }
// material.polygonOffset = true;
// material.polygonOffsetFactor = 1; // Positive value pushes object back, negative brings forward
// material.polygonOffsetUnits = 1;
material.needsUpdate = true;
// if (object.isMesh && object.material) {
// // node.material.depthTest = true;
// object.renderOrder = 2;
// }
return object;
}
function applyTransformationsAndComputeVertex(mesh) {
// Clone the current geometry
const geometry = mesh.geometry.clone();
// Apply the mesh's matrix to the geometry (this applies transformations)
geometry.applyMatrix4(mesh.matrixWorld);
// Now you can access the transformed vertex values
const vertices = geometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i];
const y = vertices[i + 1];
const z = vertices[i + 2];
// Now, x, y, and z are the transformed coordinates of the vertex
console.log(x, y, z);
}
}
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.0001, 10000);
camera.position.z = 0.2
renderer = new THREE.WebGLRenderer({
antialias: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
dracoLoader.setDecoderPath('');
loader.setDRACOLoader(dracoLoader);
loader.setKTX2Loader(ktx2Loader);
ktx2Loader.detectSupport(renderer);
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(0, 1, 1);
scene.add(directionalLight);
}
function loadModel() {
const url = document.getElementById("modelURL").value;
if (!url) {
alert("Please provide a model URL!");
return;
}
var parts = url.split('/');
modelName = parts[parts.length - 1];
// Clear existing model if any
if (model) {
scene.remove(model);
model = null;
}
loader.load(url,
(gltf) => {
const promises = [];
gltf.scene.traverse((node) => {
// if (node.isMesh || node.isGroup) {
// freezeTransformationsForObject(node);
// }
if (node.isMesh && node.material) {
// if (node.material.roughnessMap && node.material.metalnessMap) {
// const promise = combineTextures(node.material.roughnessMap, node.material.metalnessMap)
// .then(combinedTexture => {
// node.material.roughnessMap = combinedTexture;
// node.material.metalnessMap = combinedTexture;
// });
// promises.push(promise);
// }
node = setTexture(node);
}
});
Promise.all(promises).then(() => {
model = gltf.scene.children[0];
// scene.add(gltf.scene.children[0]);
scene.add(model);
});
}, undefined,
function (error) {
console.error("An error occurred while loading the GLTF model:", error);
}
);
}
const options = {
binary: true, // This ensures you get a .glb file and not a .gltf + assets
// Other options can be added here if needed
};
function changePosition() {
if (!model) {
alert("No model to export!");
return;
}
// propagateTransformDownward(model);
// resetTransformations(model);
// Get the Y-offset value from the input field
const yOffsetValue = parseFloat(document.getElementById("yOffset").value);
const xrotOffset = parseFloat(document.getElementById("rotation").value);
if (isNaN(yOffsetValue)) {
alert("Please provide a valid Y-offset value!");
return;
}
document.getElementById("changePosition").style.display = "none";
// Store the original Y position
// const originalYPosition = model.position.y;
// console.log(model.position.y);
// // Apply the Y-offset to the model
model.position.y += yOffsetValue/1000;
// model.rotation.y += yOffsetValue/1000;
// model.position.z += 4/1000;
// propagateTransformDownward(model);
// resetTransformations(model);
}
function propagateTransformDownward(object, parentMatrix) {
if (parentMatrix) {
object.applyMatrix4(parentMatrix);
}
const currentMatrix = object.matrixWorld.clone();
for (const child of object.children) {
propagateTransformDownward(child, currentMatrix);
}
}
function resetTransformations(object) {
object.position.set(0, 0, 0);
object.rotation.set(0, 0, 0);
object.scale.set(1, 1, 1);
object.updateMatrix();
// If it's a mesh, apply the transformation to its geometry.
if (object.isMesh && object.geometry) {
const geometry = object.geometry.clone();
geometry.applyMatrix4(object.matrixWorld);
object.geometry = geometry;
object.geometry.needsUpdate = true;
}
// Reset transformations for children too
for (const child of object.children) {
resetTransformations(child);
}
}
function combineTextures(roughnessMap, metalnessMap) {
return new Promise((resolve) => {
const width = roughnessMap.image.width;
const height = roughnessMap.image.height;
const roughnessData = new Uint8Array(roughnessMap.image.data);
const metalnessData = new Uint8Array(metalnessMap.image.data);
const mergedData = new Uint8Array(width * height * 4);
const roughnessAdjustmentValue = 5; // Adjust this value as needed
const metalnessAdjustmentValue = 230; // Start with this value, then adjust as needed
for (let i = 0; i < width * height; i++) {
mergedData[i * 4] = 0; // Red channel (unused)
mergedData[i * 4 + 1] = Math.max(0, metalnessData[i] - metalnessAdjustmentValue); // Green channel (adjusted metalness)
mergedData[i * 4 + 2] = Math.min(150, roughnessData[i] + roughnessAdjustmentValue); // Blue channel (adjusted roughness)
mergedData[i * 4 + 3] = 255; // Alpha channel (full opacity)
}
const mergedTexture = new THREE.DataTexture(mergedData, width, height, THREE.RGBAFormat);
mergedTexture.needsUpdate = true;
resolve(mergedTexture);
});
}
function exportModel() {
if (!model) {
alert("No model to export!");
return;
}
document.getElementById("changePosition").style.display = "block";
// Get the Y-offset value from the input field
const yOffsetValue = parseFloat(document.getElementById("yOffset").value);
if (isNaN(yOffsetValue)) {
alert("Please provide a valid Y-offset value!");
return;
}
const exporter = new THREE.GLTFExporter();
const options = {
binary: true, // This ensures you get a .glb file and not a .gltf + assets
// Other options can be added here if needed
};
exporter.parse(model, function (result) {
if (result instanceof ArrayBuffer) {
saveArrayBuffer(result, modelName ); // Ensure the extension is .glb
} else {
const output = JSON.stringify(result, null, 2);
saveString(output, modelName + '.gltf'); // This would be for non-binary output
}
}, options);
// Revert the Y-offset to its original value after exporting
}
function saveString(text, filename) {
const blob = new Blob([text], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
function saveArrayBuffer(buffer, filename) {
const blob = new Blob([buffer], { type: 'application/octet-stream' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
// if (model) {
// model.rotation.y += 0.005;
// }
renderer.render(scene, camera);
}
</script>```
I am working on a code to automate positioning of some sunglasses model. The above code works fine, but noticed that after exporting the models look too shiny. I got a warning in console saying that the model has different metallic and roughness map, but even after trying to combine it through the code before exporting, it looks reflective. Is there a workaround? I also would like to know if there is a way to bake the transforms of all the nodes (groups and meshes). I did try different approaches, as you can see in the code, but it still didn't give me good results.
Does the model look alright if you drop it in gltf-viewer ?
Of these textures, only .map
should be using sRGB. There rest use THREE.LinearEncoding
(or .colorSpace = THREE.NoColorSpace
in newer three.js versions).
GLTFLoader will assign the correct color spaces for each texture it loads automatically.
1 Like
Check what type of material is applied to your model. You may need to reassign it from your glossy to something more mat or export your model with different materials.