I'm trying to use multiple textures in a single PointCloud using a ShaderMaterial. I'm passing a texture array to the shader along with texture index attributes and selecting the appropriate texture to use in the fragment shader.
Relevant Setup Code:
var particleCount = 100;
var uniforms = {
textures: {
type: 'tv',
value: this.getTextures()
}
};
var attributes = {
texIndex: {
type: 'f',
value: []
},
color: {
type: 'c',
value: []
},
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
transparent: true
});
var geometry = new THREE.Geometry();
for (var i = 0; i < particleCount; i++) {
geometry.vertices.push(new THREE.Vector3(
(Math.random() - 0.5) * 50, (Math.random() - 0.5) * 50, (Math.random() - 0.5) * 50));
attributes.texIndex.value.push(Math.random() * 3 | 0);
attributes.color.value.push(new THREE.Color(0xffffff));
}
var particles = new THREE.PointCloud(geometry, material);
particles.sortParticles = true;
this.container.add(particles);
Vertex Shader:
attribute vec3 color;
attribute float texIndex;
varying vec3 vColor;
varying float vTexIndex;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vColor = color;
vTexIndex = texIndex;
gl_PointSize = 50.0;
gl_Position = projectionMatrix * mvPosition;
}
Fragment Shader:
uniform sampler2D textures[3];
varying vec3 vColor;
varying float vTexIndex;
void main() {
vec4 startColor = vec4(vColor, 1.0);
vec4 finalColor;
if (vTexIndex == 0.0) {
finalColor = texture2D(textures[0], gl_PointCoord);
} else if (vTexIndex == 1.0) {
finalColor = texture2D(textures[1], gl_PointCoord);
} else if (vTexIndex == 2.0) {
finalColor = texture2D(textures[2], gl_PointCoord);
}
gl_FragColor = startColor * finalColor;
}
The problem is some points (ones using a texture index higher than 0) are flickering for reasons and can't figure out. Other attempts have also seemed to flicker between textures rather than opacity.
An example of this can be seen at http://jsfiddle.net/6qrubbk6/4/.
I've given up on this over multiple projects but I'd love to find a solution once and for all. Any help is greatly appreciated.
Edit: Checking if vTexIndex is < n, instead of == n solves the issue.
if (vTexIndex < 0.5) {
finalColor = texture2D(textures[0], gl_PointCoord);
} else if (vTexIndex < 1.5) {
finalColor = texture2D(textures[1], gl_PointCoord);
} else if (vTexIndex < 2.5) {
finalColor = texture2D(textures[2], gl_PointCoord);
}
As seen here: http://jsfiddle.net/6qrubbk6/5/
The Points class in Three.js can take a geometry and display points instead of the 'full' object. With PointsMaterial you are able to control the size of your points. The code for a 'pointcloud mesh' looks something like: For the full code, click/press on the example and check out main.js in the code tab!
We create a TextureLoader and then call its load method. This returns a Texture object. It's important to note that using this method our texture will be transparent until the image is loaded asynchronously by three.js at which point it will update the texture with the downloaded image.
By default textures in three.js do not repeat. To set whether or not a texture repeats there are 2 properties, wrapS for horizontal wrapping and wrapT for vertical wrapping. Repeating is set with the [repeat] repeat property. Offseting the texture can be done by setting the offset property.
var pointCloud; // The object that represents the visible cloud of points. var geometry; // Object of type THREE.Geometry containing the visible points. var material; // Object of type THREE.PointCloudMaterial for point color and size. /* Render function draws the content of the canvas.
Also you can cast vTexIndex to int.
int textureIndex = int(vTexIndex + 0.5);
if (textureIndex == 0) {
finalColor = texture2D(textures[0], gl_PointCoord);
} else if (textureIndex == 1) {
finalColor = texture2D(textures[1], gl_PointCoord);
} else if (textureIndex == 2) {
finalColor = texture2D(textures[2], gl_PointCoord);
}
Thanks for replying to your own question. You helped me get started on a similar feature I was working.
I thought this might be helpful to someone else so I'm replying here.
I've created a fiddle that does what you're doing, but dynamically. You can add as many textures to the textures array and they will be dynamically added to the nodes. This was tricky to do in glsl, and required some hacky javascript templating.
To do this, I just created 2 methods that return the vertex and fragment shader to the shader material:
Fragment Shader Method:
World.prototype.getFragmentShader = function(numTextures){
var fragShader = `uniform sampler2D textures[${numTextures}];
varying vec3 vColor;
varying float vTexIndex;
void main() {
vec4 startColor = vec4(vColor, 1.0);
vec4 finalColor;
`;
for(var i = 0; i < numTextures; i++){
if(i == 0){
fragShader += `if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], gl_PointCoord);
}
`
}
else{
fragShader += `else if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], gl_PointCoord);
}
`
}
}
fragShader += `gl_FragColor = startColor * finalColor;
}`;
console.log('frag shader: ', fragShader)
return fragShader;
}
Vertex Shader:
World.prototype.getVertexShader = function(){
let vertexShader = `attribute vec3 color;
attribute float texIndex;
varying vec3 vColor;
varying float vTexIndex;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vColor = color;
vTexIndex = texIndex;
gl_PointSize = 50.0;
gl_Position = projectionMatrix * mvPosition;
}`;
return vertexShader;
}
You can see a live demo here: http://jsfiddle.net/jigglebilly/drmvz5co/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With