当前位置: 首页 > news >正文

WebGL系列教程十一(光照原理及Blinn Phong着色模型)

快速导航(持续更新中)

WebGL系列教程一(开篇)
WebGL系列教程二(环境搭建及着色器初始化)
WebGL系列教程三(使用缓冲区绘制三角形)
WebGL系列教程四(绘制彩色三角形)
WebGL系列教程五(使用索引绘制彩色立方体)
WebGL系列教程六(纹理映射与立方体贴图)
WebGL系列教程七(二维及三维旋转、平移、缩放)
WebGL系列教程八(GLSL着色器基础语法)
WebGL系列教程九(动画)
WebGL系列教程十(模型Model、视图View、投影Projection变换)
WebGL系列教程十一(光照原理及Blinn Phong着色模型)

目录

  • 快速导航(持续更新中)
  • 1 前言
  • 2 光源的分类
  • 3 光照的渲染
    • 3.1 高光
    • 3.2 漫反射
    • 3.3 环境光
  • 4 代码实现
    • 4.1 计算逆转置矩阵
    • 4.2 对法向量进行变换
    • 4.3 计算法向量和光线方向的点积
    • 4.4 计算漫反射分量
    • 4.5 计算环境光分量
    • 4.6 计算视线方向单位向量
    • 4.7 计算反射向量
    • 4.8 计算视线方向和反射方向的点积
    • 4.9 计算高光
    • 4.10 组合漫反射、环境光和高光
    • 4.11 完整代码
    • 4.12 效果
  • 5 总结

1 前言

  什么是光照?光照就是模拟出物体被光照射时的效果,使得渲染场景看起来更真实。那么WebGL在干什么?WebGL其实就是在计算继而还原每个像素的颜色和亮度。这就是我们这一节所要讲的内容,对一个立方体进行光照的渲染。

2 光源的分类

  我们常用的光源有点光源、面光源以及环境光。面光源是平行光,因为太阳离地球非常非常远,所以我们处理自然光时将太阳光也认为是面光源。常见的点光源有灯泡、火焰等,点光源照射在物体表面的角度是不一样的,因为会对着色有较大影响。环境光是指被墙壁等物体经过多次反射之后的光,环境光会从各个角度去照射物体,目前我们认为他们的强度都是一样的,且强度很小,因此通过一个微小的的值来代替(想要精确还原的同学可以自行学习光线追踪来进行模拟)。
在这里插入图片描述

3 光照的渲染

  在进行光照的渲染时,可以采用不同的方式,比如对面进行着色(Flat Shading),对顶点进行着色(Ground Shading),对像素进行着色(Phong Shading)。
三种着色方式对比
  本文中采用的是逐像素进行着色,即 Blinn Phong Shading,是在Phong Shading的基础上对高光项进行了改进。Blinn Phong Shading 对光照进行渲染时分成了三个部分,即高光、漫反射、环境光。
Blinn Phong着色模型

3.1 高光

在这里插入图片描述
  如图所示,v为观察方向,R为镜面反射方向,当Rv足够接近时,就会产生高光。这两向量的夹角可以通过单位向量点乘得到。因为这个角度不好计算,因此取入射方向加上出射方向结果的一半,即半程向量h和法线n的夹角来计算这个角度。在实际应用中,会对这个夹角的余弦值取m次方来满足需要。
在这里插入图片描述
在这里插入图片描述
  可以看出,次方m取值越大,高光的范围越小。

3.2 漫反射

  漫反射的反射光在各个方向上是均匀分布的,现实中的很多材质,比如纸张、岩石、塑料,表面都是粗糙的,在这种情况下,反射光将会以不固定的角度反射出去,漫反射正是在此基础上建立的反射模型。
在这里插入图片描述

3.3 环境光

  环境光下的反射称为环境反射,环境反射光的方向可以认为就是入射光的反方向,由于环境光照射物体的方式是各方向均匀的、强度相等的,所以反射光也是各项均匀的。

在这里插入图片描述

4 代码实现

  好了,讲完了理论,我们来进行一下实操。现在我们要对每个像素进行操作,因此先在片元着色器中进行声明:

// u_LightColor: 光源的颜色
uniform vec3 u_LightColor;// u_LightPosition: 光源的位置(世界坐标系中)
uniform vec3 u_LightPosition;// u_AmbientLight: 环境光的颜色
uniform vec3 u_AmbientLight;// u_ViewPosition: 观察者的位置(世界坐标系中)
uniform vec3 u_ViewPosition;// u_Shininess: 高光反射的光泽度系数
uniform float u_Shininess;// v_Normal: 从顶点着色器传递过来的法向量(世界坐标系中)
varying vec3 v_Normal;// v_Position: 从顶点着色器传递过来的顶点位置(世界坐标系中)
varying vec3 v_Position;// v_Color: 从顶点着色器传递过来的顶点颜色
varying vec4 v_Color;

4.1 计算逆转置矩阵

  首先我们要搞明白什么是逆转置矩阵?对一个矩阵先求逆矩阵,然后再求转置,就得到了逆转置矩阵。那么逆转置矩阵是干什么用的?我们之前讲过模型矩阵是对顶点进行变换的,逆转置矩阵是对法线进行变换的。当我们对模型进行了旋转、平移、缩放后,模型的法线也要跟随着变化,这时用逆转置矩阵就可以很方便的求出新的法线了。因此逆转置矩阵针对的是模型矩阵。

var modelMatrix = new Matrix4();  // 模型矩阵
// 计算用于变换法向量的矩阵,即逆转置矩阵
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();

4.2 对法向量进行变换

// u_NormalMatrix: 法向量变换矩阵,用于正确变换法向量
//glsl
uniform mat4 u_NormalMatrix;
// 对法向量进行变换并归一化
v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');  
// 将法向量变换矩阵传递给 u_NormalMatrix
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

4.3 计算法向量和光线方向的点积

// 计算从光源到片段的方向向量并进行归一化
vec3 lightDirection = normalize(u_LightPosition - v_Position);// 计算法向量和光线方向的点积(用于漫反射计算)
float nDotL = max(dot(lightDirection, normal), 0.0);

4.4 计算漫反射分量

// 计算漫反射分量
vec3 diffuse = u_LightColor * nDotL;

4.5 计算环境光分量

// 计算环境光分量
vec3 ambient = u_AmbientLight * v_Color.rgb;

4.6 计算视线方向单位向量

// 计算视线方向向量(从观察者到片段的位置)并进行归一化
vec3 viewDirection = normalize(u_ViewPosition - v_Position);

4.7 计算反射向量

// 计算反射方向向量vec3 reflectDirection = reflect(-lightDirection, normal);

4.8 计算视线方向和反射方向的点积

// 计算视线方向和反射方向的点积,并根据光泽度系数计算高光反射分量
float spec = pow(max(dot(viewDirection, reflectDirection), 0.0), u_Shininess);

4.9 计算高光

// 计算高光分量,使其接近白色
vec3 specular = vec3(1.0, 1.0, 1.0) * spec;

4.10 组合漫反射、环境光和高光

// 最终颜色由漫反射、环境光和高光三部分组成
gl_FragColor = vec4(diffuse + ambient + specular, v_Color.a);

4.11 完整代码

