Mar*_*lin 6 html javascript css 3d matrix
语境:
\n在我的网络应用程序中,我创建了几组元素,所有元素都在 3d 空间中相对定位。所有元素都有transform-style: preserve-3d. 父元素用于旋转、缩放和移动所有这些元素以响应用户导航。
其中一些元素是“门户”,即矩形窗口,通过它可以看到其他几个元素。需要注意的是,这些“门户”内的元素必须与其父门户之外的元素存在于同一全局 3d 空间(而不仅仅是 2d)中。
\n这些“门户”是overflow: hidden为了隐藏其中溢出的元素。
根据 w3 规范,overflow是几个 css 属性值之一,它将对象组合在一起并创建 2d \xe2\x80\x9cflattened\xe2\x80\x9d 渲染上下文;实际上与 \xe2\x80\x9c 相同的结果transform-style: flat;\xe2\x80\x9d
问题:
\n我必须找到某种方法来反转(取消)由创建的变换矩阵,transform-style: flat;并创建与使用保留的相同的3D 渲染上下文transform-style: preserve-3d;
所有 3D css 变换都由内部的 3d 矩阵表示。transform-style: flat在用户代理的情况下,正在对其自己的变换矩阵进行一些神秘的“扁平化”数学运算。然后这个矩阵被应用到它的子项上,创造出它的子项都被展平在父项窗格中的错觉。通过一些矩阵数学应该可以绕过这种影响。
不幸的是,w3 规范没有详细说明这种“扁平化”在数学上的含义;他们在这个主题上相当模糊,简单地称之为“他们(元素)内容的扁平化表示”,“没有深度”。
\n我无法弄清楚需要进行哪些矩阵数学来重现“展平”效果。如果可以对算法进行逆向工程,则可以通过倒置矩阵轻松消除这种“扁平化”行为。每个门户元素内的辅助元素可用于应用此倒置矩阵作为变换,从而否定“展平”变换并正确恢复 3d 位置/旋转/视角。
\n下面显示的演示演示了完全有可能在扁平化的父级中创建深度印象。在这种情况下,我天真地应用了最顶层父级变换的逆。不幸的是,反转“展平”矩阵并不那么简单:
\n\n演示和链接:
\n\n笔记:
\n编辑1:为什么不使用三个js?
\n我的整个应用程序/网站将构建在 3D 空间中存在的导航模型上,那么为什么不只使用 webgl 呢?
\n从技术上讲,有多种方法可以实现混合 html/webgl 网站,但它需要 css 转换和(对于我的用例)动态计算剪切,这在chrome和firefox上不起作用。进行剪辑的唯一其他方法是使用overflow: hidden,这导致我提出这个问题。
就我的测试/研究而言,webgl 对于我正在构建的东西来说并不是一个可行的替代方案(至少对于一个人的团队来说),但是,计算出一些数学知识应该是可行的。
\n编辑2:部分解决方案
\n感谢@Markus'的回答,我发现可以通过删除与 z 轴变换相关的列中的所有值来实现展平效果。这可以通过将其变换乘以以下矩阵来完成:
\n取单位矩阵,将第 3 列中的第 3 项更改为任意小数。
\nconst flatten = identity()\nflatten[10] = 1/10000 // arbitrarily small. not zero, since that will invalidate the matrix.\nRun Code Online (Sandbox Code Playgroud)\n这是一个演示: https: //jsfiddle.net/aywbe9p7/2/
\n\n这似乎表明,浏览器在内部通过删除第三列中的值将 3d 4x4 矩阵转换为 2d 3x3 矩阵。
\n因此,有理由认为反转这种效果就像重新填充第三列一样简单:
\n// normalize portal transform\nconst normalizedPortalTransform = normalize(portalTransform)\n\n// try to re-establish perspective in z axis\nconst final = identity()\nfinal[8] = normalizedPortalTransform[8]\nfinal[9] = normalizedPortalTransform[9]\nfinal[10] = normalizedPortalTransform[10]\nfinal[11] = normalizedPortalTransform[11]\nRun Code Online (Sandbox Code Playgroud)\n这似乎是一种工作,但观点仍然不正确:
\n这是一个演示: https: //jsfiddle.net/aywbe9p7/3/
\n\n我尝试了许多不同的重新填充矩阵的组合,例如还包括第三行(索引 2、6、10 和 14),甚至portalTransform使用下面的代码分解透视组件,并尝试重新将这些值合并到去final平坦矩阵中。但这也行不通。
// Returns the transpose of a 4x4 matrix\nfunction transpose(matrix){\n return Array.from({ length: 16 }, (_, i) => {\n const y = i % 4\n const x = Math.floor(i / 4)\n return matrix[y * 4 + x]\n })\n}\n\n// Decompose the perspective component of a 4x4 matrix.\n// https://www.w3.org/TR/css-transforms-2/#decomposing-a-3d-matrix\nfunction decompPerspective(matrix){\n // There exists some perspective\n if(matrix[15] != 0 && (matrix[3] != 0 || matrix[7] != 0 || matrix[11] != 0)){\n // Normalize the matrix.\n const m = normalize(matrix)\n \n // Used to solve for perspective\n const perspectiveMatrix = Array.from(m)\n perspectiveMatrix[3] = 0\n perspectiveMatrix[7] = 0\n perspectiveMatrix[11] = 0\n perspectiveMatrix[15] = 1\n \n // The right hand side of the equation.\n const r0 = m[3]\n const r1 = m[7]\n const r2 = m[11]\n const r3 = m[15] // should be 1\n \n // Solve the equation by inverting perspectiveMatrix and multiplying\n // rightHandSide by the inverse.\n const f = transpose(inverse(perspectiveMatrix))\n \n return [\n f[0] * r0 + f[4] * r1 + f[8] * r2 + f[12] * r3,\n f[1] * r0 + f[5] * r1 + f[9] * r2 + f[13] * r3,\n f[2] * r0 + f[6] * r1 + f[10] * r2 + f[14] * r3,\n f[3] * r0 + f[7] * r1 + f[11] * r2 + f[15] * r3, // should be 1\n ]\n }\n // No perspective\n else{\n return [0, 0, 0, 1]\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n编辑 2:为什么数学需要完美而不是近似
\n如果不制作动画,这对我来说可能有点难以解释,但想象一下您正在俯视一个包含门户的场景。在这个传送门内,又是另一番景象。
\n我想在查看顶级场景和查看嵌入在门户中的场景之间制作动画。
\n为了做到这一点,首先将视觉视角动画化为“飞向”门户元素。
\n接下来,当视口被入口完全填充时,包含入口的场景将被删除。它被嵌入的场景所取代,只是现在门户消失了,因此场景不再位于门户内部。
\n为了使这种过渡发挥作用,门户中项目的视角必须与门户外相同项目的视角完美匹配……否则这种过渡将不会顺利。
\n如果这没有意义,请告诉我。我也许可以做一个例子来更好地证明这一点。
\n如果您不想使用像 WebGL-lib 这样的东西,three.js而是纯粹的 css-transform,那么获得所需效果的直接方法是动态更改门户的透视图原点。
请注意,Firefox 在 3D-css 方面仍然存在一些问题。例如,我建议不要在对象上使用任何边距或填充。
正常的展平是由相机矩阵完成的,可以在透视参数的文档中找到(参见MDN)。它是视角值的函数。如果用户的视图与投影屏幕不正交(在您的情况下是门户平面),您可以perspective-origin相应地更改。
就像在您的示例中一样,我曾经Rematrix用来调整门户窗口的透视图原点。
<!DOCTYPE HTML>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://unpkg.com/rematrix"></script>
<style>
.rect {
width:400px;
height:200px;
border: 2px solid red;
}
.scene {
perspective-origin: center;
}
.main {
position: relative;
transform-style: preserve-3d;
display:inline-block;
vertical-align:top;
}
.inner {
position: absolute;
}
.portal{
overflow:hidden;
}
</style>
<body>
</head>
<div id="scene" width=400px>
<div id="scene1" class="scene rect">
<div id="obj1" class="main rect">
<div class="inner rect" style="transform:translateZ(-60px)"></div>
<div class="inner rect" style="transform:translateZ(-120px)"></div>
<div class="inner rect" style="transform:translateZ(-180px);background-color:#eee">preserve-3d</div>
</div>
</div>
<div id="scene2" class="scene rect">
<div id="obj2" class="main portal rect">
<div class="inner rect" style="transform:translateZ(-60px)"></div>
<div class="inner rect" style="transform:translateZ(-120px)"></div>
<div class="inner rect" style="transform:translateZ(-180px);background-color:#eee">overflow hidden</div>
</div>
</div>
</div>
<script>
let r = Rematrix;
let obj1 = $("#obj1");
let obj2 = $("#obj2");
let scene = $("#scene");
let scene1 = $("#scene1");
let scene2 = $("#scene2");
// locate objects behind center of scene
let tx = (scene1.width() - obj1.width()) / 2;
let ty = (scene1.height() - obj1.height()) / 2;
let tz = -1000;
// set perspective
let p = 2000;
scene1.css("perspective", "" + p + "px");
scene2.css("perspective", "" + p + "px");
// initial camera position
let camPos = [0, 0, p, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// start rendering
move(0.6, 0.6);
scene.mousemove(function(event){ move(event.clientX / scene.width(), event.clientY / scene.height()); });
function move(x, y) {
// move objects
let dx = 120 * (x - 0.5);
let dy = -120 * (y - 0.5);
let t = r.toString([r.translate3d(tx, ty, tz), r.rotateX(dy), r.rotateY(dx)].reduce(r.multiply));
obj1.css("transform", t);
obj2.css("transform", t);
// adjust camera perspective of portal object
let c = [r.rotateY(-dx), r.rotateX(-dy), r.translateZ(-tz), camPos].reduce(r.multiply);
let px = c[0] + obj2.width() / 2;
let py = c[1] + obj2.height() / 2;
let pz = c[2];
obj2.css("perspective", "" + pz + "px");
obj2.css("perspectiveOrigin", "" + px + "px " + py + "px");
}
</script>
</body>
Run Code Online (Sandbox Code Playgroud)