ice3 0.2.1 → 0.2.2
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.
- package/README.md +7 -6
- package/dist/_types/assets/ICEGLBReader.d.ts +8 -1
- package/dist/_types/assets/ICEPreparedGLB.d.ts +25 -0
- package/dist/_types/assets/index.d.ts +1 -0
- package/dist/_types/core/camera/ICEPerspectiveCamera.d.ts +5 -0
- package/dist/_types/core/index.d.ts +1 -1
- package/dist/_types/core/material/ICEBasicMaterial.d.ts +50 -0
- package/dist/_types/renderer/InstanceBatch.d.ts +7 -0
- package/dist/_types/renderer/SceneBounds.d.ts +16 -0
- package/dist/_types/renderer/ShadowFrustum.d.ts +20 -0
- package/dist/_types/renderer/WebGL2DepthMath.d.ts +22 -0
- package/dist/_types/renderer/WebGL2InstanceBatch.d.ts +1 -1
- package/dist/_types/renderer/WebGL2Program.d.ts +55 -1
- package/dist/_types/renderer/WebGL2RendererBackend.d.ts +41 -1
- package/dist/_types/renderer/WebGL2SceneDepth.d.ts +19 -0
- package/dist/_types/renderer/WebGL2Shader.d.ts +11 -0
- package/dist/_types/renderer/WebGL2ShadowMap.d.ts +22 -6
- package/dist/_types/renderer/WebGL2TextureCache.d.ts +31 -0
- package/dist/_types/renderer/WebGL2Uv.d.ts +16 -0
- package/dist/_types/renderer/index.d.ts +1 -1
- package/dist/_types/renderer/types.d.ts +6 -0
- package/dist/_types/runtime/ICE3.d.ts +6 -0
- package/dist/index.js +1275 -166
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -389,22 +389,41 @@ var ICEIndexedGeometry = class {
|
|
|
389
389
|
};
|
|
390
390
|
|
|
391
391
|
// packages/core/dist/material/ICEBasicMaterial.js
|
|
392
|
+
var DEFAULT_ROUGHNESS = 0.85;
|
|
392
393
|
var ICEBasicMaterial = class {
|
|
393
394
|
typeId = "ice3:BasicMaterial";
|
|
394
395
|
color;
|
|
395
396
|
edgeColor;
|
|
396
397
|
shading;
|
|
398
|
+
map;
|
|
399
|
+
normalMap;
|
|
400
|
+
ormMap;
|
|
401
|
+
metallic;
|
|
402
|
+
roughness;
|
|
403
|
+
emissive;
|
|
397
404
|
constructor(options) {
|
|
398
405
|
this.color = options.color;
|
|
399
406
|
this.edgeColor = options.edgeColor;
|
|
400
407
|
this.shading = options.shading;
|
|
408
|
+
this.map = options.map;
|
|
409
|
+
this.normalMap = options.normalMap;
|
|
410
|
+
this.ormMap = options.ormMap;
|
|
411
|
+
this.metallic = options.metallic ?? 0;
|
|
412
|
+
this.roughness = options.roughness ?? DEFAULT_ROUGHNESS;
|
|
413
|
+
this.emissive = options.emissive;
|
|
401
414
|
}
|
|
402
415
|
toJSON() {
|
|
403
416
|
return {
|
|
404
417
|
typeId: this.typeId,
|
|
405
418
|
color: this.color,
|
|
406
419
|
...this.edgeColor ? { edgeColor: this.edgeColor } : {},
|
|
407
|
-
...this.shading ? { shading: this.shading } : {}
|
|
420
|
+
...this.shading ? { shading: this.shading } : {},
|
|
421
|
+
...this.map ? { map: this.map } : {},
|
|
422
|
+
...this.normalMap ? { normalMap: this.normalMap } : {},
|
|
423
|
+
...this.ormMap ? { ormMap: this.ormMap } : {},
|
|
424
|
+
...this.metallic !== 0 ? { metallic: this.metallic } : {},
|
|
425
|
+
...this.roughness !== DEFAULT_ROUGHNESS ? { roughness: this.roughness } : {},
|
|
426
|
+
...this.emissive ? { emissive: this.emissive } : {}
|
|
408
427
|
};
|
|
409
428
|
}
|
|
410
429
|
};
|
|
@@ -450,10 +469,17 @@ var ICEPerspectiveCamera = class {
|
|
|
450
469
|
getTarget() {
|
|
451
470
|
return this.lookTarget;
|
|
452
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* 投影矩阵。屏幕空间效果要从深度重建视空间坐标,光有 viewProjection 不够。
|
|
474
|
+
*/
|
|
475
|
+
getProjectionMatrix(aspect) {
|
|
476
|
+
return new ICEMatrix4().perspective(this.fov, aspect, this.near, this.far);
|
|
477
|
+
}
|
|
478
|
+
getViewMatrix() {
|
|
479
|
+
return new ICEMatrix4().lookAt(this.eye, this.lookTarget, new ICEVector3(0, 1, 0));
|
|
480
|
+
}
|
|
453
481
|
getViewProjectionMatrix(aspect) {
|
|
454
|
-
|
|
455
|
-
const view = new ICEMatrix4().lookAt(this.eye, this.lookTarget, new ICEVector3(0, 1, 0));
|
|
456
|
-
return projection.multiply(view);
|
|
482
|
+
return this.getProjectionMatrix(aspect).multiply(this.getViewMatrix());
|
|
457
483
|
}
|
|
458
484
|
};
|
|
459
485
|
|
|
@@ -972,6 +998,7 @@ void main() {
|
|
|
972
998
|
var FRAGMENT_SOURCE = `#version 300 es
|
|
973
999
|
precision highp float;
|
|
974
1000
|
uniform vec4 u_color;
|
|
1001
|
+
uniform vec4 u_clipPlane;
|
|
975
1002
|
uniform float u_pickMode;
|
|
976
1003
|
uniform float u_useVertexColor;
|
|
977
1004
|
uniform float u_useDirectionalShading;
|
|
@@ -980,11 +1007,47 @@ uniform vec3 u_cameraPosition;
|
|
|
980
1007
|
uniform sampler2D u_shadowMap;
|
|
981
1008
|
uniform mat4 u_lightViewProjection;
|
|
982
1009
|
uniform float u_shadowEnabled;
|
|
1010
|
+
uniform float u_shadowNormalOffset;
|
|
1011
|
+
uniform float u_shadowDebug;
|
|
1012
|
+
uniform sampler2D u_baseColorMap;
|
|
1013
|
+
uniform float u_useBaseColorMap;
|
|
1014
|
+
uniform vec2 u_uvScale;
|
|
1015
|
+
uniform sampler2D u_normalMap;
|
|
1016
|
+
uniform float u_useNormalMap;
|
|
1017
|
+
uniform sampler2D u_ormMap;
|
|
1018
|
+
uniform float u_useOrmMap;
|
|
1019
|
+
uniform float u_ormOcclusion;
|
|
1020
|
+
uniform float u_ormMetallicRoughness;
|
|
1021
|
+
uniform float u_normalScale;
|
|
1022
|
+
uniform float u_metallic;
|
|
1023
|
+
uniform float u_roughness;
|
|
1024
|
+
uniform vec3 u_emissive;
|
|
1025
|
+
uniform sampler2D u_sceneDepth;
|
|
1026
|
+
uniform float u_useSceneDepth;
|
|
1027
|
+
uniform mat4 u_projection;
|
|
1028
|
+
uniform vec2 u_viewport;
|
|
1029
|
+
uniform vec2 u_clipRange;
|
|
1030
|
+
uniform float u_aoRadius;
|
|
1031
|
+
uniform float u_aoStrength;
|
|
983
1032
|
in vec3 v_color;
|
|
984
1033
|
in vec3 v_worldPosition;
|
|
985
1034
|
out vec4 outputColor;
|
|
986
1035
|
|
|
1036
|
+
// \u7531\u5C4F\u5E55 UV \u4E0E\u6DF1\u5EA6\u7F13\u51B2\u91CD\u5EFA\u89C6\u7A7A\u95F4\u5750\u6807\uFF08\u4E0E WebGL2DepthMath \u7684\u6362\u7B97\u4E00\u81F4\uFF09\u3002
|
|
1037
|
+
vec3 viewPositionAt(vec2 uv, float tanHalfFov, float aspect, float near, float far) {
|
|
1038
|
+
float depth = texture(u_sceneDepth, uv).r;
|
|
1039
|
+
float ndcZ = depth * 2.0 - 1.0;
|
|
1040
|
+
float linear = (2.0 * near * far) / (far + near - ndcZ * (far - near));
|
|
1041
|
+
vec2 ndc = uv * 2.0 - 1.0;
|
|
1042
|
+
return vec3(ndc.x * tanHalfFov * aspect * linear, ndc.y * tanHalfFov * linear, -linear);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
987
1045
|
void main() {
|
|
1046
|
+
// Section plane: discard the half-space on the negative side. A zero plane
|
|
1047
|
+
// (0,0,0,0) evaluates to 0 and keeps everything, so it doubles as "off".
|
|
1048
|
+
if (dot(v_worldPosition, u_clipPlane.xyz) + u_clipPlane.w < 0.0) {
|
|
1049
|
+
discard;
|
|
1050
|
+
}
|
|
988
1051
|
if (u_pickMode > 0.5) {
|
|
989
1052
|
outputColor = u_color;
|
|
990
1053
|
return;
|
|
@@ -995,19 +1058,74 @@ void main() {
|
|
|
995
1058
|
dFdx(v_worldPosition),
|
|
996
1059
|
dFdy(v_worldPosition)
|
|
997
1060
|
));
|
|
1061
|
+
// Box-projected UVs make the tangent frame analytic: the projection plane's
|
|
1062
|
+
// two world axes are the tangents, so no vertex tangent channel is needed.
|
|
1063
|
+
vec3 tangent = vec3(0.0);
|
|
1064
|
+
vec3 bitangent = vec3(0.0);
|
|
1065
|
+
vec3 axisWeight = abs(normal);
|
|
1066
|
+
if (axisWeight.x >= axisWeight.y && axisWeight.x >= axisWeight.z) {
|
|
1067
|
+
tangent = vec3(0.0, 0.0, -sign(normal.x));
|
|
1068
|
+
bitangent = vec3(0.0, 1.0, 0.0);
|
|
1069
|
+
} else if (axisWeight.y >= axisWeight.z) {
|
|
1070
|
+
tangent = vec3(1.0, 0.0, 0.0);
|
|
1071
|
+
bitangent = vec3(0.0, 0.0, sign(normal.y));
|
|
1072
|
+
} else {
|
|
1073
|
+
tangent = vec3(sign(normal.z), 0.0, 0.0);
|
|
1074
|
+
bitangent = vec3(0.0, 1.0, 0.0);
|
|
1075
|
+
}
|
|
1076
|
+
// \u4E09\u5F20\u8D34\u56FE\u5171\u7528\u540C\u4E00\u5957\u76D2\u6295\u5F71 UV\uFF1A\u6CD5\u7EBF\u4E3B\u5BFC\u8F74\u51B3\u5B9A\u6295\u5F71\u5E73\u9762\uFF0C\u5355\u4F4D\u662F\u7C73\u3002
|
|
1077
|
+
// \u5199\u4E00\u6B21\u5C31\u591F\u4E86\u2014\u2014\u5E95\u56FE\u3001\u6CD5\u7EBF\u56FE\u3001ORM \u5404\u5199\u4E00\u904D\u8FDF\u65E9\u4F1A\u6F02\u3002
|
|
1078
|
+
vec2 surfaceUv;
|
|
1079
|
+
if (axisWeight.x >= axisWeight.y && axisWeight.x >= axisWeight.z) {
|
|
1080
|
+
surfaceUv = vec2(-sign(normal.x) * v_worldPosition.z, v_worldPosition.y);
|
|
1081
|
+
} else if (axisWeight.y >= axisWeight.z) {
|
|
1082
|
+
surfaceUv = vec2(v_worldPosition.x, sign(normal.y) * v_worldPosition.z);
|
|
1083
|
+
} else {
|
|
1084
|
+
surfaceUv = vec2(sign(normal.z) * v_worldPosition.x, v_worldPosition.y);
|
|
1085
|
+
}
|
|
1086
|
+
surfaceUv *= u_uvScale;
|
|
1087
|
+
if (u_useNormalMap > 0.5) {
|
|
1088
|
+
vec3 sampled = texture(u_normalMap, surfaceUv).xyz * 2.0 - 1.0;
|
|
1089
|
+
sampled.xy *= u_normalScale;
|
|
1090
|
+
normal = normalize(
|
|
1091
|
+
normalize(tangent) * sampled.x
|
|
1092
|
+
+ normalize(bitangent) * sampled.y
|
|
1093
|
+
+ normal * sampled.z
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
998
1096
|
// \u4E3B\u5149 + \u534A\u7403\u5929\u5149\u3002\u6CD5\u7EBF\u7531\u5C4F\u5E55\u7A7A\u95F4\u5BFC\u6570\u6C42\u51FA\uFF0C\u671D\u5411\u4E0E\u7ED5\u5E8F\u4E00\u81F4\uFF1A
|
|
999
1097
|
// \u8FCE\u7740\u5149\u7684\u4E00\u9762\u5403\u6EE1\u76F4\u5C04\uFF0C\u80CC\u5149\u9762\u53EA\u7559 35% \u7684\u53CD\u5F39\u5149\uFF0C\u6697\u90E8\u4E0D\u81F3\u4E8E\u7CCA\u6210\u4E00\u7247\u3002
|
|
1000
1098
|
vec3 lightDirection = normalize(vec3(${LIGHT_DIRECTION.join(", ")}));
|
|
1001
1099
|
float ndl = dot(normal, lightDirection);
|
|
1002
1100
|
float key = max(ndl, 0.0) + 0.35 * max(-ndl, 0.0);
|
|
1003
1101
|
float sky = 0.5 + 0.5 * normal.y;
|
|
1102
|
+
// ORM\uFF1AR \u906E\u853D\u3001G \u7C97\u7CD9\u5EA6\u3001B \u91D1\u5C5E\u5EA6\uFF0C\u548C glTF \u7684\u6253\u5305\u7EA6\u5B9A\u4E00\u81F4\u3002
|
|
1103
|
+
// \u906E\u853D\u53EA\u538B\u95F4\u63A5\u5149\uFF08\u73AF\u5883\u4E0E\u5929\u5149\uFF09\uFF0C\u76F4\u5C04\u7167\u5EA6\u4E0D\u53D7\u5F71\u54CD\u2014\u2014\u5426\u5219\u9634\u5F71\u548C\u8D34\u56FE\u4F1A\u4E92\u76F8\u6253\u67B6\u3002
|
|
1104
|
+
float surfaceOcclusion = 1.0;
|
|
1105
|
+
float surfaceMetallic = u_metallic;
|
|
1106
|
+
float surfaceRoughness = clamp(u_roughness, 0.04, 1.0);
|
|
1107
|
+
if (u_useOrmMap > 0.5) {
|
|
1108
|
+
vec3 orm = texture(u_ormMap, surfaceUv).rgb;
|
|
1109
|
+
if (u_ormOcclusion > 0.5) surfaceOcclusion = clamp(orm.r, 0.0, 1.0);
|
|
1110
|
+
if (u_ormMetallicRoughness > 0.5) {
|
|
1111
|
+
surfaceRoughness = clamp(surfaceRoughness * orm.g, 0.04, 1.0);
|
|
1112
|
+
surfaceMetallic = clamp(surfaceMetallic * orm.b, 0.0, 1.0);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1004
1115
|
// \u76F4\u5C04\u5149\u5F3A\u5EA6\u6309\u771F\u5B9E\u65E5\u7167\u7ED9\uFF1A\u671D\u4E0A\u7684\u9762\u4F1A\u8D85\u8FC7 1\uFF0C\u8FC7\u66DD\u5230\u63A5\u8FD1\u767D\u8272\u662F\u5BF9\u7684\uFF0C
|
|
1005
1116
|
// \u4E4B\u524D\u4E0A\u9650\u538B\u5230 1 \u9644\u8FD1\uFF0C\u6574\u4E2A\u5382\u533A\u5C31\u6C38\u8FDC\u6652\u4E0D\u5230\u592A\u9633\uFF0C\u770B\u7740\u53D1\u95F7\u53D1\u6697\u3002
|
|
1006
1117
|
float shade = clamp(
|
|
1007
|
-
${LIGHTING.ambient} + ${LIGHTING.
|
|
1118
|
+
(${LIGHTING.ambient} + ${LIGHTING.sky} * sky) * surfaceOcclusion
|
|
1119
|
+
+ ${LIGHTING.direct} * key,
|
|
1008
1120
|
${LIGHTING.minimum},
|
|
1009
1121
|
${LIGHTING.maximum}
|
|
1010
1122
|
);
|
|
1123
|
+
// Base-colour texture. There is no UV vertex channel: the projection is
|
|
1124
|
+
// derived from the surface normal's dominant axis, in metres, which matches
|
|
1125
|
+
// the coordinates Studio writes into exported glTF.
|
|
1126
|
+
if (u_useBaseColorMap > 0.5) {
|
|
1127
|
+
color *= texture(u_baseColorMap, surfaceUv).rgb;
|
|
1128
|
+
}
|
|
1011
1129
|
vec3 lit = color * mix(1.0, shade, u_useDirectionalShading);
|
|
1012
1130
|
|
|
1013
1131
|
// \u8868\u9762\u9897\u7C92\uFF1A\u6309\u4E16\u754C\u5750\u6807\u53D6\u4E24\u5C42\u54C8\u5E0C\uFF0C\u6253\u6563\u6574\u7247\u7EAF\u8272\u5E26\u6765\u7684\u5851\u6599\u611F\u3002
|
|
@@ -1052,20 +1170,108 @@ void main() {
|
|
|
1052
1170
|
+ vec3(0.42, 0.52, 0.54) * sparkle * 0.55;
|
|
1053
1171
|
}
|
|
1054
1172
|
|
|
1173
|
+
// \u5C4F\u5E55\u7A7A\u95F4\u906E\u853D\uFF1A\u5148\u628A\u6DF1\u5EA6\u8FD8\u539F\u6210\u89C6\u7A7A\u95F4\u5750\u6807\uFF0C\u518D\u6309**\u4E16\u754C\u534A\u5F84**\u53D6\u6837\u3002
|
|
1174
|
+
// \u76F4\u63A5\u6BD4\u8F83 NDC \u6DF1\u5EA6\u5DEE\u8868\u8FBE\u4E0D\u4E86"1 \u7C73"\u8FD9\u79CD\u91CF\uFF08\u8FDC\u7AEF\u5343\u5206\u4E4B\u51E0\u5C31\u8DE8\u4E86\u5341\u7C73\uFF09\uFF0C
|
|
1175
|
+
// \u8FD9\u4E5F\u662F SPEC-007 \u524D\u4E09\u8F6E\u5931\u8D25\u7684\u539F\u56E0\u3002
|
|
1176
|
+
if (u_useSceneDepth > 0.5) {
|
|
1177
|
+
vec2 screenUv = gl_FragCoord.xy / u_viewport;
|
|
1178
|
+
float tanHalfFov = 1.0 / u_projection[1][1];
|
|
1179
|
+
float aspect = u_projection[1][1] / u_projection[0][0];
|
|
1180
|
+
float near = u_clipRange.x;
|
|
1181
|
+
float far = u_clipRange.y;
|
|
1182
|
+
|
|
1183
|
+
vec3 centre = viewPositionAt(screenUv, tanHalfFov, aspect, near, far);
|
|
1184
|
+
// \u6CD5\u7EBF\u7531\u89C6\u7A7A\u95F4\u4F4D\u7F6E\u7684\u5C4F\u5E55\u5BFC\u6570\u6C42\u51FA\uFF1A\u51E0\u4F55\u6CA1\u6709\u6CD5\u7EBF\u5C5E\u6027\uFF0C\u4E5F\u4E0D\u9700\u8981\u3002
|
|
1185
|
+
vec3 viewNormal = normalize(cross(dFdx(centre), dFdy(centre)));
|
|
1186
|
+
float centreDepth = -centre.z;
|
|
1187
|
+
|
|
1188
|
+
// \u4E16\u754C\u534A\u5F84\u6362\u7B97\u6210\u50CF\u7D20\u534A\u5F84\uFF1A\u8D8A\u8FDC\u8D8A\u5C0F\uFF0C\u8FD1\u666F\u8FDC\u666F\u624D\u90FD\u6709\u610F\u4E49\u3002
|
|
1189
|
+
float pixelRadius = (u_aoRadius / max(1e-4, centreDepth * tanHalfFov)) * (u_viewport.y * 0.5);
|
|
1190
|
+
// \u4EA4\u9519\u68AF\u5EA6\u566A\u58F0\uFF08IGN\uFF09\u4EE3\u66FF sin \u54C8\u5E0C\uFF1A\u540C\u6837\u662F\u9010\u50CF\u7D20\u6270\u52A8\uFF0C\u4F46\u5B83\u5728\u76F8\u90BB\u50CF\u7D20\u95F4\u662F
|
|
1191
|
+
// \u6E10\u53D8\u800C\u975E\u8DF3\u53D8\uFF0C\u91C7\u6837\u7F3A\u53E3\u770B\u8D77\u6765\u50CF\u7EC6\u5BC6\u6296\u52A8\uFF0C\u4E0D\u50CF\u6912\u76D0\u566A\u70B9\u3002
|
|
1192
|
+
float rotation = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) * 6.2831853;
|
|
1193
|
+
|
|
1194
|
+
// \u4E09\u5708\u53D6\u6837\uFF1A0.35 / 0.65 / 1.0 \u500D\u534A\u5F84\uFF0C\u6743\u91CD 1.0 / 0.7 / 0.45\uFF0C\u5206\u522B\u8D1F\u8D23
|
|
1195
|
+
// \u8D34\u5730\u63A5\u89E6\u3001\u7F1D\u9699\u4E2D\u90E8\u3001\u5927\u9762\u79EF\u5939\u89D2\u3002\u6837\u672C\u6309\u4E0E\u6CD5\u7EBF\u7684\u5939\u89D2\u52A0\u6743\u2014\u2014\u6B63\u5BF9\u7740\u7684\u5E73\u9762
|
|
1196
|
+
// \u4E0D\u8BE5\u88AB\u5468\u56F4\u540C\u6837\u5E73\u7684\u4E1C\u897F\u538B\u6697\uFF0C\u53EA\u6709"\u7ACB\u8D77\u6765\u6321\u4F4F\u6211"\u7684\u624D\u7B97\u6CD5\u3002
|
|
1197
|
+
float occlusion = 0.0;
|
|
1198
|
+
float weightSum = 0.0;
|
|
1199
|
+
for (int ring = 0; ring < 3; ring++) {
|
|
1200
|
+
// \u4E09\u5708\uFF1A\u8D34\u5730\u63A5\u89E6 / \u7F1D\u9699\u4E2D\u90E8 / \u5927\u9762\u79EF\u5939\u89D2\u3002\u6CA1\u6709\u6A21\u7CCA\u901A\u9053\u65F6\uFF0C\u9760\u5708\u5C42\u5206\u644A
|
|
1201
|
+
// \u91C7\u6837\u5BC6\u5EA6\u662F\u538B\u566A\u70B9\u6700\u76F4\u63A5\u7684\u529E\u6CD5\uFF08\u6A21\u7CCA\u8981\u989D\u5916\u7684 FBO \u4E0E\u5168\u5C4F\u901A\u9053\uFF09\u3002
|
|
1202
|
+
float ringScale = ring == 0 ? 0.35 : (ring == 1 ? 0.65 : 1.0);
|
|
1203
|
+
float ringWeight = ring == 0 ? 1.0 : (ring == 1 ? 0.7 : 0.45);
|
|
1204
|
+
for (int i = 0; i < 8; i++) {
|
|
1205
|
+
float angle = rotation + float(i) * 0.7853982 + float(ring) * 0.2618;
|
|
1206
|
+
vec2 offset = vec2(cos(angle), sin(angle)) * pixelRadius * ringScale;
|
|
1207
|
+
vec2 sampleUv = screenUv + offset / u_viewport;
|
|
1208
|
+
if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
|
|
1209
|
+
vec3 delta = viewPositionAt(sampleUv, tanHalfFov, aspect, near, far) - centre;
|
|
1210
|
+
float distance = length(delta);
|
|
1211
|
+
if (distance < 0.0001 || distance > u_aoRadius) continue;
|
|
1212
|
+
float alignment = dot(delta, viewNormal) / distance;
|
|
1213
|
+
if (alignment <= 0.15) continue;
|
|
1214
|
+
occlusion += ringWeight * alignment;
|
|
1215
|
+
weightSum += ringWeight;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (weightSum > 0.0) {
|
|
1219
|
+
occlusion /= weightSum;
|
|
1220
|
+
lit *= 1.0 - clamp(occlusion, 0.0, 1.0) * u_aoStrength;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// \u91D1\u5C5E\u6CA1\u6709\u6F2B\u53CD\u5C04\uFF1A\u5B83\u7684\u989C\u8272\u5168\u90E8\u6765\u81EA\u53CD\u5C04\u3002\u5148\u628A\u6F2B\u53CD\u5C04\u6309 metallic \u6536\u6389\uFF0C
|
|
1225
|
+
// \u518D\u7528\u53CD\u5C04\u9879\u8865\u56DE\u6765\uFF0C\u5426\u5219\u91D1\u5C5E\u548C\u5851\u6599\u5728\u540C\u4E00\u4E2A\u592A\u9633\u4E0B\u53EA\u662F"\u989C\u8272\u4E0D\u540C"\u3002
|
|
1226
|
+
{
|
|
1227
|
+
vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
|
|
1228
|
+
vec3 halfVector = normalize(lightDirection + viewDirection);
|
|
1229
|
+
float metallic = clamp(surfaceMetallic, 0.0, 1.0);
|
|
1230
|
+
float roughness = clamp(surfaceRoughness, 0.04, 1.0);
|
|
1231
|
+
vec3 reflectance = mix(vec3(0.04), color, metallic);
|
|
1232
|
+
|
|
1233
|
+
float diffuseGain = 1.0 - metallic * 0.85;
|
|
1234
|
+
float facing = max(dot(normal, lightDirection), 0.0);
|
|
1235
|
+
|
|
1236
|
+
// \u9AD8\u5149\uFF1A\u7C97\u7CD9\u5EA6\u51B3\u5B9A\u53F6\u74E3\u5BBD\u5EA6\u4E0E\u5F3A\u5EA6\uFF08\u6307\u6570 96 \u2192 4\uFF0C\u589E\u76CA 1.0 \u2192 0.18\uFF09\u3002
|
|
1237
|
+
float shininess = mix(96.0, 4.0, roughness * roughness);
|
|
1238
|
+
float highlight = pow(max(dot(normal, halfVector), 0.0), shininess);
|
|
1239
|
+
float specularGain = mix(1.0, 0.18, roughness);
|
|
1240
|
+
|
|
1241
|
+
// \u73AF\u5883\u53CD\u5C04\uFF1A\u6309\u53CD\u5C04\u65B9\u5411\u53D6\u5929\u7A7A/\u5730\u9762\u8272\uFF0C\u63A0\u5C04\u89D2\u518D\u53E0\u4E00\u5C42 Fresnel\u3002
|
|
1242
|
+
// \u53EA\u770B\u6CD5\u7EBF\u7684\u8BDD\uFF0C\u5E73\u8EBA\u7684\u91D1\u5C5E\u4F1A\u548C\u7ACB\u7740\u7684\u91D1\u5C5E\u4E00\u4E2A\u989C\u8272\u2014\u2014\u73B0\u5B9E\u91CC\u524D\u8005\u53CD\u5C04\u5929\u9876\u3001
|
|
1243
|
+
// \u540E\u8005\u53CD\u5C04\u5730\u5E73\u7EBF\uFF0C\u6070\u6070\u662F\u91D1\u5C5E\u6700\u5BB9\u6613\u8FA8\u8BA4\u7684\u7279\u5F81\u3002
|
|
1244
|
+
vec3 reflected = reflect(-viewDirection, normal);
|
|
1245
|
+
float skyMix = clamp(reflected.y * 0.9 + 0.35, 0.0, 1.0);
|
|
1246
|
+
vec3 skyColor = mix(vec3(0.15, 0.16, 0.17), vec3(0.62, 0.72, 0.84), skyMix);
|
|
1247
|
+
float fresnel = pow(1.0 - clamp(dot(normal, viewDirection), 0.0, 1.0), 4.0);
|
|
1248
|
+
float envGain = (0.22 + 0.55 * fresnel) * mix(0.4, 1.0, metallic);
|
|
1249
|
+
|
|
1250
|
+
float litScale = diffuseGain;
|
|
1251
|
+
lit *= litScale;
|
|
1252
|
+
lit += reflectance * (highlight * specularGain * mix(0.35, 1.0, facing)
|
|
1253
|
+
+ skyColor * envGain);
|
|
1254
|
+
lit += u_emissive;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1055
1257
|
// \u9634\u5F71\uFF1A\u628A\u4E16\u754C\u5750\u6807\u6295\u5230\u5149\u6E90\u7A7A\u95F4\uFF0C\u4E0E\u6DF1\u5EA6\u56FE\u6BD4\u8F83\u3002\u53D6 3\xD73 \u91C7\u6837\u7684\u5E73\u5747\u505A\u8F7B\u5FAE\u67D4\u5316\u3002
|
|
1258
|
+
// shadowLit \u5728\u8C03\u8BD5\u901A\u9053\u91CC\u76F4\u63A5\u8F93\u51FA\uFF1A1 = \u53D7\u5149\uFF0C0 = \u88AB\u906E\u3002
|
|
1259
|
+
float shadowLit = 1.0;
|
|
1056
1260
|
if (u_shadowEnabled > 0.5) {
|
|
1057
|
-
|
|
1261
|
+
// \u91C7\u6837\u70B9\u6CBF\u6CD5\u7EBF\u63A8\u51FA 1.5 \u4E2A\u7EB9\u7D20\uFF1A\u5E73\u8EBA\u5728\u5149\u91CC\u7684\u9762\uFF08\u5C4B\u9876\u3001\u5730\u9762\uFF09\u5728\u6BCF\u4E2A\u7EB9\u7D20\u5185
|
|
1262
|
+
// \u672C\u8EAB\u5C31\u6709\u6DF1\u5EA6\u5DEE\uFF0C\u9760\u6DF1\u5EA6\u504F\u5DEE\u53BB\u8FFD\u8FD9\u4E2A\u5DEE\u53EA\u4F1A\u8BA9\u5F71\u5B50\u8131\u79BB\u7269\u4F53\uFF08peter-panning\uFF09\u3002
|
|
1263
|
+
// \u628A\u6BD4\u8F83\u70B9\u62AC\u79BB\u8868\u9762\u624D\u662F\u6B63\u89E3\uFF0C\u504F\u79FB\u91CF\u6309\u7EB9\u7D20\u6362\u7B97\uFF0C\u548C\u8D34\u56FE\u5206\u8FA8\u7387\u89E3\u8026\u3002
|
|
1264
|
+
vec3 shadowPosition = v_worldPosition + normal * u_shadowNormalOffset;
|
|
1265
|
+
vec4 lightSpace = u_lightViewProjection * vec4(shadowPosition, 1.0);
|
|
1058
1266
|
vec3 projected = lightSpace.xyz / max(lightSpace.w, 1e-5) * 0.5 + 0.5;
|
|
1059
1267
|
if (projected.x > 0.002 && projected.x < 0.998
|
|
1060
1268
|
&& projected.y > 0.002 && projected.y < 0.998
|
|
1061
1269
|
&& projected.z > 0.0 && projected.z < 1.0) {
|
|
1062
|
-
// \
|
|
1063
|
-
// \
|
|
1270
|
+
// \u89C6\u9525\u8D34\u5408\u573A\u666F\u540E\u6DF1\u5EA6\u8DE8\u5EA6\u53EA\u6709\u767E\u7C73\u91CF\u7EA7\uFF0C\u504F\u5DEE\u4E5F\u5C31\u53EF\u4EE5\u6309\u7EB9\u7406\u5355\u5143\u7ED9\u3002
|
|
1271
|
+
// \u9634\u5F71 pass \u53EA\u753B\u80CC\u9762\uFF08cull FRONT\uFF09\uFF0C\u53D7\u5149\u9762\u548C\u6DF1\u5EA6\u56FE\u5929\u7136\u9519\u5F00\uFF0C\u57FA\u6570\u53EF\u4EE5\u5F88\u5C0F\uFF1B
|
|
1272
|
+
// \u659C\u9762\u6700\u654F\u611F\uFF0C\u4ECD\u6309 N\xB7L \u653E\u5927\uFF08\u9634\u5F71\u75E4\u75AE\u4E0E\u9634\u5F71\u8131\u79BB\u4E4B\u95F4\u7684\u90A3\u6761\u7A84\u7F1D\uFF09\u3002
|
|
1064
1273
|
float ndlShadow = max(dot(normal, lightDirection), 0.0);
|
|
1065
|
-
|
|
1066
|
-
// 0.006 \u5BF9\u5E94\u7EA6 5.8 \u7C73\uFF0C\u6B63\u597D\u538B\u6389\u6DF1\u5EA6\u56FE\u4E0E\u9010\u50CF\u7D20\u6295\u5F71\u4E4B\u95F4\u7684\u7CFB\u7EDF\u8BEF\u5DEE\uFF0C
|
|
1067
|
-
// \u540C\u65F6\u8FD8\u80FD\u7559\u4F4F\u5341\u51E0\u7C73\u9AD8\u6784\u7B51\u7269\u7684\u6295\u5F71\u3002
|
|
1068
|
-
float bias = 0.0015 + 0.0025 * (1.0 - ndlShadow);
|
|
1274
|
+
float bias = 0.0006 + 0.0012 * (1.0 - ndlShadow);
|
|
1069
1275
|
float lit2 = 0.0;
|
|
1070
1276
|
vec2 texel = vec2(1.0 / 2048.0);
|
|
1071
1277
|
for (int dx = -1; dx <= 1; dx++) {
|
|
@@ -1074,10 +1280,21 @@ void main() {
|
|
|
1074
1280
|
lit2 += projected.z - bias > depth ? 0.0 : 1.0;
|
|
1075
1281
|
}
|
|
1076
1282
|
}
|
|
1077
|
-
|
|
1283
|
+
shadowLit = lit2 / 9.0;
|
|
1284
|
+
lit *= mix(0.68, 1.0, shadowLit);
|
|
1078
1285
|
|
|
1079
1286
|
}
|
|
1080
1287
|
}
|
|
1288
|
+
// \u8C03\u8BD5\u901A\u9053\uFF1A?shadowdebug=1 \u770B\u906E\u853D\u5224\u5B9A\uFF0C=2 \u770B\u6DF1\u5EA6\u56FE\u672C\u8EAB\u3002
|
|
1289
|
+
if (u_shadowDebug > 0.5) {
|
|
1290
|
+
if (u_shadowDebug < 1.5) {
|
|
1291
|
+
outputColor = vec4(shadowLit, 0.12, 0.12, 1.0);
|
|
1292
|
+
} else {
|
|
1293
|
+
float stored = texture(u_shadowMap, gl_FragCoord.xy / u_viewport).r;
|
|
1294
|
+
outputColor = vec4(vec3(stored), 1.0);
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1081
1298
|
// \u753B\u5E03\u6309 sRGB \u89E3\u91CA\u5199\u5165\u503C\uFF0C\u800C\u6750\u8D28\u662F\u7EBF\u6027\u7A7A\u95F4\u7684\u5206\u91CF\uFF0C\u5FC5\u987B\u505A\u4E00\u6B21\u7F16\u7801\u3002
|
|
1082
1299
|
// \u4E0D\u505A\u7684\u8BDD\u7EBF\u6027 0.017 \u4F1A\u5F53\u4F5C sRGB 0.017 \u663E\u793A\uFF0C\u6DF1\u8272\u6750\u8D28\u5168\u90E8\u584C\u6210\u9ED1\u8272\u3002
|
|
1083
1300
|
outputColor = vec4(
|
|
@@ -1103,8 +1320,31 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
1103
1320
|
shadowMapLocation;
|
|
1104
1321
|
lightMatrixLocation;
|
|
1105
1322
|
shadowEnabledLocation;
|
|
1323
|
+
shadowNormalOffsetLocation;
|
|
1324
|
+
shadowDebugLocation;
|
|
1106
1325
|
instancingLocation;
|
|
1107
1326
|
pickModeLocation;
|
|
1327
|
+
clipPlaneLocation;
|
|
1328
|
+
baseColorMapLocation;
|
|
1329
|
+
useBaseColorMapLocation;
|
|
1330
|
+
uvScaleLocation;
|
|
1331
|
+
normalMapLocation;
|
|
1332
|
+
ormMapLocation;
|
|
1333
|
+
useOrmMapLocation;
|
|
1334
|
+
ormOcclusionLocation;
|
|
1335
|
+
ormMetallicRoughnessLocation;
|
|
1336
|
+
useNormalMapLocation;
|
|
1337
|
+
normalScaleLocation;
|
|
1338
|
+
metallicLocation;
|
|
1339
|
+
roughnessLocation;
|
|
1340
|
+
emissiveLocation;
|
|
1341
|
+
sceneDepthLocation;
|
|
1342
|
+
useSceneDepthLocation;
|
|
1343
|
+
projectionLocation;
|
|
1344
|
+
viewportLocation;
|
|
1345
|
+
clipRangeLocation;
|
|
1346
|
+
aoRadiusLocation;
|
|
1347
|
+
aoStrengthLocation;
|
|
1108
1348
|
constructor(gl) {
|
|
1109
1349
|
this.gl = gl;
|
|
1110
1350
|
const program = this.createProgram(VERTEX_SOURCE, FRAGMENT_SOURCE);
|
|
@@ -1117,9 +1357,32 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
1117
1357
|
const cameraPositionLocation = gl.getUniformLocation(program, "u_cameraPosition");
|
|
1118
1358
|
const instancingLocation = gl.getUniformLocation(program, "u_useInstancing");
|
|
1119
1359
|
const pickModeLocation = gl.getUniformLocation(program, "u_pickMode");
|
|
1360
|
+
const clipPlaneLocation = gl.getUniformLocation(program, "u_clipPlane");
|
|
1361
|
+
const baseColorMapLocation = gl.getUniformLocation(program, "u_baseColorMap");
|
|
1362
|
+
const useBaseColorMapLocation = gl.getUniformLocation(program, "u_useBaseColorMap");
|
|
1363
|
+
const uvScaleLocation = gl.getUniformLocation(program, "u_uvScale");
|
|
1364
|
+
const normalMapLocation = gl.getUniformLocation(program, "u_normalMap");
|
|
1365
|
+
const ormMapLocation = gl.getUniformLocation(program, "u_ormMap");
|
|
1366
|
+
const useOrmMapLocation = gl.getUniformLocation(program, "u_useOrmMap");
|
|
1367
|
+
const ormOcclusionLocation = gl.getUniformLocation(program, "u_ormOcclusion");
|
|
1368
|
+
const ormMetallicRoughnessLocation = gl.getUniformLocation(program, "u_ormMetallicRoughness");
|
|
1369
|
+
const useNormalMapLocation = gl.getUniformLocation(program, "u_useNormalMap");
|
|
1370
|
+
const normalScaleLocation = gl.getUniformLocation(program, "u_normalScale");
|
|
1371
|
+
const metallicLocation = gl.getUniformLocation(program, "u_metallic");
|
|
1372
|
+
const roughnessLocation = gl.getUniformLocation(program, "u_roughness");
|
|
1373
|
+
const emissiveLocation = gl.getUniformLocation(program, "u_emissive");
|
|
1374
|
+
const sceneDepthLocation = gl.getUniformLocation(program, "u_sceneDepth");
|
|
1375
|
+
const useSceneDepthLocation = gl.getUniformLocation(program, "u_useSceneDepth");
|
|
1376
|
+
const projectionLocation = gl.getUniformLocation(program, "u_projection");
|
|
1377
|
+
const viewportLocation = gl.getUniformLocation(program, "u_viewport");
|
|
1378
|
+
const clipRangeLocation = gl.getUniformLocation(program, "u_clipRange");
|
|
1379
|
+
const aoRadiusLocation = gl.getUniformLocation(program, "u_aoRadius");
|
|
1380
|
+
const aoStrengthLocation = gl.getUniformLocation(program, "u_aoStrength");
|
|
1120
1381
|
const shadowMapLocation = gl.getUniformLocation(program, "u_shadowMap");
|
|
1121
1382
|
const lightMatrixLocation = gl.getUniformLocation(program, "u_lightViewProjection");
|
|
1122
1383
|
const shadowEnabledLocation = gl.getUniformLocation(program, "u_shadowEnabled");
|
|
1384
|
+
const shadowNormalOffsetLocation = gl.getUniformLocation(program, "u_shadowNormalOffset");
|
|
1385
|
+
const shadowDebugLocation = gl.getUniformLocation(program, "u_shadowDebug");
|
|
1123
1386
|
if (!matrixLocation || !colorLocation || !clipOffsetLocation || !vertexColorLocation || !directionalShadingLocation || !waterShadingLocation || !cameraPositionLocation || !instancingLocation || !pickModeLocation) {
|
|
1124
1387
|
gl.deleteProgram(program);
|
|
1125
1388
|
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", "ICE3_SHADER_LINK_FAILED: shader uniforms are missing");
|
|
@@ -1135,11 +1398,71 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
1135
1398
|
this.shadowMapLocation = shadowMapLocation;
|
|
1136
1399
|
this.lightMatrixLocation = lightMatrixLocation;
|
|
1137
1400
|
this.shadowEnabledLocation = shadowEnabledLocation;
|
|
1138
|
-
this.
|
|
1139
|
-
this.
|
|
1140
|
-
this.shadowEnabledLocation = shadowEnabledLocation;
|
|
1401
|
+
this.shadowNormalOffsetLocation = shadowNormalOffsetLocation;
|
|
1402
|
+
this.shadowDebugLocation = shadowDebugLocation;
|
|
1141
1403
|
this.instancingLocation = instancingLocation;
|
|
1142
1404
|
this.pickModeLocation = pickModeLocation;
|
|
1405
|
+
this.clipPlaneLocation = clipPlaneLocation;
|
|
1406
|
+
this.baseColorMapLocation = baseColorMapLocation;
|
|
1407
|
+
this.useBaseColorMapLocation = useBaseColorMapLocation;
|
|
1408
|
+
this.uvScaleLocation = uvScaleLocation;
|
|
1409
|
+
this.normalMapLocation = normalMapLocation;
|
|
1410
|
+
this.ormMapLocation = ormMapLocation;
|
|
1411
|
+
this.useOrmMapLocation = useOrmMapLocation;
|
|
1412
|
+
this.ormOcclusionLocation = ormOcclusionLocation;
|
|
1413
|
+
this.ormMetallicRoughnessLocation = ormMetallicRoughnessLocation;
|
|
1414
|
+
this.useNormalMapLocation = useNormalMapLocation;
|
|
1415
|
+
this.normalScaleLocation = normalScaleLocation;
|
|
1416
|
+
this.metallicLocation = metallicLocation;
|
|
1417
|
+
this.roughnessLocation = roughnessLocation;
|
|
1418
|
+
this.emissiveLocation = emissiveLocation;
|
|
1419
|
+
this.sceneDepthLocation = sceneDepthLocation;
|
|
1420
|
+
this.useSceneDepthLocation = useSceneDepthLocation;
|
|
1421
|
+
this.projectionLocation = projectionLocation;
|
|
1422
|
+
this.viewportLocation = viewportLocation;
|
|
1423
|
+
this.clipRangeLocation = clipRangeLocation;
|
|
1424
|
+
this.aoRadiusLocation = aoRadiusLocation;
|
|
1425
|
+
this.aoStrengthLocation = aoStrengthLocation;
|
|
1426
|
+
if (sceneDepthLocation) {
|
|
1427
|
+
gl.useProgram(program);
|
|
1428
|
+
gl.uniform1i(sceneDepthLocation, 2);
|
|
1429
|
+
}
|
|
1430
|
+
if (useSceneDepthLocation) {
|
|
1431
|
+
gl.useProgram(program);
|
|
1432
|
+
gl.uniform1f(useSceneDepthLocation, 0);
|
|
1433
|
+
}
|
|
1434
|
+
if (aoRadiusLocation) {
|
|
1435
|
+
gl.useProgram(program);
|
|
1436
|
+
gl.uniform1f(aoRadiusLocation, 2.5);
|
|
1437
|
+
}
|
|
1438
|
+
if (emissiveLocation) {
|
|
1439
|
+
gl.useProgram(program);
|
|
1440
|
+
gl.uniform3f(emissiveLocation, 0, 0, 0);
|
|
1441
|
+
}
|
|
1442
|
+
if (normalMapLocation) {
|
|
1443
|
+
gl.useProgram(program);
|
|
1444
|
+
gl.uniform1i(normalMapLocation, 1);
|
|
1445
|
+
}
|
|
1446
|
+
if (ormMapLocation) {
|
|
1447
|
+
gl.useProgram(program);
|
|
1448
|
+
gl.uniform1i(ormMapLocation, 4);
|
|
1449
|
+
}
|
|
1450
|
+
if (useNormalMapLocation) {
|
|
1451
|
+
gl.useProgram(program);
|
|
1452
|
+
gl.uniform1f(useNormalMapLocation, 0);
|
|
1453
|
+
}
|
|
1454
|
+
if (baseColorMapLocation) {
|
|
1455
|
+
gl.useProgram(program);
|
|
1456
|
+
gl.uniform1i(baseColorMapLocation, 0);
|
|
1457
|
+
}
|
|
1458
|
+
if (useBaseColorMapLocation) {
|
|
1459
|
+
gl.useProgram(program);
|
|
1460
|
+
gl.uniform1f(useBaseColorMapLocation, 0);
|
|
1461
|
+
}
|
|
1462
|
+
if (clipPlaneLocation) {
|
|
1463
|
+
gl.useProgram(program);
|
|
1464
|
+
gl.uniform4f(clipPlaneLocation, 0, 0, 0, 0);
|
|
1465
|
+
}
|
|
1143
1466
|
if (shadowMapLocation) {
|
|
1144
1467
|
gl.useProgram(program);
|
|
1145
1468
|
gl.uniform1i(shadowMapLocation, 3);
|
|
@@ -1154,14 +1477,120 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
1154
1477
|
setColor(color) {
|
|
1155
1478
|
this.gl.uniform4f(this.colorLocation, ...color);
|
|
1156
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Bind a base-colour texture to unit 0 and enable sampling. Passing `null`
|
|
1482
|
+
* restores flat shading without touching the bound texture.
|
|
1483
|
+
*/
|
|
1484
|
+
setBaseColorMap(texture, scale) {
|
|
1485
|
+
const gl = this.gl;
|
|
1486
|
+
if (!this.useBaseColorMapLocation)
|
|
1487
|
+
return;
|
|
1488
|
+
if (!texture) {
|
|
1489
|
+
gl.uniform1f(this.useBaseColorMapLocation, 0);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1493
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1494
|
+
gl.uniform1f(this.useBaseColorMapLocation, 1);
|
|
1495
|
+
if (this.uvScaleLocation) {
|
|
1496
|
+
gl.uniform2f(this.uvScaleLocation, scale[0], scale[1]);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/** Bind a tangent-space normal map to unit 1; `null` disables perturbation. */
|
|
1500
|
+
setNormalMap(texture, strength) {
|
|
1501
|
+
const gl = this.gl;
|
|
1502
|
+
if (!this.useNormalMapLocation)
|
|
1503
|
+
return;
|
|
1504
|
+
if (!texture) {
|
|
1505
|
+
gl.uniform1f(this.useNormalMapLocation, 0);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
1509
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1510
|
+
gl.uniform1f(this.useNormalMapLocation, 1);
|
|
1511
|
+
if (this.normalScaleLocation) {
|
|
1512
|
+
gl.uniform1f(this.normalScaleLocation, strength);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Bind the packed ORM map. `occlusion` / `metallicRoughness` describe which
|
|
1517
|
+
* halves of the image are meaningful; both false disables sampling.
|
|
1518
|
+
*/
|
|
1519
|
+
setOrmMap(texture, occlusion, metallicRoughness) {
|
|
1520
|
+
const gl = this.gl;
|
|
1521
|
+
if (!this.useOrmMapLocation)
|
|
1522
|
+
return;
|
|
1523
|
+
if (!texture || !occlusion && !metallicRoughness) {
|
|
1524
|
+
gl.uniform1f(this.useOrmMapLocation, 0);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
gl.activeTexture(gl.TEXTURE4);
|
|
1528
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1529
|
+
gl.uniform1f(this.useOrmMapLocation, 1);
|
|
1530
|
+
if (this.ormOcclusionLocation) {
|
|
1531
|
+
gl.uniform1f(this.ormOcclusionLocation, occlusion ? 1 : 0);
|
|
1532
|
+
}
|
|
1533
|
+
if (this.ormMetallicRoughnessLocation) {
|
|
1534
|
+
gl.uniform1f(this.ormMetallicRoughnessLocation, metallicRoughness ? 1 : 0);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
/** Metallic-roughness factors plus emission for the current draw. */
|
|
1538
|
+
setSurface(metallic, roughness, emissive) {
|
|
1539
|
+
const gl = this.gl;
|
|
1540
|
+
if (this.metallicLocation)
|
|
1541
|
+
gl.uniform1f(this.metallicLocation, metallic);
|
|
1542
|
+
if (this.roughnessLocation)
|
|
1543
|
+
gl.uniform1f(this.roughnessLocation, roughness);
|
|
1544
|
+
if (this.emissiveLocation) {
|
|
1545
|
+
if (emissive)
|
|
1546
|
+
gl.uniform3f(this.emissiveLocation, emissive[0], emissive[1], emissive[2]);
|
|
1547
|
+
else
|
|
1548
|
+
gl.uniform3f(this.emissiveLocation, 0, 0, 0);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/** 绑定相机深度纹理(unit 2)并传入重建视空间所需的相机参数。 */
|
|
1552
|
+
setSceneDepth(texture, projection, near, far, deviceWidth, deviceHeight) {
|
|
1553
|
+
const gl = this.gl;
|
|
1554
|
+
if (!this.useSceneDepthLocation)
|
|
1555
|
+
return;
|
|
1556
|
+
if (!texture || !projection) {
|
|
1557
|
+
gl.uniform1f(this.useSceneDepthLocation, 0);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
1561
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1562
|
+
gl.uniform1f(this.useSceneDepthLocation, 1);
|
|
1563
|
+
if (this.projectionLocation) {
|
|
1564
|
+
gl.uniformMatrix4fv(this.projectionLocation, false, projection.elements);
|
|
1565
|
+
}
|
|
1566
|
+
if (this.viewportLocation) {
|
|
1567
|
+
gl.uniform2f(this.viewportLocation, deviceWidth, deviceHeight);
|
|
1568
|
+
}
|
|
1569
|
+
if (this.clipRangeLocation) {
|
|
1570
|
+
gl.uniform2f(this.clipRangeLocation, near, far);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
/** 遮蔽半径(米)与强度,供运行时调节。强度 0 等于关闭。 */
|
|
1574
|
+
setOcclusion(radius, strength) {
|
|
1575
|
+
const gl = this.gl;
|
|
1576
|
+
if (this.aoRadiusLocation)
|
|
1577
|
+
gl.uniform1f(this.aoRadiusLocation, radius);
|
|
1578
|
+
if (this.aoStrengthLocation)
|
|
1579
|
+
gl.uniform1f(this.aoStrengthLocation, strength);
|
|
1580
|
+
}
|
|
1157
1581
|
setClipOffset(x, y) {
|
|
1158
1582
|
this.gl.uniform2f(this.clipOffsetLocation, x, y);
|
|
1159
1583
|
}
|
|
1584
|
+
setClipPlane(plane) {
|
|
1585
|
+
if (!this.clipPlaneLocation)
|
|
1586
|
+
return;
|
|
1587
|
+
this.gl.uniform4f(this.clipPlaneLocation, plane[0], plane[1], plane[2], plane[3]);
|
|
1588
|
+
}
|
|
1160
1589
|
setVertexColors(enabled) {
|
|
1161
1590
|
this.gl.uniform1f(this.vertexColorLocation, enabled ? 1 : 0);
|
|
1162
1591
|
}
|
|
1163
1592
|
/** 绑定阴影贴图与光源矩阵;关闭时着色器跳过采样。 */
|
|
1164
|
-
setShadow(enabled, texture, matrix, unit = 3) {
|
|
1593
|
+
setShadow(enabled, texture, matrix, normalOffset = 0, unit = 3) {
|
|
1165
1594
|
const gl = this.gl;
|
|
1166
1595
|
gl.uniform1f(this.shadowEnabledLocation, enabled && texture && matrix ? 1 : 0);
|
|
1167
1596
|
if (!enabled || !texture || !matrix)
|
|
@@ -1172,8 +1601,17 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
1172
1601
|
gl.uniform1i(this.shadowMapLocation, unit);
|
|
1173
1602
|
if (this.lightMatrixLocation)
|
|
1174
1603
|
gl.uniformMatrix4fv(this.lightMatrixLocation, false, matrix);
|
|
1604
|
+
if (this.shadowNormalOffsetLocation) {
|
|
1605
|
+
gl.uniform1f(this.shadowNormalOffsetLocation, normalOffset);
|
|
1606
|
+
}
|
|
1175
1607
|
gl.activeTexture(gl.TEXTURE0);
|
|
1176
1608
|
}
|
|
1609
|
+
/** 阴影调试通道:0 关闭,1 输出遮蔽判定,2 输出深度图。 */
|
|
1610
|
+
setShadowDebug(mode) {
|
|
1611
|
+
if (!this.shadowDebugLocation)
|
|
1612
|
+
return;
|
|
1613
|
+
this.gl.uniform1f(this.shadowDebugLocation, mode);
|
|
1614
|
+
}
|
|
1177
1615
|
setDirectionalShading(enabled) {
|
|
1178
1616
|
this.gl.uniform1f(this.directionalShadingLocation, enabled ? 1 : 0);
|
|
1179
1617
|
}
|
|
@@ -1275,6 +1713,14 @@ function collectInstanceBatches(renderables, output = []) {
|
|
|
1275
1713
|
}
|
|
1276
1714
|
return output;
|
|
1277
1715
|
}
|
|
1716
|
+
function renderablesOutsideInstanceBatches(renderables, batches) {
|
|
1717
|
+
const batched = /* @__PURE__ */ new Set();
|
|
1718
|
+
for (const batch of batches) {
|
|
1719
|
+
for (const id of batch.meshIds)
|
|
1720
|
+
batched.add(id);
|
|
1721
|
+
}
|
|
1722
|
+
return renderables.filter((renderable) => !batched.has(renderable.id));
|
|
1723
|
+
}
|
|
1278
1724
|
|
|
1279
1725
|
// packages/renderer/dist/WebGL2InstanceBatch.js
|
|
1280
1726
|
var WebGL2InstanceCache = class {
|
|
@@ -1319,10 +1765,10 @@ var WebGL2InstanceCache = class {
|
|
|
1319
1765
|
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
|
|
1320
1766
|
gl.bufferData(gl.ARRAY_BUFFER, batch.instanceMatrices, gl.DYNAMIC_DRAW);
|
|
1321
1767
|
for (let column = 0; column < 4; column += 1) {
|
|
1322
|
-
const
|
|
1323
|
-
gl.enableVertexAttribArray(
|
|
1324
|
-
gl.vertexAttribPointer(
|
|
1325
|
-
gl.vertexAttribDivisor(
|
|
1768
|
+
const location2 = WebGL2Program.INSTANCE_MATRIX_ATTRIBUTE + column;
|
|
1769
|
+
gl.enableVertexAttribArray(location2);
|
|
1770
|
+
gl.vertexAttribPointer(location2, 4, gl.FLOAT, false, 64, column * 16);
|
|
1771
|
+
gl.vertexAttribDivisor(location2, 1);
|
|
1326
1772
|
}
|
|
1327
1773
|
gl.bindBuffer(gl.ARRAY_BUFFER, semanticBuffer);
|
|
1328
1774
|
gl.bufferData(gl.ARRAY_BUFFER, batch.semanticIndices, gl.DYNAMIC_DRAW);
|
|
@@ -2018,14 +2464,136 @@ var WebGL2Picker = class {
|
|
|
2018
2464
|
}
|
|
2019
2465
|
};
|
|
2020
2466
|
|
|
2467
|
+
// packages/renderer/dist/WebGL2Shader.js
|
|
2468
|
+
function createShaderProgram(gl, vertexSource, fragmentSource, label) {
|
|
2469
|
+
const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource, `${label}:vertex`);
|
|
2470
|
+
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource, `${label}:fragment`);
|
|
2471
|
+
const program = gl.createProgram();
|
|
2472
|
+
if (!program) {
|
|
2473
|
+
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", `ICE3_SHADER_LINK_FAILED: ${label}: program allocation failed`);
|
|
2474
|
+
}
|
|
2475
|
+
gl.attachShader(program, vertex);
|
|
2476
|
+
gl.attachShader(program, fragment);
|
|
2477
|
+
gl.linkProgram(program);
|
|
2478
|
+
gl.deleteShader(vertex);
|
|
2479
|
+
gl.deleteShader(fragment);
|
|
2480
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
2481
|
+
const log = gl.getProgramInfoLog(program) ?? "unknown link error";
|
|
2482
|
+
gl.deleteProgram(program);
|
|
2483
|
+
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", `ICE3_SHADER_LINK_FAILED: ${label}: ${log}`);
|
|
2484
|
+
}
|
|
2485
|
+
return program;
|
|
2486
|
+
}
|
|
2487
|
+
function compileShader(gl, type, source, label) {
|
|
2488
|
+
const shader = gl.createShader(type);
|
|
2489
|
+
if (!shader) {
|
|
2490
|
+
throw new ICEEngineError("ICE3_SHADER_COMPILE_FAILED", `ICE3_SHADER_COMPILE_FAILED: ${label}: allocation failed`);
|
|
2491
|
+
}
|
|
2492
|
+
gl.shaderSource(shader, source);
|
|
2493
|
+
gl.compileShader(shader);
|
|
2494
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
2495
|
+
const log = gl.getShaderInfoLog(shader) ?? "unknown compile error";
|
|
2496
|
+
gl.deleteShader(shader);
|
|
2497
|
+
throw new ICEEngineError("ICE3_SHADER_COMPILE_FAILED", `ICE3_SHADER_COMPILE_FAILED: ${label}: ${log}`);
|
|
2498
|
+
}
|
|
2499
|
+
return shader;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// packages/renderer/dist/ShadowFrustum.js
|
|
2503
|
+
var SHADOW_LIGHT_DIRECTION = [-0.35, 0.85, 0.4];
|
|
2504
|
+
var BOUNDS_PADDING = 0.08;
|
|
2505
|
+
function shadowFrustum(bounds, mapSize = 2048) {
|
|
2506
|
+
const box = bounds ?? { min: [-50, -5, -50], max: [650, 60, 650] };
|
|
2507
|
+
const centre = [
|
|
2508
|
+
(box.min[0] + box.max[0]) / 2,
|
|
2509
|
+
(box.min[1] + box.max[1]) / 2,
|
|
2510
|
+
(box.min[2] + box.max[2]) / 2
|
|
2511
|
+
];
|
|
2512
|
+
const radius = Math.max(1, 0.5 * Math.hypot(box.max[0] - box.min[0], box.max[1] - box.min[1], box.max[2] - box.min[2]));
|
|
2513
|
+
const pad = radius * BOUNDS_PADDING;
|
|
2514
|
+
const direction = normalize2(SHADOW_LIGHT_DIRECTION);
|
|
2515
|
+
const distance2 = radius * 2 + pad * 2;
|
|
2516
|
+
const eye = [
|
|
2517
|
+
centre[0] + direction[0] * distance2,
|
|
2518
|
+
centre[1] + direction[1] * distance2,
|
|
2519
|
+
centre[2] + direction[2] * distance2
|
|
2520
|
+
];
|
|
2521
|
+
const view = new ICEMatrix4().lookAt(new ICEVector3(eye[0], eye[1], eye[2]), new ICEVector3(centre[0], centre[1], centre[2]), new ICEVector3(0, 1, 0));
|
|
2522
|
+
let minX = Infinity;
|
|
2523
|
+
let maxX = -Infinity;
|
|
2524
|
+
let minY = Infinity;
|
|
2525
|
+
let maxY = -Infinity;
|
|
2526
|
+
let minZ = Infinity;
|
|
2527
|
+
let maxZ = -Infinity;
|
|
2528
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
2529
|
+
const point = new ICEVector3(corner & 1 ? box.max[0] : box.min[0], corner & 2 ? box.max[1] : box.min[1], corner & 4 ? box.max[2] : box.min[2]);
|
|
2530
|
+
const projected = view.transformPoint(point);
|
|
2531
|
+
minX = Math.min(minX, projected[0]);
|
|
2532
|
+
maxX = Math.max(maxX, projected[0]);
|
|
2533
|
+
minY = Math.min(minY, projected[1]);
|
|
2534
|
+
maxY = Math.max(maxY, projected[1]);
|
|
2535
|
+
minZ = Math.min(minZ, projected[2]);
|
|
2536
|
+
maxZ = Math.max(maxZ, projected[2]);
|
|
2537
|
+
}
|
|
2538
|
+
const projection = orthographic(minX - pad, maxX + pad, minY - pad, maxY + pad, -maxZ - pad, -minZ + pad);
|
|
2539
|
+
const width = maxX - minX + pad * 2;
|
|
2540
|
+
const height = maxY - minY + pad * 2;
|
|
2541
|
+
return {
|
|
2542
|
+
matrix: multiply(projection, view.elements),
|
|
2543
|
+
texelWorld: Math.max(width, height) / mapSize
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function normalize2(value) {
|
|
2547
|
+
const length = Math.hypot(value[0], value[1], value[2]) || 1;
|
|
2548
|
+
return [value[0] / length, value[1] / length, value[2] / length];
|
|
2549
|
+
}
|
|
2550
|
+
function orthographic(left, right, bottom, top, near, far) {
|
|
2551
|
+
const lr = 1 / (left - right);
|
|
2552
|
+
const bt = 1 / (bottom - top);
|
|
2553
|
+
const nf = 1 / (near - far);
|
|
2554
|
+
return [
|
|
2555
|
+
-2 * lr,
|
|
2556
|
+
0,
|
|
2557
|
+
0,
|
|
2558
|
+
0,
|
|
2559
|
+
0,
|
|
2560
|
+
-2 * bt,
|
|
2561
|
+
0,
|
|
2562
|
+
0,
|
|
2563
|
+
0,
|
|
2564
|
+
0,
|
|
2565
|
+
2 * nf,
|
|
2566
|
+
0,
|
|
2567
|
+
(left + right) * lr,
|
|
2568
|
+
(top + bottom) * bt,
|
|
2569
|
+
(far + near) * nf,
|
|
2570
|
+
1
|
|
2571
|
+
];
|
|
2572
|
+
}
|
|
2573
|
+
function multiply(a, b) {
|
|
2574
|
+
const out = new Float32Array(16);
|
|
2575
|
+
for (let column = 0; column < 4; column += 1) {
|
|
2576
|
+
for (let row = 0; row < 4; row += 1) {
|
|
2577
|
+
let sum = 0;
|
|
2578
|
+
for (let k = 0; k < 4; k += 1) {
|
|
2579
|
+
sum += (a[k * 4 + row] ?? 0) * (b[column * 4 + k] ?? 0);
|
|
2580
|
+
}
|
|
2581
|
+
out[column * 4 + row] = sum;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
return out;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2021
2587
|
// packages/renderer/dist/WebGL2ShadowMap.js
|
|
2022
2588
|
var VERTEX_SOURCE3 = `#version 300 es
|
|
2023
2589
|
layout(location = 0) in vec3 a_position;
|
|
2024
2590
|
layout(location = 3) in mat4 a_instanceMatrix;
|
|
2025
|
-
uniform mat4
|
|
2591
|
+
uniform mat4 u_matrix;
|
|
2592
|
+
uniform float u_useInstancing;
|
|
2026
2593
|
void main() {
|
|
2027
|
-
vec4
|
|
2028
|
-
|
|
2594
|
+
vec4 local = vec4(a_position, 1.0);
|
|
2595
|
+
vec4 world = mix(local, a_instanceMatrix * local, u_useInstancing);
|
|
2596
|
+
gl_Position = u_matrix * world;
|
|
2029
2597
|
}
|
|
2030
2598
|
`;
|
|
2031
2599
|
var FRAGMENT_SOURCE3 = `#version 300 es
|
|
@@ -2034,18 +2602,18 @@ void main() {
|
|
|
2034
2602
|
// \u53EA\u5199\u6DF1\u5EA6\uFF0C\u989C\u8272\u4E0D\u9700\u8981\u3002
|
|
2035
2603
|
}
|
|
2036
2604
|
`;
|
|
2037
|
-
var LIGHT_DIRECTION2 = [-0.35, 0.85, 0.4];
|
|
2038
2605
|
var SHADOW_SIZE = 2048;
|
|
2039
|
-
var ORTHO_EXTENT = 430;
|
|
2040
|
-
var ORTHO_NEAR = -260;
|
|
2041
|
-
var ORTHO_FAR = 700;
|
|
2042
2606
|
var WebGL2ShadowMap = class {
|
|
2043
2607
|
gl;
|
|
2608
|
+
/** 最近一次着色器编译/链接失败的原因,供诊断读取。 */
|
|
2609
|
+
lastError = null;
|
|
2044
2610
|
framebuffer = null;
|
|
2045
2611
|
depthTexture = null;
|
|
2046
2612
|
program = null;
|
|
2047
|
-
|
|
2613
|
+
matrixLocation = null;
|
|
2614
|
+
instancingLocation = null;
|
|
2048
2615
|
matrixScratch = new Float32Array(16);
|
|
2616
|
+
texelWorldValue = 1;
|
|
2049
2617
|
constructor(gl) {
|
|
2050
2618
|
this.gl = gl;
|
|
2051
2619
|
this.createResources();
|
|
@@ -2057,6 +2625,19 @@ var WebGL2ShadowMap = class {
|
|
|
2057
2625
|
get matrix() {
|
|
2058
2626
|
return this.matrixScratch;
|
|
2059
2627
|
}
|
|
2628
|
+
/** 一个深度纹素在世界空间的边长(米)。法线偏移按它换算,别用魔数。 */
|
|
2629
|
+
get texelWorld() {
|
|
2630
|
+
return this.texelWorldValue;
|
|
2631
|
+
}
|
|
2632
|
+
/**
|
|
2633
|
+
* 按场景包围盒拟合视锥。每帧调用没有意义——场景内容不变时矩阵也不会变,
|
|
2634
|
+
* 所以由调用方决定何时重算(包围盒变化或首帧)。
|
|
2635
|
+
*/
|
|
2636
|
+
setBounds(bounds) {
|
|
2637
|
+
const frustum = shadowFrustum(bounds, SHADOW_SIZE);
|
|
2638
|
+
this.matrixScratch.set(frustum.matrix);
|
|
2639
|
+
this.texelWorldValue = frustum.texelWorld;
|
|
2640
|
+
}
|
|
2060
2641
|
/** 绑定阴影帧缓冲,返回是否成功。 */
|
|
2061
2642
|
begin() {
|
|
2062
2643
|
const gl = this.gl;
|
|
@@ -2067,12 +2648,24 @@ var WebGL2ShadowMap = class {
|
|
|
2067
2648
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
2068
2649
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
2069
2650
|
gl.enable(gl.DEPTH_TEST);
|
|
2070
|
-
gl.
|
|
2071
|
-
gl.cullFace(gl.FRONT);
|
|
2651
|
+
gl.disable(gl.CULL_FACE);
|
|
2072
2652
|
gl.useProgram(this.program);
|
|
2073
|
-
gl.uniformMatrix4fv(this.
|
|
2653
|
+
gl.uniformMatrix4fv(this.matrixLocation, false, this.matrixScratch);
|
|
2654
|
+
gl.uniform1f(this.instancingLocation, 1);
|
|
2074
2655
|
return true;
|
|
2075
2656
|
}
|
|
2657
|
+
/**
|
|
2658
|
+
* 非实例化绘制:矩阵换成 `lightViewProjection × world`,顶点位置直接当世界坐标用。
|
|
2659
|
+
*
|
|
2660
|
+
* 阴影通道原先只会画实例化批次,瓦片(渲染批次)与落单网格因此完全不投影。
|
|
2661
|
+
* 补这两条路径靠的就是这个入口——和 `WebGL2SceneDepth` 同一套写法。
|
|
2662
|
+
*/
|
|
2663
|
+
setMatrix(matrix) {
|
|
2664
|
+
this.gl.uniformMatrix4fv(this.matrixLocation, false, matrix);
|
|
2665
|
+
}
|
|
2666
|
+
setInstancing(enabled) {
|
|
2667
|
+
this.gl.uniform1f(this.instancingLocation, enabled ? 1 : 0);
|
|
2668
|
+
}
|
|
2076
2669
|
/** 解绑,恢复默认帧缓冲。 */
|
|
2077
2670
|
end(width, height) {
|
|
2078
2671
|
const gl = this.gl;
|
|
@@ -2109,130 +2702,283 @@ var WebGL2ShadowMap = class {
|
|
|
2109
2702
|
gl.readBuffer(gl.NONE);
|
|
2110
2703
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2111
2704
|
this.program = this.createProgram(VERTEX_SOURCE3, FRAGMENT_SOURCE3);
|
|
2112
|
-
this.
|
|
2113
|
-
this.
|
|
2114
|
-
|
|
2115
|
-
/** 正交光源矩阵:覆盖整片场地,与主着色器的光方向一致。 */
|
|
2116
|
-
updateLightMatrix() {
|
|
2117
|
-
const direction = normalize2(LIGHT_DIRECTION2);
|
|
2118
|
-
const target = [300, 0, 0];
|
|
2119
|
-
const eye = [
|
|
2120
|
-
target[0] + direction[0] * 400,
|
|
2121
|
-
target[1] + direction[1] * 400,
|
|
2122
|
-
target[2] + direction[2] * 400
|
|
2123
|
-
];
|
|
2124
|
-
const view = lookAt(eye, target, [0, 1, 0]);
|
|
2125
|
-
const projection = orthographic(-ORTHO_EXTENT, ORTHO_EXTENT, -ORTHO_EXTENT, ORTHO_EXTENT, ORTHO_NEAR, ORTHO_FAR);
|
|
2126
|
-
this.matrixScratch.set(multiply(projection, view));
|
|
2705
|
+
this.matrixLocation = this.program ? gl.getUniformLocation(this.program, "u_matrix") : null;
|
|
2706
|
+
this.instancingLocation = this.program ? gl.getUniformLocation(this.program, "u_useInstancing") : null;
|
|
2707
|
+
this.setBounds(null);
|
|
2127
2708
|
}
|
|
2128
2709
|
createProgram(vertex, fragment) {
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
return null;
|
|
2134
|
-
const program = gl.createProgram();
|
|
2135
|
-
gl.attachShader(program, vertexShader);
|
|
2136
|
-
gl.attachShader(program, fragmentShader);
|
|
2137
|
-
gl.linkProgram(program);
|
|
2138
|
-
gl.deleteShader(vertexShader);
|
|
2139
|
-
gl.deleteShader(fragmentShader);
|
|
2140
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
2141
|
-
gl.deleteProgram(program);
|
|
2710
|
+
try {
|
|
2711
|
+
return createShaderProgram(this.gl, vertex, fragment, "shadow");
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
2142
2714
|
return null;
|
|
2143
2715
|
}
|
|
2144
|
-
return program;
|
|
2145
2716
|
}
|
|
2146
|
-
|
|
2717
|
+
};
|
|
2718
|
+
|
|
2719
|
+
// packages/renderer/dist/SceneBounds.js
|
|
2720
|
+
var localBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
2721
|
+
function geometryLocalBounds(geometry) {
|
|
2722
|
+
const cached = localBoundsCache.get(geometry);
|
|
2723
|
+
if (cached)
|
|
2724
|
+
return cached;
|
|
2725
|
+
const positions = geometry.positions;
|
|
2726
|
+
if (!positions || positions.length < 3)
|
|
2727
|
+
return null;
|
|
2728
|
+
let minX = Infinity;
|
|
2729
|
+
let minY = Infinity;
|
|
2730
|
+
let minZ = Infinity;
|
|
2731
|
+
let maxX = -Infinity;
|
|
2732
|
+
let maxY = -Infinity;
|
|
2733
|
+
let maxZ = -Infinity;
|
|
2734
|
+
for (let index = 0; index + 2 < positions.length; index += 3) {
|
|
2735
|
+
const x = positions[index];
|
|
2736
|
+
const y = positions[index + 1];
|
|
2737
|
+
const z = positions[index + 2];
|
|
2738
|
+
if (x < minX)
|
|
2739
|
+
minX = x;
|
|
2740
|
+
if (y < minY)
|
|
2741
|
+
minY = y;
|
|
2742
|
+
if (z < minZ)
|
|
2743
|
+
minZ = z;
|
|
2744
|
+
if (x > maxX)
|
|
2745
|
+
maxX = x;
|
|
2746
|
+
if (y > maxY)
|
|
2747
|
+
maxY = y;
|
|
2748
|
+
if (z > maxZ)
|
|
2749
|
+
maxZ = z;
|
|
2750
|
+
}
|
|
2751
|
+
const bounds = { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] };
|
|
2752
|
+
localBoundsCache.set(geometry, bounds);
|
|
2753
|
+
return bounds;
|
|
2754
|
+
}
|
|
2755
|
+
function accumulateSceneBounds(renderables) {
|
|
2756
|
+
let minX = Infinity;
|
|
2757
|
+
let minY = Infinity;
|
|
2758
|
+
let minZ = Infinity;
|
|
2759
|
+
let maxX = -Infinity;
|
|
2760
|
+
let maxY = -Infinity;
|
|
2761
|
+
let maxZ = -Infinity;
|
|
2762
|
+
let seen = false;
|
|
2763
|
+
for (const renderable of renderables) {
|
|
2764
|
+
const local = geometryLocalBounds(renderable.mesh.geometry);
|
|
2765
|
+
if (!local)
|
|
2766
|
+
continue;
|
|
2767
|
+
const matrix = renderable.worldMatrix;
|
|
2768
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
2769
|
+
const x = corner & 1 ? local.max[0] : local.min[0];
|
|
2770
|
+
const y = corner & 2 ? local.max[1] : local.min[1];
|
|
2771
|
+
const z = corner & 4 ? local.max[2] : local.min[2];
|
|
2772
|
+
const worldX = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
|
|
2773
|
+
const worldY = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
|
|
2774
|
+
const worldZ = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
|
|
2775
|
+
if (worldX < minX)
|
|
2776
|
+
minX = worldX;
|
|
2777
|
+
if (worldY < minY)
|
|
2778
|
+
minY = worldY;
|
|
2779
|
+
if (worldZ < minZ)
|
|
2780
|
+
minZ = worldZ;
|
|
2781
|
+
if (worldX > maxX)
|
|
2782
|
+
maxX = worldX;
|
|
2783
|
+
if (worldY > maxY)
|
|
2784
|
+
maxY = worldY;
|
|
2785
|
+
if (worldZ > maxZ)
|
|
2786
|
+
maxZ = worldZ;
|
|
2787
|
+
seen = true;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return seen ? { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] } : null;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// packages/renderer/dist/WebGL2SceneDepth.js
|
|
2794
|
+
var VERTEX_SOURCE4 = `#version 300 es
|
|
2795
|
+
layout(location = 0) in vec3 a_position;
|
|
2796
|
+
layout(location = 3) in mat4 a_instanceMatrix;
|
|
2797
|
+
uniform mat4 u_matrix;
|
|
2798
|
+
uniform float u_useInstancing;
|
|
2799
|
+
void main() {
|
|
2800
|
+
vec4 local = vec4(a_position, 1.0);
|
|
2801
|
+
vec4 world = mix(local, a_instanceMatrix * local, u_useInstancing);
|
|
2802
|
+
gl_Position = u_matrix * world;
|
|
2803
|
+
}
|
|
2804
|
+
`;
|
|
2805
|
+
var FRAGMENT_SOURCE4 = `#version 300 es
|
|
2806
|
+
precision highp float;
|
|
2807
|
+
void main() {}
|
|
2808
|
+
`;
|
|
2809
|
+
var WebGL2SceneDepth = class {
|
|
2810
|
+
gl;
|
|
2811
|
+
framebuffer;
|
|
2812
|
+
depthTexture = null;
|
|
2813
|
+
program;
|
|
2814
|
+
matrixLocation;
|
|
2815
|
+
instancingLocation;
|
|
2816
|
+
width = 0;
|
|
2817
|
+
height = 0;
|
|
2818
|
+
constructor(gl) {
|
|
2819
|
+
this.gl = gl;
|
|
2820
|
+
this.program = createShaderProgram(gl, VERTEX_SOURCE4, FRAGMENT_SOURCE4, "scene-depth");
|
|
2821
|
+
this.matrixLocation = gl.getUniformLocation(this.program, "u_matrix");
|
|
2822
|
+
this.instancingLocation = gl.getUniformLocation(this.program, "u_useInstancing");
|
|
2823
|
+
const framebuffer = gl.createFramebuffer();
|
|
2824
|
+
if (!framebuffer)
|
|
2825
|
+
throw new Error("ICE3_FRAMEBUFFER_FAILED: scene depth");
|
|
2826
|
+
this.framebuffer = framebuffer;
|
|
2827
|
+
}
|
|
2828
|
+
get texture() {
|
|
2829
|
+
return this.depthTexture;
|
|
2830
|
+
}
|
|
2831
|
+
begin(viewProjection, width, height) {
|
|
2147
2832
|
const gl = this.gl;
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
gl.
|
|
2152
|
-
gl.
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2833
|
+
this.ensureTexture(Math.max(1, width), Math.max(1, height));
|
|
2834
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2835
|
+
gl.viewport(0, 0, this.width, this.height);
|
|
2836
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
2837
|
+
gl.disable(gl.BLEND);
|
|
2838
|
+
gl.depthMask(true);
|
|
2839
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
2840
|
+
gl.enable(gl.DEPTH_TEST);
|
|
2841
|
+
gl.useProgram(this.program);
|
|
2842
|
+
gl.uniformMatrix4fv(this.matrixLocation, false, viewProjection);
|
|
2843
|
+
gl.uniform1f(this.instancingLocation, 0);
|
|
2844
|
+
}
|
|
2845
|
+
setMatrix(matrix) {
|
|
2846
|
+
this.gl.uniformMatrix4fv(this.matrixLocation, false, matrix);
|
|
2847
|
+
}
|
|
2848
|
+
setInstancing(enabled) {
|
|
2849
|
+
this.gl.uniform1f(this.instancingLocation, enabled ? 1 : 0);
|
|
2850
|
+
}
|
|
2851
|
+
end(width, height) {
|
|
2852
|
+
const gl = this.gl;
|
|
2853
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2854
|
+
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2855
|
+
gl.viewport(0, 0, width, height);
|
|
2856
|
+
}
|
|
2857
|
+
dispose() {
|
|
2858
|
+
const gl = this.gl;
|
|
2859
|
+
if (this.depthTexture)
|
|
2860
|
+
gl.deleteTexture(this.depthTexture);
|
|
2861
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
2862
|
+
gl.deleteProgram(this.program);
|
|
2863
|
+
this.depthTexture = null;
|
|
2864
|
+
}
|
|
2865
|
+
ensureTexture(width, height) {
|
|
2866
|
+
const gl = this.gl;
|
|
2867
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2868
|
+
if (this.depthTexture && this.width === width && this.height === height) {
|
|
2869
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.depthTexture, 0);
|
|
2870
|
+
return;
|
|
2156
2871
|
}
|
|
2157
|
-
|
|
2872
|
+
if (this.depthTexture)
|
|
2873
|
+
gl.deleteTexture(this.depthTexture);
|
|
2874
|
+
this.width = width;
|
|
2875
|
+
this.height = height;
|
|
2876
|
+
const texture = gl.createTexture();
|
|
2877
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
2878
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
|
|
2879
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
2880
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
2881
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2882
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2883
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, texture, 0);
|
|
2884
|
+
gl.drawBuffers([gl.NONE]);
|
|
2885
|
+
gl.readBuffer(gl.NONE);
|
|
2886
|
+
this.depthTexture = texture;
|
|
2158
2887
|
}
|
|
2159
2888
|
};
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
return
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
-dot2(right, eye),
|
|
2195
|
-
-dot2(cameraUp, eye),
|
|
2196
|
-
-dot2(forward, eye),
|
|
2197
|
-
1
|
|
2198
|
-
];
|
|
2199
|
-
}
|
|
2200
|
-
function orthographic(left, right, bottom, top, near, far) {
|
|
2201
|
-
const lr = 1 / (left - right);
|
|
2202
|
-
const bt = 1 / (bottom - top);
|
|
2203
|
-
const nf = 1 / (near - far);
|
|
2204
|
-
return [
|
|
2205
|
-
-2 * lr,
|
|
2206
|
-
0,
|
|
2207
|
-
0,
|
|
2208
|
-
0,
|
|
2209
|
-
0,
|
|
2210
|
-
-2 * bt,
|
|
2211
|
-
0,
|
|
2212
|
-
0,
|
|
2213
|
-
0,
|
|
2214
|
-
0,
|
|
2215
|
-
2 * nf,
|
|
2216
|
-
0,
|
|
2217
|
-
(left + right) * lr,
|
|
2218
|
-
(top + bottom) * bt,
|
|
2219
|
-
(far + near) * nf,
|
|
2220
|
-
1
|
|
2221
|
-
];
|
|
2222
|
-
}
|
|
2223
|
-
function multiply(a, b) {
|
|
2224
|
-
const out = new Array(16).fill(0);
|
|
2225
|
-
for (let column = 0; column < 4; column += 1) {
|
|
2226
|
-
for (let row = 0; row < 4; row += 1) {
|
|
2227
|
-
let sum = 0;
|
|
2228
|
-
for (let k = 0; k < 4; k += 1) {
|
|
2229
|
-
sum += (a[k * 4 + row] ?? 0) * (b[column * 4 + k] ?? 0);
|
|
2889
|
+
|
|
2890
|
+
// packages/renderer/dist/WebGL2TextureCache.js
|
|
2891
|
+
var REPEAT = 10497;
|
|
2892
|
+
var fetchImageLoader = async (url) => {
|
|
2893
|
+
const response = await fetch(url);
|
|
2894
|
+
if (!response.ok) {
|
|
2895
|
+
throw new Error(`ICE3_TEXTURE_FETCH_FAILED: ${response.status} ${url}`);
|
|
2896
|
+
}
|
|
2897
|
+
return createImageBitmap(await response.blob());
|
|
2898
|
+
};
|
|
2899
|
+
var WebGL2TextureCache = class {
|
|
2900
|
+
gl;
|
|
2901
|
+
load;
|
|
2902
|
+
onUpload;
|
|
2903
|
+
entries = /* @__PURE__ */ new Map();
|
|
2904
|
+
disposed = false;
|
|
2905
|
+
constructor(gl, load = fetchImageLoader, onUpload) {
|
|
2906
|
+
this.gl = gl;
|
|
2907
|
+
this.load = load;
|
|
2908
|
+
this.onUpload = onUpload;
|
|
2909
|
+
}
|
|
2910
|
+
/** Texture for `url`, or `null` while it is still loading (or after failure). */
|
|
2911
|
+
texture(url) {
|
|
2912
|
+
if (this.disposed)
|
|
2913
|
+
return null;
|
|
2914
|
+
const existing = this.entries.get(url);
|
|
2915
|
+
if (existing) {
|
|
2916
|
+
return existing.state === "ready" ? existing.texture : null;
|
|
2917
|
+
}
|
|
2918
|
+
this.entries.set(url, { state: "pending" });
|
|
2919
|
+
void this.load(url).then((image) => {
|
|
2920
|
+
if (this.disposed) {
|
|
2921
|
+
image.close?.();
|
|
2922
|
+
return;
|
|
2230
2923
|
}
|
|
2231
|
-
|
|
2924
|
+
const texture = this.upload(image);
|
|
2925
|
+
if (this.disposed) {
|
|
2926
|
+
this.gl.deleteTexture(texture);
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
this.entries.set(url, { state: "ready", texture });
|
|
2930
|
+
this.onUpload?.();
|
|
2931
|
+
}).catch(() => {
|
|
2932
|
+
if (!this.disposed)
|
|
2933
|
+
this.entries.set(url, { state: "failed" });
|
|
2934
|
+
});
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
stats() {
|
|
2938
|
+
let loaded = 0;
|
|
2939
|
+
let pending = 0;
|
|
2940
|
+
let failed2 = 0;
|
|
2941
|
+
for (const entry of this.entries.values()) {
|
|
2942
|
+
if (entry.state === "ready")
|
|
2943
|
+
loaded += 1;
|
|
2944
|
+
else if (entry.state === "pending")
|
|
2945
|
+
pending += 1;
|
|
2946
|
+
else
|
|
2947
|
+
failed2 += 1;
|
|
2232
2948
|
}
|
|
2949
|
+
return { loaded, pending, failed: failed2 };
|
|
2233
2950
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2951
|
+
dispose() {
|
|
2952
|
+
if (this.disposed)
|
|
2953
|
+
return;
|
|
2954
|
+
this.disposed = true;
|
|
2955
|
+
for (const entry of this.entries.values()) {
|
|
2956
|
+
if (entry.state === "ready")
|
|
2957
|
+
this.gl.deleteTexture(entry.texture);
|
|
2958
|
+
}
|
|
2959
|
+
this.entries.clear();
|
|
2960
|
+
}
|
|
2961
|
+
upload(image) {
|
|
2962
|
+
const gl = this.gl;
|
|
2963
|
+
const texture = gl.createTexture();
|
|
2964
|
+
if (!texture) {
|
|
2965
|
+
throw new Error("ICE3_TEXTURE_CREATE_FAILED");
|
|
2966
|
+
}
|
|
2967
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
2968
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
2969
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, REPEAT);
|
|
2970
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, REPEAT);
|
|
2971
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
2972
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2973
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
2974
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2975
|
+
image.close?.();
|
|
2976
|
+
return texture;
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
|
|
2980
|
+
// packages/renderer/dist/WebGL2Uv.js
|
|
2981
|
+
var DEFAULT_UV_SCALE = [1, 1];
|
|
2236
2982
|
|
|
2237
2983
|
// packages/renderer/dist/Color.js
|
|
2238
2984
|
var COLOR_CACHE_LIMIT = 128;
|
|
@@ -2325,9 +3071,12 @@ var WebGL2RendererBackend = class {
|
|
|
2325
3071
|
onContextLost;
|
|
2326
3072
|
onContextRestored;
|
|
2327
3073
|
program = null;
|
|
3074
|
+
clipPlane = [0, 0, 0, 0];
|
|
2328
3075
|
shadowMap = null;
|
|
2329
|
-
|
|
3076
|
+
/** 方向光阴影开关:默认关闭,由 `initialize({ shadows })` 或页面地址 `?shadows=` 打开。 */
|
|
2330
3077
|
shadowEnabled = false;
|
|
3078
|
+
/** `?shadowdebug=1` 输出遮蔽判定,`=2` 输出深度图;排查阴影问题的唯一可信手段。 */
|
|
3079
|
+
shadowDebug = 0;
|
|
2331
3080
|
semanticProgram = null;
|
|
2332
3081
|
geometryCache = null;
|
|
2333
3082
|
instanceCache = null;
|
|
@@ -2355,10 +3104,106 @@ var WebGL2RendererBackend = class {
|
|
|
2355
3104
|
gpuEvictions: 0,
|
|
2356
3105
|
gpuBytes: 0
|
|
2357
3106
|
};
|
|
3107
|
+
textures = null;
|
|
3108
|
+
sceneDepth = null;
|
|
3109
|
+
occlusionRadius = 2.5;
|
|
3110
|
+
/**
|
|
3111
|
+
* 三圈采样比双圈多找到约 1.7 个亮度点的遮蔽(同参数 96.36 → 94.65),原样沿用到
|
|
3112
|
+
* 默认值就把厂区压得发闷。0.30 是回调后的值:整帧 95.70(无遮蔽 99.59),
|
|
3113
|
+
* 高光像素 878(无遮蔽 1088),接触暗部仍看得见。详见 SPEC-007 第 5.5 节。
|
|
3114
|
+
*/
|
|
3115
|
+
occlusionStrength = 0.3;
|
|
3116
|
+
options = null;
|
|
3117
|
+
/**
|
|
3118
|
+
* Bind the material's colour and (if it has one) its base-colour texture.
|
|
3119
|
+
* Called for every opaque/shaded draw so a batched draw and a single mesh draw
|
|
3120
|
+
* cannot drift apart.
|
|
3121
|
+
*/
|
|
3122
|
+
applyMaterial(material) {
|
|
3123
|
+
const program = this.program;
|
|
3124
|
+
if (!program)
|
|
3125
|
+
return;
|
|
3126
|
+
program.setColor(colorToRgba(material.color));
|
|
3127
|
+
const map = material.map;
|
|
3128
|
+
const texture = map && this.textures ? this.textures.texture(map.url) : null;
|
|
3129
|
+
program.setBaseColorMap(texture, map?.scale ?? material.ormMap?.scale ?? DEFAULT_UV_SCALE);
|
|
3130
|
+
const normalMap = material.normalMap;
|
|
3131
|
+
const normalTexture = normalMap && this.textures ? this.textures.texture(normalMap.url) : null;
|
|
3132
|
+
program.setNormalMap(normalTexture, normalMap?.strength ?? 1);
|
|
3133
|
+
const ormMap = material.ormMap;
|
|
3134
|
+
const ormTexture = ormMap && this.textures ? this.textures.texture(ormMap.url) : null;
|
|
3135
|
+
program.setOrmMap(ormTexture, ormMap?.occlusion !== false, ormMap?.metallicRoughness !== false);
|
|
3136
|
+
program.setSurface(material.metallic, material.roughness, material.emissive);
|
|
3137
|
+
}
|
|
3138
|
+
/** Edge overlays are line work: never sampled, always flat. */
|
|
3139
|
+
applyEdgeMaterial(material) {
|
|
3140
|
+
const program = this.program;
|
|
3141
|
+
if (!program)
|
|
3142
|
+
return;
|
|
3143
|
+
program.setColor(colorToRgba(material.edgeColor ?? material.color));
|
|
3144
|
+
program.setBaseColorMap(null, DEFAULT_UV_SCALE);
|
|
3145
|
+
program.setNormalMap(null, 1);
|
|
3146
|
+
program.setOrmMap(null, false, false);
|
|
3147
|
+
program.setSurface(0, 0.85, void 0);
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* `?ao=<radius>,<strength>` 覆盖遮蔽参数(`ao=off` 关闭)。
|
|
3151
|
+
*
|
|
3152
|
+
* 屏幕空间遮蔽的合适参数与场景尺度强相关,靠改代码重编译来找太慢。这里只读一次
|
|
3153
|
+
* 页面地址,纯属调参通道,不参与渲染决策。
|
|
3154
|
+
*/
|
|
3155
|
+
readOcclusionOverride() {
|
|
3156
|
+
if (typeof location === "undefined")
|
|
3157
|
+
return;
|
|
3158
|
+
const value = new URLSearchParams(location.search).get("ao");
|
|
3159
|
+
if (!value)
|
|
3160
|
+
return;
|
|
3161
|
+
if (value === "off") {
|
|
3162
|
+
this.occlusionStrength = 0;
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const parts = value.split(",").map(Number);
|
|
3166
|
+
const radius = parts[0];
|
|
3167
|
+
const strength = parts[1];
|
|
3168
|
+
if (radius !== void 0 && Number.isFinite(radius) && radius > 0) {
|
|
3169
|
+
this.occlusionRadius = radius;
|
|
3170
|
+
}
|
|
3171
|
+
if (strength !== void 0 && Number.isFinite(strength) && strength >= 0) {
|
|
3172
|
+
this.occlusionStrength = strength;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* `?shadows=1` / `?shadows=0` 覆盖阴影开关,与 `?ao` 同属调参通道。
|
|
3177
|
+
*
|
|
3178
|
+
* 阴影是一次额外全场景 pass,默认不开;演示与对比时靠地址栏切换,
|
|
3179
|
+
* 不必为看清"有影子/没影子"重编译一次。
|
|
3180
|
+
*/
|
|
3181
|
+
readShadowOverride() {
|
|
3182
|
+
if (typeof location === "undefined")
|
|
3183
|
+
return;
|
|
3184
|
+
const params = new URLSearchParams(location.search);
|
|
3185
|
+
const value = params.get("shadows");
|
|
3186
|
+
if (value !== null) {
|
|
3187
|
+
this.shadowEnabled = value !== "0" && value !== "off";
|
|
3188
|
+
}
|
|
3189
|
+
const debug = Number(params.get("shadowdebug"));
|
|
3190
|
+
this.shadowDebug = Number.isFinite(debug) ? Math.max(0, Math.min(2, Math.trunc(debug))) : 0;
|
|
3191
|
+
if (this.shadowDebug > 0)
|
|
3192
|
+
this.shadowEnabled = true;
|
|
3193
|
+
}
|
|
3194
|
+
ensureTextureCache() {
|
|
3195
|
+
if (this.textures || !this.gl)
|
|
3196
|
+
return;
|
|
3197
|
+
this.textures = new WebGL2TextureCache(this.gl, void 0, () => {
|
|
3198
|
+
this.options?.onUploadReady?.();
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
2358
3201
|
handleContextLost = (event) => {
|
|
2359
3202
|
event.preventDefault();
|
|
2360
3203
|
this.picker = null;
|
|
2361
3204
|
this.program = null;
|
|
3205
|
+
this.sceneDepth?.dispose();
|
|
3206
|
+
this.sceneDepth = null;
|
|
2362
3207
|
this.shadowMap?.dispose();
|
|
2363
3208
|
this.shadowMap = null;
|
|
2364
3209
|
this.geometryCache?.clear();
|
|
@@ -2379,6 +3224,7 @@ var WebGL2RendererBackend = class {
|
|
|
2379
3224
|
this.onContextRestored?.();
|
|
2380
3225
|
};
|
|
2381
3226
|
initialize(options) {
|
|
3227
|
+
this.options = options;
|
|
2382
3228
|
const gl = options.canvas.getContext("webgl2", {
|
|
2383
3229
|
alpha: true,
|
|
2384
3230
|
antialias: true,
|
|
@@ -2402,6 +3248,11 @@ var WebGL2RendererBackend = class {
|
|
|
2402
3248
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2403
3249
|
gl.polygonOffset(2, 4);
|
|
2404
3250
|
this.createProgram(gl);
|
|
3251
|
+
this.ensureTextureCache();
|
|
3252
|
+
this.sceneDepth = new WebGL2SceneDepth(gl);
|
|
3253
|
+
this.readOcclusionOverride();
|
|
3254
|
+
this.shadowEnabled = options.shadows ?? false;
|
|
3255
|
+
this.readShadowOverride();
|
|
2405
3256
|
this.canvas.addEventListener("webglcontextlost", this.handleContextLost);
|
|
2406
3257
|
this.canvas.addEventListener("webglcontextrestored", this.handleContextRestored);
|
|
2407
3258
|
}
|
|
@@ -2436,6 +3287,9 @@ var WebGL2RendererBackend = class {
|
|
|
2436
3287
|
releaseBatchGeometry(geometry) {
|
|
2437
3288
|
this.geometryCache?.unpin(geometry);
|
|
2438
3289
|
}
|
|
3290
|
+
setClipPlane(plane) {
|
|
3291
|
+
this.clipPlane = plane ? [plane[0], plane[1], plane[2], plane[3]] : [0, 0, 0, 0];
|
|
3292
|
+
}
|
|
2439
3293
|
render(frame) {
|
|
2440
3294
|
if (!this.gl || !this.program || !this.geometryCache) {
|
|
2441
3295
|
throw new ICEEngineError("ICE3_ENGINE_NOT_READY", "Renderer is not initialized");
|
|
@@ -2458,6 +3312,7 @@ var WebGL2RendererBackend = class {
|
|
|
2458
3312
|
gpuBytes: 0
|
|
2459
3313
|
};
|
|
2460
3314
|
this.program.use();
|
|
3315
|
+
this.program.setClipPlane(this.clipPlane);
|
|
2461
3316
|
this.program.setClipOffset(0, 0);
|
|
2462
3317
|
this.program.setDirectionalShading(false);
|
|
2463
3318
|
this.program.setWaterShading(false);
|
|
@@ -2472,8 +3327,58 @@ var WebGL2RendererBackend = class {
|
|
|
2472
3327
|
const renderables = collectRenderables(frame.scene, this.renderablesScratch);
|
|
2473
3328
|
const instances = collectInstanceBatches(renderables, this.instancesScratch);
|
|
2474
3329
|
const batches = collectRenderBatches(frame.scene, this.batchesScratch);
|
|
3330
|
+
const standalone = renderablesOutsideInstanceBatches(renderables, instances);
|
|
2475
3331
|
this.profiler.mark("collect");
|
|
3332
|
+
let aoReady = false;
|
|
3333
|
+
if (this.sceneDepth) {
|
|
3334
|
+
const deviceWidth = Math.max(1, Math.round(frame.viewport.width * this.dpr));
|
|
3335
|
+
const deviceHeight = Math.max(1, Math.round(frame.viewport.height * this.dpr));
|
|
3336
|
+
this.sceneDepth.begin(viewProjection.elements, deviceWidth, deviceHeight);
|
|
3337
|
+
this.sceneDepth.setInstancing(true);
|
|
3338
|
+
for (const batch of instances) {
|
|
3339
|
+
if (isTransparentColor(batch.material.color))
|
|
3340
|
+
continue;
|
|
3341
|
+
const instance = this.instanceCache.retain(gl, this.geometryCache, batch);
|
|
3342
|
+
if (!instance)
|
|
3343
|
+
continue;
|
|
3344
|
+
gl.bindVertexArray(instance.vertexArray);
|
|
3345
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.triangleIndexBuffer);
|
|
3346
|
+
gl.drawElementsInstanced(gl.TRIANGLES, instance.triangleCount, batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
3347
|
+
}
|
|
3348
|
+
this.sceneDepth.setInstancing(false);
|
|
3349
|
+
for (const draw of batches) {
|
|
3350
|
+
if (isTransparentColor(draw.batch.material.color))
|
|
3351
|
+
continue;
|
|
3352
|
+
world.elements.set(draw.worldMatrix);
|
|
3353
|
+
mvp.elements.set(viewProjection.elements);
|
|
3354
|
+
mvp.multiply(world);
|
|
3355
|
+
this.sceneDepth.setMatrix(mvp.elements);
|
|
3356
|
+
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
3357
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3358
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3359
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, draw.batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3360
|
+
}
|
|
3361
|
+
for (const renderable of standalone) {
|
|
3362
|
+
if (isTransparentColor(renderable.mesh.material.color))
|
|
3363
|
+
continue;
|
|
3364
|
+
world.elements.set(renderable.worldMatrix);
|
|
3365
|
+
mvp.elements.set(viewProjection.elements);
|
|
3366
|
+
mvp.multiply(world);
|
|
3367
|
+
this.sceneDepth.setMatrix(mvp.elements);
|
|
3368
|
+
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
3369
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3370
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3371
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, renderable.mesh.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3372
|
+
}
|
|
3373
|
+
this.sceneDepth.end(deviceWidth, deviceHeight);
|
|
3374
|
+
aoReady = true;
|
|
3375
|
+
this.program.use();
|
|
3376
|
+
}
|
|
3377
|
+
if (this.shadowEnabled && this.shadowMap) {
|
|
3378
|
+
this.shadowMap.setBounds(accumulateSceneBounds(renderables));
|
|
3379
|
+
}
|
|
2476
3380
|
if (this.shadowEnabled && this.shadowMap?.begin()) {
|
|
3381
|
+
const lightViewProjection = this.shadowMap.matrix;
|
|
2477
3382
|
for (const batch of instances) {
|
|
2478
3383
|
if (isTransparentColor(batch.material.color))
|
|
2479
3384
|
continue;
|
|
@@ -2484,10 +3389,47 @@ var WebGL2RendererBackend = class {
|
|
|
2484
3389
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.triangleIndexBuffer);
|
|
2485
3390
|
gl.drawElementsInstanced(gl.TRIANGLES, instance.triangleCount, batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
2486
3391
|
}
|
|
3392
|
+
this.shadowMap.setInstancing(false);
|
|
3393
|
+
for (const draw of batches) {
|
|
3394
|
+
if (isTransparentColor(draw.batch.material.color))
|
|
3395
|
+
continue;
|
|
3396
|
+
world.elements.set(draw.worldMatrix);
|
|
3397
|
+
mvp.elements.set(lightViewProjection);
|
|
3398
|
+
mvp.multiply(world);
|
|
3399
|
+
this.shadowMap.setMatrix(mvp.elements);
|
|
3400
|
+
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
3401
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3402
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3403
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, draw.batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3404
|
+
}
|
|
3405
|
+
for (const renderable of standalone) {
|
|
3406
|
+
if (isTransparentColor(renderable.mesh.material.color))
|
|
3407
|
+
continue;
|
|
3408
|
+
world.elements.set(renderable.worldMatrix);
|
|
3409
|
+
mvp.elements.set(lightViewProjection);
|
|
3410
|
+
mvp.multiply(world);
|
|
3411
|
+
this.shadowMap.setMatrix(mvp.elements);
|
|
3412
|
+
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
3413
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3414
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3415
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, renderable.mesh.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3416
|
+
}
|
|
2487
3417
|
this.shadowMap.end(frame.viewport.width * this.dpr, frame.viewport.height * this.dpr);
|
|
2488
3418
|
this.program.use();
|
|
2489
|
-
this.program.setShadow(this.shadowEnabled, this.shadowMap.texture, this.shadowMap.matrix);
|
|
2490
3419
|
}
|
|
3420
|
+
if (this.shadowMap) {
|
|
3421
|
+
this.program.use();
|
|
3422
|
+
this.program.setShadow(
|
|
3423
|
+
this.shadowEnabled,
|
|
3424
|
+
this.shadowMap.texture,
|
|
3425
|
+
this.shadowMap.matrix,
|
|
3426
|
+
// 1.5 个纹素:够把平躺在光里的面抬离自身深度,又不会让影子明显离开物体。
|
|
3427
|
+
this.shadowMap.texelWorld * 1.5
|
|
3428
|
+
);
|
|
3429
|
+
this.program.setShadowDebug(this.shadowDebug);
|
|
3430
|
+
}
|
|
3431
|
+
this.program.setSceneDepth(aoReady ? this.sceneDepth.texture : null, frame.camera.getProjectionMatrix(aspect), frame.camera.near, frame.camera.far, Math.max(1, frame.viewport.width * this.dpr), Math.max(1, frame.viewport.height * this.dpr));
|
|
3432
|
+
this.program.setOcclusion(this.occlusionRadius, this.occlusionStrength);
|
|
2491
3433
|
for (const transparentPass of [false, true]) {
|
|
2492
3434
|
if (transparentPass) {
|
|
2493
3435
|
gl.enable(gl.BLEND);
|
|
@@ -2509,7 +3451,7 @@ var WebGL2RendererBackend = class {
|
|
|
2509
3451
|
this.instancedMeshIds.add(id);
|
|
2510
3452
|
mvp.elements.set(viewProjection.elements);
|
|
2511
3453
|
this.program.setMatrix(mvp);
|
|
2512
|
-
this.
|
|
3454
|
+
this.applyMaterial(batch.material);
|
|
2513
3455
|
this.program.setVertexColors(Boolean(batch.geometry.colors));
|
|
2514
3456
|
this.program.setDirectionalShading(batch.material.shading !== "flat");
|
|
2515
3457
|
this.program.setWaterShading(batch.material.shading === "water");
|
|
@@ -2521,7 +3463,7 @@ var WebGL2RendererBackend = class {
|
|
|
2521
3463
|
this.program.setVertexColors(false);
|
|
2522
3464
|
this.program.setDirectionalShading(false);
|
|
2523
3465
|
this.program.setWaterShading(false);
|
|
2524
|
-
this.
|
|
3466
|
+
this.applyEdgeMaterial(batch.material);
|
|
2525
3467
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.edgeIndexBuffer);
|
|
2526
3468
|
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
2527
3469
|
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
@@ -2540,7 +3482,7 @@ var WebGL2RendererBackend = class {
|
|
|
2540
3482
|
world.elements.set(draw.worldMatrix);
|
|
2541
3483
|
mvp.multiply(world);
|
|
2542
3484
|
this.program.setMatrix(mvp);
|
|
2543
|
-
this.
|
|
3485
|
+
this.applyMaterial(draw.batch.material);
|
|
2544
3486
|
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
2545
3487
|
this.program.setVertexColors(Boolean(draw.batch.geometry.colors));
|
|
2546
3488
|
this.program.setDirectionalShading(draw.batch.material.shading !== "flat");
|
|
@@ -2554,7 +3496,7 @@ var WebGL2RendererBackend = class {
|
|
|
2554
3496
|
this.program.setVertexColors(false);
|
|
2555
3497
|
this.program.setDirectionalShading(false);
|
|
2556
3498
|
this.program.setWaterShading(false);
|
|
2557
|
-
this.
|
|
3499
|
+
this.applyEdgeMaterial(draw.batch.material);
|
|
2558
3500
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.edgeIndexBuffer);
|
|
2559
3501
|
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
2560
3502
|
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
@@ -2573,7 +3515,7 @@ var WebGL2RendererBackend = class {
|
|
|
2573
3515
|
world.elements.set(renderable.worldMatrix);
|
|
2574
3516
|
mvp.multiply(world);
|
|
2575
3517
|
this.program.setMatrix(mvp);
|
|
2576
|
-
this.
|
|
3518
|
+
this.applyMaterial(renderable.mesh.material);
|
|
2577
3519
|
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
2578
3520
|
this.program.setVertexColors(Boolean(renderable.mesh.geometry.colors));
|
|
2579
3521
|
this.program.setDirectionalShading(renderable.mesh.material.shading !== "flat");
|
|
@@ -2586,7 +3528,7 @@ var WebGL2RendererBackend = class {
|
|
|
2586
3528
|
this.program.setVertexColors(false);
|
|
2587
3529
|
this.program.setDirectionalShading(false);
|
|
2588
3530
|
this.program.setWaterShading(false);
|
|
2589
|
-
this.
|
|
3531
|
+
this.applyEdgeMaterial(renderable.mesh.material);
|
|
2590
3532
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.edgeIndexBuffer);
|
|
2591
3533
|
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
2592
3534
|
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
@@ -6263,8 +7205,8 @@ function pickSceneNode(scene, camera, viewport, point) {
|
|
|
6263
7205
|
target.y - eye.y,
|
|
6264
7206
|
target.z - eye.z
|
|
6265
7207
|
]);
|
|
6266
|
-
const right = normalize3(
|
|
6267
|
-
const up = normalize3(
|
|
7208
|
+
const right = normalize3(cross2(forward, [0, 1, 0]));
|
|
7209
|
+
const up = normalize3(cross2(right, forward));
|
|
6268
7210
|
const aspect = viewport.width / Math.max(viewport.height, 1);
|
|
6269
7211
|
const tangent = Math.tan(camera.fov * Math.PI / 360);
|
|
6270
7212
|
const ndcX = point.x / viewport.width * 2 - 1;
|
|
@@ -6306,7 +7248,7 @@ var normalize3 = (value) => {
|
|
|
6306
7248
|
const length = Math.hypot(...value);
|
|
6307
7249
|
return length > 0 ? [value[0] / length, value[1] / length, value[2] / length] : [0, 0, 0];
|
|
6308
7250
|
};
|
|
6309
|
-
var
|
|
7251
|
+
var cross2 = (left, right) => [
|
|
6310
7252
|
left[1] * right[2] - left[2] * right[1],
|
|
6311
7253
|
left[2] * right[0] - left[0] * right[2],
|
|
6312
7254
|
left[0] * right[1] - left[1] * right[0]
|
|
@@ -6612,6 +7554,7 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
6612
7554
|
generatedOverlayCanvas = false;
|
|
6613
7555
|
scene = null;
|
|
6614
7556
|
camera = null;
|
|
7557
|
+
clipPlane = [0, 0, 0, 0];
|
|
6615
7558
|
viewport = { x: 0, y: 0, width: 1, height: 1 };
|
|
6616
7559
|
dpr = 1;
|
|
6617
7560
|
frameId = 0;
|
|
@@ -6707,6 +7650,10 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
6707
7650
|
if (this.state === "destroyed")
|
|
6708
7651
|
return;
|
|
6709
7652
|
this.recordDiagnostic(error.code, error.message, "error");
|
|
7653
|
+
},
|
|
7654
|
+
onUploadReady: () => {
|
|
7655
|
+
if (this.state === "running")
|
|
7656
|
+
this.requestRender("upload-ready");
|
|
6710
7657
|
}
|
|
6711
7658
|
});
|
|
6712
7659
|
window.addEventListener("resize", this.handleWindowResize);
|
|
@@ -6751,6 +7698,17 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
6751
7698
|
this.requestRender("background-color");
|
|
6752
7699
|
return this;
|
|
6753
7700
|
}
|
|
7701
|
+
/**
|
|
7702
|
+
* Section plane `[nx, ny, nz, d]`: fragments with `dot(world, n) + d < 0`
|
|
7703
|
+
* are discarded. `null` (or a zero plane) disables clipping.
|
|
7704
|
+
*/
|
|
7705
|
+
setClipPlane(plane) {
|
|
7706
|
+
this.assertActive();
|
|
7707
|
+
this.clipPlane = plane ? [plane[0], plane[1], plane[2], plane[3]] : [0, 0, 0, 0];
|
|
7708
|
+
this.backend.setClipPlane?.(this.clipPlane);
|
|
7709
|
+
this.requestRender("clip-plane");
|
|
7710
|
+
return this;
|
|
7711
|
+
}
|
|
6754
7712
|
prepareRenderBatchGeometry(geometry) {
|
|
6755
7713
|
if (!this.backend.prepareBatchGeometry) {
|
|
6756
7714
|
throw new ICEEngineError("ICE3_RENDER_BATCH_ADAPTER_UNAVAILABLE", "The active renderer cannot stage adaptive batch geometry");
|
|
@@ -7544,7 +8502,13 @@ function buildAssetScene(snapshot) {
|
|
|
7544
8502
|
const materials = snapshot.materials.map((material) => new ICEBasicMaterial({
|
|
7545
8503
|
color: material.baseColor,
|
|
7546
8504
|
...material.edgeColor ? { edgeColor: material.edgeColor } : {},
|
|
7547
|
-
...material.shading ? { shading: material.shading } : {}
|
|
8505
|
+
...material.shading ? { shading: material.shading } : {},
|
|
8506
|
+
...material.map ? { map: material.map } : {},
|
|
8507
|
+
...material.normalMap ? { normalMap: material.normalMap } : {},
|
|
8508
|
+
...material.ormMap ? { ormMap: material.ormMap } : {},
|
|
8509
|
+
...material.metallic !== void 0 ? { metallic: material.metallic } : {},
|
|
8510
|
+
...material.roughness !== void 0 ? { roughness: material.roughness } : {},
|
|
8511
|
+
...material.emissive ? { emissive: material.emissive } : {}
|
|
7548
8512
|
}));
|
|
7549
8513
|
const meshesByNode = /* @__PURE__ */ new Map();
|
|
7550
8514
|
for (const mesh of snapshot.meshes) {
|
|
@@ -7697,6 +8661,45 @@ function colorHex(color) {
|
|
|
7697
8661
|
}
|
|
7698
8662
|
|
|
7699
8663
|
// packages/assets/dist/ICEProfileValidator.js
|
|
8664
|
+
function validateTextures(json) {
|
|
8665
|
+
const images = records(json.images);
|
|
8666
|
+
const textures = records(json.textures);
|
|
8667
|
+
const samplers = records(json.samplers);
|
|
8668
|
+
for (const image of images) {
|
|
8669
|
+
const uri = image.uri;
|
|
8670
|
+
if (typeof uri !== "string" || !uri || uri.startsWith("/") || uri.includes("..") || uri.includes("://") || image.bufferView !== void 0) {
|
|
8671
|
+
throw assetError("ICE3_ASSET_UNSUPPORTED_FEATURE", "images must reference a relative file path");
|
|
8672
|
+
}
|
|
8673
|
+
}
|
|
8674
|
+
for (const texture of textures) {
|
|
8675
|
+
const source = Number(texture.source);
|
|
8676
|
+
if (!Number.isInteger(source) || source < 0 || source >= images.length) {
|
|
8677
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", "texture source is out of range");
|
|
8678
|
+
}
|
|
8679
|
+
if (texture.sampler !== void 0) {
|
|
8680
|
+
const sampler = Number(texture.sampler);
|
|
8681
|
+
if (!Number.isInteger(sampler) || sampler < 0 || sampler >= samplers.length) {
|
|
8682
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", "texture sampler is out of range");
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
for (const material of records(json.materials)) {
|
|
8687
|
+
const slots = [
|
|
8688
|
+
["baseColorTexture", record(record(material.pbrMetallicRoughness)?.baseColorTexture)],
|
|
8689
|
+
["normalTexture", record(material.normalTexture)],
|
|
8690
|
+
["metallicRoughnessTexture", record(record(material.pbrMetallicRoughness)?.metallicRoughnessTexture)],
|
|
8691
|
+
["occlusionTexture", record(material.occlusionTexture)]
|
|
8692
|
+
];
|
|
8693
|
+
for (const [slot, reference] of slots) {
|
|
8694
|
+
if (!reference)
|
|
8695
|
+
continue;
|
|
8696
|
+
const index = Number(reference.index);
|
|
8697
|
+
if (!Number.isInteger(index) || index < 0 || index >= textures.length) {
|
|
8698
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", `${slot} index is out of range`);
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
7700
8703
|
function validatePreparedProfile(json, binary) {
|
|
7701
8704
|
const asset = record(json.asset);
|
|
7702
8705
|
const profile = record(record(asset?.extras)?.ice3);
|
|
@@ -7724,15 +8727,13 @@ function validatePreparedProfile(json, binary) {
|
|
|
7724
8727
|
"animations",
|
|
7725
8728
|
"skins",
|
|
7726
8729
|
"cameras",
|
|
7727
|
-
"textures",
|
|
7728
|
-
"images",
|
|
7729
|
-
"samplers",
|
|
7730
8730
|
"lights"
|
|
7731
8731
|
]) {
|
|
7732
8732
|
if (Array.isArray(json[field]) && json[field].length > 0) {
|
|
7733
8733
|
throw assetError("ICE3_ASSET_UNSUPPORTED_FEATURE", `${field} are unsupported by the M1 profile`);
|
|
7734
8734
|
}
|
|
7735
8735
|
}
|
|
8736
|
+
validateTextures(json);
|
|
7736
8737
|
if (records(json.scenes).length !== 1 || json.scene !== 0 || records(json.buffers).length !== 1 || records(json.buffers)[0]?.uri !== void 0 || Number(records(json.buffers)[0]?.byteLength) > binary.byteLength) {
|
|
7737
8738
|
throw assetError("ICE3_ASSET_INVALID_GLB", "profile container fields are invalid");
|
|
7738
8739
|
}
|
|
@@ -7890,13 +8891,15 @@ function assetError(code, message) {
|
|
|
7890
8891
|
}
|
|
7891
8892
|
|
|
7892
8893
|
// packages/assets/dist/ICEGLBReader.js
|
|
7893
|
-
function readPreparedGLB(bytes) {
|
|
8894
|
+
function readPreparedGLB(bytes, options = {}) {
|
|
7894
8895
|
const { json, binary, profile } = parseGLB(bytes);
|
|
7895
8896
|
const accessors = records(json.accessors);
|
|
7896
8897
|
const views = records(json.bufferViews);
|
|
7897
8898
|
const sourceNodes = records(json.nodes);
|
|
7898
8899
|
const sourceMeshes = records(json.meshes);
|
|
7899
8900
|
const sourceMaterials = records(json.materials);
|
|
8901
|
+
const sourceTextures = records(json.textures);
|
|
8902
|
+
const sourceImages = records(json.images);
|
|
7900
8903
|
const activeScene = records(json.scenes)[Number(json.scene ?? 0)];
|
|
7901
8904
|
const sceneRoots = integers(activeScene?.nodes);
|
|
7902
8905
|
const parents = new Array(sourceNodes.length).fill(null);
|
|
@@ -7979,11 +8982,18 @@ function readPreparedGLB(bytes) {
|
|
|
7979
8982
|
const edgeMaterial = edgePrimitive ? sourceMaterials[Number(edgePrimitive.material)] : void 0;
|
|
7980
8983
|
const shading = record(record(baseMaterial?.extras)?.ice3)?.shading;
|
|
7981
8984
|
const materialIndex = materials.length;
|
|
8985
|
+
const map = materialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
8986
|
+
const normalMap = normalMaterialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
8987
|
+
const ormMap = ormMaterialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
7982
8988
|
materials.push({
|
|
7983
8989
|
id: `${meshId}/material/${primitiveIndex}`,
|
|
7984
8990
|
baseColor: materialColor(baseMaterial),
|
|
7985
8991
|
...edgeMaterial ? { edgeColor: materialColor(edgeMaterial) } : {},
|
|
7986
|
-
...shading === "directional" || shading === "water" ? { shading } : {}
|
|
8992
|
+
...shading === "directional" || shading === "water" ? { shading } : {},
|
|
8993
|
+
...map ? { map } : {},
|
|
8994
|
+
...normalMap ? { normalMap } : {},
|
|
8995
|
+
...ormMap ? { ormMap } : {},
|
|
8996
|
+
...surfaceFactors(baseMaterial)
|
|
7987
8997
|
});
|
|
7988
8998
|
const bounds = boundsFromAccessor(accessors[positionIndex]);
|
|
7989
8999
|
const geometryIndex = geometries.length;
|
|
@@ -8293,6 +9303,93 @@ function boundsFromProfile(value) {
|
|
|
8293
9303
|
max: [Number(max[0]), Number(max[1]), Number(max[2])]
|
|
8294
9304
|
};
|
|
8295
9305
|
}
|
|
9306
|
+
function materialMap(material, textures, images, baseUrl) {
|
|
9307
|
+
const reference = record(record(material?.pbrMetallicRoughness)?.baseColorTexture);
|
|
9308
|
+
if (!reference)
|
|
9309
|
+
return void 0;
|
|
9310
|
+
const texture = textures[Number(reference.index)];
|
|
9311
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9312
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9313
|
+
return void 0;
|
|
9314
|
+
const transform = record(record(reference.extensions)?.KHR_texture_transform);
|
|
9315
|
+
const scale = numbers(transform?.scale, 2);
|
|
9316
|
+
const su = scale?.[0];
|
|
9317
|
+
const sv = scale?.[1];
|
|
9318
|
+
return {
|
|
9319
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9320
|
+
...typeof su === "number" && typeof sv === "number" && su > 0 && sv > 0 ? { scale: [su, sv] } : {}
|
|
9321
|
+
};
|
|
9322
|
+
}
|
|
9323
|
+
function surfaceFactors(material) {
|
|
9324
|
+
const pbr = record(material?.pbrMetallicRoughness);
|
|
9325
|
+
const metallic = unitOrUndefined(pbr?.metallicFactor);
|
|
9326
|
+
const roughness = unitOrUndefined(pbr?.roughnessFactor);
|
|
9327
|
+
const emissive = numbers(material?.emissiveFactor, 3);
|
|
9328
|
+
return {
|
|
9329
|
+
...metallic !== void 0 ? { metallic } : {},
|
|
9330
|
+
...roughness !== void 0 ? { roughness } : {},
|
|
9331
|
+
...emissive && emissive.some((value) => value > 0) ? { emissive: [emissive[0], emissive[1], emissive[2]] } : {}
|
|
9332
|
+
};
|
|
9333
|
+
}
|
|
9334
|
+
function unitOrUndefined(value) {
|
|
9335
|
+
const parsed = Number(value);
|
|
9336
|
+
if (!Number.isFinite(parsed))
|
|
9337
|
+
return void 0;
|
|
9338
|
+
return Math.min(1, Math.max(0, parsed));
|
|
9339
|
+
}
|
|
9340
|
+
function normalMaterialMap(material, textures, images, baseUrl) {
|
|
9341
|
+
const reference = record(material?.normalTexture);
|
|
9342
|
+
if (!reference)
|
|
9343
|
+
return void 0;
|
|
9344
|
+
const texture = textures[Number(reference.index)];
|
|
9345
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9346
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9347
|
+
return void 0;
|
|
9348
|
+
const strength = Number(reference.scale);
|
|
9349
|
+
return {
|
|
9350
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9351
|
+
...Number.isFinite(strength) && strength >= 0 ? { strength } : {}
|
|
9352
|
+
};
|
|
9353
|
+
}
|
|
9354
|
+
function ormMaterialMap(material, textures, images, baseUrl) {
|
|
9355
|
+
const metallicRoughness = textureReference(record(record(material?.pbrMetallicRoughness)?.metallicRoughnessTexture), textures, images, baseUrl);
|
|
9356
|
+
const occlusion = textureReference(record(material?.occlusionTexture), textures, images, baseUrl);
|
|
9357
|
+
if (!metallicRoughness && !occlusion)
|
|
9358
|
+
return void 0;
|
|
9359
|
+
const url = metallicRoughness?.url ?? occlusion.url;
|
|
9360
|
+
const scale = metallicRoughness?.scale ?? occlusion?.scale;
|
|
9361
|
+
return {
|
|
9362
|
+
url,
|
|
9363
|
+
...scale ? { scale } : {},
|
|
9364
|
+
occlusion: occlusion?.url === url,
|
|
9365
|
+
metallicRoughness: metallicRoughness !== void 0
|
|
9366
|
+
};
|
|
9367
|
+
}
|
|
9368
|
+
function textureReference(reference, textures, images, baseUrl) {
|
|
9369
|
+
if (!reference)
|
|
9370
|
+
return void 0;
|
|
9371
|
+
const texture = textures[Number(reference.index)];
|
|
9372
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9373
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9374
|
+
return void 0;
|
|
9375
|
+
const transform = record(record(reference.extensions)?.KHR_texture_transform);
|
|
9376
|
+
const scale = numbers(transform?.scale, 2);
|
|
9377
|
+
const su = scale?.[0];
|
|
9378
|
+
const sv = scale?.[1];
|
|
9379
|
+
return {
|
|
9380
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9381
|
+
...typeof su === "number" && typeof sv === "number" && su > 0 && sv > 0 ? { scale: [su, sv] } : {}
|
|
9382
|
+
};
|
|
9383
|
+
}
|
|
9384
|
+
function resolveTextureUrl(uri, baseUrl) {
|
|
9385
|
+
if (!baseUrl)
|
|
9386
|
+
return uri;
|
|
9387
|
+
try {
|
|
9388
|
+
return new URL(uri, baseUrl).href;
|
|
9389
|
+
} catch {
|
|
9390
|
+
return uri;
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
8296
9393
|
function materialColor(material) {
|
|
8297
9394
|
if (!material)
|
|
8298
9395
|
return "#808080";
|
|
@@ -9193,7 +10290,7 @@ var ICEAssetLoader = class {
|
|
|
9193
10290
|
throw new Error(`ICE3_ASSET_FETCH_FAILED: HTTP ${response.status}`);
|
|
9194
10291
|
}
|
|
9195
10292
|
const bytes = await response.arrayBuffer();
|
|
9196
|
-
const snapshot = readPreparedGLB(bytes);
|
|
10293
|
+
const snapshot = readPreparedGLB(bytes, { baseUrl: url });
|
|
9197
10294
|
return { bytes, snapshot };
|
|
9198
10295
|
}
|
|
9199
10296
|
};
|
|
@@ -9983,7 +11080,7 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
9983
11080
|
adaptivePlanningSignature = "";
|
|
9984
11081
|
renderBatchingMode;
|
|
9985
11082
|
visibleCoverageIds = /* @__PURE__ */ new Set();
|
|
9986
|
-
constructor(engine, snapshot, camera, scheduler,
|
|
11083
|
+
constructor(engine, snapshot, camera, scheduler, fetch2, maxConcurrentTiles, maxCommitsPerFrame, maxScreenSpaceErrorPixels, lodSettleDelayMs, renderBatching, callerSignal) {
|
|
9987
11084
|
super();
|
|
9988
11085
|
this.engine = engine;
|
|
9989
11086
|
this.snapshot = snapshot;
|
|
@@ -10014,7 +11111,7 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
10014
11111
|
});
|
|
10015
11112
|
}
|
|
10016
11113
|
this.assetLoader = new ICEAssetLoader(engine, {
|
|
10017
|
-
fetch: (input, init) =>
|
|
11114
|
+
fetch: (input, init) => fetch2(input, {
|
|
10018
11115
|
...init ?? {},
|
|
10019
11116
|
signal: this.abortController.signal
|
|
10020
11117
|
})
|
|
@@ -11174,8 +12271,20 @@ export {
|
|
|
11174
12271
|
deserializeScene,
|
|
11175
12272
|
serializeScene
|
|
11176
12273
|
};
|
|
11177
|
-
//!
|
|
12274
|
+
//! 阴影视锥:按场景包围盒拟合方向光的正交投影。
|
|
11178
12275
|
//!
|
|
12276
|
+
//! 早期实现把光照视锥写死成 860 米见方、960 米深。两个后果都很贵:
|
|
12277
|
+
//! 2048 的深度图摊到 860 米是每纹素 0.42 米(薄墙的影子直接消失),
|
|
12278
|
+
//! 而深度偏差 0.0015 在 960 米的跨度上等于 1.4 米,短影子被整段吃掉。
|
|
12279
|
+
//! 视锥贴合场景后,偏差回到"厘米"量级,贴图分辨率也才有意义。
|
|
12280
|
+
//! 方向光阴影贴图。
|
|
11179
12281
|
//! 一次深度 pass 把场景从光源方向渲染进深度纹理,主 pass 再按世界坐标比较深度。
|
|
11180
|
-
//!
|
|
12282
|
+
//! 只处理单方向光、单张贴图;视锥按场景包围盒拟合(见 ShadowFrustum)。
|
|
12283
|
+
//! 场景包围盒:给阴影视锥用。
|
|
12284
|
+
//! 引擎里没有现成的场景 bounds,而阴影视锥必须贴合实际内容——写死范围会让
|
|
12285
|
+
//! 深度精度和贴图分辨率一起浪费(见 ShadowFrustum)。这里按"几何局部包围盒
|
|
12286
|
+
//! 缓存 + 世界矩阵变换"累加,避免每帧扫描几十万个顶点。
|
|
12287
|
+
//! 相机深度通道:屏幕空间遮蔽的来源。
|
|
12288
|
+
//! 从**相机**方向渲染一遍深度。必须覆盖全部几何路径——瓦片走渲染批次,其余走实例化
|
|
12289
|
+
//! 批次;只画一条会让深度图大半是空的(SPEC-007)。
|
|
11181
12290
|
//# sourceMappingURL=index.js.map
|