Custom Shader for combined buffer geometry

Hi I am learning about shaders and I have a question

So, I have 3 different geometries which I merge with BufferGeometryUtils

const geometry1 = new BufferGeometry();
geometry1.setAttribute("position", new BufferAttribute(
   new Float32Array( [
       1, 0,  0,
       0, 0,  0,
       0,  1,  0,
   ]), 3)
)
....
const geometry2 = ...
const geometry3 = ...

const allGeometries = BufferGeometryUtils.mergeBufferGeometries([geometry1, geometry2, geometry3], false)

all geometries inside this allGeometries should have a different color, and I want to render the mesh using this geometry with a single render call

I know there is a solution to add a vertexColor attribute to the geometry with desired colors, but in my case I’m going to have N elements with different colors, so instead of creating N geometries I’m thinking to create N materials with required colors

so I decided to create a custom shader.
I added an additional attribute for each geometry:

geometry1.setAttribute( 'myColor', new BufferAttribute( new Float32Array([1, 1, 1 .... number of vertices]), 1 ) );
geometry2.setAttribute( 'myColor', new BufferAttribute( new Float32Array([2, 2, 2 .... number of vertices]), 1 ) );
....

so I made a custom RawShaderMaterial pass the uniforms:

    uniforms: {
        uColor1: {value: new THREE.Color("teal")},
        uColor2: {value: new THREE.Color("orange")},
        uColor3: {value: new THREE.Color("red")},
    },

and in vertex shader, I’m passing the varying of myColor to the fragment shader
then my fragment shader looks like this:

uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
varying float vMyColor;


void main()
{
    if (vMyColor == 1.0) {
        gl_FragColor = vec4(uColor1, 1.0);
    } else if (vMyColor == 2.0) {
        gl_FragColor = vec4(uColor2, 1.0);
    }
   else {
        gl_FragColor = vec4(uColor3, 1.0);
    }

the good part - it works as desired. :slightly_smiling_face: The question is - is it a correct way to do it like this?
I tried to google a solution and bumped into mentions that it’s a bad practice to have an if/else statement in the shader.
also, is there any other solution to not add an additional attribute for each vertice?

Hi!
Instead of the set of uniforms with colours, pass a texture with colours (I used DataTexture) and read it in shaders with the UV based on the index of color, so no ifs needed in shaders: Edit fiddle - JSFiddle - Code Playground

Note, sphere and torus use the same color index - 0, box - 1, tetrahedron - 2.

3 Likes

Hey @prisoner849 , thanks for the answer!
It took some time to understand the code :sweat_smile:
now I know more about DataTexture and onBeforeCompile callback in Materials :grinning:

it’s a nice solution to avoid the if/else statement in the shader

I can assume there is nothing we can do about using the set of attributes for each vertex to keep data about hte color/colorId. (I mean we still need to use already existing or add the new attribute for each vertex in the geometry)

1 Like