【OpenGL学习】Shader和Shader类的抽象

news2024/11/27 9:44:52

Shader

本节学习OpenGL中Shader的使用并将其抽象为类,简要介绍OpenGL所使用的着色器语言GLSL

一、什么是Shader?

参考维基百科中对Shader的定义:着色器 - 维基百科,自由的百科全书 (wikipedia.org)

计算机图形学领域中,着色器(英语:shader)是一种计算机程序,原本用于进行图像的浓淡处理(计算图像中的光照、亮度、颜色等),但近来,它也被用于完成很多不同领域的工作,比如处理CG特效、进行与浓淡处理无关的视频后期处理、甚至用于一些与计算机图形学无关的其它领域。[1]

使用着色器在图形硬件上计算渲染效果有很高的自由度。尽管不是硬性要求,但目前大多数着色器是针对GPU开发的。GPU的可编程绘图管线已经全面取代传统的固定管线,可以使用着色器语言对其编程。构成最终图像的像素、顶点、纹理,它们的位置、色相、饱和度、亮度、对比度也都可以利用着色器中定义的算法进行动态调整。调用着色器的外部程序,也可以利用它向着色器提供的外部变量、纹理来修改这些着色器中的参数。

二、OpenGL中的Shader

在OpenGL中,我们使用GLSL语言进行Shader的编写,GLSL是为图形计算量身定制的,包含一些针对向量和矩阵操作的有用特性。

Shader的开头必须声明版本,接着添加输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,该函数中处理所有的输入变量,并将结果输出到输出变量中。

一般情况下,一个Shader中会包含以下内容:

#version version_number
in type in_variable_name;//输入变量
in type in_variable_name;

out type out_variable_name;//输出变量

uniform type uniform_name;//全局变量

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

对于顶点着色器,其对应的输入变量也就是in后面的变量成为顶点属性(Vertex Attribute),可以声明的顶点属性是有上限的,一般由硬件决定,可以通过GL_MAX_VERTEX_ATTRIBS来获取:

int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

通常情况下它至少会返回16个。

二、GLSL中的数据类型

GLSL的数据类型可以来指定变量的种类。GLSL中包含默认基础数据类型intfloatdoubleuintbool。此外GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)。

2.1. 向量

类型含义
vecn包含n个float分量的默认向量
bvecn包含n个bool分量的向量
ivecn包含n个int分量的向量
uvecn包含n个unsigned int分量的向量
dvecn包含n个double分量的向量

一个向量的分量可以通过vec.x获取,这里x是指这个向量的第一个分量。也可以分别使用.x.y.z.w来获取它们的第1、2、3、4个分量。GLSL允许对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。

2.2. 向量的重组

重组方式如下面所示:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

要注意两边向量的长度必须相同。

同时也可以将一个向量作为参数传给另一个向量的构造函数来减少需求参数的数量:

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

三、着色器的输入和输出

GLSL通过inout 两个关键字来声明用于输入和输出的变量,只要变量能够匹配,数据就能够沿着管线传输。

对于顶点着色器,它的输入是从顶点数据中获取的。为了定义顶点数据的管理方式,使用location 来指定输入变量对应的顶点属性,例如在之前的小节当中 layout(location = 0)指定了顶点的顶点坐标属性。

对于片元着色器,最终的输出一定是一个vec4的颜色变量,因为片元着色器的目标就是计算最终输出的颜色,如果没有输出颜色,OpenGL会默认把你的物体渲染为黑色或者白色(像上节中的在没有添加shader情况下渲染出的三角形为黑色的)。

如果想要在顶点着色器和片元着色器之间传输数据,要在顶点着色器中定义out变量,在片元着色器中定义in变量,变量的类型和名字必须完全相同,这样OpenGL运行的时候就会把两个变量链接到一起。

四、Uniform定义的全局变量

Uniform定义的变量可以在任何着色器中进行访问,并且在定义之后一直保持。

