WebGL 视图矩阵、模型视图矩阵
目录
立方体由三角形构成
视点和视线
视点、观察目标点和上方向
视点:
观察目标点:
上方向:
在WebGL中,观察者的默认状态应该是这样的:
视图矩阵程序(LookAtTriangles.js)
实际上,“根据自定义的观察者状态,绘制观察者看到的景象”与“使用默认的观察状态,但是对三维对象进行平移、旋转等变换,再绘制观察者看到的景象”,这两种行为是等价的。(归根结底:不论视图怎样变化,无疑就是旋转、平移等操作,最终本质上都是对物体进行相反方向的旋转平移(默认的视角))
移动视点和移动被观察对象等效
从指定视点观察旋转后的三角形
<旋转后顶点坐标>=<旋转矩阵>×<原始顶点坐标>
<“从视点看上去”的旋转后顶点坐标>=<视图矩阵>×<旋转后顶点坐标>
<“从视点看上去”的旋转后顶点坐标>=<视图矩阵>×<旋转矩阵>×<原始顶点坐标>
<视图矩阵>×<模型矩阵>×<原始顶点坐标>
模型视图矩阵程序(LookAtRotatedTriangles.js)
模型视图矩阵
<模型视图矩阵>=<视图矩阵>×<模型矩阵>
<模型视图矩阵>×<顶点坐标>
立方体由三角形构成
三维物体也是由二维图形(特别是三角形)组成的。如下图所示,12个三角形组成了一个立方体。
既然三维物体是由三角形组成的,那么则需要逐个绘制组成物体的每个三角形,最终就可以绘制出整个三维物体了。但是,三维与二维还有一个显著区别:在绘制二维图形时,只需要考虑顶点的x和y坐标,而绘制三维物体时,还得考虑它们的深度信息(depth information)。那就开始吧,首先我们来研究一下如何定义三维世界的观察者:在什么地方、朝哪里看、视野有多宽、能看多远。
视点和视线
三维物体与二维图形的最显著区别就是,三维物体具有深度,也就是Z轴。因此,你会遇到一些之前不曾考虑过的问题。事实上,我们最后还是得把三维场景绘制到二维的屏幕上,即绘制观察者看到的世界,而观察者可以处在任意位置观察。为了定义一个观察者,你需要考虑以下两点:
● 观察方向,即观察者自己在什么位置,在看场景的哪一部分?
● 可视距离,即观察者能够看多远?
我们将观察者所处的位置称为视点(eye point),从视点出发沿着观察方向的射线称作视线(viewing direction)。本次将研究如何通过视点和视线来描述观察者。到下面我们再来研究“观察者能看多远”的问题。
我们来创建一个新的示例程序LookAtTriangles。在程序中,视点位于(0.20,0.25,0.25),视线向着原点(0,0,0)方向,可以看到原点附近有三个三角形。程序中的这三个三角形前后错落摆放,以帮助你理解三维场景中深度的概念。下图显示了LookAtTriangles的运行结果。
视点、观察目标点和上方向
为了确定观察者的状态,你需要获取两项信息:视点,即观察者的位置;观察目标点(look-at point),即被观察目标所在的点,它可以用来确定视线。此外,因为我们最后要把观察到的景象绘制到屏幕上,还需要知道上方向(up direction)。有了这三项信息,就可以确定观察者的状态了。下面将逐一进行解释(见下图)。
视点:
观察者所在的三维空间中位置,视线的起点。在接下来的几节中,视点坐标都用(eyeX,eyeY,eyeZ)表示。
观察目标点:
被观察目标所在的点。视线从视点出发,穿过观察目标点并继续延伸。注意,观察目标点是一个点,而不是视线方向,只有同时知道观察目标点和视点,才能算出视线方向。观察目标点的坐标用(atX,atY,atZ)表示。
上方向:
最终绘制在屏幕上的影像中的向上的方向。试想,如果仅仅确定了视点和观察点,观察者还是可能以视线为轴旋转的(如下图所示,头部偏移会导致观察到的场景也偏移了)。所以,为了将观察者固定住,我们还需要指定上方向。上方向是具有3个分量的矢量,用(upX,upY,upZ)表示。
在WebGL中,我们可以用上述三个矢量创建一个视图矩阵(view matrix),然后将该矩阵传给顶点着色器。视图矩阵可以表示观察者的状态,含有观察者的视点、观察目标点、上方向等信息。之所以被称为视图矩阵,是因为它最终影响了显示在屏幕上的视图,也就是观察者观察到的场景。根据提供的Matrix4.setLookAt()函数可以根据上述三个矢量:视点、观察点和上方向,来创建出视图矩阵 (矩阵库 WebGL矩阵变换库_山楂树の的博客-CSDN博客)。
在WebGL中,观察者的默认状态应该是这样的:
● 视点位于坐标系统原点(0,0,0)。
● 视线为Z轴负方向,观察点为(0,0,-1)
如果将上方向改为X轴正半轴方向(1,0,0),你将看到场景旋转了90度。
创建这样一个矩阵,你只需要简单地使用如下代码(见下图)。
现在,你已经了解了setLookAt()函数,下面来看示例程序的代码。
视图矩阵程序(LookAtTriangles.js)
程序显示了LookAtTriangles.js的代码,我们修改了视点,然后绘制了3个三角形,如上图三角形所示。实际上这3个三角形的颜色分别为蓝色到红色、黄色到红色和绿色到红色的渐变色。
var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'attribute vec4 a_Color;\n' +'uniform mat4 u_ViewMatrix;\n' +'varying vec4 v_Color;\n' +'void main() {\n' +' gl_Position = u_ViewMatrix * a_Position;\n' +' v_Color = a_Color;\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)) {console.log('Failed to intialize shaders.');return;}// 设置顶点坐标和颜色(蓝色三角形在最前面)var n = initVertexBuffers(gl);gl.clearColor(0, 0, 0, 1);// 获取u_ViewMatrix变量的存储地址var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');// 设置视点、视线和上方向var viewMatrix = new Matrix4();viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);// 将视图矩阵传给u_ViewMatrix变量gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);gl.clear(gl.COLOR_BUFFER_BIT);// 绘制三角形gl.drawArrays(gl.TRIANGLES, 0, n);
}function initVertexBuffers(gl) {var verticesColors = new Float32Array([// 顶点和坐标颜色0.0, 0.5, -0.4, 0.4, 1.0, 0.4, // 绿色三角形在最后面-0.5, -0.5, -0.4, 0.4, 1.0, 0.4,0.5, -0.5, -0.4, 1.0, 0.4, 0.4, 0.5, 0.4, -0.2, 1.0, 0.4, 0.4, // 黄色三角形在中间-0.5, 0.4, -0.2, 1.0, 1.0, 0.4,0.0, -0.6, -0.2, 1.0, 1.0, 0.4, 0.0, 0.5, 0.0, 0.4, 0.4, 1.0, // 蓝色三角形在最前面-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,0.5, -0.5, 0.0, 1.0, 0.4, 0.4, ]);var n = 9;// 创建缓冲区对象var vertexColorbuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer);gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);var FSIZE = verticesColors.BYTES_PER_ELEMENT;var a_Position = gl.getAttribLocation(gl.program, 'a_Position');gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);gl.enableVertexAttribArray(a_Position);var a_Color = gl.getAttribLocation(gl.program, 'a_Color');gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);gl.enableVertexAttribArray(a_Color);gl.bindBuffer(gl.ARRAY_BUFFER, null);return n;
}
首先,来看一下initVertexBuffers()函数(第42行)。verticesColors数组包含了3个三角形共计9个顶点的数据,而且顶点坐标的z分量也不再是0了。接着我们创建了缓冲区对象(第57行),并将数组中的数据填了进去(第58~59行)。此外,我们还把gl.drawArrays()的第3个参数改成了9(第39行),因为这里共有9个顶点。
然后,需要建立视图矩阵(包含了视点、视线和上方向信息)并传给顶点着色器。为此,我们先创建了一个Matrix4对象viewMatrix(第33行),然后用setLookAt()方法将其设置为视图矩阵(第34行),最后将视图矩阵中的元素传给顶点着色器中的u_ViewMatrix变量(第36行)。
实际上,“根据自定义的观察者状态,绘制观察者看到的景象”与“使用默认的观察状态,但是对三维对象进行平移、旋转等变换,再绘制观察者看到的景象”,这两种行为是等价的。(归根结底:不论视图怎样变化,无疑就是旋转、平移等操作,最终本质上都是对物体进行相反方向的旋转平移(默认的视角))
举个例子,默认情况下视点在原点,视线沿着Z轴负方向进行观察。假如我们将视点移动到(0,0,1),如下图(左)所示。这时,视点与被观察的三角形在Z轴上的距离增加了1.0个单位。实际上,如果我们使三角形沿Z轴负方向移动1.0个单位,也可以达到同样的效果,因为观察者看上去是一样的,如下图(右)所示。
移动视点和移动被观察对象等效
事实上,上述过程就发生在示例程序LookAtTriangles.js中。根据视点、观察点和上方向参数,setLookAt()方法计算出的视图矩阵恰恰就是“沿着Z轴负方向移动1.0个单位”的变换矩阵。所以,把这个矩阵与顶点坐标相乘,就相当于获得了“将视点设置在(0.0,0.0,1.0)”的效果。视点移动的方向与被观察对象(也就是整个世界)移动的方向正好相反。对于视点的旋转,也可以采用类似的方式。
“改变观察者的状态”与“对整个世界进行平移和旋转变换”,本质上是一样的,它们都可以用矩阵来描述。接下来,我们将从一个指定的视点来观察旋转后的三角形。
从指定视点观察旋转后的三角形
现在将修改LookAtTriangles程序来绘制一个从指定位置看过去的旋转后的三角形。这时,我们需要两个矩阵:旋转矩阵(表示三角形的旋转)和视图矩阵(表示观察世界的方式)。首先有一个问题是,以怎样的顺序相乘这两个矩阵。
我们知道,矩阵乘以顶点坐标,得到的结果是顶点经过矩阵变换之后的新坐标。也就是说,用旋转矩阵乘以顶点坐标,就可以得到旋转后的顶点坐标。
用视图矩阵乘以顶点坐标会把顶点变换到合适的位置,使得观察者(以默认状态)观察新位置的顶点,就好像在观察者处在(视图矩阵描述的)视点上观察原始顶点一样。现在要在某个视点处观察旋转后的三角形,我们需要先旋转三角形,然后从这个视点来观察它。换句话说,我们需要先对三角形进行旋转变换,再对旋转后的三角形进行与“移动视点”等效的变换。我们按照上述顺序相乘两个矩阵。具体看一下等式。
我们知道,如果想旋转图形,就需要用旋转矩阵乘以旋转前的顶点坐标:
<旋转后顶点坐标>=<旋转矩阵>×<原始顶点坐标>
用视图矩阵乘以旋转后的顶点坐标,就可以获得“从视点看上去”的旋转后的顶点坐标:
<“从视点看上去”的旋转后顶点坐标>=<视图矩阵>×<旋转后顶点坐标>
将第1个式子代入第2个,可得:
<“从视点看上去”的旋转后顶点坐标>=<视图矩阵>×<旋转矩阵>×<原始顶点坐标>
除了旋转矩阵,你还可以使用平移、缩放等基本变换矩阵或它们的组合,这时矩阵被称为模型矩阵(model matrix)。这样,上式就可以写成:
<视图矩阵>×<模型矩阵>×<原始顶点坐标>
示例程序在着色器中实现了该式。很简单,直接照着该式修改顶点着色器。修改后的LootAtRotatedTriangles程序实现了上述变换,如下图所示。图中白色虚线为旋转前三角形的所在位置,可以看到三角形确实被旋转过了。
模型视图矩阵程序(LookAtRotatedTriangles.js)
LookAtRotatedTriangles.js与LookAtTriangles.js相比,只有几处小改动:加入了uniform变量u_ModelMatrix;JavaScript中的main()函数将模型矩阵传给该变量。相应的代码如下所示。
首先,顶点着色器中添加了uniform变量u_ModelMatrix(第7行),该变量从JavaScript中接收模型矩阵,以实现等式<视图矩阵>×<模型矩阵>×<原始顶点坐标>。
JavaScript的main()函数已经有了与视图矩阵相关的代码,只需添加几行计算和传入旋转矩阵的代码,将三角形绕Z轴旋转10度。第53行代码实现了获取u_ModelMatrix变量的存储地址,第64行实现了创建一个新的矩阵对象modelMatrix,调用Matrix.setRotate()将其设为旋转矩阵(第65行),然后传给顶点着色器中的u_ModelMatrix(第69行)。
运行示例程序,顶点坐标依次与旋转矩阵和视图矩阵相乘,最终获得了预期的效果。如上图所示,即先用u_ModelMatrix旋转三角形,再将旋转后的坐标用u_ViewMatrix变换到正确的位置,使其看上去就像是从指定视点处观察一样。
模型视图矩阵
在LookAtRotatedTriangle.js中,着色器实现了式(视图矩阵×模型矩阵×原始坐标)。这样,程序对每个顶点都要计算视图矩阵×模型矩阵。如果顶点数量很多,这一步操作就会造成不必要的开销。这是因为,无论对哪个顶点而言,式(视图矩阵×模型矩阵×原始坐标)中的两个矩阵相乘的结果都是一样的。所以我们可以在JavaScript中事先把这两个矩阵相乘的结果计算出来,再传给顶点着色器。这两个矩阵相乘得到的结果被称为模型视图矩阵(model view matrix),如下所示:
<模型视图矩阵>=<视图矩阵>×<模型矩阵>
式视图矩阵×模型矩阵×原始坐标可以重写为下式
<模型视图矩阵>×<顶点坐标>
新的示例程序LookAtRotatedTriangles_mvMatrix.js按照式<模型视图矩阵>×<顶点坐标>重写了LookAtRotatedTriangles中的代码,如下所示。
顶点着色器中出现了新的uniform变量u_mvMatrix,它参与了对gl_Position的计算(第9行)。顶点着色器执行的流程与最初的LookAtTriangles.js中一样(除了uniform变量的名称)。
在JavaScript代码中,我们分别计算出了视图矩阵viewMatrix和模型矩阵modelMatrix(第59~63行),就像在LookAtTriangle.js中一样。然后,我们调用Matrix4.multiply()方法使这两个矩阵相乘,并将结果赋值给modelviewMatrix(第66行)。注意,我们是在viewMatrix上调用了multiply()方法,并传入modelMatrix为参数,所以这样做的结果实际上就是modelViewMatrix=viewMatrix*modelMatrix。因为在JavaScript中,我们不能像在GLSL ES中那样直接使用*号来进行矩阵相乘,而需要用矩阵库所提供的方法。
得到了modelViewMatrix后,就将它传给着色器的u_ModelMatrix变量(第69行)。运行程序,效果如下图所示。
最后还需要指出,示例程序显式地把视图矩阵和模型矩阵单独计算出来,再相乘为模型视图矩阵(第59~66行),是为了更好地表达模型视图矩阵的由来。实际上,只需要一行代码就可以计算出模型视图矩阵了:(计算出视图矩阵,再将视图矩阵与模型矩阵(当前只有旋转)相乘)
相关文章:

WebGL 视图矩阵、模型视图矩阵
目录 立方体由三角形构成 视点和视线 视点、观察目标点和上方向 视点: 观察目标点: 上方向: 在WebGL中,观察者的默认状态应该是这样的: 视图矩阵程序(LookAtTriangles.js) 实际上&…...
Python 3 – 文件 readline() 方法
Python 3 – 文件 readline() 方法|极客笔记 # 打开文件 file open("example.txt", "r")# 读取文件中的一行数据 line file.readline() while line:# 移除行尾的换行符print(line.strip())# 读取文件中的下一行数据line file.readline()# 关闭文件 file…...

如何在微软Edge浏览器上一键观看高清视频?
编者按:视频是当下最流行的媒体形式之一。但由于视频压缩、网络不稳定等原因,我们常常可以看到互联网上的很多视频其画面质量并不理想,尤其是在浏览器端,这极大地影响了观看体验。不过,近期微软 Edge 浏览器推出了一项…...
Telegram BoT的主流项目盘点
目录 DeFi 类 数据分析类 空投埋伏交易 其他 Telegram Bot赛道的发展趋势预测 Telegram BoT赛道发展较快,具体来看可以分为DeFi 类、数据分析类、空投埋伏交易类以及其他。 DeFi 类 Unibot(交易)、Banana Gun、WagieBot(交…...
PTA 甲级 1044 Shopping in Mars
题目链接 思路:前缀和滑动窗口 #include<bits/stdc.h> #define MAXN 100010 using namespace std; int a[MAXN];int main(){int n,m;cin>>n>>m;//n数量 m金额for(int i1;i<n;i){int t;cin>>t;a[i]a[i-1]t;//前缀和}vector<pair<in…...
Linux学习之MyCat实现分库分表
环境准备 先准备一套MySQL主从服务器,可参考MySQL主从配置配置MyCat服务 资源下载 网盘链接: https://pan.baidu.com/s/1cLTMH_e1-6loc_gF9ZNHTg?pwda63n 提取码: a63n MyCat配置 # 1)安装mycat软件 //安装jdk [rootmycat58 upload]# yum -y insta…...

DirectX12(d3d12)初始化
一、前置要求 Windows 10及以上(安装有DirectX12)VisualStudio 2022 二、DirectX12入门 1.引用头文件 #include<Windows.h> #include<d3d12.h> #include<dxgi1_4.h>2.注册窗口类并初始化窗口 这里我们调用Windows API 通过应用程序的句柄来注册一个唯一…...
算法通关村-----回溯模板如何解决排列组合问题
组合总和 问题描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限…...

【1++的C++进阶】之智能指针
👍作者主页:进击的1 🤩 专栏链接:【1的C进阶】 文章目录 一,什么是智能指针二,为什么需要智能指针三,智能指针的发展 一,什么是智能指针 要了解智能指针,我们先要了解RA…...

一百七十九、Linux——Linux报错No package epel-release available
一、目的 在Linux中配置Xmanager服务时,执行脚本时Linux报错No package epel-release available 二、解决措施 (一)第一步,# wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm (二&…...

【AI视野·今日CV 计算机视觉论文速览 第248期】Mon, 18 Sep 2023
AI视野今日CS.CV 计算机视觉论文速览 Mon, 18 Sep 2023 Totally 83 papers 👉上期速览✈更多精彩请移步主页 Interesting: 📚Robust e-NeRF,处理高速且大噪声事件相机流的NERF模型。(from NUS新加坡国立) 稀疏噪声事件与稠密事件数据的区别:…...
解决Vue项目中的“Cannot find module ‘vue-template-compiler‘”错误
1. 问题描述 在Vue项目中,当我们使用Vue的单文件组件(.vue文件)时,有时会遇到以下错误信息: ERROR: Cannot find module vue-template-compiler这个错误通常发生在我们使用Vue的版本不匹配或者缺少必要的依赖模块时。…...

tensorflow基础
windows安装tensorflow anaconda或者pip安装tensorflow,tensorflow只支持win7 64系统,本人使用tensorflow1.5版本(pip install tensorflow1.5) tensorboard tensorboard只支持chrome浏览器,而且加载过程中可能有一段…...

spring_注解笔记
spring使用注解开发 文章目录 1.前提1 Bean2 属性注入3 衍生的注解4.自动装配5 作用域 1.前提 步骤1: 要使用注解开发,就必须要保证AOP包的导入 步骤2: xml文件添加context约束 步骤3: 配置注解的支持 <context:annotation-…...

c++运算符重载
目录 运算符重载的基本概念 重载加号运算符() 类内实现 类外实现 运算符重载碰上友元函数 可重载和不可重载的运算符 可重载的运算符 不可重载的运算符 重载自加自减运算符(a a) 智能指针 重载等号运算符() 重载等于和不等运算符(…...
vue子组件向父组件传参的方式
在Vue中,子组件向父组件传递参数可以通过自定义事件和props属性来实现。下面是一些关键代码示例: 1. 使用自定义事件: 在子组件中,通过 $emit 方法触发一个自定义事件,并传递参数。 <template><button cli…...
代码随想录Day41| 343. 整数拆分 |
343. 整数拆分 class Solution { public:int integerBreak(int n) {vector<int> f(n1,0);f[2]1;for(int i3;i<n;i){for(int j1;j<i-1;j){f[i]max(f[i],max(f[i-j]*j,(i-j)*j));}}return f[n];} }; 96. 不同的二叉搜索树 class Solution { public:int numTrees(int…...
工厂模式-(简单工厂模式)
首先看一下设计模式的六大原则 设计模式的六大原则 1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概…...

V8引擎是如何提升对象属性访问速度的?
JavaScript 中的对象是由一组组属性和值的集合,从 JavaScript 语言的角度来看,JavaScript 对象像一个字典,字符串作为键名,任意对象可以作为键值,可以通过键名读写键值。 然而在 V8 实现对象存储时,并没有…...

彩色相机工作原理——bayer格式理解
早期,图像传感器只能记录光的强弱,无法记录光的颜色,所以只能拍摄黑白照片。 1974年,拜尔提出了bayer阵列,发明了bayer格式图片。不同于高成本的三个图像传感器方案,拜尔提出只用一个图像传感器,在其前面放…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...
RLHF vs RLVR:对齐学习中的两种强化方式详解
在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...