GAMES202作业1

news2024/12/25 9:15:33

目录

  • Shadow Map
    • CalcLightMVP函数
    • useShadowMap函数
      • Bias函数
    • 最终效果
  • PCF
    • 两个采样函数
    • PCF函数
    • 最终效果
  • PCSS
    • findBlocker函数
    • PCSS函数
    • 最终效果
  • 参考

先放上公式:
在这里插入图片描述
后面的积分项是我们在作业0中就做好的blinnphong项,我们要求的就是积分项前,等号后的可见项。
最终体现在代码中便是

  • gl_FragColor = vec4(visibility * phongColor, 1.0);

这部分代码在homework1\src\shaders\phongShader\phongFragment.glsl中

Shadow Map

第一部分是使用Shadow Map的方法来渲染阴影,也就是渲染硬阴影,经典的Two Pass Shadow Map方法。
在这里插入图片描述
在这里插入图片描述
简单来说,shadow map主要分为两步操作:

  • 第一部分,我们需要将摄像机移动到光源的位置,从光源视角出发看向场景。视线能看到的各最小深度就是光源能直接照射到的物体深度,获得这个视角下的场景深度图。(记录场景中距离光源最近的每一点,形成一张图)
    在这里插入图片描述
  • 第二部分,绘制时需要将相机挪到眼睛的位置再观测,得到视线与物体交点,再将该点的深度,如下图中深黄色所示,与第一趟绘制得到的该点的深度比较,如果此次得到的深度更深(或者说值更大),则判断为阴影,如果相比更浅(或者说值更小或相等),则判断为非阴影可直接着色。
    在这里插入图片描述

先看src\renderers\WebGLRenderer.js,两次Pass计算阴影的主要代码:
在这里插入图片描述


CalcLightMVP函数

首先是src/lights/DirectionalLight.js的矩阵变换部分,该矩阵参与了第一步从光源处渲染场景从而构造 ShadowMap 的过程。

CalcLightMVP(translate, scale) {
        let lightMVP = mat4.create();
        let modelMatrix = mat4.create();
        let viewMatrix = mat4.create();
        let projectionMatrix = mat4.create();

        // Model transform 模型矩阵,对相机先平移,再缩放
        mat4.translate(modelMatrix,modelMatrix,translate);
        mat4.scale(modelMatrix,modelMatrix,scale);
 
        // View transform 视图矩阵
        mat4.lookAt(viewMatrix,this.lightPos,this.focalPoint,this.lightUp);
    
        // Projection transform 投影矩阵
        mat4.ortho(projectionMatrix,-100,100,-100,100,1e-2,400);

        mat4.multiply(lightMVP, projectionMatrix, viewMatrix);
        mat4.multiply(lightMVP, lightMVP, modelMatrix);

        return lightMVP;
    }
  • 因为我们需要获取从光源处看世界的坐标。所以在直射光中需要补充一个获得转换到光源处空间的矩阵。我们可以调用API来快速生成translate和scale矩阵。
  • 再调用lookAt函数得到viewMatrix矩阵。关于lookAt()函数可以参考:learnopengl教程
  • 最后使用projectionMatrix正交投影矩阵得到Shadow Map。关于正交矩阵和透视矩阵的选取:投影矩阵,由于透视投影会产生深度精度问题,因此作业中选择正交投影。

完成该矩阵的输出后,我们就可以在片元着色器中获取到Shadow Map:
在这里插入图片描述

useShadowMap函数

src\shaders\phongShader\phongFragment.glsl
在这里插入图片描述

  • shadowCoord —— 纹理图片上像素对应的坐标
  • 在着色时我们使用了可见项对blinnPhong得到的颜色进行一个可见性衰减。
  • 我们在useShadowMap函数中可以看到我们需要使用到当前着色片元在光源坐标系下的坐标shadowCoord,这个坐标是在片元着色器前插值生成的。为了在贴图采样中使用该坐标,我们需要将向量的各个分量从( -1 , 1 ),强制转化到( 0 , 1 )(uv坐标的范围都是0-1)。

