WebGL坐标系

确认过了WebGL 你给个三角形A深度是-0.5三角形B深度是0,开启深度检测后,画出来发现深度小的三角形(-0.5)画在了前面,注意深度检测后就是深度大的在后面,由此得出WebGL/OpenGL 裁剪空间和NDC默认就是左手坐标系

然而为什么说一些引擎如threejs是右手坐标系呢,是因为在投影矩阵里给第三行乘了个-1,就是左乘一个Z方向反转矩阵(注意三行三列为-1,其他和单位矩阵一样),会导致z轴翻转,转换成了所谓右手坐标系,可以看下正射投影和透视投影算出的第三行和正常的投影矩阵比,整个过程是在投影变换过程中翻转Z轴的

AutoAccessibilityService 模拟坐标点击_unity

THREE世界坐标系(右手)

RGB分别代表XYZ,相机位置是 0 0 200,目标物体位置是0 0 0

AutoAccessibilityService 模拟坐标点击_unity_02

THREE相机坐标系(右手)

证明:

我们先假设相机坐标系是右手坐标系的,世界坐标系中相机位置是(0,0,200), 物体A绿色在(0,0,0), 物体B红色在(0,0,100),所以相机坐标系中,物体A绿色坐标为(0,0,-200),物体B红色坐标为(0,0,-100),我们知道THREE裁剪空间会对Z轴进行反转,所以经过裁剪空间处理后,物体A绿色坐标为(0,0,200),物体B红色坐标为(0,0,100),按照深度大的显示在后面的原理,所以应该是红色物体显示在绿色物体前面,由下图看假设是正确的

AutoAccessibilityService 模拟坐标点击_游戏引擎_03

let camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 200);
camera.lookAt(new THREE.Vector3(0, 0,0));
			
const boxA = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshPhongMaterial({
	color: 'rgb(0,255,0)',
}));
boxA.position.x = 0;
boxA.position.y = 0;
boxA.position.z = 0;
scene.add(boxA);

const boxB = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshPhongMaterial({
	color: 'rgb(255,0,0)',

}));
boxB.position.x = 0;
boxB.position.y = 0;
boxB.position.z = 100;
scene.add(boxB);

THREE裁剪空间

以three的正射投影为例,near=-1,far=1,算出的投影矩阵如下图所示,这都和unity是统一的,最终裁剪空间会对z轴进行反转

AutoAccessibilityService 模拟坐标点击_投影变换_04

Unity世界坐标系(左手)

RGB分别代表XYZ,相机位置是 0 0 0,目标物体位置是0 0 10

AutoAccessibilityService 模拟坐标点击_投影变换_05

Unity相机坐标系(右手)

unity相机空间是右手坐标系,相机朝向为z轴的负方向,下面证明一下。

我们先假设相机坐标系是右手坐标系的,世界坐标系中相机位置是(0,0,-20), 物体A绿色在(0,0,0), 物体B红色在(0,0,-10),所以相机坐标系中,物体A绿色坐标为(0,0,-20),物体B红色坐标为(0,0,-10),我们知道Unity裁剪空间会对Z轴进行反转,所以经过裁剪空间处理后,物体A绿色坐标为(0,0,20),物体B红色坐标为(0,0,10),按照深度大的显示在后面的原理,所以应该是红色物体显示在绿色物体前面,由下图看假设是正确的

AutoAccessibilityService 模拟坐标点击_投影变换_06

Unity裁剪空间

以unity的正射投影为例,near=-1,far=1,算出的投影矩阵如下图所示

AutoAccessibilityService 模拟坐标点击_游戏引擎_07


AutoAccessibilityService 模拟坐标点击_世界坐标系_08


所以unity的正射投影矩阵为

AutoAccessibilityService 模拟坐标点击_投影变换_09


这里三行三列为-1,,最终裁剪空间会对z轴进行反转

这就是因为在Unity里当World Space变到Camera Space时,需要做一个反转z轴的操作(因此Unity的视图变换矩阵,相比文章前面的推理还需再乘以一个z轴反转的矩阵)。然后我们做投影变换到Clip Space时,又要给它转回来(再做一次z反转),使得near clip plane在-1,far clip plane在1。

Unity中视图矩阵需要做个特殊处理

AutoAccessibilityService 模拟坐标点击_投影矩阵_10

Unity和Three 坐标系异同(分析暂时有问题先不看)

three和unity的世界坐标系区别是three右手,unity左手,unity中使用从three计算得到的坐标应该世界矩阵再前乘 negatezMaT,对z轴反转

// 这里后面乘negatezMaT意思给模型矩阵前乘negatezMaT,将世界坐标系转到左手
Matrix4x4 viewMatrix = float16ToMatrix4x4(three.viewMatrix) * negatezMaT;

