前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【前端er入门Shader系列】02—GLSL语言基础

【前端er入门Shader系列】02—GLSL语言基础

原创
作者头像
CS逍遥剑仙
修改于 2025-02-06 09:01:46
修改于 2025-02-06 09:01:46
79300
代码可运行
举报
文章被收录于专栏:禅林阆苑禅林阆苑
运行总次数:0
代码可运行

【前端er入门Shader系列】02—GLSL语言基础

Shader 一般由顶点着色器和片段着色器成对使用,GLSL 则是编写 Shader 着色器的语言,而 GLSL ES 是在 OpenGL Shader 着色器语言的基础上针对移动端和嵌入式设备的简化版。本章介绍 GLSL 语言相关语法。

1. GLSL数据类型

变量类型

说明

Cocos Shader 中的默认值

Cocos Shader 中的可选项

bool

布尔型标量数据类型

false

int/ivec2/ivec3/ivec4

包含 1/2/3/4 个整型向量

0/0, 0/0, 0, 0/0, 0, 0, 0

float/vec2/vec3/vec4

包含 1,2,3,4 个浮点型向量

0/0, 0/0, 0, 0/0, 0, 0, 0

sampler2D

表示 2D 纹理

default

black、grey、white、normal、default

samplerCube

表示立方体纹理

default-cube

black-cube、white-cube、default-cube

mat2..3

表示 2x2 和 3x3 的矩阵

不可用

mat4

表示 4x4 的矩阵

1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1

Cocos Shader 是指 shader 在 cocos creator 引擎中的应用,在系列第五章中会介绍

glsl 语言的语法非常简单,数据类型分三大类,但不支持字符串类型:

1.1 标量(字面量)

(1) 数字类型 int / float

需要注意,和 js 不同,1 和 1.0 类型不同,不能一起运算

代码语言:glsl
AI代码解释
复制
float floatValue = 1.0;

(2) 布尔类型 bool

包括 true / false

(3) 类型转换

float(int) / float(bool) / int(float) / int(bool) / bool(int) / bool(float)

(4) 计算

+-*/ 运算左右值类型需一致

支持 ++--+=-=*=/=

比较符 <><=>===!=

其他 !&&^^、三元选择

1.2 向量 & 矩阵

(1) 浮点型向量Vector vec2 / vec3 / vec4

整型向量 ivec2 / ivec3 / ivec4

布尔型向量 bvec2 / bvec3 / bvec4

代码语言:glsl
AI代码解释
复制
vec4 myVec4 = vec4(1.0);              // myVec4 = {1.0, 1.0, 1.0, 1.0}
vec4 color = vec4(1.0, 1.0, myVec2);
// 向量取值方式
// color.x, color.y, color.z, color.w // xyzw
// color.r, color.g, color.b, color.a // rgba
// color.s, color.t, color.p, color.q // stpq
vec3 myVec3_0 = color.xyz;

(2) 矩阵Matrix mat2 / mat3 / mat4

代码语言:glsl
AI代码解释
复制
// [构造方式1] 矩阵可以由单个标量从左到右进行构造
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0, // 第一列
  0.0, 1.0, 0.0, 0.0, // 第二列
  0.0, 0.0, 1.0, 0.0, // 第三列
  0.5, 0.0, 0.0, 1.0 // 末尾不能带,
);
// [构造方式2] 若只为矩阵构造器提供了一个标量,则该值会构造矩阵对角线上的值
mat4(1.0); // myMat4 = {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0}
// [构造方式3] 矩阵可以由多个向量构造
vec2 col1 = vec2(1.0, 0.0);
vec2 col2 = vec2(1.0, 0.0);
mat2 matrix2x2 = mat2(coll1, col2);
// 注意,定义的矩阵和实际矩阵是行列转置的关系
// 取值方式
// 获取【第一列】 (1.0, 2.0, 3.0, 4.0)
vec4 v4 = m4[0];
// 获取【第一列第二个】2.0
float m01 = m4[0][1];
// 获取【第一列第二个】2.0
float m01_2 = m4[1].y;
// 注意:不能使用未经const修饰的变量作为索引值
int i = 0;
// 错误 vec4 v4c = m4[i]; // 可以使用 const int i = 0;

1.3 sampler(取样器)

纹理句柄包含 sampler2D / samplerCube 只能是 uniform 类型变量

代码语言:glsl
AI代码解释
复制
uniform sampler2D u_Sampler;

2. 控制流程 & 函数

2.1 数组

