图形学介绍-3D转换

学习图形学过程中看到来自Hobart and William Smith Colleges的David J. Eck教授编写的图形学介绍V1.3.0,读起来确实有不少的感悟。

第7.1节

3D 转换

我们已经在第 6 章看到了如何使用 WebGL 绘制图元,以及如何实现 2D 转换。 在 3D 中绘制图元是相同的,只是每个顶点有三个坐标而不是两个。 3D 中的转换也类似于 2D,但对于转换而言,第三个维度带来的复杂性增加是可观的。 本节介绍使用 WebGL 的 3D 图形的几何方面。 在下一节中,我们将继续讨论灯光和材质的问题。

7.1.1 关于着色器脚本

但在我们开始更认真地使用 WebGL 之前,最好有一种更好的方法在网页上包含着色器源代码。 到目前为止,我已经通过连接一堆 JavaScript 字符串文字来创建源代码字符串,每行代码一个。 这种格式很难阅读,也很难编辑。 至少还有两种经常使用的其他技术。 一种是将 GLSL 着色器源代码放在 <script> 元素中。 这是一个顶点着色器的示例:

<script type="x-shader/x-vertex" id="vshader">

    attribute vec3 a_coords;

    uniform mat4 modelviewProjection;

    void main() {

        vec4 coords = vec4(a_coords,1.0);

        gl_Position = modelviewProjection * coords;

    }

</script>

这依赖于 Web 浏览器无法识别 <script> 元素中列出的类型的事实,因此它不会尝试执行脚本。 但是,它确实将 <script> 元素的内容存储在表示网页的 DOM 数据结构中。 可以使用标准 DOM API 将内容作为字符串检索。 我不会解释使用的 API 函数,但这里有一个函数,它以脚本元素的 id 作为参数并返回包含元素内部文本的字符串:

function getTextContent( elementID ) {

    let element = document.getElementById(elementID);

    let node = element.firstChild;

    let str = "";

    while (node) {

        if (node.nodeType == 3) // this is a text node

            str += node.textContent;

        node = node.nextSibling;

    }

    return str;

}

示例程序 webgl/glmatrix-cube-unlit.html 使用了这种技术。 另一个想法是将源代码定义为 JavaScript 模板字符串。 (见 A.3.1 小节)。 模板字符串包含在单个反引号字符之间,并且可以跨越多行。 (“反引号”也称为“反引号”。)模板字符串仅作为 ES6 的一部分引入 JavaScript。 它们可以包含 JavaScript 表达式的值,但我们在这里不需要这种能力。 以下是如何将上述着色器定义为模板字符串:

const vertexShaderSource = `

attribute vec3 a_coords;

uniform mat4 modelviewProjection;

void main() {

    vec4 coords = vec4(a_coords,1.0);

    gl_Position = modelviewProjection * coords;

}`;

本章的许多示例程序都使用了这种技术。 请注意,如果您将 GLSL ES 3.00 着色器定义为模板字符串,则应确保在开始的反引号之后立即包含所需的第一行 #version 3.00 es,因为该行前面不能有空行。

7.1.2介绍glMatrix

转换对于计算机图形学是必不可少的。 WebGL API 不提供任何用于处理转换的函数。 在 6.5 节中,我们使用了一个简单的 JavaScript 类来表示 2D 中的建模转换。 事情在三个维度上变得更加复杂。 对于使用 WebGL 的 3D 图形,JavaScript 端通常必须创建模型视图变换和投影变换,并且必须对模型视图矩阵应用旋转、缩放和平移,所有这些都没有 WebGL 的帮助。 如果您有一个 JavaScript 库来完成这项工作,那么这样做会容易得多。 一个常用的库是 glMatrix,这是一个免费的用于向量和矩阵数学的 JavaScript 库,由 Brandon Jones 和 Colin MacKenzie IV 编写。 它可从 https://glmatrix.net 获得。 这本教科书使用 2015 年的图书馆 2.3 版,尽管有更新的版本可用。 根据其许可,该文件可以自由使用和分发。 我的程序使用脚本 gl-matrix-min.js。 您可以在本书网站下载的源文件夹中找到一份副本。 这个文件是一个“缩小”的 JavaScript 文件,它并不是人类可读的。 (您还可以在文件 webgl/gl-matrix.js 中以人类可读的形式阅读 2.2 版的完整源代码,包括注释,更多信息可以在 glmatrix 网站上找到。)

glMatrix API 可以在带有脚本元素的网页上使用,例如

