0
\$\begingroup\$

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.

/** @type {HTMLElement} */ var container;
var camera, scene, renderer;
/** @type {THREE.Mesh} */ var mesh;
var uniforms;
/** @type {THREE.TrackballControls} */ var controls;
/** @type {THREE.LineSegments} */ var wireframe;

var clock = new THREE.Clock();

try {
    init();
    animate();
} catch (err) {
    alert(err);
}
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);

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

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

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

    var geo, lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 });
    // lineMaterial.depthTest = false;
    // lineMaterial.polygonOffset = true;
    // lineMaterial.depthTest = true;
    // lineMaterial.polygonOffsetFactor = -2;
    // lineMaterial.polygonOffsetUnits = 0.1;
    if (0) {
        geo = new THREE.EdgesGeometry(mesh.geometry);
    } else {
        geo = new THREE.WireframeGeometry(boxGeometry);
    }
    wireframe = new THREE.LineSegments(geo, lineMaterial);
    mesh.add(wireframe); // wireframe.material === lineMaterial

    scene.add(mesh);

    var axesHelper = new THREE.AxesHelper();
    mesh.add(axesHelper);

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

    onWindowResize();

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

    initControls();
    initDatGui();
}

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

function animate() {
    // requestAnimationFrame(animate);
    try {
        render();
    } catch (err) {
        if (confirm("An error occured:\n" + err.message)) {
            return;
        }
    }
    requestAnimationFrame(animate);
}

function render() {
    if (controls) controls.update();
    var delta = clock.getDelta();
    uniforms.u_time.value += delta;
    mesh.rotation.y += delta * 0.5;
    renderer.render(scene, camera);
}

function initControls() {
    if (!THREE.TrackballControls) return

    controls = new THREE.TrackballControls(camera, renderer.domElement);

    // controls.enabled = true; // default is true
    // controls.target = new THREE.Vector3(); // default is new Vector3()

    // controls.rotateSpeed = 3.0; // default is 1.0
    // controls.zoomSpeed = 1.2; // default is 1.2
    // controls.panSpeed = 0.3; // default is 0.3

    // controls.noRotate = false; // default is false
    // controls.noZoom = false; // default is false
    // controls.noPan = false; // default is false

    // controls.staticMoving = false; // default is false
    // controls.dynamicDampingFactor = 0.2; // default is 0.2

    // controls.minDistance = 0;
    // controls.maxDistance = Infinity;
}

function initDatGui() {
    if ('object' !== typeof dat && !dat.GUI) return;
    var datGui = new dat.GUI({ closed: true }); // panel is collapsed 
    if (controls) {
        datGui.add(controls, "enabled").name("Enable").onChange(change);
        datGui.add(controls, "rotateSpeed", 1.0, 10.0).onChange(change);
        datGui.add(controls, "zoomSpeed", 1.0, 5).onChange(change);
        datGui.add(controls, "panSpeed", 0.1, 1.0).onChange(change);
        datGui.add(controls, "noRotate").name("Rotate").onChange(change);
        datGui.add(controls, "noZoom").name("Zoom").onChange(change);
        datGui.add(controls, "noPan").name("Pan").onChange(change);
        datGui.add(controls, "staticMoving").name("Static Moving").onChange(change);
        datGui.add(controls, "dynamicDampingFactor", 0.1, 1.0).name("Dynamic Damping Factor").onChange(change);
        datGui.add(controls, "minDistance", 0, 5).name("MinDistance").onChange(change);
        datGui.add(controls, "maxDistance", 5, 10).name("MaxDistance").onChange(change);
        datGui.add(controls, "reset").name("Reset").onChange(function resetTrackballControls() {
            this.object.reset();
        });
    }

    // var gui = { widthSegments: 1 }
    datGui.add(mesh.geometry.parameters, "widthSegments", 1, 66, 1).name("Width Segments").onChange(function updateGeometrySegments(newValue) {
        wireframe.geometry = mesh.geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5, newValue);
    });
    datGui.add(wireframe, "visible").name("Show Wireframe");

    function change(newValue) {
        this.object[this.property] = newValue;
    }
}
<!DOCTYPE html>
<html>