代码语言:glsl
AI代码解释
复制
float floatArray[2]; // 数组必须声明长度
vec4 vec4Array[2]; // 不支持多维数组
// 数组不能在声明时一次性初始化,只能显式地对每个元素初始化
vec4Array[0] = vec4(1.0, 2.0, 3.0, 4.0); // 必须由常量表达式初始化
vec4Array[1] = vec4(2.0, 3.0, 4.0, 5.0);

2.2 结构体

代码语言:glsl
AI代码解释
复制
// 定义结构体light,包含两个成员变量,同时声明变量l1(可选)
struct light {
  vec4 color;
  vec3 position;
} l1;
light l2, l3;
l1 = light(vec4(0.0, 0.0, 0.0, 1.0), vec3(8.0, 2.0, 0.0));
vec4 color = l1.color;
// 结构体支持赋值(=)和比较(==和!=),但不适用含数组或纹理成员的结构体

2.3 循环/条件判断/函数

  • if-else / switch-case
  • for / while / do-while
  • break / continue / return

关注下面代码中 for / if / else / continue / break / discard 用法,其中 discard 仅适用片元色器,表示放弃当前片元直接处理下一片元。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const vertexShader = /*glsl*/`
attribute vec3 a_position;
void main() {
  float deg = radians(45.0); // 内置函数radians
  mat4 rotateMatrix = mat4(
    cos(deg), sin(deg), 0.0, 0.0, // 内置函数sin/cos
    -sin(deg), cos(deg), 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0
  );
  gl_Position = rotateMatrix * vec4(a_position, 1.0);
}`;

const fragmentShader = /*glsl*/ `
precision mediump float;
// glsl函数,计算两个点的距离
float getDiatance(vec2 p1, vec2 p2) {
  return pow(pow(p1.x - p2.x, 2.0) + pow(p1.y - p2.y, 2.0), 0.5);
}
void main() {
  float x = (gl_FragCoord.x / 400.0 - 0.5) * 2.0;
  float y = (gl_FragCoord.y / 400.0 - 0.5) * 2.0;
  vec3 color1 = vec3(1.0, 1.0, 1.0);
  vec3 color2 = vec3(0.0, 0.0, 1.0);
  float dis = distance(vec2(x, y), vec2(0.0, 0.0));
  /* 条件判断 */
  if (dis > 0.4) {
    gl_FragColor = vec4(color1 - color2, 1.0);
    // 仅适用片元色器,表示放弃当前片元直接处理下一片元
    discard;
  } else {
    gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
  }
  /* 循环 */
  for (int i = 0; i < 10; i++) {
    if (i == 1) {
        continue;
    }
    if (i == 8) {
        break;
    }
  }
}`;

3. 内置函数

代码语言:glsl
AI代码解释
复制
【角度函数】
radians 角度制转孤度制
degrees 弧度制转角度制
【三角函数】
sin 正弦
cos 余弦
tan 正切
asin 反正弦
acos 反余弦
atan 反正切
【指数函数】
pow 开方
exp 自然指数
log 自然对数
exp2 2的x方
log2 以2为底对数
sqrt 开平方
inversesqrt 平开方的倒数
【通用函数】
abs 绝对值
min 最小值
max 最大值
mod 取余数
sign 取下负号
floor 向下取整
ceil 向上取整
clamp 限定范围
mix 线性内插
step 步进函数
smoothstep 艾米内插步进
fract 获取最小数部分
【几何函数】
length 矢量长度
distance 两点间距离
dot 内积
cross 外积
normalize 归一化
reflect 矢量反射
faceforward 使向量"朝前"
【矩阵函数】
matrixCmpMult 逐元素乘法
【矢量函数】
lessThan 逐元素小于
lessThanEqual 逐元素小于等于
greaterThan 逐元素大于
greaterThanEqual 逐元素等于
equal 逐元素等于
notEqual 逐元素不等
any 任一元素为true则为true
all 所有元素为true则为true
not 逐元素取补
【纹理查询函数】
texture2D 在二维纹理中获取纹素
textureCube 在立方体纹理中获取纹素
texture2DProj texture2D 的投影版本
texture2DLod texture2D的金字塔版本
textureCubeLod textureCube的金字塔版本
texture2DProjLod textureCubeLod的投影版本

4. 存储限定字

4.1 const

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 常量限定
const vec4 red = vec4(1.0, 0.0, 0.0, 1.0);

4.2 attribute / uniform / varying

对应三种数据传递的存储限定字,详情见后。

attributevarying 变量类型只能是:floatvec2vec3vec4mat2mat3mat4

uniform 变量类型可以是除结构体外的任意类型

5. Shader初始化函数封装

