three.jsglslshader

Alpha Gradient over mesh geometry in shader code


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.

enter image description here


Solution

  • Yes 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.geometry.computeBoundingBox()
        }
    
        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: image of result and without box helper: image of result without box helper you can check full code and view result from different angles here: https://codepen.io/andromeda2/full/LEYJMEN