<script src="gl-matrix-min.js"></script>

这假设 gl-matrix-min.js 与网页位于同一目录中。

glMatrix 库定义了名为 vec2、vec3 和 vec4 的所谓“类”,用于处理 2、3 和 4 个数字的向量。 它定义了 mat3 用于处理 3×3 矩阵和 mat4 用于 4×4 矩阵。 这些名称不应与同名的 GLSL 类型混淆; glMatrix 完全在 JavaScript 端。 但是,可以将 glMatrix mat4 传递给着色器程序以指定 GLSL mat4 的值,对于其他向量和矩阵类型也是如此。

每个 glMatrix 类都定义了一组用于处理向量和矩阵的函数。 然而事实上,尽管文档使用了“类”这个术语,但 glMatrix 并不是面向对象的。 它的类实际上只是 JavaScript 对象,其类中的函数就是 Java 中所谓的静态方法。 向量和矩阵在 glMatrix 中表示为数组,而 vec4 和 mat4 等类中的函数只是对这些数组进行操作。 没有 vec4 或 mat4 类型的对象,只有长度分别为 4 或 16 的数组。 数组可以是普通的 JavaScript 数组,也可以是 Float32Array 类型的类型数组。 如果您让 glMatrix 为您创建数组,它们将是 Float32Arrays,但所有 glMatrix 函数都适用于任何一种数组。 例如,如果 glMatrix 文档说参数应该是 vec3 类型,则可以传递 Float32Array 或三个数字的常规 JavaScript 数组作为该参数的值。

请注意,任何一种数组都可以在诸如 gl.uniform3fv() 和 gl.uniformMatrix4fv() 之类的 WebGL 函数中使用。 glMatrix 旨在使用这些功能。 例如,glMatrix 中的 mat4 是一个长度为 16 的数组,它以列优先顺序保存 4×4 数组的元素,与 gl.uniformMatrix4fv 使用的格式相同。

每个 glMatrix 类都有一个 create() 函数,它创建一个适当长度的数组并用默认值填充它。 例如, transform = mat4.create();

将 transform 设置为长度为 16 的新 Float32Array,初始化为表示单位矩阵。

相似地, vector = vec3.create();

创建一个长度为 3 的 Float32Array,用零填充。 每个类还有一个函数 clone(x),它创建其参数 x 的副本。

例如:

 saveTransform = mat4.clone(modelview); 

大多数其他函数不会创建新数组。 相反,他们修改了第一个参数的内容。例如,mat4.multiply(A,B,C) 将修改 A,使其包含 B 和 C 的矩阵乘积。每个参数必须是一个已经存在的 mat4(即长度为 16 的数组)。 一些数组相同是可以的。 例如, mat4.multiply(A,A,B) 具有将 A 乘以 B 并修改 A 以使其包含答案的效果。

有一些函数可以将矩阵乘以标准变换,例如缩放和旋转。 例如,如果 A 和 B 是 mat4s 而 v 是 vec3,则 mat4.translate(A,B,v) 使 A 等于 B 与向量 v 表示平移的矩阵的乘积。在实践中,我们将使用此类操作主要在表示模型视图转换的矩阵上进行。 因此,假设我们有一个名为 modelview 的 mat4 来保存当前的模型视图变换。 要通过向量 [dx,dy,dz] 应用平移,我们可以说

mat4.translate( modelview, modelview, [dx,dy,dz] );

这相当于在 OpenGL 中调用 glTranslatef(dx,dy,dz)。 也就是说,如果我们在这个语句之后绘制一些几何图形,使用模型视图作为模型视图变换,那么几何图形将首先通过 [dx,dy,dz] 进行平移,然后将通过模型视图之前的值进行变换。 请注意在此命令中使用向量来指定平移,而不是三个单独的参数; 这是 glMatrix 的典型特征。 要使用比例因子 sx、sy 和 sz 应用比例变换,请使用

mat4.scale( modelview, modelview, [sx,sy,sz] );

对于旋转,glMatrix 有四个函数,其中三个用于绕 x、y 或 z 轴旋转的常见情况。 第四个旋转函数将旋转轴指定为从 (0,0,0) 到点 (dx,dy,dz) 的直线。 这等效于 glRotatef(angle,dx,dy,dz) 不幸的是,这些函数中的旋转角度以弧度而不是度数指定:

mat4.rotateX( modelview, modelview, radians );

mat4.rotateY( modelview, modelview, radians );

