1 前言

​ 本文将介绍 GLSL 中数据类型、数组、结构体、宏、运算符、向量运算、矩阵运算、函数、流程控制、精度限定符、变量限定符(in、out、inout)、函数参数限定符等内容,另外提供了一个 include 工具,方便多文件管理 glsl 代码,实现代码的精简、复用。

​ Unity 中 Shader 介绍详见 → 【Unity3D】Shader常量、变量、结构体、函数,渲染管线介绍详见 → 【OpenGL ES】渲染管线

2 数据类型

2.1 基本数据类型

2.1.1 基本数据类型

类型 说明 案例
void 空类型,即不返回任何值 void fun()
bool 布尔类型 true、false bool a = true;
int 带符号的整数 int a = 0;
float 带符号的浮点数 float a = 1.0;
float b = 2.;
vec2、vec3、vec4 2 维、3 维、4 维浮点数向量 vec2 a = vec2(1., 2.);
vec2 b = vec2(1.); // ⇔ vec2(1., 1.)
vec3 c = vec3(a, 3.); // ⇔ vec3(1., 2., 3.)
bvec2、bvec3、bvec4 2 维、3 维、4 维布尔向量 bvec2 a = bvec2(true, false);
bvec2 b = bvec2(true); // ⇔ bvec2(true, true)
bec3 c = bvec3(a, true); // ⇔ bvec3(true, false, true)
ivec2、ivec3、ivec4 2 维、3 维、4 维整数向量 ivec2 a = ivec2(1, 2);
ivec2 b = ivec2(1); // ⇔ ivec2(1, 1)
ivec3 c = ivec3(a, 3); // ⇔ ivec3(1, 2, 3)
mat2、mat3、mat4 2x2、3x3、4x4 浮点数矩阵 (列向量) mat2 a = mat2(1., 2., 3., 4.);
mat2 b = mat2(1.); // ⇔ mat2(1., 1., 1., 1.)
sampler2D 2D 纹理 sampler2D sampler;
samplerCube 盒纹理 samplerCube sampler;

​ 说明:mat2、mat3、mat4 中的元素都是按照列向量的顺序排列的,即 mat2 m = mat2(m11, m21, m12, m22) 对应的公式如下。

2.1.2 向量分量访问

​ GLSL 中的向量(vec2、vec3、vec4)可以表示一个空间坐标 (x, y, z, w),也可以表示一个颜色 (r, g, b, a),还可以表示一个纹理坐标 (s, t ,p, q),所以 GLSL 提供了多样的分量访问方式。

vec4 v = vec4(1.0, 2.0, 3.0, 4.0);
float x1 = v.x; // 1.0
float x2 = v.r; // 1.0
float x3 = v.s; // 1.0
float x4 = v[0]; // 1.0 vec3 xyz = v.xyz; // vec3(1.0, 2.0, 3.0)
vec3 stq = v.stq; // vec3(1.0, 2.0, 3.0)
vec3 rgb = v.rgb; // vec3(1.0, 2.0, 3.0)
vec3 abc = vec3(v[0], v[1], v[2]); // vec3(1.0, 2.0, 3.0)

2.1.3 数据类型转换

​ GLSL 可以使用构造函数进行显式类型转换。

// 0或0.0转换为false, 非0转换为true
bool a1 = bool(1.0); // true
bool a2 = bool(0); // false // true转换为1或1.0, false转换为0或0.0
int a3 = int(true); // 1
float a4 = float(false); // 0.0 int a5 = int(2.0); // 2
float a6 = float(1); // 1.0

2.2 数组

​ GLSL 只支持一维数组。

// float 数组
float[3] a = float[] (1.0, 2.0, 3.0);
float b[3] = float[] (1.0, 2.0, 3.0);
float c[3] = float[3] (1.0, 2.0, 3.0); // vec 数组
vec2[2] d = vec2[] (vec2(0.0), vec2(1.0));

2.3 结构体

struct light {
vec4 color;
vec3 pos;
};
const light lgt = light(vec4(1.0), vec3(0.0));

​ 说明:结构体中的字段不可用 const 修饰。

2.4 内置变量

