I have this scene and for the half transparent box geometry I want to create a shader that gradually reduced the alpha value of gl_FragColor going over the long axis (in this case the z axis in three js).
I am a bit at a loss on how to do that. I thought I could do that with UVs but they run along the wrong axis.
I basically need a way to find out at which percentage point I am on the z axis relative to the mesh size.
Is that even possible in a shader?
I could also send the length size via a uniform. But then again, I missing the overall concept on how to reach the goal. Any help would be greatly appreciated.
I have this scene and for the half transparent box geometry I want to create a shader that gradually reduced the alpha value of gl_FragColor going over the long axis (in this case the z axis in three js).
I am a bit at a loss on how to do that. I thought I could do that with UVs but they run along the wrong axis.
I basically need a way to find out at which percentage point I am on the z axis relative to the mesh size.
Is that even possible in a shader?
I could also send the length size via a uniform. But then again, I missing the overall concept on how to reach the goal. Any help would be greatly appreciated.
Share Improve this question asked Mar 27 at 20:56 Johannes KlaußJohannes Klauß 11.1k18 gold badges71 silver badges127 bronze badges 1 |2 Answers
Reset to default 2Yes it is possible to be done with shaders, here is function which will return material which you can set for your mesh to achieve effect you want:
function materialGradientByAxis(mesh, color = 0xff_ff_00, axis = 0, reverse = false) {
/*- mesh: must be instanceof THREE.Mesh
mesh must have geometry (mesh.geometry)
expected static geometry attribute, but dynamic transform matrix
- axis: 0: x , 1: y, 2: z (in mesh.geometry space)
- reverse:
false: axis local min: alpha = 0, max: alpha = 1
true: axis local min: alpha = 1, max: alpha = 0
*/
if ( !(mesh?.geometry?.boundingBox instanceof THREE.Box3) ) {
mesh.geometryputeBoundingBox()
}
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
u_bboxMin: { value: mesh.geometry.boundingBox.min },
u_bboxDelta: { value: mesh.geometry.boundingBox.max.clone().sub(mesh.geometry.boundingBox.min) },
u_axisIndex: { value: axis },
u_reverse: { value: reverse },
u_color: { value: new THREE.Color(color) }
},
vertexShader:`
varying vec3 vPosition;
void main() {
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`,
fragmentShader:`
varying vec3 vPosition;
uniform vec3 u_bboxMin;
uniform vec3 u_bboxDelta;
uniform int u_axisIndex;
uniform bool u_reverse;
uniform vec3 u_color;
void main() {
float pos = ((vPosition - u_bboxMin)/u_bboxDelta)[u_axisIndex];
vec4 color = vec4(
u_color,
u_reverse ? 1.0 - pos : pos
);
gl_FragColor = color;
}`
});
shaderMaterial.transparent = true;
shaderMaterial.side = THREE.DoubleSide;
shaderMaterial.depthWrite = false;
return shaderMaterial;
}
you can use it like this:
const geometry = new THREE.BoxGeometry( 2, 2, 2 );
const cube = new THREE.Mesh( geometry );
cube.material = materialGradientByAxis( cube, 0xff_ff_00, 0, false )
and here is a result: and without box helper: you can check full code and view result from different angles here: https://codepen.io/andromeda2/full/LEYJMEN
You can control each pixel via transparency of box or any geometry, by smoothstep
function you can calculate transparency based on x-coords
of the fragment
. Then alpha
value determines transparency of fragment
.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 3;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(2, 1, 1);
const settings = {
fade: 1.0,
fadePoint: 0.0
};
const material = new THREE.ShaderMaterial({
uniforms: {
uFade: {
value: settings.fade
},
uFadePoint: {
value: settings.fadePoint
},
uFadeRange: {
value: 1.0
}
},
vertexShader: `
varying vec3 vPosition;
void main() {
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uFade;
uniform float uFadePoint;
uniform float uFadeRange;
varying vec3 vPosition;
void main() {
float alpha = smoothstep(uFadePoint, uFadePoint + uFadeRange, vPosition.x) * uFade;
gl_FragColor = vec4(0.0, 0.5, 1.0, alpha);
}
`,
transparent: true,
depthWrite: false
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const gui = new GUI();
gui.add(settings, 'fade', 0, 1).name('Fade Strength').onChange(value => {
material.uniforms.uFade.value = value;
});
gui.add(settings, 'fadePoint', -3, 3).name('Fade Point (World X)').onChange(value => {
material.uniforms.uFadePoint.value = value;
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
https://codepen.io/Lucas_Mas/full/yyLxWEM
float
attribute for each vertex, which is 0 on one end of the z-axis and 1 on the other end. Set thisfloat
as output for the VS and input for the FS, where these will arrive interpolated (from 0 to 1) along the z-axis and could then be used as the alpha value. Depending on how the geometry (i.e. positions) of the box are defined, you could also use the position's z-values in the VS directly to set the outputfloat
. Does this answer your question? – Yun Commented Mar 28 at 4:11