WebGL 根据模型矩阵的逆转置矩阵计算运动物体的光照效果

news2024/11/20 11:25:56

目录

前言

坐标变换引起法向量变化 

变化规律:

魔法矩阵:逆转置矩阵

逆转置矩阵的用法总结

Matrix4对象的 setInverseOf 、transpose 方法规范(以完成逆转置矩阵)

示例代码(LightedTranslatedRotatedCube.js)

代码详解 

示例效果


前言

场景中的物体运动,观察者的视角也很可能会改变,物体平移、缩放、旋转都可以用坐标变换来表示。显然,物体的运动会改变每个表面的法向量,从而导致光照效果发生变化。下面就来研究如何实现这一点。

在本次程序LightedTranslatedRotatedCube中,立方体先绕z轴顺时针旋转了90度,然后沿着y轴平移了0.9个单位。场景中的光照情况与前面的 WebGL光照介绍——平行光、环境光下的漫反射_山楂树の的博客-CSDN博客 LightedCube_ambient一样,即有平行光又有环境光。程序运行的效果如下所示。

坐标变换引起法向量变化 

立方体旋转时,每个表面的法向量也会随之变化。在下图中,我们沿着z轴负方向观察一个立方体,最左边是立方体的初始状态,图中标出了立方体右侧面的法向量(1,0,0),它指向x轴正方向,然后对该立方体进行变换,观察右侧面法向量随之变化的情况。 

变化规律:

● 平移变换不会改变法向量,因为平移不会改变物体的方向。 

● 旋转变换会改变法向量,因为旋转改变了物体的方向。

● 缩放变换对法向量的影响较为复杂。如你所见,最右侧的图显示了立方体先旋转了45度,再在y轴上拉伸至原来的2倍的情况。此时法向量改变了,因为表面的朝向改变了。但是,如果缩放比例在所有的轴上都一致的话,那么法向量就不会变化。最后,即使物体在某些轴上的缩放比例并不一致,法向量也并不一定会变化,比如将最左侧图中的立方体在y轴方向上拉伸两倍,法向量就不会变化。

显然,在对物体进行不同变换时,法向量的变化情况较为复杂(特别是缩放变换时)。这时候,数学公式就会派上用场了。

魔法矩阵:逆转置矩阵

曾讨论过,对顶点进行变换的矩阵称为模型矩阵。如何计算变换之后的法向量呢?只要将变换之前的法向量乘以模型矩阵的逆转置矩阵(inverse transpose matrix)即可。所谓逆转置矩阵,就是逆矩阵的转置。

逆矩阵的含义是,如果矩阵M的逆矩阵是R,那么R*M或M*R的结果都是单位矩阵。转置的意思是,将矩阵的行列进行调换(看上去就像是沿着左上-右下对角线进行了翻转)。

逆转置矩阵的用法总结

规则:用法向量乘以模型矩阵的逆转置矩阵,就可以求得变换后的法向量。

求逆转值矩阵的两个步骤:

1.求原矩阵的逆矩阵。

2.将上一步求得的逆矩阵进行转置。

Matrix4对象 WebGL矩阵变换库_山楂树の的博客-CSDN博客 提供了便捷的方法来完成上述任务,如下所示。 

Matrix4对象的 setInverseOf 、transpose 方法规范(以完成逆转置矩阵)

假如模型矩阵存储在modelMatrix对象(Matrix4类型的实例)中,那么下面这段代码将会计算它的逆转值矩阵,并将其存储在normalMatrix对象中(将其命名为normalMatrix是因为它被用来变换法向量):

 

下面来看看示例程序LightedTranslatedRotatedCube.js的代码。该程序使立方体绕z轴顺时针旋转90度,然后沿y轴平移0.9个单位,并且处于平行光和环境光的照射下。立方体在变换之前,与WebGL光照介绍——平行光、环境光下的漫反射_山楂树の的博客-CSDN博客LightedCube_ambient中的立方体完全相同。 

示例代码(LightedTranslatedRotatedCube.js)

如下显示了示例程序的代码。与WebGL光照介绍——平行光、环境光下的漫反射_山楂树の的博客-CSDN博客LightedCube_ambient相比,顶点着色器新增了u_NormalMatrix矩阵(第6行)用来对顶点的法向量进行变换(第14行)。你需要事先在JavaScript中计算出该变量,再将其传入着色器。