范围 变量 说明
顶点着色器的 Output 变量 highp vec4 gl_Position; 顶点坐标信息
顶点着色器的 Output 变量 mediump float gl_PointSize; 顶点大小 (只在 GL_POINTS 图元模式下有效)
片元着色器的 Input 变量 mediump vec4 gl_FragCoord; 片元在屏幕空间的坐标,假设屏幕宽高分别为 width、height
x: 片元的x坐标,值域 [0, width - 1]
y: 片元的x坐标,值域 [0, height - 1]
z: 片元的深度坐标,值域 [0, 1]
w: 总是 1,通常用于透视除法
片元着色器的 Input 变量 bool gl_FrontFacing; 标志当前图元是否是正面图元的一部分
片元着色器的 Output 变量 mediump vec4 gl_FragColor; 设置当前片点的颜色

2.5 宏

​ 与 C 语言一样,GLSL 中也可以通过 #define 定义宏,如下。

#define PI 3.14159265359

​ 另外,GLSL 也提供了一些内置宏。

__LINE__ // 当前源码中的行号
__VERSION__ // 当前glsl版本号, 如: 300
GL_ES // 当前运行环境是否是 OPGL ES, 1 表示是
GL_FRAGMENT_PRECISION_HIGH // 当前系统的片元着色器是否支持高浮点精度, 1表示支持

3 运算符

3.1 基础运算符

优先级 (越小越高) 运算符 说明 结合性
1 () 聚组: a * (b + c) N/A
2 []
()
.
++ --
数组下标
方法参数: fun(arg1, arg2)
属性访问
自增 (a++) / 自减 (a--)
L - R
3 ++ --
+ -
!
自增 (++a) / 自减 (--a)
正 (+a) 负 (-a) 号
取反 (!a)
R - L
4 * /
%
乘法 / 除法运算
整数求余运算 (浮点数求余用 mod 函数)
L - R
5 + - 加法 / 减法运算 L - R
7 < > <= >= 关系运算符 L - R
8 == != 相等性运算符 L - R
12 && 逻辑与 L - R
13 ^^ 逻辑排他或 (用处基本等于 !=) L - R
14 || 逻辑或 L - R
15 ? : 三目运算符 L - R
16 = += -= *= /= 赋值和复合赋值 L - R
17 , 顺序分配运算 L - R

​ 说明:GLSL 中没有隐式类型转换,因此任何表达式左右两侧的类型必须一致,以下表达式都是错误的。

// 以下代码运行时会报错
int a = 2.;
int b = 1. + 2;
float c = 2;
float d = 2. + 1;
bool e = 0;
vec2 f = vec2(1., 2.) * 2;

3.2 向量运算符

// 标量与向量运算
vec2 a = vec2(1., 2.) + 3.; // vec2(4., 5.)
vec2 b = 3. + vec2(1., 2.); // vec2(4., 5.)
vec2 c = vec2(1., 2.) * 3.; // vec2(3., 6.)
vec2 d = 3. * vec2(1., 2.); // vec2(3., 6.) // 向量与向量运算
vec2 e = vec2(1., 2.) + vec2(3., 4.); // vec2(4., 6.)
vec2 f = vec2(1., 2.) * vec2(3., 4.); // vec2(3., 8.)

3.3 矩阵运算符

// 标量与矩阵运算
mat2 a = mat2(1.) + 2.; // mat2(3.)
mat2 b = 2. + mat2(1.); // mat2(3.)
mat2 c = mat2(1.) * 2.; // mat2(2.)
mat2 d = 2. * mat2(1.); // mat2(2.) // 向量与矩阵运算
vec2 e = vec2(1., 2.) * mat2(1., 2., 3., 4.); // vec2(5., 11.)
vec2 f = mat2(1., 2., 3., 4.) * vec2(1., 2.); // vec2(7., 10.) // 矩阵与矩阵运算(矩阵对应元素运算)
mat2 g = mat2(1.) + mat2(2.); // mat2(3.)
mat2 h = matrixCompMult(mat2(1.), mat2(2.)); // mat2(2.) // 矩阵与矩阵运算(矩阵乘法)
mat2 i = mat2(1., 2., 3., 4.) * mat2(5., 6., 7., 8.); // mat2(23., 34., 31., 46.)
mat2 j = mat2(5., 6., 7., 8.) * mat2(1., 2., 3., 4.); // mat2(19., 22., 43., 50.)

4 函数

4.1 自定义函数

