Threejs DataTexture Not Updating - three.js

UPDATE: Issue was that texData object was recreated each time and thus reference for DataTexture was lost. Solution by WestLangley was to overwrite the data in texData instead of recreating texData object.
I have a simple threejs scene with a DataTexture in a ShaderMaterial. The data array passed to it once during initialization is updated on mouse events. However the DataTexture does not seem to update.
Did i assign uniforms or texture data wrongly? Or using the needsUpdate flags wrongly? It does work when deleting and recreating the texture, material, mesh and scene objects each time, but this shouldnt really be necessary as i have seen from many examples which i could however not reproduce.
Note that the data itself is updated nicely, just not the DataTexture.
// mouse event triggers request to server
// server then replies and this code here is called
// NOTE: this code **is** indeed called on every mouse update!
// this is the updated data from the msg received
// NOTE: texData **does** contain the correct updated data on each event
texData = new Float32Array(evt.data.slice(0, msgByteLength));
// init should happen only once
if (!drawContextInitialized) {
// init data texture
dataTexture = new THREE.DataTexture(texData, texWidth, texHeight, THREE.LuminanceFormat, THREE.FloatType);
dataTexture.needsUpdate = true;
// shader material
material = new THREE.ShaderMaterial({
vertexShader: document.querySelector('#vertexShader').textContent.trim(),
fragmentShader: document.querySelector('#fragmentShader').textContent.trim(),
uniforms: {
dataTexture: { value: dataTexture }
}
});
// mesh with quad geometry and material
geometry = new THREE.PlaneGeometry(width, height, 1, 1);
mesh = new THREE.Mesh(geometry, material);
// scene
scene = new THREE.Scene();
scene.add(mesh);
// camera + renderer setup
// [...]
drawContextInitialized = true;
}
// these lines seem to have no effect
dataTexture.needsUpdate = true;
material.needsUpdate = true;
mesh.needsUpdate = true;
scene.needsUpdate = true;
renderer.render(scene, camera);

When updating the DataTexture data, do not instantiate a new array. Instead, update the array elements like so:
texData.set( javascript_array );
Also, the only flag you need to set when you update the texture data is:
dataTexture.needsUpdate = true;
three.js r.83