mat4.rotateZ( modelview, modelview, radians );

mat4.rotate( modelview, modelview, radians, [dx,dy,dz] );

这些功能使我们能够进行 3D 图形所需的所有基本建模和查看转换。 为了做分层图形,我们还需要在遍历场景图时保存和恢复变换。 为此,我们需要一个堆栈。 我们可以使用一个常规的 JavaScript 数组,它已经有 push 和 pop 操作。 因此,我们可以将堆栈创建为一个空数组:

const matrixStack = [];

然后我们可以通过说将当前模型视图矩阵的副本推送到堆栈中

matrixStack.push( mat4.clone(modelview) );

我们可以从堆栈中删除一个矩阵并将其设置为当前的模型视图矩阵

modelview = matrixStack.pop();

这些操作等价于OpenGL中的glPushMatrix()和glPopMatrix()。

模型视图变换的起点通常是视图变换。 在 OpenGL 中,函数 gluLookAt 常用于设置视图变换(3.3.4 小节)。 glMatrix 库有一个“lookAt”函数来做同样的事情:

mat4.lookAt( modelview, [eyex,eyey,eyez], [refx,refy,refz], [upx,upy,upz] );

请注意,此函数使用三个 vec3 代替 gluLookAt 中的九个单独参数,并将结果放在其第一个参数中而不是全局变量中。 这个函数调用其实相当于两个OpenGL命令

glLoadIdentity();

gluLookAt( eyex,eyey,eyez,refx,refy,refz,upx,upy,upz );

因此,您不必在调用 mat4.lookAt 之前将模型视图设置为等于单位矩阵,就像您通常在 OpenGL 中所做的那样。 但是,您必须在使用 mat4.lookAt 之前的某个时间点创建模型视图矩阵,例如通过调用

let modelview = mat4.create();

如果您确实想将现有 mat4 设置为单位矩阵,您可以使用 mat4.identity 函数来实现。例如,

mat4.identity( modelview );

如果您想将视图转换组合成超出基本比例、旋转和平移转换,则可以以此为起点。 类似地,glMatrix 具有设置投影变换的功能。 它具有等效于 glOrtho、glFrustum 和 gluPerspective 的功能(第 3.3.3 小节),除了 mat4.perspective 中的视场角以弧度而不是度数给出:

mat4.ortho( projection, left, right, bottom, top, near, far );

mat4.frustum( projection, left, right, bottom, top, near, far );

mat4.perspective( projection, fovyInRadians, aspect, near, far );

与模型视图转换一样,您不需要在调用其中一个函数之前加载具有标识的投影,但您必须将投影创建为 mat4(或长度为 16 的数组)。

7.1.3 变换坐标

当然,进行投影和模型视图转换的目的是在绘制图元时使用它们来转换坐标。 在 WebGL 中,转换通常在顶点着色器中完成。 图元的坐标在对象坐标(最初指定对象中点的坐标的坐标系,然后再通过任何建模或将应用于对象的其他变换对其进行变换。)中指定。 它们乘以模型视图转换以将它们转换为眼睛坐标(查看器定义的 3D 空间坐标系。 在 OpenGL 1.1 的眼睛坐标中,观察者位于原点,朝负 z 轴的方向看,正 y 轴朝上,正 x 轴朝右。 模型视图变换将对象映射到眼睛坐标系,投影变换将眼睛坐标映射到裁剪坐标。),然后乘以投影矩阵以将它们转换为实际用于绘制图元的最终裁剪坐标(OpenGL 中的默认坐标系。 投影变换将 3D 场景映射到裁剪坐标。 渲染图像将在裁剪坐标系中显示立方体的内容,其中包含从 -1 到 1 范围内的 x、y 和 z 值; 超出该范围的任何内容都会被“剪掉”。)。 或者,模型视图和投影矩阵可以相乘,得到一个表示组合变换的矩阵; 然后可以将对象坐标乘以该矩阵以将它们直接转换为裁剪坐标。 在着色器程序中,坐标变换通常表示为 mat4 类型的 GLSL 统一变量(表示可编程图形管道中着色器程序输入的变量。 统一变量在图元的每个顶点和每个像素处具有相同的值。)。 着色器程序可以使用单独的投影和模型视图矩阵或组合矩阵(或两者)。 有时,需要一个单独的模型视图变换矩阵,因为某些光照计算是在眼睛坐标中完成的,但这里有一个使用组合矩阵的最小 GLSL ES 1.00 顶点着色器:

attribute vec3 a_coords;           // (x,y,z) object coordinates of vertex.

uniform mat4 modelviewProjection;  // Combined transformation matrix.

void main() {

    vec4 coords = vec4(a_coords,1.0);   // Add 1.0 for the w-coordinate.

    gl_Position = modelviewProjection * coords;  // Transform the coordinates.

}

此着色器来自示例程序 webgl/glmatrix-cube-unlit.html。该程序允许用户查看一个彩色立方体,只使用基本颜色而不应用照明。用户可以选择正交投影或透视投影,并且可以使用键盘旋转立方体。旋转作为建模变换应用,包括围绕 x、y 和 z 轴的单独旋转。对于 JavaScript 端的变换矩阵,程序使用 glMatrix 库中的 mat4 类来表示投影、模型视图和组合变换矩阵:

const projection = mat4.create();  // projection matrix

const modelview = mat4.create();   // modelview matrix

const modelviewProjection = mat4.create();  // combined matrix

(这些变量可以是 const,因为在整个程序中将使用相同的矩阵对象,即使对象中的数字会发生变化。)只有 modelviewProjection 对应于着色器变量。 该变量在着色器程序中的位置是在初始化期间使用

u_modelviewProjection = gl.getUniformLocation(prog, "modelviewProjection");

使用 glMatrix mat4 类中的函数在 draw() 函数中计算变换矩阵。 在绘制构成立方体的图元之前,使用 gl.uniformMatrix4fv 将 modelviewProjection 的值发送到着色器程序。 这是执行此操作的代码:

/* Set the value of projection to represent the projection transformation */

if (document.getElementById("persproj").checked) {

     mat4.perspective(projection, Math.PI/5, 1, 4, 8);

}

else {

     mat4.ortho(projection, -2, 2, -2, 2, 4, 8);

}

/* Set the value of modelview to represent the viewing transform. */

mat4.lookAt(modelview, [2,2,6], [0,0,0], [0,1,0]);

/* Apply the modeling tranformation to modelview. */

mat4.rotateX(modelview, modelview, rotateX);

mat4.rotateY(modelview, modelview, rotateY);

mat4.rotateZ(modelview, modelview, rotateZ);

/* Multiply the projection matrix times the modelview matrix to give the

   combined transformation matrix, and send that to the shader program. */

mat4.multiply( modelviewProjection, projection, modelview );

gl.uniformMatrix4fv(u_modelviewProjection, false, modelviewProjection );

如果在着色器程序中使用单独的模型视图和投影矩阵,则可以应用模型视图矩阵将对象坐标转换为眼睛坐标,然后可以将投影应用于眼睛坐标以计算 gl_Position。 这是一个最小的顶点着色器:

attribute vec3 a_coords;  // (x,y,z) object coordinates of vertex.

uniform mat4 modelview;   // Modelview transformation.

uniform mat4 projection;  // Projection transformation

void main() {

    vec4 coords = vec4(a_coords,1.0);      // Add 1.0 for w-coordinate.

    vec4 eyeCoords = modelview * coords;   // Apply modelview transform.

    gl_Position = projection * eyeCoords;  // Apply projection transform.

}

7.1.4 转换法线

法线向量对于照明计算是必不可少的(第 4.1.3 小节)。 当一个表面以某种方式变换时,该表面的法线向量似乎也会改变。 但是,如果转换是平移,则情况并非如此。 法线向量告诉表面朝向的方向。 平移曲面不会改变曲面所面对的方向,因此法线向量保持不变。 请记住,向量没有位置,只有长度和方向。 因此,谈论移动或平移向量甚至没有意义。 您的第一个猜测可能是法线向量应该仅通过变换的旋转/缩放部分进行变换。 猜测是正确的变换由 3×3 矩阵表示,该矩阵是通过从 4×4 坐标变换矩阵中删除右列和底行而获得的。 (右列表示变换的平移部分,只有最下面一行是因为在矩阵中实现平移需要使用齐次坐标来表示向量。法向量,平移不是问题,不要使用齐次坐标.) 但这并非在所有情况下都是正确的。 例如,考虑剪切变换。 如图所示,如果对象的法线向量与对象进行相同的剪切变换,则生成的向量将不会垂直于对象:

然而,可以从坐标变换矩阵中获得法向量的正确变换矩阵。 事实证明,您需要删除第四行和第四列,然后对生成的 3×3 矩阵进行所谓的“逆转置”。 你不需要知道这意味着什么或它为什么起作用。 glMatrix 库将为您计算它。 您需要的函数是 normalFromMat4,它在 mat3 类中定义:

