Search code examples
reactjsglslshaderfragment-shaderreact-three-fiber

Three js and glsl multiple texture rendering issue


Hello i am trying to do 3d model configuration example with react, three fiber js and glsl

There must be multiple stickers and stickers has to be arrangeable dynamicly(position, scale, rotation) so I decided to go with shaders

When all the stickers set in advance there is no problem

But the problem is stickers will be added by client and i couldnt find a way out

Currently the issue is i can not use dynamic indexing for for loop in glsl

Does anybody has any idea how can i accomplish that Many thanks in advance

this is what i have in canvas

return (
  <mesh geometry={nodes.cloth_parent.geometry}>
    <shaderMaterial
      uniforms={{
        leftSleeveColor: { value: new Vector3(...leftSleeveColor) },
        blendFactor: { value: blendFactor },
        lightPosition: { value: new Vector3(2.0, 2.0, 2.0) }, // Lighting position
        diffuseMap: { value: diffuseMapTexture },
        normalMap: { value: normalMapTexture },
        numStickers: { value: stickers.length },
        stickerTextures: { value: stickerTextures },
        stickerPositions: {
          value: stickers.map(
            (sticker) => new Vector2(...sticker.position)
          ),
        },
        stickerScales: {
          value: stickers.map((sticker) => sticker.scale),
        },
        stickerRotations: {
          value: stickers.map((sticker) => sticker.rotation),
        },
      }}
      vertexShader={vertexShader}
      fragmentShader={fragmentShader}
    />
  </mesh>
);

and this is what i have as fragment

const fragmentShader = `
    uniform vec3 leftSleeveColor;
    uniform float blendFactor; // Dynamic blend factor
    uniform vec3 lightPosition;
    uniform sampler2D diffuseMap;
    uniform sampler2D normalMap; // Normal map for fabric texture
    uniform int numStickers; // Number of stickers
    uniform sampler2D stickerTextures[10]; // Array of up to 10 stickers
    uniform vec2 stickerPositions[10]; // Array of sticker positions
    uniform float stickerScales[10]; // Array of sticker scales
    uniform float stickerRotations[10]; // Array of sticker rotations

    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;

    void main() {
      // Sample the normal map and adjust normals
      vec3 normalFromMap = texture2D(normalMap, vUv).rgb;
      normalFromMap = normalize(normalFromMap * 2.0 - 1.0);
      vec3 adjustedNormal = normalize(vNormal + normalFromMap);

      // Lighting calculation
      vec3 lightDir = normalize(lightPosition - vPosition);
      float lightIntensity = max(dot(adjustedNormal, lightDir), 0.0);
      vec3 baseColor = texture2D(diffuseMap, vUv).rgb * lightIntensity;

      // Apply the red color to the left sleeve
      if (vUv.x > 0.1 && vUv.x < 0.3 && vUv.y > 0.6 && vUv.y < 0.8) {
        vec3 sleeveColorWithLight = leftSleeveColor * lightIntensity;
        baseColor = mix(baseColor, sleeveColorWithLight, blendFactor);
      }

      // Loop through each sticker and apply transformations
      for (int i = 0; i < numStickers; i++) {
        vec2 translatedUV = (vUv - stickerPositions[i]) / stickerScales[i];

        // Rotate the UVs around the sticker's center
        vec2 stickerCenter = vec2(0.5, 0.5);
        vec2 centeredUV = translatedUV - stickerCenter;
        float cosTheta = cos(stickerRotations[i]);
        float sinTheta = sin(stickerRotations[i]);
        mat2 rotationMatrix = mat2(cosTheta, -sinTheta, sinTheta, cosTheta);
        vec2 rotatedUV = rotationMatrix * centeredUV + stickerCenter;

        // Sample the sticker texture using the transformed UVs
        vec4 stickerColor = texture2D(stickerTextures[i], rotatedUV);

        // Apply the sticker based on alpha and lighting
        if (rotatedUV.x >= 0.0 && rotatedUV.x <= 1.0 && rotatedUV.y >= 0.0 && rotatedUV.y <= 1.0) {
          vec3 stickerEffect = stickerColor.rgb * lightIntensity;
          baseColor = mix(baseColor, stickerEffect, stickerColor.a);
        }
      }

      gl_FragColor = vec4(baseColor, 1.0); // Output final color with lighting and stickers
    }
  `;