在代码里发现是调用了useShadowMap方法,

float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
  float mapDepth = unpack(texture2D(shadowMap,shadowCoord.xy));//shadow map中各点的最小深度,unpack将RGBA值转换成[0,1]的float
  float shadingDepth = shadowCoord.z; //当前着色点的深度
  float visibility1 = ((mapDepth + EPS) < shadingDepth) ? 0.0 : 1.0;  
  return visibility1;
}
  • 先获取第一步已经获取到的shadow map中的深度。unpack()用来将RGBA值转换成在范围[0,1]的float值。
  • 再获取第二次从相机出发观察到的点离光源的深度。
  • 然后进行比较,EPS考虑精度问题。
  • 返回visibility。

效果:
在这里插入图片描述

Bias函数

放大之后会发现会有很多锯齿,因为发生了自遮挡现象:

  • 其一是处理器的数值精度的限制

  • 还有一个原因是因为shadow map本身保存的值是离散值,也就是说shadow map上每个采样点都代表着一块范围内图元的深度值,因此在第二个pass比较深度的时候,shadow map中的深度可能会略低于物体表面的深度,部分片元就会被误计算为阴影,导致自遮挡。该现象在光源与平面趋于平行时(掠射)尤为严重。
    在这里插入图片描述
    在这里插入图片描述

  • 为了解决这个问题,课上也说了,使用bias(偏移值)方法。

  • 在shadow map中引入一个偏移值(bias),使得每次在比较深度大小的时候,都将一定区间内的shadow map深度认作与屏幕空间深度相等,强行减弱阴影判定。

  • 但这样做又会引入一个新的问题——detached shadow,或者说,peter panning(阴影悬浮)——即丢失部分原本可能发生遮挡的阴影
    在这里插入图片描述
    在这里插入图片描述

那来看一下怎么做这个bias,还是在src\shaders\phongShader\phongFragment.glsl中直接添加一个Bias方法:
在这里插入图片描述

放两个,其实没多大区别,差别在于这个阴影的出现程度,第二个的阴影区域会比第一个小:

float Bias(float CDepth){
  vec3 lightDir1 = normalize(uLightPos);
  vec3 normal1 = normalize(vNormal);
  float m = 200.0 / 2048.0 / 2.0; // 正交矩阵宽高/shadowmap分辨率/2
  float bias1 = max(m * (1.0-dot(normal1,lightDir1)),m) * CDepth;
  return bias1;
}

float Bias1(){
  vec3 lightDir1 = normalize(uLightPos);
  vec3 normal1 = normalize(vNormal);
  float bias1 = max(0.08 * (1.0-dot(normal1,lightDir1)),0.08);
  return bias1;
}

然后修改useShadowMap方法:

float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
  float mapDepth = unpack(texture2D(shadowMap,shadowCoord.xy));//shadow map中各点的最小深度,unpack将RGBA值转换成[0,1]的float
  float shadingDepth = shadowCoord.z; //当前着色点的深度
  //float visibility1 = ((mapDepth + EPS) < shadingDepth) ? 0.0 : 1.0;
  float bias = Bias(1.4);
  float visibility1 = ((mapDepth + EPS) <= (shadingDepth - bias)) ? 0.2 : 0.9;
  return visibility1;
}

最终效果

采用Bias方法后的效果:
在这里插入图片描述

放大之后,虽然不会产生严重的锯齿现象,但还是有锯齿:
在这里插入图片描述
但很明显,腿部的阴影没有了,因为发生了我们上面说的阴影悬浮现象,这就可以通过修改bias方法来改善,但不可避免。


完成了硬阴影的two pass shadow map方法后,在实际生活中我们更希望我们得到的是软阴影,接下来就是写软阴影部分的代码,软阴影又分为PCF和PCSS两种。