// 顶点着色器
<script id="vertex-shader" type="x-shader/x-vertex">// a_Position: 顶点位置// a_Color: 顶点颜色// a_Normal: 顶点法向量attribute vec4 a_Position;attribute vec4 a_Color;attribute vec4 a_Normal;// u_MvpMatrix: 模型视图投影矩阵,用于将顶点从模型坐标转换为裁剪坐标uniform mat4 u_MvpMatrix;// u_ModelMatrix: 模型矩阵,用于将顶点从局部坐标变换到世界坐标uniform mat4 u_ModelMatrix;// u_NormalMatrix: 法向量变换矩阵,用于正确变换法向量uniform mat4 u_NormalMatrix;// v_Color: 传递给片段着色器的顶点颜色varying vec4 v_Color;// v_Normal: 传递给片段着色器的法向量(经过变换后的世界坐标系中的法向量)varying vec3 v_Normal;// v_Position: 传递给片段着色器的顶点位置(世界坐标系中的位置)varying vec3 v_Position;void main() {// 计算顶点位置在裁剪坐标系中的位置gl_Position = u_MvpMatrix * a_Position;// 计算顶点在世界坐标系中的位置v_Position = vec3(u_ModelMatrix * a_Position);// 对法向量进行变换并归一化v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));// 将顶点颜色传递给片段着色器v_Color = a_Color;}
</script>
// 片段着色器
<script id="vertex-shader" type="x-shader/x-vertex">
#ifdef GL_ESprecision mediump float;  // 设置浮点数精度#endif// u_LightColor: 光源的颜色uniform vec3 u_LightColor;// u_LightPosition: 光源的位置(世界坐标系中)uniform vec3 u_LightPosition;// u_AmbientLight: 环境光的颜色uniform vec3 u_AmbientLight;// u_ViewPosition: 观察者的位置(世界坐标系中)uniform vec3 u_ViewPosition;// u_Shininess: 高光反射的光泽度系数uniform float u_Shininess;// v_Normal: 从顶点着色器传递过来的法向量(世界坐标系中)varying vec3 v_Normal;// v_Position: 从顶点着色器传递过来的顶点位置(世界坐标系中)varying vec3 v_Position;// v_Color: 从顶点着色器传递过来的顶点颜色varying vec4 v_Color;void main() {// 对法向量进行归一化处理vec3 normal = normalize(v_Normal);// 计算从光源到片段的方向向量并进行归一化vec3 lightDirection = normalize(u_LightPosition - v_Position);// 计算法向量和光线方向的点积(用于漫反射计算)float nDotL = max(dot(lightDirection, normal), 0.0);// 计算漫反射分量vec3 diffuse = u_LightColor * nDotL;// 计算环境光分量vec3 ambient = u_AmbientLight * v_Color.rgb;// 计算视线方向向量(从观察者到片段的位置)并进行归一化vec3 viewDirection = normalize(u_ViewPosition - v_Position);// 计算反射方向向量vec3 reflectDirection = reflect(-lightDirection, normal);// 计算视线方向和反射方向的点积,并根据光泽度系数计算高光反射分量float spec = pow(max(dot(viewDirection, reflectDirection), 0.0), u_Shininess);// 计算高光分量,使其接近白色vec3 specular = vec3(1.0, 1.0, 1.0) * spec;// 最终颜色由漫反射、环境光和高光三部分组成gl_FragColor = vec4(diffuse + ambient + specular, v_Color.a);}
</script>
function main() {// 获取 <canvas> 元素var canvas = document.getElementById('webgl');// 获取 WebGL 的渲染上下文var gl = getWebGLContext(canvas);if (!gl) {console.log('无法获取 WebGL 的渲染上下文');return;}// 初始化着色器if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log('无法初始化着色器');return;}// 初始化顶点缓冲区var n = initVertexBuffers(gl);if (n < 0) {console.log('无法设置顶点信息');return;}// 设置清除颜色并启用深度测试gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);// 获取 uniform 变量的存储位置var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');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_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');var u_ViewPosition = gl.getUniformLocation(gl.program, 'u_ViewPosition');var u_Shininess = gl.getUniformLocation(gl.program, 'u_Shininess');if (!u_ModelMatrix || !u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightPosition || !u_AmbientLight || !u_ViewPosition || !u_Shininess) { console.log('无法获取存储位置');return;}// 设置光的颜色(白色)gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);// 设置光源的位置(世界坐标系中)gl.uniform3f(u_LightPosition, 8.5, 4.0, 3.5);// 设置环境光gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);// 设置观察者的位置gl.uniform3f(u_ViewPosition, 6.0, 6.0, 14.0);// 设置光泽度系数gl.uniform1f(u_Shininess, 128.0);var modelMatrix = new Matrix4();  // 模型矩阵var mvpMatrix = new Matrix4();    // 模型视图投影矩阵var normalMatrix = new Matrix4(); // 法向量变换矩阵var currentAngle = 0.0;  // Current rotation anglefunction tick(){currentAngle = animate(currentAngle);  // Update the rotation angle// 计算模型矩阵modelMatrix.setRotate(currentAngle, 0, 1, 0); // 绕 y 轴旋转// 计算视图投影矩阵mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);mvpMatrix.multiply(modelMatrix);// 计算用于变换法向量的矩阵,即逆转置矩阵normalMatrix.setInverseOf(modelMatrix);normalMatrix.transpose();// 将模型矩阵传递给 u_ModelMatrixgl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);// 将模型视图投影矩阵传递给 u_MvpMatrixgl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);// 将法向量变换矩阵传递给 u_NormalMatrixgl.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);requestAnimationFrame(tick, canvas); // 开启动画,每一帧都去调用}tick();
}function initVertexBuffers(gl) {// 创建立方体//    v6----- v5//   /|      /|//  v1------v0|//  | |     | |//  | |v7---|-|v4//  |/      |///  v2------v3// 顶点坐标var vertices = new Float32Array([2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3 前面2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5 右面2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1 上面-2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2 左面-2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2 下面2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5 后面]);// 颜色var colors = 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 前面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-v3-v4-v5 右面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-v5-v6-v1 上面0.0, 0.0, 1.0,  0.0, 0.0, 1.0,  0.0, 0.0, 1.0,  0.0, 0.0, 1.0,     // v1-v6-v7-v2 左面0.0, 0.0, 1.0,  0.0, 0.0, 1.0,  0.0, 0.0, 1.0,  0.0, 0.0, 1.0,     // v7-v4-v3-v2 下面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 后面]);// 法向量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 前面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 右面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 上面-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 左面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 下面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 后面]);// 顶点的索引var indices = new Uint8Array([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     // 后面]);// 将顶点属性写入缓冲区(坐标、颜色和法向量)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();if (!indexBuffer) {console.log('无法创建缓冲区对象');return false;}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();if (!buffer) {console.log('无法创建缓冲区对象');return false;}// 将数据写入缓冲区对象gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);// 将缓冲区对象分配给 attribute 变量var a_attribute = gl.getAttribLocation(gl.program, attribute);if (a_attribute < 0) {console.log('无法获取 ' + attribute + ' 的存储位置');return false;}gl.vertexAttribPointer(a_attribute, num, gl.FLOAT, false, 0, 0);// 启用缓冲区对象分配gl.enableVertexAttribArray(a_attribute);return true;
}
// Rotation angle (degrees/second)
var ANGLE_STEP = 30.0;
// Last time that this function was called
var g_last = Date.now();
function animate(angle) {// Calculate the elapsed timevar now = Date.now();var elapsed = now - g_last;g_last = now;// Update the current rotation angle (adjusted by the elapsed time)var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;return newAngle %= 360;
}