Solution

  • SOLUTİON I created fragment and uniform template for sticker and added them dynamicly into fragments and also i put add sticker and update sticker functions inside startTransition

    fragment template:

    const createStickerFragment = (index) => `
      vec2 translatedUV${index} = (vUv - stickerPosition${index}) / stickerScale${index};
      vec2 stickerCenter${index} = vec2(0.5, 0.5);
      vec2 centeredUV${index} = translatedUV${index} - stickerCenter${index};
      float cosTheta${index} = cos(stickerRotation${index});
      float sinTheta${index} = sin(stickerRotation${index});
      mat2 rotationMatrix${index} = mat2(cosTheta${index}, -sinTheta${index}, sinTheta${index}, cosTheta${index});
      vec2 rotatedUV${index} = rotationMatrix${index} * centeredUV${index} + stickerCenter${index};
      vec4 stickerColor${index} = texture2D(stickerTexture${index}, rotatedUV${index});
    
      if (rotatedUV${index}.x >= 0.0 && rotatedUV${index}.x <= 1.0 && rotatedUV${index}.y >= 0.0 && rotatedUV${index}.y <= 1.0) {
        baseColor = mix(baseColor, stickerColor${index}.rgb * lightIntensity, stickerColor${index}.a);
      }
    `;
    

    uniform template:

    const createStickerFragmentUniforms = (index) => `
      uniform sampler2D stickerTexture${index};
      uniform float stickerScale${index};
      uniform vec2 stickerPosition${index};
      uniform float stickerRotation${index};
    `;
    

    adding stickers without suspending the dom with startTransition:

    const addSticker = () => {
        startTransition(() => {
          setStickers((currentStickers) => [
            ...currentStickers,
            { texture: "/t1.png", position: [0.5, 0.5], scale: 1.0, rotation: 0.0 },
          ]);
        });
      };
    

    How to generate dynamic declaration for stickers

    const stickerFragmentsUniforms = stickers.map((_, index) =>
        createStickerFragmentUniforms(index)
      );
      const stickerFragments = stickers.map((_, index) =>
        createStickerFragment(index)
      );
    

    How to turn every sticker in shader:

    <Suspense fallback={<p>wait please</p>}>
            <mesh geometry={nodes.cloth_parent.geometry}>
              <shaderMaterial
                uniforms={{
                  leftSleeveColor: { value: new Vector3(...leftSleeveColor) },
                  blendFactor: { value: blendFactor },
                  lightPosition: { value: new Vector3(2.0, 2.0, 2.0) }, // Lighting position
                  diffuseMap: { value: diffuseMapTexture },
                  normalMap: { value: normalMapTexture },
                  ...stickers.reduce(
                    (acc, sticker, i) => ({
                      ...acc,
                      [`stickerTexture${i}`]: { value: stickerTextures[i] },
                      [`stickerPosition${i}`]: {
                        value: new Vector2(...sticker.position),
                      },
                      [`stickerScale${i}`]: { value: sticker.scale },
                      [`stickerRotation${i}`]: { value: sticker.rotation },
                    }),
                    {}
                  ),
                }}
                vertexShader={vertexShader}
                fragmentShader={fragmentShader}
              />
            </mesh>
          </Suspense>
    

    How to implement uniforms and fragments in to fragmentshader:

    const fragmentShader = `
        uniform vec3 leftSleeveColor;
        uniform float blendFactor;
        uniform vec3 lightPosition;
        uniform sampler2D diffuseMap;
        uniform sampler2D normalMap;
        varying vec2 vUv;
        varying vec3 vNormal;
        varying vec3 vPosition;
    
        ${stickerFragmentsUniforms.join(
          "\n"
        )} // Insert dynamically generated uniforms
    
        void main() {
          // Sample the normal map and adjust normals
          vec3 normalFromMap = texture2D(normalMap, vUv).rgb;
          normalFromMap = normalize(normalFromMap * 2.0 - 1.0);
          vec3 adjustedNormal = normalize(vNormal + normalFromMap);
    
          // Lighting calculation
          vec3 lightDir = normalize(lightPosition - vPosition);
          float lightIntensity = max(dot(adjustedNormal, lightDir), 0.0);
    
          // Sample base diffuse map texture
          vec3 baseColor = texture2D(diffuseMap, vUv).rgb * lightIntensity;
    
          // Apply the red color to the left sleeve
          if (vUv.x > 0.1 && vUv.x < 0.3 && vUv.y > 0.6 && vUv.y < 0.8) {
            vec3 sleeveColorWithLight = leftSleeveColor * lightIntensity;
            baseColor = mix(baseColor, sleeveColorWithLight, blendFactor);
          }
    
          ${stickerFragments.join(
            "\n"
          )} // Insert dynamically generated sticker logic
    
          gl_FragColor = vec4(baseColor, 1.0);
        }
      `;