@xmov/avatar 2.0.0-alpha.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,1293 @@
1
+ // @ts-nocheck
2
+ import { GLDevice } from './GLDevice'
3
+ import { RigidTransform, IBRAnimationGeneratorCharInfo_NN, IBRAnimationFrameData_NN, TextureFloat_3D } from './DataInterface'
4
+ import { vec3, mat3, mat4, transpose, identity, mmul, mvmul, inv3, inv4, flatten } from './Math'
5
+ import { performanceConstant } from "./perfermance";
6
+
7
+ const max_bones_per_vertex = 8; // need to be a multiple of 4
8
+ const shadow_sample_count = 4;
9
+
10
+ const shader_common = `
11
+ float randFloat(vec2 p) {
12
+ // generate a uniformely distributed float in [0, 1).
13
+ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
14
+ }
15
+ bool isMaskedBitSet(uint value, uint mask){
16
+ return bool(value & mask);
17
+ }
18
+ vec4 RGB2sRGB(vec4 color){
19
+ for(uint i = 0u; i < 4u; i++){
20
+ if(color[i] <= 0.0031308)color[i] *= 12.92;
21
+ else color[i] = 1.055 * pow(color[i], 1.0 / 2.4) - 0.055;
22
+ }
23
+ return color;
24
+ }
25
+ vec4 sRGB2RGB(vec4 color){
26
+ for(uint i = 0u; i < 4u; i++){
27
+ if(color[i] <= 0.04045)color[i] /= 12.92;
28
+ else color[i] = pow((color[i] + 0.055) / 1.055, 2.4);
29
+ }
30
+ return color;
31
+ }
32
+ `
33
+
34
+ const vs_background = `#version 300 es
35
+ layout(location = 0) in vec2 pos;
36
+ layout(location = 1) in vec2 texCoord;
37
+ out vec2 v_texCoord;
38
+ void main() {
39
+ gl_Position = vec4(pos, 0, 1);
40
+ v_texCoord = texCoord;
41
+ }
42
+ `
43
+
44
+ const fs_background = `#version 300 es
45
+ precision mediump float;
46
+ uniform sampler2D u_image_bg, u_image_char_body, u_image_mesh_color, u_image_mesh_alpha;
47
+ uniform mat2 u_transform_2d; // mat2[0] = scale, mat2[1] = translation
48
+ uniform uint flags; // 1 - has background, 2 - with multisample coverage, 4 - shadow fx
49
+
50
+ layout (std140) uniform ub_shadow_fx_info {
51
+ mat3 transform[2];
52
+ vec4 color;
53
+ vec4 sampleParams; // sample radius, unused, unused, unused
54
+ } ub_shadow_fx;
55
+
56
+ in vec2 v_texCoord;
57
+ out vec4 fragColor;
58
+
59
+ ${shader_common}
60
+
61
+ vec2 tex2scr(vec2 coord){
62
+ return vec2(coord.x * 2.0 - 1.0, 1.0 - coord.y * 2.0);
63
+ }
64
+ vec2 scr2tex(vec2 coord){
65
+ return vec2(coord.x * 0.5 + 0.5, 0.5 - coord.y * 0.5);
66
+ }
67
+ vec2 randomUnitVec2D(float seed) {
68
+ float theta = seed * 2.0 * 3.14159265359;
69
+ return vec2(cos(theta), sin(theta));
70
+ }
71
+ float sampleOpacity(vec2 texCoord, mat2 u_transform_2d){
72
+ float result = texture(u_image_mesh_color, vec2(texCoord.x, 1.0 - texCoord.y)).a;
73
+ texCoord = texCoord * u_transform_2d[0] + u_transform_2d[1];
74
+ texCoord.x = texCoord.x * 0.5 + 0.5;
75
+ result += (1.0 - result) * texture(u_image_char_body, texCoord).r;
76
+ return result;
77
+ }
78
+ float PI = 3.14159265359;
79
+
80
+ void main() {
81
+ float randSeed = randFloat(vec2(gl_FragCoord.xy));
82
+
83
+ vec2 texcoord_transformed = v_texCoord * u_transform_2d[0] + u_transform_2d[1];
84
+ texcoord_transformed = clamp(texcoord_transformed, 0.0, 1.0);
85
+ vec2 texCoord_char_color = vec2(texcoord_transformed.x * 0.5, texcoord_transformed.y);
86
+ vec2 texCoord_char_alpha = vec2(texcoord_transformed.x * 0.5 + 0.5, texcoord_transformed.y);
87
+ vec2 texCoord_mesh = vec2(v_texCoord.x, 1.0 - v_texCoord.y);
88
+
89
+ vec4 char_color = texture(u_image_char_body, texCoord_char_color);
90
+ vec4 char_alpha = texture(u_image_char_body, texCoord_char_alpha);
91
+ char_color = sRGB2RGB(char_color);
92
+ char_alpha = sRGB2RGB(char_alpha);
93
+ float char_alpha_back = char_alpha.r;
94
+ float char_alpha_front = max(char_alpha.r - char_alpha.b, 0.0);
95
+
96
+ vec4 mesh_color_srgb = texture(u_image_mesh_color, texCoord_mesh);
97
+ float mesh_alpha = mesh_color_srgb.a;
98
+ if(isMaskedBitSet(flags, 2u)){
99
+ float mesh_coverage = texture(u_image_mesh_alpha, texCoord_mesh).r;
100
+ mesh_color_srgb.rgb /= max(mesh_coverage, 9e-5); // epsilon for float16
101
+ }
102
+
103
+ vec3 final_rgb = char_alpha_back * char_color.rgb;
104
+ float final_alpha = char_alpha_back;
105
+
106
+ if(isMaskedBitSet(flags, 4u)){ // fake shadow
107
+ vec3 pos_shadow = vec3(tex2scr(v_texCoord), 1.0);
108
+ pos_shadow = ub_shadow_fx.transform[1] * pos_shadow;
109
+ if(pos_shadow.z > 0.0 && pos_shadow.y > 0.0){
110
+ // simple and noisy blur
111
+ pos_shadow /= pos_shadow.z;
112
+ float radius = ub_shadow_fx.sampleParams.x * pos_shadow.y;
113
+ vec2 randVec = randomUnitVec2D(randSeed);
114
+ mat2 randMat = mat2(randVec.x, randVec.y, -randVec.y, randVec.x);
115
+ float shadow_opacity = 0.0;
116
+
117
+ int sampleCount = radius > 0.0 ? ${shadow_sample_count} : 1;
118
+ for(int i=0;i<sampleCount;i++){
119
+ float sampleDist = (float(i) + 0.5) / float(sampleCount);
120
+ float angle = 26.0 * PI * sampleDist; // prime number * 2pi
121
+ sampleDist = radius * sqrt(sampleDist);
122
+ vec2 sampleVec = sampleDist * randMat * vec2(cos(angle), sin(angle));
123
+
124
+ vec3 pos_shadow_proj = ub_shadow_fx.transform[0] * (pos_shadow + vec3(sampleVec, 0.0));
125
+ vec2 texCoord_shadow = clamp(pos_shadow_proj.xy / pos_shadow_proj.z, -1.0, 1.0);
126
+ texCoord_shadow = scr2tex(texCoord_shadow);
127
+ shadow_opacity += sampleOpacity(texCoord_shadow, u_transform_2d);
128
+ }
129
+ shadow_opacity /= float(sampleCount);
130
+ float shadow_alpha = shadow_opacity * ub_shadow_fx.color.a;
131
+
132
+ final_rgb = final_rgb + (1.0 - final_alpha) * shadow_alpha * ub_shadow_fx.color.rgb;
133
+ final_alpha = final_alpha + (1.0 - final_alpha) * shadow_alpha;
134
+ }
135
+ }
136
+ final_rgb = mesh_alpha * mesh_color_srgb.rgb + (1.0 - mesh_alpha) * final_rgb;
137
+ final_alpha = mesh_alpha + (1.0 - mesh_alpha) * final_alpha;
138
+ final_rgb = char_alpha_front * char_color.rgb + (1.0 - char_alpha_front) * final_rgb;
139
+ final_alpha = char_alpha_front + (1.0 - char_alpha_front) * final_alpha;
140
+
141
+ if(isMaskedBitSet(flags, 1u)){
142
+ vec4 bg_color = sRGB2RGB(texture(u_image_bg, v_texCoord));
143
+ final_rgb += bg_color.rgb * (1.0 - final_alpha);
144
+ final_alpha = 1.0;
145
+ }
146
+ final_rgb = RGB2sRGB(vec4(final_rgb, 1.0)).rgb;
147
+ fragColor = vec4(final_rgb, final_alpha);
148
+ }
149
+ `
150
+
151
+ const uniform_var_list_bg = [
152
+ "u_image_bg", "u_image_char_body", "u_image_mesh_color", "u_image_mesh_alpha", "u_transform_2d", "flags"
153
+ ];
154
+ const uniform_block_list_bg = [
155
+ "ub_shadow_fx_info"
156
+ ];
157
+
158
+ interface MeshStatistics {
159
+ max_pca_component_count: number,
160
+ max_bs_count: number,
161
+ max_bones: number
162
+ }
163
+
164
+ function generateMeshPipelineShader(params: MeshStatistics): { [index: string]: string } {
165
+ const vs_mesh = `#version 300 es
166
+ precision highp float;
167
+
168
+ layout (std140) uniform ub_rig_info {
169
+ mat4 joint_matrices[${params.max_bones}];
170
+ vec4 bs_weights[${params.max_bs_count / 4}];
171
+ } ub_rig;
172
+
173
+ uniform sampler2D u_image_bs; // used for blendshape deformation
174
+ uniform mat4 u_proj_mat;
175
+ uniform mat2 u_transform_2d; // mat2[0] = scale, mat2[1] = translation
176
+ uniform uint u_bs_count; // number of bs weight vectors (divided by 4)
177
+ uniform uint flags; // 1 - has opacity
178
+
179
+ layout(location = 0) in vec3 pos;
180
+ layout(location = 1) in vec2 tex_coord;
181
+ layout(location = 2) in float opacity;
182
+ layout(location = 3) in uvec4 bone_index_0_4;
183
+ layout(location = 4) in uvec4 bone_index_4_8;
184
+ layout(location = 5) in vec4 bone_weight_0_4;
185
+ layout(location = 6) in vec4 bone_weight_4_8;
186
+
187
+ out vec4 v_pos;
188
+ out vec2 v_tex_coord;
189
+ out float v_opacity;
190
+
191
+ ${shader_common}
192
+
193
+ vec4 lbs_transform(vec3 pos){
194
+ float total_weight = 0.0;
195
+ vec4 aug_pos = vec4(pos, 1.0);
196
+ vec4 result = vec4(0.0);
197
+ uint index;
198
+ for(index = 0u;index < 8u;index++){
199
+ uint joint_index;
200
+ float joint_weight;
201
+ if(index < 4u) {
202
+ joint_index = bone_index_0_4[index];
203
+ joint_weight = bone_weight_0_4[index];
204
+ }
205
+ else {
206
+ joint_index = bone_index_4_8[index - 4u];
207
+ joint_weight = bone_weight_4_8[index - 4u];
208
+ }
209
+ if(255u == joint_index)break;
210
+ else result += joint_weight * (ub_rig.joint_matrices[joint_index] * aug_pos);
211
+ }
212
+ result /= result.w;
213
+ return vec4(result.xyz, 1.0);
214
+ }
215
+
216
+ vec3 bs_accumulate(uint vertex_id){
217
+ uint texWidth = uint(textureSize(u_image_bs, 0).x);
218
+ vec3 pos = vec3(0.0);
219
+
220
+ uint vertex_offset = vertex_id * u_bs_count * 3u;
221
+ for(uint i = 0u; i < u_bs_count; i++){
222
+ vec4 bs_weight_vec = ub_rig.bs_weights[i];
223
+ for (uint j = 0u; j < 3u; j++){
224
+ uint texel_offset = j * u_bs_count + vertex_offset + i;
225
+ uint texel_x = texel_offset % texWidth;
226
+ uint texel_y = texel_offset / texWidth;
227
+ vec4 data = texelFetch(u_image_bs, ivec2(texel_x, texel_y), 0);
228
+ pos[j] += dot(data, bs_weight_vec);
229
+ }
230
+ }
231
+ return pos;
232
+ }
233
+
234
+ void main() {
235
+ if(isMaskedBitSet(flags, 1u))v_opacity = opacity; else v_opacity = 1.0;
236
+
237
+ vec3 bs_pos = vec3(0.0);
238
+ if(u_bs_count > 0u)bs_pos = bs_accumulate(uint(gl_VertexID)) * v_opacity;
239
+ v_pos = u_proj_mat * lbs_transform(pos + bs_pos);
240
+ v_pos /= v_pos.w;
241
+ v_pos.xy = u_transform_2d[0] * v_pos.xy + u_transform_2d[1];
242
+ v_tex_coord = tex_coord;
243
+ gl_Position = v_pos;
244
+ }
245
+ `
246
+
247
+ const fs_mask = `#version 300 es
248
+ precision highp float;
249
+
250
+ in vec4 v_pos;
251
+ in float v_opacity;
252
+ in vec2 v_tex_coord;
253
+
254
+ out vec4 fragColor;
255
+
256
+ void main() {
257
+ if(gl_FrontFacing)fragColor = vec4(1.0);
258
+ else fragColor = vec4(0.0);
259
+ }
260
+ `
261
+
262
+ const fs_mesh = `#version 300 es
263
+ precision highp int;
264
+ precision highp float;
265
+ precision highp sampler2DArray;
266
+ precision highp sampler3D;
267
+ layout (std140) uniform ub_pca_info {
268
+ vec4 weights[${params.max_pca_component_count / 4}];
269
+ } ub_pca;
270
+ uniform sampler2DArray u_image_pca;
271
+ uniform sampler3D u_image_lut;
272
+ uniform uint flags; // 2 - has LUT, 4 - unsigned PCA component
273
+ uniform vec3 u_gamma;
274
+ uniform vec3 u_color_balance; // r/c, g/m, b/y
275
+
276
+ ${shader_common}
277
+
278
+ vec4 pca_accumulate(vec2 tex_coord, bool _unsigned){
279
+ ivec3 texSize = textureSize(u_image_pca, 0);
280
+ vec4 result = vec4(0.0);
281
+ for(int i = 0; i < texSize.z; i++){
282
+ int vec_index = i / 4;
283
+ int elem_index = i % 4;
284
+ vec4 pca_sample = texture(u_image_pca, vec3(tex_coord, float(i)));
285
+ if(_unsigned)pca_sample = 2.0 * pca_sample - 1.0;
286
+ result += ub_pca.weights[vec_index][elem_index] * pca_sample;
287
+ }
288
+ return result;
289
+ }
290
+
291
+ in vec4 v_pos;
292
+ in float v_opacity;
293
+ in vec2 v_tex_coord;
294
+
295
+ out vec4 fragColor;
296
+
297
+ void main() {
298
+ if(gl_FrontFacing){
299
+ vec2 fragTexCoord = v_tex_coord;
300
+ vec4 sample_color = pca_accumulate(v_tex_coord, isMaskedBitSet(flags, 4u));
301
+ if(isMaskedBitSet(flags, 2u))sample_color = texture(u_image_lut, sample_color.rgb);
302
+ // Apply gamma correction
303
+ vec3 final_color = pow(sample_color.rgb, u_gamma);
304
+
305
+ // Apply color balance
306
+ // Convert -100 to 100 range to -0.5 to 0.5 range
307
+ vec3 balance_adjusted = u_color_balance / 200.0; // Map -100..100 to -0.5..0.5
308
+
309
+ // Red/Cyan adjustment
310
+ final_color.r += balance_adjusted.x;
311
+ final_color.g -= balance_adjusted.x; // Cyan is opposite of Red
312
+
313
+ // Green/Magenta adjustment
314
+ final_color.g += balance_adjusted.y;
315
+ final_color.b -= balance_adjusted.y; // Magenta is opposite of Green
316
+
317
+ // Blue/Yellow adjustment
318
+ final_color.b += balance_adjusted.z;
319
+ final_color.r -= balance_adjusted.z; // Yellow is opposite of Blue
320
+
321
+ // Clamp values to [0, 1]
322
+ final_color = clamp(final_color, 0.0, 1.0);
323
+ fragColor = sRGB2RGB(vec4(final_color.bgr, v_opacity));
324
+ }
325
+ else fragColor = vec4(0.0);
326
+ }
327
+ `
328
+
329
+ return { vertex: vs_mesh, fragment_mask: fs_mask, fragment: fs_mesh };
330
+ }
331
+
332
+ const uniform_var_list_mesh = [
333
+ "u_proj_mat", "u_transform_2d", "u_image", "u_image_bs", "u_image_pca", "u_image_lut", "u_bs_count", "flags", "u_gamma", "u_color_balance"
334
+ ];
335
+ const uniform_block_list_mesh = [
336
+ "ub_pca_info", "ub_rig_info"
337
+ ];
338
+
339
+ interface PipelineInfo {
340
+ program: WebGLProgram,
341
+ progUniforms: { [index: string]: WebGLUniformLocation },
342
+ progUniformBlocks: { [index: string]: GLint }
343
+ }
344
+ interface TexturePCAModel {
345
+ texture: WebGLTexture,
346
+ unsigned: boolean,
347
+ scalingFactor?: Float32Array;
348
+ }
349
+ interface MeshInfo {
350
+ VAO: WebGLVertexArrayObject,
351
+ buffers: { [index: string]: WebGLBuffer },
352
+ textures: { [index: string]: WebGLTexture },
353
+ texturePCAModels: TexturePCAModel[],
354
+ uniformUInts: { [index: string]: number }
355
+ }
356
+
357
+ export interface ScaleTranslate2D {
358
+ offsetX: number;
359
+ offsetY: number;
360
+ scaleX: number;
361
+ scaleY: number;
362
+ }
363
+
364
+ export interface GLPipelineCharData {
365
+ char: IBRAnimationGeneratorCharInfo_NN | null,
366
+ LUT: TextureFloat_3D | null,
367
+ transform: ScaleTranslate2D;
368
+ multisample: number | null;
369
+ }
370
+
371
+ export interface GLPipelineFrameData {
372
+ backgroundTexture: HTMLImageElement | HTMLVideoElement | null,
373
+ charBodyTexture: HTMLImageElement | HTMLVideoElement,
374
+ data: IBRAnimationFrameData_NN | null,
375
+ fx: { [index: string]: unknown } | null,
376
+ onPostRender: (pipeline: GLPipeline) => GLPipelineFrameData | null // used for capturing rendered images.
377
+ }
378
+
379
+ export interface GLPipelineFXShadow {
380
+ refPos: vec3;
381
+ light: vec3;
382
+ lightType: "point" | "directional";
383
+ intensity: number;
384
+ blurRadius: number;
385
+ tint: vec3;
386
+ }
387
+
388
+ interface GLPipelineFXShadowGeometryInfo {
389
+ Mat_ShadowImage2World: mat3;
390
+ Mat_World2ObjImage: mat3;
391
+ }
392
+
393
+ export class GLPipeline {
394
+ public readonly device: GLDevice;
395
+ private compat_features: { [index: string]: unknown } = {};
396
+
397
+ private charData: GLPipelineCharData | null = null;
398
+ private backgroundPipelineInfo!: PipelineInfo
399
+ private backgroundVAO!: WebGLVertexArrayObject;
400
+ private backgroundBuffers: { [index: string]: WebGLBuffer } = {};
401
+ private backgroundTextures: { [index: string]: WebGLTexture } = {};
402
+ private backgroundTextureSrc: unknown = null;
403
+
404
+ private meshPipelineInfo: PipelineInfo | null = null;
405
+ private maskPipelineInfo: PipelineInfo | null = null;
406
+ private meshInfos: Array<MeshInfo> = [];
407
+ private meshStatistics: MeshStatistics = {
408
+ max_pca_component_count: 4,
409
+ max_bs_count: 4,
410
+ max_bones: 1
411
+ };
412
+
413
+ private FrameTexture_meshColor: WebGLTexture | null = null;
414
+ private FrameTexture_meshAlpha: WebGLTexture | null = null;
415
+ private LUTTexture: WebGLTexture | null = null;
416
+
417
+ private FrameBuffer_mesh: WebGLFramebuffer | null = null;
418
+ private FrameBuffer_meshColor: WebGLFramebuffer | null = null;
419
+ private FrameBuffer_meshAlpha: WebGLFramebuffer | null = null;
420
+ private initSkeletonStatus: RigidTransform[] = [];
421
+
422
+ private frameDataCallback: () => GLPipelineFrameData | null = () => null;
423
+
424
+ private currentGamma: { r: number, g: number, b: number } = { r: 1.0, g: 1.0, b: 1.0 };
425
+ private currentColorBalance: { rc: number, gm: number, by: number } = { rc: 0.0, gm: 0.0, by: 0.0 };
426
+
427
+ private _ub_shadow_fx_data: Float32Array | null = null;
428
+ private _ub_rig_info_data: Float32Array | null = null;
429
+ private _ub_pca_info_data: Float32Array | null = null;
430
+ private first_webgl_render: boolean = true;
431
+ private sendSdkPoint: (point: string, data: any) => void = () => {};
432
+
433
+ private assembleBackgroundPipeline() {
434
+ this._ub_shadow_fx_data = new Float32Array(2 * 12 + 2 * 4);
435
+
436
+ const program = this.device.compileShaderProgram({
437
+ "vertex": vs_background,
438
+ "fragment": fs_background
439
+ });
440
+ const progUniforms = this.device.getShaderProgramUniformLocation(program, uniform_var_list_bg);
441
+ const progUniformBlocks = this.device.getShaderProgramUniformBlockLocation(program, uniform_block_list_bg);
442
+ this.backgroundPipelineInfo = {
443
+ program: program,
444
+ progUniforms: progUniforms,
445
+ progUniformBlocks: progUniformBlocks
446
+ };
447
+ this.device.gl.uniformBlockBinding(this.backgroundPipelineInfo.program, this.backgroundPipelineInfo.progUniformBlocks["ub_shadow_fx_info"], 0);
448
+
449
+ this.backgroundVAO = this.device.gl.createVertexArray();
450
+ this.device.gl.bindVertexArray(this.backgroundVAO);
451
+
452
+ // Setup buffers and attributes
453
+ const positionBuffer = this.device.gl.createBuffer();
454
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, positionBuffer);
455
+ const positions = [
456
+ -1, -1, 0, 1,
457
+ 1, -1, 1, 1,
458
+ -1, 1, 0, 0,
459
+ -1, 1, 0, 0,
460
+ 1, -1, 1, 1,
461
+ 1, 1, 1, 0
462
+ ];
463
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, new Float32Array(positions), this.device.gl.STATIC_DRAW);
464
+
465
+ // pos
466
+ this.device.gl.enableVertexAttribArray(0);
467
+ this.device.gl.vertexAttribPointer(0, 2, this.device.gl.FLOAT, false, 16, 0);
468
+
469
+ // texCoord
470
+ this.device.gl.enableVertexAttribArray(1);
471
+ this.device.gl.vertexAttribPointer(1, 2, this.device.gl.FLOAT, false, 16, 8);
472
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, null);
473
+
474
+ // Create buffer
475
+ let buffers: { [index: string]: WebGLBuffer } = {}
476
+
477
+ buffers['ub_shadow_fx_info'] = this.device.gl.createBuffer();
478
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, buffers['ub_shadow_fx_info']);
479
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, this._ub_shadow_fx_data, this.device.gl.DYNAMIC_DRAW);
480
+
481
+ this.backgroundBuffers = buffers;
482
+
483
+ this.device.gl.bindVertexArray(null);
484
+
485
+ // Create texture
486
+ let textures: { [index: string]: WebGLTexture } = {}
487
+
488
+ textures["u_image_bg"] = this.device.gl.createTexture();
489
+ if (!textures["u_image_bg"]) throw new Error('WebGL texture creation failed.');
490
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, textures["u_image_bg"])
491
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE)
492
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE)
493
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.LINEAR)
494
+
495
+ textures["u_image_char_body"] = this.device.gl.createTexture();
496
+ if (!textures["u_image_char_body"]) throw new Error('WebGL texture creation failed.');
497
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, textures["u_image_char_body"])
498
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE)
499
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE)
500
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.LINEAR)
501
+
502
+ this.backgroundTextures = textures;
503
+ }
504
+ assembleMeshPipelines(data: GLPipelineCharData) {
505
+ if(data.char){
506
+ const char = data.char;
507
+
508
+ // statistics
509
+ let meshStatistics = {
510
+ max_pca_component_count: 4,
511
+ max_bs_count: 4,
512
+ max_bones: Math.max(char.skeleton.length, 1)
513
+ }
514
+ for (let mesh_index = 0; mesh_index < char.mesh.length; mesh_index++) {
515
+ const textureModels = char.mesh[mesh_index].textureModels;
516
+ let pca_component_count = 0;
517
+ for (let model of textureModels) {
518
+ let pca_component_count_model = model.data.size[0] + (4 - 1);
519
+ pca_component_count_model -= pca_component_count_model % 4;
520
+ pca_component_count = Math.max(pca_component_count, pca_component_count_model);
521
+ }
522
+ meshStatistics.max_pca_component_count = Math.max(meshStatistics.max_pca_component_count, pca_component_count);
523
+
524
+ let max_bs_count = char.mesh[mesh_index].blendshapes.size[2] + (4 - 1);
525
+ max_bs_count -= max_bs_count % 4;
526
+ meshStatistics.max_bs_count = Math.max(meshStatistics.max_bs_count, max_bs_count);
527
+ }
528
+ this.meshStatistics = meshStatistics;
529
+ this._ub_rig_info_data = new Float32Array(this.meshStatistics.max_bones * 16 + this.meshStatistics.max_bs_count);
530
+ this._ub_pca_info_data = new Float32Array(this.meshStatistics.max_pca_component_count);
531
+ const shader = generateMeshPipelineShader(this.meshStatistics);
532
+
533
+ let program = this.device.compileShaderProgram({ vertex: shader.vertex, fragment: shader.fragment });
534
+ let progUniforms = this.device.getShaderProgramUniformLocation(program, uniform_var_list_mesh);
535
+ let progUniformBlocks = this.device.getShaderProgramUniformBlockLocation(program, uniform_block_list_mesh);
536
+ this.meshPipelineInfo = {
537
+ program: program,
538
+ progUniforms: progUniforms,
539
+ progUniformBlocks: progUniformBlocks,
540
+ };
541
+ this.device.gl.uniformBlockBinding(this.meshPipelineInfo.program, this.meshPipelineInfo.progUniformBlocks["ub_rig_info"], 0);
542
+ this.device.gl.uniformBlockBinding(this.meshPipelineInfo.program, this.meshPipelineInfo.progUniformBlocks["ub_pca_info"], 1);
543
+
544
+ program = this.device.compileShaderProgram({ vertex: shader.vertex, fragment: shader.fragment_mask });
545
+ progUniforms = this.device.getShaderProgramUniformLocation(program, uniform_var_list_mesh);
546
+ progUniformBlocks = this.device.getShaderProgramUniformBlockLocation(program, uniform_block_list_mesh);
547
+ this.maskPipelineInfo = {
548
+ program: program,
549
+ progUniforms: progUniforms,
550
+ progUniformBlocks: progUniformBlocks,
551
+ };
552
+ this.device.gl.uniformBlockBinding(this.maskPipelineInfo.program, this.maskPipelineInfo.progUniformBlocks["ub_rig_info"], 0);
553
+
554
+ this.meshInfos = [];
555
+ for (let mesh_index = 0; mesh_index < char.mesh.length; mesh_index++) {
556
+ let currentMeshInfo: MeshInfo = {
557
+ VAO: this.device.gl.createVertexArray(),
558
+ buffers: {},
559
+ textures: {},
560
+ texturePCAModels: [],
561
+ uniformUInts: {},
562
+ };
563
+
564
+ // Setup buffers and attributes
565
+ this.device.gl.bindVertexArray(currentMeshInfo.VAO);
566
+
567
+ if (null === this.device.getGLArrayElementType(char.mesh[mesh_index].blendshapes.data)) throw new Error(`Data type unsupported by WebGL: ${char.mesh[mesh_index].blendshapes.data}`);
568
+ currentMeshInfo.buffers['pos'] = this.device.gl.createBuffer();
569
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, currentMeshInfo.buffers['pos']);
570
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, char.mesh[mesh_index].vertices.data, this.device.gl.STATIC_DRAW);
571
+ this.device.gl.enableVertexAttribArray(0);
572
+ this.device.gl.vertexAttribPointer(0, 3, this.device.getGLArrayElementType(char.mesh[mesh_index].blendshapes.data)!, false, 0, 0);
573
+
574
+ if (null === this.device.getGLArrayElementType(char.mesh[mesh_index].UVCoord.data)) throw new Error(`Data type unsupported by WebGL: ${char.mesh[mesh_index].UVCoord.data}`);
575
+ currentMeshInfo.buffers['tex_coord'] = this.device.gl.createBuffer();
576
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, currentMeshInfo.buffers['tex_coord']);
577
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, char.mesh[mesh_index].UVCoord.data, this.device.gl.STATIC_DRAW);
578
+ this.device.gl.enableVertexAttribArray(1);
579
+ this.device.gl.vertexAttribPointer(1, 2, this.device.getGLArrayElementType(char.mesh[mesh_index].UVCoord.data)!, true, 0, 0);
580
+
581
+ if (char.mesh[mesh_index].opacity) {
582
+ if (null === this.device.getGLArrayElementType(char.mesh[mesh_index].opacity!.data)) throw new Error(`Data type unsupported by WebGL: ${char.mesh[mesh_index].opacity!.data}`);
583
+ currentMeshInfo.buffers['opacity'] = this.device.gl.createBuffer();
584
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, currentMeshInfo.buffers['opacity']);
585
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, char.mesh[mesh_index].opacity!.data, this.device.gl.STATIC_DRAW);
586
+ this.device.gl.enableVertexAttribArray(2);
587
+ this.device.gl.vertexAttribPointer(2, 1, this.device.getGLArrayElementType(char.mesh[mesh_index].opacity!.data)!, true, 0, 0);
588
+ }
589
+
590
+ currentMeshInfo.buffers['bone_indices'] = this.device.gl.createBuffer();
591
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, currentMeshInfo.buffers['bone_indices']);
592
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, char.mesh[mesh_index].jointIndex, this.device.gl.STATIC_DRAW);
593
+ this.device.gl.enableVertexAttribArray(3);
594
+ this.device.gl.vertexAttribIPointer(3, 4, this.device.gl.UNSIGNED_BYTE, 8, 0);
595
+ this.device.gl.enableVertexAttribArray(4);
596
+ this.device.gl.vertexAttribIPointer(4, 4, this.device.gl.UNSIGNED_BYTE, 8, 4);
597
+
598
+ currentMeshInfo.buffers['bone_weight'] = this.device.gl.createBuffer();
599
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, currentMeshInfo.buffers['bone_weight']);
600
+ this.device.gl.bufferData(this.device.gl.ARRAY_BUFFER, char.mesh[mesh_index].jointWeight, this.device.gl.STATIC_DRAW);
601
+ this.device.gl.enableVertexAttribArray(5);
602
+ this.device.gl.vertexAttribPointer(5, 4, this.device.gl.FLOAT, false, 32, 0)
603
+ this.device.gl.enableVertexAttribArray(6);
604
+ this.device.gl.vertexAttribPointer(6, 4, this.device.gl.FLOAT, false, 32, 16)
605
+ this.device.gl.bindBuffer(this.device.gl.ARRAY_BUFFER, null);
606
+
607
+ if (null === this.device.getGLArrayElementType(char.mesh[mesh_index].triangles.data)) throw new Error(`Data type unsupported by WebGL: ${char.mesh[mesh_index].triangles.data}`);
608
+ currentMeshInfo.buffers['indices'] = this.device.gl.createBuffer();
609
+ this.device.gl.bindBuffer(this.device.gl.ELEMENT_ARRAY_BUFFER, currentMeshInfo.buffers['indices']);
610
+ this.device.gl.bufferData(this.device.gl.ELEMENT_ARRAY_BUFFER, char.mesh[mesh_index].triangles.data, this.device.gl.STATIC_DRAW);
611
+
612
+ currentMeshInfo.buffers['ub_pca_info'] = this.device.gl.createBuffer();
613
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, currentMeshInfo.buffers['ub_pca_info']);
614
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, new Float32Array(meshStatistics.max_pca_component_count), this.device.gl.DYNAMIC_DRAW);
615
+
616
+ currentMeshInfo.buffers['ub_rig_info'] = this.device.gl.createBuffer();
617
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, currentMeshInfo.buffers['ub_rig_info']);
618
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, new Float32Array(meshStatistics.max_bones * 16 + meshStatistics.max_bs_count), this.device.gl.DYNAMIC_DRAW);
619
+
620
+ this.device.gl.bindVertexArray(null);
621
+
622
+ // Create texture
623
+
624
+ // blendshape texture
625
+ if (char.mesh[mesh_index].blendshapes.size[2] > 0) {
626
+ let bs_texture_size = [Math.ceil(char.mesh[mesh_index].blendshapes.size[2] / 4), char.mesh[mesh_index].blendshapes.size[0] * char.mesh[mesh_index].blendshapes.size[1]];
627
+ // reshape the texture into a square one
628
+ let square_bs_texture_size = Math.ceil(Math.sqrt(bs_texture_size[0] * bs_texture_size[1]));
629
+ let bs_vec_stride = bs_texture_size[0] * 4;
630
+ let bs_texture_data = new Float32Array(square_bs_texture_size * square_bs_texture_size * 4);
631
+ for (let i = 0; i < bs_texture_size[1]; i++) {
632
+ for (let j = 0; j < char.mesh[mesh_index].blendshapes.size[2]; j++)bs_texture_data[i * bs_vec_stride + j] = char.mesh[mesh_index].blendshapes.data[i * char.mesh[mesh_index].blendshapes.size[2] + j] as number;
633
+ for (let j = char.mesh[mesh_index].blendshapes.size[2]; j < bs_vec_stride; j++)bs_texture_data[i * bs_vec_stride + j] = 0.0;
634
+ }
635
+ currentMeshInfo.textures["u_image_bs"] = this.device.gl.createTexture();
636
+ if (!currentMeshInfo.textures["u_image_bs"]) throw new Error('WebGL texture creation failed.');
637
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, currentMeshInfo.textures["u_image_bs"]);
638
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE);
639
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE);
640
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.NEAREST);
641
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MAG_FILTER, this.device.gl.NEAREST);
642
+ this.device.texImage2D(this.device.gl.TEXTURE_2D, 0, square_bs_texture_size, square_bs_texture_size, this.device.gl.RGBA, bs_texture_data);
643
+
644
+ currentMeshInfo.uniformUInts["u_bs_count"] = bs_texture_size[0];
645
+ }
646
+ else currentMeshInfo.uniformUInts["u_bs_count"] = 0;
647
+
648
+ // PCA textures
649
+ for (let pca_model of char.mesh[mesh_index].textureModels) {
650
+ let pca_texture = this.device.gl.createTexture();
651
+ if (!pca_texture) throw new Error('WebGL texture creation failed.');
652
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D_ARRAY, pca_texture);
653
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D_ARRAY, this.device.gl.TEXTURE_WRAP_R, this.device.gl.CLAMP_TO_EDGE);
654
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D_ARRAY, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE);
655
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D_ARRAY, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE);
656
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D_ARRAY, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.LINEAR);
657
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D_ARRAY, this.device.gl.TEXTURE_MAG_FILTER, this.device.gl.LINEAR);
658
+
659
+ let dataType = this.device.getGLArrayElementType(pca_model.data.data);
660
+ let assembledPCAModel: TexturePCAModel = {
661
+ texture: pca_texture,
662
+ unsigned: this.device.gl.UNSIGNED_BYTE === dataType || this.device.gl.UNSIGNED_SHORT === dataType || this.device.gl.UNSIGNED_INT === dataType
663
+ }
664
+
665
+ let PCAData = pca_model.data.data;
666
+ if ("Float64Array" === pca_model.data.data[Symbol.toStringTag]
667
+ || (this.device.gl.FLOAT === dataType && !this.compat_features["OES_texture_float_linear"])
668
+ || (this.device.gl.HALF_FLOAT === dataType && !this.compat_features["OES_texture_half_float_linear"])) {
669
+ const texture_component_size = pca_model.data.size[1] * pca_model.data.size[2] * 3;
670
+ const roundedTexture = new Uint8Array(pca_model.data.size[0] * texture_component_size);
671
+ // for every component, calculate their scale factor and round (-1, 1) to (0, 255)
672
+ let scalingFactor = new Float32Array(pca_model.data.size[0]);
673
+ for (let i = 0; i < pca_model.data.size[0]; i++) {
674
+ let scale_factor = 1e-6;
675
+ for (let j = 0; j < texture_component_size; j++)scale_factor = Math.max(scale_factor, Math.abs(pca_model.data.data[i * texture_component_size + j] as number));
676
+ let inv_scale_factor = 1.0 / scale_factor;
677
+ for (let j = 0; j < texture_component_size; j++) {
678
+ let scaled_value = pca_model.data.data[i * texture_component_size + j] as number * inv_scale_factor;
679
+ roundedTexture[i * texture_component_size + j] = Math.round((scaled_value * 0.5 + 0.5) * 255.0);
680
+ }
681
+ scalingFactor[i] = scale_factor;
682
+ }
683
+ PCAData = roundedTexture;
684
+ assembledPCAModel.unsigned = true;
685
+ assembledPCAModel.scalingFactor = scalingFactor;
686
+ }
687
+ if (pca_model.scalingFactor) assembledPCAModel.scalingFactor = pca_model.scalingFactor.data as Float32Array;
688
+
689
+ this.device.texImage3D(this.device.gl.TEXTURE_2D_ARRAY, 0, pca_model.data.size[1], pca_model.data.size[2], pca_model.data.size[0], this.device.gl.RGB, PCAData);
690
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D_ARRAY, null);
691
+ currentMeshInfo.texturePCAModels.push(assembledPCAModel);
692
+ }
693
+ this.meshInfos.push(currentMeshInfo);
694
+ }
695
+
696
+ this.initSkeletonStatus = char.evalSkeleton();
697
+ }
698
+
699
+ // texture render targets
700
+ this.FrameTexture_meshColor = this.device.gl.createTexture();
701
+ if (!this.FrameTexture_meshColor) throw Error('WebGL texture creation failed.');
702
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.FrameTexture_meshColor);
703
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE);
704
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE);
705
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.NEAREST);
706
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MAG_FILTER, this.device.gl.NEAREST);
707
+ this.device.gl.texImage2D(this.device.gl.TEXTURE_2D, 0, this.device.gl.RGBA8, this.device.canvas.width, this.device.canvas.height, 0, this.device.gl.RGBA, this.device.gl.UNSIGNED_BYTE, null);
708
+
709
+ if (data.multisample) {
710
+ // FrameTexture_meshAlpha has to be RGBA to match the framebuffer format.
711
+ this.FrameTexture_meshAlpha = this.device.gl.createTexture();
712
+ if (!this.FrameTexture_meshAlpha) throw Error('WebGL texture creation failed.');
713
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.FrameTexture_meshAlpha);
714
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE);
715
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE);
716
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.NEAREST);
717
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_2D, this.device.gl.TEXTURE_MAG_FILTER, this.device.gl.NEAREST);
718
+ this.device.gl.texImage2D(this.device.gl.TEXTURE_2D, 0, this.device.gl.RGBA8, this.device.canvas.width, this.device.canvas.height, 0, this.device.gl.RGBA, this.device.gl.UNSIGNED_BYTE, null);
719
+
720
+ this.FrameBuffer_meshColor = this.device.gl.createFramebuffer();
721
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_meshColor);
722
+ this.device.gl.framebufferTexture2D(this.device.gl.FRAMEBUFFER, this.device.gl.COLOR_ATTACHMENT0, this.device.gl.TEXTURE_2D, this.FrameTexture_meshColor, 0);
723
+
724
+ this.FrameBuffer_meshAlpha = this.device.gl.createFramebuffer();
725
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_meshAlpha);
726
+ this.device.gl.framebufferTexture2D(this.device.gl.FRAMEBUFFER, this.device.gl.COLOR_ATTACHMENT0, this.device.gl.TEXTURE_2D, this.FrameTexture_meshAlpha, 0);
727
+
728
+ // depth renderbuffer for mask rendering
729
+ const depthBuffer = this.device.gl.createRenderbuffer();
730
+ this.device.gl.bindRenderbuffer(this.device.gl.RENDERBUFFER, depthBuffer);
731
+ this.device.gl.renderbufferStorageMultisample(this.device.gl.RENDERBUFFER, data.multisample, this.device.gl.DEPTH_COMPONENT16, this.device.canvas.width, this.device.canvas.height);
732
+
733
+ const colorBuffer = this.device.gl.createRenderbuffer();
734
+ this.device.gl.bindRenderbuffer(this.device.gl.RENDERBUFFER, colorBuffer);
735
+ this.device.gl.renderbufferStorageMultisample(this.device.gl.RENDERBUFFER, data.multisample, this.device.gl.RGBA8, this.device.canvas.width, this.device.canvas.height);
736
+
737
+ const coverageBuffer = this.device.gl.createRenderbuffer();
738
+ this.device.gl.bindRenderbuffer(this.device.gl.RENDERBUFFER, coverageBuffer);
739
+ this.device.gl.renderbufferStorageMultisample(this.device.gl.RENDERBUFFER, data.multisample, this.device.gl.R8, this.device.canvas.width, this.device.canvas.height);
740
+
741
+ this.FrameBuffer_mesh = this.device.gl.createFramebuffer();
742
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
743
+ this.device.gl.framebufferRenderbuffer(this.device.gl.FRAMEBUFFER, this.device.gl.COLOR_ATTACHMENT0, this.device.gl.RENDERBUFFER, colorBuffer);
744
+ this.device.gl.framebufferRenderbuffer(this.device.gl.FRAMEBUFFER, this.device.gl.COLOR_ATTACHMENT1, this.device.gl.RENDERBUFFER, coverageBuffer);
745
+ this.device.gl.framebufferRenderbuffer(this.device.gl.FRAMEBUFFER, this.device.gl.DEPTH_ATTACHMENT, this.device.gl.RENDERBUFFER, depthBuffer);
746
+ }
747
+ else {
748
+ this.FrameTexture_meshAlpha = null;
749
+ this.FrameBuffer_meshColor = null;
750
+ this.FrameBuffer_meshAlpha = null;
751
+
752
+ // depth renderbuffer for mask rendering
753
+ const depthBuffer = this.device.gl.createRenderbuffer();
754
+ this.device.gl.bindRenderbuffer(this.device.gl.RENDERBUFFER, depthBuffer);
755
+ this.device.gl.renderbufferStorage(this.device.gl.RENDERBUFFER, this.device.gl.DEPTH_COMPONENT16, this.device.canvas.width, this.device.canvas.height);
756
+
757
+ this.FrameBuffer_mesh = this.device.gl.createFramebuffer();
758
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
759
+ this.device.gl.framebufferTexture2D(this.device.gl.FRAMEBUFFER, this.device.gl.COLOR_ATTACHMENT0, this.device.gl.TEXTURE_2D, this.FrameTexture_meshColor, 0);
760
+ this.device.gl.framebufferRenderbuffer(this.device.gl.FRAMEBUFFER, this.device.gl.DEPTH_ATTACHMENT, this.device.gl.RENDERBUFFER, depthBuffer);
761
+ }
762
+
763
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, null);
764
+
765
+ // LUT textures
766
+ if (data.LUT === null) this.LUTTexture = null;
767
+ else {
768
+ this.LUTTexture = this.device.gl.createTexture();
769
+ if (!this.LUTTexture) throw new Error('WebGL texture creation failed.');
770
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_3D, this.LUTTexture);
771
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_3D, this.device.gl.TEXTURE_WRAP_R, this.device.gl.CLAMP_TO_EDGE);
772
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_3D, this.device.gl.TEXTURE_WRAP_S, this.device.gl.CLAMP_TO_EDGE);
773
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_3D, this.device.gl.TEXTURE_WRAP_T, this.device.gl.CLAMP_TO_EDGE);
774
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_3D, this.device.gl.TEXTURE_MIN_FILTER, this.device.gl.LINEAR);
775
+ this.device.gl.texParameteri(this.device.gl.TEXTURE_3D, this.device.gl.TEXTURE_MAG_FILTER, this.device.gl.LINEAR);
776
+ if (this.compat_features["OES_texture_float_linear"]) {
777
+ this.device.texImage3D(this.device.gl.TEXTURE_3D, 0, data.LUT.size[1], data.LUT.size[2], data.LUT.size[0], this.device.gl.RGB, data.LUT.data);
778
+ }
779
+ else {
780
+ // this will reduce the quality of LUT color correction significantly
781
+ const roundedTexture = new Uint8Array(data.LUT.size[0] * data.LUT.size[1] * data.LUT.size[2] * 3);
782
+ // for the mean texture, convert float to uint8 directly
783
+ for (let j = 0; j < roundedTexture.length; j++)roundedTexture[j] = Math.round(data.LUT.data[j] * 255.0);
784
+ this.device.texImage3D(this.device.gl.TEXTURE_3D, 0, data.LUT.size[1], data.LUT.size[2], data.LUT.size[0], this.device.gl.RGB, roundedTexture);
785
+ }
786
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_3D, null);
787
+ }
788
+
789
+ this.backgroundTextureSrc = null;
790
+ }
791
+ private getProjMat(camera: CameraConfig): mat4 {
792
+ const char = this.charData!.char!;
793
+ return mmul(camera.getProjMatrix([this.device.canvas.width, this.device.canvas.height], 200, 800), camera.getExtrinsicMatrix()) as mat4;
794
+ }
795
+ private initFrame() {
796
+ this.device.gl.viewport(0, 0, this.device.gl.drawingBufferWidth, this.device.gl.drawingBufferHeight);
797
+ this.device.gl.clearColor(0.0, 0.0, 0.0, 0.0);
798
+ this.device.gl.clearDepth(1.0);
799
+ if(this.charData){
800
+ if(this.charData.multisample){
801
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_meshColor);
802
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT);
803
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_meshAlpha);
804
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT);
805
+ }
806
+ else {
807
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
808
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT);
809
+ }
810
+ }
811
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, null);
812
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT | this.device.gl.DEPTH_BUFFER_BIT);
813
+ }
814
+ private renderBackground(frame_data: GLPipelineFrameData) {
815
+ this.device.gl.disable(this.device.gl.DEPTH_TEST);
816
+ this.device.gl.disable(this.device.gl.CULL_FACE);
817
+ this.device.gl.disable(this.device.gl.BLEND);
818
+
819
+ let flags = 0;
820
+ if (frame_data.backgroundTexture) flags += 1;
821
+ if (this.charData!.multisample) flags += 2;
822
+
823
+ this.device.gl.useProgram(this.backgroundPipelineInfo.program);
824
+
825
+ let transform = this.charData ? this.charData.transform : null;
826
+
827
+ if (frame_data.fx) {
828
+ if (frame_data.fx.transform) transform = frame_data.fx.transform as ScaleTranslate2D;
829
+ if (frame_data.fx.shadow) {
830
+ const fx = frame_data.fx.shadow as GLPipelineFXShadow;
831
+ const geometryInfo = this.getBillboardShadowGeometryInfo(fx.refPos, fx.light, fx.lightType === "point");
832
+ flags += 4;
833
+
834
+ for (let i = 0; i < 3; i++) {
835
+ for (let j = 0; j < 3; j++)this._ub_shadow_fx_data![i * 4 + j] = geometryInfo.Mat_World2ObjImage[j][i];
836
+ }
837
+ for (let i = 0; i < 3; i++) {
838
+ for (let j = 0; j < 3; j++)this._ub_shadow_fx_data![i * 4 + j + 12] = geometryInfo.Mat_ShadowImage2World[j][i];
839
+ }
840
+ for (let i = 0; i < 3; i++)this._ub_shadow_fx_data![24 + i] = fx.tint[i];
841
+ this._ub_shadow_fx_data![24 + 3] = fx.intensity;
842
+ this._ub_shadow_fx_data![24 + 4] = fx.blurRadius;
843
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, this.backgroundBuffers["ub_shadow_fx_info"]);
844
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, this._ub_shadow_fx_data, this.device.gl.DYNAMIC_DRAW);
845
+ }
846
+ }
847
+ this.device.gl.bindBufferBase(this.device.gl.UNIFORM_BUFFER, 0, this.backgroundBuffers["ub_shadow_fx_info"]);
848
+
849
+ if (transform) {
850
+ this.device.gl.uniformMatrix2fv(this.backgroundPipelineInfo.progUniforms['u_transform_2d'], false, new Float32Array([
851
+ 1.0 / transform.scaleX, 1.0 / transform.scaleY, -transform.offsetX / transform.scaleX, -transform.offsetY / transform.scaleY
852
+ ]));
853
+ }
854
+
855
+ else this.device.gl.uniformMatrix2fv(this.backgroundPipelineInfo.progUniforms['u_transform_2d'], false, new Float32Array([1.0, 1.0, 0.0, 0.0]));
856
+ this.device.gl.uniform1ui(this.backgroundPipelineInfo.progUniforms['flags'], flags);
857
+ this.device.gl.bindVertexArray(this.backgroundVAO);
858
+
859
+ this.device.gl.uniform1i(this.backgroundPipelineInfo.progUniforms[`u_image_bg`], 0);
860
+ if (frame_data.backgroundTexture !== null) {
861
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 0);
862
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.backgroundTextures!["u_image_bg"]);
863
+ if (this.backgroundTextureSrc !== frame_data.backgroundTexture) {
864
+ this.device.gl.texImage2D(this.device.gl.TEXTURE_2D, 0, this.device.gl.RGB, this.device.gl.RGB, this.device.gl.UNSIGNED_BYTE, frame_data.backgroundTexture);
865
+ this.backgroundTextureSrc = frame_data.backgroundTexture;
866
+ }
867
+ }
868
+ this.device.gl.uniform1i(this.backgroundPipelineInfo.progUniforms[`u_image_char_body`], 1);
869
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 1);
870
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.backgroundTextures!["u_image_char_body"]);
871
+ this.device.gl.texImage2D(this.device.gl.TEXTURE_2D, 0, this.device.gl.RGB, this.device.gl.RGB, this.device.gl.UNSIGNED_BYTE, frame_data.charBodyTexture);
872
+ if(this.charData){
873
+ this.device.gl.uniform1i(this.backgroundPipelineInfo.progUniforms[`u_image_mesh_color`], 2);
874
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 2);
875
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.FrameTexture_meshColor);
876
+ this.device.gl.uniform1i(this.backgroundPipelineInfo.progUniforms[`u_image_mesh_alpha`], 3);
877
+ if (this.charData.multisample) {
878
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 3);
879
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, this.FrameTexture_meshAlpha);
880
+ }
881
+ }
882
+ this.device.gl.drawArrays(this.device.gl.TRIANGLES, 0, 6);
883
+ }
884
+ private renderMesh(data: GLPipelineCharData, frame_data: GLPipelineFrameData) {
885
+ this.device.gl.enable(this.device.gl.DEPTH_TEST);
886
+ this.device.gl.disable(this.device.gl.CULL_FACE);
887
+ this.device.gl.disable(this.device.gl.BLEND);
888
+
889
+ const char = data.char!;
890
+
891
+ let proj_mat = new Float32Array(flatten(this.getProjMat(char.cameraConfig)));
892
+ let transform = this.charData ? this.charData.transform : null;
893
+ if (frame_data.fx && frame_data.fx.transform) transform = frame_data.fx.transform as ScaleTranslate2D;
894
+ let transform_2d_mat: Float32Array;
895
+ if (transform) transform_2d_mat = new Float32Array([transform.scaleX, transform.scaleY, (transform.scaleX - 1.0) + 2.0 * transform.offsetX, (1.0 - transform.scaleY) - 2.0 * transform.offsetY]);
896
+ else transform_2d_mat = new Float32Array([1.0, 1.0, 0.0, 0.0]);
897
+
898
+ let currentSkeletonStatus = char.evalSkeletonFromMovable(frame_data.data!.movableJointTransforms);
899
+ let joint_matrices = Array(char.skeleton.length);
900
+ for (let i = 0; i < char.skeleton.length; i++) {
901
+ const joint_transform = currentSkeletonStatus[i].apply(this.initSkeletonStatus[i].inv()) as RigidTransform;
902
+ joint_matrices[i] = joint_transform.homogeneous_matrix();
903
+ }
904
+
905
+ let ub_rig_info_data_offset = 0;
906
+ for (let i = 0; i < Math.min(this.meshStatistics.max_bones, char.skeleton.length); i++) {
907
+ for (let j = 0; j < 4; j++) {
908
+ for (let k = 0; k < 4; k++)this._ub_rig_info_data![ub_rig_info_data_offset + j * 4 + k] = joint_matrices[i][k][j];
909
+ }
910
+ ub_rig_info_data_offset += 16;
911
+ }
912
+
913
+ this.device.gl.clearColor(0.0, 0.0, 0.0, 0.0);
914
+ this.device.gl.clearDepth(1.0);
915
+
916
+ if (this.charData!.multisample) {
917
+ // render mask
918
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
919
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT | this.device.gl.DEPTH_BUFFER_BIT);
920
+
921
+ this.device.gl.useProgram(this.maskPipelineInfo!.program);
922
+ this.device.gl.uniformMatrix4fv(this.maskPipelineInfo!.progUniforms['u_proj_mat'], true, proj_mat);
923
+ this.device.gl.uniformMatrix2fv(this.maskPipelineInfo!.progUniforms['u_transform_2d'], false, transform_2d_mat);
924
+ for (let mesh_index = 0; mesh_index < char.mesh.length; mesh_index++) {
925
+ if (char.mesh[mesh_index].genMask) {
926
+ const currentMeshInfo = this.meshInfos[mesh_index];
927
+
928
+ if (char.mesh[mesh_index].blendshapes.size[2] > 0) {
929
+ ub_rig_info_data_offset = this.meshStatistics.max_bones * 16;
930
+ let effective_bs_count = Math.min(char.mesh[mesh_index].blendshapeIndices.length, this.meshStatistics.max_bs_count);
931
+ for (let i = 0; i < effective_bs_count; i++)this._ub_rig_info_data![ub_rig_info_data_offset + i] = frame_data.data!.blendshapeWeights[char.mesh[mesh_index].blendshapeIndices[i]];
932
+ for (let i = effective_bs_count; i < this.meshStatistics.max_bs_count; i++)this._ub_rig_info_data![ub_rig_info_data_offset + i] = 0.0;
933
+ }
934
+
935
+ let flags = 0;
936
+ if (char.mesh[mesh_index].opacity) flags += 1;
937
+
938
+ for (const var_name in currentMeshInfo.uniformUInts) this.device.gl.uniform1ui(this.maskPipelineInfo!.progUniforms[var_name], currentMeshInfo.uniformUInts[var_name]);
939
+ this.device.gl.uniform1ui(this.maskPipelineInfo!.progUniforms['flags'], flags);
940
+
941
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, currentMeshInfo.buffers!['ub_rig_info']);
942
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, this._ub_rig_info_data, this.device.gl.DYNAMIC_DRAW);
943
+ this.device.gl.bindBufferBase(this.device.gl.UNIFORM_BUFFER, 0, currentMeshInfo.buffers!["ub_rig_info"]);
944
+
945
+ this.device.gl.bindVertexArray(currentMeshInfo.VAO);
946
+ this.device.gl.bindBuffer(this.device.gl.ELEMENT_ARRAY_BUFFER, currentMeshInfo.buffers!["indices"]);
947
+ this.device.gl.uniform1i(this.maskPipelineInfo!.progUniforms[`u_image_bs`], 0);
948
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 0);
949
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, currentMeshInfo.textures!["u_image_bs"]);
950
+
951
+ this.device.gl.drawElements(this.device.gl.TRIANGLES, char.mesh[mesh_index].triangles.itemSize(), this.device.getGLArrayElementType(char.mesh[mesh_index].triangles.data)!, 0);
952
+ }
953
+ }
954
+ this.device.gl.bindFramebuffer(this.device.gl.DRAW_FRAMEBUFFER, this.FrameBuffer_meshAlpha);
955
+ this.device.gl.blitFramebuffer(0, 0, this.device.canvas.width, this.device.canvas.height, 0, 0, this.device.canvas.width, this.device.canvas.height, this.device.gl.COLOR_BUFFER_BIT, this.device.gl.NEAREST);
956
+ }
957
+
958
+ // render color
959
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
960
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT | this.device.gl.DEPTH_BUFFER_BIT);
961
+ this.device.gl.useProgram(this.meshPipelineInfo!.program);
962
+ this.device.gl.uniformMatrix4fv(this.meshPipelineInfo!.progUniforms['u_proj_mat'], true, proj_mat);
963
+ this.device.gl.uniformMatrix2fv(this.meshPipelineInfo!.progUniforms['u_transform_2d'], false, transform_2d_mat);
964
+ let genMaskForCurrentMesh = true;
965
+ for (let mesh_index = 0; mesh_index < char.mesh.length; mesh_index++) {
966
+ const currentMeshInfo = this.meshInfos[mesh_index];
967
+ let PCAModel = currentMeshInfo.texturePCAModels[frame_data.data!.mesh[mesh_index].textureModelIndex];
968
+
969
+ if (char.mesh[mesh_index].blendshapes.size[0] > 0) {
970
+ ub_rig_info_data_offset = this.meshStatistics.max_bones * 16;
971
+ let effective_bs_count = Math.min(char.mesh[mesh_index].blendshapeIndices.length, this.meshStatistics.max_bs_count);
972
+ for (let i = 0; i < effective_bs_count; i++)this._ub_rig_info_data![ub_rig_info_data_offset + i] = frame_data.data!.blendshapeWeights[char.mesh[mesh_index].blendshapeIndices[i]];
973
+ for (let i = effective_bs_count; i < this.meshStatistics.max_bs_count; i++)this._ub_rig_info_data![ub_rig_info_data_offset + i] = 0.0;
974
+ }
975
+
976
+ let flags = 0;
977
+ if (char.mesh[mesh_index].opacity) flags += 1;
978
+ if (this.LUTTexture !== null) flags += 2;
979
+ if (PCAModel.unsigned) flags += 4;
980
+
981
+ if (genMaskForCurrentMesh != char.mesh[mesh_index].genMask) {
982
+ if (char.mesh[mesh_index].genMask) {
983
+ this.device.gl.colorMask(true, true, true, true);
984
+ }
985
+ else {
986
+ this.device.gl.colorMask(true, true, true, false);
987
+ }
988
+ genMaskForCurrentMesh = char.mesh[mesh_index].genMask;
989
+ }
990
+
991
+ for (const var_name in currentMeshInfo.uniformUInts) this.device.gl.uniform1ui(this.meshPipelineInfo!.progUniforms[var_name], currentMeshInfo.uniformUInts[var_name]);
992
+ this.device.gl.uniform1ui(this.meshPipelineInfo!.progUniforms['flags'], flags);
993
+ this.device.gl.uniform3f(this.meshPipelineInfo!.progUniforms['u_gamma'], this.currentGamma.r, this.currentGamma.g, this.currentGamma.b);
994
+ this.device.gl.uniform3f(this.meshPipelineInfo!.progUniforms['u_color_balance'], this.currentColorBalance.rc, this.currentColorBalance.gm, this.currentColorBalance.by);
995
+
996
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, currentMeshInfo.buffers!['ub_rig_info']);
997
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, this._ub_rig_info_data, this.device.gl.DYNAMIC_DRAW);
998
+ this.device.gl.bindBufferBase(this.device.gl.UNIFORM_BUFFER, 0, currentMeshInfo.buffers!["ub_rig_info"]);
999
+
1000
+ let effective_pca_component_count = Math.min(frame_data.data!.mesh[mesh_index].texturePCAWeights.length + 1, this._ub_pca_info_data!.length);
1001
+ this._ub_pca_info_data![0] = 1.0;
1002
+ for (let i = 1; i < effective_pca_component_count; i++)this._ub_pca_info_data![i] = frame_data.data!.mesh[mesh_index].texturePCAWeights[i - 1];
1003
+ if (PCAModel.scalingFactor) {
1004
+ for (let i = 0; i < effective_pca_component_count; i++)this._ub_pca_info_data![i] *= PCAModel.scalingFactor[i];
1005
+ }
1006
+ for (let i = effective_pca_component_count; i < this.meshStatistics.max_pca_component_count; i++)this._ub_pca_info_data![i] = 0.0;
1007
+ this.device.gl.bindBuffer(this.device.gl.UNIFORM_BUFFER, currentMeshInfo.buffers!['ub_pca_info']);
1008
+ this.device.gl.bufferData(this.device.gl.UNIFORM_BUFFER, this._ub_pca_info_data, this.device.gl.DYNAMIC_DRAW);
1009
+ this.device.gl.bindBufferBase(this.device.gl.UNIFORM_BUFFER, 1, currentMeshInfo.buffers!["ub_pca_info"]);
1010
+
1011
+ this.device.gl.bindVertexArray(currentMeshInfo.VAO);
1012
+ this.device.gl.bindBuffer(this.device.gl.ELEMENT_ARRAY_BUFFER, currentMeshInfo.buffers!["indices"]);
1013
+ this.device.gl.uniform1i(this.meshPipelineInfo!.progUniforms[`u_image_bs`], 0);
1014
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 0);
1015
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_2D, currentMeshInfo.textures!["u_image_bs"]);
1016
+ this.device.gl.uniform1i(this.meshPipelineInfo!.progUniforms[`u_image_pca`], 1);
1017
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 1);
1018
+ if (PCAModel.texture) this.device.gl.bindTexture(this.device.gl.TEXTURE_2D_ARRAY, PCAModel.texture);
1019
+ this.device.gl.uniform1i(this.meshPipelineInfo!.progUniforms[`u_image_lut`], 2);
1020
+ if (this.LUTTexture) {
1021
+ this.device.gl.activeTexture(this.device.gl.TEXTURE0 + 2);
1022
+ this.device.gl.bindTexture(this.device.gl.TEXTURE_3D, this.LUTTexture);
1023
+ }
1024
+ this.device.gl.drawElements(this.device.gl.TRIANGLES, char.mesh[mesh_index].triangles.itemSize(), this.device.getGLArrayElementType(char.mesh[mesh_index].triangles.data)!, 0);
1025
+ }
1026
+
1027
+ if (!genMaskForCurrentMesh) this.device.gl.colorMask(true, true, true, true);
1028
+
1029
+ if (this.charData!.multisample) {
1030
+ this.device.gl.bindFramebuffer(this.device.gl.DRAW_FRAMEBUFFER, this.FrameBuffer_meshColor);
1031
+ this.device.gl.blitFramebuffer(0, 0, this.device.canvas.width, this.device.canvas.height, 0, 0, this.device.canvas.width, this.device.canvas.height, this.device.gl.COLOR_BUFFER_BIT, this.device.gl.NEAREST);
1032
+ }
1033
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, null);
1034
+ }
1035
+
1036
+ constructor(device: GLDevice, sendSdkPoint: (point: string, data: any) => void) {
1037
+ this.device = device;
1038
+ this.sendSdkPoint = sendSdkPoint;
1039
+ this.reinitialize();
1040
+ }
1041
+ public reinitialize() {
1042
+ this.compat_features = {};
1043
+ this.compat_features["OES_texture_float_linear"] = this.device.gl.getExtension("OES_texture_float_linear"); // this extension is essential for loseless PCA texture composition
1044
+ this.compat_features["OES_texture_half_float_linear"] = this.device.gl.getExtension("OES_texture_half_float_linear");
1045
+ this.assembleBackgroundPipeline();
1046
+ if (this.charData) this.assembleMeshPipelines(this.charData);
1047
+ }
1048
+ public setSyncMedia(syncMedia?: HTMLVideoElement) {
1049
+ this.device.run((device) => this.render(), syncMedia);
1050
+ }
1051
+ public setCharData(charData?: GLPipelineCharData) {
1052
+ if (charData) {
1053
+ this.charData = { ...charData }; // shallow copy
1054
+ // if (this.charData.multisample && this.charData.multisample <= 1) this.charData.multisample = null;
1055
+ this.charData.multisample = null;
1056
+ this.assembleMeshPipelines(this.charData);
1057
+ }
1058
+ else this.charData = null;
1059
+ }
1060
+ public setFrameDataCallback(cb: () => GLPipelineFrameData | null) {
1061
+ this.frameDataCallback = cb;
1062
+ }
1063
+ public setGamma(gammaR: number, gammaG: number, gammaB: number) {
1064
+ this.currentGamma = { r: gammaR, g: gammaG, b: gammaB };
1065
+ }
1066
+ public setColorBalance(rc: number, gm: number, by: number) {
1067
+ this.currentColorBalance = { rc: rc, gm: gm, by: by };
1068
+ }
1069
+ public getBillboardTransform2D(pos_orig: vec3, pos_new: vec3): ScaleTranslate2D {
1070
+ if (null == this.charData || null == this.charData.char) {
1071
+ return {
1072
+ offsetX: 0.0,
1073
+ offsetY: 0.0,
1074
+ scaleX: 1.0,
1075
+ scaleY: 1.0
1076
+ };
1077
+ }
1078
+ else {
1079
+ const projMat = this.getProjMat(this.charData.char.cameraConfig);
1080
+ const projMatInv = inv4(projMat);
1081
+ const posWorld = [
1082
+ [pos_orig[0], pos_new[0]],
1083
+ [pos_orig[1], pos_new[1]],
1084
+ [pos_orig[2], pos_new[2]],
1085
+ [1.0, 1.0]
1086
+ ];
1087
+ const posScr = mmul(projMat, posWorld);
1088
+ const w_ratio = posScr[3][0] / posScr[3][1];
1089
+ const dxdPx_orig = [
1090
+ projMatInv[0][0] - pos_orig[0] * projMatInv[3][0],
1091
+ projMatInv[1][0] - pos_orig[1] * projMatInv[3][0],
1092
+ projMatInv[2][0] - pos_orig[2] * projMatInv[3][0]
1093
+ ];
1094
+ const dxdPx_new = [
1095
+ projMatInv[0][0] - pos_new[0] * projMatInv[3][0],
1096
+ projMatInv[1][0] - pos_new[1] * projMatInv[3][0],
1097
+ projMatInv[2][0] - pos_new[2] * projMatInv[3][0]
1098
+ ];
1099
+ const dxdPy_orig = [
1100
+ projMatInv[0][1] - pos_orig[0] * projMatInv[3][1],
1101
+ projMatInv[1][1] - pos_orig[1] * projMatInv[3][1],
1102
+ projMatInv[2][1] - pos_orig[2] * projMatInv[3][1]
1103
+ ];
1104
+ const dxdPy_new = [
1105
+ projMatInv[0][1] - pos_new[0] * projMatInv[3][1],
1106
+ projMatInv[1][1] - pos_new[1] * projMatInv[3][1],
1107
+ projMatInv[2][1] - pos_new[2] * projMatInv[3][1]
1108
+ ];
1109
+
1110
+ const scaleX = w_ratio * Math.sqrt(dxdPx_orig.reduce((sum, val) => (sum + val * val), 0.0) / dxdPx_new.reduce((sum, val) => (sum + val * val), 0.0));
1111
+ const scaleY = w_ratio * Math.sqrt(dxdPy_orig.reduce((sum, val) => (sum + val * val), 0.0) / dxdPy_new.reduce((sum, val) => (sum + val * val), 0.0));
1112
+ const offsetX = posScr[0][1] / posScr[3][1] - posScr[0][0] / posScr[3][0];
1113
+ const offsetY = posScr[1][1] / posScr[3][1] - posScr[1][0] / posScr[3][0];
1114
+ return {
1115
+ offsetX: 0.5 * (offsetX - (scaleX - 1.0)),
1116
+ offsetY: 0.5 * ((1.0 - scaleY) - offsetY),
1117
+ scaleX: scaleX,
1118
+ scaleY: scaleY
1119
+ };
1120
+ }
1121
+ }
1122
+ public getBillboardShadowGeometryInfo(pos_orig: vec3, light: vec3, point_src: boolean): GLPipelineFXShadowGeometryInfo {
1123
+ if (null == this.charData || null == this.charData.char) {
1124
+ return {
1125
+ Mat_ShadowImage2World: identity(3) as mat3,
1126
+ Mat_World2ObjImage: identity(3) as mat3
1127
+ };
1128
+ }
1129
+ else {
1130
+ const projMat = this.getProjMat(this.charData.char.cameraConfig);
1131
+ const projMatInv = inv4(projMat);
1132
+
1133
+ // get transition mat
1134
+ const C1 = [
1135
+ [1.0, 0.0, 0.0],
1136
+ [0.0, pos_orig[1], 1.0],
1137
+ [0.0, 0.0, 0.0]
1138
+ ];
1139
+ const C2 = [
1140
+ [1.0, 0.0, 0.0],
1141
+ [0.0, pos_orig[2], 1.0],
1142
+ [0.0, 0.0, 0.0]
1143
+ ];
1144
+
1145
+ if (point_src) {
1146
+ C1[2] = [light[0], light[1], 1.0];
1147
+ C2[2] = [pos_orig[0], -light[2], 0.0];
1148
+ }
1149
+ else {
1150
+ C1[2] = [light[0], light[1], 0.0];
1151
+ C2[2] = [0.0, -light[2], 0.0];
1152
+ }
1153
+
1154
+ const transitionMat = transpose(mmul(inv3(C2 as mat3), C1));
1155
+ const posWorld = [pos_orig[0], pos_orig[1], pos_orig[2], 1.0];
1156
+ const posScr = mvmul(projMat, posWorld);
1157
+ const w_ratio = posScr[2] / posScr[3];
1158
+ const ProjMat_FixY = [
1159
+ [projMat[0][0], projMat[0][2], projMat[0][1] * pos_orig[1] + projMat[0][3]],
1160
+ [projMat[1][0], projMat[1][2], projMat[1][1] * pos_orig[1] + projMat[1][3]],
1161
+ [projMat[3][0], projMat[3][2], projMat[3][1] * pos_orig[1] + projMat[3][3]]
1162
+ ];
1163
+ const ProjMat_FixY_Inv = inv3(ProjMat_FixY as mat3);
1164
+ const ProjMat_FixY_Inv_transitioned = mmul(transitionMat, ProjMat_FixY_Inv);
1165
+ const ProjMatInv_FixZ = [
1166
+ [projMatInv[0][0], projMatInv[0][1], projMatInv[0][2] * w_ratio + projMatInv[0][3]],
1167
+ [projMatInv[1][0], projMatInv[1][1], projMatInv[1][2] * w_ratio + projMatInv[1][3]],
1168
+ [projMatInv[3][0], projMatInv[3][1], projMatInv[3][2] * w_ratio + projMatInv[3][3]]
1169
+ ];
1170
+ const ProjMatInv_FixZ_Inv = inv3(ProjMatInv_FixZ as mat3);
1171
+ return {
1172
+ Mat_ShadowImage2World: ProjMat_FixY_Inv_transitioned as mat3,
1173
+ Mat_World2ObjImage: ProjMatInv_FixZ_Inv as mat3
1174
+ };
1175
+ }
1176
+ }
1177
+ private render() {
1178
+ const frameData = this.frameDataCallback();
1179
+ if (null !== frameData) {
1180
+ this.initFrame();
1181
+ if(null !== this.charData && null !== this.charData.char && null !== frameData.data)this.renderMesh(this.charData, frameData);
1182
+ else {
1183
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, this.FrameBuffer_mesh);
1184
+ this.device.gl.clear(this.device.gl.COLOR_BUFFER_BIT | this.device.gl.DEPTH_BUFFER_BIT);
1185
+ this.device.gl.bindFramebuffer(this.device.gl.FRAMEBUFFER, null);
1186
+ }
1187
+ this.renderBackground(frameData);
1188
+ if(null !== frameData.onPostRender)frameData.onPostRender(this);
1189
+ }
1190
+
1191
+ this.device.gl?.flush();
1192
+ }
1193
+ public renderFrame(frame: GLPipelineFrameData) {
1194
+ this.initFrame();
1195
+ // 检查faceFrame数据是否有效
1196
+ if (null !== this.charData && null !== this.charData.char && null !== frame.data)this.renderMesh(this.charData!, frame);
1197
+ // 总是渲染背景
1198
+ this.renderBackground(frame);
1199
+ if(this.first_webgl_render) {
1200
+ (window as any).performanceTracker.markEnd(performanceConstant.first_render);
1201
+ (window as any).performanceTracker.markEnd(performanceConstant.first_webgl_render);
1202
+ this.first_webgl_render = false;
1203
+ this.sendSdkPoint('render_success_time', {
1204
+ timestamp: Date.now(),
1205
+ });
1206
+ }
1207
+ this.device.gl.flush();
1208
+ }
1209
+
1210
+ public destroy(): void {
1211
+ // 销毁网格相关资源
1212
+ if(this.device.gl) {
1213
+ if (this.meshInfos) {
1214
+ this.meshInfos.forEach(meshInfo => {
1215
+ // 销毁VAO
1216
+ if (meshInfo.VAO) {
1217
+ this.device.gl.deleteVertexArray(meshInfo.VAO);
1218
+ }
1219
+
1220
+ // 销毁缓冲区
1221
+ Object.values(meshInfo.buffers).forEach(buffer => {
1222
+ if (buffer) {
1223
+ this.device.gl.deleteBuffer(buffer);
1224
+ }
1225
+ });
1226
+
1227
+ // 销毁纹理
1228
+ Object.values(meshInfo.textures).forEach(texture => {
1229
+ if (texture) {
1230
+ this.device.gl.deleteTexture(texture);
1231
+ }
1232
+ });
1233
+
1234
+ // 销毁PCA纹理数组
1235
+ meshInfo.texturePCAModels.forEach(pcaModel => {
1236
+ if (pcaModel.texture) {
1237
+ this.device.gl.deleteTexture(pcaModel.texture);
1238
+ }
1239
+ });
1240
+ });
1241
+ this.meshInfos = [];
1242
+ }
1243
+
1244
+ // 销毁帧缓冲和渲染缓冲
1245
+ if (this.FrameBuffer_MSAA) {
1246
+ this.device.gl.deleteFramebuffer(this.FrameBuffer_MSAA);
1247
+ this.FrameBuffer_MSAA = null;
1248
+ }
1249
+ if (this.FrameBuffer_meshColor) {
1250
+ this.device.gl.deleteFramebuffer(this.FrameBuffer_meshColor);
1251
+ this.FrameBuffer_meshColor = null;
1252
+ }
1253
+ if (this.FrameBuffer_meshAlpha) {
1254
+ this.device.gl.deleteFramebuffer(this.FrameBuffer_meshAlpha);
1255
+ this.FrameBuffer_meshAlpha = null;
1256
+ }
1257
+
1258
+ // 销毁帧纹理
1259
+ if (this.FrameTexture_meshColor) {
1260
+ this.device.gl.deleteTexture(this.FrameTexture_meshColor);
1261
+ this.FrameTexture_meshColor = null;
1262
+ }
1263
+ if (this.FrameTexture_meshAlpha) {
1264
+ this.device.gl.deleteTexture(this.FrameTexture_meshAlpha);
1265
+ this.FrameTexture_meshAlpha = null;
1266
+ }
1267
+
1268
+ // 销毁LUT纹理
1269
+ if (this.LUTTexture) {
1270
+ this.device.gl.deleteTexture(this.LUTTexture);
1271
+ this.LUTTexture = null;
1272
+ }
1273
+
1274
+ // 销毁着色器程序
1275
+ if (this.meshPipelineInfo?.program) {
1276
+ this.device.gl.deleteProgram(this.meshPipelineInfo.program);
1277
+ this.meshPipelineInfo.program = null;
1278
+ }
1279
+ if (this.maskPipelineInfo?.program) {
1280
+ this.device.gl.deleteProgram(this.maskPipelineInfo.program);
1281
+ this.maskPipelineInfo.program = null;
1282
+ }
1283
+ }
1284
+
1285
+ // 清除数据数组引用
1286
+ this._ub_rig_info_data = null;
1287
+ this._ub_pca_info_data = null;
1288
+ this.first_webgl_render = true;
1289
+
1290
+ // 通知设备销毁(如果需要)
1291
+ this.device.destroy?.();
1292
+ }
1293
+ }