简单尝试一下,首先在 Fragment Shader 中我们声明一个uniform变量用来控制输出颜色

			std::string FragmentSrc = R"(
			#version 330 core
			layout(location = 0) out vec4 Fragcolor;
			
			in vec3 v_Position;
			uniform vec4 u_Color;
			void main()
			{
				Fragcolor = u_Color;
			}
			)";

在shader中定义了uniform变量还不够,因为还没有指定变量存储的数据,因此需要在程序中对其进行指定,在 Render Loop 中,首先创建了一个随时间变化的颜色值用于传给uniform变量:

	//Set Uniform
	float timeValue = glfwGetTime(); //获取运行时间
	float colorValue = (sin(timeValue) / 2.f) + 0.5f;//颜色随时间变化,并归一化到 (0,1)之间
	GLint location = glGetUniformLocation(ShaderProgram, "u_COlor");
	glUseProgram(ShaderProgram);
	glUniform4f(location, 0.0f, colorValue, 0.0f, 1.0f);

使用函数glUniform4f来指定vec4类型的uniform变量,指定之前,我们首先需要知道要指定的uniform变量的位置,所以使用函数 glGetUniformLocation来获取之前定义的uniform变量的位置,第一个参数指定使用的着色器程序,第二个参数指定要获取的uniform变量的名称,注意:该名称必须和你定义的名称完全相同,否则会找不到对应的位置,返回-1。

更新一个uniform之前你必须先调用glUseProgram,因为是在当前激活的着色器程序中设置uniform的。

对于glUniform函数的后缀,后缀指定了要设置的uniform变量的类型。一般有:

后缀含义
f函数需要一个float作为它的值
i函数需要一个int作为它的值
ui函数需要一个unsigned int作为它的值
3f函数需要3个float作为它的值
fv函数需要一个float向量/数组作为它的值

运行一下程序观察结果:

请添加图片描述

五、添加顶点属性

之前说过顶点数组中可以存放很多的顶点属性,例如顶点坐标,顶点的颜色,纹理坐标等,接下来我们尝试在顶点数组中添加颜色属性:

	//Create an array of vertices
	float vertices[3 * 6] =
	{
		//pos				//Color
		-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
		 0.5f, -0.5f, 0.0f,	0.0f, 1.0f, 0.0f,
		 0.0f,  0.5f, 0.0f,	0.0f, 0.0f, 1.0f
	};

这个顶点数组传入顶点缓冲区之后的布局应该是这样的:

img

因此现在一个顶点对应的数据有6个浮点数,那么OpenGL要怎么知道这些数据分别表示什么呢?

还记得函数glVertexAttribPointer吗?这个函数就是用来指定内存中数据的布局的,所以我们添加代码如下:

// 顶点坐标属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);

在第一次调用glVertexAttribPointer时,首先第一个参数指定该属性所在的位置,在内存中顶点坐标属性是第一个属性,所以这里我们设置为0,但是这次步长发生了变化,每个顶点的属性现在变为了6个float,所以步长设置为 6 * sizeof(float),最后一个参数指定偏移量,顶点坐标在内存中偏移量为0,设置为(void*)0,而第二次调用函数glVertexAttribPointer,颜色属性在内存中是第二个属性,所以设置为1,步长仍然为 6,颜色属性在内存中的偏移量为 3 * sizeof(float),因为前面有三个float变量,不要忘记设置完之后要启用该顶点布局,也就是调用glEnableVertexAttribArray

之后还需要在shader中进行设置:

//---------------Vertex Shader----------------------
	std::string VertexSrc = R"(
			#version 330 core		
			layout(location = 0) in vec3 a_Position;
			layout(location = 1) in vec3 a_Color;
			
			out vec3 v_Position;
			out vec3 v_Color;
			void main()
			{
				v_Position = a_Position;
				v_Color = a_Color;
				gl_Position = vec4(a_Position, 1.0);
			}
			)";
	//--------------------------------------------------
	//--------------Fragment Shader---------------------
	std::string FragmentSrc = R"(
			#version 330 core
			layout(location = 0) out vec4 Fragcolor;
			
			in vec3 v_Position;
			in vec3 v_Color;
			uniform vec4 u_Color;
			void main()
			{
				Fragcolor = vec4(v_Color, 1);
			}
			)";
	//---------------------------------------------------

