M = identity();
M = M * T; // Where T = Translation
M = M * R; // Where R = Rotation
M = T * R;
transformedVertex = M * vertex
回想一下 M = T * R
,这与以下表达式相同
transformedVertex = T * R * vertex
transformedVertex = T * (R * vertex)
或者,为了更加明显:
rotatedVertex = R * vertex
transformedVertex = T * rotatedVertex
首先旋转顶点。(然后,将旋转后的顶点平移)
newMatrix = oldMatrix * additionalTransformation
(就像您在代码中所做的那样)。另一种选择是编写
newMatrix = additionalTransformation * oldMatrix
这有时被称为“预乘法”或“左乘法”。因此,您也可以写成
M = identity();
M = T * M; // Where T = Translation
M = R * M; // Where R = Rotation
最终,
M = R * T
但在OpenGL的上下文中,这是相当不寻常的。 (同时混合两种方式会非常令人困惑 - 我不建议这样做)。
glPushMatrix
和 glPopMatrix
仍然是 OpenGL API 的一部分时,所有这些可能会更有意义。这种思考方式类似于遍历场景图。您首先应用“全局”变换,然后再应用“本地”变换。
针对评论,我将尝试写几句话来解释一些概念。在这里总结有点困难,我会尽量简化,并省略一些细节,因为这些细节可能超出了单个答案的范围。这里提到的一些事情是关于OpenGL早期版本中如何完成的,现在已经有了不同的解决方法 - 尽管许多概念仍然相同!
用场景图的形式表示3D场景并不罕见。这是场景的分层结构表示,通常以树的形式呈现:
root
/ \
nodeA nodeB
/ \ \
nodeA0 nodeA1 nodeB0
object object object
节点包含变换矩阵(如旋转或平移)。3D对象附加到这些节点上。在渲染期间,遍历此图形:访问每个节点,并将其对象渲染。这是递归完成的,从根节点开始,访问所有子节点,直至叶子节点。例如,渲染器可以按以下顺序访问上述节点:
root
nodeA
nodeA0
nodeA1
nodeB
nodeB0
glPushMatrix
用于将当前“顶部”矩阵的一份副本推入堆栈,glPopMatrix
用于从堆栈中移除最顶端的矩阵。或者使用 glMultMatrix
将堆栈中当前“顶部”矩阵与另一个矩阵相乘。mat4
uniform …)void render(Node node) {
glPushMatrix();
glMultMatrix(node.matrix);
renderObject(node.object);
foreach (child in node.children) {
render(child);
}
glPopMatrix();
}
glPushMatrix
/glPopMatrix
对中,渲染器可以始终维护正在访问的节点的正确当前矩阵。现在,渲染器访问这些节点,并维护矩阵堆栈:Node: Matrix Stack:
-----------------------------
root identity
nodeA identity * nodeA.matrix
nodeA0 identity * nodeA.matrix * nodeA0.matrix
nodeA1 identity * nodeA.matrix * nodeA1.matrix
nodeB identity * nodeB.matrix
nodeB0 identity * nodeB.matrix * nodeB0.matrix
可以看到,在节点中用于渲染对象的矩阵是由从根节点到相应节点路径上所有矩阵的乘积给出的。
当考虑一个“大型”场景图时,这些概念的可能性能优势和优雅性可能会更加明显:
root
nodeA
nodeB
nodeC
nodeD0
nodeD1
nodeD2
...
nodeD1000
我们可以计算乘积
nodeA.matrix * nodeB.matrix * nodeC.matrix
一次,然后用这个矩阵一直乘以nodeD0
...nodeD1000
的矩阵。相反地,如果想要改变乘法顺序,就必须计算
nodeD0.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
nodeD1.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
...
nodeD1000.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
我不太确定这个glMatrix是反向的。
例如观看这些视频,似乎标准做法是
m1 * m2 * m3 * vector
而根据视频中显示的顺序,这将对应于
gl_Position = projection * view * world * position;
它完全匹配GL和GLSL。
它也与glMatrix相匹配。
var m = mat4.create();
mat4.projection(m, fov, aspect, zNear, zFar);
mat4.multiply(m, m, view);
mat4.translate(m, m, [x, y, z]);
mat4.rotateY(m, m, someAngle);
mat4.scale(m, m, [sx, sy, sz]);
准确对应于
m = projection *
view *
translation *
rotation *
scale;
对我来说似乎是向前的。
var vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texCoord;
void main() {
v_texCoord = texcoord;
gl_Position = u_worldViewProjection * position;
}
`;
var fs = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_diffuse;
void main() {
gl_FragColor = texture2D(u_diffuse, v_texCoord);
}
`;
"use strict";
var gl = document.querySelector("canvas").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var arrays = {
position: [1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
normal: [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],
texcoord: [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1],
indices: [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23],
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 0, 0, 255,
192, 192, 192, 255,
0, 0, 192, 255,
255, 0, 255, 255,
],
});
var uniforms = {
u_lightWorldPos: [1, 8, -10],
u_lightColor: [1, 0.8, 0.8, 1],
u_ambient: [0, 0, 0, 1],
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var view = mat4.create();
var camera = mat4.create();
// glMatrix's lookAt is arguably backward.
// It's making an inverse lookAt which is far less useful.
// There's one camera in the scene but hundreds of other
// objects that might want to use a lookAt to you know, look at things.
mat4.lookAt(view, eye, target, up);
//mat4.lookAt(camera, eye, target, up);
//mat4.invert(view, camera);
var m = mat4.create();
var fov = 30 * Math.PI / 180;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.5;
var zFar = 10;
mat4.perspective(m, fov, aspect, zNear, zFar);
mat4.multiply(m, m, view);
mat4.translate(m, m, [1, 0, 0]);
mat4.rotateY(m, m, time);
mat4.scale(m, m, [1, 0.5, 0.7]);
uniforms.u_worldViewProjection = m;
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display block; }
<script src="https://twgljs.org/dist/twgl-full.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
<canvas></canvas>
我知道你需要什么,看一下:
http://nidza.html-5.me/zlatnaspirala2/project/index.html
源代码:
https://github.com/zlatnaspirala/zlatnaspirala2 https://github.com/zlatnaspirala/zlatnaspirala2/blob/master/project/zlatnaspirala/zlatnaspirala.js
魔法是:
mat4.translate(mvMatrix, [0.0, 0.0, 0.0]);
xRot = YY;
yRot = alfa + XX;
mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]);
mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
mat4.translate(mvMatrix, [transX +TX,transY + TY,transZ +TZ]);
1) 将其翻译为零
2) 旋转
3) 在三维世界中将其翻译为最后或当前位置。
T * R * vector
时,"数学定义"(不考虑OpenGL)是,直观地说,顶点首先被旋转,然后再进行平移。正如已经提到的:你可以稍微改变代码来改变这个顺序,但即使你写成M = Identity; M = T * M; M = R * T;
,结果仍然会是M = R * T
,这仍然是"先平移再旋转"。(抱歉,我不确定如何以一种听起来有说服力的方式描述这个问题。我试图为场景图遍历绘制一个图像,但这看起来很混乱...) - Marco13+-*/
运算符进行算术运算,使用命题演算进行条件语句,使用代数进行面向对象编程...这在计算机图形学中尤为明显,它总是与矩阵、向量空间等相关。然而,我对答案进行了更新,希望不会太令人困惑。 - Marco13