PCF

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我们通过SM生成了一个硬阴影,但是在实际生活中我们希望我们得到的是软阴影,而在我们刚才的硬阴影的计算中我们得到的visibility项非0即1.如果我们着色点周围的一圈像素进行一个加权平均,我们就可以得到一个相对来说较软的阴影——visibility项不再是非0即1。

请注意,这个过程发生在采样过程中:

    1. 滤波对象既不是Shadow Map自身(对shadow map滤完波再做深度测试,结果仍是二值化数据,相当于什么都没做);
    1. 也不是深度测试结束后得到的阴影图(非但不会消除锯齿,还会让阴影变糊,在101中有提到过);
    1. 而是找我们选定的点对应于shadow map中周围一圈邻域的点,将领域中的每个点与我们选定的点进行比较,进行一个二值化处理。比较完成之后,对这一圈邻域内的二值化数据进行求和平均(也就是filter,其实叫filter不够准确,就是做一个求和平均罢了)。

两个采样函数

作业中建议用泊松圆盘采样和均匀圆盘采样,代码里都给出来了:

  • 泊松圆盘
    在这里插入图片描述
    在这里插入图片描述

  • 均匀圆盘
    在这里插入图片描述
    在这里插入图片描述

实话说没看懂哈,会用就行了,要是有小伙伴感兴趣的可以看下面的链接:

  • 三维点云泊松圆盘采样(Poisson-Disk Sampling)
  • 泊松盘采样(Poisson Disk Sampling)生成均匀随机点
  • 需要注意的是,两个采样方法都把数据存储到了下面这个数组中:
    在这里插入图片描述
    所以不管我们用哪个方法,都可以直接使用这个数组来取数据。

PCF函数

课上老师也提到过,PCF其实是基于shadow map做AA(Anti-Aliasing,即反走样)。PCF就是在做卷积,把卷积核也叫做过滤器,也就是filter。

卷积原理看这个:卷积 (Convolution) 填充 (Padding) 步长 (Stride)

在进行shading point的深度与shadowmap比较时,不只比较一个方向的值,而是与周围像素做卷积,在周围采样多个点的深度值,逐一比较之后求平均值,就能得到一个[0,1]的连续分布,可以表示不同明暗程度的阴影,不再是硬阴影那样非0即1对比强烈的感觉,阴影就变得柔和起来,也就实现了人工软阴影化。

在这里插入图片描述

  • filter size 卷积核大小
    • 作业1中filter的大小由采样数量决定,也就是一开始给定的NUM_SAMPLES,初始值设置成了20。
    • filter的大小、个数一般都是先设定一个初始值,再根据实验效果进行调整。
    • 在进行卷积时,在输出要求相同的情况下,filter越大参与计算的参数越多,那么对于作业1来说达到的阴影柔和的效果越明显。
float PCF(sampler2D shadowMap, vec4 coords) {
  float stride = 2.0;           //定义步长
  float shadowMapSize = 2048.0;  //shadowmap分辨率
  float visibility1 = 0.0;        //初始可见项
  float cur_depth = coords.z;    //卷积范围内当前点的深度
  float filterRange = stride / shadowMapSize; //滤波窗口的范围

  //泊松圆盘采样得到采样点
  poissonDiskSamples(coords.xy);

  //均匀圆盘采样得到采样点
  //uniformDiskSamples(coords.xy);

  //对每个点进行比较深度值并累加
  for(int i = 0; i < NUM_SAMPLES; i++){
    float shadow_depth = unpack(texture2D(shadowMap,coords.xy + poissonDisk[i] * filterRange));
    float res = (cur_depth < shadow_depth + EPS) ? 1.0 : 0.0;
    visibility1 += res;
  }

  //返回均值
  float avgVisibility = visibility1 / float(NUM_SAMPLES);
  return avgVisibility;
}
  • 采样偏移值 -> 与步长Sride关系
    • 在卷积过程中,将每次卷积核滑动的行数/列数称为Stride(步长)。有时需要在卷积时通过设置的Stride来压缩一部分信息,成倍缩小尺寸。
    • 对于作业1而言,由于PCF输入的坐标coords归一到了[0,1]的范围,那么给定采样点的偏移值poissonDiskSamples[i]也需要缩小一定范围以迎合coords坐标的尺寸,因此需要给定Stride以缩小尺寸。缩小比例当然是stride/shaodowMapSize,框架中shadowMapSize=2048,Stride可以给定一个初始值1,根据效果进行调整。