var VSHADER_SOURCE = // p301
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'attribute vec4 a_Normal;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform mat4 u_NormalMatrix;\n' +   // 用来变换法向量的矩阵
  'uniform vec3 u_LightColor;\n' +     // 平行光颜色
  'uniform vec3 u_LightDirection;\n' + // 光线方向归一化的世界坐标
  'uniform vec3 u_AmbientLight;\n' +   // 环境光颜色
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
     // 计算变换后的法向量并归一
  '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
     // 计算光线方向和法向量的点积(即两者归一化后的夹角的余弦值:cosθ)
  '  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
     // 计算漫反射光的颜色(入射光颜色 * 表面基底色 * cosθ)
  '  vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
     // 计算环境光产生的反射光的颜色
  '  vec3 ambient = u_AmbientLight * a_Color.rgb;\n' +
     // 将以上两者相加作为最终的颜色(物体表面的反射光颜色 = 漫反射光颜色 + 环境反射光颜色)
  '  v_Color = vec4(diffuse + ambient, a_Color.a);\n' + 
  '}\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  gl.clearColor(0, 0, 0, 1);
  gl.enable(gl.DEPTH_TEST);

  // 获取uniform等变量的存储地址
  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
  var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
  var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
  var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');

  // 设置平行光为白色
  gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
  // 设置光线方向
  var lightDirection = new Vector3([0.0, 3.0, 4.0]);
  lightDirection.normalize();     // 归一
  gl.uniform3fv(u_LightDirection, lightDirection.elements);
  // 设置环境光颜色
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

  var modelMatrix = new Matrix4();  // 视图矩阵
  var mvpMatrix = new Matrix4();    // 模型视图投影矩阵
  var normalMatrix = new Matrix4(); // 用来变换法向量的逆转置矩阵

  // 计算模型矩阵
  modelMatrix.setTranslate(0, 0.9, 0); // 沿Y轴平移
  modelMatrix.rotate(90, 0, 0, 1);     // 绕Z轴旋转
  // 计算模型视图投影矩阵
  mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
  mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
  mvpMatrix.multiply(modelMatrix); // 模型 视图投影 相乘得到最终矩阵
  // 将模型视图投影矩阵传给u_MvpMatrix变量
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

  /* 根据模型矩阵计算逆转置矩阵以变换法线 */ 
  normalMatrix.setInverseOf(modelMatrix); // 求原矩阵的逆矩阵
  normalMatrix.transpose(); // 将上一步求得的逆矩阵进行转置,并将自己设为转置后的结果
  // 将用来变换法向量的矩阵传给u_NormalMatrix变量
  gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
  // Create a cube
  //    v6----- v5
  //   /|      /|
  //  v1------v0|
  //  | |     | |
  //  | |v7---|-|v4
  //  |/      |/
  //  v2------v3
  // Coordinates
  var vertices = new Float32Array([
     1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0, // v0-v1-v2-v3 front
     1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,   1.0, 1.0,-1.0, // v0-v3-v4-v5 right
     1.0, 1.0, 1.0,   1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
    -1.0, 1.0, 1.0,  -1.0, 1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
    -1.0,-1.0,-1.0,   1.0,-1.0,-1.0,   1.0,-1.0, 1.0,  -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
     1.0,-1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0, 1.0,-1.0,   1.0, 1.0,-1.0  // v4-v7-v6-v5 back
  ]);

  // Colors
  var colors = new Float32Array([
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v1-v2-v3 front
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v3-v4-v5 right
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v5-v6-v1 up
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v1-v6-v7-v2 left
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v7-v4-v3-v2 down
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0     // v4-v7-v6-v5 back
 ]);

  // Normal
  var normals = new Float32Array([
    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,  // v0-v1-v2-v3 front
    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,  // v0-v3-v4-v5 right
    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,  // v0-v5-v6-v1 up
   -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  // v1-v6-v7-v2 left
    0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,  // v7-v4-v3-v2 down
    0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0   // v4-v7-v6-v5 back
  ]);

  // Indices of the vertices
  var indices = new Uint8Array([
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,21,22,  20,22,23     // back
 ]);

  // 将顶点属性写入缓冲区(坐标、颜色和法线)
  if (!initArrayBuffer(gl, 'a_Position', vertices, 3)) return -1;
  if (!initArrayBuffer(gl, 'a_Color', colors, 3)) return -1;
  if (!initArrayBuffer(gl, 'a_Normal', normals, 3)) return -1;
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  var indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  return indices.length;
}

