ThreeJS學習7_裁剪平面(clipping)
1. 裁剪平面簡介
裁剪平面指的是存在一個平面, 能夠對場景中的物質進行截斷, 這個平面就是裁剪平面, 裁剪平面分為全局的裁剪和局部裁剪
- 全局裁剪指的有一個平面裁剪了整個場景的物體, 這需要在renderer中設置
- 局部裁剪指的有一個平面裁剪裁剪指定物體, 這需要在指定物體的material中設置
- 里面涉及到被裁剪的物體的是否需要渲染陰影
- 里面還涉及到被多個平面裁剪時, 保留並集還是交集, 下面一一講解
2. 全局裁剪和局部裁剪
-
全局裁剪只需要設置一樣
renderer.clippingPlanes = planes
renderer 是 WebGLRenderer實例,
clippingPlanes 是用戶自定義的剪裁平面,在世界空間中被指定為THREE.Plane對象。 這些平面全局使用。空間中與該平面點積為負的點將被切掉。 默認值是[]
planes類型為[], 元素是任意的平面, 啥子叫做點積為負, 也就是點到平面的向量和平面法向量夾角大於90度, 簡單來說, 平面法向量的反方向都被截斷, , 該平面的法向量為(-1, 0, 0), 法向量的方向為x軸的負方向, 距離原點的距離為0.2, 因此, 在x>0.2的區域全部被截斷不顯示
結合案例來看,
- 局部裁剪, 除了設置 renderer , 還要設置指定物質的material, 當平面裁剪時, 就只有物質被裁剪, 看案例
此時的設置是
// 這個也是全局的, localClippingEnable = false也有效
// 全局截斷平面
renderer.clippingPlanes = Empty;
// 這個是全局的, 不開的話material中的clipping無效
// 這個設置為true后才能局部截斷
renderer.localClippingEnabled = true;
// 設置clipping
// 在material中設置clippingPlanes: 局部截斷平面, clipShadows為截斷陰影是否展示
clippingPlanes: [localPlane], clipShadows: true
- 截斷陰影展示
通過比對可以看出設置截斷陰影( clipShadows )為true時, 就像截斷后的物質不存在, 已經沒有陰影了, 設置為clipShadows為false時, 截斷后的物質仍然能產生陰影
3. 被多個裁剪平面裁剪后
設置material中的clipIntersection = true, 會只裁剪更改剪裁平面的行為,以便僅剪切其交叉點,而不是它們的並集。
clipIntersection默認為false, 裁剪平面的並集
簡單來說, 有多個裁剪平面, 每個裁剪平面裁剪的區域分布為c1, c2, ... ,cn, 默認設置, 裁剪 c1區域+c2區域+...+cn區域, 當clipIntersection=true時, 裁剪的只有這些區域共同的部分, c1 ∩ c2 ∩ ... cn
// 更改剪裁平面的行為,以便僅剪切其交叉點,而不是它們的並集。默認值為 false。
// 設置為true后剪切交集而不是並集
clipIntersection: true
三個截斷平面法向量分布為(1, 0, 0), (0, -1, 0), (0, 0, -1), c1 為x負半軸區域, c2為 y正半軸, c3為z正半軸, 可以看到, 當clipIntersection為false時, c1, c2, c3區域都被截斷了, 當為true時, 截斷了他們共同的部分
效果如下
- clipIntersection = true;
- clipIntersection = false;
4. 被多個裁剪平面截斷后代碼
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container">
</div>
<script type="module">
import * as THREE from '../build/three.module.js';
import {OrbitControls} from "./jsm/controls/OrbitControls.js";
import {GUI} from "./jsm/libs/dat.gui.module.js";
let container, camera, scene, renderer, mesh;
let params = {
clipIntersection: true,
planeConstant: 0,
showHelpers: false
};
let clipPlanes = [
new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
new THREE.Plane(new THREE.Vector3(0, 0, -1), 0)
];
init();
animation();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x8FBCD4);
scene.add(new THREE.AmbientLight(0x8FBCD4, 0.4));
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
camera.position.set(-1.5, 2.5, 3.0);
scene.add(camera);
let pointLight = new THREE.PointLight(0xffffff, 1);
// 燈跟着相機走, 效果不錯
camera.add(pointLight);
scene.add(new THREE.AxesHelper(5));
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.localClippingEnabled = true;
container.appendChild(renderer.domElement);
let group = new THREE.Group();
for (let i = 0; i <= 30; i += 2) {
let geometry = new THREE.SphereBufferGeometry(i / 30, 48, 24);
let material = new THREE.MeshLambertMaterial({
color: new THREE.Color().setHSL(Math.random(), 0.5, 0.5),
side: THREE.DoubleSide,
clippingPlanes: clipPlanes,
// 更改剪裁平面的行為,以便僅剪切其交叉點,而不是它們的並集。默認值為 false。
// 設置為true后剪切交集而不是並集
clipIntersection: params.clipIntersection
});
group.add(new THREE.Mesh(geometry, material));
}
scene.add(group);
let helpers = new THREE.Group();
helpers.add(new THREE.PlaneHelper(clipPlanes[0], 2, 0xff0000));
helpers.add(new THREE.PlaneHelper(clipPlanes[1], 2, 0x00ff00));
helpers.add(new THREE.PlaneHelper(clipPlanes[2], 2, 0x0000ff));
helpers.visible = false;
scene.add(helpers);
let gui = new GUI();
gui.add(params, 'clipIntersection').name('clip intersection').onChange(value=>{
/*for(let item in group.children){
item.material.clipIntersection = value;
}*/
let children = group.children;
for (let i = 0; i < children.length; i++) {
children[i].material.clipIntersection = value;
}
});
gui.add(params, 'planeConstant', -1, 1).step(0.01).name('plane constant').onChange(value=>{
for (let i = 0; i < clipPlanes.length; i++) {
clipPlanes[i].constant = value;
}
});
gui.add(params, 'showHelpers').name('show helpers').onChange(value=>{
helpers.visible = value;
});
let controls = new OrbitControls(camera, renderer.domElement);
controls.enabledZoom = false;
window.addEventListener('resize', onWindowResize, false);
}
function animation(){
render();
requestAnimationFrame(animation);
}
function render() {
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>