this picture is no problem. but under picture is problem.
I am using aframe and https://threejs.org/docs/#examples/en/controls/TransformControls
<a-scene embedded>
<!--
<a-entity data-type="entity" id="yellowSphere" geometry="primitive: sphere" material="color:#ff0; metalness:0.0; roughness:1.0" position="-2 2 -2"></a-entity>
-->
<a-entity gltf-model="url(https://cdn.aframe.io/examples/ar/models/triceratops/scene.gltf)" animation-mixer scale="0.1 0.1 0.1"></a-entity>
<a-sky data-type="sky" color="#fff"></a-sky>
</a-scene>
this.transformControls = new TransformControls(this.camera, this.canvasEl);
this.transformControls.size = 0.75;
this.transformControls.addEventListener('objectChange', (evt) => {
const object = this.transformControls.object;
if (!object) {
return;
}
console.log(evt, object);
this.selectionBox.setFromObject(object).update();
});
The code is trying to move the skinnedMesh, not the root mesh.
One solution would be adding a check for Mesh, and if there isn't one, find it with getObject3D('mesh'):
/* TransformControls, line 546 */
// Set current object
attach( object ) {
if (object.type != "Mesh") {
object = object.el.getObject3D("mesh") // this is the solution mentioned above
}
this.object = object;
this.visible = true;
return this;
}
glitch here
Related
Try return old color if the mouse is not hovering over the object, but have error
Error:
ERROR TypeError: Cannot read properties of undefined (reading 'setHex')
Code:
function hoverPieces() {
// Material
const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
for (let i = 0; i < intersects.length; i++) {
if (intersects) {
if (intersects[0].object != INTERSECTED) {
if (INTERSECTED) {
// Use hex rather than RGB (setRGB uses 3 arguments, hex one)
INTERSECTED.face.color.setHex(INTERSECTED.currentHex);
}
INTERSECTED = intersects[0];
// Save actual color so we can restore it later
INTERSECTED.currentHex = INTERSECTED.face.color.getHex();
INTERSECTED.face.color.setHex(0x777777);
INTERSECTED.object.geometry.colorsNeedUpdate = true;
}
} else {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED) {
// Use hex rather than RGB (setRGB uses 3 arguments, hex one)
INTERSECTED.face.color.setHex(INTERSECTED.currentHex);
}
// Remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
// set material
// intersects[i].object.material = material;
}
}
Gist link: https://gist.github.com/artur33s/21a9501576b1058333fd89a4b2216a4d
I've imported a GLTF file with two different meshes. My goal is to give each mesh a material with a unique custom fragment shader using onBeforeCompile. Each mesh has the same type of material (MeshNormalMaterial).
When I try to apply one fragment shader to one material and the other fragment shader to the other material, both materials wind up with the same fragment shader. The fragment shader each material has depends on which material I setup first.
Here's a few pictures showing what I'm talking about:
Below is all the relevant code.
Main code: This is the general structure of my code. I've enclosed the important part between "PRIMARY AREA OF INTEREST" comments. For simplicity, I've replaced my shader code with "..." or a comment describing what it does. They do work as shown in the pictures above.
// Three.JS Canvas
const threeDisplay = document.getElementById("threeDisplay");
// Globals
var displayDimensions = getElemDimensions(threeDisplay); // Uniform
var currentTime = 0; // Uniform
var helix = null; // Mesh
var innerHelix = null; // Mesh
var horseshoe = null; // Mesh
// Set the scene and camera up
const scene = new THREE.Scene();
const camera = initCamera();
// Setup a directional light
const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
light.position.set(-0.2, 1, -0.6);
scene.add(light);
// Setup WebGL renderer
const renderer = initRenderer();
threeDisplay.appendChild( renderer.domElement );
// Load the gltf model
new GLTFLoader().load( "./spiral_pillar_hq_horseshoe.glb", function (object) {
const helixFragmentShaderReplacements = [
{
from: ' ... ',
to: ' // rainbow '
}
];
const horseshoeFragmentShaderReplacements = [
{
from: ' ... ',
to: ' // white '
}
];
//////////////////////////////////////
// PRIMARY AREA OF INTEREST - START //
//////////////////////////////////////
// Turn the horseshoe into a shader.
horseshoe = object.scene.children[1];
var horseshoeGeometry = horseshoe.geometry;
var horseshoeMaterial = shaderMeshMaterial(new THREE.MeshNormalMaterial(), horseshoeGeometry, horseshoeFragmentShaderReplacements);
var horseshoeMesh = new THREE.Mesh(horseshoeGeometry, horseshoeMaterial);
horseshoe = horseshoeMesh;
horseshoe.rotation.z = deg2rad(180); // Re-orient the horseshoe to the correct position and rotation.
horseshoe.position.y = 13;
scene.add(horseshoe);
// Turn the inner helix into a colorful, wiggly shader.
helix = object.scene.children[0];
var helixGeometry = helix.geometry;
var helixMaterial = shaderMeshMaterial(new THREE.MeshNormalMaterial(), helixGeometry, helixFragmentShaderReplacements);
var helixMesh = new THREE.Mesh(helixGeometry, helixMaterial);
helix = helixMesh;
scene.add(innerHelix);
animate();
////////////////////////////////////
// PRIMARY AREA OF INTEREST - END //
////////////////////////////////////
}, undefined, function (error) {
console.error(error);
});
Below are functions which are relevant.
shaderMeshMaterial: Constructs a new material based on the supplied materialType that supports editing the default shader. If it's not initProcessing, then the problem may stem from this function.
// Globals used: displayDimensions
function shaderMeshMaterial(materialType, geometry, fragmentShaderReplacements) {
var material = materialType;
material.onBeforeCompile = function ( shader ) {
// Uniforms
shader.uniforms.time = { value: 0 };
shader.uniforms.resolution = { value: new THREE.Vector2(displayDimensions.width, displayDimensions.height) };
shader.uniforms.bboxMin = { value: geometry.boundingBox.min };
shader.uniforms.bboxMax = { value: geometry.boundingBox.max };
fragmentShaderReplacements.forEach((rep) => {
shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
});
console.log(shader);
material.userData.shader = shader;
}
return material;
}
initRenderer: Sets up the renderer. Just showing you guys the renderer setup I have in case that's important.
// Globals used: displayDimensions
function initRenderer() {
var renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
precision: "mediump"
});
renderer.setClearColor( 0x000000, 0);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( displayDimensions.width, displayDimensions.height );
renderer.shadowMap.enabled = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.25;
return renderer;
}
animate: Handles the animation frames.
// Globals used: renderer, currentTime, postprocessing
function animate (timestamp = 0) {
requestAnimationFrame(animate);
resizeRendererToDisplaySize(renderer);
currentTime = timestamp/1000; // Current time in seconds.
scene.traverse( function ( child ) {
if ( child.isMesh ) {
const shader = child.material.userData.shader;
if ( shader ) {
shader.uniforms.time.value = currentTime;
}
}
} );
renderer.render( scene, camera );
postprocessing.composer.render( 0.1 );
};
One last thing to note is that when I inspected the console log of shader from the shaderMeshMaterial function, I can see that the fragment shaders are indeed different as they should be for each material. Also not sure why there are 4 console logs when there should only be 2.
Sorry for all the code, but I did condense it to where all irrelevant code was stripped out. I'm fairly new to Three.JS, so any possible explanations as to why this is happening are much appreciated!
EDIT: Removed vertex shader parameter from shaderMeshMaterial function to keep this question focused on just the fragment shaders. Though this problem does apply to both the vertex and fragment shaders, I figure if you fix one then you'll fix the other.
EDIT 2: Added language identifiers to code snippets. Also I removed the postprocessing function and the problem still persists, so I know the problem isn't caused by that. I've updated the code above to reflect this change. As a happy side effect of removing the postprocessing function, the console.log of the shader variable from shaderMeshMaterial new appears twice in the log (as it should).
EDIT 3: (Implementing WestLangley's suggestion) I tweaked the shaderMeshMaterial function by adding the customProgramCacheKey function. I had to condense the four parameters of shaderMeshMaterial into one for the sake of the customProgramCacheKey function. I believe I implemented the function correctly, but I'm still getting the same result as before where both materials display the same fragment shader.
New "PRIMARY AREA OF INTEREST" code:
horseshoe = object.scene.children[1];
var horseshoeGeometry = horseshoe.geometry;
var meshData = {
materialType: new THREE.MeshNormalMaterial(),
geometry: horseshoeGeometry,
fragmentShaderReplacements: horseshoeFragmentShaderReplacements
}
var horseshoeMaterial = shaderMeshMaterial(meshData);
var horseshoeMesh = new THREE.Mesh(horseshoeGeometry, horseshoeMaterial);
horseshoe = horseshoeMesh;
horseshoe.rotation.z = deg2rad(180); // Re-orient the horseshoe to the correct position and rotation.
horseshoe.position.y = 13;
scene.add(horseshoe);
// Turn the inner helix into a colorful, wiggly shader.
helix = object.scene.children[0];
var helixGeometry = helix.geometry;
var meshData2 = {
materialType: new THREE.MeshNormalMaterial(),
geometry: helixGeometry,
fragmentShaderReplacements: helixFragmentShaderReplacements
}
var helixMaterial = shaderMeshMaterial(meshData2);
var helixMesh = new THREE.Mesh(helixGeometry, helixMaterial);
helix = helixMesh;
scene.add(innerHelix);
animate();
New shaderMeshMaterial code:
// Globals used: displayDimensions
function shaderMeshMaterial(meshData) {
var material = meshData.materialType;
material.onBeforeCompile = function ( shader ) {
// Uniforms
shader.uniforms.time = { value: 0 };
shader.uniforms.resolution = { value: new THREE.Vector2(displayDimensions.width, displayDimensions.height) };
shader.uniforms.bboxMin = { value: meshData.geometry.boundingBox.min };
shader.uniforms.bboxMax = { value: meshData.geometry.boundingBox.max };
meshData.fragmentShaderReplacements.forEach((rep) => {
shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
});
material.customProgramCacheKey = function () {
return meshData;
};
console.log(shader);
material.userData.shader = shader;
}
return material;
}
WestLangley suggestion worked for me!
material.onBeforeCompile = ...
// Make sure WebGLRenderer doesnt reuse a single program
material.customProgramCacheKey = function () {
return UNIQUE_PER_MATERIAL_ID;
};
I believe your mistake is returning meshData from customProgramCacheKey.
I think customProgramCacheKey need concrete identifier like a number or string.
It would be nice to understand what exactly happening and why do we need to specify customProgramCacheKey.
EDIT: I discover that default value for customProgramCacheKey calculated as follow in Threejs source.
customProgramCacheKey() {
return this.onBeforeCompile.toString();
}
Perhaps this is explains this default caching behavior because calling toString on function returns that function body literally as string.
For example consider function const myFunc = () => { return 1 }. Calling myFunc.toString() returns "() => { return 1 }"
So if your calling onBeforeCompile in a for loop you function body as string never change.
I'm facing a very weird experience with WebXR API. WebXR API changes the VR camera properties when I re-enter the VR. The camera somehow cuts my objects (shown below) when I re-enter the VR mode the second, third or nth time.
It always works properly (shown below) when I enter VR first time.
I would like to know why the objects are getting cut on the second/third/nth VR attempt and also how to debug WebXR immersive-vr camera properties.
I'm using very basic WebXR API codes as follows:
window.onload=function()
{
init();
animate();
}
function init()
{
canvas = document.getElementById( 'vw_canvas' );
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
canvasCssWidth = canvas.style.width;
canvasCssHeight = canvas.style.height;
group = new THREE.Object3D();//we create this to make it a parent of camera object.
camera = new THREE.PerspectiveCamera( 75, canvas.width / canvas.height, 1, 10000 );
group.rotation.order = 'XZY';//default is XYZ..we change this to XZY. because in case of XYZ when we rotate the object around its Y axis even the X and Z axis changes. so to avoid that we give higher priority to Z axis.
scene = new THREE.Scene();
group.add(camera);
scene.add(group);
//add more 3D objects to scene
renderer = new THREE.WebGLRenderer({antialias:true, powerPreference: "high-performance"});
renderer.setPixelRatio( canvas.devicePixelRatio );
renderer.setSize( canvas.width, canvas.height );
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType( 'local' );
canvas.appendChild(renderer.domElement);
var WEBVR =
{
createButton: function ( renderer )
{
function showEnterXR( /*device*/ ) {
var currentSession = null;
function onSessionStarted( session )
{
session.addEventListener( 'end', onSessionEnded );
renderer.xr.setSession( session );
vrButton.style.backgroundImage="url('icons/noVR.svg')";
document.getElementById("vr-button-tooltip").setAttribute("tooltip","Exit VR");
currentSession = session;
isVRpresenting=true;
openVRMenu();
}
function onSessionEnded( event )
{
currentSession.removeEventListener( 'end', onSessionEnded );
renderer.xr.setSession( null );
vrButton.style.backgroundImage="url('icons/yesVR.svg')";
document.getElementById("vr-button-tooltip").setAttribute("tooltip","Enter VR");
currentSession = null;
isVRpresenting=false;
closeVRMenu();
}
vrButton.style.backgroundImage="url('icons/yesVR.svg')";
document.getElementById("vr-button-tooltip").setAttribute("tooltip","Enter VR");
isVRpresenting=false;
vrButton.onclick = function ()
{
if (runOnlyOnce)
{
makeVRMenuItems();
runOnlyOnce=false;
}
if ( currentSession === null )
{
var sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor' ] };
navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
}
else
{
currentSession.end();
}
};
}
function showVRNotFound()
{
vrButton.onclick = function ()
{
//open VR popup that shows devices to use in order to exp VR
};
isVRavailable=false;
vrButton.style.backgroundImage="url('icons/noVR.svg')";
document.getElementById("vr-button-tooltip").setAttribute("tooltip","VR is not supported on your device");
vrButton.onclick = null;
// renderer.xr.setDevice( null );
isVRpresenting=false;
}
if ( 'xr' in navigator )
{
isVRavailable=true;
navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) {
supported ? showEnterXR() : showVRNotFound();
} );
}
else
{
vrButton.onclick = function ()
{
//open VR popup that shows devices to use in order to exp VR
};
isVRavailable=false;
vrButton.style.backgroundImage="url('icons/noVR.svg')";
document.getElementById("vr-button-tooltip").setAttribute("tooltip","VR is not supported on you device");
isVRpresenting=false;
}
},
};
WEBVR.createButton( renderer );
}
function animate()
{
renderer.setAnimationLoop( animate );
update();
}
function update()
{
renderer.render( scene, camera );
}
Seems like the WebXR session takes the scene camera parameters for the first time it enters VR. Then, on second and subsequent visits the WebXR session sets the camera to default settings. Hence, in order to update the camera properties to be same as scene camera properties, we need to use this session.updateRenderState({ depthFar: 10000 });.In my case my scene camera had depthFar=10000 but WebXR camera resets the depthFar property to 1000 in the second and subsequent visits in VR, which was the reason of frustum culling (image in question).
I created a shapegeometry with the text. How can I keep the text face the camera on move the camera?
...
this.textGeometry = new THREE.ShapeGeometry(THREE.FontUtils.generateShapes(value, parameters));
this.textValue = new THREE.Mesh(this.textGeometry, new THREE.MeshBasicMaterial({ color: color, side: THREE.DoubleSide }));
this.textValue.matrixAutoUpdate = true;
this.add(this.textValue)
...
I think my problem is that I modified the parent quaternion 3D object:
this.quaternion.setFromAxisAngle (axis, radians);
then the only operation:
textValue.quaternion.copy (camera.quaternion);
is not sufficient
how can I fix the rotation considering the state of the quaternion?
If you don't care about calling the base updateMatrix function,
this can be a solution
yourShapeGeometry.prototype.updateMatrix = function(){
// THREE.Object3D.prototype.updateMatrix.call(this);
fixOrientation(this.textValue);
}
function fixOrientation(mesh){
mesh.setRotationFromQuaternion(camera.quaternion);
mesh.updateMatrix();
}
or simply edit the updateMatrix of your text mesh like
textMesh.updateMatrixWorld = updateSpriteWorld;
function updateSpriteWorld(){
if ( this.matrixWorldNeedsUpdate === true || force === true ) {
this.setRotationFromQuaternion(camera.quaternion);
this.updateMatrix();
this.matrixWorld.copy( this.matrix );
this.matrixWorldNeedsUpdate = false;
force = true;
}
// update children
for ( var i = 0, l = this.children.length; i < l; i ++ ) {
this.children[ i ].updateSpriteWorld( force );
}
}
I think this should do the trick:
this.textValue.lookAt( camera.position );
i'm trying to use the THREE.LOD object in my ThreeJS scene. i've been inspired by http://threejs.org/examples/webgl_lod.html
But I wanted to push the idea further and use DAE model (using this loader : http://threejs.org/examples/webgl_loader_collada.html)
Problem is i can't switch the visibility of the lod level. First, i tried an automated one in my Render function (based on distance to camera, found in the example):
this.m_Scene.traverse( function ( object ) {
if ( object instanceof THREE.LOD ) {
object.update( that.m_Camera );
}
} );
As it wasn't working (All my lod were displayed at the same time). I try something more manual. and it appears the Object3D.visibility attribute isn't really used by the renderer, or not inherited by children.
As far as I understand, this attribute is for meshes. But i'm not sure it's checked at render time.
So this doesn't work as expected:
var LodTemporaryObject = new THREE.LOD();
function LoadLod1()
{
//TEST COLLADA LOADER
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(Lod2Path, function ( collada ) {
dae = collada.scene;
dae.scale.x = dae.scale.y = dae.scale.z = 0.1;
dae.updateMatrix();
dae.visible = false; //THIS HAS NO EFFECT
LodTemporaryObject.addLevel(dae,100);
AddLodToScene(LodTemporaryObject ); //where the lod is added to the threeJS scene object
} );
}
so question : how do I actually set (in)visible any Object3D or subScene ?
EDIT: The answer below is outdated. Visibility is now inherited. See, for example, Show children of invisible parents.
three.js r.71
Visibility is not inherited by children with WebGLRenderer.
The work-around is to use a pattern like so:
object.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) {
child.visible = false;
}
}
three.js r.64
Thx to WestLangley answer above, i came up with an recursive solution to my problem:
first, a recursive function to update visibility of children to match the parent's:
function SetChildrenVisible(parent)
{
if(parent instanceof THREE.Object3D)
{
for(var i = 0; i< parent.children.length; i ++)
{
parent.children[i].visible = parent.visible;
SetChildrenVisible(parent.children[i]);
}
}
}
then in my render loop:
this.m_Scene.traverse( function ( object ) {
if ( object instanceof THREE.LOD ) {
//save all lodLevel state before updating
var oldVisible =[]; object.visible;
for(var i = 0; i< object.children.length; i++)
{
oldVisible.push(object.children[i].visible)
}
//Update Lod
object.update( that.m_Camera );
//Check for changes and update accordingly
for(var i = 0; i< object.children.length; i++)
{
if(oldVisible[i] != object.children[i].visible )
{
SetChildrenVisible(object.children[i]);
}
}
}
} );
Goal is to only update object whose attribute changed.