Using webgl-texture-utils with three.js for dds/crn support - three.js

I'm trying to use webgl-texture-utils ( https://github.com/toji/webgl-texture-utils ) with Three.js (r56) to take advantage of DDS and CRN (http://code.google.com/p/crunch/) textures.
I cannot figure out how to use the texture returned by textureLoader.load(src, callback); in THREE.Texture(image, mapping);
THREE.Texture expects javascript Image() object, but texture-utils returns something reported by Firebug to be XrayWrapper [Object WebGLTexture {} which I assume to be some native WebGL texture type.
Can I use that texture somehow as the image for THREE.Texture?
Here's some code I tried:
var mapping = new THREE.UVMapping();
var image = new Image();
var map = null;
var usetexturetools = true;
if (!usetexturetools) {
// normal way of loading
map = new THREE.Texture( image, mapping );
var loader = new THREE.ImageLoader();
loader.addEventListener( 'load', function ( event ) {
map.image = event.content;
map.needsUpdate = true;
});
loader.addEventListener( 'error', function ( event ) {
window.console.log("error loading " + texture);
});
loader.crossOrigin = 'anonymous';
loader.load(texture, image);
} else {
// using webgl-texture-tools - not working because i don't know how to use the object returned by textureLoader.load() with THREE.Texture..
var textureLoader = new TextureUtil.TextureLoader(renderer.getContext());
map = new THREE.Texture(textureLoader.load(texture, function(loaded) {
window.console.log(loaded);
map.needsUpdate = true;
}), mapping );
}
map.sourceFile = texture;
map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.offset.x = this.getParameter("texture_offset_x");
map.offset.y = this.getParameter("texture_offset_y");
map.repeat.x = this.getParameter("texture_scale_x");
map.repeat.y = this.getParameter("texture_scale_y");
EDIT: I guess I could create a fork of webgl-texture-utils that instead of creating and returning WebGLTexture, would only return the raw data and as such, would allow me to use that data in THREE.DataTexture(). I'll try that when I get to it... Looking at the source looks like it might be quite a lot of work, but unless anything comes up, I'll try that.

Related

Applying two different fragment shaders to two different materials (of the same type) using onBeforeCompile?

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.

WebGL THREE: changing position of obj, but the object doesn't move

I am working with an object which i loaded like this (I save it as a global variable such that I can change it later):
model1 = null;
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load("http://blabla.mtl", function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load("http://blabla.obj", function(object) {
object.scale.x = 0.0004;
object.scale.y = 0.0004;
object.scale.z = 0.0004;
object.rotateX(Math.PI / 2);
object.rotateZ(Math.PI / 2);
object.add(new THREE.AxisHelper(2))
model1 = object; //save in global variables
scene.add(model1);
});
});
When I am rendering I want to change the position of the model, based on a box's change in position:
function render(ms: number) {
if (lastTime) {
update((ms-lastTime)/1000)
}
lastTime = ms
requestAnimationFrame(render)
renderer.render( scene, camera )
}
var pos_current;
var pos_new;
var trans;
function update(dt: number) {
if (pause.on) return
//save position of box before it is transformed
pos_current = box.position.clone();
box.updatePosition()
//save new position of box
pos_new = box.position.clone();
//transform the 3D-model according to the box's transformation
trans = pos_current.sub(pos_new);
model1.position.add(trans);
console.log(model1.position)
I can in the console see that the position of model1 changes, but the object just doesn't move at all, and I can't really figure out why.
Any help is very appreciated :)
Got it to work - the problem was that I was trying to update the position of model1, before it was actually done loading in the obj-loader :D
I have a query. I too made a global variable and equaled it to object when loading. but it says in console log that variable in null. Is there a something I am missing.
Update:-
I understood the part where I am making the humanModel equal to object before it loaded but then how to handle it then?
let humanModel = null;
OBJLoader.load(
// resource URL
"/Models/Model_1(Malequins)/1/Cadnav.com_B0426014.obj",
// called when resource is loaded
function(object) {
scene.add(object);
humanModel = object; //adding the humanModel to the global variable
camera.lookAt(object.position);
console.log(object.position);
console.log(camera.position);
},
// called when loading is in progresses
function(xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function(error) {
console.log("An error happened " + error);
}
);
console.log(humanModel.position);
Thanks
you can keep the object(obj file) in x,y or z direction, for it you need to set its position as:
var loader = new THREE.OBJLoader();
loader.load(
'/models/female.obj',
function(object){
object.position.y=-20;
scene.add(object);
}
)

Define prototype.updatePosition so that when an Object is clicked it animates/rotates/scales to certain value

Having problem understanding the class system in Three.js
I have a code in player.js :
function Player() {
var mesh = new THREE.Object3D();
this.player = null;
this.loader = new THREE.JSONLoader();
this.name = 'player';
this.loader.load(
'obj/models/minecraft_sole.json',
function ( geometry, materials ) {
var material = new THREE.MultiMaterial( materials );
this.player = new THREE.Mesh( geometry, material );
this.player.position.set(0, 0, 0);
this.player.scale.set(.5,.5,.5);
this.player.castShadow = true;
this.player.receiveShadow = false;
mesh.add( this.player );
}
);
Player.prototype.constructor = Player;
Player.prototype = Object.create(THREE.Object3D.prototype);
}
Player.prototype.updatePosition = function(){
this.mesh.position.y += 0.05;
}
And basically what I'm trying to achieve:
In main.js after all standard setup for init()/render()/animate() and all...
I create variable called johny:
var johny = new Player();
Now everything loads great and all, but in player.js i want to be able to define some prototype ? method, and in that method I want to listen for a click event. After that event is called I want my player mesh to animate to certain position or start rotating/scaling.
My pseudo code for better understanding is:
var Player = function(){
// define mesh and all
}
player.add.eventListener( 'click' ){
//code to animate player
}
Remember that all this; I want to be a part of player.js so that after calling:
var johny = new Player();
I don't have to add event listening functions to the main.js and all that.
And second of all I want my code to be modular, as You may already noticed :)
So I did managed to understand it.

