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

2023-11-18

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 )。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【OpenGL学习】Shader和Shader类的抽象 的相关文章

  • 退出 glutFullScreen()

    我不明白为什么当我按 f 时它进入全屏但不退出全屏 在这个方法的开头我已经设置了bool fullscreen false 这是我的切换代码 case f toggle screenmode if fullscreen glutFullSc
  • OpenGL 说“from_param 收到了一个不连续的数组”

    安装 Yosemite 后 我必须升级 numpy PyOpenGL 等 现在 以前运行的程序给了我以下堆栈跟踪 file latebind pyx line 44 in OpenGL accelerate latebind Curry c
  • CPU 到 GPU 法线映射

    我正在创建一个地形网格 然后这个答案 https stackoverflow com a 5284527 1356106我正在尝试将 CPU 计算法线迁移到基于着色器的版本 以便通过降低网格分辨率并使用在片段着色器中计算的法线贴图来提高性能
  • 使用 glDrawElements 时在 OpenGL 核心配置文件中选取三角形

    我正在使用 glDrawElements 绘制三角形网格 并且希望能够使用鼠标单击来拾取 选择三角形 三角形的网格可以很大 在固定功能 OpenGL 中 可以使用 GL SELECT http content gpwiki org inde
  • 即使在顶点着色器中使用,glGetUniformLocation()也会返回-1

    我正在尝试用法线渲染一个简单的立方体 我使用以下代码来初始化着色器 void initShader const char vertexShaderPath const char fragmentShaderPath cout lt lt I
  • 如何安装适用于 Windows C++ 的最新版本 OpenGL?

    我正在使用 Visual Studio 2010 运行 Windows 7 包含的 OpenGL 版本 include 是版本 1 1 我希望使用合理的当前版本 某种版本 3 或 4 我需要做什么才能达到该状态 OpenGL SDK 页面位
  • 简单的线框格式?

    我正在寻找一种用于线框模型的简单文件格式 我知道 VRML u3D 等 但这些对于我的需求来说似乎很重要 我的标准是 必须有明确的规格 要么是开放的 要么是非常完善 记录的 我只需要 想要 简单的模型 顶点和边 我不想处理面孔或物体 如果格
  • OpenGL 着色器不与着色器程序链接

    我正在尝试使用 GLFW GLEW 添加着色器 我收到一个错误 指出着色器已加载 但它们没有有效的对象代码 这是我用于加载着色器的代码 class SHADER public void LoadShaders const char vert
  • lnk1104:无法打开“LIBC.lib”链接

    使用 GLee 将着色器写入我的 OpenGL 项目并编译后 我收到了错误LNK1104 cannot open file LIBC lib 我尝试按照其他人的建议添加它并忽略它 但没有解决问题 有没有其他方法可以解决我错过的这个问题 以下
  • 将像素传递给 glTexImage2D() 后会发生什么?

    例如 如果我创建一个像素数组 如下所示 int getPixels int pixels new int 10 pixels 0 1 pixels 1 0 pixels 1 1 etc glTexImage2D getPixels glTe
  • 使用draw()而不是eventloop时的pyglet

    我正在尝试用 pyglet 画一个圆 但当我使用 draw 函数而不是 app run 循环时 它是不可见的 有什么建议我可以做什么吗 谢谢 from math import from pyglet gl import window pyg
  • 为什么OpenGL使用float而不是double? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • GL_CULL_FACE使所有对象消失

    我正在尝试在 openGL3 3 中创建一些简单的多边形 我有两种类型的对象 具有以下属性 对象 1 10 个顶点 按顺序在下面列出 存储在GL ARRAY BUFFER并使用GL TRIANGLE FAN v x y z w v 0 0
  • Visual Studio 2010 中的 SOIL 设置

    我无法得到SOIL http www lonesock net soil html正确使用 Visual Studio 2010 我远非 VS 专家 但据我所知 只需执行以下步骤即可使环境正常运行 属性 gt gt C C gt 常规 gt
  • OpenGL:仅获取模板缓冲区而没有深度缓冲区?

    我想获取一个模板缓冲区 但如果可能的话 不要承受附加深度缓冲区的开销 因为我不会使用它 我发现的大多数资源表明 虽然模板缓冲区是可选的 例如 排除它以利于获得更高的深度缓冲区精度 但我还没有看到任何请求并成功获取仅 8 位模板缓冲区的代码
  • gldrawarrays 不绘制任何东西

    我正在尝试用 VBO 绘制一个三角形 我在窗口上没有看到任何像素 我也没有看到任何 GL ERROR 这是我尝试运行的代码 include
  • 致命错误 gl.h 包含在 glew.h 之前

    include
  • OpenGL:如何检查用户是否支持glGenBuffers()?

    我检查了文档 它说 OpenGL 版本必须至少为 1 5 才能制作glGenBuffers 工作 用户使用的是1 5版本但是函数调用会导致崩溃 这是文档中的错误 还是用户的驱动程序问题 我正在用这个glGenBuffers 对于VBO 我如
  • 编译 GLUI 库,VS2010 给我一个奇怪的命名空间错误

    我有一个针对我所在班级的 OpenGL 项目 它基于 GLUI 提供的 GLUI 库无法工作 这就是我尝试自己编译它的原因 因此 我从 SourceForge 下载了源代码并尝试编译 glui 库 它给了我这个 但我找不到任何相关信息 1
  • 不明确的 OpenGL 默认相机位置

    在我的Opengl程序中 在我应用透视投影矩阵之前 每当我绘制一些对象时 我都会在世界坐标系的原点处绘制它 但是几乎所有Opengl教程都指出相机 我的投影视图 位于原点朝向正 z 轴 这取决于您稍后如何处理投影矩阵中的 z 值 但是如果这