运行结果:

在这里插入图片描述

六、抽象着色器类

为了更方便的管理和使用shader,把shader抽象成一个类,并添加对应的方法,下面介绍我一般抽象的方式:

首先创建Shader类,添加头文件和源文件,并添加构造函数和析构函数:

#pragma once
#include <glad/glad.h>
#include <string>

class Shader
{
public:
	Shader(const std::string& VertexShaderPath, const std::string FragmentShaderPath);
	~Shader();
private:
	GLint m_ShaderProgram;
};

回想一下我们之前使用shader时候都需要做什么,首先创建了两段字符串用于存储glsl代码,然后创建了两个shader对象,之后对两段字符串进行编译,确认了编译结果之后,和我们的着色器程序进行了链接并检查链接状态,在本节中,我们不再使用字符串硬编码glsl代码,而是将其存储在文件当中,这样在代码量大的时候不会影响观感,也便于管理,所以,我在Shader类中添加了如下接口:

	//use Program
	void Bind();
	void UnBind();

private:
	std::string  ReadFile(const std::string& FilePath);
	void Compile(const std::string& VertexShaderSrc, const std::string& FragmentShaderSrc);

其中Bind函数和UnBind函数是为了使用ShaderProgram,因为每次对Shader进行操作的时候需要调用函数ShaderProgramReadFile函数是为了从文件中读取glsl代码到字符串中,Compile函数对读取的glsl代码字符串进行编译。

函数定义如下:

Shader::Shader(const std::string& VertexShaderPath, const std::string FragmentShaderPath)
{
	m_ShaderProgram = glCreateProgram();
	std::string VertexShaderSrc = ReadFile(VertexShaderPath);
	std::string FragmentShaderSrc = ReadFile(FragmentShaderPath);
	Compile(VertexShaderSrc, FragmentShaderSrc);
}

Shader::~Shader()
{
	glDeleteProgram(m_ShaderProgram);
}

void Shader::Bind()
{
	glUseProgram(m_ShaderProgram);
}

void Shader::UnBind()
{
	glUseProgram(0);
}

std::string Shader::ReadFile(const std::string& FilePath)
{
	std::string ShaderSrc;
	std::ifstream in(FilePath, std::ios::in, std::ios::binary);
	if (in)
	{
		in.seekg(0, std::ios::end);
		size_t size = in.tellg();
		if (size != -1)
		{
			ShaderSrc.resize(size);
			in.seekg(0, std::ios::beg);
			in.read(&ShaderSrc[0], size);
			in.close();
		}
		else
			std::cout << "Could not read from file " << FilePath << std::endl;
	}
	else
		std::cout << "Could not open the file! " << std::endl;
	return ShaderSrc;
}

