3d – GLSL Shade a box different colors for two ends with one material in Three.js

First, I knew the following basic knowledges,

Then, I can use shader to render 2D graphics on the Shadertoy or GLSL Sandbox Gallery cross-browser online editor through WebGL or with the cross-platform SHADERed IDE. It’s easy to use GLSL to draw 2D objects because 2D coordinates correspond to gl_Position. But I failed to use ShaderMaterial with the GLSL code to render a BoxGeometry I created in three.js. See the following code for my case.

var container;
var camera, scene, renderer;
var mesh;
var uniforms;

var clock = new THREE.Clock();

init();
animate();

function init() {
    container = document.getElementById('container');

    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
    camera.position.z = 2.0;
    camera.position.y = 1.0;
    camera.rotation.x = -0.45;

    scene = new THREE.Scene();

    //var boxGeometry = new THREE.PlaneGeometry(0.75, 0.75, 1);
    var boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5, 1,1,1);

    uniforms = { u_time: { type: "f", value: 0.0 } };

    var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        side: THREE.DoubleSide,
        transparent: true,
        vertexShader: document.getElementById('vertexShader2').textContent,
        fragmentShader: document.getElementById('fragment_shader2').textContent
    });

    mesh = new THREE.Mesh(boxGeometry, material);
    scene.add(mesh);

    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(0xffffff, 1);
    container.appendChild(renderer.domElement);

    onWindowResize();

    window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize(event) {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
    requestAnimationFrame(animate);
    render();
}

function render() {
    var delta = clock.getDelta();
    uniforms.u_time.value += delta;
    mesh.rotation.y += delta * 0.5;
    renderer.render(scene, camera);
}
<html>
    <head>
        <title>App</title>
    </head>
    <body>
<script>
window.onerror = function (msg, url, line, col, error) {
    // Note that col & error are new to the HTML 5 spec and may not be 
    // supported in every browser.  It worked for me in Chrome.
    var extra = !col ? '' : 'ncolumn: ' + col;
    extra += !error ? '' : 'nerror: ' + error;

    // You can view the information in an alert to see things working like this:
    alert("Error: " + msg + "nurl: " + url + "nline: " + line + extra);

    // TODO: Report this error via ajax so you can keep track of what pages have JS issues

    var suppressErrorAlert = true;
    // If you return true, then error alerts (like in older versions of 
    // Internet Explorer) will be suppressed.
    return suppressErrorAlert;
};
</script>
    <!-- <script src="https://threejs.org/build/three.min.js"></script> -->
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script> -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>

    <div id="container"></div>

<script id="fragment_shader2" type="x-shader/x-fragment">
varying vec3 v_color;

void main( void ) {
    gl_FragColor = vec4(v_color, 1.);    
}
</script>
<script id="vertexShader2" type="x-shader/x-vertex">
varying vec3 v_color;
            
void main()
{
    v_color = vec3(position.z < 0.25, 0, position.z >= 0.25);
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}
</script>

<script id="fragment_shader" type="x-shader/x-fragment">
varying vec2 v_uv;

void main( void ) {
    vec2 uv = v_uv;
    // Zooms out by a factor of 2.0
    uv *= 2.0;
    // Shifts every axis by -1.0
    uv -= 1.0;
    
    // Base color for the effect
    vec3 color = vec3 ( .2, 1., 0. );

    // specify size of border. 0.0 - no border, 1.0 - border occupies the entire space
    vec2 borderSize = vec2(0.3); 

    // size of rectangle in terms of uv 
    vec2 rectangleSize = vec2(1.0) - borderSize; 

    // distance field, 0.0 - point is inside rectangle, 1.0 point is on the far edge of the border.
    float distanceField = length(max(abs(uv)-rectangleSize,0.0) / borderSize);
    
    // calculate alpha accordingly to the value of the distance field
    float alpha = 1.0 - distanceField;

    gl_FragColor = vec4(color, alpha);    
}
</script>

<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 v_uv;
            
void main()
{
    v_uv = uv;
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}
</script>

    <div id="info">
        <a href="https://threejs.org/examples/#webgl_postprocessing_outline">WebGL Postprocessing Outline</a>
    </div>
    <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Outline Pass by 
    <a href="http://eduperiment.com" target="_blank" rel="noopener">Prashant Sharma</a> and
    <a href="https://clara.io" target="_blank" rel="noopener">Ben Houston</a>
    <br><br>
</body>
<script type="module" src="index.js"></script>
</html>

I guessed that it was because it could distinguish between vertexes in the same middle position of the +x sides and -x sides for v_color = vec3(position.x < 0.25, 0, position.x >= 0.25);.

Finally, I also knew that I can Split the geometry into groups with different materials, but I just want to use one ShaderMaterial to do this.


var geo = new THREE.BoxGeometry(48, 48, 48, 2);
var magnetMaterial = ('red', 'blue', 'green', 'purple', 'cyan', 'white', 'black', 'pink', 'orange', 'gray').map(it =>
    new THREE.MeshPhongMaterial({
        color: it, side: THREE.DoubleSide,
        polygonOffset: true, polygonOffsetFactor: 1
    }));

//Initializes the magnetic orientation to +x, red on the left side and blue on the right side.
//faces().materialIndex: -x,-x, +x,+x, +z,+z,+z,+z, -y,-y,-y,-y, -z,-z,-z,-z, +y,+y,+y,+y
// var faceColors = (1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0);
// console.log('faceColors', geo.faces.map((it, idx) => it.materialIndex = faceColors(idx))); // for Three.js 1.20

// let's regroup materialIndex of five faces of the two ends of the Cuboid (x+,x-,y+,y-,z+,z-)
console.log(geo.getIndex().count, geo.groups.slice(0));
geo.clearGroups();
(0, 1, 1, 0, 1, 0, 1, 0, 0, 1).forEach((val, idx) => geo.addGroup(6 * idx, 6, val));

Anyone could help me to explain and solve this problem because the gradient is not what I want?