During the slicing process of the model, I used a stencil to patch the removed parts, but the generated patches were rendered before the model. How can I fix it?

The source code I referenced is available at https://raw.gitcode.com/GitHub_Trending/th/three.js/raw/r184/examples/webgl_clipping_stencil.html, and my public repository is located at GitHub - binbin0916/vue3d · GitHub .

The specific slicing code is stored in src/tools/section.ts, while the initialization code for scenes and models is located in `src/three/`. It is called in src/composables/useThreeScene. Below is a screenshot of the bug phenomenon:

I read src/tools/section.ts in your repo against the r184 example you linked. Your stencil setup is a faithful copy — createPlaneStencilGroup matches the original line for line, including IncrementWrap/DecrementWrap on back/front faces and NotEqualStencilFunc with ReplaceStencilOp on the fill. That part is not the problem.

The problem is that the model never gets a renderOrder, so it draws first.

Every renderOrder assignment in your file:

line2.renderOrder    = 1        // 轮廓线框
item.renderOrder     = 2        // 6个辅助面
mesh0/mesh1          = i + 1    // stencil group
po.renderOrder       = i + 1.1  // fill plane

The sliced mesh itself is not in that list, which leaves it at the default 0. Your fill planes are 1.1, 2.1, 3.1 … — every one of them greater than zero, so every patch is drawn after the model. Whatever is drawn later and passes its tests wins the pixel, and that is exactly what your screenshots show.

The example does the opposite, and this is the line that makes it work:

const clippedColorFront = new THREE.Mesh( geometry, material );
clippedColorFront.renderOrder = 6;      // webgl_clipping_stencil.html:200

Six, with fill planes at 1.1 … 3.1. The model is deliberately last, after every patch. The order is: stencil groups mark the cut, the fill plane paints the hole, then the model paints over everything and its depth buffer decides what remains visible.

So set it above your highest patch. You build one patch per plane, so:

model.renderOrder = planes.value.length + 2;

One more thing, and it is why the patch wins even where depth should have stopped it:

polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -1,

The example has no polygonOffset on the fill material at all. Negative values pull the polygon toward the camera in depth, so the patch is deliberately biased in front of the geometry it is supposed to sit flush with. Combined with a renderOrder that already puts it last, it will cover the model even where the stencil is doing its job. Drop it — the fill sits exactly on the clipping plane and does not need a bias. If you do hit z-fighting afterwards, positive values push away, negative pull toward.

A detail worth checking while you are in there: sliceObject is added to ctx.modelGroup, so the stencil meshes inherit the model group’s transform, while poGroup goes straight onto ctx.scene. In the example both live under the same object. As long as modelGroup has an identity transform this is invisible; the moment you rotate or move the model, the patches stay behind in world space.

The reason this is not obvious from the example is that the example never states the rule it depends on — it just happens to assign 6. The rule is: stencil markers → fill → model, and the numbers only have to preserve that order.

Thank you very much for your response and for pointing out the issue.

Following your answer, I modified the code in src/tools/section.ts with the following logic:

ctx.addFrameTask(PLANE_SYNC_TASK_ID, () => {
		syncPlanesFromVisualPlanes();

		if (!stencilReady && planes.value[0] && planes.value[0].normal.lengthSq() > 0) {
			stencilReady = true;
			initStencilFills(ctx);
			ctx.meshes.forEach((mesh) => {
				mesh.renderOrder = 10;
			});
			ctx.modelGroup.renderOrder = 10;
		}

		if (stencilReady) {
			updateStencilPolygonPositions();
		}
	});
};

Here, ctx.meshes stores all the meshes from the model, and ctx.modelGroup is the parent Group of the model. I also removed the polygonOffset related code. However, the issue in the screenshot still persists.

I downloaded three.js-r184 and ran webgl_clipping_stencil.html. After that, I changed the value of clippedColorFront.renderOrder to an arbitrary value or simply commented out that line of code, and found that there was no change in the browser’s output.