void Shader::Compile(const std::string& VertexShaderSrc, const std::string& FragmentShaderSrc)
{
	//--------------Create and Compile Shader-----------------------
	unsigned int VertexShader, FragmentShader;

	// Create an empty vertex shader handle
	VertexShader = glCreateShader(GL_VERTEX_SHADER);
	// Send the vertex shader source code to GL
	// Note that std::string's .c_str is NULL character terminated.
	const GLchar* source = VertexShaderSrc.c_str();
	glShaderSource(VertexShader, 1, &source, 0);
	// Compile the vertex shader
	glCompileShader(VertexShader);

	GLint isCompiled = 0;
	glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &isCompiled);
	if (isCompiled == GL_FALSE)
	{
		GLint maxLength = 0;
		glGetShaderiv(VertexShader, GL_INFO_LOG_LENGTH, &maxLength);

		// The maxLength includes the NULL character
		std::vector<GLchar> infoLog(maxLength);
		glGetShaderInfoLog(VertexShader, maxLength, &maxLength, &infoLog[0]);

		// We don't need the shader anymore.
		glDeleteShader(VertexShader);

		// Use the infoLog as you see fit.

		// In this simple program, we'll just leave
		std::cout << infoLog.data() << std::endl;
		std::cout << "VertexShader Compilation failed!" << std::endl;
		return;
	}

	// Create an empty Fragment shader handle
	FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	// Send the Fragment shader source code to GL
	// Note that std::string's .c_str is NULL character terminated.
	source = FragmentShaderSrc.c_str();
	glShaderSource(FragmentShader, 1, &source, 0);
	// Compile the Fragment shader
	glCompileShader(FragmentShader);

	isCompiled = 0;
	glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &isCompiled);
	if (isCompiled == GL_FALSE)
	{
		GLint maxLength = 0;
		glGetShaderiv(FragmentShader, GL_INFO_LOG_LENGTH, &maxLength);

		// The maxLength includes the NULL character
		std::vector<GLchar> infoLog(maxLength);
		glGetShaderInfoLog(FragmentShader, maxLength, &maxLength, &infoLog[0]);

		// We don't need the shader anymore.
		glDeleteShader(FragmentShader);

		// Use the infoLog as you see fit.

		// In this simple program, we'll just leave
		std::cout << infoLog.data() << std::endl;
		std::cout << "FragmentShader Compilation failed!" << std::endl;
		return;
	}

	glAttachShader(m_ShaderProgram, VertexShader);
	glAttachShader(m_ShaderProgram, FragmentShader);
	glLinkProgram(m_ShaderProgram);

	// Note the different functions here: glGetProgram* instead of glGetShader*.
	GLint isLinked = 0;
	glGetProgramiv(m_ShaderProgram, GL_LINK_STATUS, (int*)&isLinked);
	if (isLinked == GL_FALSE)
	{
		GLint maxLength = 0;
		glGetProgramiv(m_ShaderProgram, GL_INFO_LOG_LENGTH, &maxLength);

		// The maxLength includes the NULL character
		std::vector<GLchar> infoLog(maxLength);
		glGetProgramInfoLog(m_ShaderProgram, maxLength, &maxLength, &infoLog[0]);

		// We don't need the program anymore.
		glDeleteProgram(m_ShaderProgram);
		// Don't leak shaders either.
		glDeleteShader(VertexShader);
		glDeleteShader(FragmentShader);

		// Use the infoLog as you see fit.

		// In this simple program, we'll just leave
		std::cout << infoLog.data() << std::endl;
		std::cout << "Shader link failed!" << std::endl;

		return;
	}

	// Always detach shaders after a successful link.
	glDetachShader(m_ShaderProgram, VertexShader);
	glDetachShader(m_ShaderProgram, FragmentShader);

	glDeleteShader(VertexShader);
	glDeleteShader(FragmentShader);
}

此外,为了对全局变量Uniform进行设置,我们还需要添加类似如下的函数:

		void SetFloat4(const std::string& name, glm::vec4);

该函数用于设置Uniform变量,定义如下:

void SetFloat4(const std::string& name, glm::vec4)
{
		GLint location = glGetUniformLocation(m_ShaderProgram, name.c_str());
		glUniform4f(location, values.x, values.y, values.z, values.w);
}

注意:这里使用了数学库glm,有关glm库的配置,可以参考下面的教程:(3条消息) OpenGL GLM 环境配置_Wonz的博客-CSDN博客_glm安装

后续用到其他uniform变量的时候可以按照上面的内容进行添加。

之后可以使用创建好的Shader类啦!把原来有关shader的代码删除,创建一个Shader对象:

	//Shader
	Shader TriangleShader("Asset/Shader/Triangle_VertexShader.glsl", "Asset/Shader/Triangle_FragmentShader.glsl");

在循环中指定我们之前设置的uniform全局变量:

		//ShaderProgram Use
		TriangleShader.Bind();

		//Set Uniform
		float timeValue = (float)glfwGetTime();
		float colorValue = (sin(timeValue) / 2.f) + 0.5f;
		glm::vec4 color(0.0f, colorValue, 0.0f, 1.0f);
		TriangleShader.SetFloat4("u_Color", color);

运行观察结果,会看到之前的彩色三角形,也可以改变输出渐变绿色三角形,说明我们的类抽象成功了(将shader中的输出颜色设置为 u_Color )。

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

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

