(自用)learnOpenGL学习总结-高级OpenGL-立方体贴图

news2024/11/16 10:44:54

ok终于来到了立方体贴图了,在这里面我们可以加入好看的天空包围盒,这样的画我们的背景就不再是黑色的了!

首先,立方体贴图和前面的sampler2D贴图一样,不过是6个2D组成的立方体而已。

那么为什么要把6个组合在一起呢?立方体贴图可以通过一个方向向量来进行索引(或者说采样)。什么意思?

我们类比一下,之前在一个2D面上我们通过uv纹理坐标来找到对应的纹理值对吧。这里也一样,不过是通过一个方向向量来获得。

 假设我们有一个立方体、然后又有一个立方体贴图,只要立方体中心在原点,我就可以通过立方体表面的位置来确定对应的方向向量从而确定对应的立方体纹理坐标。

创建立方体贴图

和前面创建贴图一样。

unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

不过因为有6个面,所以需要贴6次。

int width, height, nrChannels;
unsigned char *data;  
for(unsigned int i = 0; i < textures_faces.size(); i++)
{
    data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}

这里我们有一个叫做textures_faces的vector,它包含了立方体贴图所需的所有纹理路径,并以表中的顺序排列。这将为当前绑定的立方体贴图中的每个面生成一个纹理。

然后还需要设置环绕和过滤方式

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

可以看到这里出现了个R坐标,其实就是纹理的第三个维度,和位置的z一样。

最后在片段着色器中要使用一个samplerCube就行

天空盒

我们现在下载一个天空盒到我们的项目中。

 加载天空盒

和之前把图片加载到gpu一样,现在是加载一个cubemap。

unsigned int LoadCubeMapToGPU(std::vector<std::string> faces) {

	unsigned int textureID;
	glGenTextures(1, &textureID);
	glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

	int width, height, nrChannels;
	for (unsigned int i = 0; i < faces.size(); i++) {
		unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
		if (data) {
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
				0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
			stbi_image_free(data);
		}
		else {
			std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
			stbi_image_free(data);
		}
	}
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	return textureID;

};

然后需要存一个skybox的图片vector

float skyboxVertices[] = {
	// positions          
	-1.0f,  1.0f, -1.0f,
	-1.0f, -1.0f, -1.0f,
	 1.0f, -1.0f, -1.0f,
	 1.0f, -1.0f, -1.0f,
	 1.0f,  1.0f, -1.0f,
	-1.0f,  1.0f, -1.0f,

	-1.0f, -1.0f,  1.0f,
	-1.0f, -1.0f, -1.0f,
	-1.0f,  1.0f, -1.0f,
	-1.0f,  1.0f, -1.0f,
	-1.0f,  1.0f,  1.0f,
	-1.0f, -1.0f,  1.0f,

	 1.0f, -1.0f, -1.0f,
	 1.0f, -1.0f,  1.0f,
	 1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f, -1.0f,
	 1.0f, -1.0f, -1.0f,

	-1.0f, -1.0f,  1.0f,
	-1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f,  1.0f,
	 1.0f, -1.0f,  1.0f,
	-1.0f, -1.0f,  1.0f,

	-1.0f,  1.0f, -1.0f,
	 1.0f,  1.0f, -1.0f,
	 1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f,  1.0f,
	-1.0f,  1.0f,  1.0f,
	-1.0f,  1.0f, -1.0f,

	-1.0f, -1.0f, -1.0f,
	-1.0f, -1.0f,  1.0f,
	 1.0f, -1.0f, -1.0f,
	 1.0f, -1.0f, -1.0f,
	-1.0f, -1.0f,  1.0f,
	 1.0f, -1.0f,  1.0f
};

std::vector<std::string> faces
{
    "right.jpg",
    "left.jpg",
    "top.jpg",
    "bottom.jpg",
    "front.jpg",
    "back.jpg"
};

然后需要给这cube给一个VAO VBO

	unsigned int cubemapTexture = LoadCubeMapToGPU(faces);

	unsigned int skyboxVAO, skyboxVBO;
	glGenBuffers(1, &skyboxVBO);
	glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);

	glGenVertexArrays(1, &skyboxVAO);
	glBindVertexArray(skyboxVAO);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