额,最后别忘了在main函数里改一下:
在这里插入图片描述

最终效果

用泊松圆盘采样的结果:
NUM_SAMPLES=80,stride=10:
在这里插入图片描述

要是更改一下参数:NUM_SAMPLES=80,stride=2:
在这里插入图片描述
用均匀圆盘采样的结果:
NUM_SAMPLES=80,stride=2:
在这里插入图片描述

NUM_SAMPLES=80,stride=2:
在这里插入图片描述

  • 可以发现NUM_SAMPLES越小或者stride越大 阴影边缘噪点越多,有兴趣可以自己试试调整这两个参数值。

PCSS

然后就是最后的PCSS方法了。为了达到前实后虚的软阴影效果,就可以采用PCSS(Percentage Closer Soft Shadow),通过计算投影平面与遮挡物之间的距离,来确定滤波范围的大小(自适应的filter size)。

算法的整体思路是:

  • 首先将shading point点x投应到shadow map上,找到其对应的像素点p。
  • 在点p附近取一个范围(这个范围是自己定义或动态计算的),将范围内各像素的最小深度与x的实际深度比较,从而判断哪些像素是遮挡物,把所有遮挡物的深度记下来取个平均值作为blocker distance。(Blocker search)
  • 第二步:用取得的遮挡物深度距离来算在PCF中filtering的范围。
  • 第三步:进行pcf操作。

这里有个问题,filter size可以按上述方法确定了,那么计算filter size时需要用到的d(blocker)同样需要在一定范围内做平均,这个范围又怎么确定呢?我们可以认为规定一个固定的大小,如4 * 4,16 * 16等,但这么做绝对不是最优解,更好的方法是在光源处设置一个视锥,将shadow map置于近平面上,接着连接着色点和光源,以其在shadow map上所截得的范围作为样本,来计算平均深度。

这么做有一个非常大的好处,就是计算d(blocker)也采用了自适应的方法,离光源越远,遮挡物越多,计算blocker所用的样本范围就越小;而离光源越近,遮挡物越少,计算blocker所用的样本空间就越大,非常合理。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


在这里插入图片描述

  • W(Light)是光源的大小。
  • W(Penumbra)是filter的大小,某种程度算是阴影软硬程度的表现,该值越大,阴影就越软。
  • d(Receiver)是阴影接受物的深度。
  • d(Blocker)是遮挡物,也就是阴影投射物的深度。
    • 遮挡物Blocker越接近接受物Receiver,W(Penumbra)越小,阴影越硬;
    • 遮挡物Blocker越接近光源Light,W(Penumbra)越大,阴影越软;

由相似三角形就能得到:
在这里插入图片描述

所以PCSS的具体步骤为:

  • 首先依据着色点选择一块范围,对Shadow Map做一次局部深度测试,找到范围内的blocker并计算其平均深度(计算平均深度的目的是减小遮挡物自身的几何影响,避免漏光)。
  • 得到d(blocker)后代入公式计算滤波范围, d(blocker)越小, [d(receiver)-d(blocker)] 越大,卷积核越大,得到的阴影就越软。
  • 重新进行深度测试,继续完成PCF的过程。

那作业需要我们完成findBlocker(sampler2D shadowMap,vec2 uv, float zReceiver) 和 PCSS(sampler2D shadowMap, vec4 shadowCoord)函数。

findBlocker函数

