three.js+WebGL踩坑经验合集(5.1):THREE.Line2又一坑:镜像后不见了
什么情况,怎么讲的还是线?就没别的了?
不得不说,笔者自己都有点审美疲劳了。写到第5篇,有4篇都在讲线。
然而线的坑确实比较多,也比较容易暴露引擎的问题。就像第一篇,实际上换成mesh也一样可以出问题,只不过通常情况下mesh的包围球不大,并且修改geometry的频率也不高,问题才不容易被发现。
但本篇的问题就真的只出现在THREE.Line2上,THREE.Mesh乃至THREE.Line都没有任何异常。
下面我们就做个测试用例,建个容器,里面Mesh,Line(细线)和Line2(粗线)各放一个。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>three_line2Flip</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script src="three/build/three.js"></script>
<script src="three/examples/js/controls/OrbitControls.js"></script>
<script src="three/examples/js/lines/LineSegmentsGeometry.js"></script>
<script src="three/examples/js/lines/LineGeometry.js"></script>
<script src="three/examples/js/lines/LineMaterial.js"></script>
<script src="three/examples/js/lines/LineSegments2.js"></script>
<script src="three/examples/js/lines/Line2.js"></script>
<script src="three/examples/js/libs/dat.gui.min.js"></script>
</head>
<body>
<script>
var scene = new THREE.Scene();
var container = new THREE.Object3D();
scene.add(container);
var meshGeometry = new THREE.BoxGeometry(50, 50, 50);
var meshMaterial = new THREE.MeshLambertMaterial({color: 0xFF6600});
var mesh = new THREE.Mesh(meshGeometry, meshMaterial);
mesh.position.set(5, 5, 5);
mesh.rotation.set(0, Math.PI * 0.25, 0);
container.add(mesh);
var lineGeometry = new THREE.BufferGeometry();
lineGeometry.setFromPoints([new THREE.Vector3(-50, 0, 0), new THREE.Vector3(50, 0, 0)]);
var lineMaterial = new THREE.LineBasicMaterial({color: 0xFF6600});
var line = new THREE.Line(lineGeometry, lineMaterial);
line.position.set(-50, 10, 0);
line.rotation.set(0, 0, -Math.PI / 4);
container.add(line);
var line2Geometry = new THREE.LineGeometry();
line2Geometry.setPositions([-50, 0, 0, 50, 0, 0]);
var line2Material = new THREE.LineMaterial({color: 0xFF6600, resolution: new THREE.Vector2(window.innerWidth, window.innerHeight), linewidth: 5});
var line2 = new THREE.Line2(line2Geometry, line2Material);
line2.position.set(60, -15, 0);
line2.rotation.set(0, 0, Math.PI / 3);
container.add(line2);
var light = new THREE.DirectionalLight({color: 0xFFFFFF, intensity: 0.5});
light.position.set(-500, 500, 500);
scene.add(light);
var ambLight = new THREE.AmbientLight({color: 0xFFFFFF, intensity: 0.3});
scene.add(ambLight);
var width = window.innerWidth;
var height = window.innerHeight;
var camera = new THREE.PerspectiveCamera(60, width / height, 1, 20000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 1);
document.body.appendChild(renderer.domElement);
var flipX = false;
var flipY = false;
var gui = new dat.GUI();
folderCamera = gui.addFolder("镜像"),
propsContainer = {
get '水平镜像'() {
return flipX;
},
set '水平镜像'( v ) {
flipX = v;
container.scale.x = v ? -1 : 1;
},
get '垂直镜像'() {
return flipY;
},
set '垂直镜像'( v ) {
flipY = v;
container.scale.y = v ? -1 : 1;
},
};
gui.width = 150;
folderCamera.add( propsContainer, '水平镜像');
folderCamera.add( propsContainer, '垂直镜像');
folderCamera.open();
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
var controls = new THREE.OrbitControls(camera,renderer.domElement);
controls.addEventListener('change', render);
</script>
</body>
</html>
运行代码,在不点击UI面板上镜像操作的情况下,相机不管怎么动,这3个物体都能正常显示
但若随便勾选一个方向的镜像,粗线(Line2)就消失了。然后如果两个方向同时镜像,那粗线又可以显示回来,呈现出负负得正的效果。
这个问题是笔者一个同事碰到的,他觉得很神奇,为什么同样是线,就只有Line2会没掉,Line就安然无恙。当时笔者还没研读过引擎的这一部分,然后凭着自己半吊子的经验给对方一本正经地解释道:“因为Line是用原生的画线api,没有点绕序(顺逆时针)的问题,而Line2是用两个三角面模拟的,镜像一个方向,绕序变更,正反面颠倒,所以就看不到了。”
笔者还拿着笔在纸上给同事把Line2的原理图给画了出来(这也是前面的博文提到的)。
然而还没等到笔者画完,这位同事就很“机智”地给出了“解决方案”:既然是正面变成了背面,那我直接给线开双面是不是就解决了?
line2Material.side = THREE.DoubleSide;
就这么一行代码,完美了。
虽然这位小伙伴在提交代码之前因为双面的性能损耗犹豫了一下,但苦于没有更好的解决方案,就只能先这样凑合着了。
如果仅考虑本用例,那笔者的这位同事其实是有优化方案的,就是写一个方法判断负缩放的总次数。
function updateFace(){
line2Material.side = container.scale.x * container.scale.y * container.scale.z > 0 ? THREE.FrontSide : THREE.BackSide;
}
然后在勾选镜像的地方调用一下,也能得到正确的结果。
但此法局限性太大,因为在我们的项目里,Line2自身也可能存在负缩放,这时候,Line2将始终不可见。
如果有更多层的容器嵌套,那么每一层容器的负缩放都要考虑进去,不但读取起来麻烦,而且最重要的是,我们不知道什么时候Line2触发了缩放正负性的变更,什么时候需要调用updateFace。
若先不考虑时机问题,那有个很蹩脚的方法是,把line2的scale也乘进去:
function updateFace(){
line2Material.side = container.scale.x * container.scale.y * container.scale.z * line2.scale.x * line2.scale.y * line2.scale.z > 0 ? THREE.FrontSide : THREE.BackSide;
}
事已至此,我那位同事还是老老实实地开双面绕过去了先。
但既然Line和Mesh都没有问题,那就说明引擎是有做处理的,最起码Mesh跟Line2是同一类型的物体,镜像时正反面也一样会翻转。
所以这里我们有几个问题:
1 为什么Mesh镜像后没问题(Line走原生画线api,不用再讨论)
2 有没有一个办法,获取到一个Line2对象的全局缩放(scaleWorld)
3 Line2对象被多层容器嵌套时,如何收到缩放更新的通知
这篇好像写长了,分到下一篇去,本篇暂不总结,大家也先喝杯茶休息一下,我们稍后见!