​ GLSL 允许在程序的最外部声明函数,函数不能嵌套、不能递归调用,且必须声明返回值类型(无返回值时声明为 void)在其他方面 GLSL 函数与 C 语言函数非常类似。

vec4 getPosition() {
vec4 pos = vec4(0.,0.,0.,1.);
return pos;
} void doubleSize(inout float size) {
size = size * 2.0;
}

4.2 内置函数

1)数值运算

sign(x)、abs(x) // 符号、绝对值
min(a, b)、max(a, b) // 最值函数
ceil(x)、floor(x)、round(x) // 取整函数
fract(x) // 取小数部分
mod(x, y) // 取余数
sqrt(x)、pow(x)、inversesqrt(x) // 幂函数, inversesqrt(x)=1/sqrt(x)
exp(x)、exp2(x) // 指数函数(e^x、2^x)
log(x)、log2(x) // 对数函数
degrees(x)、radians(x) // 角度转换函数
sin(x)、cos(x)、tan(x)、asin(x)、acos(x)、atan(x) // 三角函数
sinh(x)、cosh(x)、tanh(x) // 双曲线函数
clamp(x, min, max) // 将x约束在min和max之间, 超过边界就取边界值
smoothstep(min, max, x) // 平滑比例, 公式: k=saturate((x-min)/(max-min)), y=k*k*(3-2*k)
mix(a, b, f) // 混合, 公式: y=(1-f)*a+f*b
step(a, b) // 如果a>b, 返回0; 如果a<=b, 返回1; 当a、b是向量时, 每个分量独立判断, 如: step(fixed2(1,1),fixed(0,2))=(0,1)

​ 说明:以上函数输入的可以是:float、vec2、vec3、vec4,且可以逐分量操作;对于整数求余运算,只能用 %;对于浮点数求余运算,只能用 mod。

2)逻辑运算

bvec z = lessThan(x, y) // 逐分量比较x < y, 将结果写入z的对应位置
bvec z = lessThanEqual(x, y) // 逐分量比较x <= y, 将结果写入z的对应位置
bvec z = greaterThan(x, y) // 逐分量比较x > y, 将结果写入z的对应位置
bvec z = greaterThanEqual(x, y) // 逐分量比较x >= y, 将结果写入z的对应位置
bvec z = equal(x, y) // 逐分量比较x == y, 将结果写入z的对应位置
bvec z = notEqual(x, y) // 逐分量比较x != y, 将结果写入z的对应位置
bvec y = not(x) // bool矢量的逐分量取反
bool y = any(x) // 如果x的任意一个分量是true, 则结果为true
bool y = all(x) // 如果x的所有分量是true, 则结果为true

3)向量运算

distance(pos1, pos2) // 计算pos1与pos2之间的距离
length(vec) // 计算向量的模长
normalize(vec) // 计算向量的单位向量
dot(v1, v2) // 向量点乘
cross(v1, v2) // 向量叉乘
reflect(i, n) // 根据入射向量和法线向量, 计算反射向量(i和n不需要归一化)
refract(i, n, ratio); // 根据入射向量、法线向量、折射率比值, 计算折射向量(i和n需要归一化, ratio为入射介质折射率/折射介质折射率, 或sin(折射角)/sin(入射角))

4)矩阵运算

// 矩阵与矩阵运算(矩阵对应元素运算)
mat2 a = mat2(1.) + mat2(2.); // mat2(3.)
mat2 b = matrixCompMult(mat2(1.), mat2(2.)); // mat2(2.) // 矩阵与矩阵运算(矩阵乘法)
mat2 c = mat2(1., 2., 3., 4.) * mat2(5., 6., 7., 8.); // mat2(23., 34., 31., 46.)
mat2 d = mat2(5., 6., 7., 8.) * mat2(1., 2., 3., 4.); // mat2(19., 22., 43., 50.)

5)纹理查询函数

vec4 texture(sampler2D sampler, vec2 coord);
vec4 texture2D(sampler2D sampler, vec2 coord);
vec4 texture2DProj(sampler2D sampler, vec3 coord);
vec4 texture2DProj(sampler2D sampler, vec4 coord);
vec4 textureCube(samplerCube sampler, vec3 coord);

5 流程控制

