Can't send a texture to a custom shader (ShaderPass/EffectComposer) - three.js

I'm currently playing with this great library, but I have some difficulties with the EffectComposer.
I can't send a texture to a postprocess shader introduced by a ShaderPass.
I think this is a bug... or I'm doing something stupid (needUpDate, warp,... ??)
(r54, W7, Nv9700mGT, FF 17.0.1 and Chrome 24.0.1312.52)
I used the "webgl_postprocessing.html" example to reproduce the phenomenon just by adding these shaders :
<script id="vertexShaderBasic" type="x-shader/x-vertex">
varying vec2 glTexCoord;
void main() {
glTexCoord = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script id="fragmentShaderBlender" type="x-shader/x-fragment">
varying vec2 glTexCoord;
uniform sampler2D sprite1;
uniform sampler2D previousRender;
void main() {
vec3 color = texture2D( previousRender, glTexCoord ).rgb;
color += texture2D( sprite1, glTexCoord ).rgb;
gl_FragColor.rgb = color;
gl_FragColor.a = 1.0;
}
</script>
this at the begining of the main script to be sure the sprite is loaded :
var composer2;
function start() {
init();
animate();
}
var sprite1 = THREE.ImageUtils.loadTexture( "textures/sprite1.jpg", null, start );
this in the composer field, after :
composer.addPass( new THREE.RenderPass( scene, camera ) );
composer2 = new THREE.EffectComposer( renderer );
var uniforms1 = {
sprite1: { type: "t", value: sprite1 }, // <- something wrong here
previousRender: { type: "t", value: null }
};
var blenderShader = {
uniforms: uniforms1,
vertexShader: $( 'vertexShaderBasic' ).textContent,
fragmentShader: $( 'fragmentShaderBlender' ).textContent
};
// link with the previous render
blenderShader.uniforms.previousRender.value = composer.renderTarget2;
// the custom shader
var blenderPass = new THREE.ShaderPass( blenderShader );
blenderPass.renderToScreen = true;
composer2.addPass( blenderPass );
I also coment this, beacause it's not relevent any more :
//effect.renderToScreen = true;
and I add this at the end :
composer2.render();
The link between the two passes work well, but the sprite never appear on the EffectComposer quad that cover the screen...
Thanks and sorry for my english.
EDIT to recap :
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - postprocessing</title>
<meta charset="utf-8">
<style>
body {
margin: 0px;
background-color: #000000;
overflow: hidden;
}
</style>
</head>
<body>
<script src="../build/three.min.js"></script>
<script src="js/shaders/CopyShader.js"></script>
<script src="js/shaders/DotScreenShader.js"></script>
<script src="js/shaders/RGBShiftShader.js"></script>
<script src="js/postprocessing/EffectComposer.js"></script>
<script src="js/postprocessing/RenderPass.js"></script>
<script src="js/postprocessing/MaskPass.js"></script>
<script src="js/postprocessing/ShaderPass.js"></script>
<script id="vertexShaderBasic" type="x-shader/x-vertex">
varying vec2 glTexCoord;
void main() {
glTexCoord = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script id="fragmentShaderBlender" type="x-shader/x-fragment">
varying vec2 glTexCoord;
uniform sampler2D sprite1;
uniform sampler2D previousRender;
void main() {
vec3 color = texture2D( previousRender, glTexCoord ).rgb;
color += texture2D( sprite1, glTexCoord ).rgb;
gl_FragColor.rgb = color;
gl_FragColor.a = 1.0;
}
</script>
<script>
var camera, scene, renderer, composer;
var composer2;
var object, light;
function start() {
init();
animate();
}
var sprite1 = THREE.ImageUtils.loadTexture( "textures/sprite1.jpg", null, start );
//var sprite1 = THREE.ImageUtils.loadTexture( "textures/sprite1.jpg", new THREE.UVMapping(), start ); // change anything
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 400;
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, 1, 1000 );
object = new THREE.Object3D();
scene.add( object );
var geometry = new THREE.SphereGeometry( 1, 4, 4 );
var material = new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } );
for ( var i = 0; i < 100; i ++ ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5 ).normalize();
mesh.position.multiplyScalar( Math.random() * 400 );
mesh.rotation.set( Math.random() * 2, Math.random() * 2, Math.random() * 2 );
mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
object.add( mesh );
}
scene.add( new THREE.AmbientLight( 0x222222 ) );
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
// postprocessing
composer = new THREE.EffectComposer( renderer );
composer.addPass( new THREE.RenderPass( scene, camera ) );
/////////////////////////////////////
/////////////////////////////////////
composer2 = new THREE.EffectComposer( renderer );
var uniforms1 = {
sprite1: { type: "t", value: sprite1 },
previousRender: { type: "t", value: null }
};
//uniforms1.sprite1.value.wrapS = uniforms1.sprite1.value.wrapT = THREE.RepeatWrapping;
var blenderShader = {
uniforms: uniforms1,
vertexShader: $( 'vertexShaderBasic' ).textContent,
fragmentShader: $( 'fragmentShaderBlender' ).textContent
};
blenderShader.uniforms.previousRender.value = composer.renderTarget2;
var blenderPass = new THREE.ShaderPass( blenderShader );
blenderPass.renderToScreen = true;
composer2.addPass( blenderPass );
/////////////////////////////////////
/////////////////////////////////////
var effect = new THREE.ShaderPass( THREE.DotScreenShader );
effect.uniforms[ 'scale' ].value = 4;
composer.addPass( effect );
var effect = new THREE.ShaderPass( THREE.RGBShiftShader );
effect.uniforms[ 'amount' ].value = 0.0015;
//effect.renderToScreen = true;
composer.addPass( effect );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function $( id ) {
return document.getElementById( id );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
var time = Date.now();
object.rotation.x += 0.005;
object.rotation.y += 0.01;
composer.render();
composer2.render();
}
</script>
</body>
</html>
What I want :
Good
What I get :
NotGood