4.12 效果

在这里插入图片描述
看下动画效果
在这里插入图片描述

5 总结

  本文介绍了光照原理的基础,并在此基础上讲解了法线计算、入射光计算、反射光计算、逆转置矩阵等等,最后我们使用Blinn Phong 着色模型,通过组合高光、漫反射和环境光,实现了对一个立方体的动态光照效果渲染。本文在理解上有一定的难度,希望读者仔细体会,回见~

相关文章:

WebGL系列教程十一(光照原理及Blinn Phong着色模型)

快速导航&#xff08;持续更新中&#xff09; WebGL系列教程一&#xff08;开篇&#xff09; WebGL系列教程二&#xff08;环境搭建及着色器初始化&#xff09; WebGL系列教程三&#xff08;使用缓冲区绘制三角形&#xff09; WebGL系列教程四&#xff08;绘制彩色三角形&…...

《ASP.NET Web Forms 实现短视频点赞功能的完整示例》

在现代Web开发中&#xff0c;实现一个动态的点赞功能是非常常见的需求。本文将详细介绍如何在ASP.NET Web Forms中实现一个视频点赞功能&#xff0c;包括前端页面的展示和后端的处理逻辑。我们将确保点赞数量能够实时更新&#xff0c;而无需刷新整个页面。 技术栈 ASP.NET We…...

Linux SSH服务

Linux SSH&#xff08;Secure Shell&#xff09;服务是一种安全的远程登录协议&#xff0c;用于在Linux操作系统上远程登录和执行命令。它提供了加密的通信通道&#xff0c;可以在不安全的网络环境中安全地进行远程访问。 SSH服务在Linux系统中通常使用OpenSSH软件包来实现。它…...

MySQL--视图(详解)

目录 一、前言二、视图2.1概念2.2语法2.3创建视图2.3.1目的 2.4查看视图2.5修改数据2.5.1通过真实表修改数据&#xff0c;会影响视图2.5.2通过修改视图&#xff0c;会影响基表 2.6注意2.7 删除视图2.8 视图的优点 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导&…...

Javascript 普通非async函数调用async函数

