你好,我正在尝试使用 React、三纤维 js 和 glsl 做 3D 模型配置示例
必须有多个贴纸,并且贴纸必须可以动态排列(位置、缩放、旋转),以便 我决定使用着色器
当所有贴纸都提前设置好就没有问题了
但问题是贴纸会被客户添加,我找不到出路
目前的问题是我无法在 glsl 中使用动态索引 for for 循环
有人知道我怎样才能做到这一点吗 非常感谢提前
这就是我在画布上的内容
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>
);
这就是我的片段
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
}
`;
解决方案 我为贴纸创建了片段模板,并将它们动态添加到片段中,并且我还在 startTransition 中添加了添加贴纸和更新贴纸功能
return (
<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>
);
}
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);
}
`;
解决方案 我为贴纸创建了片段和统一模板,并将它们动态添加到片段中,并且我还在 startTransition 中添加了添加贴纸和更新贴纸功能
片段模板:
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);
}
`;
统一模板:
const createStickerFragmentUniforms = (index) => `
uniform sampler2D stickerTexture${index};
uniform float stickerScale${index};
uniform vec2 stickerPosition${index};
uniform float stickerRotation${index};
`;
在不使用 startTransition 暂停 dom 的情况下添加贴纸:
const addSticker = () => {
startTransition(() => {
setStickers((currentStickers) => [
...currentStickers,
{ texture: "/t1.png", position: [0.5, 0.5], scale: 1.0, rotation: 0.0 },
]);
});
};
如何生成贴纸动态声明
const stickerFragmentsUniforms = stickers.map((_, index) =>
createStickerFragmentUniforms(index)
);
const stickerFragments = stickers.map((_, index) =>
createStickerFragment(index)
);
如何转动着色器中的每个贴纸:
<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>
如何将制服和片段嵌入到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);
}
`;