​ GLSL 的流控制与 C 语言非常相似,主要有 if、for、while、continue、break,不同的是 GLSL 中多了 discard。使用 discard 会退出片元着色器,不会执行后面的操作,片元也不会写入帧缓冲区。

for (int i = 0; i < 10; i++) {
sum += a[i];
if (sum > 5.0)
break;
} while (i < 10) {
sum += a[i];
if (i % 3 == 1)
continue;
i++;
} do {
sum += a[i];
if (sum > 5.0)
discard;
i++;
} while (i < 10)

6 限定符

6.1 精度限定符

​ 片元着色器中,对于浮点数 GLSL 有 highp(高)、mediump(中)、lowp(低) 三种精度。

lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;

​ 在片元着色器的第一行加上 precision highp float,表示设定了默认的精度,所有没有显式表明精度的变量都会按照默认精度处理。

precision highp float;

​ 通过判断系统环境,来选择合适的精度。

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif

6.2 变量限定符

限定符 说明
none 默认的变量限定符,可省略,可读写。
const 修饰的变量为只读类型,变量在定义时必须初始化。
attribute 只能在顶点着色器中使用,修饰全局只读变量,一般用于修饰顶点属性,如:顶点的位置、法线、纹理坐标、颜色等。
uniform 修饰全局只读变量,一般用于修饰程序传递给 shader 的变量,如:屏幕宽高比、光源位置等。
varying 修饰需要进行光栅化插值的变量,在片元着色器中是只读的,如:纹理坐标等。
// 顶点着色器
attribute vec4 a_position;
attribute vec2 a_texCoord0;
varying vec2 v_texCoord0; void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord0 = a_texCoord0;
} // 片元着色器
precision highp float;
uniform sampler2D u_texture;
varying vec2 v_texCoord0; void main() {
gl_FragColor = texture(u_texture, v_texCoord0);
}

6.3 函数参数限定符

​ 函数的参数默认以拷贝的形式传递,即值传递,我们可以为参数添加限定符实现引用传递,GLSL 中提供的参数限定符如下。

限定符 说明
in 默认的限定符,参数是值传递,在函数中可读写。
out 参数是引用传递,在函数中只能写(write-only)。
inout 参数是引用传递,在函数中可读写(read-write)。

​ 除了函数的参数可以用 in、out、inout 修饰,全局变量也可以用这些限定符修饰,如下。

// 顶点着色器
in vec3 a_position;
in vec2 a_texCoord0;
out vec2 v_texCoord0; void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord0 = a_texCoord0;
} // 片元着色器
precision highp float;
uniform sampler2D u_texture;
in vec2 v_texCoord0;
out vec4 o_fragColor; void main() {
o_fragColor = texture(u_texture, v_texCoord0);
}

7 include

​ GLSL 中没有提供 #include 功能,要想实现一个 glsl 文件依赖另一个 glsl 文件,可以使用以下工具加载 glsl 字符串。

​ ShaderUtils.java

import android.content.Context;