看一下findBlocker函数,需要完成对遮挡物平均深度的计算,也就是上面的d(Blocker)。

  • 首先给定基数BlockerNum和总的Block_depth。
  • 从当前的shading point连向方向光源light,方向上击中位于shadow map上的一点P,取点P周围的一个区域(利用到了泊松圆盘采样),判断区域里的点是否在阴影里,如果在则BlockerNum加一、Block_depth加上cur_depth;如果不在,则不纳入计算。
    以发现这个判断过程跟前面的shadowmap和PCF都是一样的,但是目的不同,这里是在求blocker的深度!
float findBlocker( sampler2D shadowMap,  vec2 uv, float zReceiver ) {
  int blockerNum = 0;           //着色点对应到shadow map中后,周围一圈邻域内为blocker的点的数量
  float blocker_depth = 0.0;    //blocker的深度
  float shadowMapSize = 2048.0; //shadow map的分辨率
  float stride = 50.0;          //采样的步长
  float filterRange = stride / shadowMapSize; //滤波窗口的范围

  //泊松圆盘采样得到采样点
  poissonDiskSamples(uv);

  //均匀圆盘采样得到采样点
  //uniformDiskSamples(uv);

  //判断着色点对应到shadow map中后,邻域中的点是否为blocker,如果是就累加
  for(int i = 0; i < NUM_SAMPLES; i++){
    float shadow_depth = unpack(texture2D(shadowMap,uv+ poissonDisk[i] * filterRange)); 
    if(zReceiver > shadow_depth + 0.01){
      blockerNum++;
      blocker_depth += shadow_depth;
    }
  }

  if(blockerNum == 0){
    return 1.0;
  }

  blocker_depth = blocker_depth / float(blockerNum);
	return blocker_depth;
}

PCSS函数

float PCSS(sampler2D shadowMap, vec4 coords){
  // STEP 1: avgblocker depth
  float avgBlocker_depth = findBlocker(shadowMap,coords.xy,coords.z);   //在这步里我们已经做好了采样,后面就能直接调用数据
  float wLight = 1.0; //光源大小
  float dReceiver = coords.z;

  // STEP 2: penumbra size
  float wPenumbra = wLight * (dReceiver - avgBlocker_depth) / avgBlocker_depth;

  // STEP 3: filtering 就是做PCF,不过加入了wPenumra的影响
  //首先定义变量
  float stride = 10.0;
  float shadowMapSize = 2048.0;
  float visibility1 = 0.0;
  float cur_depth = coords.z;
  float filterRange = stride / shadowMapSize;

  //做采样,前面已经做好了
  //poissonDiskSamples(coords.xy);

  //然后循环比较
  for(int i = 0; i < NUM_SAMPLES; i++){
    float shadow_depth = unpack(texture2D(shadowMap,coords.xy + poissonDisk[i] * filterRange * wPenumbra));
    float res = cur_depth < shadow_depth+0.01 ? 1.0 : 0.0;
    visibility1 += res;
  }

  //求平均
  visibility1 /= float(NUM_SAMPLES);
  
  return visibility1;
}

最终效果

最后main函数改一下记得,结果图:
下面这个是NUM_SAMPLES=80
在这里插入图片描述
这个是让cur_depth < shadow_depth+EPS,NUM_SAMPLES=20的结果:
在这里插入图片描述
在这里插入图片描述
也可以加bias,但是得调…

参考

  • GAMES202作业1-实现过程详细步骤
  • GAMES202 作业1解答
  • GAMES202作业1-万字分析代码框架
  • GAMES202高质量实时渲染-个人笔记:实时阴影
  • GAMES202 Real-Time High Quality Rendrting 高质量实时渲染课程笔记Lecture 4: Shadow 02
  • GAMES202-高质量实时渲染

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

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

相关文章

认识 Protobuf 及其简单使用

文章目录 一、序列化与反序列化1.1 序列化1.2 反序列化1.3 序列化与反序列化的使用场景 二、初识 Protobuf三、Protobuf 的安装四、Protobuf 的使用案例4.1 创建并编写 .proto 文件的基本规范与语法4.2 编译 .proto 文件4.3 序列化与反序列化的使用 五、总结 ProtoBuf 的使用特…