假设我们有一个异步函数 async function asyncFunction() {console.log("开始执行异步函数");await new Promise(resolve > setTimeout(resolve, 1000)); // 模拟异步操作console.log("异步函数执行完毕"); } 我们在调用这个异步函数时&#xff0c;比…...

【LeetCode】修炼之路-0004-Median of Two Sorted Arrays【python】

题目 Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays. The overall run time complexity should be O(log (mn)). Example 1: Input: nums1 [1,3], nums2 [2] Output: 2.00000 Explanation: merged…...

C++面试速通宝典——10

177. #include <filename> 和 #include "filname.h" 有什么区别&#xff1f; ‌‌‌‌  对于 #include <filename> &#xff0c; 编译器从标准库路径开始搜索 filename.h。 ‌‌‌‌  对于 #include "filename.h&#xff0c;编译器从用户的工作…...

肺腺癌预后新指标:全切片图像中三级淋巴结构密度的自动化量化|文献精析·24-10-09

小罗碎碎念 本期这篇文章&#xff0c;我去年分享过一次。当时发表在知乎上&#xff0c;没有标记参考文献&#xff0c;配图的清晰度也不够&#xff0c;并且分析的还不透彻&#xff0c;所以趁着国庆假期重新分析一下。 这篇文章的标题为《Computerized tertiary lymphoid structu…...

基于jmeter+perfmon的稳定性测试记录

1. 引子 最近承接了项目中一些性能测试的任务&#xff0c;因此决定记录一下&#xff0c;将测试的过程和一些心得收录下来。 说起来性能测试算是软件测试行业内&#xff0c;有些特殊的部分。这部分的测试活动&#xff0c;与传统的测试任务差别是比较大的&#xff0c;也比较依赖…...

前沿论文 M5Product 组会 PPT

对比学习&#xff08;Contrast learning&#xff09;&#xff1a;对比学习是一种自监督学习方法&#xff0c;用于在没有标签的情况下&#xff0c;通过让模型学习哪些数据点相似或不同来学习数据集的一般特征。假设一个试图理解世界的新生婴儿。在家里&#xff0c;假设有两只猫和…...

navicat~导出数据库密码

当我们mysql密码忘记了&#xff0c;而在navicat里有记录&#xff0c;我们应该如何导出这个密码呢&#xff1f; 第一步:文件菜单&#xff0c;导出链接&#xff0c;导出连接获取到 connections.ncx 文件 这里需要勾选 导出密码&#xff01;&#xff01;&#xff01; 不然导出的文…...

【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析

目录 7.2.4 Vector部分源码分析 7.3 链表LinkedList 7.3.1 链表与动态数组的区别 7.3.2 LinkedList源码分析 启示与开发建议 7.2.4 Vector部分源码分析 jdk1.8.0_271中&#xff1a; //属性 protected Object[] elementData; protected int elementCount;//构造器 public …...

YOLOv5改进——添加SimAM注意力机制

目录 一、SimAM注意力机制核心代码 二、修改common.py 三、修改yolo.py ​三、建立yaml文件 四、验证 一、SimAM注意力机制核心代码 在models文件夹下新建modules文件夹&#xff0c;在modules文件夹下新建一个py文件。这里为simam.py。复制以下代码到文件里面。 import…...

SQL 自学:表别名的运用与对被联结表使用聚集函数

一、表别名的概念与作用 &#xff08;一&#xff09;表别名的定义 表别名是为表指定的临时名称&#xff0c;在 SQL 查询中使用别名可以简化表名&#xff0c;提高代码的可读性和可维护性。当表名较长或在复杂的查询中多次引用表时&#xff0c;使用表别名可以避免重复输入冗长的…...

jmeter学习(2)变量

1&#xff09;用户定义的变量 路径&#xff1a;添加-》配置元件-》用户定义的变量 用户定义的变量是全局变量&#xff0c;可以跨线程组被调用&#xff0c;但在启动运行时获取一次值&#xff0c;在运行过程中不再动态获取值。 注意的是&#xff0c;如果在某个线程组定义了全…...

【C#生态园】C#文件压缩库全面比较:选择最适合你的库

从核心功能到API概览&#xff1a;深度解析六大C#文件压缩库 前言 在软件开发过程中&#xff0c;文件的压缩和解压缩是一个常见的需求。针对C#开发者而言&#xff0c;选择合适的文件压缩库可以极大地简化开发工作。本文将介绍几个常用的C#文件压缩库&#xff0c;包括其核心功能…...

【测试】接口测试与接口自动化

壹、接口测试基础 一、接口测试概念 I、基础概念 是测试系统组件间接口的一种测试。 主要用于检测外部系统与系统间、内部子系统间的交互点&#xff1b;测试重点检查数据的交换、传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系。 内部接口调用相当于函数调用&am…...

Android设置边框圆角

在Android开发中&#xff0c;圆角设计十分常见&#xff0c;那么实现边框圆角有几种形式呢&#xff1f; 文章目录 设置圆角边框样式使用ClipToOutline进行裁切最后 设置圆角边框样式 常见的方式是在drawable文件夹下设置一个xml文件的边框样式&#xff0c;比如 <shape andro…...

SpringBoot项目打成jar包,在其他项目中引用

1、首先新建一个SpringBoot工程 记得要将Gradle换成Maven 2、新建一个要引用的方法 3、打包的时候要注意&#xff1a; ① 不能使用springboot项目自带的打包插件进行打包&#xff0c;下面是自带的&#xff1a; ②要换成传统项目的maven打包&#xff0c;如下图&#xff1a; 依…...

【音频可视化】通过canvas绘制音频波形图

前言 这两天写项目刚好遇到Ai对话相关的需求&#xff0c;需要录音功能&#xff0c;绘制录制波形图&#xff0c;写了一个函数用canvas实现可视化&#xff0c;保留分享一下&#xff0c;有需要的直接粘贴即可&#xff0c;使用时传入一个1024长的&#xff0c;0-255大小的Uint8Arra…...

解决github每次pull push输入密码问题

# 解决git pull/push每次都需要输入密码问题 git bash进入你的项目目录&#xff0c;输入&#xff1a; git config --global credential.helper store然后你会在你本地生成一个文本&#xff0c;上边记录你的账号和密码。配置项写入到 "C:\Users\用户名\ .gitconfig" …...

Java重修笔记 第六十四天 坦克大战(十四)IO 流 - 标准输入输出流、InputStreamReader 和 OutputStreamWriter

标准输入输出流 1. System.in 标准输入流 本质上是一个InputString&#xff0c;对应键盘&#xff0c;表示从键盘输入。 定义&#xff1a;public final static InputStream in null; 所以 Scanner scanner new Scanner(System.in); 会从键盘中获取数据 2. System.out 标准输…...

prctl的函数和pthread_self函数

1.prctl的函数原型如下&#xff1a; #include<sys/prctl.h> ​prctl(PR_SET_NAME, “process_name”);第一个参数是操作类型&#xff0c;指定PR_SET_NAME&#xff08;对应数字15&#xff09;&#xff0c;即设置进程名&#xff1b; 第二个参数是进程名字符串&#xff0c;…...

Vim 命令行模式下的常用命令

Vim 命令行模式下的常用命令 文件操作&#xff1a; :w &#xff1a;保存当前文件。:w filename &#xff1a;将当前内容另存为指定的 filename 。:q &#xff1a;退出 Vim&#xff0c;如果文件有修改但未保存&#xff0c;会提示错误。:q! &#xff1a;强制退出 Vim&#xff0c…...

【动态规划-最长递增子序列(LIS)】力扣2826. 将三个组排序

给你一个整数数组 nums 。nums 的每个元素是 1&#xff0c;2 或 3。在每次操作中&#xff0c;你可以删除 nums 中的一个元素。返回使 nums 成为 非递减 顺序所需操作数的 最小值。 示例 1&#xff1a; 输入&#xff1a;nums [2,1,3,2,1] 输出&#xff1a;3 解释&#xff1a; …...

Elastic Stack--16--ES三种分页策略

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 方式一&#xff1a;from size实现原理使用方式优缺点 方式二&#xff1a;scroll实现原理使用方式优缺点 方式三&#xff1a;search_after实现原理使用方式优缺点 三…...

[LeetCode] 315. 计算右侧小于当前元素的个数

题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;按要求返回一个新数组 counts 。数组 counts 有该性质&#xff1a; counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。 题目链接&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 题目主要思路&a…...

【hot100-java】二叉树展开为链表

二叉树篇。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …...

如何在在 YOLOv3模型中添加Attention机制

在YOLOv3模型中添加Attention机制需要以下几个步骤&#xff1a; 1. 规定格式 当添加新的模块&#xff08;如Attention机制模块&#xff09;时&#xff0c;需要像定义[convolutional]、[maxpool]等层在cfg文件中的格式一样&#xff0c;对新模块进行格式规定。例如对于SE模块&a…...

单点登录Apereo CAS 7.1安装配置教程

笔者目前正在做一个单点登录的课题,历时较长总算摸到一些门路,其中的辛酸不易按下不表。截至本文发布,CAS的最新版本为7.1。由于涉及到课题内容,而且内容比较新,整理试验不容易,暂时只对VIP开放,后续课题完成后会完全开放,敬请谅解。 CAS项目区别 在CAS的项目选择上,…...