<head>
<meta charset='utf-8'>
<meta name="description" content="WebGL Shader in Three.js">
<title>Shader</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<style>
a { color: #08f; }
b { color: lightgreen; }
html, body, .bespread { margin:0; padding:0; width:100%; height:100%; }
canvas { border: 1px dotted salmon; }
#info { position:absolute; bottom:0; }
</style>
</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 id="fragmentShader" type="x-shader/x-fragment">
varying vec3 v_color;

void main( void ) {
    gl_FragColor = vec4(v_color, 1.);    
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 v_color;

void main()
{
    v_color = vec3(position.x < 0., 0, position.x >= 0.);
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}
</script>

    <div id="container"></div>
    <div id="info">
        <a href="https://threejs.org/examples/#webgl_postprocessing_outline">WebGL Postprocessing Outline</a>
        <br>
        <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>
    </div>
</body>
<!-- <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 type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.130.1/examples/js/controls/TrackballControls.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js"></script>
<!-- <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?

\$\endgroup\$
3
  • \$\begingroup\$ What kind of image does your current code generate, and how does it differ from what you want it to generate? \$\endgroup\$
    – DMGregory
    Jul 22 at 13:14
  • \$\begingroup\$ It's a cuboid of which one end is blue, the other end is red. but the color is gradient. \$\endgroup\$
    – samm
    Jul 22 at 13:21
  • \$\begingroup\$ Want to include pictures in your question showing the result you have now? From your reply, I can't tell whether you're describing what you have now or what you want. I can't tell how you want to change what you have now to make it into what you want. \$\endgroup\$
    – DMGregory
    Jul 22 at 13:35
0
\$\begingroup\$

Last week, I moved the code about determining color from the vertex fragment to the fragment shader, it worked. That is to say,

Fragment Shader

varying vec3 v_pos;

void main( void ) {
    gl_FragColor = vec4(vec3(step(v_pos.x, 0.), 0., step(0., v_pos.x)), 1.);    
}

Vertex Shader

varying vec3 v_pos;

void main()
{
    v_pos = position;
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}

I guessed I had a fixed mind-set or could not understand thoroughly how the fragment/pixel shader works with a custom 3D mesh defined by vertices, because I always played with 2D gl_FragCoord.xy in an online live editor for fragment shaders in early learning.

  1. Vertex Shader

A vertex shader can manipulate the attributes of vertices. which are the corner points of your polygons.

Despite the fact varying was deprecated in GLSL 1.30 (OpenGL 3.0) and removed in GLSL 1.40 (Removed qualifiers).

  1. Fragment Shader (Pixel Shader)

The fragment shader on the other hand takes care of how the pixels between the vertices look. They are interpolated between the defined vertices following specific rules.

  1. varying

See ShaderMaterial - Vertex shaders and fragment shaders

Varyings are variables that are passed from the vertex shader to the fragment shader. For each fragment, the value of each varying will be smoothly interpolated from the values of adjacent vertices.


The output of a vertex shader will be interpolated as the input of a fragment shader. As a result, as for the example in the question, WebGL interpolates between these colors on each face (triangle) using a varying variable (v_color).

If we want our faces to be completely red/blue, we should define all pixels red/blue. So we can do that in the fragment shader rather than in the vertex shader because the internal pixels cannot be controlled by a vertex shader except for the pixels specified by those corner vertices.

To manipulate the color for each position, we should use a fragment shader, or there is a gradient between the vertices if gl_FragColor is directly determined in a vertex shader.

\$\endgroup\$
1
  • \$\begingroup\$ See also WebGL How It Works for the color interpolation animation (v_color is interpolated between v0, v1 and v2). \$\endgroup\$
    – samm
    Jul 26 at 12:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.