import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* Shader工具类
* @author little fat sheep
*/
public class ShaderUtils {
// 匹配: #include <xxx> 或 #include “xxx”, 并且不以"//"开头
private static final String INCLUDE_REGEX = "(?m)^(?!//\\s*)#include\\s+(<([^>]+)>|\"([^\"]+)\")"; /**
* 通过asset加载shader
* @param vertexShader 顶点着色器路径, 如: "shaders/origin_vert.glsl"
* @param fragmentShader 片元着色器路径, 如: "shaders/origin_frag.glsl"
*/
public static String[] loadShader(Context context, String vertexShader, String fragmentShader) {
String vertex = StringUtils.loadString(context, vertexShader);
String fragment = StringUtils.loadString(context, fragmentShader);
String vertex1 = replaceIncludeFiles(context, vertex);
String fragment1 = replaceIncludeFiles(context, fragment);
return new String[] { vertex1, fragment1 };
} /**
* 通过资源id加载shader
* @param vertexShader 顶点着色器资源id, 如: R.raw.origin_vertex
* @param fragmentShader 片元着色器资源id, 如: R.raw.origin_fragment
*/
public static String[] loadShader(Context context, int vertexShader, int fragmentShader) {
String vertex = StringUtils.loadString(context, vertexShader);
String fragment = StringUtils.loadString(context, fragmentShader);
String vertex1 = replaceIncludeFiles(context, vertex);
String fragment1 = replaceIncludeFiles(context, fragment);
return new String[] { vertex1, fragment1 };
} /**
* 将shader字符串中#include的文件替换为文件内容
*/
public static String replaceIncludeFiles(Context context, String shaderContent) {
Pattern pattern = Pattern.compile(INCLUDE_REGEX);
HashSet<String> set = new HashSet<>(); // 用于去掉重复的include
return replaceIncludeFiles(context, shaderContent, pattern, set);
} /**
* 将shader字符串中#include的文件替换为文件内容(include的文件中可能也有include, 需要递归调用)
*/
private static String replaceIncludeFiles(Context context, String shaderContent, Pattern pattern, HashSet<String> set) {
Matcher matcher = pattern.matcher(shaderContent);
StringBuffer sb = new StringBuffer(shaderContent.length());
while (matcher.find()) {
String angleBrackets = matcher.group(2); // 尖括号内的路径
String quotationMarks = matcher.group(3); // 引号内的路径
String file = angleBrackets != null ? angleBrackets : quotationMarks;
if (set.contains(file)) {
matcher.appendReplacement(sb, ""); // 删除重复的include
} else {
set.add(file);
String includeShader = StringUtils.loadString(context, file); // 加载include文件中的字符串
String quoteShader = Matcher.quoteReplacement(includeShader); // 替换字符串中的转义字符
String wholeShader = replaceIncludeFiles(context, quoteShader, pattern, set); // 递归替换字串中的include
matcher.appendReplacement(sb, wholeShader); // 将字符串添加到sb中
}
}
matcher.appendTail(sb);
return sb.toString();
}
}

​ StringUtils.java