通过上述代码不难看出,Shader 的初始化过程需要编写较多固定的代码,通过函数封装可以简化调用逻辑,精力专注于两段 Shader 的编写,初始化函数封装于 initShaders.js,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export default function initShaders(gl, vertexSource, fragmentSource) {
  const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
  // 将创建的顶点着色器和片元着色器绑定到着色程序上,顶点着色器和片段着色器需要成对提供
  const program = createProgram(gl, vertexShader, fragmentShader);
  if (program) {
    gl.useProgram(program);
    gl.program = program; // 挂载gl对象上方便获取
    return true;
  } else {
    console.log("create program error.");
    return false;
  }
}

/**
 * 创建着色器
 * @param gl WebGL上下文
 * @param type 着色器类型
 * @param source 着色器文本
 */
function createShader(gl, type, source) {
  // 根据 type 创建着色器
  const shader = gl.createShader(type);
  // 绑定内容文本 source
  gl.shaderSource(shader, source);
  // 编译着色器(将文本内容转换成着色器程序)
  gl.compileShader(shader);
  // 获取编译后的状态
  const state = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (state) {
    return shader;
  } else {
    // 获取当前着色器相关信息
    const error = gl.getShaderInfoLog(shader);
    console.log("compile shaders error: " + error);
    // 删除失败的着色器
    gl.deleteShader(shader);
    return null;
  }
}

/**
 * 创建着色程序
 * @param gl WebGL上下文
 * @param vertexShader 顶点着色器对象
 * @param fragmentShader 片元着色器对象
 */
function createProgram(gl, vertexShader, fragmentShader) {
  // 创建着色程序
  const program = gl.createProgram();
  if (!program) return null;
  // 使着色程序获取顶点/片段着色器
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  // 将两个着色器与着色程序绑定
  gl.linkProgram(program);
  const state = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (state) {
    return program;
  } else {
    const error = gl.getProgramInfoLog(program);
    console.log("link program error: " + error);
    gl.deleteProgram(program);
    gl.deleteShader(vertexShader);
    gl.deleteShader(fragmentShader);
    return null;
  }
}

后面的开发都可以直接调用 initShaders.js,第一节的代码结构可以简化如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import initShaders from "./initShaders.js";
const gl = document.getElementById("webgl").getContext("webgl");
const vertexShader = `
void main() {
  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
  gl_PointSize = 10.0;
}
`;
const fragmentShader = `
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
initShaders(gl, vertexShader, fragmentShader);
gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);

6. Shader数据传递 attribute / uniform / varying

GLSL中可以使用三种存储限定符实现数据传递:

  • attribute: 属性和缓冲 用于从外部应用程序(如 js)向 vertexShader 中传递逐顶点数据
  • uniform: 全局只读变量 用于从外部应用程序(如 js)向 vertexShader 或 fragmentShader 中传递数据,着色程序运行前赋值,全局有效,Shader 内不可修改声明的 uniform 常量。常量的传递使用了 GPU 中的常量寄存器
  • varying: 全局可变量 支持 vertexShader 和 fragmentShader 间使用同名变量传递【插值】数据
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import initShaders from "./initShaders.js";
const gl = document.getElementById("webgl").getContext("webgl");
const vertexShader = `
attribute vec2 a_position; // 从js接收顶点位置数据
uniform float u_size; // 从js接收顶点尺寸数据
varying vec2 v_ab; // 向片段着色器提供 vec2 两个数据

void main() {
    v_ab = a_position;
    gl_Position = vec4(a_position, 0.0, 1.0); // 顶点坐标需要接收vec4
    gl_PointSize = u_size;
}
`;
const fragmentShader = `
precision mediump float; // 精度设置
uniform vec3 u_color; // 从js接收片段颜色数据
varying vec2 v_ab; // 从顶点着色器接收 vec2 两个数据