function initArrayBuffer(gl, attribute, data, num) {
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  gl.vertexAttribPointer(a_attribute, num, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
  return true;
}

代码详解 

顶点着色器的流程与LightedCube_ambient类似,区别在于,本例根据前述的规则先用模型矩阵的逆转置矩阵对a_Normal进行了变换,再赋值给normal(第14行),而不是直接赋值:

a_Normal是vec4类型的,u_NormalMatrix是mat4类型的,两者可以相乘,其结果也是vec4类型。我们只需要知道结果的前三个分量,所以就使用vec3()函数取其前3个分量,转为vec3类型。你也可以使用.xyz来这样做,比如这样写:(u_NormalMatrix*a_Normal).xyz。现在你已经了解了在物体旋转和平移时,如何变换每个顶点的法向量了。下面来看在JavaScript代码中如何计算传给着色器的u_NormalMatrix变量的矩阵。 

u_NormalMatrix是模型矩阵的逆转置矩阵。示例中立方体先绕z轴旋转再沿y轴平移,所以首先使用serTranslate()和rotate()计算出模型矩阵(第63~64行);接着求模型矩阵的逆矩阵,再对结果进行转置,得到逆转置矩阵normalMatrix(第73~74行);最后,将逆转置矩阵传给着色器中的u_NormalMatrix变量(第76行)。gl.uniformMatrix4fv()函数的第2个参数指定是否对矩阵矩形转置。

运行程序,效果如下所示。与LightedCube_ambient相比,立方体各个表面的颜色没有改变,只是位置向上移动了一段距离,这是因为:(1)平移没有改变法向量;(2)旋转虽然改变了法向量,但这里恰好旋转了90度,原来的前面现在处在右侧面的位置上,所以立方体看上去没有变化;(3)场景中的光照条件不会随着立方体位置的变化而改变;(4)漫反射光在各方向上是均匀的。 

示例效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1017498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟机Ubuntu操作系统常用终端命令(2)(详细解释+详细演示)

本篇概要 本篇讲述了Ubuntu操作系统常用的几个功能,即超级用户,虚拟机系统损坏如何修复,用户和组,如何以root登录界面以及文件的权限方面的知识。希望能够得到大家的支持。 文章目录 本篇概要1.超级用户1.1使用超级用户1.2切换到…

【ICer必备基础】MOS电容——电容电压特性详解

【ICer必备基础】MOS电容——电容电压特性详解 1相关定义2MOS电容描述3MOS电容能带分析4可变电容实际应用 1相关定义 MOS电容是集成电路中非常重要的应用,器件电容的定义为: 阈值反型点: 当达到最大耗尽宽度且反型层电荷密度为零时的情形。此…

在Windows上无法使用TortoiseSVN等工具管理WSL2中的代码的问题

环境 Windows 11 WSL2(Ubuntu 22.04) 前言 众所周知,WSL2 的跨系统IO读写性能非常差(详情见我之前写的这篇文章),而我的代码又是在 WSL2 中运行的,为了提高性能,所以我的代码也必…

2023 Google 开发者大会:无障碍游戏体验升级、安卓开发人员生产力爆棚

目录 前言 一、会说话的狗 - “大黄” 二、GameFace -联合《荒野行动》,提升无障碍游戏体验 三、Android Studio Bot-解放开发人员生产力 五、Purnima致中国开发者的一封“信” 结语 🎈个人主页:库库的里昂 🎐CSDN新晋作者 …

9. 原型模式

引言 关键在于具备clone函数&#xff1b;克隆自身。 原型模式&#xff08;Prototype&#xff09;&#xff0c;用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象。 UML 测试代码&#xff1a; #include <iostream> using namespace std;class …

Python 之plt.plot()的介绍以及使用

文章目录 介绍代码实例 介绍 plt.plot() 是Matplotlib库中用于绘制线图&#xff08;折线图&#xff09;的主要函数之一。它的作用是将一组数据点连接起来&#xff0c;以可视化数据的趋势、关系或模式。以下是 plt.plot() 的详细介绍&#xff1a; plt.plot(x, y, fmt, **kwarg…

sun.security.validator.ValidatorException: PKIX path building failed

问题说明 在A系统使用HttpPost调用B系统的接口时报&#xff0c;B系统的ssl证书使用的是阿里云免费ssl证书&#xff0c;很郁闷调用别的系统都没有出现过这样的问题。 sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.Su…

ChatGLM-6b的微调与推理

基于ChatGLM-6B的推理与部署 1.使用git clone命令ChatGLM项目地址&#xff0c;将项目clone到本地。 2.下载ChatGLM-6B模型文件 【注意】运行下面代码的时候&#xff0c;要将源代码中的模型文件路径改成自己的地址&#xff0c;不然会报错&#xff01;&#xff01;&#xff01;…

destoon自定义一个archiver内容文档

在archiver目录建立以下代码&#xff1a; <?php define(DT_REWRITE, true); require ../common.inc.php; $EXT[archiver_enable] or dheader(DT_PATH); //$DT_BOT or dheader(DT_PATH); $N $M $T array(); $mid or $mid 5; $vmid $list 0; foreach($MODULE as $k>…

微服务保护-授权规则

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

【JUC】Java并发编程从挖坑到入土全解(2)

目录 我们锁的到底是什么&#xff08;8个案例&#xff09; 案例1 案例2 案例3 案例4 案例5 案例6 案例7 案例8 总结 我们锁的到底是什么&#xff08;8个案例&#xff09; 有a、b两个线程&#xff0c;我们基于如下代码进行改造&#xff1a; public static void main…

数据结构---二叉搜索树

二叉搜索树 二叉搜索树什么是二叉搜索树&#xff1f; 二叉搜索树的操作查找插入删除 源代码非递归版 二叉搜索树 什么是二叉搜索树&#xff1f; 二叉搜索树(Binary Search Tree 简称BST)又称二叉排序树&#xff0c;是一种二叉树的特殊形式&#xff0c;它在每个节点上存储的键…

动态规划-货币问题

动态规划-货币问题 题目一 arr是货币数组&#xff0c;其中的值都是正数。再给定一个正数aim。每个值都认为是一张货币&#xff0c;即便是值相同的货币也认为每一张都是不同的&#xff0c;返回组成aim的方法数。例如 : arr { 1,1,1 }&#xff0c;aim 2&#xff0c;第0个和第…

C++之std::monostate应用实例(二百二十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

安卓恶意应用识别(三)(批量反编译与属性值提取)

前言 上篇说到对安卓APK反编译&#xff0c;本篇实现批量反编译和批量特征提取及计算&#xff0c;主要就是通过python代码与cmd进行批量化交互&#xff0c;我在写文章之前&#xff0c;尝试批量下载了安卓apk&#xff08;大约10来个&#xff09;&#xff0c;发现现在这个应用软件…

基于SSM的珠宝首饰交易平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

GMAC PHY介绍

1.1PHY接口发展 &#xff08;1&#xff09;MII支持10M/100Mbps&#xff0c;一个接口由14根线组成&#xff0c;它的支持还是比较灵活的&#xff0c;但是有一个缺点是因为它一个端口用的信号线太多。参考芯片&#xff1a;DP83848 、DM900A&#xff08;该芯片内部集成了MAC和PHY接…

义乌购yiwugo根据ID取商品详情 API 接口系列,可测试

义乌购是义乌市场官方网站&#xff0c;以义乌市场为核心&#xff0c;覆盖全国小商品产业带优质供应商&#xff0c;提供一手货源&#xff0c;品类丰富&#xff0c;在线商品达500万&#xff0c;涉及玩具、饰品、工艺品、日用百货等26个大类。同时提供线上线下对应&#xff0c;交易…

线上论坛之性能测试

使用loadrunner进行简单性能测试&#xff1a;针对用户登录、发布帖子、点赞帖子、修改帖子内容、修改用户名、退出等功能进行简单的性能测试。 然后在实现的过程中&#xff0c;插入集合点以及事务等&#xff0c;并通过设置来实现用户的并发操作。 创建Vuser脚本。在自动化脚本中…

FIR数字滤波器设计及MATLAB实现

摘要&#xff1a;FIR数字滤波器是数字信号处理中得重要组成部分。本文主要介绍了利用MATLAB软件采用窗函数法设计符合指标的FIR数字滤波器。该方法也是窗函数法设计FIR数字滤波器的一般方法。 一、设计目的 MATLAB是一款功能强大的软件&#xff0c;它将数值分析、矩阵计算、科…