随机推荐

  • 8.性能测试流程及策略

    8 性能测试流程及策略 一 准备工作 1 系统基础功能验证 性能测试在什么阶段适合实施 切入点很重要 一般而言 只有在系统基础功能测试验证完成 系统区域稳定的情况下 才会进行性能测试 否则性能测试是无意义的 2 测试团队组建 根据项目的具体
  • 高防cdn有什么优势?

    CDN即内容分发网络 通过在网络上各处放置节点服务器 来协助网站进行缓存 当用户访问网站时 就近分配节点为用户提供服务 从而加快访问速度 提升用户体验 但是普通的cdn并没有防御能力 那么这时就需要用到高防cdn来应对网络上的攻击 下面为大
  • Matlab:从文本文件中读取数值数据到矩阵

    Matlab 从文本文件中读取数值数据到矩阵 在Matlab中 我们常常需要将保存在文本文件中的数值数据读取到程序中 以便进行数据处理和分析 本文将介绍如何使用Matlab将文本文件中的数值数据导入到矩阵中 首先 我们需要创建一个文本文件
  • pandas-新手使用教程

    Pandas 是 Python 语言的一个扩展程序库 用于数据分析 下面将针对该模块进行讲解 一 导入模块 导入pandas模块 import pandas as pd 二 导入数据 常见用法 创建数据框 pd DataFrame 从CSV
  • C# --- Case Study

    C Case Study C Mongo数据库事务的应用 C 如何解析Json文件并注入MongoDB C MongoDB如何安全的替换Collection C netcore MVC项目框架结构 with MongoDB
  • 积分获取方式的两点优化建议

    积分获取是积分体系运营中的一个重要环节 也是针对用户运营的一个开端 如果积分获取做的不到位 那么商家就没法对用户的行为进行引导 也就没法实现商家的预期目标 整个积分体系就相当于白忙活了 这一点商家要特别的注意 在积分体系运营中 商家对于积分
  • 网课-cnn

    图像识别中遇到的问题可能有图片特征的纬度过高 1000 1000像素的图片 特征维度是1000 1000 3 如果你要输入3百万的数据量就意味着特征向量的维度高达三百万 也许有1000个隐藏单元 而所有的权值组成的矩阵W 1 如果使用标准的
  • git第一次配置ssh key,clone代码出错解决方法

    错误 The authenticity of host can t be established ED25519 key fingerprint is SHA256 k4ViHJBFryacGI BqHphyjDBaRLwt5eSGRMJG
  • vue table合并行 动态列名

    需求 1 合并行 相同数据合并 2 根据后端返回数据动态显示列名 我这个业务需求是 每年增加一列 也就是列名不是固定的 后端返回数据每年会多一条数据 根据返回数据显示列名 实现 html
  • 论文阅读 StyleCLIP:《StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery》

    论文地址 https arxiv org pdf 2103 17249 pdf 文章目录 摘要 1 介绍 2 相关工作 2 1 视觉与语言 2 2 潜空间图像处理 3 StyleCLIP文本驱动操作 4 潜在优化 5 潜在映射 6 全局方向
  • 解密蓝牙mesh系列

    转载自 蓝牙技术联盟 蓝牙mesh网络 友谊篇 低功耗蓝牙 Bluetooth Low Energy 是全球最具节能性的短距离无线通信技术之一 其低功耗的特性广受开发者和消费者赞誉 随着蓝牙mesh网络的推出 开发者可能想知道蓝牙mesh网
  • Python3 如何优雅地使用正则表达式(详解五)

    非捕获组命名组 精心设计的正则表达式可能会划分很多组 这些组不仅可以匹配相关的子串 还能够对正则表达式本身进行分组和结构化 在复杂的正则表达式中 由于有太多的组 因此通过组的序号来跟踪和使用会变得困难 有两个新的功能可以帮你解决这个问题 非
  • 线性稳压器基础知识

    1 1 什么是线性稳压器 线性稳压器的工作原理是 采用一个压控电流源以强制在稳压器输出端上产生一个固定 电压 控制电路连续监视 检测 输出电压 并调节电流源 根据负载的需求 以把输 出电压保持在期望的数值 电流源的设计极限限定了稳压器在仍然
  • C++输入输出(一)

    C 并没有专门的输入输出函数 他们都存在于库中 如果要使用cout cin和getline 需要导入iostream库 如果要是用其他的输入输出方式 需要导入cstdio库 一 cout流输出 这个输出方式我们很早就学过了 他的使用方式如下
  • Home Assistant 南方电网 计算电费

    目录 1 China Southern Power Grid Statistics集成 2 获取当月用电情况 3 计算电费然后在UI上显示 3 效果 1 China Southern Power Grid Statistics集成 链接 2
  • ANSYS WORKBENCH 后处理 之 提取截面查看云图

    这是一个结果的云图 现在想要查看管道某横截面的云图 步骤1右键coordinate systems 创建一个坐标系步骤2更改新建坐标轴属性 步骤3新建一个surface 定义中选择刚才创建的坐标系步骤4右键solution 选择刚才创建的面
  • c++的char[]和char*的区别

    大家先来看这道测试题 char str1 abc char str2 abc const char str3 abc const char str4 abc const char str5 abc const char str6 abc c
  • docker命令自动补全

    很多命令都会提供一个bash complete的脚本 在执行该命令时 敲tab可以自动补全参数 会极大提高生产效率 docker亦如此 如 yum install docker后 会有一个文件 usr share bash completi
  • Linux搭建gitlab以及汉化

    注 请使用管理员权限的用户 执行如下操作 文章目录 GitLab搭建 一 安装并配置必要的依赖关系 1 安装ssh 3 启动SSH服务 4 安装防火墙 如果已经安装了防火墙并且已经在运行状态 则可直接进行第6步 5 开启防火墙 6 添加ht
  • 【OpenGL学习】Shader和Shader类的抽象

    Shader 本节学习OpenGL中Shader的使用并将其抽象为类 简要介绍OpenGL所使用的着色器语言GLSL 一 什么是Shader 参考维基百科中对Shader的定义 着色器 维基百科 自由的百科全书 wikipedia org