spring boot日志

日志介绍日志的使用日志级别日志持久化更简单的输入日志lombok的运行原理 日志介绍 日志的作用&#xff1a; 1&#xff1a;发现问题&#xff1b; 2&#xff1a;定位问题&#xff1b; 3&#xff1a;记录用户的行为&#xff1a;看哪些是方法用户&#xff1b;还能拿到用户的ip&am…

【云原生|探索 Kubernetes 系列 4】理解现代云原生时代的引擎

文章目录 系列文章目录&#x1f479; 关于作者一、前言|回顾二、静态和动态视图三、爆火的容器编排工具 Kubernetes 的诞生四、Kubernetes 要解决的问题是什么&#xff1f;五、理解 Kubernetes 全局架构图Master&#xff08;控制节点&#xff09;Node&#xff08;计算节点&…

源码分析:springboot如何确定当前应用程序类型

文章目录 一、介绍二、源码分析三、测试 一、介绍 大多数java后端开发的朋友们想必都是通过创建springboot项目&#xff0c;然后通过编写Controller进行接口开发的&#xff0c;该接口底层是由非响应式的servlet提供支持的&#xff0c;其接口内部逻辑为阻塞式的。但也有一部分朋…

leetcode 837. New 21 Game(新21点)

起始点数为0&#xff0c;当手上点数 < k 时抽取新的卡片&#xff0c; 每次抽取的点数范围在 1 ~ maxPts. 每次收取是独立的&#xff0c;每个点数概率相同。 当手上点数 > k 时游戏结束。 返回手上点数 < n 的概率。 思路&#xff1a; 先看特殊情况&#xff0c; k …

JQuery实现小项目

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 一、JQuery是什么 二、JQuery项目 2.1 猜数字 2.2 表白墙 2.3 聚合搜索 2.4 计算器 一、JQuery是什么 jQuery是一个快速、简洁的JavaScript框架&#xff0c;是继Prototype之…

MQTT(1):MQTT协议介绍

随着 5G 时代的来临&#xff0c;万物物联的伟大构想正在成为现实。联网的物联网设备在 2018 年已经达到了 70 亿&#xff0c;在未来两年&#xff0c;仅智能水电气表就将超过10亿 海量的设备接入和设备管理对网络带宽、通信协议以及平台服务架构都带来了很大挑战。对于物联网协议…

pandas 遇到Key Error错误的一个小问题

最近刚刚接触Python&#xff0c;安装了Anaconda&#xff0c; 编程小白一个&#xff0c;照着教程准备做一个中考成绩录取分数线分析的案例&#xff0c; 使用read_excel()读入数据后&#xff0c; import pandas as pd data pd.read_excel(rC:\2021-2022深圳中考录取分数线(1).xl…

SpringBoot配置文件 | 多环境配置 | 读取配置的4种方式

文章目录 一、写配置文件的位置读取的优先级&#xff1a;1.文件位置&#xff1a;2.文件名和文件后缀&#xff1a;3.配置文件中的profile-specific文件&#xff1a;4.命令行参数 二、多环境配置1. properties&#xff1a;2. yaml 三、yaml配置文件yaml、properties、xml对比&…

【软考-中级】系统集成项目管理工程师 【13合同管理】

持续更新。。。。。。。。。。。。。。。 【第十三章】合同管理 2 分 考点 1考点 2考点 3考点4:成本补偿合同考点5:工料合同考点6:合同类型的选择考点 7考点 8:合同管理包括考点9考点 10考点 11考点 12考点 13考点 14考点 15历年真题2022 年 05 月2021 年 11 月2021 年 05 月 考…

Redis底层原理深入学习