I had this issue too, and found a workaround.
I debugged it to determine that the texture ID for the extra texture is different in the shader pass than expected, which is bad. If you look in the ShaderPass constructor, you'll see it clones the uniforms. That seems to be the cause. If I edit that code to restore the original texture object in the cloned uniforms, it works as expected. So that should work for you too.
I'm trying to get some kind of (proper) bug fix integrated into the actual release.

Try this
var sprite1 = THREE.ImageUtils.loadTexture( "textures/sprite1.jpg", new THREE.UVMapping(), start );
three.js r.54

Related

How can I project a 2d shader in a 3d object

the title make it looks easy but I'm struggling to get this pie chart shader on my 3d model. For this I'm using three js. here is my code till now:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Site Prof Matheus</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='./styles/main.css'>
<script type='module' src='./src/main.js'></script>
</head>
<body>
</body>
</html>
main.js:
import * as THREE from 'https://cdn.skypack.dev/three'
import { OrbitControls } from 'https://cdn.skypack.dev/three-stdlib/controls/OrbitControls'
import { GLTFLoader } from 'https://cdn.skypack.dev/three-stdlib/loaders/GLTFLoader'
import { vertexShader } from '../shaders/vertex.glsl.js'
import { fragmentShader } from '../shaders/fragment.glsl.js'
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const loader = new GLTFLoader();
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(0x002653, 0.5)
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setPixelRatio(window.devicePixelRatio);
const r_material = new THREE.ShaderMaterial({ //Roulette material
uniforms: {
iTime: { value: 1.0 },
resolution: { value: new THREE.Vector2 }
},
vertexShader,
fragmentShader
})
loader.load(
'../roulette.glb',
function (gltf) {
gltf.scene.traverse((o) => {
if (o.isMesh) o.material = r_material;
});
scene.add(gltf.scene);
},
function () { },
function (err) {
console.log('error: ' + err);
}
)
camera.position.z = 5;
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
controls.enableDamping = true;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
vertex.glsl.js:
const vertexShader = /* glsl */`
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`
// uniform vec3 iResolution;
// uniform int SEGMENTCOUNT;
export { vertexShader };
fragment.glsl.js:
const fragmentShader = /* glsl */`
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
gl_Position
}
`
export { fragmentShader };
and here is my roulette.gbl model
the intended result is to have a shader with colors that I choose, all the parts equal, the colors can repeat and the shader should covers the whole mesh, less the bottom. intended result
PS. i see my object looks a flat plain on the image, i guess just need to add some proper light, here is my object geometry just for sake of curiosity: my mesh geometry
PSS. some antialiasing on the pie chart shader would be very welcome
This is how you can do it, modifying a material with .onBeforeCompile():
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {
OrbitControls
} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 2000);
camera.position.set(0, 1, 1).setLength(12);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", onWindowResize);
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper());
let light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let path = new THREE.Path();
path.moveTo(0, -1);
path.lineTo(4, -1);
path.absarc(4, -0.5, 0.5, Math.PI * 1.5, 0);
path.absarc(4, 0.5, 0.5, 0, Math.PI * 0.5);
path.lineTo(0, -0.5);
let g = new THREE.LatheGeometry(path.getPoints(50), 72);
let m = new THREE.MeshLambertMaterial({
color: 0x00ff7f,
//wireframe: true,
onBeforeCompile: shader => {
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
vPos = position;
`
);
//console.log(shader.vertexShader);
shader.fragmentShader = `
#define ss(a, b, c) smoothstep(a, b, c)
varying vec3 vPos;
${shader.fragmentShader}
`.replace(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec3 col = diffuse;
int N = 37;
float a = atan(vPos.x,-vPos.z)+PI;
float r = PI2/float(N);
float cId = floor(a/r);
vec3 br = mod(cId, 2.) == 0. ? vec3(0) : vec3(1, 0, 0); // black / red
br = cId == 0. ? vec3(0, 0.75, 0) : br; // green
float d = length(vPos.xz);
float fw = length(fwidth(vPos.xz));
col = mix(col, br, ss(3. - fw, 3., d) - ss(4., 4. + fw, d));
col = mix(diffuse, col, clamp(sign(vPos.y), 0., 1.));
vec4 diffuseColor = vec4( col, opacity );
`
);
//console.log(shader.fragmentShader);
}
})
let o = new THREE.Mesh(g, m);
o.position.y = 0.5;
o.rotation.y = Math.PI;
scene.add(o);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>