I had a real hard time for some reason seeing any change to modifactions . In desperation i just made a new DataTexture . Its important to set needsUpdate to true
imgData.set(updatedData)
var newDataTex = new THREE.DataTexture( imgData,...
var newDataTex.needsUpdate = true
renderMesh.material.uniforms.texture.value = newDataTex

Related

Attempts to load a texture show no error but the texture does not display

I have a model, a background sky and a ground surface. Texturing the surface results in no surface.
I've tried the basic approach and come to the conclusion that it is probably that the scene is being rendered before the texture has finished loading. Having searched and found various possible solutions, I have tried several of them, without really understanding how they are supposed to work. None of them has worked. One problem is that it is an old problem and most of the suggestions involve outdated versions of the three.js library.
// Ground
// create a textured Ground based on an answer in Stackoverflow.
var loader = new THREE.TextureLoader();
loader.load('Textures/Ground128.jpg',
function (texture) {
var groundGeometry = new THREE.PlaneBufferGeometry(2000, 2000, 100, 100);
const groundMaterial = new THREE.MeshLambertMaterial({map: texture});
var ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.receiveShadow = true; //Illumination addition
ground.rotation.x = -0.5 * Math.PI; // rotate into the horizontal.
scene.add(ground);
}
);
// This variation does not work either
http://lhodges.users37.interdns.co.uk/me/downloads/Aphaia/Temple.htm
http://lhodges.users37.interdns.co.uk/me/downloads/Aphaia/Temple7jsV0.15b.htm
The first of the above is the complete page in which the ground is a plain billiard table green. The second is the page containing the above code.
There appear to be no error (Last time I tried.)
By the time your texture loads and you add the ground, your scene has already rendered (and there is no other render call).
You need to call renderer.render(scene, camera); after adding the ground to the scene.
// Ground
// create a textured Ground based on an answer in Stackoverflow.
var loader = new THREE.TextureLoader();
loader.load('Textures/Ground128.jpg',
function (texture) {
var groundGeometry = new THREE.PlaneBufferGeometry(2000, 2000, 100, 100);
const groundMaterial = new THREE.MeshLambertMaterial({map: texture});
var ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.receiveShadow = true; //Illumination addition
ground.rotation.x = -0.5 * Math.PI; // rotate into the horizontal.
scene.add(ground);
renderer.render(scene, camera); // <--- add this line
}
);

Using OutlinePass (THREE.js r102) with skinned mesh

/examples/js/postprocessing/OutlinePass.js from THREE.js r102 does not appear to work with skinned meshes. Specifically, the rendered outline always stays in the mesh's rest position.
Is there some way to get this working (that is, to update the outline to reflect the current pose of an animated mesh)? OutlinePass does not appear to be documented (mod the comments in the code itself).
Is there some other accepted method of outlining animated meshes? I'm in the process of migrating some code from r7x, where I ended up accomplishing this by manually creating a copy of the mesh and applying a shader material that scales along the normals. I can do that again, but if there's a simpler/better supported method to accomplish the same effect I'd rather use it instead of reproducing a method that breaks every new major release.
A simple jsfiddle illustrating the issue:
https://jsfiddle.net/L69pe5q2/3/
This is the code from the jsfiddle. The mesh I use is the SimpleSkinning.gltf example from the three.js distribution. In the jsfiddle I load it from a dataURI so it doesn't complain about XSS loading, and I've edited the base64-encoded data out (and replaced it with [FOO]) in the code below, purely for readability.
The OutlinePass is created and added to the composer in initComposer().
var camera, light, renderer, composer, mixer, loader, clock;
var scene, mesh, outlinePass;
var height = 480,
width = 640;
var clearColor = '#666666';
load();
function load() {
loader = new THREE.GLTFLoader();
clock = new THREE.Clock();
scene = new THREE.Scene();
loader.load('data:text/plain;base64,[FOO]', function(obj) {
scene.add(obj.scene);
mixer = new THREE.AnimationMixer(obj.scene);
var clip = THREE.AnimationClip.findByName(obj.animations,
'Take 01');
var a = mixer.clipAction(clip);
a.reset();
a.play();
mesh = obj.scene;
mesh.position.set(-7, 2.5, -7);
init();
animate();
});
}
function init() {
initCamera();
initScene();
initRenderer();
initComposer();
outlinePass.selectedObjects = [mesh];
}
function initCamera() {
camera = new THREE.PerspectiveCamera(30, width / height, 1, 10000);
camera.position.set(7, 0, 7);
camera.lookAt(0, 0, 0);
}
function initScene() {
light = new THREE.AmbientLight(0xffffff)
scene.add(light);
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({
width: width,
height: height,
antialias: false,
});
renderer.setSize(width, height);
renderer.setClearColor(clearColor);
document.body.appendChild(renderer.domElement);
}
function initComposer() {
var renderPass, copyPass;
composer = new THREE.EffectComposer(renderer);
renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
outlinePass = new THREE.OutlinePass(new THREE.Vector2(width, height),
scene, camera);
composer.addPass(outlinePass);
outlinePass.edgeStrength = 10;
outlinePass.edgeThickness = 4;
outlinePass.visibleEdgeColor.set('#ff0000');
copyPass = new THREE.ShaderPass(THREE.CopyShader);
copyPass.renderToScreen = true;
composer.addPass(copyPass);
}
function animate() {
var delta = clock.getDelta();
requestAnimationFrame(animate);
update(delta);
render(delta);
}
function update(delta) {
if (mixer) mixer.update(delta);
}
function render(delta) {
composer.render();
}
according to Mugen87 in Jan 2019 he said:
With this small patch, it's now possible to use the outline pass with animated meshes. The only thing users have to do at app level is to set morphTargets or skinning to true for OutlinePass.depthMaterial and OutlinePass.prepareMaskMaterial. That's of course still a manual effort but at least the more complicated shader enhancement is already done.
take this example:
https://jsfiddle.net/2ybks7rd/
reference link on github

Three.js. How do I use a custom material for a scene background rather then color or texture?

The docs for scene say a color or texture can be used for scene.background. I would like to use a ShaderMaterial with my own custom shaders. How can I do this?
Specifically, I want to paint a color ramp behind the foreground elements. Here is the fragment shader:
uniform vec2 uXYPixel;
void main() {
vec2 xy = vec2(gl_FragCoord.x/uXYPixel.x, gl_FragCoord.y/uXYPixel.y);
gl_FragColor.rgb = vec3(xy.x, xy.y, 0);
gl_FragColor.a = 1.0;
}
uXYPixel is a uniform vec2 with the values window.innerWidth, window.innerHeight
You'd need to manually create two render passes. One that renders the background plane with a simple Camera, and the second one that renders the rest of the scene. You can use the most basic Camera class since you won't be using transformation or projection matrices:
// Set renderer with no autoclear
var renderer = new THREE.WebGLRenderer();
renderer.autoClear = false;
document.body.append(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
// Set up background scene
var bgScene = new THREE.Scene();
var bgCam = new THREE.Camera();
var bgGeom = new THREE.PlaneBufferGeometry(2, 2);
var bgMat = new THREE.ShaderMaterial({
// Add shader stuff in here
// ..
// Disable depth so it doesn't interfere with foreground scene
depthTest: false,
depthWrite: false
});
var bgMesh = new THREE.Mesh(bgGeom, bgMat);
bgScene.add(bgMesh);
// Set up regular scene
var scene = new THREE.Scene();
var cam = new THREE.PerspectiveCamera(45, w/h, 1, 100);
function update() {
// Clear previous frame
renderer.clear();
// Background render pass
renderer.render(bgScene, bgCam);
// Foreground render pass
renderer.render(scene, cam);
requestAnimationFrame(update);
}
update();
Here you can see a working example.
Notice that the renderer's autoClear = false attribute makes sure it doesn't clear the buffer in between each render() call; you'll have to clear it manually at the beginning of each frame.

custom attributes failing on shaderMaterial

I am trying to use a custom attribute on a shaderMaterial, but I can't get it to work.
My simplified code is
attributes = {
aColor: { type: "f", value:] },
};
for ( i = 0; i < points.length; i ++ ) {
attributes.aColor.value.push (0.9) ;
}
var uniforms = THREE.UniformsLib['lights'];
sMaterial = new THREE.ShaderMaterial ({
attributes: attributes,
uniforms: uniforms,
vertexShader: vShader,
fragmentShader: fShader,
lights: true,
})
var line2 = new THREE.Line( geometry, sMaterial);
scene.add( line2 );
In my shader I set a debug statement
attribute float aColor;
void main()
if (aColor == 0.0) {
// debugcode
}
and the debugcode is always executed.
Inspecting the WebGlProgram, I can see in the ACTIVE_ATTRIBUTES the aColor, and it looks ok.
What is going wrong here ?
Or, even better, how can I debug a problem like this ?
Just trying, I found out the problem.
I was reusing a geometry that I had already used for another mesh, and somehow that was causing the problem.
Anyway, I am still interested in learning techniques to deal with this kind of problems
Following on from #vals' answer, I've just solved a similar problem, not with shared geometries, but with trying to assign a new shader material with attributes to an existing object. It's because if three.js detects that the geometry and its owning object are already initialised (using the variable geometry.__webglInit), then even if the material has changed, it won't try to update the geometry's buffers, including attributes, in the GPU memory. The initialisation also won't run unless the renderer detects that objects have been added to the scene.
The solution I used:
// Create the new shader
var shader = new THREE.ShaderMaterial({
attributes: {intensity: {type: 'f', value: []}},
vertexShader: vShaderText,
fragmentShader: fShaderText
});
// Populate the intensity attribute
// ...
// Remove the existing scene object
var existingObject = myObjects[0]; // ... retrieve from scene or variable
var geometry = existingObject.geometry;
scene.remove(existingObject);
// Recreate the new scene object (important!: note geometry.clone() below)
var pc = new THREE.PointCloud(geometry.clone(), shader);
scene.add(pc);
// Clean up memory
geometry.dispose();
There is probably a more memory-efficient way to do this by reusing the existing vertex buffers that are already in GPU memory, but this works well enough for our application.

How can I change the texture in a Blender model loaded into three.js, after it's been loaded, with ImageUtils.loadTexture?

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);

Resources