import android.content.Context;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; /**
* 字符串工具类
* @author little fat sheep
*/
public class StringUtils {
/**
* 根据资源路径读取字符串
* @param assetPath 资源路径, 如: "shaders/origin_vert.glsl"
*/
public static String loadString(Context context, String assetPath) {
String str = "";
try (InputStream inputStream = context.getAssets().open(assetPath)) {
str = loadString(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return str;
} /**
* 根据资源id读取字符串
* @param rawId 资源id, 如: R.raw.origin_vertex
*/
public static String loadString(Context context, int rawId) {
String str = "";
try (InputStream inputStream = context.getResources().openRawResource(rawId)) {
str = loadString(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return str;
} private static String loadString(InputStream inputStream) {
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}

​ 声明:本文转自【OpenGL ES】GLSL基础语法

【OpenGL ES】GLSL基础语法的更多相关文章

  1. 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法

    一.前期基础储备笔者之前的四篇文综述了Android中使用OpenGL ES绘制基本图形和实现了简单的相机预览,初次接触OpenGL ES开发的读者可能对其中新的概念比较迷惑,尤其是其中的顶点着色器( ...

  2. 短视频图像处理 OpenGL ES 实践

    2017年,短视频正以其丰富的内容表现力和时间碎片化的特点,快速崛起,而短视频最具可玩性之处就在支持人脸识别的动态贴图和各种不同效果的美颜.滤镜等.那短视频动态贴纸.滤镜.美颜等功能究竟是如何实现的呢 ...

  3. Android OpenGL ES 开发(八): OpenGL ES 着色器语言GLSL

    前面的文章主要是整理的Android 官方文档对OpenGL ES支持的介绍.通过之前的文章,我们基本上可以完成的基本的形状的绘制. 这是本人做的整理笔记: https://github.com/re ...

  4. OpenGL ES 3.0 基础知识

    首先要了解OpenGL的图形管线有哪些内容,再分别去了解其中的相关的关系: 管线分别包括了顶点缓冲区/数组对象,定点着色器,纹理,片段着色器,变换反馈,图元装配,光栅化,逐片段操作,帧缓冲区.其中顶点 ...

  5. OpenGL ES 3.0 帧缓冲区对象基础知识

    最近在帧缓冲区对象这里卡了一下,不过前面已经了解了相关的OpenGL ES的知识,现在再去了解就感觉轻松多了.现在就进行总结. 基础知识 我们知道,在应用程序调用任何的OpenGL ES命令之前,需要 ...

  6. 梳理 Opengl ES 3.0 (二)剖析一个GLSL程序

    OpenGL ES shading language 3.0 也被称作 GLSL,是个 C风格的编程语言. Opengl ES 3.0内部有两种可编程处理单元,即Vertex processor和Fr ...

  7. OpenGL ES: (2) OpenGL ES 与 EGL、GLSL的关系

    OpenGL ES 是负责 GPU 工作的,目的是通过 GPU 计算,得到一张图片,这张图片在内存中其实就是一块 buffer,存储有每个点的颜色信息等.而这张图片最终是要显示到屏幕上,所以还需要具体 ...

  8. Node.js 全栈开发(二)——ES 201x 新语法的使用之基础篇

    在讲 ES 2015 新语法之前,先来说一下为什么叫 ES.JavaScript 是这门语言的名称,它有一个为它制定标准化的组织 European Computer Manufacturers Ass ...

  9. 04: OpenGL ES 基础教程03 纹理

    前言 1:常用类: 1:纹理的作用 正文 一:常用类 上下文 顶点数据缓存 着色器 baseEffect 一:纹理 1.1:   纹理可以控制渲染的每个像素的颜色. 1.2: 纹素:与像素一样,保存每 ...

  10. 03: OpenGL ES 基础教程02 使用OpenGL ES 基本步骤

    第二章:让硬件为你工作(OpenGL ES 应用实践指南 iOS卷) 前言: 1:使用OpenGL ES 基本步骤 2:绘制三角形 3:效果 正文: 一:使用OpenGL ES 基本步骤 1:生成缓存 ...

随机推荐

  1. KubeSphere 3.3.0 离线安装教程

    作者:老Z,中电信数智科技有限公司山东分公司运维架构师,云原生爱好者,目前专注于云原生运维,云原生领域技术栈涉及Kubernetes.KubeSphere.DevOps.OpenStack.Ansib ...

  2. Spring中的事务提交事件

    如果想在spring操作事务结束后执行一些代码,应该怎么办? 为什么要这样?比如我们在事务中给其他系统发了消息,期望事务提交后过一会收到这个系统的回应,然后操作刚刚提交的数据.但是如果回应来的太快就像 ...

  3. C语言之常量

    常量 常量,常量表达式和const关键字修饰的只读变量都存储在.rodata只读数据段中 1.字面值常量 literal 整形字面值 123, 0xff00ff 字符常量,类型总是 int, 'a', ...

  4. 使用NodeJS 搭建 Vue + TypeScipt 快速构建工具

    使用 NodeJS 搭建 Vue + TypeScipt 快速构建工具 前言: 为保证使用 Typescript 开发 Vue 的规范性和开发效率,添加组件.页面.路由.store 的时候尽量使用工具 ...

  5. CMU15445学习记录

    写在开头 我已经深刻意识到找工作的不易,因此想要开始恶补计算机基础知识,以此作为起点 由于考研的时候学过408综合,因此试图逃课CSAPP并直接开始CMU,发表此篇用作记录. 关于底层原理 原理 数据 ...

  6. Spring3.0核心组件的源码简单分析

    前言 本文结合Spring3.0的源码进行简单的介绍,这里的核心组件不是我们常见所谓的IOC和AOP,而是以Spring3.0发布的开发包为切入点,当然Spring3.0以后的各个版本基本上差不多,思 ...

  7. pycharm之常用插件

    参考:http://pycharm.iswbm.com/zh_CN/latest/ 1. Key Promoter X 如果让我给新手推荐一个 PyCharm 必装插件,那一定是 Key Promot ...

  8. 【项目学习】Pendle 项目的简单调研

    项目介绍 将 1 ETH 质押成 1 stETH,年利率为 5%,那么到期后 1 stETH 就能收回 1 ETH(本金)+ 0.05 ETH(收益).而 Pendle 所做的就是把 1 stETH ...

  9. kube-apiserver 高可用,keepalived + haproxy

    为什么要做高可用 在生产环境中,kubernetes 集群中会多多个 master 节点,每个 master 节点上都会部署 kube-apiserver 服务,实现高可用.但是 client 访问 ...

  10. PCB设计AD规则设置(按照嘉立创设置)

    本文转载自https://blog.csdn.net/subtitle_/article/details/121648972 官方参考https://www.jlc.com/portal/vtechn ...