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
}
`;
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);
}
`;