void main() {
    gl_FragColor = vec4(u_color, 1.0);
    // gl_FragColor = vec4(v_ab, 0.0, 1.0);
}
`;
initShaders(gl, vertexShader, fragmentShader);

// 【1】attribute (js=>vertexShader)
const a_position = gl.getAttribLocation(gl.program, "a_position");
gl.vertexAttrib2f(a_position, 0.5, 0.5);

// 【2】uniform (js=>vertexShader/fragmentShader)
const u_color = gl.getUniformLocation(gl.program, "u_color");
gl.uniform3f(u_color, 1.0, 0.0, 0.0);
const u_size = gl.getUniformLocation(gl.program, "u_size");
gl.uniform1f(u_size, 10.0);

// 【3】varying (vertexShader=>fragmentShader)

gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);

7. 精度限定符

符号

描述

highp

高精度,整型-2^16~2^16,浮点型-2^62~2^62

mediump

中精度,整型 -2^10~2^10,浮点型-2^14~2^14

lowp

低精度,整型 -2^8~2^8,浮点型-2~2

在着色器第一行可以声明着色器内所有浮点数的精度,如: precision highp float;

8. uniform后缀

前面的案例中使用了 uniform3funiform1f 设置 uniform 全局变量。

后缀

含义

f

一个 float

i

一个 int

ui

一个 unsigned int

3f

一个 float

fv

一个 float 向量/数组

例如分别使用 uniform4funiform4fv 传递颜色 rgba 值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gl.uniform4f(vertexColorLocation, Math.random(), Math.random(), Math.random(), 1.0);
gl.uniform4fv(vertexColorLocation, [Math.random(), Math.random(), Math.random(), 1.0]);

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
【Innovus】记录后端的一些小心得
当pin宽度等于最小线宽且不与grid对齐时,可能会引导起iobuf与pin不route。
ExASIC
2022/12/06
2.4K0
DC中常用到的命令(示例)总结
本文将描述在Design Compliler中常用到的命令,这些命令按照流程的顺序进行嵌套讲解,主要是列举例子;大概的讲解布局如下所示:
数字芯片社区
2020/07/20
8.7K0
DC中常用到的命令(示例)总结
《数字集成电路静态时序分析基础》笔记③
例如,统计时钟数量,人数显然不合理,可以用脚本语言统计,但是可以更加简单,直接用Synopsys Tcl的拓展命令更加便捷
空白的贝塔
2020/06/24
1.5K0
《数字集成电路静态时序分析基础》笔记③
用python实现网表分模块统计面积
虽然dc也有report_area -hier命令来报告各级模块的面积,本python方案看似有点造轮子,但还是有一定的便利性。一、不受网表类型的限制,综合网表、DFT网表、APR都可以。二、可以过滤面积小于指定值的小模块,比如工具自动插入的ICG模块。三、还可以根据面积占比做排序,方便分析面积的瓶颈。
ExASIC
2022/12/06
7810
用python实现网表分模块统计面积
P&R | 物理设计流程概述
题记,VLSI System Design 上的这篇文章其实没什么实质性的内容,只是一个特别特别笼统的概述,而且由于年久失修,某些地方的概念欠完备,但该文趣味十足,尤其是文中的手图——阐释了什么叫『简约美』,沉溺于这几张手图,所以一定要转一下,文章前半部分做了非一一对应的翻译,后半部分翻译过来比译制片都搞笑,还是看原文把,或者只看图就可以了。
老秃胖驴
2020/03/13
3K0
FPGA时序约束之Tcl命令的对象及属性
  在前面的章节中,我们用了很多Tcl的指令,但有些指令并没有把所有的参数多列出来解释,这一节,我们就把约束中的Tcl指令详细讲一下。
猫叔Rex
2020/06/30
1.4K0
FPGA时序约束之Tcl命令的对象及属性
跟老驴一起学PR | 数据的输入
前文《跟老驴一起学PR | Innovus 输入》阐述了 Innovus 所需要的输入数据有哪些,今天来学习如何将这些数据读入Innovus. 以前熟悉的工具,不论是综合、formal 还是STA 读入数据大多都是read_xxx 命令读入,思维定势之前以为Innovus 也是用一坨read_xxx 的命令读入,然而并不是。
老秃胖驴
2020/10/29
3.6K0
跟老驴一起学PR | 数据的输入
综合对象及环境属性
如果设置面积的约束为0, DC将为面积做优化直到再继续优化也不能有大的效果,这时,DC将中止优化。
数字芯片社区
2022/09/19
7670
综合对象及环境属性
Innovus 小技巧 | Innovus 中如何验证低功耗设计
此处论及的低功耗设计是指带IEEE1801 或CPF 的设计,即有多个电压域的设计,对于这样的设计,power mesh 跟placement 做完后,通常需要检查:
老秃胖驴
2019/05/30
3.2K0
PnR | Innovus 中的Soft Guide, Guide, Region, Fence
后端概念好繁琐,自从开始做ispatial 就被后端各种概念搞得七荤八素,挫败!挫败!挫败!在做物理综合时,除了LEF, DEF 概念搞懂之外,最常用到的就是各种 "guide" 了,在Innovus 中有以下四种.
老秃胖驴
2020/04/08
6.4K0
PnR | Innovus 中的Soft Guide, Guide, Region, Fence
论P&R | Pin Density 跟 Congestion
在《论综合:为什么做physical aware synthesis》中论及做Physical 综合的二分之一原因是Congestion 优化;Congestion 是一个硬指标,因为前面做的再好,如果congestion 太严重线绕不通还是要重头再来。跟功耗的优化类似,设计本身对Congestion 影响最大,如果设计已经足够优化,Coding Style 对Congestion 足够友好;那FloorPlan 对Congestion 的影响就至关重要,如果FloorPlan 也足够优化,也都按着数据流精细调整过;那剩下解Congestion 的办法就十分有限,目前在综合阶段可用的办法不外乎:
老秃胖驴
2019/11/21
3.4K0
【分享】升级Vivado工程脚本
Vivado可以导出脚本,保存创建工程的相关命令和配置,并可以在需要的时候使用脚本重建Vivado工程。脚本通常只有KB级别大小,远远小于工程打包文件的大小,因此便于备份和版本管理。 脚本里指定了Vivado的版本、器件的型号,IP的版本。如果导出脚本时的相关版本,和恢复工程时的相关版本不一样,会导致创建工程失败。可以通过手工检查和修改相关版本信息,升级Vivado工程脚本,使新的Vivado也能恢复出对应的工程。 目前我电脑中只有Vivado 2020.2。但是得到了一份Vivado 2020.1为开发板vck190 es的创建的工程脚本。下面以把前述脚本升级到Vivado 2020.2为例,讨论如何升级Vivado工程脚本。
hankfu
2021/03/03
2.2K0
【附录A SDC】静态时序分析圣经翻译计划
SDC语法是基于TCL的格式,即所有命令都遵循TCL语法。一个SDC文件会在文件开头包含SDC版本号,其次是设计约束,注释(注释以字符#开始,并在行尾处结束)在SDC文件中可以散布在设计约束中。设计约束中较长的命令行可以使用反斜杠(\)字符分成多行。
空白的贝塔
2021/01/28
1.7K0
【分享】MicroBlaze大内部存储器(AXI BRAM)设计
MicroBlaze可以使用AXI BRAM存放数据和指令。有些客户软件很大,需要把AXI BRAM的空间做到最大。AXI BRAM底层是Block RAM或者Ultra RAM。器件的Block RAM或者Ultra RAM个数,决定了AXI BRAM的大小。
hankfu
2020/07/27
2K0
Vivado中用于时钟操作的几个Tcl命令
理论上,使用Tcl可以在Vivado上完成一切操作,但是没必要,因为命令太多,很难记忆,我们只需要知道几个常用的即可,方便我们使用Vivado。
Reborn Lee
2021/10/11
9220
【Innovus】做postmask功能ECO需要分几步
一般Postmask功能ECO流程分成以下几步:修改RTL和验证、修改网表(LEC)、后端工具里ECO Route。如下图:
ExASIC
2021/12/02
2.3K0
【Innovus】做postmask功能ECO需要分几步
论PR | Innovus 中cell density计算解析
在Innovus 中可用如下命令来report density, 不同命令的应用场景和计算方式有所不同。
老秃胖驴
2019/10/21
8.5K0
Innovus 小技巧 | Innovus 对multibit 的支持
目前主流先进工艺库都有multibit cell, 在《论功耗:动态功耗优化》中有陈诉multibit cell 的好处及在综合时如何实现。如果在综合阶段没有做multibit merge, 或综合阶段由于缺失物理信息multibit cell merge 不合理,那就需要PR 工具做multibit merge 或split. 
老秃胖驴
2019/05/30
3K0
P&R | 汽车电子,Functional Safety 在place阶段的特别处理
Functional Safety 是汽车电子的独特之处,在P&R 阶段有以下几点需要特别处理:
老秃胖驴
2020/06/16
1.1K0
用python实现分模块按cell类型统计cell个数并降序排列
有同学想看看综合网表里某模块里and、or、inv等cell的个数,谁最多谁最少。虽然用dc的各种命令组合也可以实现,但今天我们用python来实现。
ExASIC
2022/12/06
9070
推荐阅读
相关推荐
【Innovus】记录后端的一些小心得
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 【前端er入门Shader系列】02—GLSL语言基础
    • 1. GLSL数据类型
      • 1.1 标量(字面量)
      • 1.2 向量 & 矩阵
      • 1.3 sampler(取样器)
    • 2. 控制流程 & 函数
      • 2.1 数组
      • 2.2 结构体
      • 2.3 循环/条件判断/函数
    • 3. 内置函数
    • 4. 存储限定字
      • 4.1 const
      • 4.2 attribute / uniform / varying
    • 5. Shader初始化函数封装
    • 6. Shader数据传递 attribute / uniform / varying
    • 7. 精度限定符
    • 8. uniform后缀
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档