mat3.normalFromMat4( normalMatrix, coordinateMatrix );

在此函数调用中,coordinateMatrix 是表示应用于坐标的变换的 mat4,而 normalMatrix 是已经存在的 mat3。 此函数计算坐标矩阵的旋转/缩放部分的逆转置,并将结果放在 normalMatrix 中。 由于光照计算需要法线向量,而光照计算是在眼睛坐标中完成的,所以我们感兴趣的坐标变换通常是模型视图变换。 法线矩阵应发送到着色器程序,在该程序中需要转换法线向量以用于光线计算。 照明需要单位法向量,即长度为 1 的法向量。 法线矩阵通常不保留应用它的向量的长度,因此有必要对变换后的向量进行归一化。 GLSL 有一个用于标准化向量的内置函数。 实现光照的顶点着色器可能采用以下形式:

attribute vec3 a_coords;   // Untransformed object coordinates.

attribute vec3 normal;     // Normal vector.

uniform mat4 projection;   // Projection transformation matrix.

uniform mat4 modelview;    // Modelview transformation matrix.

uniform mat3 normalMatrix; // Transform matrix for normal vectors.

  .

  .  // Variables to define light and material properties.

  .

void main() {

    vec4 coords = vec4(a_coords,1.0);  // Add a 1.0 for the w-coordinate.

    vec4 eyeCoords = modelview * coords;  // Transform to eye coordinates.

    gl_Position = projection * eyeCoords;  // Transform to clip coordinates.

    vec3 transformedNormal = normalMatrix*normal;  // Transform normal vector.

    vec3 unitNormal = normalize(transformedNormal);  // Normalize.

       .

       .  // Use eyeCoords, unitNormal, and light and material

       .  // properties to compute a color for the vertex.

       .

}

我们将在下一节中看几个具体的例子。 我会注意到 GLSL ES 3.00(但不是 GLSL ES 1.00)具有用于计算逆矩阵和转置矩阵的内置函数,这使得在着色器中计算法线矩阵变得相当容易。 但是,在 JavaScript 端计算一次矩阵可能仍然更有效,而不是在每次执行顶点着色器时计算它。

7.1.5 鼠标旋转

当有用户交互时,计算机图形学会更有趣。 仅通过让用户旋转场景以从各个方向查看它,3D 体验就大大增强了。 未点亮的立方体示例允许用户使用键盘旋转场景。 但是使用鼠标进行旋转可以为用户提供更好的控制。 我编写了两个 JavaScript 类,SimpleRotator 和 TrackballRotator,以实现两种不同风格的鼠标旋转。 SimpleRotator 类在文件 webgl/simple-rotator.js 中定义。 要在网页上使用它,您需要将该文件包含在 <script> 标记中,并且您需要创建一个 SimpleRotator 类型的对象:

rotator = new SimpleRotator( canvas, callback, viewDistance );

第一个参数必须是 DOM <canvas> 元素。 它应该是 WebGL 渲染场景的画布。 SimpleRotator 构造函数向画布添加鼠标事件的侦听器; 它还处理触摸屏上的触摸事件。 构造函数的第二个参数是可选的。 如果它被定义,它必须是一个函数。 每次旋转变化时都会调用该函数,不带参数。 通常,回调函数是在画布中呈现图像的函数。 第三个参数也是可选的。 如果定义,它必须是一个非负数。 它给出了观察者到旋转中心的距离。 默认值为零,这对于正交投影来说可以,但通常不正确。 SimpleRotator 跟踪随着用户旋转场景而发生变化的查看变换。 最重要的函数是 rotator.getViewMatrix()。 此函数返回一个由 16 个数字组成的数组,该数组表示按列优先顺序进行查看转换的矩阵。 矩阵可以使用 gl.uniformMatrix4fv 直接发送到着色器程序,也可以与 glMatrix 库中的函数一起用作模型视图矩阵的初始值。 示例程序 webgl/cube-with-simple-rotator.html 是使用 SimpleRotator 的示例。 该程序使用由 glMatrix 函数定义的透视投影

mat4.perspective(projection, Math.PI/8, 1, 8, 12);

旋转器的 viewDistance 必须在投影的近距和远距之间。 这里,near 为 8,far 为 12,viewDistance 可以设置为 10。 rotator 是在初始化时使用语句创建的

rotator = new SimpleRotator(canvas, draw, 10);

