Skip to content

Commit 0284480

Browse files
committed
Translate webgl-attributes.md into Chinese
1 parent 499a50a commit 0284480

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
Title: WebGL2 属性(Attributes)
2+
Description: WebGL 中的 attributes 是什么?
3+
TOC: 属性(Attributes)
4+
5+
本文旨在帮助你建立对 WebGL 中属性状态是如何设置的一个直观理解。
6+
另有[关于纹理单元的类似文章](webgl-texture-units.html)以及[framebuffer 的文章](webgl-framebuffers.html)
7+
8+
前置知识建议阅读:[WebGL 是如何工作的](webgl-how-it-works.html)
9+
[WebGL 着色器和 GLSL](https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html)
10+
11+
## Attributes(属性)
12+
13+
在 WebGL 中,attributes 是传入顶点着色器的输入,数据来自 buffer。
14+
每当调用 `gl.drawArrays``gl.drawElements` 时,WebGL 会执行用户提供的顶点着色器 N 次。
15+
每次迭代,attributes 定义了如何从绑定到它们的 buffer 中提取数据,
16+
并将其传递给顶点着色器中的属性变量。
17+
18+
如果用 JavaScript 来模拟实现,它们可能像这样:
19+
20+
```js
21+
// pseudo code
22+
const gl = {
23+
arrayBuffer: null,
24+
vertexArray: {
25+
attributes: [
26+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
27+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
28+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
29+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
30+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
31+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
32+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
33+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
34+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
35+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
36+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
37+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
38+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
39+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
40+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
41+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?, divisor: 0 },
42+
],
43+
elementArrayBuffer: null,
44+
},
45+
}
46+
```
47+
48+
如上所示,总共有 16 个 attributes。
49+
50+
当你调用 `gl.enableVertexAttribArray(location)``gl.disableVertexAttribArray`,可以将其理解为如下操作:
51+
52+
```js
53+
// pseudo code
54+
gl.enableVertexAttribArray = function(location) {
55+
const attrib = gl.vertexArray.attributes[location];
56+
attrib.enable = true;
57+
};
58+
59+
gl.disableVertexAttribArray = function(location) {
60+
const attrib = gl.vertexArray.attributes[location];
61+
attrib.enable = false;
62+
};
63+
```
64+
65+
换句话说,location 就是 attribute 的索引。
66+
67+
类似地,`gl.vertexAttribPointer` 用来设置 attribute 的几乎所有其他属性。
68+
实现可能如下所示:
69+
70+
```js
71+
// pseudo code
72+
gl.vertexAttribPointer = function(location, size, type, normalize, stride, offset) {
73+
const attrib = gl.vertexArray.attributes[location];
74+
attrib.size = size;
75+
attrib.type = type;
76+
attrib.normalize = normalize;
77+
attrib.stride = stride ? stride : sizeof(type) * size;
78+
attrib.offset = offset;
79+
attrib.buffer = gl.arrayBuffer; // !!!! <-----
80+
};
81+
```
82+
83+
注意,调用 `gl.vertexAttribPointer` 时,`attrib.buffer` 会设置为当前的 `gl.arrayBuffer`
84+
`gl.arrayBuffer` 如上述伪代码所示,通过调用 `gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer)` 设置。
85+
86+
87+
```js
88+
// pseudo code
89+
gl.bindBuffer = function(target, buffer) {
90+
switch (target) {
91+
case ARRAY_BUFFER:
92+
gl.arrayBuffer = buffer;
93+
break;
94+
case ELEMENT_ARRAY_BUFFER;
95+
gl.vertexArray.elementArrayBuffer = buffer;
96+
break;
97+
...
98+
};
99+
```
100+
101+
接下来是顶点着色器。在顶点着色器中你声明属性,例如:
102+
103+
```glsl
104+
#version 300 es
105+
in vec4 position;
106+
in vec2 texcoord;
107+
in vec3 normal;
108+
109+
...
110+
111+
void main() {
112+
...
113+
}
114+
```
115+
116+
当你使用 `gl.linkProgram(someProgram)` 将顶点着色器和片段着色器链接时,
117+
WebGL(驱动/GPU/浏览器)会自行决定每个属性使用哪个索引(location)。
118+
除非你手动分配位置(见下文),否则你无法预知它们会选哪个索引。
119+
因此你需要通过 `gl.getAttribLocation` 查询它们:
120+
121+
```js
122+
const positionLoc = gl.getAttribLocation(program, 'position');
123+
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');
124+
const normalLoc = gl.getAttribLocation(program, 'normal');
125+
```
126+
127+
假设 `positionLoc` = `5`,意味着在执行顶点着色器时(即调用 `gl.drawArrays``gl.drawElements`),
128+
WebGL 期待你已经为 attribute 5 设置好了正确的 `type``size``offset``stride``buffer` 等。
129+
130+
注意:在调用 `gl.linkProgram` *之前*,你可以使用`gl.bindAttribLocation(program, location, nameOfAttribute) `指定位置,例如:
131+
132+
```js
133+
// Tell `gl.linkProgram` to assign `position` to use attribute #7
134+
gl.bindAttribLocation(program, 7, 'position');
135+
```
136+
137+
如果使用的是GLSL ES 3.00着色器,您也可以直接在着色器中指定要使用的location位置,例如:
138+
139+
```glsl
140+
layout(location = 0) in vec4 position;
141+
layout(location = 1) in vec2 texcoord;
142+
layout(location = 2) in vec3 normal;
143+
144+
...
145+
```
146+
147+
使用 `bindAttribLocation` 看起来更符合 [D.R.Y. 原则](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself),
148+
不过你可以根据个人偏好选择不同的方式。
149+
150+
## 完整的属性状态
151+
152+
上面的描述中省略了一点:每个 attribute 实际上都有一个默认值。
153+
这在实际应用中较少使用,所以之前没有提及。
154+
155+
```js
156+
attributeValues: [
157+
[0, 0, 0, 1],
158+
[0, 0, 0, 1],
159+
...
160+
],
161+
vertexArray: {
162+
attributes: [
163+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?,
164+
 divisor: 0, },
165+
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?,
166+
 divisor: 0, },
167+
...
168+
```
169+
170+
你可以通过一系列 `gl.vertexAttribXXX` 函数设置每个 attribute 的值。
171+
`enable``false` 时,会使用这些值;当 `enable``true`,则会从分配的 `缓冲区buffer` 中读取数据。
172+
173+
<a id="vaos"></a>
174+
## 顶点数组对象(VAO)
175+
176+
```js
177+
const vao = gl.createVertexArray();
178+
```
179+
180+
这会创建一个如上伪代码中 `gl.vertexArray` 所示的对象。
181+
调用 `gl.bindVertexArray(vao)` 将你创建的 VAO 设为当前 VAO:
182+
183+
```js
184+
// pseudo code
185+
gl.bindVertexArray = function(vao) {
186+
gl.vertexArray = vao ? vao : defaultVAO;
187+
};
188+
```
189+
190+
这样你就可以在当前 VAO 中设置所有 `attributes``ELEMENT_ARRAY_BUFFER`
191+
当你要绘制某个图形时,只需调用一次 `gl.bindVertexArray` 即可设置所有属性。
192+
否则你可能需要为每个属性分别调用 `gl.bindBuffer``gl.vertexAttribPointer``gl.enableVertexAttribArray`
193+
194+
由此可见,使用 VAO 是很有价值的。
195+
不过,要正确使用 VAO 需要更好的组织结构。
196+
197+
举个例子,假设你想用 `gl.TRIANGLES` 和一个着色器绘制一个立方体,
198+
再用 `gl.LINES` 和另一个着色器重新绘制它。
199+
200+
假设用三角形绘制时要做光照处理,因此顶点着色器声明了这些属性:
201+
202+
```glsl
203+
#version 300 es
204+
// lighting-shader
205+
// 用于绘制三角形的着色器
206+
207+
in vec4 a_position;
208+
in vec3 a_normal;
209+
```
210+
211+
然后使用这些位置和法线向我在[第一篇光照文章](webgl-3d-lighting-directional.html)中做的那样。
212+
213+
对于不需要光照的线条,您需要使用纯色,可以参照本教程系列[第一页](webgl-fundamentals.html)中的基础着色器实现方式。
214+
声明一个uniform的颜色变量。 这意味着在顶点着色器中只需处理位置数据即可
215+
216+
```glsl
217+
#version 300 es
218+
// solid-shader
219+
// shader for cube with lines
220+
221+
in vec4 a_position;
222+
```
223+
224+
我们并不知道 WebGL 为每个 shader 分配的 attribute 位置是多少。
225+
假设 lighting-shader 的分配结果是:
226+
227+
```
228+
a_position location = 1
229+
a_normal location = 0
230+
```
231+
232+
solid-shader只有一个attribute属性。
233+
234+
```
235+
a_position location = 0
236+
```
237+
238+
显然,在切换着色器时需要重新设置属性。
239+
一个着色器期望 `a_position` 的数据出现在attribute 0,另一个着色器期望它出现在attribute 1。
240+
241+
重新设置属性是一件麻烦事。更糟的是,使用 VAO 的初衷就是避免这些重复操作。
242+
为了解决这个问题,我们需要在链接程序之前使用 bindAttribLocation 显式指定位置:
243+
244+
重新设置属性是一件麻烦事。更糟的是,使用 VAO 的初衷就是避免这些重复操作。
245+
为了解决这个问题,我们需要在链接程序之前使用 `bindAttribLocation` 显式指定位置。
246+
247+
我们告诉 WebGL:
248+
249+
```js
250+
gl.bindAttribLocation(solidProgram, 0, 'a_position');
251+
gl.bindAttribLocation(lightingProgram, 0, 'a_position');
252+
gl.bindAttribLocation(lightingProgram, 1, 'a_normal');
253+
```
254+
255+
务必在**调用 `gl.linkProgram` 之前**执行以上操作。
256+
这样 WebGL 在链接着色器时就会使用我们指定的位置。
257+
现在这两个着色器就可以使用相同的 `VAO`
258+
259+
## 最大属性数量
260+
261+
WebGL2 要求至少支持 16 个 attribute,但具体设备 / 浏览器 / 驱动可能支持更多。
262+
你可以通过下面的方式获取实际支持数量:
263+
264+
```js
265+
const maxAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
266+
```
267+
268+
如果你打算使用超过 16 个 attributes,建议检查支持数量,
269+
并在设备不足时提示用户,或者降级使用更简单的着色器。

0 commit comments

Comments
 (0)