当前位置: 首页 > news >正文

Android显示系统(04)- OpenGL ES - Shader绘制三角形

一、前言:

OpenGL 1.0采用固定管线,OpenGL 2.0以上版本重要的改变就是采用了可编程管线,Shader 编程是指使用着色器(Shader)编写代码来控制图形渲染管线中特定阶段的处理过程。在图形渲染中,着色器是在 GPU 上执行的小型程序,用于定义图形渲染管线中不同阶段的处理逻辑,以实现各种视觉效果,那么渲染管线哪个阶段可编程呢?

在这里插入图片描述

就是上图的渲染管线蓝色部分。

二、Shader介绍:

1、Vertex Shader和Fragment Shader:

在现代的图形渲染中,通常会使用两种主要类型的着色器:顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)。这两种着色器分别负责处理图形的顶点数据和片元(像素)数据,通过编写这些着色器程序,开发人员可以实现各种复杂的图形效果和渲染技术。

下面是对顶点着色器和片元着色器的简要介绍:

  • 顶点着色器
    • 顶点着色器用于处理图形的顶点数据,如位置、颜色、法线等。
    • 主要作用包括对顶点位置的变换(如模型变换、视图变换、投影变换)、法线变换、顶点着色等。
    • 顶点着色器的输出通常是裁剪空间坐标或者屏幕空间坐标。
  • 片元着色器
    • 片元着色器用于处理图元的片元(像素)数据,负责计算最终的颜色输出。
    • 在片元着色器中,开发人员可以实现光照、纹理映射、阴影、透明度等效果。
    • 片元着色器的输出通常是片元的颜色、深度值、法线等。

2、图元:

在Shader编程中,“图元”(Primitive)指的是基本的几何图形单元,通常是指在3D图形渲染中的基本几何形状,如点、线、三角形、四边形等。这些基本的几何图形单元是构成复杂场景的基础,它们通过渲染管线进行处理和转换,最终呈现在屏幕上。

在Shader编程中,图元是渲染管线处理的基本单位,Shader程序会对每个图元进行相应的处理,包括顶点变换、光照计算、纹理映射等操作,最终将图元渲染到屏幕上。常见的几何图元有以下几种:

  1. 点(Point):最简单的图元,通常用于表示粒子、光源等。
  2. 线(Line):由两个点组成的图元,可用于绘制线条、边缘等。
  3. 三角形(Triangle):由三个顶点组成的图元,是最基本的多边形,是3D图形学中最重要的图元之一,因为所有复杂的表面都可以由三角形网格构成。
  4. 四边形(Quadrilateral):由四个顶点组成的图元,通常被拆分为两个三角形处理。

Shader程序通过对这些基本图元进行处理和变换,最终形成了复杂的场景和图像。在Shader编程中,开发人员可以通过编写顶点着色器和片元着色器来对这些图元进行处理,实现各种视觉效果和渲染技术。处理图元是Shader程序中的一个重要任务,它直接影响着最终的渲染效果和性能。

3、三角形:

上面的四个图元中,其实最重要的是三角形,我们把大多数复杂的图形都可以用三角形拼起来,就像我小时候(不敢说你们00后小时候)糊灯笼一样。

比如,我之前文章提到的这个复杂的图,也都是由众多小三角形构成:

在这里插入图片描述

三、重要坐标系:

在 OpenGL ES 中,“标准设备坐标系”(Normalized Device Coordinates)和“屏幕坐标系”(Screen Coordinates)是两种不同的坐标系,它们在图形渲染过程中扮演不同的角色。

  1. 标准设备坐标系(Normalized Device Coordinates)
    • 标准设备坐标系是一个抽象的坐标系,它是一个以屏幕空间的中心为原点,范围从 -1 到 1 的立方体空间。
    • 在标准设备坐标系中,坐标 (0, 0) 表示屏幕中心,(-1, -1) 表示左下角,(1, 1) 表示右上角。
    • 所有的顶点数据在通过VertexShader处理后都会被映射到标准设备坐标系,这是 OpenGL ES 中进行图形变换和裁剪的标准坐标系。
  2. 屏幕坐标系(Screen Coordinates)
    • 屏幕坐标系是实际显示设备的坐标系,它通常以左上角为原点,向右为 x 轴正方向,向下为 y 轴正方向。
    • 屏幕坐标系的坐标值通常是以像素为单位的整数值,用来确定在屏幕上绘制图像和文本等元素的位置。

在 OpenGL ES 渲染过程中,顶点数据首先被定义在对象坐标系中,然后通过模型变换、视图变换和投影变换将其转换到标准设备坐标系中,最终在屏幕上绘制出来。

在这里插入图片描述

总结来说,标准设备坐标系是为了方便进行图形变换和裁剪而定义的坐标系,而屏幕坐标系则是实际显示设备上的坐标系,用于确定最终图像的位置。OpenGL ES 中的渲染过程涉及将顶点数据从对象坐标系转换到标准设备坐标系,最终映射到屏幕坐标系进行显示。

四、GLSL语言:

1、概念:

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。

一个典型的着色器有下面的结构:

#version version_number
in type in_variable_name;
in type in_variable_name;out type out_variable_name;uniform type uniform_name;void main()
{// 处理输入并进行一些图形操作...// 输出处理过的结果到输出变量out_variable_name = weird_stuff_we_processed;
}

当我们特别谈论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:

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

通常情况下它至少会返回16个,大部分情况下是够用了。

2、数据传递:

在这里插入图片描述

  • 可以将Prog想象成一个芯片,有很多的引脚,比如这儿的1绑定的是VertextShader,2绑定的是FragmentShader。
  • 顶点着色器Vertex负责确定3D图形的三个顶点,片元着色器Fragment负责确定每个像素的颜色;
  • 数据传给1的时候,就会自动传给vPosition;看看具体怎么传递的:
    • 通过glGetAttribLocation获取vPositon的引脚;
    • glEnableVertextAttribArray传入的参数就是上一步获取的ID,这样,就可以打开这个引脚;
    • 通过glVertexAttribPointer将准备好的顶点数据Vertex Buffer Object传递给引脚1,这样vPosition就收到了;
  • 数据传给2的时候,就会自动传给vColor
    • 通过glGetUniformLocation获取引脚;
    • 通过glUniform4fv传递给vColor即可;

3、变量:

1)vertex shader变量:

在这里插入图片描述

  • 输入变量有:Attribute类型、Uniforms类型(unform变量相当于全局变量)、Samplers类型;
  • 输出变量有:Varying类型(Vertex Shader的输出,可以作为后续的Shader的输入)
  • 内部变量有:gl_Position(这个最重要);
    • 以gl开头的变量都是内部变量;
    • vertex shader需要给gl_Position赋值来确定顶点的位置;
    • 渲染管线会根据gl_Position进行图元装配;

2)fragment shader变量:

在这里插入图片描述

  • Varying类型:作为Fragment Shader的输入,和Vertex Shader的输出一一对应;
  • gl_FragColor:内部变量,是Fragment Shader的输出,保存每个像素的颜色;
    • 确定每个像素的最终颜色;
    • 可以和纹理结合,实现纹理的映射;
    • 还可以通过它实现光照、高亮等颜色特效;

五、绘制一个三角形:

1、步骤:

  • 使用GLSurfaceView创建OpenGL ES环境;

  • 定义顶点着色器和片元着色器以及OpenGL ES程序;

  • 编译顶点着色器和片元着色器;

    • 使用GLES30.glCreateShader()创建Shader对象;
    • 使用GLES30.glShaderSource()绑定Shader和其源代码;
    • 使用GLES30.glCompileShader()编译Shader;
  • 链接OpenGL ES程序;

    • 使用GLES30.glCreateProgram()创建OpenGL ES程序;
    • 使用GLES30.glAttachShader()绑定Shader到OpenGL ES程序;
    • 使用GLES30.glLinkProgram()链接整个OpenGL ES程序;
  • 使用OpenGL ES程序;

    • 调用GLES30.glUseProgram()使用OpenGL ES程序;
    • 传递顶点数据和片元数据

2、创建OpenGL ES环境:

定义GLSurfaceView

// 文件路径:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java
public class GLSurfaceViewTest extends GLSurfaceView {public GLSurfaceViewTest(Context context) {super(context);// 设置OpenGL ES版本(由于3.0兼容2.0,我们使用3.0)setEGLContextClientVersion(3);// 设置渲染器Renderer,函数调用后,里面会启动一个新线程构造EGL环境setRenderer(new GLRenderTest());}
}

MainActivity使用GLSurfaceView:

// 文件路径:com/example/glsurfaceviewdemo/MainActivity.java
public class MainActivity extends AppCompatActivity {private GLSurfaceViewTest mGlSurfaceViewTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mGlSurfaceViewTest = new GLSurfaceViewTest(this);setContentView(mGlSurfaceViewTest);}
}

3、定义顶点着色器和片元着色器:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.java
// 定义的顶点着色器代码private final String mVertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +"  gl_Position = vPosition;" +"}";// 定义的片段着色器代码private final String mFragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";// 定义的三角形顶点坐标数组private final float[] mTriangleCoords = new float[]{0.0f, 0.2f, 0.0f,   // 顶部-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f   // 右下角};// 定义的fragment的颜色数组,表示每个像素的颜色private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};

4、定义OpenGL ES程序:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.java
private int mProgram;
mProgram = GLES30.glCreateProgram();

5、编译着色器:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.javapublic Triangle() {// ...// 2.加载并编译vertexShader和fragmentShaderint vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);// ...}// 定义静态内部类public static class Companion {// 创建并编译着色器public static int compileShader(int type, String shaderCode) {// 创建一个着色器int shader = GLES30.glCreateShader(type);// 将着色器代码设置到着色器对象中GLES30.glShaderSource(shader, shaderCode);// 编译着色器GLES30.glCompileShader(shader);return shader;}}

6、链接OpenGL ES程序:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.javapublic Triangle() {// ...// 4.attach两个编译好的着色器到program当中GLES30.glAttachShader(mProgram, vertexShader);GLES30.glAttachShader(mProgram, fragmentShader);// 5.链接整个programGLES30.glLinkProgram(mProgram);}

7、使用OpenGL ES程序:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.javapublic void draw() {// 使用programGLES30.glUseProgram(mProgram);// 获取顶点着色器的位置句柄mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");// 启用顶点属性数组GLES30.glEnableVertexAttribArray(mPositionHandle);// 准备三角形坐标数据// 重置缓冲区位置mVertexBuffer.position(0);// 指定顶点属性数据的格式和位置GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);// 获取片元着色器的颜色句柄mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");// 设置绘制三角形的颜色GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);// 绘制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);// 禁用顶点属性数组GLES30.glDisableVertexAttribArray(mPositionHandle);}

这就是一个绘制三角形的函数;

8、渲染器调用:

在渲染器中周期性地调用上面的绘制函数。

// 文件路径:com/example/glsurfaceviewdemo/GLRenderTest.java
public class GLRenderTest implements GLSurfaceView.Renderer {private Triangle mTriangle;@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);mTriangle = new Triangle();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES30.glViewport(0, 0,  width, height);}@Overridepublic void onDrawFrame(GL10 gl){GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);mTriangle.draw();}
}

9、运行结果:

在这里插入图片描述

10、附:全部代码:

文件路径:com/example/glsurfaceviewdemo/MainActivity.java

package com.example.glsurfaceviewdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;public class MainActivity extends AppCompatActivity {private GLSurfaceViewTest mGlSurfaceViewTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mGlSurfaceViewTest = new GLSurfaceViewTest(this);setContentView(mGlSurfaceViewTest);}
}

文件路径:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java

package com.example.glsurfaceviewdemo;import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;public class GLSurfaceViewTest extends GLSurfaceView {public GLSurfaceViewTest(Context context) {super(context);// 设置OpenGL ES版本(由于3.0兼容2.0,我们使用3.0)setEGLContextClientVersion(3);// 设置渲染器Renderer,函数调用后,里面会启动一个新线程构造EGL环境setRenderer(new GLRenderTest());}
}

文件路径:com/example/glsurfaceviewdemo/GLRenderTest.java

package com.example.glsurfaceviewdemo;import android.opengl.GLES30;
import android.opengl.GLSurfaceView;import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;public class GLRenderTest implements GLSurfaceView.Renderer {private Triangle mTriangle;@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);mTriangle = new Triangle();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES30.glViewport(0, 0,  width, height);}@Overridepublic void onDrawFrame(GL10 gl){GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);mTriangle.draw();}
}

文件路径:com/example/glsurfaceviewdemo/Triangle.java

package com.example.glsurfaceviewdemo;import android.opengl.GLES30;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;import javax.microedition.khronos.opengles.GL;public class Triangle {public Triangle() {// 1.初始化顶点缓冲区,存储三角形坐标// 为顶点坐标分配DMA内存空间ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);// 设置字节顺序为本地字节顺序(会根据硬件架构自适应大小端)byteBuffer.order(ByteOrder.nativeOrder());// 将字节缓冲区转换为浮点缓冲区mVertexBuffer = byteBuffer.asFloatBuffer();// 将顶点三角形坐标放入缓冲区mVertexBuffer.put(mTriangleCoords);// 设置缓冲区的位置指针到起始位置mVertexBuffer.position(0);// 2.加载并编译vertexShader和fragmentShaderint vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);// 3.创建一个OpenGL程序mProgram = GLES30.glCreateProgram();// 4.attach两个编译好的着色器到program当中GLES30.glAttachShader(mProgram, vertexShader);GLES30.glAttachShader(mProgram, fragmentShader);// 5.链接整个programGLES30.glLinkProgram(mProgram);}// 顶点数据是float类型,因此,使用这个存储private FloatBuffer mVertexBuffer;private int mProgram;// 定义的顶点着色器代码private final String mVertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +"  gl_Position = vPosition;" +"}";// 定义的片段着色器代码private final String mFragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";// 定义的三角形顶点坐标数组private final float[] mTriangleCoords = new float[]{0.0f, 0.2f, 0.0f,   // 顶部-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f   // 右下角};// 定义的fragment的颜色数组,表示每个像素的颜色private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};// 顶点着色器的位置句柄private int mPositionHandle = 0;// 片元着色器的位置句柄private int mColorHandle = 0;private final int COORDS_PER_VERTEX = 3;// 定义静态内部类public static class Companion {// 创建并编译着色器public static int compileShader(int type, String shaderCode) {// 创建一个着色器int shader = GLES30.glCreateShader(type);// 将着色器代码设置到着色器对象中GLES30.glShaderSource(shader, shaderCode);// 编译着色器GLES30.glCompileShader(shader);return shader;}}public void draw() {// 使用programGLES30.glUseProgram(mProgram);// 获取顶点着色器的位置句柄mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");// 启用顶点属性数组GLES30.glEnableVertexAttribArray(mPositionHandle);// 准备三角形坐标数据// 重置缓冲区位置mVertexBuffer.position(0);// 指定顶点属性数据的格式和位置GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);// 获取片元着色器的颜色句柄mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");// 设置绘制三角形的颜色GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);// 绘制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);// 禁用顶点属性数组GLES30.glDisableVertexAttribArray(mPositionHandle);}
}

六、总结:

本文主要介绍了Shader以及GLSL语言,同时画出了一个三角形,但是一般工程中GLSL语言会用单独的文件写,后续我们改进下!

相关文章:

Android显示系统(04)- OpenGL ES - Shader绘制三角形

一、前言&#xff1a; OpenGL 1.0采用固定管线&#xff0c;OpenGL 2.0以上版本重要的改变就是采用了可编程管线&#xff0c;Shader 编程是指使用着色器&#xff08;Shader&#xff09;编写代码来控制图形渲染管线中特定阶段的处理过程。在图形渲染中&#xff0c;着色器是在 GP…...

微信 创建小程序码-有数量限制

获取小程序码&#xff1a;小程序码为圆图&#xff0c;有数量限制。 目录 文档 接口地址 功能描述 注意事项 请求参数 对接 获取小程序码 调用获取 小程序码示例 总结 文档 接口地址 https://api.weixin.qq.com/wxa/getwxacode?access_tokenaccess_token 功能描述 …...

重生之我在异世界学编程之C语言:操作符篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文1. 算术操作符2. 关系&#xff0…...

365天深度学习训练营-第P7周:马铃薯病害识别(VGG-16复现)

文为「365天深度学习训练营」内部文章 参考本文所写记录性文章&#xff0c;请在文章开头带上「&#x1f449;声明」 &#x1f37a; 要求&#xff1a; 自己搭建VGG-16网络框架【达成√】调用官方的VGG-16网络框架【达成√】如何查看模型的参数量以及相关指标【达成√】 &#…...

解密时序数据库的未来:TDengine Open Day技术沙龙精彩回顾

在数字化时代&#xff0c;开源已成为推动技术创新和知识共享的核心力量&#xff0c;尤其在数据领域&#xff0c;开源技术的涌现不仅促进了行业的快速发展&#xff0c;也让更多的开发者和技术爱好者得以参与其中。随着物联网、工业互联网等技术的广泛应用&#xff0c;时序数据库…...

Kubernetes 告警标签规范与最佳实践

1. 前言 在现代化的 Kubernetes 运维环境中,规范的告警标签系统对于快速定位和解决问题至关重要。本文将详细介绍告警标签的设计规范和最佳实践,帮助团队建立高效的告警处理流程。 © ivwdcwso (ID: u012172506) 2. 标签体系设计 2.1 基本概念 告警标签(Labels)是一…...

前端开发 之 15个页面加载特效中【附完整源码】

前端开发 之 15个页面加载特效中【附完整源码】 文章目录 前端开发 之 15个页面加载特效中【附完整源码】八&#xff1a;圆环百分比加载特效1.效果展示2.HTML完整代码 九&#xff1a;毒药罐加载特效1.效果展示2.HTML完整代码 十&#xff1a;无限圆环加载特效1.效果展示2.HTML完…...

rsync+nfs+lrsync服务部署流程

rsyncnfslrsync服务 主机信息 主机角色外网IP内网IP主机名nfs、lsync10.0.0.31176.16.1.31nfs客户端10.0.0.7176.16.1.7web01rsync、nfs10.0.0.41172.16.1.41backup 部署流程 1.backup服务器部署rsync --下载rsync服务 [rootbackup ~]# yum install -y rsync --配置rsync服…...

基于SpringBoot+Vue的宠物咖啡馆系统-无偿分享 (附源码+LW+调试)

目录 1. 项目技术 2. 功能菜单 3. 部分功能截图 4. 研究背景 5. 研究目的 6. 可行性分析 6.1 技术可行性 6.2 经济可行性 6.3 操作可行性 7. 系统设计 7.1 概述 7.2 系统流程和逻辑 7.3 系统结构 8. 数据库设计 8.1 数据库ER图 &#xff08;1&#xff09;宠物订…...

SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2

Caused by: javax.net.ssl.SSLHandshakeException: the server selected protocol version TLS10 is not accepted by client preferences [TLS12] 原因描述&#xff1a;SQLServer 服务器只接受 TLS1.0&#xff0c;但是客户端给的是 TLS1.2 解决方法如下&#xff1a; 打开文件…...

Golang内存模型总结1(mspan、mcache、mcentral、mheap)

1.内存模型 1.1 操作系统存储模型 从上到下分别是寄存器、高速缓存、内存、磁盘&#xff0c;其中越往上速度越快&#xff0c;空间越小&#xff0c;价格越高。 关键词是多级模型和动态切换 1.2 虚拟内存与物理内存 虚拟内存是一种内存管理技术&#xff0c;允许计算机使用比…...

lobeChat安装

一、安装Node.js version > v18.17.0 二、下载 cd F:\AITOOLS\LobeChat git clone https://github.com/lobehub/lobe-chat.git &#xff08;下载要是失败就手动下&#xff1a;https://codeload.github.com/lobehub/lobe-chat/zip/refs/heads/main&#xff09; npm install …...

Android学习8 -- NDK2--练习2(Opencv)

以下是一个简单的安卓项目示例&#xff0c;通过NDK调用OpenCV来处理图像&#xff08;例如&#xff0c;将彩色图像转换为灰度图像&#xff09;。 开发环境 安装 Android Studio&#xff08;支持NDK开发&#xff09;。配置NDK和CMake&#xff08;通过Android Studio的SDK Manage…...

nodejs循环导出多个word表格文档

文章目录 nodejs循环导出多个word表格文档一、文档模板编辑二、安装依赖三、创建导出工具类exportWord.js四、调用五、效果图nodejs循环导出多个word表格文档 结果案例: 一、文档模板编辑 二、安装依赖 // 实现word下载的主要依赖 npm install docxtemplater pizzip --save/…...

elasticsearch-如何给文档新增/更新的字段

文章目录 前言elasticsearch-如何给文档新增/更新的字段1. 如何给某些文档新增/更新的字段2. 给所有文档添加/更新一个新的字段3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且…...

https/http访问接口工具类,附带ssl忽略证书验证,以及head头部的添加-java版

复制即用 package utils;import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.net.ssl.*; import java.io.BufferedReader; import java.io.IOException; impo…...

node.js基础学习-express框架-静态资源中间件express.static(十一)

前言 在 Node.js 应用中&#xff0c;静态资源是指那些不需要服务器动态处理&#xff0c;直接发送给客户端的文件。常见的静态资源包括 HTML 文件、CSS 样式表、JavaScript 脚本、图片&#xff08;如 JPEG、PNG 等&#xff09;、字体文件和音频、视频文件等。这些文件在服务器端…...

Python语法基础---正则表达式

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 我们这个文章所讲述的&#xff0c;也是数据分析的基础文章&#xff0c;正则表达式 首先&#xff0c;我们在开始之前&#xff0c;引出一个问题。也是我们接下来想要解决的问题。…...

Uniapp 微信小程序分享 - 自定义绘制分享图片

技术栈&#xff1a; Uniapp Vue3 简介 因实际业务需求&#xff0c;需要实现微信小程序自定义分享&#xff0c;根据当前数据动态生成&#xff08;绘制&#xff09;分享卡片的图片。 基础分享使用 配置此处不在赘述&#xff0c;可查看上篇博客&#xff1a;Uniapp 微信小程序分…...

鸿蒙技术分享:Navigation页面容器封装-鸿蒙@fw/router框架源码解析(三)

本文是系列文章&#xff0c;其他文章见&#xff1a;鸿蒙fw/router框架源码解析&#xff08;一&#xff09;-router页面管理鸿蒙fw/router框架源码解析&#xff08;二&#xff09;-Navigation页面管理鸿蒙fw/router框架源码解析&#xff08;四&#xff09;-路由Hvigor插件实现原…...

三步入门Log4J 的使用

本篇基于Maven 的Project项目&#xff0c; 快速演示Log4j 的导入和演示。 第一步&#xff1a; 导入Log4j依赖 <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.24.2</version&…...

VBA中类的解读及应用第十八讲:利用类方法,判断任意单元格类型

《VBA中类的解读及应用》教程【10165646】是我推出的第五套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。 类&#xff0c;是非常抽象的&#xff0c;更具研究的价值。随着我们学习、应用VBA的深入&#xff0…...

查询品牌涉及两张表(brand、brand_admin_mapping)

文章目录 1、BrandController2、AdminCommonService3、BrandApiService3、BrandCommonService4、BrandSqlService涉及的表SQL 查询逻辑参数处理执行查询完整 SQL 逻辑参数映射总结 查询指定管理员下的品牌所涉及的表有哪些&#xff1f; http://127.0.0.1:8087/brand/admin/list…...

Eureka和Zookeeper、Nacos的区别

目录 一、Eureka与Zookeeper的区别 适用场景&#xff1a; 架构设计&#xff1a; 功能特性&#xff1a; 社区生态&#xff1a; 二、Eureka与Nacos的区别 接口方式&#xff1a; 实例类型&#xff1a; 健康检测&#xff1a; 服务发现&#xff1a; 一致性与可用性&#…...

微信小程序怎么实现非tabbar页面显示tabbar,自定义组件实现

微信小程序没有发现可以实现非tabbar页面显示tabbar的方法&#xff0c;但是可以在tabbar页面当中隐藏tabbar&#xff0c;使用wx.hideTabBar()方法就可以实现&#xff0c;在非tabbar页面调用wx.showTabBar()方法却会显示失败&#xff0c;不能显示tabbar onLoad() {wx.showTabBar…...

SpringBoot如何使用EasyExcel实现表格导出(简洁快速入门版本)

前言 前面给大家介绍了动态表头的导入&#xff0c;这篇文章给大家介绍如何实现导出 前面给大家介绍了动态表头的导入&#xff0c;我们了解了如何通过EasyExcel灵活地读取结构不固定的Excel文件。这次&#xff0c;我们将目光转向数据导出——即如何将数据以Excel文件的形式输出…...

多种平台上安装部署调试Open5GS(四)

OpenWRT 源码安装 UERANSIM 安装依赖openwrt源码安装cmake其他依赖准备UERANSIM安装测试验证Open5GS 是一个功能完善的开源5G项目,具备5G、4G核心网功能,最新代码支持R17标准, 本系列文章介绍Open5GS在x86、ARM平台上的安装部署方法,并通过搭建UERANSIN、商用5G基站和终端两…...

单片机的基本构成与工作原理

单片机&#xff0c;即微控制器&#xff08;Microcontroller Unit&#xff0c;MCU&#xff09;&#xff0c;是一种将中央处理器(CPU)、存储器(ROM/RAM)、定时/计数器(Timer/Counter)、中断系统、输入输出(I/O)接口等集成在一块芯片上的微型计算机。它具有体积小、功耗低、成本低…...

opencv常用图像处理操作

OpenCV 处理图像的通用流程通常包括以下几个步骤&#xff0c;根据具体需求可以调整或跳过某些步骤。以下是一个通用的框架&#xff1a; 读取图像 加载图像文件到内存中以进行后续处理。 import cv2 读取图像 image cv2.imread(‘image.jpg’) # 彩色图像 gray_image cv2…...

Svn如何切换删除账号

记录Svn清除切换账号 1.首先打开小乌龟的设置如下图 打开设置后单击已保存数据&#xff0c;然后选择清除 接上图选择清除后&#xff0c;就可以打勾选择清除已保存的账号&#xff0c;我们再次检出的就可以切换账号了 &#x1f449;总结 本次记录Svn清除切换账号 如能帮助到你…...