在draw()函数中,查看变换是在绘制场景之前从旋转器中获取的。 这个程序中没有建模变换,所以视图矩阵也是模型视图矩阵。 使用 glMatrix 函数将该矩阵乘以投影矩阵,然后将组合的变换矩阵发送到着色器程序:

let modelview = rotator.getViewMatrix();

mat4.multiply( modelviewProjection, projection, modelview );

gl.uniformMatrix4fv(u_modelviewProjection, false, modelviewProjection );

如果您只想在自己的程序中使用 SimpleRotator,这就是您需要知道的全部内容。 我还编写了一个替代的旋转器类,TrackballRotator,它在 JavaScript 文件 webgl/trackball-rotator.js 中定义。 TrackballRotator 的使用方式与 SimpleRotator 相同。 主要区别在于 TrackballRotator 允许完全自由旋转,而 SimpleRotator 具有 Y 轴在图像中始终保持垂直的约束。 示例程序 webgl/cube-with-trackball-rotator.html 使用 TrackballRotator,但在其他方面与 SimpleRotator 示例相同。 下面的演示让您尝试两种类型的旋转器。 左侧的立方体使用 SimpleRotator,右侧使用 TrackballRotator:

默认情况下,任何一种旋转器的旋转中心都是原点,即使原点不在图像的中心。 但是,您可以通过调用 rotation.setRotationCenter([a,b,c]) 将旋转中心更改为点 (a,b,c)。 参数必须是三个数字的数组。 通常,(a,b,c) 将是显示在图像中心的点(该点将是 gluLookAt 中的视图参考点)。

您无需了解用于实现旋转器的数学。 事实上,TrackballRotator 使用了一些我不想在这里解释的高级技术。 然而,SimpleRotator 更简单,很高兴知道它是如何工作的。 因此,我将解释如何为 SimpleRotator 计算视图转换。 实际上,从整体场景上相应的建模转换来考虑会更容易。 (回想一下建模和查看之间的等效性(第 3.3.4 小节)。) 建模转换包括绕 y 轴旋转,然后绕 x 轴旋转。 旋转的大小随着用户拖动鼠标而改变。 左/右运动控制围绕 y 轴的旋转,而上/下运动控制围绕 x 轴的旋转。 绕 x 轴的旋转被限制在 -85 到 85 度的范围内。 请注意,当投影到屏幕上时,绕 y 轴旋转然后绕 x 轴旋转总是使 y 轴指向垂直方向。 假设旋转中心是 (tx,ty,tz) 而不是 (0,0,0)。 为了实现这一点,在进行旋转之前,我们需要平移场景以将点 (tx,ty,tz) 移动到原点。 我们可以通过 (-tx,-ty,-tz) 的翻译来做到这一点。 然后,在进行旋转之后,我们需要将原点平移回点 (tx,ty,tz)。 最后,如果 viewDistance 不为零,我们需要将场景 viewDistance 单位推离观察者。 我们可以通过 (0,0,-viewDistance) 的平移来做到这一点。 如果 d 是视距,ry 是绕 y 轴的旋转,rx 是绕 x 轴的旋转,那么我们需要应用于场景的建模变换序列如下: 将视图中心移至原点:平移 (-tx,-ty,-tz)。 围绕 y 轴将场景旋转 ry 弧度。 围绕 x 轴将场景旋转 rx 弧度。 将原点移回视图中心:平移 (tx,ty,tz)。 将场景远离观察者:平移 (0,0,-d)。 请记住,建模转换以与它们在代码中出现的顺序相反的顺序应用于对象,可以通过以下 glMatrix 命令创建视图矩阵:

viewmatrix = mat4.create();

mat4.translate(viewmatrix, viewmatrix, [0,0,-d]);

mat4.translate(viewmatrix, viewmatrix, [tx,ty,tz]);

mat4.rotateX(viewmatrix, viewmatrix, rx);

mat4.rotateY(viewmatrix, viewmatrix, ry);

mat4.translate(viewmatrix, viewmatrix, [-tx,-ty,-tz]);

事实上,在我的代码中,我直接根据各​​个变换的矩阵创建了视图矩阵。 旋转和平移的 4×4 矩阵在 3.5.2 小节中给出。 SimpleRotator 的视图矩阵是五个平移和旋转矩阵的矩阵乘积:

实现乘法实际上并不太难。 如果您好奇,请参阅 JavaScript 文件 webgl/simple-rotator.js。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值