最后写一个skybox的shader就好。

#version 330 core

layout(location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projMat;
uniform mat4 viewMat;

void main(){
	gl_Position = projMat*viewMat*vec4(aPos,1.0f);
	TexCoords = aPos;
}

#version 330 core
out vec4 FragColor;
in vec3 TexCoords;

uniform samplerCube skybox;

void main(){

	FragColor =	 texture(skybox,TexCoords);
}

可以看到在顶点着色器里面我们把坐标信息作为纹理信息传给了片段着色器,这是因为cubemap的纹理坐标就是一个向量,所以我们直接把原点在世界中心的立方体的各点坐标当作纹理向量给片段着色器就好。

最后是loop中,我们要记住,画天空盒,我们得先画,而且画的时候需要把深度测试关了,这样的画,之后的物体就是画在背景之前了。

loop中这么写。

//第一阶段
...
//clear screen
...
//skybox
glDepthMask(GL_FALSE);
skyboxShader->use();

iewMat = camera.GetViewMatrix();
projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyboxShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(skyboxShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));


glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
...
//木块 机器人 其他

不过结果不对,我们希望天空盒是以玩家为中心的,这样不论玩家移动了多远,天空盒都不会变近,让玩家产生周围环境非常大的印象。然而,当前的观察矩阵会旋转、缩放和位移来变换天空盒的所有位置,所以当玩家移动的时候,立方体贴图也会移动!我们希望移除观察矩阵中的位移部分,让移动不会影响天空盒的位置向量。

在基础光照小节中,我们通过取4x4矩阵左上角的3x3矩阵来移除变换矩阵的位移部分。我们可以将观察矩阵转换为3x3矩阵(移除位移),再将其转换回4x4矩阵,来达到类似的效果。

修改viewMat

glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));

优化

 在刚刚的过程中,是先画天空盒,在这中间关闭了深度测试。是因为我们想让其他物体画在天空盒上。不过这样不过高效,是因为在画天空盒的时候会对每个像素都跑一次片段着色器。

所以我们要最后渲染天空盒。但是怎么做呢?提前深度测试可以做到,我们先渲染其他东西,然后再提前深度测试通过的地方渲染天空盒就行了。

不过要知道的我们的天空盒只有1x1x1,其他东西很容易就在天空盒上,所以我们需要欺骗深度缓冲,或者说让他认为天空盒的深度值有最大,要让天空盒最深。

在坐标系统小节中我们说过,透视除法是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量。我们又从深度测试小节中知道,相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为w / w = 1.0

修改顶点着色器

void main(){
	vec4 pos = projMat*viewMat*vec4(aPos,1.0);
	gl_Position = pos.xyww;
	TexCoords = aPos;
}

修改main中的顺序,把天空盒放在其他物体之后,但是是再透明物体之前。

我们还要改变一下深度函数,将它从默认的GL_LESS改为GL_LEQUAL。

深度缓冲将会填充上天空盒的1.0值,所以我们需要保证天空盒在值小于或等于深度缓冲而不是小于时通过深度测试。

结果和之前一样

环境映射

现在我们可以通过这个环境纹理来给物体一些反射折射的属性了

反射

 这里灰色向量是我们的观察方向向量、红色是法向方向,绿色是反射方向,可以通过reflect()来获得,之后把这个绿色的向量作为天空盒的纹理方向坐标就可以获得立方体贴图了。

所以重新写一个反射天空盒的箱子着色器。

#version 330 core

layout(location = 0) in vec3 aPos;
layout(location =1) in vec3 aNormal;

out vec3 Position;
out vec3 Normal;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;

void main(){

	Normal = mat3(transpose(inverse(modelMat)))*aNormal;
	Position = vec3(modelMat* vec4(aPos,1.0f));
	gl_Position = projMat * viewMat * modelMat*vec4(aPos,1.0f);
}

#version 330 core

in vec3 Position;
in vec3 Normal;