Not getting expect result from THREE.js/GLSL code

Apologies for the vague question but I wasn't sure how to phrase this. I'm trying to write some THREE.js/GLSL code that produces a circular gradient (for some SDF stuff). With the code below I would expect to see the gradient on the plane, but the plane remains white and nothing else renders on it. Can anyone tell me where I'm going wrong?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script type="x-shader/x-vertex" id="sdfVS">
varying vec2 vUv; // pass the uv coordinates of each pixel to the frag shader
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="sdfFS">
precision mediump float;
uniform vec2 u_resolution;
varying vec2 vUv;
float circle(vec2 pos, float radius)
{
return distance(pos, vec2(radius));
}
void main()
{
vec2 pos = (gl_FragCoord.xy / vUv) * 2.0 - 1.0;
float circle1 = circle(pos, 0.5);
vec3 color = vec3(circle1);
gl_FragColor = vec4(color, 1.0);
}
</script>
<script src="../../js/three.min.js"></script>
<script>
var scene, camera, renderer, aspect, geometry, material, plane;
var container;
var frustumSize = 2;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera( 0.5 * frustumSize * aspect / - 2, 0.5 * frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 0.1, 1 );
cameraOrthoHelper = new THREE.CameraHelper( camera );
scene.add( cameraOrthoHelper );
var width = 1;
var height = 1;
geometry = new THREE.PlaneGeometry(width, height);
material = new THREE.ShaderMaterial( {
vertexShader: document.getElementById('sdfVS').textContent,
fragmentShader: document.getElementById('sdfFS').textContent,
side: THREE.DoubleSide
} );
plane = new THREE.Mesh( geometry, material );
plane.rotation.x = 0;
plane.rotation.y = THREE.Math.degToRad( -90 );
plane.rotation.z = 0;
scene.add( plane )
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
camera.position.x = -1;
camera.position.y = 0;
camera.position.z = 0;
camera.lookAt( scene.position );
camera.updateMatrixWorld();
renderer.render( scene, camera );
}
</script>
</body>
</html>
Can't get this line:
vec2 pos = (gl_FragCoord.xy / vUv) * 2.0 - 1.0;
Seems like you want to calculate uv coordinates, but if so, then you already have uvs (passed in vUv).
You can do the thing this way, as an option:
var scene, camera, renderer, aspect, geometry, material, plane;
var container;
var clock = new THREE.Clock();
var frustumSize = 2;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera(0.5 * frustumSize * aspect / -2, 0.5 * frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, 0.1, 1);
cameraOrthoHelper = new THREE.CameraHelper(camera);
scene.add(cameraOrthoHelper);
var width = 1;
var height = 1;
geometry = new THREE.PlaneGeometry(width, height);
material = new THREE.ShaderMaterial({
uniforms: {time: {value: 0}},
vertexShader: document.getElementById('sdfVS').textContent,
fragmentShader: document.getElementById('sdfFS').textContent,
side: THREE.DoubleSide
});
plane = new THREE.Mesh(geometry, material);
plane.rotation.x = 0;
plane.rotation.y = THREE.Math.degToRad(-90);
plane.rotation.z = 0;
scene.add(plane)
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
camera.position.x = -1;
camera.position.y = 0;
camera.position.z = 0;
camera.lookAt(scene.position);
camera.updateMatrixWorld();
var t = clock.getElapsedTime();
material.uniforms.time.value = t;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script type="x-shader/x-vertex" id="sdfVS">
varying vec2 vUv; // pass the uv coordinates of each pixel to the frag shader
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="sdfFS">
precision mediump float;
uniform float time;
uniform vec2 u_resolution;
varying vec2 vUv;
float circle(vec2 uv, vec2 pos, float radius) {
return smoothstep(radius, 0., length(uv - pos));
}
void main()
{
vec2 uv = vUv * 2. - 1.;
vec2 pos = vec2(cos(time), sin(time));
float circle1 = circle(uv, pos, 1.0);
vec3 color = vec3(circle1);
gl_FragColor = vec4(color, 1.0);
}
</script>

