OpenGL ES 绘制一张图片

news2025/3/1 9:39:37

GLSL 语法与内建函数

GLSL 的修饰符与数据类型

GLSL 中变量的修饰符
  • const:修饰不可被外界改变的常量
  • attribute:修饰经常更改的变量,只可以在顶点着色器中使用
  • uniform:修饰不经常更改的变量,可用于顶点着色器和片段着色器
  • varying:修饰在顶点着色器计算,然后传递到片元着色器中使用的变量
GLSL 的基本数据类型
  • int
  • float
  • bool
    float 是可以再加一个修饰符的,这个修饰符用来指定精度。
  • highp:32bit,一般用于顶点坐标(vertex Coordinate)
  • medium:16bit,一般用于纹理坐标(texture Coordinate)
  • lowp:8bit,一般用于颜色表示(color)
GLSL 中就是向量类型(vec)

向量类型是 Shader 中最常用的一个数据类型,因为在做数据传递的时候经常要传递多个参数,相较于写多个基本数据类型,使用向量类型更加简单。
比如,通过 OpenGL 接口把物体坐标和纹理坐标传递到顶点着色器中,用的就是向量类型。每个顶点都是一个四维向量,在顶点着色器中利用这两个四维向量就能去做自己的运算。

attribute vec4 position;
矩阵类型(matrix)

矩阵类型在 GLSL 中同样也是一个非常重要的数据类型,在某些效果器的开发中,需要开发者自己传入一些矩阵类型的数据,用于像素计算。

uniform lowp mat4 colorMatrix;

上面的代码表示的是一个 44 的浮点矩阵,如果是 mat2 的声明,代表的就是 22 的浮点矩阵,而 mat3 代表的就是 3*3 的浮点矩阵。
OpenGL 为开发者提供了以下接口,把内存中的数据(mColorMatrixLocation)传递给着色器。

glUniformMatrix4fv(mColorMatrixLocation, 1, false, mColorMatrix);

其中,mColorMatrix 是这个变量在接口程序中的句柄。这里一定要注意,上边的这个函数不属于 GLSL 部分,而是属于客户端代码,也就是说,我们调用这个函数来和着色器进行交互。

纹理类型

一般只在片元着色器中使用,下面 GLSL 代码是二维纹理类型的声明方式。

uniform sampler2D texSampler;

首先我们需要拿到这个变量的句柄,定义为 mGLUniformTexture,然后就可以给它绑定一个纹理,接口程序的代码如下:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texId);
glUniform1i(mGLUniformTexture, 0);

注意,上述接口程序中的第一行代码激活的是哪个纹理句柄,在第三行代码中的第二个参数就需要传递对应的 Index,就比如说代码中激活的纹理句柄是 GL_TEXTURE0,对应的第三行代码中的第二个参数 Index 就是 0,如果激活的纹理句柄是 GL_TEXTURE1,那对应的 Index 就是 1,句柄的个数在不同的平台不一样,但是一般都会在 32 个以上。

传递类型

在 GLSL 中有一个特殊的修饰符就是 varying,这个修饰符修饰的变量都是用来在顶点着色器和片元着色器之间传递参数的。
最常见的使用场景就是在顶点着色器中修饰纹理坐标,顶点着色器会改变这个纹理坐标,然后把这个坐标传递到片元着色器,代码如下:

attribute vec2 texcoord;
varying vec2 v_texcoord;
void main(void)
{
    //计算顶点坐标
    v_texcoord = texcoord;
}

接着在片元着色器中也要声明同名的变量,然后使用 texture2D 方法来取出二维纹理中这个纹理坐标点上的纹理像素值,代码如下:

varying vec2 v_texcoord;
vec4 texel = texture2D(texSampler, v_texcoord);

取出了这个坐标点上的像素值,就可以进行像素变化操作了,比如说去提高对比度,最终将改变的像素值赋值给 gl_FragColor。