out vec4 FragColor;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main(){
	vec3 viewDir = normalize(Position-cameraPos);
	vec3 refDir = reflect(viewDir,normalize(Normal));
	FragColor = vec4(texture(skybox,refDir).rgb,1.0f);
}

这里注意看下,因为片段着色器需要法向量信息,所以需要通过顶点着色器传个normal过去,但是这里是需要经过model变换的,也不能直接变换。所以需要通过

Normal = mat3(transpose(inverse(model))) * aNormal;

这样才能得到正确的法向量。

然后在main中 建立新的shader,绑定VAOVBO,之后再loop中画一个这样的cube,记得要把camera的position传进去。

//反射天空盒cube
		skyboxReflectShader->use();
		modelMat = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 5.0f));
		viewMat = camera.GetViewMatrix();
		projMat = projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

		glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
		glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
		glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

		glUniform3f(glGetUniformLocation(skyboxReflectShader->ID, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);

		glBindVertexArray(skyBoxReflectionVAO);
		glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
		glDrawArrays(GL_TRIANGLES, 0, 36);

记得再画之前绑定一下天空盒的材质。

这是怎么少一个面呢,是因为面剔除的原因,这里的顶点坐标没有按照逆时针的顺序排列。

修改一下,在画的时候把面剔除关了。

 

OK了 。

如果直接另外画一个机器人模型,但是用这里的reflectShader的话

 这看起来非常棒,但在现实中大部分的模型都不具有完全反射性。我们可以引入反射贴图(Reflection Map),来给模型更多的细节。与漫反射和镜面光贴图一样,反射贴图也是可以采样的纹理图像,它决定这片段的反射性。通过使用反射贴图,我们可以知道模型的哪些部分该以什么强度显示反射。在本节的练习中,将由你来为我们之前创建的模型加载器中引入反射贴图,显著提升纳米装模型的细节。

折射

折射通过refract函数实现。需要一个法向量,一个观察方向,两个材质之间的折射率。

现在修改下片段着色器就好。

void main()
{             
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

 它不太能显示折射的效果,现在看起来只是有点像一个放大镜。对纳米装使用相同的着色器却能够展现出了我们期待的效果:一个类玻璃的物体。

你可以想象出有了光照、反射、折射和顶点移动的正确组合,你可以创建出非常漂亮的水。注意,如果要想获得物理上精确的结果,我们还需要在光线离开物体的时候再次折射,现在我们使用的只是单面折射(Single-side Refraction),但它对大部分场合都是没问题的。

动态环境贴图

现在我们使用的都是静态图像的组合来作为天空盒,看起来很不错,但它没有在场景中包括可移动的物体。我们一直都没有注意到这一点,因为我们只使用了一个物体。如果我们有一个镜子一样的物体,周围还有多个物体,镜子中可见的只有天空盒,看起来就像它是场景中唯一一个物体一样。

通过使用帧缓冲,我们能够为物体的6个不同角度创建出场景的纹理,并在每个渲染迭代中将它们储存到一个立方体贴图中。之后我们就可以使用这个(动态生成的)立方体贴图来创建出更真实的,包含其它物体的,反射和折射表面了。这就叫做动态环境映射(Dynamic Environment Mapping),因为我们动态创建了物体周围的立方体贴图,并将其用作环境贴图。

虽然它看起来很棒,但它有一个很大的缺点:我们需要为使用环境贴图的物体渲染场景6次,这是对程序是非常大的性能开销。现代的程序通常会尽可能使用天空盒,并在可能的时候使用预编译的立方体贴图,只要它们能产生一点动态环境贴图的效果。虽然动态环境贴图是一个很棒的技术,但是要想在不降低性能的情况下让它工作还是需要非常多的技巧的。

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

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

相关文章

粒子群优化算法(Particle Swarm Optimization,PSO)求解基于移动边缘计算的任务卸载与资源调度优化(提供MATLAB代码)

一、优化模型介绍 移动边缘计算的任务卸载与资源调度优化原理是通过利用配备计算资源的移动无人机来为本地资源有限的移动用户提供计算卸载机会&#xff0c;以减轻用户设备的计算负担并提高计算性能。具体原理如下&#xff1a; 任务卸载&#xff1a;移动边缘计算系统将用户的计…

嵌入式学习第十三天

9.指针: &#xff08;1&#xff09;const指针 const 关键字 常量(只读) 1.const int *p; 2.int const *p; 1和2是等价的 const修饰 *p,指针变量p的值可以改变,但不能利用指针修改指向空间中的值 3.int *const p; const修饰 p,指针变量p的值不能改变…

幻兽帕鲁越玩越卡,内存溢出问题如何解决?

近期幻兽帕鲁游戏大火&#xff0c;在联机组队快乐游玩的同时&#xff0c;玩家们也发现了一些小问题。由于游戏有随机掉落材料的设定&#xff0c;服务器在加载掉落物的过程中很容易会出现掉帧、卡顿的情况。某些玩家甚至在游戏1&#xff5e;2时后就出现服务器崩溃的情况&#xf…

招聘网站简单爬虫_24.1.26

完整程序传送门 24.1.26 前些天接了一个大两届的师兄的小活&#xff0c;做了一下爬boss直聘岗位信息的程序&#xff0c;在这里记录一下 程序框架 定义一个名为paQu的接口函数&#xff0c;用于检查窗口的输入&#xff0c;它接受一个参数self&#xff0c;获取self对象的a属性&am…

服务攻防-开发框架安全SpringBootStruts2LaravelThinkPHPCVE复现

知识点&#xff1a; 1、PHP-框架安全-Thinkphp&Laravel 2、J2EE-框架安全-SpringBoot&Struts2 章节点&#xff1a; 1、目标判断-端口扫描&组合判断&信息来源 2、安全问题-配置不当&CVE漏洞&弱口令爆破 3、复现对象-数据库&中间件&开发框架&am…

Python爬虫请求库安装

请求库的安装 爬虫可以简单分为几步&#xff1a;抓取页面、分析页面和存储数据。 在抓取页面的过程中&#xff0c;我们需要模拟浏览器向服务器发出请求&#xff0c;所以需要用到一些 Python 库来实现 HTTP 请求操作。在本教程中&#xff0c;我们用到的第三方库有 requests、S…

基于vue实现待办清单案例

一、需求 新增内容&#xff1b; 删除内容&#xff1b; 统计操作&#xff1b; 清空数据。 示例图&#xff1a; 二、代码演示 1、基础准备 index.css代码 html, body {margin: 0;padding: 0; } body {background: #fff ; } button {margin: 0;padding: 0;border: 0;backgr…

BF16与FP16的区别

参考 BF16 与 FP16 在模型上哪个精度更高呢BF16 与 FP16 在模型上哪个精度更高呢【bf16更适合深度学习计算&#xff0c;精度更高】&#xff1a; 两者差异图示如下&#xff1a; BF16 是对FP32单精度浮点数截断数据&#xff0c;即用8bit 表示指数&#xff0c;7bit 表示小数。…

微博怎么把客户引流到私域?(引流技巧)

微博 1&#xff09;背景banner图 在微博主页顶部的背景图里&#xff0c;可以引导添加个人微信、公众号等信息&#xff0c;通常配合福利引导用户添加。 2&#xff09;个人简介 微博中比较常见的引流方式&#xff0c;可以直接在简介区内留下微信号、公众号名称、邮箱等信息&#…

差异性分析汇总

在做科研写论文的时候&#xff0c;我们总会听说要对数据进行差异性分析&#xff0c;那么何为差异性分析&#xff1f;差异性分析常用的方法有哪些&#xff1f;这些方法应该如何进行分类&#xff1f;如何选择&#xff1f;差异性分析的数据格式是怎么样的&#xff1f;软件如何操作…

MarkDown快速入门-以Obsidian编辑器为例

直接上图&#xff0c;左右对应。 首先是基础语法。 # 标题&#xff0c;几个就代表几级标题&#xff1b;* 单个是序号&#xff0c;两个在一起就是斜体&#xff1b;- [ ] 代表任务&#xff0c;注意其中的空格&#xff1b; 然后是表格按钮代码 | 使用中竖线代表表格&#xff0c…

Bean的注入方法和区别

Bean有几种注入方式&#xff1f;它们有什么区别&#xff1f; Bean对象中有以下几种注入方式&#xff1a; 属性注入Setter注入构造方法注入 属性注入 属性注入是我们最熟悉的&#xff0c;也是日常开发最常使用的一种注入方式&#xff0c;它的实现代码如下&#xff1a; Rest…

HTML小白入门学习-表单标签

一、前言 HTML标签千千万&#xff0c;要学习的占一半。上篇文章中我们学习了列表标签&#xff0c;针对有序列表、无序列表、自定义列表和嵌套列表进行简单的学习了解和实操。本篇文章将为大家介绍另一种常用标签&#xff0c;那就是表单类标签。 我们在网页中经常会看到表单的…

JavaScript版数据结构与算法(二)图、堆、搜索排序算法、算法设计思想

一、图 &#xff08;一&#xff09;图是什么 图是网络结构的抽象模型&#xff0c;是一组由边连接的节点。图可以表示任何二元关系&#xff0c;比如道路、航班… JS中没有图&#xff0c;但是可以用 Object 和 Array 构建图。图的表示法&#xff1a;邻接矩阵、邻接表… 1、邻接…

SOME/IP 协议介绍(七)传输 CAN 和 FlexRay 帧

SOME/IP 不应仅用于传输 CAN 或 FlexRay 帧。但是&#xff0c;消息 ID 空间需要在两种用例之间进行协调。 传输 CAN/FlexRay 应使用完整的 SOME/IP 标头。 AUTOSAR Socket-Adapter 使用消息 ID 和长度来构建所需的内部 PDU&#xff0c;但不会查看其他字段。因此&#xff0c;必…

如何发现帕鲁私服漏洞

白天当帕鲁、晚上抓帕鲁 相信所有的帕鲁玩家都不希望辛辛苦苦肝了几百小时抓的帕鲁因为网络入侵消失&#xff0c;除了抵御游戏内的强盗入侵&#xff0c;还要抵御现实世界的网络入侵&#xff0c;原本单纯的帕鲁变的复杂无比。 服务器弱口令、服务漏洞、未授权访问等入侵手段&a…

怎样自行搭建幻兽帕鲁游戏联机服务器?

幻兽帕鲁是一款深受玩家喜爱的多人在线游戏&#xff0c;为了获取更好的游戏体验&#xff0c;许多玩家希望能够自行搭建幻兽帕鲁游戏联机服务器&#xff0c;本文将指导大家如何自行搭建幻兽帕鲁游戏联机服务器。 自行搭建幻兽帕鲁游戏联机服务器&#xff0c;阿里云是一个不错的选…

【UVM源码】UVM Config_db机制使用总结与源码解析

UVM Config_db机制使用总结与源码解析 UVM Config_db机制介绍UVM Config_db 机制引入的背景基本介绍使用方法优缺点&#xff1a; UVM Config_db机制使用示例&#xff1a;UVM Config_db使用高阶规则Config_db资源优先级 UVM Config_db 源码解析 UVM Config_db机制介绍 UVM Conf…

合作文章(IF=13.6)| 神经损伤修复:“多效气体发射器”凝胶的妙用”

研究背景 周围神经损伤&#xff08;PNI&#xff09;包括对周围神经的形态学结构或生理功能的所有损伤。由于周围神经的结构和功能复杂&#xff0c;PNI往往导致预后不良和高致残率。药物递送移植物因其重建周围神经微环境的潜力而备受关注&#xff0c;但调节微环境的适当调控时…

2024年自动化测试岗位需求的 7 项必备技能 (最新版)

随着敏捷和DevOps等新时代项目开发方法逐渐取代旧的瀑布模型&#xff0c;测试需求在业界不断增长。测试人员现在正在与开发人员一起工作&#xff0c;自动化测试在许多方面极大地取代了手动测试。 如果您是自动化测试领域的新手&#xff0c;刚雇用您的组织将期望您快速&#xf…