EaselJS: Using updateCache() with AlphaMaskFilter When Dragging Mask

I'm using an imported png with an alpha gradient that I'm setting as a mask that reveals the bitmap it is assigned to. The mask object is draggable (kind of like a flashlight). I know I'm supposed to use an AlphaMaskFilter as one of the filters, and I know I'm supposed to use .updateCache()... I'm just not sure I'm using them correctly?
var stage;
var assetQueue;
var bg;
var bgMask;
var container;
var amf;
$(document).ready(function(){
loadImages();
});
function loadImages()
{
// Set up preload queue
assetQueue = new createjs.LoadQueue();
assetQueue.addEventListener("complete", preloadComplete);
assetQueue.loadManifest([{id:"img_bg",src:"images/Nintendo-logo-red.jpg"}, {id:"img_bg_mask",src:"images/background_mask.png"}]);
}
function preloadComplete()
{
assetQueue.removeEventListener("complete", preloadComplete);
init();
}
function init()
{
stage = new createjs.Stage("stage_canvas");
setBackgrounds();
sizeStage();
$(document).mousemove(function(evt){
trackMouse(evt);
});
}
function trackMouse(evt)
{
var mouseX = evt.pageX;
var mouseY = evt.pageY;
// Move the containing clip around
container.x = mouseX - (bgMask.image.width / 2);
container.y = mouseY - (bgMask.image.height / 2);
// Offset the position of the masked image.
bg.x = -container.x;
bg.y = -container.y;
container.updateCache();
stage.update();
}
function setBackgrounds()
{
bg = new createjs.Bitmap(assetQueue.getResult("img_bg"));
bgMask = new createjs.Bitmap(assetQueue.getResult("img_bg_mask"));
container = new createjs.Container();
container.addChild(bg);
amf = new createjs.AlphaMaskFilter(bgMask.image)
container.filters = [amf];
container.cache(0, 0, bg.image.width, bg.image.height);
stage.addChild(container);
stage.update();
}
function sizeStage()
{
var windowW = 600;
var windowH = 600;
stage.canvas.width = windowW;
stage.canvas.height = windowH;
stage.update();
}
Solution found (for anyone interested). The key is to add the image you want to mask to a container. Move the container to any position you want, then offset the contained image within the container. The code has been updated to reflect this.

WebGLRenderingContext ERROR loading texture maps

First of all, thank you for this wonderfull work, i'm having a lot of fun working with three.js.
I tried to find answer about a recurent issue, .WebGLRenderingContext: GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 2
I'm making a website in webgl, i spend few week understanding all about three.js but i can't fix this issue.
I get this message on Chrome and firefox (latest) each time i try to load a canvas into a map, bumpmap and specmap.
All my mesh are loaded from obj files, by the way i rewrote OBJMTLLoader.js to be able to load more parameters from obj files and more.
here the code used to load image.
THREE.MTLLoader.loadTexture = function ( url, mapping, onLoad, onError ) {
var isCompressed = url.toLowerCase().endsWith( ".dds" );
var texture = null;
if ( isCompressed ) {
texture = THREE.ImageUtils.loadCompressedTexture( url, mapping, onLoad, onError );
} else {
var image = new Image();
texture = new THREE.Texture( image, mapping );
var loader = new THREE.ImageLoader();
loader.addEventListener( 'load', function ( event ) {
texture.image = THREE.MTLLoader.ensurePowerOfTwo_( event.content );
texture.needsUpdate = true;
if ( onLoad )
onLoad( texture );
} );
loader.addEventListener( 'error', function ( event ) {
if ( onError ) onError( event.message );
} );
loader.crossOrigin = this.crossOrigin;
loader.load( url, image );
}
return texture;
};
I'm pretty sure it is from this, because when i disable this function, no more warning.
Is it because the mesh has a texture with an empty image while loading datas ?
Is there any restriction on the dimensions of image ?
For now everything works fines, but i feel strange having those message in console.
Thanks
This error become because the Three.js buffers are outdated. When your add some textures (map,bumpMap ...) to a Mesh, you must recompose the buffers like this :
ob is THREE.Mesh, mt is a Material, tex is a texture.
tex.needsUpdate = true;
mt.map = tex;
ob.material = mt;
ob.geometry.buffersNeedUpdate = true;
ob.geometry.uvsNeedUpdate = true;
mt.needsUpdate = true;
That's all folks !
Hope it's help.
Regards.
Sayris

Resources