GLSL 的内置变量与内嵌函数

内置变量

常见的是两个 Shader 的输出变量,一个是顶点着色器的内置变量 gl_position,它用来设置顶点转换到屏幕坐标的位置。

vec4 gl_posotion;

另外一个内置变量用来设置每一个粒子矩形大小,一般是在粒子效果的场景下,需要为粒子设置绘制的半径大小时使用。

float gl_pointSize;

其次是片元着色器的内置变量 gl_FragColor,用来指定当前纹理坐标所代表的像素点的最终颜色值。

vec4 gl_FragColor;

然后是 GLSL 内嵌函数部分,我们在这里只介绍常用的几个常用函数。

内嵌函数

官方文档

内嵌函数说明
abs(genType x)绝对值函数
floor(genType x)向下取整函数
ceil(genType x)向上取整函数
mod(genType x, genType y)取模函数
min(genType x, genType y)取得最小值函数
max(genType x, genType y)取得最大值函数
clamp(genType x, genType y, genType z)取得中间值函数
step(genType edge, genType x)如果x<edge,返回0.0,否则返回1.0
smoothstep(genType edge0, genType edge1, genType x)如果x<=edge0,返回0.0,如果x>=dege1,返回1.0,如果edge0<x<edge1,则执行0~1之间的平滑差值
mix(genType x, genType y, genType a)返回线性混合的x和y,用公式表示为x*(1-a)+y*a,这个函数在mix两个纹理图像的时候非常有用
对于一种语言的语法来讲,剩下的就是控制流的部分了。
GLSL 的控制流与 C 语言非常类似,既可以使用 for、while 以及 do-while 实现循环,也可以使用 if 和 if-else 进行条件分支的操作。

OpenGL ES 的纹理

OpenGL 中的纹理用 GLUint 类型来表示,通常我们称之为 Texture 或者 TextureID,可以用来表示图像、视频画面等数据。
对于二维的纹理,每个二维纹理都由许多小的片元组成,每一个片元我们可以理解为一个像素点。
大多数的渲染过程,都是基于纹理进行操作的,最简单的一种方式就是从一个图像文件加载数据,然后上传到显存中构造成一个纹理。

纹理坐标系

为了访问到纹理中的每一个片元(像素点),OpenGL ES 构造了纹理坐标空间,坐标空间的定义是从左下角的(0,0)到右上角的(1,1)。
横轴维度称为 S 轴,左边是 0,右边是 1,纵轴维度称为 T 轴,下面是 0,上面是 1。
按照这个规则就构成了左图所示的坐标系,可以看到上下左右四个顶点的坐标位置,而中间的位置就是(0.5,0.5)。
另外在这里不得不提的是计算机系统里的坐标空间,通常 X 轴称之为横轴,从左到右是 0~1,Y 轴称之为纵轴,是从上到下是 0~1,如图所示:
在这里插入图片描述
无论是计算机还是手机的屏幕坐标,X 轴是从左到右是 0~1,Y 轴是从上到下是 0~1,这种存储方式是和图片的存储是一致的。
我们这里假设图片(Bitmap)的存储是把所有像素点存储到一个大数组中,数组的第一个像素点表示的就是图片左上角的像素点(即第一排第一列的像素点),数组中的第二个元素表示的是第一排第二列的第二个像素点,依此类推。
这样你会发现这种坐标其实是和 OpenGL 中的纹理坐标做了一个旋转 180 度。 因此从本地图片中加载一张纹理并且渲染到界面上的时候,就会用到纹理坐标和计算机系统的坐标的转换。

纹理创建与绑定

创建

加载一张图片作为 OpenGL 中的纹理。首先要在显卡中创建一个纹理对象,OpenGL ES 提供了方法原型如下:

void glGenTextures (GLsizei n, GLuint* textures)

这个方法中的第一个参数是需要创建几个纹理对象,第二个参数是一个数组(指针)的形式,函数执行之后会将创建好的纹理句柄放入到这个数组中。
如果仅仅需要创建一个纹理对象的话,只需要声明一个 GLuint 类型的 texId,然后将这个纹理 ID 取地址作为第二个参数,就可以创建出这个纹理对象,代码如下:

glGenTextures(1, &texId);

执行完上面这个指令之后,OpenGL 引擎就会在显卡中创建出一个纹理对象,并且把这个纹理对象的句柄存储到 texId 这个变量中。

绑定

那接下来我们要对这个纹理对象进行操作,OpenGL ES 提供的都是类似于状态机的调用方式,也就是说在对某个 OpenGL ES 对象操作之前,先进行绑定操作,然后接下来所有操作的目标都是针对这个绑定的对象进行的。对于纹理 ID 的绑定调用代码如下:

glBindTexture(GL_TEXTURE_2D, texId);

执行完上面这个指令之后,OpenGL ES 引擎认为这个纹理对象已经处于绑定状态,那么接下来所有对于纹理的操作都是针对这个纹理对象的了,当我们操作完毕之后可以调用如下代码进行解绑:

glBindTexture(GL_TEXTURE_2D, 0);

上面这行指令执行完毕之后,就代表我们不会对 texId 这个纹理对象做任何操作了,所以上面这行代码一般在一个 GLProgram 执行完成之后调用。

过滤

首先就是纹理的过滤方式,当纹理对象被渲染到物体表面上的时候,纹理的过滤方式指定纹理的放大和缩小规则。
实际上,是 OpenGL ES 的绘制管线中将纹理的元素映射到片元这一过程中的映射规则,因为纹理(可以理解为一张图片)大小和物体(可以理解为手机屏幕的渲染区域)大小不太可能一致,所以要指定放大和缩小的时候应该具体确定每个片元(像素)是如何被填充的。
放大(magnification)规则的设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

缩小(minification)规则的设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

上述两个指令设置的过滤方式都是 GL_LINEAR,这种过滤方式叫做双线性过滤,底层使用双线性插值算法来平滑像素之间的过渡部分,OpenGL 的具体实现会使用四个邻接的纹理元素,并在它们之间用一个线性插值算法做插值,这种过滤方式是最常用的。
OpenGL 还提供了 GL_NEAREST 的过滤方式,GL_NEAREST 被称为最邻近过滤,底层为每个片段选择最近的纹理元素进行填充,缺点就是当放大的时候会丢失掉一些细节,会有很严重的锯齿效果。因为是原始的直接放大,相当于降采样。而当缩小的时候,因为没有足够的片段来绘制所有的纹理单元,也会丢失很多细节,是真正的降采样。
其实 OpenGL 还提供了另外一种技术,叫做 MIP 贴图,但是这种技术会占用更多的内存,优点是渲染也会更快。当缩小和放大到一定程度之后效果也比双线性过滤的方式更好,但是它对纹理的尺寸以及内存的占用是有一定限制的。不过,在处理以及渲染视频的时候不需要放大或者缩小这么多倍,所以在这种场景下 MIP 贴图并不适用。
综合对比这几种过滤方式,在使用纹理的过滤方式时我们一般都会选用双线性过滤的过滤方式(GL_LINEAR)。

在纹理坐标系中的 s 轴和 t 轴超出范围的纹理处理规则,常见的代码设置如下:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

上述代码表示的含义就是,给这个纹理的 s 轴和 t 轴的坐标设置为 GL_CLAMP_TO_EDGE 类型,代表所有大于 1 的像素值都按照 1 这个点的像素值来绘制,所有小于 0 的值都按照 0 这个点的像素值来绘制。
除此之外,OpenGL ES 还提供了 GL_REPEAT 和 GL_MIRRORED_REPEAT 的处理规则,从名字也可以看得出来,GL_REPEAT 代表超过 1 的会从 0 再重复一遍,也就是再平铺一遍,而 GL_MIRRORED_REPEAT 就是完全镜像地平铺一遍。

纹理的上传与下载

假设我们有一张 PNG 类型的图片,我们需要将它解码为内存中 RGBA 裸数据,所以首先我们需要解码。可以采用跨平台(C++ 层)的方式,引用 libpng 这个库来进行解码操作,当然也可以采用各自平台的 API 进行解码。无论哪一种方式,最终都可以得到 RGBA 的数据。等拿到 RGBA 的数据之后,记为 uint8_t 数组类型的 pixels。
接下来,就是要将 PNG 素材的内容放到这个纹理对象上面去

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
        GL_UNSIGNED_BYTE, pixels);

执行上述指令的前提是我们已经绑定了某个纹理,OpenGL 的大部分纹理一般只接受 RGBA 类型的数据。当然在视频场景下,考虑性能问题也会使用到 GL_LUMINANCE 类型,不过需要在片元着色器中,把 YUV420P 格式转换成 RGBA 格式。
上述指令正确执行之后,RGBA 的数组表示的像素内容会上传到显卡里面 texId 所代表的纹理对象中,以后要使用这个图片,直接使用这个纹理 ID 就可以了。
既然有内存数据上传到显存的操作,那么一定也会有显存的数据回传回内存的操作

glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

执行上述指令的前提是我们已经绑定了某个纹理,然后将绑定的这个纹理对象代表的内容拷贝回 pixels 这个数组中,这个拷贝会比较耗时,并且拷贝时间会和分辨率(width\height)大小成正比。
一般在实际的开发工作中要尽量避免这种内存和显存之间的数据拷贝与传输,而是使用各个平台提供的快速映射 API 去完成内存与显存的拷贝工作。

物体坐标与纹理绘制

物体坐标系

OpenGL 规定物体坐标系中 X 轴从左到右是从 -1 到 1 变化的,Y 轴从下到上是从 -1 到 1 变化的,物体的中心点是 (0, 0) 的位置。
在这里插入图片描述
接下来的任务就是将这个纹理绘制到物体(屏幕)上,首先要搭建好各自平台的 OpenGL ES 的环境,包括上下文与窗口管理,然后创建显卡可执行程序,最终让程序跑起来。

纹理的绘制

先来看一个最简单的顶点着色器(Vertex Shader),代码如下:

static char* COMMON_VERTEX_SHADER =
      "attribute vec4 position;                   \n"
      "attribute vec2 texcoord;                   \n"
      "varying vec2 v_texcoord;                   \n"
      "                                            \n"
      "void main(void)                              \n"
      "{                                              \n"
      "   gl_Position = position;                 \n"
      "   v_texcoord = texcoord;                  \n"
      "}                                              \n";

片元着色器(Fragment Shader),代码如下:

static char* COMMON_FRAG_SHADER =
      "precision highp float;                                       \n"
      "varying highp vec2 v_texcoord;                               \n"
      "uniform sampler2D texSampler;                                \n"
      "                                                               \n"
      "void main() {                                                 \n"
      "    gl_FragColor = texture2D(texSampler, v_texcoord);      \n"
      "}                                                                                                                                       \n";

利用上面两个 Shader 创建好的这个 Program,我们记为 mGLProgId。

接下来我们需要将这个 Program 中的重点属性以及常量的句柄寻找出来,以备后续渲染过程中向顶点着色器和片元着色器传递数据。

mGLVertexCoords = glGetAttribLocation(mGLProgId, "position");
mGLTextureCoords = glGetAttribLocation(mGLProgId, "texcoord");
mGLUniformTexture = glGetUniformLocation(mGLProgId, "texSampler");

在这个例子里,我们要从 Program 的顶点着色器中读取两个 attribute,并放置到全局变量的 mGLVertexCoords 与 mGLTextureCoords 中,从 Program 的片元着色器中读取出来的 uniform 会放置到 mGLUniformTexture 这个变量里。
所有准备工作都做好了之后,接下来进行真正的绘制操作。
首先,规定窗口大小:

glViewport(0, 0, screenWidth, screenHeight);

函数中的参数 screenWidth 表示绘制 View 或者目标 FBO 的宽度,screenHeight 表示绘制 View 或者目标 FBO 的高度。
然后使用显卡绘制程序:

glUseProgram(mGLProgId);

设置物体坐标与纹理坐标:

GLfloat vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f };
glVertexAttribPointer(mGLVertexCoords, 2, GL_FLOAT, 0, 0, vertices);
glEnableVertexAttribArray(mGLVertexCoords);

设置纹理坐标:

GLfloat texCoords1[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f };
GLfloat texCoords2[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f };
glVertexAttribPointer(mGLTextureCoords, 2, GL_FLOAT, 0, 0,  texCoords2);
glEnableVertexAttribArray(mGLTextureCoords);

代码中有两个纹理坐标数组,分别是 texCoords1 与 texCoords2,最终我们使用的是 texCoords2 这个纹理坐标。
因为我们的纹理对象是将一个 RGBA 格式的 PNG 图片上传到显卡上,其实上传上来本身就需要转换坐标系,这两个纹理坐标恰好就是做了一个上下的翻转,从而将计算机坐标系和 OpenGL 坐标系进行转换。
对于第一次上传内存数据的场景纹理坐标一般都会选用 texCoords2。
但是如果这个纹理对象是 OpenGL 中的一个普通纹理对象的话,则需要使用 texCoords1。
接下来,指定我们要绘制的纹理对象,并且将纹理句柄传递给片元着色器中的 uniform 常量:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texId);
glUniform1i(mGLUniformTexture, 0);

执行绘制操作:

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

上述这行指令执行成功之后,就相当于将最初内存中的 PNG 图片绘制到默认的 FBO 上去了,最终再通过各平台的窗口管理操作(Android 平台的 swapBuffer、iOS 平台的 renderBuffer),就可以让用户在屏幕上看到了。
当确定这个纹理对象不再使用了,则需要删掉它,执行代码是:

glDeleteTextures(1, &texId);

除此之外,关于纹理的绘制我们还要额外注意一点:我们提交给 OpenGL 的绘图指令并不会马上送给图形硬件执行,而是会放到一个指令缓冲区中。
考虑性能的问题,等缓冲区满了以后,这些指令会被一次性地送给图形硬件执行,指令比较少或比较简单的时候,是没办法填满缓冲区的,所以这些指令不能马上执行,也就达不到我们想要的效果。
因此每次写完绘图代码,想让它立即完成效果的时候,就需要我们自己手动调用 glFlush() 或 gLFinish() 函数。

  • glFlush:将缓冲区中的指令(无论是否为满)立刻送给图形硬件执行,发送完立即返回;
  • glFinish:将缓冲区中的指令(无论是否为满)立刻送给图形硬件执行,但是要等待图形硬件执行完后这些指令才返回。

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

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

相关文章

vue中go.js的使用教学(五:数据绑定)

一、一个简单的数据绑定&#xff08;go.Binding&#xff09;看注释为绑定 diagram.nodeTemplate $(go.Node, "Auto",$(go.Shape, "RoundedRectangle",{ fill: "white" },new go.Binding("fill", "color")), // shape.fill …

Nginx 部署 Vue 项目以及 Vue 项目刷新出现 404 的问题(完整步骤)(亲测有效)

Nginx 部署 Vue 项目以及 Vue 项目刷新出现 404 的问题&#xff08;完整步骤&#xff09;&#xff08;亲测有效&#xff09; 1.流程步骤&#xff08;本教程下载的是1.20.2版本&#xff0c;放在D盘&#xff09; 1-1. 首先去官方下载 nginx &#xff0c;然后在当前目录下创建ht…

大幅提升爬取效率的一款实用工具

在做爬虫的时候&#xff0c;我们往往可能这些情况&#xff1a;网站比较复杂&#xff0c;会碰到很多重复请求。有时候爬虫意外中断了&#xff0c;但我们没有保存爬取状态&#xff0c;再次运行就需要重新爬取。还有诸如此类的问题。那怎么解决这些重复爬取的问题呢&#xff1f;大…

什么国产蓝牙耳机颜值高又好用?好用且高颜值蓝牙耳机推荐

随着蓝牙耳机的受欢迎程度加深&#xff0c;其受众群体也越来越多样。什么国产蓝牙耳机颜值高又好用&#xff1f;针对这个问题&#xff0c;我来给大家推荐几款好用且颜值高的蓝牙耳机&#xff0c;可以当个参考。 一、南卡小音舱蓝牙耳机 参考价&#xff1a;239 蓝牙版本&…

Go进阶(3):上下文context

一、背景 在 Go http包的Server中&#xff0c;每一个请求在都有一个对应的 goroutine去处理。请求处理函数通常会启动额外的goroutine用来访问后端服务&#xff0c;比如数据库和RPC服务。一个上游服务通常需要访问多个下游服务&#xff0c;比如终端用户的身份认证信息、验证相关…

【网工最关心的问题,看Chat GPT怎么回答?】

最近打开微信群聊&#xff0c;都是在说ChatGPT相关内容 那ChatGPT是什么&#xff1f; ChatGPT是由美国人工智能实验室OpenAI开发的一个对话AI模型&#xff0c;于2022年11月正式推出。它因其极其出色的文本生成和对话交互能力在世界范围内迅速走红&#xff0c;五天内用户破百万&…

19岁就患老年痴呆!这些前兆别忽视!

在大部分人的印象中&#xff0c;阿尔兹海默症好像是专属于老年人的疾病&#xff0c;而且它的另一个名字就是老年痴呆症。然而&#xff0c;前不久&#xff0c;一位19岁的男生患上了阿尔兹海默症&#xff0c;是迄今为止最年轻的患者。这个男生从17岁开始&#xff0c;就出现了注意…

return和finally执行顺序、运行时异常与一般异常异同、error和exception区别、Java异常处理机制原理与应用

文章目录1.try {}里有一个return语句&#xff0c;那么紧跟在这个try后的finally{}里的code会不会被执行&#xff0c;什么时候被执行&#xff0c;在return前还是后?2.运行时异常与一般异常有何异同&#xff1f;3.java 程序中的错误有三种类型分别是什么4.error和exception有什么…

GitHub推送报错:You‘re using an RSA key with SHA-1, which is no longer allowed

文章目录1、问题描述2、解决方案&#xff1a;重新生成密钥对3、将生成的公钥添加到GitHub4、向GitHub推送代码测试1、问题描述 在向GitHub推送代码的时候&#xff0c;执行git push命令出现如下问题&#xff1a; 原因是github不再支持RSA算法生成的密钥了&#xff0c;我们需要重…

《爆肝整理》保姆级系列教程python接口自动化(二十)--token登录(详解)

简介 为了验证用户登录情况以及减轻服务器的压力&#xff0c;减少频繁的查询数据库&#xff0c;使服务器更加健壮。有些登录不是用 cookie 来验证的&#xff0c;是用 token 参数来判断是否登录。token 传参有两种一种是放在请求头里&#xff0c;本质上是跟 cookie 是一样的&…

微机原理学习总结0:前言

近期结束了微机课程的学习&#xff0c;&#xff08;指刚考完试&#xff09;&#xff0c;正常情况下&#xff0c;后面应该不会再接触这门课程了&#xff0c;故在此记录自己这段时间的收获。 首先&#xff0c;十分推荐b站的一门课程&#xff0c;老师讲的很细致&#xff0c;很适合…

