Learn OpenGL In Qt之纹理

在这里插入图片描述

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~
公众号: C++学习与探索  |  个人主页: rainInSunny  |  个人专栏: Learn OpenGL In Qt

文章目录

  • 纹理
    • 纹理坐标
    • 纹理环绕方式
    • 纹理采样
    • 多级渐远纹理
  • 纹理加载和创建
    • 加载纹理
    • 创建纹理
  • 应用纹理

纹理

纹理坐标

  在前面的教程中,我们给三角形的每个顶点传递一个颜色值,画出了一个类似调色盘的三角形。如果我们想要画出更加逼真的图像,就需要传递更多的顶点和更多的颜色值。如果绘制一个特定的三角形都需要成千上万个点,那实在是太麻烦了。在OpenGL中通常通过纹理来实现这样的绘制效果。
  纹理通常是一张 2D 图片(也可能是 1D 或者 3D),它可以用来添加物体的细节;你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的 3D 的房子上,这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。

在这里插入图片描述

  为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。纹理坐标在 x 和 y 轴上,范围为 0 到 1 之间(注意我们使用的是 2D 纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终止于(1, 1),即纹理图片的右上角。下面的图片展示了我们是如何把纹理坐标映射到三角形上的。

在这里插入图片描述

  三角形有三个顶点坐标,我们只需要依次指定三个纹理坐标像下面这样:

float texCoords[] = {0.0f, 0.0f, // 左下角1.0f, 0.0f, // 右下角0.5f, 1.0f  // 上中
};

纹理环绕方式

  纹理坐标通常范围是(0, 1),如果设置数值超出了这个范围,OpenGL 默认的行为是重复这个纹理图像,当然也为我们提供了更多选择:

纹理环绕方式
环绕方式描述
GL_REPEAT对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。

  当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出。我们来看看这些纹理图像的例子:

在这里插入图片描述

  我们通过 glTexParameter* 函数对单独的一个坐标轴设置(s、t(如果是使用 3D 纹理那么还有一个 r)它们和 x、y、z 是等价的):

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
  • 第一个参数指定了纹理目标;使用的是 2D 纹理,因此纹理目标是 GL_TEXTURE_2D
  • 第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是 WRAP 选项,并且指定 ST 轴。
  • 最后一个参数需要我们传递一个环绕方式(Wrapping),这里环绕方式为 GL_MIRRORED_REPEAT
  • 如果选择 GL_CLAMP_TO_BORDER 选项,我们还需要指定一个边缘的颜色。这需要使用 glTexParameter 函数的 fv 后缀形式,用 GL_TEXTURE_BORDER_COLOR 作为它的选项,并且传递一个 float 数组作为边缘的颜色:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

纹理采样

  纹理坐标不依赖分辨率,如果实际绘制到屏幕上的区域很大,但是提供的纹理图片分辨率不够高,就需要通过插值的方式来填充缺失的像素值。OpenGL 提供了很多中采样的方式来进行插值,这里只讨论 GL_NEARESTGL_LINEAR

  • GL_NEAREST 是 OpenGL 默认的纹理插值方式。当设置为 GL_NEAREST 的时候,OpenGL 会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:

在这里插入图片描述

  • GL_LINEAR 它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:

在这里插入图片描述

  两种方式的效果如下:

在这里插入图片描述

  当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理插值的选项,比如你可以在纹理被缩小的时候使用邻近插值,被放大时使用线性插值。我们需要使用 glTexParameter* 函数为放大和缩小指定插值方式。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

多级渐远纹理

  当实际屏幕绘制的分辨率太小,和纹理分辨率相差太多的时候,无论设置那种插值方式可能都无法得到较好的效果。OpenGL 使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL 会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。让我们看一下多级渐远纹理是什么样子的:

在这里插入图片描述

  OpenGL 提供 glGenerateMipmap 帮我们创建多集渐远纹理。在渲染中切换多级渐远纹理级别(Level)时,OpenGL 在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用 NEAREST 和 LINEAR 插值。为了指定不同多级渐远纹理级别之间的插值方式,你可以使用下面四个选项中的一个代替原有的方式:

纹理插值方式
插值方式描述
GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近采样进行纹理插值
GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性采样进行插值
GL_NEAREST_MIPMAP_LINEAR在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近采样进行插值
GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性采样进行插值

  同样使用 glTexParameteri 设置这些采样方式:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  一个常见的错误是,将放大采样的选项设置为多级渐远纹理采样选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个 GL_INVALID_ENUM 错误代码。

纹理加载和创建

加载纹理

  首先我们需要将纹理加载进来,由于是基于 Qt 编写代码,加载图片直接使用 QImage 就可以。

// Load image
QImage image;
image.load(fileName);
image = QGLWidget::convertToGLFormat(image);

[static] QImage QGLWidget::convertToGLFormat(const QImage &img)
Converts the image img into the unnamed format expected by OpenGL functions such as glTexImage2D(). The returned image is not usable as a QImage, but QImage::width(), QImage::height() and QImage::bits() may be used with OpenGL. The GL format used is GL_RGBA.

  需要注意的是 QGLWidget::convertToGLFormat 转换出来的图片数据都是 GL_RGBA 格式。

创建纹理

  和之前的 vAO、VBO一样,OpenGL 通过 ID 来标识一个纹理对象。

unsigned int texture;
glGenTextures(1, &texture);

  glGenTextures 函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的 unsigned int 数组中(我们的例子中只是单独的一个 unsigned int),就像其他对象一样,我们需要绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:

glBindTexture(GL_TEXTURE_2D, texture);

  现在纹理已经绑定了,我们可以使用前面载入的图片数据生成一个纹理了。纹理可以通过 glTexImage2D 来生成:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

  函数参数解释如下:

  • 第一个参数指定了纹理目标(Target)。设置为 GL_TEXTURE_2D 意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到 GL_TEXTURE_1DGL_TEXTURE_3D 的纹理不会受到影响)。
  • 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填 0,也就是基本级别。
  • 第三个参数告诉 OpenGL 我们希望把纹理储存为何种格式。上面提到 Qt 转换出来的格式都是 GL_RGBA
  • 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
  • 下个参数应该总是被设为 0(历史遗留的问题)。
  • 第七第八个参数定义了源图的格式和数据类型。我们使用 RGBA 值加载这个图像,并把它们储存为 char(byte)数组,我们将会传入对应值。
  • 最后一个参数是真正的图像数据。

  直接在生成纹理之后调用 glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。综上,生成一个纹理的过程大概是这样:

unsigned int loadTexture(const QString& fileName)
{unsigned int texture = 0;glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);// 为当前绑定的纹理对象设置环绕、过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 加载并生成纹理QImage image;image.load(fileName);image = QGLWidget::convertToGLFormat(image);unsigned char *data = image.bits();if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}return texture;
}

  当然我们也可以用 Qt 提供的 QOpenGLTexture 来实现纹理的加载:

unsigned int loadTexture(const QString& fileName)
{QImage img(fileName);if (img.isNull()){qWarning() << "Could not find the image";Q_ASSERT(false);}QOpenGLTexture *texture = new QOpenGLTexture(img);texture->setMinificationFilter(QOpenGLTexture::Linear);texture->setMagnificationFilter(QOpenGLTexture::Linear);texture->setAutoMipMapGenerationEnabled(true);return m_texture->textureId();
}

应用纹理

  我们会使用 glDrawElements 绘制「你好,三角形」教程最后一部分的矩形。我们需要告知 OpenGL 如何采样纹理,所以我们必须使用纹理坐标更新顶点数据:

float vertices[] = {
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

  由于我们添加了一个额外的顶点属性,我们必须告诉OpenGL我们新的顶点格式:

在这里插入图片描述

  我们同样需要调整前面两个顶点属性的步长参数为 8 * sizeof(float)

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

  接着我们需要调整顶点着色器使其能够接受顶点坐标为一个顶点属性,并把坐标传给片段着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;out vec3 ourColor;
out vec2 TexCoord;void main()
{gl_Position = vec4(aPos, 1.0);ourColor = aColor;TexCoord = aTexCoord;
}

  片段着色器应该接下来会把输出变量 TexCoord 作为输入变量。片段着色器也应该能访问纹理对象,但是我们怎样能把纹理对象传给片段着色器呢?GLSL 有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如 sampler1D、sampler3D,或在我们的例子中的 sampler2D。我们可以简单声明一个 uniform sampler2D 把一个纹理添加到片段着色器中,稍后我们会把纹理赋值给这个 uniform。

#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;uniform sampler2D ourTexture;void main()
{FragColor = texture(ourTexture, TexCoord);
}

  我们使用 GLSL 内建的 texture 函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture 函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。接着使用 glUniform1i,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是 0,它是默认的激活纹理单元。

glUniform1i(glGetUniformLocation(ourShader.ID, "ourTexture"), 0);
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

OpenGL 至少保证有 16 个纹理单元供你使用,也就是说你可以激活从 GL_TEXTURE0GL_TEXTRUE15 。它们都是按顺序定义的,所以我们也可以通过 GL_TEXTURE0 + 8 的方式获得 GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

  如果没有激活纹理单元,那么默认纹理单元 0 将被默认激活。另外片段着色器中的 uniform sampler2D ourTexture 是设置 Uniform 传递的纹理单元,然后我们在程序中将传递的纹理单元绑定上我们需要的纹理,片段着色器就能从这个纹理中采样了

  下面通过一个例子来看看纹理的应用。首先实现着色器:

#version 330 core......uniform sampler2D texture0;
uniform sampler2D texture1;void main()
{// 按比例混合 texture0 和 texture1FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), 0.2);
}

  然后加载并创建两个纹理:

unsigned int texture0 = loadTexture("container.jpg");
unsigned int texture1 = loadTexture("awesomeface.png");

  其次定义哪个 uniform 采样器对应哪个纹理单元:


......ShaderProgram *pShaderProgram = new ShaderProgram(pContext);
pShaderProgram->compileSourceCode(vertexShaderSource, fragmentShaderSource);
pShaderProgram->use(); // 不要忘记在设置uniform变量之前激活着色器程序!
pShaderProgram->setInteger("texture0", 0);
pShaderProgram->setInteger("texture1", 1);......

  再绑定两个纹理到对应的纹理单元后绘制:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture1);glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

  如果一切都顺利,会得到下面的效果:

在这里插入图片描述

  发现笑脸是倒过来的,这是因为 OpenGL 要求 y 轴 0.0 坐标是在图片的底部的,但是图片的 y 轴 0.0 坐标通常在顶部。我们直接在加载图片的时候将图片上下翻转即可,image = image.mirrored(false, true);。最后得到下面的结果:

在这里插入图片描述

  这样我们就利用 OpenGL 绘制出了想要的纹理。

在这里插入图片描述


关注公众号:C++学习与探索,有惊喜哦~

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

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

相关文章

【AWS AMI跨境备份】跨境使用 S3 备份和还原 AMI 镜像

文章目录 一、实验场景二、实验目标三、实验架构图四、涉及到AWS服务五、演示操作5.1 创建EC2实例5.2 创建映像5.3 备份AMI至Global S35.4 复制AMI从Global S3至 CN S35.5 还原AMI5.6 测试AMI 六、参考链接 一、实验场景 将 AWS Global区域的EC2实例备份至 AWS CN区域。 备份…

苍穹外卖学习笔记(二十五)

文章目录 Spring Task介绍应用场景&#xff1a; cron表达式例如&#xff1a; 入门案例 订单状态定时处理处理超时订单处理一直配送中的订单OrderMapper WebSocket介绍HTTP协议和WebSocket协议对比应用场景&#xff1a;入门案例1. 使用websocket.html作为WebSocket客户端2. 导入…

前端打印功能(vue +springboot)

后端 后端依赖生成pdf的方法pdf转图片使用(用的打印模版是带参数的 ,参数是aaa)总结 前端页面 效果 后端 依赖 依赖 一个是用模版生成对应的pdf,一个是用来将pdf转成图片需要的 <!--打印的--><dependency><groupId>net.sf.jasperreports</groupId>&l…

LCD补充

LCD补充 目录 LCD补充 tip:随着我们学的越来越多&#xff0c;代码长度越来越长&#xff0c;编译越来越慢&#xff0c;有没有超过内存是我们比较关心的一件事&#xff0c;通过以下方法可以实时看到写的代码的大小 回顾LCD LCD补充功能 -- 1、有关在LCD上显示动图&#xff…

前端使用Canvas实现网页电子签名(撤销、下载)

前言&#xff1a;一般在一些后台的流程资料以及审核的场景中会需要电子签名&#xff0c;介绍一种用canvas实现的电子签名&#xff0c;此案例用的是原生js 效果展示&#xff1a; 一、html和css&#xff1a; <div class"divCla2"><canvas id"myCanvas&q…

数据结构-排序算法

基于交换的排序算法 快速排序&#xff1a; 最优情况 最优情况下&#xff0c;每次找到的参考轴把数据分成均匀的两半&#xff0c;最后应该是一个平衡二叉树状态&#xff1b;二叉树的层数&#xff08;logn&#xff09;即为递归需要进行的次数&#xff0c;并且每轮递归结束时&…

Java语言-抽象类

目录 1.抽象类概念 2.抽象类语法 3.抽象类特性 4.抽象类作用 1.抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c; 如果 一个类中没有包含足够的信息来描绘一个具体…

115.WEB渗透测试-信息收集-ARL(6)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;114.WEB渗透测试-信息收集-ARL&#xff08;5&#xff09; httpd就是apache环境&#xff0…

跨平台音摄像头|屏幕推送选OBS还是SmartPublisher?

好多开发者希望搞明白OBS和 SmartPublisher的区别和使用场景差别&#xff0c;本文就二者差别做个对比&#xff1a; OBS OBS&#xff08;Open Broadcaster Software&#xff09;是一款免费且开源的跨平台流媒体和视频录制软件。以下是关于它的详细介绍&#xff1a; 功能特点&a…

音乐播放器项目专栏介绍​

1.简介 本专栏使用Qt QWidget作为显示界面&#xff0c;你将会学习到以下内容&#xff1a; 1.大量ui美化的实例。 2.各种复杂ui布局。 3.常见显示效果实现。 4.大量QSS实例。 5.Qt音频播放&#xff0c;音乐歌词文件加载&#xff0c;展示。 6.播放器界面换肤。 相信学习了本专栏…

Oracle Expdp按条件导出-指定表数据

1.场景描述 业务需求&#xff1a;导出A机构、2024的数据&#xff0c;以dmp格式&#xff0c;保留导出日志。首先&#xff0c;需要分析库中需要导出的表清单、表的机构字段约束、表的时间约束&#xff1b;然后再导出。 2.方案分析 本次采用Oracle的expdp数据泵方式导出&#xf…

基于Docker的FRP内网穿透部署

服务器搭建&#xff08;FRPS&#xff09; 创建配置文件 # 创建存放目录 sudo mkdir /etc/frp # 创建frps.ini文件 nano /etc/frp/frps.ini frps.ini内容如下&#xff1a; [common] # 监听端口 bind_port 7000 # 面板端口 dashboard_port 7500 # 登录面板账号设置 dashboa…

《数字信号处理》学习09-部分分式展开法计算z 逆变换

在之前的文章中&#xff0c;我已经学习了使用留数法&#xff08;围线积分法&#xff09;来计算z逆变换 《数字信号处理》学习08-围线积分法&#xff08;留数法&#xff09;计算z 逆变换-CSDN博客 接着学习第二种计算z变换的方法&#xff1a;部分分式展开法。 目录 一&…

决策智能与强化学习:重放比率(replay ratio)

知乎&#xff1a;DILab决策实验室&#xff08;已授权&#xff09;链接&#xff1a;https://zhuanlan.zhihu.com/p/898641863 0. 概览 近年来&#xff0c;深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09;在诸多领域取得了显著的成果。然而&#xff0c;随…

域环境模拟实验搭建

1. 域环境搭建 总体来说下一步下一步即可 域&#xff1a;统一的管理计算机的集群&#xff0c;中心管理机器&#xff08;域控制器 DC&#xff09;管理整个内网&#xff08;域内成员机器&#xff09; 条件&#xff1a;1. 需要一台域控制器 -- windows server版本&#xff08;w…

高级英语1第四版教材全解pdf课后答案+课文翻译张汉熙

《高级英语1》是张汉熙教授编著的一本英语教材&#xff0c;广泛用于国内高校英语专业高年级学生的教学。这本书以提高学生的英语综合能力为目标&#xff0c;注重语言知识的系统性和实用性&#xff0c;同时强调跨文化交际能力的培养。书中选材丰富&#xff0c;涵盖了文学、历史、…

Cloudlog delete_oqrs_line 未授权SQL注入漏洞复现

0x01 产品简介 Cloudlog 是一个自托管的 PHP 应用程序,可让您在任何地方记录您的业余无线电联系人。使用PHP和MySQL构建的基于Web的业余无线电记录应用程序支持从HF到微波的一般站记录任务 0x02 漏洞概述 Cloudlog delete_oqrs_line 接口存在未授权SQL注入漏洞,未经身份验…

UE5学习笔记24-添加武器弹药

一、给角色的武器添加弹药 1.创建界面&#xff0c;根据笔记23的界面中添加 2.绑定界面控件 UPROPERTY(meta (Bindwidget))UTextBlock* WeaponAmmoAmount;UPROPERTY(meta (Bindwidget))UTextBlock* CarriedAmmoAmount; 3.添加武器类型枚举 3.1创建武器类型枚举头文件 3.2创建文…

Java 小游戏《超级马里奥》

文章目录 一、效果展示二、代码编写1. 素材准备2. 创建窗口类3. 创建常量类4. 创建动作类5. 创建关卡类6. 创建障碍物类7. 创建马里奥类8. 编写程序入口 一、效果展示 二、代码编写 1. 素材准备 首先创建一个基本的 java 项目&#xff0c;并将本游戏需要用到的图片素材 image…

PPT怎么转成PDF?5个超简单转换方法,快来学(包靠谱)

PPT怎么转成PDF&#xff1f;PPT&#xff08;PowerPoint演示文稿&#xff09;是一种重要的文件格式&#xff0c;通过它&#xff0c;我们能够直观地呈现信息、分享观点以及进行商务演示。然而在有些时候&#xff0c;我们需要将PPT文件转化为PDF格式&#xff0c;以便于分享、打印或…