/
glTF-renderer-old.html
875 lines (767 loc) · 36.3 KB
/
glTF-renderer-old.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>glTF-Renderer</title>
<script src="./loader.js"></script>
</head>
<body>
<script type="module">
(async function main() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
document.body.append(canvas);
const t0 = performance.now();
let success;
const VS_SHADER = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
const FS_SHADER = `
precision mediump float;
void main() {
gl_FragColor = vec4(0, 0.5, 1, 1);
}
`;
// 先回忆一下怎么绘制三角形的。。。这东西不使用就会忘记的了
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, VS_SHADER);
gl.compileShader(vs);
success = gl.getShaderParameter(vs, gl.COMPILE_STATUS);
if (!success)
return console.log('compile vs shader fail', gl.getShaderInfoLog(vs));
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, FS_SHADER);
gl.compileShader(fs);
success = gl.getShaderParameter(fs, gl.COMPILE_STATUS);
if (!success)
return console.log('compile fs shader fail', gl.getShaderInfoLog(fs));
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success)
return console.log(
'link program fail',
gl.getProgramInfoLog(program),
);
const positionLocation = gl.getAttribLocation(program, 'a_position');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([0, 0.5, -0.5, 0, 0.5, 0]),
gl.STATIC_DRAW,
);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
const t1 = performance.now();
for (let i = 0; i < 1_000_000; i++) {
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
const t2 = performance.now();
console.log('t1 - t0', t1 - t0);
console.log('t2 - t1', t2 - t1);
console.log('t2 - t0', t2 - t0);
// 这是最基本的webgl使用方式,其实就是一堆修改webgl状态机的API
})();
</script>
<script type="module">
import * as AsBind from './node_modules/as-bind/dist/as-bind.esm.js';
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
document.body.append(canvas);
const memory = new WebAssembly.Memory({ initial: 64 });
const FN_LIST = [
'createShader',
'shaderSource',
'compileShader',
'getShaderParameter',
'getShaderInfoLog',
'createProgram',
'attachShader',
'linkProgram',
'getProgramParameter',
'getProgramInfoLog',
'getAttribLocation',
'createBuffer',
'bindBuffer',
'bufferData',
'useProgram',
'enableVertexAttribArray',
'drawArrays',
'vertexAttribPointer',
];
const WebGLClassInstanceMap = {};
const WebGLClassArray = [
'WebGLActiveInfo',
'WebGLBuffer',
'WebGLContextEvent',
'WebGLFramebuffer',
'WebGLProgram',
'WebGLQuery',
'WebGLRenderbuffer',
'WebGLRenderingContext',
'WebGLSampler',
'WebGLShader',
'WebGLShaderPrecisionFormat',
'WebGLSync',
'WebGLTexture',
'WebGLTransformFeedback',
'WebGLUniformLocation',
'WebGLVertexArrayObject',
];
let WebGLClassArrayMap = {};
WebGLClassArray.forEach((v, k) => {
WebGLClassArrayMap[v] = k;
WebGLClassInstanceMap[v] ??= [];
});
let parseGLCallArgCost = 0;
function parseGLCallArg(arg) {
if (Number.isInteger(arg) && arg & 0b11100000000000000000000000000000) {
const classIndex = (arg & 0b00011111000000000000000000000000) >> 24;
const instanceIndex = arg & 0b00000000111111111111111111111111;
if (classIndex < WebGLClassArray.length)
return WebGLClassInstanceMap[WebGLClassArray[classIndex]][
instanceIndex
];
}
return arg;
}
let parseGLCallReturnCost = 0;
let WebGLClassArrayFindCost = 0;
let WebGLClassInstanceNameCost = 0;
function parseGLCallReturn(ret) {
if (
typeof ret === 'object' &&
ret.constructor.name.indexOf('WebGL') === 0
) {
const WebGLClassInstanceName = ret.constructor.name;
if (WebGLClassInstanceName) {
const classIndex = WebGLClassArrayMap[WebGLClassInstanceName];
const instanceIndex =
WebGLClassInstanceMap[WebGLClassInstanceName].push(ret) - 1;
// 3位 5位 24位
// 标识符 instanceIndex classIndex
// prettier-ignore
ret = 0b11100000000000000000000000000000 | (classIndex << 24) | instanceIndex;
}
}
return ret;
}
const GLTF_DIR_URL = './models/glTF/';
let instance;
const gltfPromise = fetch(GLTF_DIR_URL + 'Cube.gltf')
.then(i => i.json())
.then(gltf => {
// 加载依赖资源
const imgs = gltf.images.map(img =>
loadImg(GLTF_DIR_URL + img.uri).then($img => (img.el = $img)),
);
const buffers = gltf.buffers.map(buffer =>
fetch(GLTF_DIR_URL + buffer.uri)
.then(i => i.arrayBuffer())
.then(arrayBuffer => {
if (buffer.byteLength !== arrayBuffer.byteLength)
throw new Error('byteLength unmatch');
buffer.data = arrayBuffer;
}),
);
return Promise.all([...imgs, ...buffers]).then(() => gltf);
});
// loader
AsBind.instantiate(
fetch('./build/gltf-renderer.wasm').then(i => i.arrayBuffer()),
{
env: { memory },
'gltf-renderer': {
'console.log'(str) {
console.log(instance.__getString(str));
},
...FN_LIST.reduce((acc, fnName) => {
const glFnName = 'gl.' + fnName;
acc[glFnName] = function () {
const argsParsed = new Array(arguments.length);
for (let i = 0; i < arguments.length; i++)
argsParsed[i] = parseGLCallArg(arguments[i]);
return parseGLCallReturn(gl[fnName](...argsParsed));
};
return acc;
}, {}),
},
},
).then(({ exports }) => {
return;
console.log(exports);
instance = exports;
// 加上细分性能打点会非常耗时,取消性能打点之后耗时大幅下降, 是值得留意一个现象
// 只保留 最开始结尾 和 wasm内,耗时只有4.5 最低
// 只保留 最开始结尾,耗时只有3.5 最低
// 3.5 / 1000 也就是平均每个gl call 的耗时增加是很小的,但是循环多起来,累加多起来就会比较明显
// 如果想要避免wasm2js的耗时,估计就是减少这类的调用,或者batch的方式
// 后面需要尝试command buffer的方式,或者wasm是结构运算部分
// https://zhuanlan.zhihu.com/p/102692865 里也有人提到用command buffer来优化了
// 新发现!把这3行移出循环外,WASM耗时稳定低于JS,wasm稳定470 ~ 490 JS最低490,但是优势600+ 700+ 800+
// gl.useProgram(program);
// gl.enableVertexAttribArray(positionLocation);
// gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, 0, 0, 0);
// 循环里的JS CALL合并成一个可能会更好??说到底还是batch的方式
// 本来以为wasm 体积上面会有优势,但是发现并没有,可能目前gl api的也占用了不少体积吧
// 可以使用as-bind实现基础数据的互通string statictype array
// 先尝试提个PR 给as-bind吧 done 没想到这么简单就支持了
// 然而缺泛型的支持。。。Float32Array还报错了。。。不太成熟的写法
// 修了Float32Array的问题,性能上面没有明显变化,不过as-bind.esm.js 28k 体积上面比较庞大了(去除sourcemap
// 还差泛型,现在相当于bufferData接口仅仅支持Float32Array
// command buffer是减少js call的损耗,但是数字类型的损耗依然存在,js call的损耗在chrome下面非常少,5%左右
// 火狐下面是比较明显的损耗,这里还仅仅是调用,还没涉及数据格式的转换,增加了数据的格式转换测试,确实比较耗时
// 接下来需要测试command buffer,但是我不知道command buffer是大概怎么实现的,触发规则等等,是buffer满了发送,还是手动触发发送
// 额,简单想了一下,command buffer只能操作那些无返回值的API,比如createShader这些资源管理相关的就不支持到合并js call了
// 那么就是定协议,只能是无返回值API支持,有返回值的还是直接触发,但是有没有可能有返回值依赖无返回值api呢
// 有返回值1api exec
// 无返回值2api stash
// 有返回值3api exec
// 无返回值4api stash
// flush
// 无返回值2api exec
// 无返回值4api exec
// 修改实行顺序了
// 是有依赖的比如createProgram和useProgram,需要等program link完成之后在use?
// 准备开始实现gltf-renderer,需要实现gltf的解析
gltfPromise.then(gltf => {
console.log(gltf);
exports.glTFSetAsset(gltf.asset.version, gltf.asset.generator);
gltf.accessors.forEach(accessor => {
exports.glTFAddAccessor(
accessor.bufferView,
accessor.byteOffset,
accessor.componentType,
accessor.count,
accessor.type,
accessor.max,
accessor.min,
);
});
gltf.bufferViews.forEach(bufferView => {
exports.glTFAddBufferView(
bufferView.buffer,
bufferView.byteLength,
bufferView.byteOffset,
bufferView.target,
);
});
gltf.buffers.forEach(buffer => {
exports.glTFAddBuffer(buffer.byteLength);
});
gltf.images.forEach(image => {
exports.glTFAddImage();
});
gltf.materials.forEach(material => {
exports.glTFAddMaterial(
material.name,
material.pbrMetallicRoughness.baseColorTexture.index,
material.pbrMetallicRoughness.metallicRoughnessTexture.index,
);
});
gltf.meshes.forEach(mesh => {
const index = exports.glTFAddMesh(mesh.name) - 1;
mesh.primitives.forEach(primitive => {
exports.glTFAddMeshPrimitive(
index,
primitive.attributes.NORMAL,
primitive.attributes.POSITION,
primitive.attributes.TANGENT,
primitive.attributes.TEXCOORD_0,
primitive.indices,
primitive.material,
primitive.mode,
);
});
});
gltf.nodes.forEach(node => {
exports.glTFAddNode(node.name, node.mesh);
});
gltf.samplers.forEach(sampler => {
exports.glTFAddSampler();
});
gltf.scenes.forEach(scene => {
exports.glTFAddScene(scene.name, scene.nodes);
});
gltf.textures.forEach(texture => {
exports.glTFAddTexture(texture.sampler, texture.source);
});
// TODO: 加载图片等依赖的buffer才行,这里上传gltf之前就已经是均加载完毕了,这里就是需要实现一个并发秦秋控制函数的了
// 然后gltf结构传入,复杂数据结构的传递可能需要自定生成这样的胶水代码实现
// 然后需要实现一个gltf的渲染,直接使用gltf的结构作为runtime的结构是否合适呢?一种表的结构
// 先实现js端的渲染gltf的,然后迁移到wasm里
exports.main();
});
});
gltfPromise.then(gltf => {
// 模型无关的就是相机和光照了
// webgl资源管理
// 拆分阶段:
// 解析gltf的顶点,uv,法线,上传顶点
// 按照对应阶段,可配置情况进行相应的抽象
// 资源更新状态three是由一个needsUpdate,version来手动触发
// 渲染顺序就是按照模型的顺序实现,半透明是否需要处理呢
// webgl是以program为基础单位
// 貌似能直接使用gltf的抽象,直接使用那么应该是怎么去
const WEBGL_COMPONENT_TYPES = {
5120: Int8Array,
5121: Uint8Array,
5122: Int16Array,
5123: Uint16Array,
5125: Uint32Array,
5126: Float32Array,
};
const WEBGL_TYPE_SIZES = {
SCALAR: 1,
VEC2: 2,
VEC3: 3,
VEC4: 4,
MAT2: 4,
MAT3: 9,
MAT4: 16,
};
const glCache = {
buffer: new Map(), // gltf accessor: gl buffer
texture: new Map(), // gltf textures: gl texture
};
const programCache = {
get basic() {
if (!programCache._basic)
programCache._basic = createGLProgram(
basicVertexShader,
basicFragmentShader,
);
return programCache._basic;
},
};
const basicVertexShader = `
// geometry
attribute vec3 a_position;
attribute vec2 a_texcoord;
// attribute vec3 a_normal;
// camera
uniform mat4 u_projection;
// uniform mat4 u_camera_pose;
uniform mat4 u_camera_pose_invert;
uniform mat4 u_model_pose;
// fragment shader
varying vec2 v_texcoord;
varying vec3 v_normal;
void main() {
// 模型到世界坐标系, 转到相机坐标系,只记得是一个逆矩阵,但是为什么是逆矩阵有点忘记了
// 公式里的mt = wt * M; 的M是世界坐标系转到模型坐标系的变换,这里的model_pose是一样的,世界坐标到模型坐标
// 之前学的东西都白学了一样。。。要是面试webgl估计上来就做一些矩阵运算题了
// 世界坐标系乘上u_model_pose变化到模型坐标系,比如模型坐标系一开始在原点,然后沿x轴正方向位移一个单位,一个点位于模型坐标系的原点
// 那么模型坐标系的原点在世界坐标系的坐标是(1,0,0)
// 那么就是相乘起来就行,但是理解起来有点绕
// 我怎么记得有两种说法,还是得按照公式的写法去理解
// p点 = M^t模型坐标系 * p向量
// M^t模型坐标系 = W^t世界坐标系 * M变换矩阵
// p点 = W^t世界坐标系 * M变换矩阵 * p向量
// 所以P点在世界坐标系是 M * P
// C^t = W^t * C
// M^t = W^t * M
// C^t * C^-1 = W^t * C * C^-1 = W^t * I单位矩阵
// W^t = C^t * C^-1
// p点 = C^t * C^-1 * M * p向量
// 所以在相机坐标系是 C的逆 * M * p
// glsl 没有inverse mat4的内置函数,还是在CPU算吧
// mat4 i_camera_pose = inverse(u_camera_pose);
vec4 p_camera_frame = u_camera_pose_invert * u_model_pose * vec4(a_position, 1);
// vec4 p_camera_frame = u_model_pose * vec4(a_position, 1);
// 投影矩阵,相机是朝向负z轴看的,这个是在哪里体现呢?
// 考察投影矩阵理解程度可以问题:如何把相机修改为朝向z轴的正方向看呢?
// 还得重新理解投影矩阵
vec4 p_near_plane = u_projection * p_camera_frame;
// vec4 p_near_plane = u_projection * vec4(a_position, 1);
// 法线插值记得还有区别。。。记得不能直接用重心插值
// v_normal = a_normal;
v_texcoord = a_texcoord;
gl_Position = p_near_plane;
// gl_Position = vec4(a_position / 3.0, 1);
// 工程相关的话,就是glsl的代码混淆和压缩,编辑器提示,测试等等流程,
// 都会涉及glsl的ast解析,就是做类似babel tenser的逻辑,可能还会加上模块系统,如果加上模块系统就需要加上vscode的插件支持
}
`;
const basicFragmentShader = `
precision mediump float;
// light
uniform vec4 u_ambient_light;
uniform vec4 u_directional_light;
uniform vec4 u_point_light;
uniform vec4 u_point_light_position; // 点光源用位置即可
uniform mat4 u_directional_light_pose;
// matrial
uniform sampler2D u_base_color_texture;
uniform vec3 u_base_color;
// varying
varying vec2 v_texcoord;
varying vec4 v_normal;
void main() {
// 法线是用来光照计算用的
// 点光源,平行光,环境光
// gl_FragColor = vec4(0, 1, 0, 1); // 先最简单的
gl_FragColor = texture2D(u_base_color_texture, v_texcoord); // 贴上去的图片效果不对,应该是min max没格式化
// vec4 color_from_tex = texture2D(u_base_color_texture, v_texcoord);
// gl_FragColor = mix(color_from_tex, u_base_color, 0.5); // 与设置的颜色混合
// 但是存在贴图不存在的情况,shader里不好使用if判断
// 这种数据有无是如何做兼容呢?多个shader么,一个是有纹理贴图的,一个是没纹理贴图的
// 简单归类了一下,所以那些material,geometry,light,camera是对shader的attribute和uniform的抽象
// 感觉做完简单版本gltf-renderer就重新回到Foundations of 3D Computer Graphics的学习了,进一步完成渲染引擎的工程抽象还有实现
}
`;
const CAMERA_PROJECTION_MATRIX = perspective(
Math.PI / 3,
canvas.width / canvas.height,
1,
100,
); // 60°
// prettier-ignore
const CAMERA_POSE_MATRIX = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);
const CAMERA_POSE_INVERT_MATRIX = inverse(CAMERA_POSE_MATRIX);
const { cos, sin, PI } = Math;
const rad = (30 / 180) * PI;
// prettier-ignore
const MODEL_POSE_MATRIX = new Float32Array([
1, 0, 0, 0,
0, cos(rad), sin(rad), 0,
0, -sin(rad), cos(rad), 0,
0, 0, -3, 1,
]);
// !!列主序!!需要清晰知道存储是矩阵格式,和使用的矩阵读取方式
// 旋转则是对matrix的抽象,由四元素或欧拉角来确定姿态,缩放,位置
// 加旋转
renderScene(gltf.scene);
function renderScene(sceneIndex) {
// gl.clearColor(1, 0, 0, 1);
// gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
const scene = gltf.scenes[gltf.scene];
scene.nodes.forEach(nodeIndex => {
const node = gltf.nodes[nodeIndex];
const meshIndex = node.mesh;
if (meshIndex !== undefined) {
const mesh = gltf.meshes[meshIndex];
// 创建shader,program(应该是和mesh的material类型相关,一类只需要一个program, 不过这样Uniform每次都得上传了
// 但是隐隐约约webgl program是需要一个层封装的,属于定制的封装
const program = programCache.basic;
gl.useProgram(program);
// 获取location
const aPosition = gl.getAttribLocation(program, 'a_position');
const aTexcoord = gl.getAttribLocation(program, 'a_texcoord');
const aNormal = gl.getAttribLocation(program, 'a_normal');
const uProjection = gl.getUniformLocation(
program,
'u_projection',
);
// const uCameraPose = gl.getUniformLocation(
// program,
// 'u_camera_pose',
// );
const uCameraPoseInvert = gl.getUniformLocation(
program,
'u_camera_pose_invert',
);
const uModelPose = gl.getUniformLocation(program, 'u_model_pose');
const uBaseColorTexture = gl.getUniformLocation(
program,
'u_base_color_texture',
);
// 设置uniform
gl.uniformMatrix4fv(uProjection, false, CAMERA_PROJECTION_MATRIX);
gl.uniformMatrix4fv(
uCameraPoseInvert,
false,
CAMERA_POSE_INVERT_MATRIX,
);
gl.uniformMatrix4fv(uModelPose, false, MODEL_POSE_MATRIX);
// 设置WebGL全局状态
// 上传顶点数据, 为什么primitives是多个呢,是指多个primitives, 多组geometry公用一个材质??
mesh.primitives.forEach(primitive => {
// const normalBuffer = uploadAttribute(
// primitive.attributes.NORMAL,
// );
const positionBuffer = uploadAttribute(
primitive.attributes.POSITION,
);
// const tangentBuffer = uploadAttribute(
// primitive.attributes.TANGENT,
// ); // 还不知道TANGENT的作用
const texcoord_0Buffer = uploadAttribute(
primitive.attributes.TEXCOORD_0,
);
// const positionBuffer = gl.createBuffer();
// gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// new Float32Array([0, 0.5, 0, -0.5, 0,0, 0.5, 0,0]),
// gl.STATIC_DRAW,
// );
// set attribute,但是还需要那一个shader
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0); // 还有各种细分类型需要确定,目前先固定一种
// gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// gl.enableVertexAttribArray(aNormal);
// gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoord_0Buffer);
gl.enableVertexAttribArray(aTexcoord);
gl.vertexAttribPointer(aTexcoord, 2, gl.FLOAT, false, 0, 0);
// 根据material创建shader,目前仅仅支持pbr,先只实现baseColor的渲染, 看了一下threejs的physical的shader,好多内容还是先实现basic的吧。。。
const material = gltf.materials[primitive.material];
// 上传纹理
const baseColorTexture = uploadTexture(
material.pbrMetallicRoughness.baseColorTexture.index,
);
// 绑定纹理到纹理单元, 然后再把纹理单元写入uniform
const texUnit = 1;
gl.activeTexture(gl.TEXTURE0 + texUnit);
gl.bindTexture(gl.TEXTURE_2D, baseColorTexture);
gl.uniform1i(uBaseColorTexture, texUnit);
const accessor = gltf.accessors[primitive.attributes.POSITION];
const itemSize = WEBGL_TYPE_SIZES[accessor.type];
// 绘制
gl.drawArrays(gl.TRIANGLES, 0, accessor.count * itemSize);
// gl.drawArrays(gl.TRIANGLES, 0, 3);
});
}
});
}
function uploadTexture(textureIndex) {
const texture = gltf.textures[textureIndex];
const image = gltf.images[texture.source];
const sampler = gltf.samplers[texture.sampler];
if (glCache.texture[textureIndex])
return glCache.texture[textureIndex];
const colorFormat = image.uri.match(/\.jpg$/i) ? gl.RGB : gl.RGBA;
const textureGL = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textureGL);
// 需要根据什么设置?纹理是否是2的幂次方?
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
colorFormat,
colorFormat,
gl.UNSIGNED_BYTE,
image.el,
);
// 这些参数应该哪里获取呢,测试的sampler为空,纹理的格式,颜色格式,数据类型等
// 简单通过图片后缀来区别,这里各种格式都得支持的话,就比较复杂了一些不过分层分得好的话也是没问题的
// 这里还得处理各种类型的纹理格式,常见的就是ImageElement,还有TypedArray,还有GPU压缩纹理
return textureGL;
}
function uploadAttribute(accessorIndex) {
const accessor = gltf.accessors[accessorIndex];
const itemSize = WEBGL_TYPE_SIZES[accessor.type];
const TypedArray = WEBGL_COMPONENT_TYPES[accessor.componentType];
const elementBytes = TypedArray.BYTES_PER_ELEMENT;
const itemBytes = elementBytes * itemSize;
const byteOffset = accessor.byteOffset | 0;
const byteStride =
accessor.bufferView !== undefined
? gltf.bufferViews[accessor.bufferView].byteStride
: undefined;
const normalized = accessor.normalized === true;
const uploadedToGL = glCache.buffer.has(accessorIndex);
if (!uploadedToGL) {
const bufferView = gltf.bufferViews[accessor.bufferView];
const buffer = gltf.buffers[bufferView.buffer];
const arrayBuffer = buffer.data.slice(
bufferView.byteOffset,
bufferView.byteOffset + bufferView.byteLength,
);
const typedArray = new TypedArray(
arrayBuffer,
byteOffset,
accessor.count * itemSize,
);
// upload attribute to gl
const glBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
gl.bufferData(gl.ARRAY_BUFFER, typedArray, gl.STATIC_DRAW);
glCache.buffer.set(accessorIndex, glBuffer);
console.log('uploadAttribute', typedArray);
}
return glCache.buffer.get(accessorIndex);
}
function createGLShader(shaderType, shaderSource) {
const shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success)
console.error(
'compile fs shader fail',
gl.getShaderInfoLog(shader),
);
return shader;
}
function createGLProgram(vsSource, fsSource) {
const vs = createGLShader(gl.VERTEX_SHADER, vsSource);
const fs = createGLShader(gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success)
console.error('link program fail', gl.getProgramInfoLog(program));
return program;
}
// 每次渲染不一定都需要创建新的glBuffer,所以three的额外一层的作用就是这个
// 先理清楚gl的资源管理,还是得有一层中间层抽象
// 渲染一个三角形容易,一个gltf模型就复杂多了
// gl也分两个阶段,一个数据初始化阶段,一个是渲染阶段
// 初始化阶段,就是把渲染需要的数据准备好,比如,(资源类,主要是纹理和attribute依赖的buffer)
// attribute依赖的顶点,uv,法线buffer
// 材质对应的shader
// 渲染阶段则是
// 设置attribute,纹理单元,uniform,framebuffer,gl全局状态
//
// js 资源 webgl 资源管理
// three是通过needsUpdate来手动控制纹理上传,创建
// js需要上传资源到webgl,所以资源有两个存储位置,js,webgl,需要一个工具去维护这个映射关系
// 需要给资源一个id,可以是md5,md5的话就是全局的一个资源复用逻辑了,如果想要实现一个全局通用的资源逻辑
// three都是实现的是局部复用,uuid应该是对应生成是的认为是唯一的,生成一次
// arraybuffer倒是好计算md5,就是图片不太好计算,一个是绘制到canvas然后读取像素点然后再计算md5,但是又涉及一层dom的操作wasm里又需要调用胶水代码了
// attribute每次渲染都需要设置,但是buffer是只需要绑定buffer即可,所以attribute的逻辑是渲染的逻辑,buffer的逻辑是数据顺便阶段的逻辑
// texture和uniform则不需要每次设置,但是纹理单元还是需要每次都分配?可能是的
// three的正常逻辑就是资源上传+各种设置
// material是对shader的抽象封装
// geometry是对Attribute normal uv的抽象封装
// mesh是geometry和material的组合
// 所以gltf已经包含了这些的拆分,除了geometry
// webgl最小单位数据类型: buffer texture(不包含texture的设置) 貌似也没有多少
// js 到 webgl的数据存储需要用一个map来记录
// 这里实现就先使用下标来实现了,同样是资源上传和设置一并执行,资源上传会有一层缓存层
// attribute -> buffer 存储应该怎么存储呢
// accessor -> bufferview -> buffer 意味着js层的是accessor对应gl的buffer, 其中间粒度更加细分,但是上传状态是根据gl的buffer来记录
// useProgram和设置attribute是否有顺序依赖?没有,先设置attribute后useProgram也可以
// material是和shader强相关的,所以是先处理material,然后再处理geometry,也可以在mesh里做处理,geometry提供顶点数据,material提供shader设置
// 先看看three是如何处理的, 和想象的并不一样,three的shader是集中在WebGLProgram里做映射,material和geometry都是存储数据,不过也对,如果每个实例都有一份,就冗余了
// 这里估计也需要一小部分的抽象,所以geometry和material是shader设置的两类划分
// 发现render 需要传递一个场景树和相机,估计是和这个有关
// 记得three也有layer的概念,感觉和chrome的是类似的
// 从webgl-fundamatals直接拿一个
function perspective(fieldOfViewInRadians, aspect, near, far) {
var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
var rangeInv = 1.0 / (near - far);
// prettier-ignore
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
]);
}
// prettier-ignore
function inverse(m, dst) {
dst = dst || new Float32Array(16);
var m00 = m[0 * 4 + 0];
var m01 = m[0 * 4 + 1];
var m02 = m[0 * 4 + 2];
var m03 = m[0 * 4 + 3];
var m10 = m[1 * 4 + 0];
var m11 = m[1 * 4 + 1];
var m12 = m[1 * 4 + 2];
var m13 = m[1 * 4 + 3];
var m20 = m[2 * 4 + 0];
var m21 = m[2 * 4 + 1];
var m22 = m[2 * 4 + 2];
var m23 = m[2 * 4 + 3];
var m30 = m[3 * 4 + 0];
var m31 = m[3 * 4 + 1];
var m32 = m[3 * 4 + 2];
var m33 = m[3 * 4 + 3];
var tmp_0 = m22 * m33;
var tmp_1 = m32 * m23;
var tmp_2 = m12 * m33;
var tmp_3 = m32 * m13;
var tmp_4 = m12 * m23;
var tmp_5 = m22 * m13;
var tmp_6 = m02 * m33;
var tmp_7 = m32 * m03;
var tmp_8 = m02 * m23;
var tmp_9 = m22 * m03;
var tmp_10 = m02 * m13;
var tmp_11 = m12 * m03;
var tmp_12 = m20 * m31;
var tmp_13 = m30 * m21;
var tmp_14 = m10 * m31;
var tmp_15 = m30 * m11;
var tmp_16 = m10 * m21;
var tmp_17 = m20 * m11;
var tmp_18 = m00 * m31;
var tmp_19 = m30 * m01;
var tmp_20 = m00 * m21;
var tmp_21 = m20 * m01;
var tmp_22 = m00 * m11;
var tmp_23 = m10 * m01;
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
dst[0] = d * t0;
dst[1] = d * t1;
dst[2] = d * t2;
dst[3] = d * t3;
dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33));
dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33));
dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23));
dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22));
dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02));
dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12));
dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02));
return dst;
}
});
function loadImg(uri) {
return new Promise((resolve, reject) => {
const $img = document.createElement('img');
$img.onload = () => resolve($img);
$img.onerror = e => reject(e);
$img.src = uri;
});
}
</script>
</body>
</html>