0
\$\begingroup\$

Hey I am writing a Shader for Three.js.

My goal is to have multiple objects with the same shader and each object should have a color set on the object that is then given to the shader. The goal is to have multiple "units" (think of an RTS game) each with the team color.

Screenshots of two units I red through a couple of documentation pages and am not really sure how to handle that. I looked into https://threejs.org/docs/#api/materials/ShaderMaterial.

I have tried:

  • changing a uniform but from my limited knowledge and trial and error I came to the conclusion that I can not change a uniform value during a drawcycle.
  • change the uniform in onBeforeRender which gave me an error that v was not defined

I am not sure how to go on now. In the ShaderMaterial Documentation is written:

You're recommended to update custom Uniform values depending on object and camera in Object3D.onBeforeRender because Material can be shared among meshes, matrixWorld of Scene and Camera are updated in WebGLRenderer.render, and some effects(ex:VREffect) render a scene with private their own cameras.

So did I just change the values wrong and it actually works with a uniform that changes? In this Github issue someone wanted to draw meshes in different colors and got suggested to create different materials for each mesh (https://github.com/mrdoob/three.js/issues/1532). But that would kill the idea of having one shader per unit regardless of the team.

\$\endgroup\$
6
  • \$\begingroup\$ Make multiple materials, set a different color on each material, then make a new Mesh for each object. A Mesh is a collection of a Material and a Geometry. It's whole point is to let you have multiple copies of some Geometry with different material parameters at different places in your scene. \$\endgroup\$
    – gman
    Commented Dec 27, 2017 at 4:14
  • \$\begingroup\$ well that defies the purpose of this shader. I would not be able to have a color wheel and my color be choosable by the player. I'm looking into something that let's me dynamically swap the colors of a team like this gamedevelopment.tutsplus.com/tutorials/… but in 3D. Thanks for your answer nevertheless. \$\endgroup\$
    – TheCell
    Commented Dec 27, 2017 at 8:25
  • \$\begingroup\$ Not sure what you mean. You can do that exact thing from the page you linked to by using different materials. \$\endgroup\$
    – gman
    Commented Dec 27, 2017 at 8:31
  • \$\begingroup\$ hm maybe I'm just too new to the whole shaderprogramming. Would I have to create a new Material every drawcycle while I was dragging my mouse along the color wheel? Would I have to have 2^8, 2^16 or more different materials to cover every possible color? Thanks. \$\endgroup\$
    – TheCell
    Commented Dec 27, 2017 at 8:39
  • 2
    \$\begingroup\$ You would have one material per object you're drawing. In the page you linked the top screenshot shows 5 people in different colors so you'd have 5 materials, one for each person with that person's color settings. If you want to change the color of one person you just need to set that person's material colors. Each person would have a mesh. Each Mesh would use the same Geometry but that person's Material. 1 Geometry, 5 Materials, 5 Meshes \$\endgroup\$
    – gman
    Commented Dec 27, 2017 at 10:04

1 Answer 1

1
\$\begingroup\$

Thanks to gman for the help:

You would have one material per object you're drawing. In the page you linked the top screenshot shows 5 people in different colors so you'd have 5 materials, one for each person with that person's color settings. If you want to change the color of one person you just need to set that person's material colors. Each person would have a mesh. Each Mesh would use the same Geometry but that person's Material. 1 Geometry, 5 Materials, 5 Meshes

And here is the solution:

enter image description here Prepare the different Materials with the same uniform names.

let team1knightUniforms = {
        textureKnight:
        {
            type: "t",
            value: THREE
                .ImageUtils
                .loadTexture("objects/customObjs/knight/Myknight.png")
        },
        teamcolor:
        {
            value: new THREE.Color(options.teamcolor1)
        }
    };

let team1KnightMaterial = new THREE.ShaderMaterial(
    {
        vertexShader:
            document
            .getElementById("teamcolorVertexShader")
            .textContent,
        fragmentShader:
            document
            .getElementById("teamcolorFragmentShader")
            .textContent,
        uniforms: team1knightUniforms
    }
);

let team2knightUniforms = {
        textureKnight:
        {
            type: "t",
            value: THREE
                .ImageUtils
                .loadTexture("objects/customObjs/knight/Myknight.png")
        },
        teamcolor:
        {
            value: new THREE.Color(options.teamcolor2)
        }
    };

let team2KnightMaterial = new THREE.ShaderMaterial(
    {
        vertexShader:
            document
            .getElementById("teamcolorVertexShader")
            .textContent,
        fragmentShader:
            document
            .getElementById("teamcolorFragmentShader")
            .textContent,
        uniforms: team2knightUniforms
    }
);

Then add the object twice and asign the different Materials. The Materials do have the Texture and uniform names in common:

let objFile = "objects/customObjs/knight/Myknight.obj";
let loader = new THREE.OBJLoader();
loader.load( objFile, function( object )
{
    object.children.forEach( function (obj, index)
    {
        obj.material = team1KnightMaterial;
        obj.position.x = 5;
        obj.position.z = 5;
        obj.teamNumber = 1;
        objects.push( obj );
    });

    shaderExampleScene.add(object);
});

loader.load( objFile, function( object )
{
    object.children.forEach( function (obj, index)
    {
        obj.material = team2KnightMaterial;
        obj.position.x = 5;
        obj.position.z = 10;
        obj.teamNumber = 2;
        objects.push( obj );
    });


    shaderExampleScene.add(object);
});

Finally the Fragmentshader picks up the uniform and mixes the color in where Alpha is getting transparent:

precision mediump float;
uniform sampler2D textureKnight;
uniform vec3 teamcolor;

varying vec2 vUv;

void main()
{
    vec4 fragColor = texture2D(textureKnight, vUv);
    fragColor.r = fragColor.r * fragColor.a + teamcolor.r * ( 1.0 - fragColor.a);
    fragColor.g = fragColor.g * fragColor.a + teamcolor.g * ( 1.0 - fragColor.a);
    fragColor.b = fragColor.b * fragColor.a + teamcolor.b * ( 1.0 - fragColor.a);
    gl_FragColor = fragColor;
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .