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. KubeKey 部署 K8s v1.28.8 实战

    在某些生产环境下,我们仅需要一个原生的 K8s 集群,无需部署 KubeSphere 这样的图形化管理控制台.在我们已有的技术栈里,已经习惯了利用 KubeKey 部署 KubeSphere 和 K8 ...

  2. curl命令详解【转载】

    本文转载自curl 的用法指南-阮一峰 简介 curl 是常用的命令行工具,用来请求 Web 服务器.它的名字就是客户端(client)的 URL 工具的意思. 它的功能非常强大,命令行参数多达几十种 ...

  3. 全中国有多少公网IP地址?

    之前为了准备CTF比赛,搞了一个云服务器,顺便申请了一个公网IP. 看着申请到的IP地址,我突然想到了一个问题:不是早就说IP地址已经耗尽了吗,为什么我随便就能申请到,是谁在负责IP地址的管理分配,咱 ...

  4. Nuxt.js 应用中的 components:dirs 事件钩子详解

    title: Nuxt.js 应用中的 components:dirs 事件钩子详解 date: 2024/10/31 updated: 2024/10/31 author: cmdragon exc ...

  5. 强化学习性能指标之一:以训练的episodes数和训练所需样本数作为评价算法性能的指标

    在强化学习领域,一般都是限定训练的episodes数和训练所需样本数的,也就是说在进行算法性能对比的时候各个算法都是在相同的训练的episodes数和训练所需样本数的,但是如果我们在算法得分数保持相同 ...

  6. Lattice、Xilinx FPGA reg初始化赋值问题

    一.起因 最近在开发Lattice的一款低功耗FPGA时,遇到了reg初始化复位问题,经过在网上搜寻相关资料整理如下: 二.FPGA中reg的初始化方式 在定义时初始化,例如: reg r_test ...

  7. 基于Java+SpringBoot心理测评心理测试系统功能实现九

    一.前言介绍: 1.1 项目摘要 心理测评和心理测试系统在当代社会中扮演着越来越重要的角色.随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态.诊断心理问题.制定心理治疗方案的工 ...

  8. highcharts在vue中的应用

    1.安装命令 npm install highcharts --save 2.在页面中按需引入 import Highcharts from 'highcharts/highstock'; impor ...

  9. 根据Xml节点名获取Xpath

    我们经常使用Xpath去获取Xml中某个节点的值,但是有时候我们需要反过来,根据Xml的节点名来获取对应的Xpath. 找了一下,几乎所有的Xml库都没有提供类似的功能,这里我们就简单说一说这玩意咋实 ...

  10. 专为移动端设计的PDF阅读器-小白PDF阅读器

    以前在手机端看扫描版的pdf电子书,要么放大后用手指来回拖动着看,要么将手机横屏看,阅读体验非常差!后来就想自己做一款能自动重排版的pdf阅读器,于是小白PDF阅读器就诞生了! 小白PDF阅读器的重排 ...