WebGL shader attribute is not being passed accurately

I'm attempting to adopt the threejs example for wireframes ( https://threejs.org/examples/?q=wire#webgl_materials_wireframe ) using WebGL instancing.
This simple reproduction codepen ( https://codepen.io/ubermario/pen/gzByjP?editors=1000 ) shows that a wireframed cube is rendered when a 'center' attribute is passed to the vertex/fragment shaders with THREE.BufferAttribute.
However, it is not renedered as wireframe when the 'center' attribute is passed to the same shaders with THREE.InstancedBufferAttribute.
No errors are generated. Ideas?
<html lang="en">
<head>
<title>Adopted from three.js webgl - materials - wireframe</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
margin: 0px;
background-color: #000000;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.js"></script>
<script type="x-shader/x-vertex" id="vertexShader">
attribute vec3 center;
varying vec3 vCenter;
void main() {
vCenter = center;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec3 vCenter;
float edgeFactorTri() {
vec3 d = fwidth( vCenter.xyz );
vec3 a3 = smoothstep( vec3( 0.0 ), d * 1.5, vCenter.xyz );
return min( min( a3.x, a3.y ), a3.z );
}
void main() {
gl_FragColor.rgb = mix( vec3( 1.0 ), vec3( 0.0 ), edgeFactorTri() );
gl_FragColor.a = 1.0;
}
</script>
<script>
var camera, scene, renderer;
init();
renderer.render( scene, camera );
function init() {
var bufferGeometry, material, mesh;
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 700;
scene = new THREE.Scene();
var geometry1 = new THREE.BoxBufferGeometry( 100, 100,100 );
geometry1.addAttribute( 'center', new THREE.BufferAttribute( fnGetFloat32ArrayCenters( geometry1 ), 3 ) );
var material_1 = new THREE.ShaderMaterial( {
uniforms: {},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
material_1.extensions.derivatives = true;
mesh1 = new THREE.Mesh( geometry1, material_1 );
mesh1.position.x = -100;
scene.add( mesh1 );
var bufferGeometry = new THREE.BoxBufferGeometry( 100, 100,100 );
var geometry2 = new THREE.InstancedBufferGeometry();
geometry2.index = bufferGeometry.index;
geometry2.attributes.position = bufferGeometry.attributes.position;
geometry2.attributes.uv = bufferGeometry.attributes.uv;
//Now with instancing
geometry2.addAttribute( 'center', new THREE.InstancedBufferAttribute( fnGetFloat32ArrayCenters( geometry2 ), 3 ) );
var material_2 = new THREE.ShaderMaterial( {
uniforms: {},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
material_2.extensions.derivatives = true;
mesh2 = new THREE.Mesh( geometry2, material_2 );
mesh2.position.x = 100;
scene.add( mesh2 );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// events
window.addEventListener( 'resize', onWindowResize, false );
function fnGetFloat32ArrayCenters( geometry ) {
var vectors = [
new THREE.Vector3( 1, 0, 0 ),
new THREE.Vector3( 0, 1, 0 ),
new THREE.Vector3( 0, 0, 1 )
];
var position = geometry.attributes.position;
var centers = new Float32Array( position.count * 3 );
for ( var i = 0, l = position.count; i < l; i ++ ) {
vectors[ i % 3 ].toArray( centers, i * 3 );
}
return centers;
} //fnGetFloat32ArrayCenters
} //init
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
</body>
</html>
Solution: The problem appears to be a bug in three.js r83 with meshes created on BoxBufferGeometry vs InstancedBufferGeometry; see the updated codepen that illustrates this in the comments.
The wireframe attribute for material_1 is not applied when its value is false. It appears to be 'stuck' on 'true'; forcing it to 'false' will have no affect on the first cube.
This lead to the misperception that the cube based on material_2 was in error when it was right all along; because it was respecting the default wireframe attribute value of 'false'. When set to 'true' it will display as a wireframed cube.
<html lang="en">
<head>
<title>Adopted from three.js webgl - materials - wireframe</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
margin: 0px;
background-color: #000000;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.js"></script>
<script type="x-shader/x-vertex" id="vertexShader">
attribute vec3 center;
varying vec3 vCenter;
void main() {
vCenter = center;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec3 vCenter;
float edgeFactorTri() {
vec3 d = fwidth( vCenter.xyz );
vec3 a3 = smoothstep( vec3( 0.0 ), d * 1.5, vCenter.xyz );
return min( min( a3.x, a3.y ), a3.z );
}
void main() {
gl_FragColor.rgb = mix( vec3( 1.0 ), vec3( 0.0 ), edgeFactorTri() );
gl_FragColor.a = 1.0;
}
</script>
<script>
var camera, scene, renderer;
init();
renderer.render( scene, camera );
function init() {
var bufferGeometry, material, mesh;
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 700;
scene = new THREE.Scene();
var geometry1 = new THREE.BoxBufferGeometry( 100, 100,100 );
geometry1.addAttribute( 'center', new THREE.BufferAttribute( fnGetFloat32ArrayCenters( geometry1 ), 3 ) );
var material_1 = new THREE.ShaderMaterial( {
uniforms: {},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
wireframe: false //bug:For r83/three.js, property is not applied when the THREE.Mesh constructor is invoked. https://threejs.org/docs/index.html#api/materials/ShaderMaterial.wireframe
} );
material_1.extensions.derivatives = true;
mesh1 = new THREE.Mesh( geometry1, material_1 ); //bug: material_1's wireframe:false property is not applied for r83/three.js
mesh1.position.x = -100;
scene.add( mesh1 );
var bufferGeometry = new THREE.BoxBufferGeometry( 100, 100,100 );
var geometry2 = new THREE.InstancedBufferGeometry();
geometry2.index = bufferGeometry.index;
geometry2.attributes.position = bufferGeometry.attributes.position;
geometry2.attributes.uv = bufferGeometry.attributes.uv;
//Now with instancing
geometry2.addAttribute( 'center', new THREE.InstancedBufferAttribute( fnGetFloat32ArrayCenters( geometry2 ), 3 ) );
var material_2 = new THREE.ShaderMaterial( {
uniforms: {},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
wireframe:true //See: documentation: https://threejs.org/docs/index.html#api/materials/ShaderMaterial.wireframe
} );
material_2.extensions.derivatives = true;
mesh2 = new THREE.Mesh( geometry2, material_2 );
mesh2.position.x = 100;
scene.add( mesh2 );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// events
window.addEventListener( 'resize', onWindowResize, false );
function fnGetFloat32ArrayCenters( geometry ) {
var vectors = [
new THREE.Vector3( 1, 0, 0 ),
new THREE.Vector3( 0, 1, 0 ),
new THREE.Vector3( 0, 0, 1 )
];
var position = geometry.attributes.position;
var centers = new Float32Array( position.count * 3 );
for ( var i = 0, l = position.count; i < l; i ++ ) {
vectors[ i % 3 ].toArray( centers, i * 3 );
}
return centers;
} //fnGetFloat32ArrayCenters
} //init
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
</script>
</body>
</html>

Multiple textures overlapping webGL

I have two objects that I need to render in two different textures using WebGLRenderTarget. After mapping both textures to a plane using shaders and then adding that plane to the main scene, I am having issues drawing the closer object.
I have created a jsfiddle to show my issue: https://jsfiddle.net/11qb7ay7/82/
The real magic occurs here:
void main() {
vec4 color = texture2D(tex_sphere, vUv);
vec4 color_cube = texture2D(tex_cube, vUv);
gl_FragColor = vec4(color.rgb * color_cube.rgb, 1.0);
}
The sphere is placed in front of the cube relative to the camera. How can I draw the sphere pixels instead of the cubes when they overlap?
To be clear, I am trying to find a way to compute the distance from camera of each pixel and render the closer one first
In general if tex_sphere has an alpha channel the you can mix the colors by the alpha channel:
void main()
{
vec4 color = texture2D(tex_sphere, vUv);
vec4 color_cube = texture2D(tex_cube, vUv);
vec3 mixCol = mix(color_cube.rgb, color.rgb, color.a);
gl_FragColor = vec4(mixCol.rgb, 1.0);
}
If the tex_sphere has a black background, which should be omitted, the you have to check if the color of tex_sphere is not black:
void main()
{
vec4 color = texture2D(tex_sphere, vUv);
vec4 color_cube = texture2D(tex_cube, vUv);
vec3 test = step(1.0/512.0, color.rgb);
float a = max(max(test.r, test.g), test.b);
vec3 mixCol = mix(color_cube.rgb, color.rgb, a);
gl_FragColor = vec4(mixCol.rgb, 1.0);
}
Note, mix interpolates between 2 values according to a floating point interpolation value a in the range [0.0, 1.0]. If the a is equal 0.0 then the 1st value is returned and if the a is equal 1.0 then the 2nd value is returned.
step tests whether a value is less than an edge value. If it is less then 0.0 is returned, else 1.0 is returned.
To get a black background you have to set a black "clear" color when you render the sphere to the render target:
function render() {
controls.update();
renderer.setClearColor(0x00);
renderer.render(sphereScene, camera, renderTargets.sphere, true);
renderer.setClearColor(0xccccff);
renderer.render(cubeScene, camera, renderTargets.cube, true);
renderer.setClearColor(0xccccff);
renderer.render(scene, camera);
}
If you want to use an alpha channel, the you have to set setClearAlpha before you render to the render target:
function render() {
controls.update();
renderer.setClearAlpha(0);
renderer.render(sphereScene, camera, renderTargets.sphere, true);
renderer.setClearAlpha(1);
renderer.render(cubeScene, camera, renderTargets.cube, true);
renderer.render(scene, camera);
}
var scene, renderer, camera, controls;
var cubeScene, sphereScene;
var renderTargets;
init();
animate();
function init() {
scene = new THREE.Scene();
cubeScene = new THREE.Scene();
sphereScene = new THREE.Scene();
renderer = new THREE.WebGLRenderer( { antialias: false, alpha: true } );
renderer.setClearColor(0xccccff);
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 4000 );
camera.position.set(0, 0, 200);
renderer.setSize( window.innerWidth, window.innerHeight );
controls = new THREE.OrbitControls(camera, renderer.domElement);
camera.lookAt( scene.position );
var light = new THREE.HemisphereLight( 0xffffff, 0x444444 );
scene.add( light );
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
initObjects();
initRenderTargets(window.innerWidth, window.innerHeight);
}
function onResize() {
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderTargets.sphere.setSize( window.innerWidth, window.innerHeight );
renderTargets.cube.setSize( window.innerWidth, window.innerHeight );
}
function initObjects() {
var cGeometry = new THREE.BoxGeometry( 100, 100, 100 );
var cMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
var cube = new THREE.Mesh( cGeometry, cMaterial );
cube.position.z = -210;
cube.position.y = 100;
var sGeometry = new THREE.SphereGeometry( 75, 32, 32 );
var sMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
});
var sphere = new THREE.Mesh( sGeometry, sMaterial );
sphere.position.z = -100;
sphereScene.add( sphere );
cubeScene.add( cube );
}
function initRenderTargets(width, height){
renderTargets = createRenderTargets(width, height);
var uniforms = {
"tex_cube": { type: "t", value: renderTargets.cube.texture },
"tex_sphere": { type: "t", value: renderTargets.sphere.texture }
}
material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vs_rt').textContent,
fragmentShader: document.getElementById('fs_rt').textContent
});
var plane = new THREE.PlaneGeometry(width, height);
quad = new THREE.Mesh(plane, material);
scene.add(quad);
}
function createRenderTargets(width, height) {
var parameters = {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
};
return {
cube: new THREE.WebGLRenderTarget( width, height, parameters ),
sphere: new THREE.WebGLRenderTarget( width, height, parameters )
};
}
function animate() {
requestAnimationFrame(animate);
render();
}
//------------------------------------------
// Main rendering
//------------------------------------------
function render() {
controls.update();
renderer.setClearAlpha(0);
renderer.render(sphereScene, camera, renderTargets.sphere, true);
renderer.setClearAlpha(1);
renderer.render(cubeScene, camera, renderTargets.cube, true);
renderer.render(scene, camera);
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js"></script>
<script type="text/javascript" src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script id="vs_rt" type="x-shader/x-vertex">
uniform sampler2D tex_cube;
uniform sampler2D tex_sphere;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fs_rt" type="x-shader/x-fragment">
uniform sampler2D tex_cube;
uniform sampler2D tex_sphere;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tex_sphere, vUv);
vec4 color_cube = texture2D(tex_cube, vUv);
vec3 mixCol = mix(color_cube.rgb, color.rgb, color.a);
gl_FragColor = vec4(mixCol.rgb, 1.0);
}
</script>

Sprite transparency sorting error in three.js

I've just downloaded the latest Three.js master "mrdoob-three.js-d6384d2" , I've modified the "webgl_nearestneighbour.html" to show a transparent image and this is the result: http://i.share.pho.to/8cccac74_l.jpeg
I can't understand if it's by design , if it's a webgl error or if it's a three.js error but, as you can see in the bigger ball, near sprites are clipped while far sprites aren't.
Any information is much appreciated (I'm new to webgl).
edit: here's the code.
<html>
<head>
<meta charset="utf-8">
<title>three.js webgl - nearest neighbour</title>
<style>
html, body {
width: 100%;
height: 100%;
}
body {
background-color: #ffffff;
margin: 0;
overflow: hidden;
font-family: arial;
}
#info {
text-align: center;
padding: 5px;
position: absolute;
width: 100%;
color: white;
}
</style>
</head>
<body>
<div id="info">three.js webgl - typed arrays - nearest neighbour for 500,000 sprites</div>
<script src="../build/three.min.js"></script>
<script src="js/TypedArrayUtils.js"></script>
<script src="js/controls/FirstPersonControls.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
//uniform float zoom;
attribute float alpha;
varying float vAlpha;
void main() {
vAlpha = 1.0 - alpha;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = 4.0 * ( 300.0 / length( mvPosition.xyz ) );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D tex1;
varying float vAlpha;
void main() {
gl_FragColor = texture2D(tex1, gl_PointCoord);
gl_FragColor.r = (1.0 - gl_FragColor.r) * vAlpha + gl_FragColor.r;
}
</script>
<script>
var camera, scene, renderer;
var geometry, material, mesh;
var controls;
var objects = [];
var amountOfParticles = 500000, maxDistance = Math.pow(120, 2);
var positions, alphas, particles, _particleGeom
var clock = new THREE.Clock();
var blocker = document.getElementById( 'blocker' );
var instructions = document.getElementById( 'instructions' );
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000000);
scene = new THREE.Scene();
controls = new THREE.FirstPersonControls( camera );
controls.movementSpeed = 100;
controls.lookSpeed = 0.1;
var materials = [
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/px.jpg' ) } ), // right
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/nx.jpg' ) } ), // left
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/py.jpg' ) } ), // top
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/ny.jpg' ) } ), // bottom
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/pz.jpg' ) } ), // back
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/cube/skybox/nz.jpg' ) } ) // front
];
mesh = new THREE.Mesh( new THREE.BoxGeometry( 10000, 10000, 10000, 7, 7, 7 ), new THREE.MeshFaceMaterial( materials ) );
mesh.scale.x = - 1;
scene.add(mesh);
//
renderer = new THREE.WebGLRenderer(); // Detector.webgl? new THREE.WebGLRenderer(): new THREE.CanvasRenderer()
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// create the custom shader
var imagePreviewTexture = THREE.ImageUtils.loadTexture( 'textures/football.png');
imagePreviewTexture.minFilter = THREE.LinearMipMapLinearFilter;
imagePreviewTexture.magFilter = THREE.LinearFilter;
pointShaderMaterial = new THREE.ShaderMaterial( {
uniforms: {
tex1: { type: "t", value: imagePreviewTexture },
zoom: { type: 'f', value: 9.0 },
},
attributes: {
alpha: { type: 'f', value: null },
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
transparent: true
});
//create particles with buffer geometry
var distanceFunction = function(a, b){
return Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2);
};
positions = new Float32Array( amountOfParticles * 3 );
alphas = new Float32Array( amountOfParticles );
_particleGeom = new THREE.BufferGeometry();
_particleGeom.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
_particleGeom.addAttribute( 'alpha', new THREE.BufferAttribute( alphas, 1 ) );
particles = new THREE.PointCloud( _particleGeom, pointShaderMaterial );
for (var x = 0; x < amountOfParticles; x++) {
positions[ x * 3 + 0 ] = Math.random() * 1000;
positions[ x * 3 + 1 ] = Math.random() * 1000;
positions[ x * 3 + 2 ] = Math.random() * 1000;
alphas[x] = 1.0;
}
var measureStart = new Date().getTime();
// creating the kdtree takes a lot of time to execute, in turn the nearest neighbour search will be much faster
kdtree = new THREE.TypedArrayUtils.Kdtree( positions, distanceFunction, 3 );
console.log('TIME building kdtree', new Date().getTime() - measureStart);
// display particles after the kd-tree was generated and the sorting of the positions-array is done
scene.add(particles);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
}
function animate() {
requestAnimationFrame( animate );
//
displayNearest(camera.position);
controls.update( clock.getDelta() )
renderer.render( scene, camera );
}
function displayNearest(position) {
// take the nearest 200 around him. distance^2 'cause we use the manhattan distance and no square is applied in the distance function
var imagePositionsInRange = kdtree.nearest([position.x, position.y, position.z], 100, maxDistance);
// We combine the nearest neighbour with a view frustum. Doesn't make sense if we change the sprites not in our view... well maybe it does. Whatever you want.
var _frustum = new THREE.Frustum();
var _projScreenMatrix = new THREE.Matrix4();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
_frustum.setFromMatrix( _projScreenMatrix );
for ( i = 0, il = imagePositionsInRange.length; i < il; i ++ ) {
var object = imagePositionsInRange[i];
var objectPoint = new THREE.Vector3().fromArray( object[ 0 ].obj );
if (_frustum.containsPoint(objectPoint)){
var objectIndex = object[0].pos;
// set the alpha according to distance
alphas[ objectIndex ] = 1.0 / maxDistance * object[1];
// update the attribute
_particleGeom.attributes.alpha.needsUpdate = true;
}
}
}
init();
animate();
</script>
</body>
</html>
To recap what has been said in the comments, the solution to this problem is to disable alpha blending, and in the shader you have to discard the pixel drawn based on the alpha value of the input texture.
So that this...
void main() {
gl_FragColor = texture2D(tex1, gl_PointCoord);
gl_FragColor.r = (1.0 - gl_FragColor.r) * vAlpha + gl_FragColor.r;
}
...becomes this
void main() {
gl_FragColor = texture2D(tex1, gl_PointCoord);
gl_FragColor.r = (1.0 - gl_FragColor.r) * vAlpha + gl_FragColor.r;
if ( gl_FragColor.a < 0.5 ) discard;
}

Resources