I'm implementing a simple 3D-paint and want to implement an erasing. In order to do it I decided to use two layers of canvas: one for texture image and another one (transparent) for drawing to erase only canvas for drawing. Can you please advice how to have one meshStandardMaterial with the image texture (which shouldn't be modified while painting/erasing) and one ore more transparent meshStandardMaterial just for painting Here is the component code:
<Canvas onCreated={({ gl }) => { gl.physicallyCorrectLights = true; }} camera={{ position: [0, 0, 10] }} > {isControlsAllowed && <Controls />} <mesh onPointerLeave={handleDrawStop} onPointerDown={() => { setIsControlsAllowed(false); }} onPointerUp={() => { setIsControlsAllowed(true); }} onPointerMove={(ev) => { handleBrushPointerMove(ev, previousPoint.current); }} geometry={geometry} > <meshStandardMaterial attach="material" metalness={0} roughness={1} > <canvasTexture ref={currentTextureRef} attach="map" image={currentCanvasRef.current} /> </meshStandardMaterial> <meshStandardMaterial attach="material" metalness={0} roughness={1}> <canvasTexture ref={currentDrawingTextureRef} attach="map" image={currentDrawingCanvasRef.current} /> </meshStandardMaterial> </mesh> <ambientLight intensity={1} /> <spotLight position={[0, 0, 10]} intensity={10} /> <spotLight position={[-10, 0, -5]} intensity={10} /> <spotLight position={[10, 0, -5]} intensity={10} /> </Canvas> My hook code:
const currentCanvasRef = useRef(document.createElement("canvas")); const currentTextureRef = useRef<CanvasTexture>(null); const currentDrawingCanvasRef = useRef(document.createElement("canvas")); const currentDrawingTextureRef = useRef<CanvasTexture>(null); useLayoutEffect(() => { const context = currentDrawingCanvasRef.current.getContext("2d"); context?.clearRect( 0, 0, currentDrawingCanvasRef.current.width, currentDrawingCanvasRef.current.height ); createCanvasTextureFromImage("/Stool.jpg"); }, []); const createCanvasTextureFromImage = ( imageUrl: string, ) => { const canvas = currentCanvasRef.current; new ImageLoader().load(imageUrl, (image) => { const ctx = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; if (ctx) { ctx.clearRect(0.0, 0.0, canvas.width, canvas.height); ctx.drawImage(image, 0.0, 0.0, image.width, image.height); } }); }; const handleBrushPointerMove = ( currentPoint: ThreeEvent<PointerEvent>, prevPoint?: ThreeEvent<PointerEvent> ) => { // Some drawing logic }; What I'm expecting to see:
And what I'm actually see:
UPDATE: I just checked that array of materials doesn't work for mesh. Although each of the materials works separately, when using an array, the 3D model is not displayed at all
1 Answer
To Answer Your Comment :
- Increase the Offset: Increase the distance between the two meshes to prevent overlap. Adjust the mesh position:
position={[0, 0, 0.005]} - Depth Write/Read: Modify depth settings of the materials:
depthWrite={false} depthTest={false} - Polygon Offset: Use Three.js's polygon offset to artificially push the mesh backward in z-depth:
polygonOffset={true} polygonOffsetFactor={1} polygonOffsetUnits={1} Use a Shader: Manipulate the depth in shaders to ensure correct rendering order. Requires knowledge of GLSL.
Rethink Layering: Consider painting directly onto the image canvas, eliminating the need for a second mesh.
Start with the simplest solution (increasing the offset) and progress to more complex solutions as needed.