21个有用的python工具

Python是最流行的编程语言之一。 它简单、强大&#xff0c;并且由一个致力于开源项目的社区驱动。Python的大量使用是它如此流行的原因; 您可以免费构建软件、开发Web服务、执行数据分析和可视化以及训练机器学习模型。 Python开发工具 开发工具帮助我们构建快速可靠的Python…

生物素点击试剂1884349-58-9,Alkyne-PEG3-Biotin Diazo,炔基PEG3生物素重氮

Diazo Biotin-PEG3-alkyne&#xff0c;Alkyne-PEG3-Biotin Diazo&#xff0c;重氮生物素-PEG3-炔烃&#xff0c;重氮生物素PEG3炔烃&#xff0c;炔基PEG3生物素重氮产品结构式&#xff1a;产品规格&#xff1a;1.CAS号&#xff1a;1884349-58-92.分子式&#xff1a;C39H53N7O9S…

HANA SDA-远程数据源访问

我们需要把其他系统的数据拿过来&#xff0c;到BW里和财务的数据集成。 HANA SDA就是不复制数据&#xff0c;建立虚拟表&#xff08;virtual table&#xff09;来映射到远程数据源。通过这个虚拟表访问其他系统的数据。 对虚拟表的操作现在也可以查询&#xff0c;更新&#xff…

熵权法计算权重

文章目录1. 多属性决策问题2. 熵&#xff08;entropy&#xff09;3. 信息熵4. 熵权法5. 熵权法的实现基于信息论的熵值法是根据各指标所含信息有序程度的差异性来确定指标权重的客观赋权方法&#xff0c;仅依赖于数据本身的离散程度。熵用于度量不确定性&#xff0c;指标的离散…

LeetCode-Kotlin-Array-EASY-21至30题

关键字 PriorityQueuePairHashMap和HashSet的区别 1.HashMap实现了Map接口&#xff0c;而HashSet实现了Set接口。2.HashMap用于存储键值对&#xff0c;而HashSet用于存储对象。3.HashMap不允许有重复的键&#xff0c;可以允许有重复的值。HashSet不允许有重复元素。4.HashMap…

新库上线 | CnOpenData专精特新“小巨人”企业工商注册基本信息数据

专精特新“小巨人”企业工商注册基本信息数据 一、数据简介 “专精特新”一词最早来源于2011年7月&#xff0c;由时任工信部总工程师朱宏任在《中国产业发展和产业政策报告&#xff08;2011&#xff09;》新闻发布会上首次提出。“专精特新”是指具备专业化、精细化、特色化、…

第三届区块链服务网络(BSN)全球合作伙伴大会在杭州成功举办

为持续推动分布式技术和产业创新发展&#xff0c;2023年2月17日&#xff0c;由杭州市人民政府指导&#xff0c;杭州市拱墅区人民政府、国家信息中心主办&#xff0c;中国移动通信集团有限公司、区块链服务网络&#xff08;BSN&#xff09;发展联盟承办&#xff0c;中国移动通信…

如何在六秒内吸引观众的注意力

根据《2022国民专注力洞察报告》显示&#xff0c;当代人的连续专注时长&#xff0c;已经从2000年的12秒&#xff0c;下降到了现在的8秒。对于这个事实你可能难以相信&#xff0c;实际上这意味着&#xff0c;大多数互联网用户跳到一些页面上时&#xff0c;可能眼皮都不眨一下就离…

通过finalshell远程连接Windows中linux虚拟机

在Windows系统中安装Linux虚拟机&#xff0c;在日常使用中跨系统会造成很多不便&#xff0c;以finalshell为媒介&#xff0c;连接windows和linux虚拟机&#xff0c;方便日程练习&#xff0c;具体安装过程如下&#xff1a; 一、 安装finalshell 安装链接&#xff1a; https://…