因为坐标系不同遇到的问题(基于opengl)

threejs
相机位置 (0,0,10),默认看向负半轴方向,小车位置是(0,0,0),具体代码如下,渲染结果如下。

<!DOCTYPE html>
<html lang="en">

<head>
	<title>three.js webgl - glTF loader</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	<link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
	<div id="info">
		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader<br />
		Battle Damaged Sci-fi Helmet by
		<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
		<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <a
			href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
	</div>

	<div id = "test" style="width: 1180px; height: 695px;">
		
	</div>

	<script type="module">

		import * as THREE from '../build/three.module.js';

		import { OrbitControls } from './jsm/controls/OrbitControls.js';
		import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';

		let camera, scene, renderer;

		init();
		render();

		function init() {

			// const container = document.createElement('div');
			// document.body.appendChild(container);
			var canvasContainer = document.getElementById("test")
	        document.body.appendChild(canvasContainer);

			camera = new THREE.PerspectiveCamera(60, canvasContainer.clientWidth / canvasContainer.clientHeight, 0.3, 1000);
			camera.position.set(0, 0, 10);
			console.log(camera)
			// camera.lookAt(new THREE.Vector3(0, 0, 0));
			scene = new THREE.Scene();
			scene.add(new THREE.DirectionalLight())
			// scene.add(new THREE.AmbientLight())

			window.camera = camera;


			const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
			loader.load('DamagedHelmet.glb', function (gltf) {
				scene.add(gltf.scene);
				console.log(gltf.scene.position)


				render();

			});


			let axes = new THREE.AxisHelper(10);
			scene.add(axes);


			// 
			var cameraPerspective = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.3, 20);
			window.cameraPerspective = cameraPerspective;
			cameraPerspective.position.set(0, 0, 10);
			var cameraPerspectiveHelper = new THREE.CameraHelper( cameraPerspective );
			window.cameraPerspectiveHelper = cameraPerspectiveHelper;
			scene.add( cameraPerspectiveHelper );
			scene.add( cameraPerspective );
			




			renderer = new THREE.WebGLRenderer({ antialias: true });
			renderer.setPixelRatio(window.devicePixelRatio);
			// renderer.setSize(window.innerWidth, window.innerHeight);
			renderer.setSize(canvasContainer.clientWidth, canvasContainer.clientHeight);

			canvasContainer.appendChild(renderer.domElement);


			const controls = new OrbitControls(camera, renderer.domElement);
			controls.addEventListener('change', render); // use if there is no animation loop
			controls.minDistance = 2;
			controls.maxDistance = 100;
			controls.target.set(0, 0, 0);
			controls.update();
		}

		function render() {
			window.cameraPerspectiveHelper.update()
            window.cameraPerspective.updateMatrix()
			renderer.render(scene, camera);

		}
	</script>

</body>

</html>

小车位置是(0,0,0)

AutoAccessibilityService 模拟坐标点击_投影变换_11


three相机位置是(0,0,10)

AutoAccessibilityService 模拟坐标点击_投影矩阵_12

unity中

相机位置(0,0,-10),小车位置(0,0,0),最终渲染结果如下

AutoAccessibilityService 模拟坐标点击_unity_13


小车和相机在世界坐标系下位置如下图所示

AutoAccessibilityService 模拟坐标点击_投影变换_14

three和unity相机的模型矩阵分别是

AutoAccessibilityService 模拟坐标点击_投影矩阵_15

AutoAccessibilityService 模拟坐标点击_unity_16

three和unity中视图矩阵分别是

AutoAccessibilityService 模拟坐标点击_游戏引擎_17


AutoAccessibilityService 模拟坐标点击_世界坐标系_18

three和unity中投影矩阵分别是

AutoAccessibilityService 模拟坐标点击_unity_19


AutoAccessibilityService 模拟坐标点击_世界坐标系_20

看出来两个引擎中
视图矩阵是第三行三列正好相反
投影矩阵是相同的

目前发现了两个问题:

1.问题1 就算在unity中修改视图矩阵和three中一样,最终显示结果是下面这样

AutoAccessibilityService 模拟坐标点击_游戏引擎_21


但是,这个渲染结果和threejs渲染结果还是不一样看着似乎一样,但是它是X轴取反了,而且是我强制改了双面渲染,不然它的模型三角面顶点顺序都变了,这个就涉及到了世界坐标系左手和右手的转换问题,这块令人困惑。

2.问题2

这个汽车模型在three的世界坐标系(右手)中和在unity的世界坐标系(左手)中摆放好像是X轴取反了! 这是为啥

AutoAccessibilityService 模拟坐标点击_unity_22


AutoAccessibilityService 模拟坐标点击_投影变换_23


可以看那个字母m的朝向,是不是很奇怪。