相关文章

SpringBoot处理跨域总结

解决跨域的五种方法1、CorsFilter新建一个类Configuration public class CorsConfig {Beanpublic CorsFilter corsFilter() {//1. 添加 CORS配置信息CorsConfiguration config new CorsConfiguration();//放行哪些原始域//springboot版本为2.4.0以前写法config.addAllowedOrig…

kaggle竞赛 | Instant Gratification

kaggle比赛链接&#xff1a; https://www.kaggle.com/competitions/instant-gratification/data 目录普通方案优胜方案1. 用方差筛选特征2.QDA二次判别分析3.数据分组&#xff08;伪标签&#xff09;4.查看结果赛题总结普通方案 # 数据集路径 INPUT_PATH ../input/import num…

python学习笔记---进程和线程【廖雪峰】

进程和线程 现在&#xff0c;多核CPU已经非常普及了&#xff0c;但是&#xff0c;即使过去的单核CPU&#xff0c;也可以执行多任务。由于CPU执行代码都是顺序执行的&#xff0c;那么&#xff0c;单核CPU是怎么执行多任务的呢&#xff1f; 答案就是操作系统轮流让各个任务交替…

ESP-IDF:企业链表例程,实现初始化,插入,打印等功能。

例程&#xff1a; 简单地写一下企业链表&#xff0c;实现初始化&#xff0c;插入&#xff0c;打印等功能。 /企业链表/ typedef struct LINKNODE09 { // 定义节点 LINKNODE09 *next; } linknode09; // 定义表头 typedef struct LINKLIST09 { // 定义表头 linknode09 head; in…

【胖虎的逆向之路】03——Android一代壳脱壳办法罗列实操

【胖虎的逆向之路】03——Android脱壳办法罗列&脱壳原理详解 【胖虎的逆向之路】01——动态加载和类加载机制详解 【胖虎的逆向之路】02——Android整体加壳原理详解&实现 文章目录【胖虎的逆向之路】03——Android脱壳办法罗列&脱壳原理详解前言一、主流脱壳方法…

uefi和legacy的区别对比

legacy&#xff1a;[ˈleɡəsi]&#xff0c;遗产、遗留。 uefi&#xff1a;Unified Extensible Firmware Interface&#xff0c;统一可扩展固件接口。 当我们自己重装或安装操作系统的时候&#xff0c;可能会遇到硬盘的uefi和legacy两种&#xff0c;不过大多数人并不知道uefi和…

低代码开发前景如何?大家都真的看好低代码开发吗?

栖低代码开发前景如何&#xff0c;大家都真的看好低代码开发吗&#xff1f;之前有些过很多关于低代码的内容&#xff0c;这篇就来梳理下国内外低代码开发平台发展现状及前景。 关于低代码解读看这篇>> 什么是低代码&#xff08;Low-Code&#xff09;&#xff1f; 关于低…

SpreadJS.Release.16.0.2 Crack by Xacker

SpreadJS拥有 500 多个 Excel 函数的世界销量第一的 JavaScript 电子表格 快速提供真正类似 Excel 的电子表格体验 - 对 Excel 零依赖。创建财务应用程序&#xff0c;仪表板,图表,数据透视表,性能基准,科学实验室笔记本&#xff0c;以及其他类似的 JavaScript 电子表格应用程序…

77. 语言模型以及代码实现

1. 语言模型 给定文本序列 x1,…,xT,语言模型的目标是估计联合概率p&#xff08;x1,…,xT&#xff09;它的应用包括 做预训练模型&#xff08;eg BERT&#xff0c;GPT-3&#xff09;生成文本&#xff0c;给定前面几个词&#xff0c;不断使用xt~p(x1,…,xt-1) 来生成后续文本判…

CSS选择器整理学习(上)

在前端项目开发中&#xff0c;有时候需要对特殊的元素进行特殊的处理&#xff0c;但有时候元素的位置不确定、层级不确定、数量不确定等问题&#xff0c;导致我们没办法进行元素的选择&#xff0c;这个时候我们就需要用到元素选择器了。 一、CSS选择器 1、.class 选择器例子…

图像处理解决流程--外观检测

一、图像外观检测和面积计算 1、获取标准图像&#xff0c;提取要测定的区域&#xff08;截取成多个ROI&#xff09; 2、将目标图像的位置进行平移和旋转&#xff08;将目标图像和标准图像进行重叠&#xff09; 3、根据标准图像的区域进行以此计算目标图像的信息 4、判断统计 二…

Ajax基础

Ajax 是 Asynchronous JavaScript and XML&#xff08;异步 JavaScript 和 XML&#xff09;的简写 Ajax 中的异步&#xff1a;可以异步地向服务器发送请求&#xff0c;在等待响应的过程中&#xff0c;不会阻塞当前页面&#xff0c;浏览器可以做自己的事情。直到成功获取响应后…

Maven高级进阶

文章目录1&#xff0c;分模块开发1.1 分模块开发设计1.2 分模块开发实现1.2.1 环境准备1.2.2 抽取domain层步骤1:创建新模块步骤2:项目中创建domain包步骤3:删除原项目中的domain包步骤4:建立依赖关系步骤5:编译maven_02_ssm项目步骤6:将项目安装本地仓库1.2.3 抽取Dao层步骤1:…

iOS vue devtools工具的手把手安装,及Vue.js not detected的解决

使用vue插件Vue.js devtools 一.通过谷歌商店直接下载&#xff08;要翻墙&#xff09; 二.不翻墙的方法&#xff1a; 1.官网下载 git地址&#xff1a;https://github.com/vuejs/devtools git clone https://github.com/vuejs/devtools2.完成后命令行里切到该目录下&#x…

AppScan绕过登录验证码深入扫描

系列文章 AppScan介绍和安装 AppScan 扫描web应用程序 AppScan被动手动探索扫描 第四节-绕过登录验证码深入扫描 我们工作中最长碰到的工作场景是网站采用https协议&#xff0c;这时我们要用appScan进行扫描时&#xff0c;就需要先安装证书 1.证书安装 1.新建一个文件&…

渗透测试— —扫描与爆破账号

渗透测试— —扫描与爆破靶机账号 1 扫描与爆破账号流程 注意&#xff1a;仅用于教学与实验&#xff0c;不能用于攻击&#xff0c;否则后果自负 扫描&#xff1a;主机探测与端口扫描。&#xff08;主机探测&#xff1a;目标主机是存活&#xff0c;端口扫描&#xff1a;在线主…

总结 62 种在深度学习中的数据增强方式

数据增强 数据增强通常是依赖从现有数据生成新的数据样本来人为地增加数据量的过程 这包括对数据进行不同方向的扰动处理 或使用深度学习模型在原始数据的潜在空间(latent space)中生成新数据点从而人为的扩充新的数据集 这里我们需要区分两个概念&#xff0c;即增强数据和…

SpringBoot的filter过滤器

SpringBoot的filter过滤器 目录SpringBoot的filter过滤器一、过滤器的作用和概述1.1 简述1.2 使用场景二、自定义过滤的两种方式2.1 第一种方式2.1.1 启动类增加注解ServletComponentScan2.1.2 定义一个filter类2.1.3. 测试2.2 第二种方式2.2.1 自定义fitler类2.2.4 在启动类中…

《Linux Shell脚本攻略》学习笔记-第五章

5.1 简介 借助HTTP协议所提供的功能以及命令行实用工具&#xff0c;我们可以用脚本满足大量的web自动化需求。 5.2 web页面下载 wget是一个用于文件下载的命令行工具&#xff0c;选项繁多且用法灵活。 下载单个文件或web页面 指定从多个URL处进行下载 我们可以通过选项-O指定输…

centos上用nginx搭建简单的点播服务器

查看centos系统信息&#xff1a;cat /etc/centos-release配置服务器DNSecho "nameserver 114.114.114.114" >> /etc/resolv.conf 安装网络工具yum install ntpdate wget net-tools -y同步服务器时间ntpdate ntp.aliyun.com安装编译工具及依赖库yum install -y …