一、基本类型及底层实现 1.String 1&#xff09;使用场景&#xff1a;简单字符串存储、分布式锁、计数器、全局唯一ID 2&#xff09;数据结构&#xff1a;C语言中String用char[]表示&#xff0c;源码中用SDS封装char[]&#xff0c;这是Redis存储的最小单元&#xff0c;一个SD…

安全中级3-nginx反向代理负载均衡的webshell

目录 一、负载均衡 1.nginx的负载均衡 2.nginx 支持的几种策略&#xff1a; 二、负载均衡下的webshell连接&#xff08;负载均衡下的wenbshell环境下载地址&#xff09; 1.内部网络的结构 2.场景描述 3.利用我们的中国蚁剑连接我们的代理服务器nginx 三、webshell遇到的…

电脑待机或者睡眠后TeamViewer就无法连接了

电脑待机或者睡眠后TeamViewer就无法连接了 设置睡眠状态下不关闭网卡驱动 公司的笔记本&#xff0c;安装了teamviewer&#xff0c;离开时把teamviewer打开&#xff0c;回家后连接时提示伙伴未在机器上运行&#xff0c;此时电脑处于黑屏、待机、睡眠状态 其实电脑睡眠后会关掉网…

创新管理工具:低代码平台在学校管理中的应用实践

随着信息技术的不断发展&#xff0c;学校管理也随之发生了变革。传统的学校管理方式往往是依靠人工操作&#xff0c;存在信息不透明、效率低下等问题&#xff0c;而数字化管理的出现&#xff0c;可以帮助学校提高管理效率、降低管理成本、提升数据统计和分析能力。而低代码技术…

SWAT模型教程

详情点击链接&#xff1a;SWAT模型教程详情点击链接&#xff1a;SWAT模型&#xff08;建模方法、实例应用、高级进阶&#xff09; 一&#xff1a;基于网络资源的SWAT模型快速建模​ 二&#xff1a;基于遥感产品的SWAT模型率定与验证​ 三&#xff1a;基于水文响应单元&#xff…

相机光圈和快门

相机光圈和快门 光圈光圈结构光圈值由来光圈范围光圈作用控制画面明暗控制画面景深和锐度 自动光圈 Auto IrisDC-IRIS原理及问题P-IRIS工作原理 快门快门简介快门速度与曝光快门速度与运动安全快门速度高速快门和慢速快门B门和T门 参考文献 光圈 光圈结构 光圈&#xff08;Ap…

记一次 Android 源码编译刷机过程

0 背景 为了能在开发设备上运行 adb root 命令得到 root 权限&#xff0c;获得更加强大的调试能力、开发体验&#xff0c;方便以后阅读源码时 Debug 跟踪、进行定制化开发&#xff0c;需要编译 Android 源码 userdebug 版本并刷入手机当中。 1 准备 1.1 硬盘 首先 Android 源…

新星计划 Electron+vue2 桌面应用 2 项目编写

练手的项目&#xff0c;需求简单&#xff0c;打算做平面设计社交类的软件。 一、需求 练手用&#xff0c;简单处理&#xff0c;写个简单的记事本&#xff0c;本地保存txt&#xff0c;能导出为其他格式的文件。 获取用户的网卡地址用于数据加密&#xff0c;本地保存加密后的文…

想变身“科技型”企业?掌汇云数字化服务平台为工业升级加分

40万亿元&#xff01;占GDP比重达到33.2%&#xff0c;这就是国新办公布的2022年数据&#xff0c;中国工业可以说是当之无愧的支柱产业。 中国工业规模大、覆盖面广&#xff0c;企业员工众多&#xff0c;项目遍及海内外。但由于科技欠发达、信息不流通等因素&#xff0c;近些年…

IDEA中 lombok不生效解决方法

目录 前言: springboot启动的时候报错, 说没有lombok编译器 第一步: 检查插件lombok是否存在 第二步: 查看springboot 官方推荐的lombok版本 -> 2.1 第一步 找到这个parent ctrl点进去 -> 2.2 在点红框位置 进去 -> 2.3 ctrlf 搜索一下 lombok.version 复制这个…