action-engine-js 1.0.0
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/LICENSE +45 -0
- package/README.md +348 -0
- package/actionengine/3rdparty/goblin/goblin.js +9609 -0
- package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
- package/actionengine/camera/actioncamera.js +90 -0
- package/actionengine/camera/cameracollisionhandler.js +69 -0
- package/actionengine/character/actioncharacter.js +360 -0
- package/actionengine/character/actioncharacter3D.js +61 -0
- package/actionengine/core/app.js +430 -0
- package/actionengine/debug/basedebugpanel.js +858 -0
- package/actionengine/display/canvasmanager.js +75 -0
- package/actionengine/display/gl/programmanager.js +570 -0
- package/actionengine/display/gl/shaders/lineshader.js +118 -0
- package/actionengine/display/gl/shaders/objectshader.js +1756 -0
- package/actionengine/display/gl/shaders/particleshader.js +43 -0
- package/actionengine/display/gl/shaders/shadowshader.js +319 -0
- package/actionengine/display/gl/shaders/spriteshader.js +100 -0
- package/actionengine/display/gl/shaders/watershader.js +67 -0
- package/actionengine/display/graphics/actionmodel3D.js +191 -0
- package/actionengine/display/graphics/actionsprite3D.js +230 -0
- package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
- package/actionengine/display/graphics/lighting/actionlight.js +211 -0
- package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
- package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
- package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
- package/actionengine/display/graphics/renderableobject.js +44 -0
- package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
- package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
- package/actionengine/display/graphics/texture/texturemanager.js +242 -0
- package/actionengine/display/graphics/texture/textureregistry.js +177 -0
- package/actionengine/input/actionscrollablearea.js +1405 -0
- package/actionengine/input/inputhandler.js +1647 -0
- package/actionengine/math/geometry/geometrybuilder.js +161 -0
- package/actionengine/math/geometry/glbexporter.js +364 -0
- package/actionengine/math/geometry/glbloader.js +722 -0
- package/actionengine/math/geometry/modelcodegenerator.js +97 -0
- package/actionengine/math/geometry/triangle.js +33 -0
- package/actionengine/math/geometry/triangleutils.js +34 -0
- package/actionengine/math/mathutils.js +25 -0
- package/actionengine/math/matrix4.js +785 -0
- package/actionengine/math/physics/actionphysics.js +108 -0
- package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
- package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
- package/actionengine/math/physics/actionraycast.js +129 -0
- package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
- package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
- package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
- package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
- package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
- package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
- package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
- package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
- package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
- package/actionengine/math/quaternion.js +61 -0
- package/actionengine/math/vector2.js +277 -0
- package/actionengine/math/vector3.js +318 -0
- package/actionengine/math/viewfrustum.js +136 -0
- package/actionengine/network/ACTIONNETREADME.md +810 -0
- package/actionengine/network/client/ActionNetManager.js +802 -0
- package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
- package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
- package/actionengine/network/client/SyncSystem.js +422 -0
- package/actionengine/network/p2p/ActionNetPeer.js +142 -0
- package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
- package/actionengine/network/p2p/DataConnection.js +282 -0
- package/actionengine/network/p2p/README.md +510 -0
- package/actionengine/network/p2p/example.html +502 -0
- package/actionengine/network/server/ActionNetServer.js +577 -0
- package/actionengine/network/server/ActionNetServerSSL.js +579 -0
- package/actionengine/network/server/ActionNetServerUtils.js +458 -0
- package/actionengine/network/server/SERVERREADME.md +314 -0
- package/actionengine/network/server/package-lock.json +35 -0
- package/actionengine/network/server/package.json +13 -0
- package/actionengine/network/server/start.bat +27 -0
- package/actionengine/network/server/start.sh +25 -0
- package/actionengine/network/server/startwss.bat +27 -0
- package/actionengine/sound/audiomanager.js +1589 -0
- package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
- package/actionengine/sound/soundfont/actionparser.js +718 -0
- package/actionengine/sound/soundfont/actionreverb.js +252 -0
- package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
- package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
- package/actionengine/sound/soundfont/soundfont.js +2 -0
- package/dist/action-engine.min.js +328 -0
- package/package.json +35 -0
|
@@ -0,0 +1,1756 @@
|
|
|
1
|
+
// actionengine/display/gl/shaders/objectshader.js
|
|
2
|
+
|
|
3
|
+
class ObjectShader {
|
|
4
|
+
constructor() {
|
|
5
|
+
// Store references to different object shader variants
|
|
6
|
+
this.variants = {
|
|
7
|
+
default: {
|
|
8
|
+
getVertexShader: this.getDefaultVertexShader,
|
|
9
|
+
getFragmentShader: this.getDefaultFragmentShader
|
|
10
|
+
},
|
|
11
|
+
pbr: {
|
|
12
|
+
getVertexShader: this.getPBRVertexShader,
|
|
13
|
+
getFragmentShader: this.getPBRFragmentShader
|
|
14
|
+
},
|
|
15
|
+
virtualboy: {
|
|
16
|
+
getVertexShader: this.getVirtualBoyVertexShader,
|
|
17
|
+
getFragmentShader: this.getVirtualBoyFragmentShader
|
|
18
|
+
}
|
|
19
|
+
// Additional variants can be added here
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Current active variant (default to 'default')
|
|
23
|
+
this.currentVariant = "default";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set the current shader variant
|
|
28
|
+
* @param {string} variantName - Name of the variant to use
|
|
29
|
+
*/
|
|
30
|
+
setVariant(variantName) {
|
|
31
|
+
if (this.variants[variantName]) {
|
|
32
|
+
this.currentVariant = variantName;
|
|
33
|
+
console.log(`[ObjectShader] Set shader variant to: ${variantName}`);
|
|
34
|
+
} else {
|
|
35
|
+
console.warn(`[ObjectShader] Unknown variant: ${variantName}, using default`);
|
|
36
|
+
this.currentVariant = "default";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the current variant name
|
|
42
|
+
* @returns {string} - Current variant name
|
|
43
|
+
*/
|
|
44
|
+
getCurrentVariant() {
|
|
45
|
+
return this.currentVariant;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the current variant's vertex shader
|
|
50
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
51
|
+
* @returns {string} - Vertex shader source code
|
|
52
|
+
*/
|
|
53
|
+
getVertexShader(isWebGL2) {
|
|
54
|
+
return this.variants[this.currentVariant].getVertexShader.call(this, isWebGL2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the current variant's fragment shader
|
|
59
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
60
|
+
* @returns {string} - Fragment shader source code
|
|
61
|
+
*/
|
|
62
|
+
getFragmentShader(isWebGL2) {
|
|
63
|
+
return this.variants[this.currentVariant].getFragmentShader.call(this, isWebGL2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//--------------------------------------------------------------------------
|
|
67
|
+
// DEFAULT SHADER VARIANT
|
|
68
|
+
//--------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Default object vertex shader
|
|
72
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
73
|
+
* @returns {string} - Vertex shader source code
|
|
74
|
+
*/
|
|
75
|
+
getDefaultVertexShader(isWebGL2) {
|
|
76
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
77
|
+
// Add precision qualifier to make it match fragment shader
|
|
78
|
+
precision mediump float;
|
|
79
|
+
|
|
80
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aPosition;
|
|
81
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aNormal;
|
|
82
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aColor;
|
|
83
|
+
${isWebGL2 ? "in" : "attribute"} vec2 aTexCoord;
|
|
84
|
+
${isWebGL2 ? "in" : "attribute"} float aTextureIndex;
|
|
85
|
+
${isWebGL2 ? "in" : "attribute"} float aUseTexture;
|
|
86
|
+
|
|
87
|
+
uniform mat4 uProjectionMatrix;
|
|
88
|
+
uniform mat4 uViewMatrix;
|
|
89
|
+
uniform mat4 uModelMatrix;
|
|
90
|
+
uniform mat4 uLightSpaceMatrix; // Added for shadow mapping
|
|
91
|
+
uniform vec3 uLightDir;
|
|
92
|
+
|
|
93
|
+
${isWebGL2 ? "out" : "varying"} vec3 vColor;
|
|
94
|
+
${isWebGL2 ? "out" : "varying"} vec2 vTexCoord;
|
|
95
|
+
${isWebGL2 ? "out" : "varying"} float vLighting;
|
|
96
|
+
${isWebGL2 ? "flat out" : "varying"} float vTextureIndex;
|
|
97
|
+
${isWebGL2 ? "flat out" : "varying"} float vUseTexture;
|
|
98
|
+
${isWebGL2 ? "out" : "varying"} vec4 vFragPosLightSpace; // Added for shadow mapping
|
|
99
|
+
${isWebGL2 ? "out" : "varying"} vec3 vNormal;
|
|
100
|
+
${isWebGL2 ? "out" : "varying"} vec3 vFragPos;
|
|
101
|
+
|
|
102
|
+
void main() {
|
|
103
|
+
vec4 worldPos = uModelMatrix * vec4(aPosition, 1.0);
|
|
104
|
+
vFragPos = worldPos.xyz;
|
|
105
|
+
gl_Position = uProjectionMatrix * uViewMatrix * worldPos;
|
|
106
|
+
|
|
107
|
+
// Position in light space for shadow mapping
|
|
108
|
+
vFragPosLightSpace = uLightSpaceMatrix * worldPos;
|
|
109
|
+
|
|
110
|
+
// Pass world-space normal
|
|
111
|
+
vNormal = mat3(uModelMatrix) * aNormal;
|
|
112
|
+
|
|
113
|
+
// Calculate basic diffuse lighting
|
|
114
|
+
// Note: We negate the light direction to make it consistent with shadow mapping
|
|
115
|
+
vec3 worldNormal = normalize(vNormal);
|
|
116
|
+
vLighting = max(0.3, min(1.0, dot(worldNormal, normalize(-uLightDir))));
|
|
117
|
+
|
|
118
|
+
// Pass other variables to fragment shader
|
|
119
|
+
vColor = aColor;
|
|
120
|
+
vTexCoord = aTexCoord;
|
|
121
|
+
vTextureIndex = aTextureIndex;
|
|
122
|
+
vUseTexture = aUseTexture;
|
|
123
|
+
}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Default object fragment shader
|
|
128
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
129
|
+
* @returns {string} - Fragment shader source code
|
|
130
|
+
*/
|
|
131
|
+
getDefaultFragmentShader(isWebGL2) {
|
|
132
|
+
// Directly include shadow calculation functions
|
|
133
|
+
const shadowFunctions = isWebGL2
|
|
134
|
+
? `
|
|
135
|
+
// Sample from shadow map with hardware-enabled filtering
|
|
136
|
+
float shadowCalculation(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
137
|
+
// Perform perspective divide to get NDC coordinates
|
|
138
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
139
|
+
|
|
140
|
+
// Transform to [0,1] range for texture lookup
|
|
141
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
142
|
+
|
|
143
|
+
// Check if position is outside the shadow map bounds
|
|
144
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
145
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
146
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
147
|
+
return 1.0; // No shadow outside shadow map
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Explicitly sample shadow map with explicit texture binding
|
|
151
|
+
// This helps avoid texture binding conflicts
|
|
152
|
+
float closestDepth = texture(shadowMap, projCoords.xy).r;
|
|
153
|
+
|
|
154
|
+
// Get current depth value
|
|
155
|
+
float currentDepth = projCoords.z;
|
|
156
|
+
|
|
157
|
+
// Apply bias from uniform to avoid shadow acne
|
|
158
|
+
float bias = uShadowBias;
|
|
159
|
+
|
|
160
|
+
// Check if fragment is in shadow
|
|
161
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
162
|
+
|
|
163
|
+
return shadow;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// PCF shadow mapping for smoother shadows
|
|
167
|
+
float shadowCalculationPCF(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
168
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
169
|
+
if (!uPCFEnabled) {
|
|
170
|
+
return shadowCalculation(fragPosLightSpace, shadowMap);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Perform perspective divide to get NDC coordinates
|
|
174
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
175
|
+
|
|
176
|
+
// Transform to [0,1] range for texture lookup
|
|
177
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
178
|
+
|
|
179
|
+
// Check if position is outside the shadow map bounds
|
|
180
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
181
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
182
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
183
|
+
return 1.0; // No shadow outside shadow map
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get current depth value
|
|
187
|
+
float currentDepth = projCoords.z;
|
|
188
|
+
|
|
189
|
+
// Apply bias from uniform - adjust using softness factor
|
|
190
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
191
|
+
float bias = uShadowBias * softnessFactor;
|
|
192
|
+
|
|
193
|
+
// Calculate PCF with explicit shadow map sampling
|
|
194
|
+
float shadow = 0.0;
|
|
195
|
+
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
|
196
|
+
|
|
197
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
198
|
+
int pcfRadius = uPCFSize / 2;
|
|
199
|
+
float totalSamples = 0.0;
|
|
200
|
+
|
|
201
|
+
// Dynamic PCF sampling using the specified kernel size
|
|
202
|
+
for(int x = -pcfRadius; x <= pcfRadius; ++x) {
|
|
203
|
+
for(int y = -pcfRadius; y <= pcfRadius; ++y) {
|
|
204
|
+
// Skip samples outside the kernel radius
|
|
205
|
+
// (needed for non-square kernels like 3x3, 5x5, etc.)
|
|
206
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
207
|
+
// Apply softness factor to sampling coordinates
|
|
208
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
209
|
+
|
|
210
|
+
// Explicitly sample shadow map with clear texture binding
|
|
211
|
+
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
|
212
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
213
|
+
totalSamples += 1.0;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Average samples
|
|
219
|
+
shadow /= max(1.0, totalSamples);
|
|
220
|
+
|
|
221
|
+
return shadow;
|
|
222
|
+
}`
|
|
223
|
+
: `
|
|
224
|
+
// Unpack depth from RGBA color
|
|
225
|
+
float unpackDepth(vec4 packedDepth) {
|
|
226
|
+
const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0));
|
|
227
|
+
return dot(packedDepth, bitShift);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Shadow calculation for WebGL1
|
|
231
|
+
float shadowCalculation(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
232
|
+
// Perform perspective divide to get NDC coordinates
|
|
233
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
234
|
+
|
|
235
|
+
// Transform to [0,1] range for texture lookup
|
|
236
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
237
|
+
|
|
238
|
+
// Check if position is outside the shadow map bounds
|
|
239
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
240
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
241
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
242
|
+
return 1.0; // No shadow outside shadow map
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Get packed depth value
|
|
246
|
+
vec4 packedDepth = texture2D(shadowMap, projCoords.xy);
|
|
247
|
+
|
|
248
|
+
// Unpack the depth value
|
|
249
|
+
float closestDepth = unpackDepth(packedDepth);
|
|
250
|
+
|
|
251
|
+
// Get current depth value
|
|
252
|
+
float currentDepth = projCoords.z;
|
|
253
|
+
|
|
254
|
+
// Apply bias from uniform to avoid shadow acne
|
|
255
|
+
float bias = uShadowBias;
|
|
256
|
+
|
|
257
|
+
// Check if fragment is in shadow
|
|
258
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
259
|
+
|
|
260
|
+
return shadow;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// PCF shadow calculation for WebGL1
|
|
264
|
+
float shadowCalculationPCF(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
265
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
266
|
+
if (!uPCFEnabled) {
|
|
267
|
+
return shadowCalculation(fragPosLightSpace, shadowMap);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Perform perspective divide to get NDC coordinates
|
|
271
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
272
|
+
|
|
273
|
+
// Transform to [0,1] range for texture lookup
|
|
274
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
275
|
+
|
|
276
|
+
// Check if position is outside the shadow map bounds
|
|
277
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
278
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
279
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
280
|
+
return 1.0; // No shadow outside shadow map
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Get current depth value
|
|
284
|
+
float currentDepth = projCoords.z;
|
|
285
|
+
|
|
286
|
+
// Apply bias from uniform - adjust using softness factor
|
|
287
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
288
|
+
float bias = uShadowBias * softnessFactor;
|
|
289
|
+
|
|
290
|
+
// Calculate PCF with explicit shadow map sampling
|
|
291
|
+
float shadow = 0.0;
|
|
292
|
+
float texelSize = 1.0 / uShadowMapSize;
|
|
293
|
+
|
|
294
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
295
|
+
int pcfRadius = int(uPCFSize) / 2;
|
|
296
|
+
float totalSamples = 0.0;
|
|
297
|
+
|
|
298
|
+
// WebGL1 has more limited loop support, so limit to max 9x9 kernel
|
|
299
|
+
// We need fixed loop bounds in WebGL1
|
|
300
|
+
for(int x = -4; x <= 4; ++x) {
|
|
301
|
+
for(int y = -4; y <= 4; ++y) {
|
|
302
|
+
// Skip samples outside the requested kernel radius
|
|
303
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
304
|
+
// Apply softness factor to sampling coordinates
|
|
305
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
306
|
+
|
|
307
|
+
vec4 packedDepth = texture2D(shadowMap, projCoords.xy + offset);
|
|
308
|
+
float pcfDepth = unpackDepth(packedDepth);
|
|
309
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
310
|
+
totalSamples += 1.0;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Average samples
|
|
316
|
+
shadow /= max(1.0, totalSamples);
|
|
317
|
+
|
|
318
|
+
return shadow;
|
|
319
|
+
}`;
|
|
320
|
+
|
|
321
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
322
|
+
precision mediump float;
|
|
323
|
+
${isWebGL2 ? "precision mediump sampler2DArray;\n" : ""}
|
|
324
|
+
|
|
325
|
+
${isWebGL2 ? "in" : "varying"} vec3 vColor;
|
|
326
|
+
${isWebGL2 ? "in" : "varying"} vec2 vTexCoord;
|
|
327
|
+
${isWebGL2 ? "in" : "varying"} float vLighting;
|
|
328
|
+
${isWebGL2 ? "flat in" : "varying"} float vTextureIndex;
|
|
329
|
+
${isWebGL2 ? "flat in" : "varying"} float vUseTexture;
|
|
330
|
+
${isWebGL2 ? "in" : "varying"} vec4 vFragPosLightSpace;
|
|
331
|
+
${isWebGL2 ? "in" : "varying"} vec3 vNormal;
|
|
332
|
+
${isWebGL2 ? "in" : "varying"} vec3 vFragPos;
|
|
333
|
+
|
|
334
|
+
// Texture array for albedo textures
|
|
335
|
+
${isWebGL2 ? "uniform sampler2DArray uTextureArray;" : "uniform sampler2D uTexture;"}
|
|
336
|
+
|
|
337
|
+
// Shadow map with explicit separate binding
|
|
338
|
+
// Always use sampler2D for shadow maps
|
|
339
|
+
uniform sampler2D uShadowMap;
|
|
340
|
+
${isWebGL2 ? "uniform samplerCube uPointShadowMap;" : "uniform sampler2D uPointShadowMap;"}
|
|
341
|
+
|
|
342
|
+
// Light counts
|
|
343
|
+
uniform int uDirectionalLightCount;
|
|
344
|
+
uniform int uPointLightCount;
|
|
345
|
+
uniform int uSpotLightCount;
|
|
346
|
+
|
|
347
|
+
// Light data textures - each light has multiple pixels for all properties
|
|
348
|
+
uniform sampler2D uDirectionalLightData;
|
|
349
|
+
uniform vec2 uDirectionalLightTextureSize;
|
|
350
|
+
uniform sampler2D uPointLightData;
|
|
351
|
+
uniform vec2 uPointLightTextureSize;
|
|
352
|
+
|
|
353
|
+
// Legacy directional light uniforms (for backward compatibility)
|
|
354
|
+
uniform vec3 uLightPos;
|
|
355
|
+
uniform vec3 uLightDir;
|
|
356
|
+
uniform float uLightIntensity;
|
|
357
|
+
uniform vec3 uLightColor;
|
|
358
|
+
|
|
359
|
+
// Legacy point light uniforms (for backward compatibility)
|
|
360
|
+
uniform vec3 uPointLightPos;
|
|
361
|
+
uniform float uPointLightIntensity;
|
|
362
|
+
uniform float uLightRadius;
|
|
363
|
+
uniform vec3 uPointLightColor;
|
|
364
|
+
|
|
365
|
+
// Legacy second point light uniforms
|
|
366
|
+
uniform vec3 uPointLightPos1;
|
|
367
|
+
uniform float uPointLightIntensity1;
|
|
368
|
+
uniform float uPointLightRadius1;
|
|
369
|
+
uniform vec3 uPointLightColor1;
|
|
370
|
+
uniform samplerCube uPointShadowMap1;
|
|
371
|
+
uniform bool uPointShadowsEnabled1;
|
|
372
|
+
|
|
373
|
+
// Additional point light shadow maps
|
|
374
|
+
uniform samplerCube uPointShadowMap2;
|
|
375
|
+
uniform bool uPointShadowsEnabled2;
|
|
376
|
+
uniform samplerCube uPointShadowMap3;
|
|
377
|
+
uniform bool uPointShadowsEnabled3;
|
|
378
|
+
|
|
379
|
+
uniform float uIntensityFactor; // Factor controlling intensity effect in default shader
|
|
380
|
+
uniform bool uShadowsEnabled;
|
|
381
|
+
uniform bool uPointShadowsEnabled; // Enable point light shadows
|
|
382
|
+
//uniform int uPointLightCount; // Number of point lights
|
|
383
|
+
uniform float uShadowBias; // Shadow bias uniform for controlling shadow acne
|
|
384
|
+
uniform float uShadowMapSize; // Shadow map size for texture calculations
|
|
385
|
+
uniform float uShadowSoftness; // Controls shadow edge softness (0-1)
|
|
386
|
+
uniform int uPCFSize; // Controls PCF kernel size (1, 3, 5, 7, 9)
|
|
387
|
+
uniform bool uPCFEnabled; // Controls whether PCF filtering is enabled
|
|
388
|
+
uniform float uFarPlane; // Far plane for point light shadows
|
|
389
|
+
|
|
390
|
+
// Structures to hold light data
|
|
391
|
+
struct DirectionalLight {
|
|
392
|
+
vec3 position;
|
|
393
|
+
vec3 direction;
|
|
394
|
+
vec3 color;
|
|
395
|
+
float intensity;
|
|
396
|
+
bool shadowsEnabled;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
struct PointLight {
|
|
400
|
+
vec3 position;
|
|
401
|
+
vec3 color;
|
|
402
|
+
float intensity;
|
|
403
|
+
float radius;
|
|
404
|
+
bool shadowsEnabled;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Functions to extract light data from textures
|
|
408
|
+
DirectionalLight getDirectionalLight(int index) {
|
|
409
|
+
// Each light takes 3 pixels horizontally
|
|
410
|
+
int basePixel = index * 3;
|
|
411
|
+
|
|
412
|
+
// Calculate UV coordinates for each pixel
|
|
413
|
+
// First pixel: position + enabled
|
|
414
|
+
float u1 = (float(basePixel) + 0.5) / uDirectionalLightTextureSize.x;
|
|
415
|
+
// Second pixel: direction + shadowEnabled
|
|
416
|
+
float u2 = (float(basePixel + 1) + 0.5) / uDirectionalLightTextureSize.x;
|
|
417
|
+
// Third pixel: color + intensity
|
|
418
|
+
float u3 = (float(basePixel + 2) + 0.5) / uDirectionalLightTextureSize.x;
|
|
419
|
+
|
|
420
|
+
// Use centered V coordinate (there's only one row)
|
|
421
|
+
float v = 0.5 / uDirectionalLightTextureSize.y;
|
|
422
|
+
|
|
423
|
+
// Sample pixels from texture
|
|
424
|
+
vec4 posData = texture(uDirectionalLightData, vec2(u1, v));
|
|
425
|
+
vec4 dirData = texture(uDirectionalLightData, vec2(u2, v));
|
|
426
|
+
vec4 colorData = texture(uDirectionalLightData, vec2(u3, v));
|
|
427
|
+
|
|
428
|
+
// Create and populate the light structure
|
|
429
|
+
DirectionalLight light;
|
|
430
|
+
light.position = posData.xyz;
|
|
431
|
+
light.direction = dirData.xyz;
|
|
432
|
+
light.color = colorData.rgb;
|
|
433
|
+
light.intensity = colorData.a;
|
|
434
|
+
light.shadowsEnabled = dirData.a > 0.5;
|
|
435
|
+
|
|
436
|
+
return light;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
PointLight getPointLight(int index) {
|
|
440
|
+
// Each light takes 3 pixels horizontally
|
|
441
|
+
int basePixel = index * 3;
|
|
442
|
+
|
|
443
|
+
// Calculate UV coordinates for each pixel
|
|
444
|
+
// First pixel: position + enabled
|
|
445
|
+
float u1 = (float(basePixel) + 0.5) / uPointLightTextureSize.x;
|
|
446
|
+
// Second pixel: color + intensity
|
|
447
|
+
float u2 = (float(basePixel + 1) + 0.5) / uPointLightTextureSize.x;
|
|
448
|
+
// Third pixel: radius + shadowEnabled + padding
|
|
449
|
+
float u3 = (float(basePixel + 2) + 0.5) / uPointLightTextureSize.x;
|
|
450
|
+
|
|
451
|
+
// Use centered V coordinate (there's only one row)
|
|
452
|
+
float v = 0.5 / uPointLightTextureSize.y;
|
|
453
|
+
|
|
454
|
+
// Sample pixels from texture
|
|
455
|
+
vec4 posData = texture(uPointLightData, vec2(u1, v));
|
|
456
|
+
vec4 colorData = texture(uPointLightData, vec2(u2, v));
|
|
457
|
+
vec4 radiusData = texture(uPointLightData, vec2(u3, v));
|
|
458
|
+
|
|
459
|
+
// Create and populate the light structure
|
|
460
|
+
PointLight light;
|
|
461
|
+
light.position = posData.xyz;
|
|
462
|
+
light.color = colorData.rgb;
|
|
463
|
+
light.intensity = colorData.a;
|
|
464
|
+
light.radius = radiusData.r;
|
|
465
|
+
light.shadowsEnabled = radiusData.g > 0.5;
|
|
466
|
+
|
|
467
|
+
return light;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
${isWebGL2 ? "out vec4 fragColor;" : ""}
|
|
471
|
+
|
|
472
|
+
// Shadow mapping functions
|
|
473
|
+
${shadowFunctions}
|
|
474
|
+
|
|
475
|
+
// Point light shadow functions
|
|
476
|
+
${
|
|
477
|
+
isWebGL2
|
|
478
|
+
? `
|
|
479
|
+
// Calculate shadow for omnidirectional point light with cubemap shadow
|
|
480
|
+
float pointShadowCalculation(vec3 fragPos, vec3 lightPos, samplerCube shadowMap, float farPlane) {
|
|
481
|
+
// Calculate fragment-to-light vector
|
|
482
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
483
|
+
|
|
484
|
+
// Get current distance from fragment to light
|
|
485
|
+
float currentDepth = length(fragToLight);
|
|
486
|
+
|
|
487
|
+
// Normalize to [0,1] range using far plane
|
|
488
|
+
currentDepth = currentDepth / farPlane;
|
|
489
|
+
|
|
490
|
+
// Apply bias
|
|
491
|
+
float bias = uShadowBias;
|
|
492
|
+
|
|
493
|
+
// Sample from cubemap shadow map in the direction of fragToLight
|
|
494
|
+
float closestDepth = texture(shadowMap, fragToLight).r;
|
|
495
|
+
|
|
496
|
+
// Check if fragment is in shadow
|
|
497
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
498
|
+
|
|
499
|
+
return shadow;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// PCF shadow calculation for omnidirectional point light
|
|
503
|
+
float pointShadowCalculationPCF(vec3 fragPos, vec3 lightPos, samplerCube shadowMap, float farPlane) {
|
|
504
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
505
|
+
if (!uPCFEnabled) {
|
|
506
|
+
return pointShadowCalculation(fragPos, lightPos, shadowMap, farPlane);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Calculate fragment-to-light vector (will be used as cubemap direction)
|
|
510
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
511
|
+
|
|
512
|
+
// Get current distance from fragment to light
|
|
513
|
+
float currentDepth = length(fragToLight);
|
|
514
|
+
|
|
515
|
+
// Normalize to [0,1] range using far plane
|
|
516
|
+
currentDepth = currentDepth / farPlane;
|
|
517
|
+
|
|
518
|
+
// Apply bias, adjusted by softness
|
|
519
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
520
|
+
float bias = uShadowBias * softnessFactor;
|
|
521
|
+
|
|
522
|
+
// Set up PCF sampling
|
|
523
|
+
float shadow = 0.0;
|
|
524
|
+
int samples = 0;
|
|
525
|
+
float diskRadius = 0.01 * softnessFactor; // Adjust based on softness and distance
|
|
526
|
+
|
|
527
|
+
// Generate a tangent space TBN matrix for sampling in a cone
|
|
528
|
+
vec3 absFragToLight = abs(fragToLight);
|
|
529
|
+
vec3 tangent, bitangent;
|
|
530
|
+
|
|
531
|
+
// Find least used axis to avoid precision issues
|
|
532
|
+
if (absFragToLight.x <= absFragToLight.y && absFragToLight.x <= absFragToLight.z) {
|
|
533
|
+
tangent = vec3(0.0, fragToLight.z, -fragToLight.y);
|
|
534
|
+
} else if (absFragToLight.y <= absFragToLight.x && absFragToLight.y <= absFragToLight.z) {
|
|
535
|
+
tangent = vec3(fragToLight.z, 0.0, -fragToLight.x);
|
|
536
|
+
} else {
|
|
537
|
+
tangent = vec3(fragToLight.y, -fragToLight.x, 0.0);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
tangent = normalize(tangent);
|
|
541
|
+
bitangent = normalize(cross(fragToLight, tangent));
|
|
542
|
+
|
|
543
|
+
// Determine sample count based on PCF size
|
|
544
|
+
int pcfRadius = uPCFSize / 2;
|
|
545
|
+
int maxSamples = (pcfRadius * 2 + 1) * (pcfRadius * 2 + 1);
|
|
546
|
+
|
|
547
|
+
for (int i = 0; i < maxSamples; i++) {
|
|
548
|
+
// Skip if we exceed the requested PCF size
|
|
549
|
+
int x = (i % 9) - 4;
|
|
550
|
+
int y = (i / 9) - 4;
|
|
551
|
+
|
|
552
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
553
|
+
// Generate offset direction based on x, y grid position
|
|
554
|
+
float angle = float(x) * (3.14159265359 / float(pcfRadius + 1)); // Convert x to angle
|
|
555
|
+
float distance = float(y) + 0.1; // Add small offset to avoid zero
|
|
556
|
+
|
|
557
|
+
// Calculate offset direction in tangent space
|
|
558
|
+
vec3 offset = tangent * (cos(angle) * distance * diskRadius) +
|
|
559
|
+
bitangent * (sin(angle) * distance * diskRadius);
|
|
560
|
+
|
|
561
|
+
// Sample from the cubemap with offset
|
|
562
|
+
float closestDepth = texture(shadowMap, normalize(fragToLight + offset)).r;
|
|
563
|
+
|
|
564
|
+
// Check if fragment is in shadow with bias
|
|
565
|
+
shadow += currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
566
|
+
samples++;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Average all samples
|
|
571
|
+
shadow /= float(max(samples, 1));
|
|
572
|
+
|
|
573
|
+
return shadow;
|
|
574
|
+
}`
|
|
575
|
+
: `
|
|
576
|
+
// For WebGL1 without cubemap support, calculate shadow from a single face
|
|
577
|
+
float pointShadowCalculation(vec3 fragPos, vec3 lightPos, sampler2D shadowMap, float farPlane) {
|
|
578
|
+
// We can't do proper cubemap in WebGL1, so this is just an approximation
|
|
579
|
+
// using the first face of what would be a cubemap
|
|
580
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
581
|
+
|
|
582
|
+
// Get current distance from fragment to light
|
|
583
|
+
float currentDepth = length(fragToLight);
|
|
584
|
+
|
|
585
|
+
// Normalize to [0,1] range using far plane
|
|
586
|
+
currentDepth = currentDepth / farPlane;
|
|
587
|
+
|
|
588
|
+
// Simple planar mapping for the single shadow map face
|
|
589
|
+
// This is just a fallback - won't look great but better than nothing
|
|
590
|
+
vec2 shadowCoord = vec2(
|
|
591
|
+
(fragToLight.x / abs(fragToLight.x + 0.0001) + 1.0) * 0.25,
|
|
592
|
+
(fragToLight.y / abs(fragToLight.y + 0.0001) + 1.0) * 0.25
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// Apply bias
|
|
596
|
+
float bias = uShadowBias;
|
|
597
|
+
|
|
598
|
+
// Sample from shadow map
|
|
599
|
+
vec4 packedDepth = texture2D(shadowMap, shadowCoord);
|
|
600
|
+
float closestDepth = unpackDepth(packedDepth);
|
|
601
|
+
|
|
602
|
+
// Check if fragment is in shadow
|
|
603
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
604
|
+
|
|
605
|
+
return shadow;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Simplified PCF for WebGL1 single-face approximation
|
|
609
|
+
float pointShadowCalculationPCF(vec3 fragPos, vec3 lightPos, sampler2D shadowMap, float farPlane) {
|
|
610
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
611
|
+
if (!uPCFEnabled) {
|
|
612
|
+
return pointShadowCalculation(fragPos, lightPos, shadowMap, farPlane);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Calculate fragment-to-light vector
|
|
616
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
617
|
+
|
|
618
|
+
// Get current distance from fragment to light
|
|
619
|
+
float currentDepth = length(fragToLight);
|
|
620
|
+
|
|
621
|
+
// Normalize to [0,1] range using far plane
|
|
622
|
+
currentDepth = currentDepth / farPlane;
|
|
623
|
+
|
|
624
|
+
// Simple planar mapping for the single shadow map face
|
|
625
|
+
vec2 shadowCoord = vec2(
|
|
626
|
+
(fragToLight.x / abs(fragToLight.x + 0.0001) + 1.0) * 0.25,
|
|
627
|
+
(fragToLight.y / abs(fragToLight.y + 0.0001) + 1.0) * 0.25
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
// Apply bias
|
|
631
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
632
|
+
float bias = uShadowBias * softnessFactor;
|
|
633
|
+
|
|
634
|
+
// Set up PCF sampling
|
|
635
|
+
float shadow = 0.0;
|
|
636
|
+
float texelSize = 1.0 / uShadowMapSize;
|
|
637
|
+
|
|
638
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
639
|
+
int pcfRadius = int(uPCFSize) / 2;
|
|
640
|
+
float totalSamples = 0.0;
|
|
641
|
+
|
|
642
|
+
// Limit loop size for WebGL1
|
|
643
|
+
for(int x = -4; x <= 4; ++x) {
|
|
644
|
+
for(int y = -4; y <= 4; ++y) {
|
|
645
|
+
// Skip samples outside the requested kernel radius
|
|
646
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
647
|
+
// Apply softness factor to sampling coordinates
|
|
648
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
649
|
+
|
|
650
|
+
vec4 packedDepth = texture2D(shadowMap, shadowCoord + offset);
|
|
651
|
+
float pcfDepth = unpackDepth(packedDepth);
|
|
652
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
653
|
+
totalSamples += 1.0;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Average samples
|
|
659
|
+
shadow /= max(1.0, totalSamples);
|
|
660
|
+
|
|
661
|
+
return shadow;
|
|
662
|
+
}`
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
void main() {
|
|
666
|
+
// Base color calculation
|
|
667
|
+
vec4 baseColor;
|
|
668
|
+
if (vUseTexture > 0.5) { // Check if this fragment uses texture
|
|
669
|
+
${isWebGL2 ? "baseColor = texture(uTextureArray, vec3(vTexCoord, vTextureIndex));" : "baseColor = texture2D(uTexture, vTexCoord);"}
|
|
670
|
+
} else {
|
|
671
|
+
baseColor = vec4(vColor, 1.0);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Apply ambient and diffuse lighting
|
|
675
|
+
float ambient = 0.3; // Higher ambient to ensure dungeon isn't too dark
|
|
676
|
+
// Negate light direction to be consistent with shadow mapping convention
|
|
677
|
+
float diffuse = max(0.0, dot(normalize(vNormal), normalize(-uLightDir)));
|
|
678
|
+
|
|
679
|
+
// Apply light intensity directly - use a more dramatic effect
|
|
680
|
+
// Scale from 0 (no light) to very bright at high intensity values
|
|
681
|
+
float intensity = uLightIntensity / 100.0; // More aggressive scaling
|
|
682
|
+
diffuse = diffuse * clamp(intensity, 0.1, 10.0); // Allow for dramatically brighter light
|
|
683
|
+
|
|
684
|
+
// Calculate shadow factor for directional light
|
|
685
|
+
float shadow = 1.0;
|
|
686
|
+
if (uShadowsEnabled) {
|
|
687
|
+
// Use explicit texture lookup to avoid sampler conflicts
|
|
688
|
+
float shadowFactor = shadowCalculationPCF(vFragPosLightSpace, uShadowMap);
|
|
689
|
+
// Match the PBR shader calculation - shadows should be darker
|
|
690
|
+
shadow = 1.0 - (1.0 - shadowFactor) * 0.8;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Calculate point light contributions
|
|
694
|
+
vec3 pointLightColors = vec3(0.0);
|
|
695
|
+
|
|
696
|
+
// Process all point lights from the data texture
|
|
697
|
+
for (int i = 0; i < uPointLightCount; i++) {
|
|
698
|
+
if (i >= 100) break; // Reasonable safety limit
|
|
699
|
+
|
|
700
|
+
// Extract light data from texture
|
|
701
|
+
PointLight light = getPointLight(i);
|
|
702
|
+
|
|
703
|
+
vec3 lightDir = normalize(vFragPos - light.position);
|
|
704
|
+
float pointDiffuse = max(0.0, dot(normalize(vNormal), -lightDir));
|
|
705
|
+
|
|
706
|
+
// Calculate distance attenuation
|
|
707
|
+
float distance = length(vFragPos - light.position);
|
|
708
|
+
float attenuation = 1.0 / (1.0 + (distance * distance) / (light.radius * light.radius));
|
|
709
|
+
|
|
710
|
+
// Calculate shadow for point light
|
|
711
|
+
float pointShadow = 1.0;
|
|
712
|
+
if (light.shadowsEnabled) {
|
|
713
|
+
// Handle first 8 shadow maps with a switch statement
|
|
714
|
+
int lightIdx = i % 8; // Limit to 8 shadowed lights
|
|
715
|
+
switch(lightIdx) {
|
|
716
|
+
case 0:
|
|
717
|
+
if (uPointShadowsEnabled) {
|
|
718
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap, uFarPlane);
|
|
719
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
case 1:
|
|
723
|
+
if (uPointShadowsEnabled1) {
|
|
724
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap1, uFarPlane);
|
|
725
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
case 2:
|
|
729
|
+
if (uPointShadowsEnabled2) {
|
|
730
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap2, uFarPlane);
|
|
731
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
case 3:
|
|
735
|
+
if (uPointShadowsEnabled3) {
|
|
736
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap3, uFarPlane);
|
|
737
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Calculate final point light contribution
|
|
744
|
+
float pointLightFactor = max(0.0, pointDiffuse * attenuation * pointShadow);
|
|
745
|
+
|
|
746
|
+
// Add contribution from this light
|
|
747
|
+
pointLightColors += baseColor.rgb * pointLightFactor * light.color * light.intensity;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Legacy point light handling - only use if no new lights are available
|
|
751
|
+
if (uPointLightCount == 0) {
|
|
752
|
+
// Backward compatibility for first point light
|
|
753
|
+
vec3 lightDir = normalize(vFragPos - uPointLightPos);
|
|
754
|
+
float pointDiffuse = max(0.0, dot(normalize(vNormal), -lightDir));
|
|
755
|
+
|
|
756
|
+
float distance = length(vFragPos - uPointLightPos);
|
|
757
|
+
float attenuation = 1.0 / (1.0 + (distance * distance) / (uLightRadius * uLightRadius));
|
|
758
|
+
|
|
759
|
+
float pointShadow = 1.0;
|
|
760
|
+
if (uPointShadowsEnabled) {
|
|
761
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, uPointLightPos, uPointShadowMap, uFarPlane);
|
|
762
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
float pointLightFactor = max(0.0, pointDiffuse * attenuation * pointShadow);
|
|
766
|
+
pointLightColors = baseColor.rgb * pointLightFactor * uPointLightColor * uPointLightIntensity;
|
|
767
|
+
|
|
768
|
+
// Legacy second point light
|
|
769
|
+
if (uPointLightCount > 1) {
|
|
770
|
+
lightDir = normalize(vFragPos - uPointLightPos1);
|
|
771
|
+
pointDiffuse = max(0.0, dot(normalize(vNormal), -lightDir));
|
|
772
|
+
|
|
773
|
+
distance = length(vFragPos - uPointLightPos1);
|
|
774
|
+
attenuation = 1.0 / (1.0 + (distance * distance) / (uPointLightRadius1 * uPointLightRadius1));
|
|
775
|
+
|
|
776
|
+
pointShadow = 1.0;
|
|
777
|
+
if (uPointShadowsEnabled1) {
|
|
778
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, uPointLightPos1, uPointShadowMap1, uFarPlane);
|
|
779
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
pointLightFactor = max(0.0, pointDiffuse * attenuation * pointShadow);
|
|
783
|
+
pointLightColors += baseColor.rgb * pointLightFactor * uPointLightColor1 * uPointLightIntensity1;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Get total directional light contribution
|
|
788
|
+
float lighting = ambient; // Start with ambient
|
|
789
|
+
vec3 directionalContribution = vec3(0.0);
|
|
790
|
+
|
|
791
|
+
// Process all directional lights from the data texture
|
|
792
|
+
for (int i = 0; i < uDirectionalLightCount; i++) {
|
|
793
|
+
if (i >= 100) break; // Reasonable safety limit
|
|
794
|
+
|
|
795
|
+
// Extract light data from texture
|
|
796
|
+
DirectionalLight light = getDirectionalLight(i);
|
|
797
|
+
|
|
798
|
+
// Calculate diffuse component
|
|
799
|
+
float lightDiffuse = max(0.0, dot(normalize(vNormal), normalize(-light.direction)));
|
|
800
|
+
|
|
801
|
+
// Calculate shadow - currently only first light gets shadow mapping
|
|
802
|
+
float lightShadow = 1.0;
|
|
803
|
+
if (light.shadowsEnabled && i == 0 && uShadowsEnabled) {
|
|
804
|
+
lightShadow = shadow;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Add this light's contribution
|
|
808
|
+
float contribution = lightDiffuse * lightShadow * light.intensity * uIntensityFactor;
|
|
809
|
+
directionalContribution += baseColor.rgb * contribution * light.color;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// If there are no directional lights or for backward compatibility
|
|
813
|
+
if (uDirectionalLightCount == 0 && uShadowsEnabled) {
|
|
814
|
+
// Legacy directional light calculation
|
|
815
|
+
float legacyContribution = diffuse * shadow * uLightIntensity * uIntensityFactor;
|
|
816
|
+
directionalContribution = baseColor.rgb * (ambient + legacyContribution);
|
|
817
|
+
} else if (uDirectionalLightCount == 0) {
|
|
818
|
+
// Just ambient with no directional lights
|
|
819
|
+
directionalContribution = baseColor.rgb * ambient;
|
|
820
|
+
} else {
|
|
821
|
+
// Add ambient to the direct contribution
|
|
822
|
+
directionalContribution += baseColor.rgb * ambient;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Properly handle the combination of both light types
|
|
826
|
+
// This ensures they're physically correctly combined and don't over-brighten
|
|
827
|
+
vec3 result = directionalContribution;
|
|
828
|
+
|
|
829
|
+
// Only add point light if it exists
|
|
830
|
+
if (uPointLightCount > 0) {
|
|
831
|
+
// NO BLENDING - JUST ADD THE LIGHT CONTRIBUTIONS DIRECTLY
|
|
832
|
+
// This eliminates the intensity-based blend factor that was causing inversion
|
|
833
|
+
result = directionalContribution + pointLightColors;
|
|
834
|
+
|
|
835
|
+
// OPTIONAL: To prevent over-brightening, we could clamp the result
|
|
836
|
+
// but for now let's see what happens with direct addition
|
|
837
|
+
// result = min(vec3(1.0), result);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
${isWebGL2 ? "fragColor" : "gl_FragColor"} = vec4(result, baseColor.a);
|
|
841
|
+
}`;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
//--------------------------------------------------------------------------
|
|
845
|
+
// PBR SHADER VARIANT
|
|
846
|
+
//--------------------------------------------------------------------------
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* PBR vertex shader
|
|
850
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
851
|
+
* @returns {string} - Vertex shader source code
|
|
852
|
+
*/
|
|
853
|
+
getPBRVertexShader(isWebGL2) {
|
|
854
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
855
|
+
// Attributes - data coming in per vertex
|
|
856
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aPosition;
|
|
857
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aNormal;
|
|
858
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aColor;
|
|
859
|
+
${isWebGL2 ? "in" : "attribute"} vec2 aTexCoord;
|
|
860
|
+
${isWebGL2 ? "in" : "attribute"} float aTextureIndex;
|
|
861
|
+
${isWebGL2 ? "in" : "attribute"} float aUseTexture;
|
|
862
|
+
|
|
863
|
+
// Uniforms - shared data for all vertices
|
|
864
|
+
uniform mat4 uProjectionMatrix;
|
|
865
|
+
uniform mat4 uViewMatrix;
|
|
866
|
+
uniform mat4 uModelMatrix;
|
|
867
|
+
uniform mat4 uLightSpaceMatrix; // Added for shadow mapping
|
|
868
|
+
|
|
869
|
+
uniform vec3 uLightDir;
|
|
870
|
+
uniform vec3 uCameraPos;
|
|
871
|
+
|
|
872
|
+
// Outputs to fragment shader
|
|
873
|
+
${isWebGL2 ? "out" : "varying"} vec3 vNormal; // Surface normal
|
|
874
|
+
${isWebGL2 ? "out" : "varying"} vec3 vWorldPos; // Position in world space
|
|
875
|
+
${isWebGL2 ? "out" : "varying"} vec4 vFragPosLightSpace; // Added for shadow mapping
|
|
876
|
+
${isWebGL2 ? "out" : "varying"} vec3 vFragPos;
|
|
877
|
+
${isWebGL2 ? "out" : "varying"} vec3 vColor;
|
|
878
|
+
${isWebGL2 ? "out" : "varying"} vec3 vViewDir; // Direction to camera
|
|
879
|
+
${isWebGL2 ? "flat out" : "varying"} float vTextureIndex;
|
|
880
|
+
${isWebGL2 ? "out" : "varying"} vec2 vTexCoord;
|
|
881
|
+
${isWebGL2 ? "flat out" : "varying"} float vUseTexture;
|
|
882
|
+
|
|
883
|
+
void main() {
|
|
884
|
+
// Calculate world position
|
|
885
|
+
vec4 worldPos = uModelMatrix * vec4(aPosition, 1.0);
|
|
886
|
+
vWorldPos = worldPos.xyz;
|
|
887
|
+
vFragPos = worldPos.xyz;
|
|
888
|
+
// Transform normal to world space
|
|
889
|
+
vNormal = mat3(uModelMatrix) * aNormal;
|
|
890
|
+
|
|
891
|
+
// Calculate view direction
|
|
892
|
+
vViewDir = normalize(uCameraPos - worldPos.xyz);
|
|
893
|
+
|
|
894
|
+
// Position in light space for shadow mapping
|
|
895
|
+
vFragPosLightSpace = uLightSpaceMatrix * worldPos;
|
|
896
|
+
|
|
897
|
+
// Pass color and texture info to fragment shader
|
|
898
|
+
vColor = aColor;
|
|
899
|
+
vTexCoord = aTexCoord;
|
|
900
|
+
vTextureIndex = aTextureIndex;
|
|
901
|
+
vUseTexture = aUseTexture;
|
|
902
|
+
|
|
903
|
+
// Final position
|
|
904
|
+
gl_Position = uProjectionMatrix * uViewMatrix * worldPos;
|
|
905
|
+
}`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* PBR fragment shader
|
|
910
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
911
|
+
* @returns {string} - Fragment shader source code
|
|
912
|
+
*/
|
|
913
|
+
getPBRFragmentShader(isWebGL2) {
|
|
914
|
+
// Directly include shadow calculation functions
|
|
915
|
+
const shadowFunctions = isWebGL2
|
|
916
|
+
? `
|
|
917
|
+
// Sample from shadow map with hardware-enabled filtering
|
|
918
|
+
float shadowCalculation(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
919
|
+
// Perform perspective divide to get NDC coordinates
|
|
920
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
921
|
+
|
|
922
|
+
// Transform to [0,1] range for texture lookup
|
|
923
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
924
|
+
|
|
925
|
+
// Check if position is outside the shadow map bounds
|
|
926
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
927
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
928
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
929
|
+
return 1.0; // No shadow outside shadow map
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Explicitly sample shadow map with explicit texture binding
|
|
933
|
+
// This helps avoid texture binding conflicts
|
|
934
|
+
float closestDepth = texture(shadowMap, projCoords.xy).r;
|
|
935
|
+
|
|
936
|
+
// Get current depth value
|
|
937
|
+
float currentDepth = projCoords.z;
|
|
938
|
+
|
|
939
|
+
// Apply bias from uniform to avoid shadow acne
|
|
940
|
+
float bias = uShadowBias;
|
|
941
|
+
|
|
942
|
+
// Check if fragment is in shadow
|
|
943
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
944
|
+
|
|
945
|
+
return shadow;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// PCF shadow mapping for smoother shadows
|
|
949
|
+
float shadowCalculationPCF(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
950
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
951
|
+
if (!uPCFEnabled) {
|
|
952
|
+
return shadowCalculation(fragPosLightSpace, shadowMap);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Perform perspective divide to get NDC coordinates
|
|
956
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
957
|
+
|
|
958
|
+
// Transform to [0,1] range for texture lookup
|
|
959
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
960
|
+
|
|
961
|
+
// Check if position is outside the shadow map bounds
|
|
962
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
963
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
964
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
965
|
+
return 1.0; // No shadow outside shadow map
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Get current depth value
|
|
969
|
+
float currentDepth = projCoords.z;
|
|
970
|
+
|
|
971
|
+
// Apply bias from uniform - adjust using softness factor
|
|
972
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
973
|
+
float bias = uShadowBias * softnessFactor;
|
|
974
|
+
|
|
975
|
+
// Calculate PCF with explicit shadow map sampling
|
|
976
|
+
float shadow = 0.0;
|
|
977
|
+
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
|
978
|
+
|
|
979
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
980
|
+
int pcfRadius = uPCFSize / 2;
|
|
981
|
+
float totalSamples = 0.0;
|
|
982
|
+
|
|
983
|
+
// Dynamic PCF sampling using the specified kernel size
|
|
984
|
+
for(int x = -pcfRadius; x <= pcfRadius; ++x) {
|
|
985
|
+
for(int y = -pcfRadius; y <= pcfRadius; ++y) {
|
|
986
|
+
// Skip samples outside the kernel radius
|
|
987
|
+
// (needed for non-square kernels like 3x3, 5x5, etc.)
|
|
988
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
989
|
+
// Apply softness factor to sampling coordinates
|
|
990
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
991
|
+
|
|
992
|
+
// Explicitly sample shadow map with clear texture binding
|
|
993
|
+
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
|
994
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
995
|
+
totalSamples += 1.0;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Average samples
|
|
1001
|
+
shadow /= max(1.0, totalSamples);
|
|
1002
|
+
|
|
1003
|
+
return shadow;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Calculate shadow for omnidirectional point light with cubemap shadow
|
|
1007
|
+
float pointShadowCalculation(vec3 fragPos, vec3 lightPos, samplerCube shadowMap, float farPlane) {
|
|
1008
|
+
// Calculate fragment-to-light vector
|
|
1009
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
1010
|
+
|
|
1011
|
+
// Get current distance from fragment to light
|
|
1012
|
+
float currentDepth = length(fragToLight);
|
|
1013
|
+
|
|
1014
|
+
// Normalize to [0,1] range using far plane
|
|
1015
|
+
currentDepth = currentDepth / farPlane;
|
|
1016
|
+
|
|
1017
|
+
// Apply bias
|
|
1018
|
+
float bias = uShadowBias;
|
|
1019
|
+
|
|
1020
|
+
// Sample from cubemap shadow map in the direction of fragToLight
|
|
1021
|
+
float closestDepth = texture(shadowMap, fragToLight).r;
|
|
1022
|
+
|
|
1023
|
+
// Check if fragment is in shadow
|
|
1024
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
1025
|
+
|
|
1026
|
+
return shadow;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// PCF shadow calculation for omnidirectional point light
|
|
1030
|
+
float pointShadowCalculationPCF(vec3 fragPos, vec3 lightPos, samplerCube shadowMap, float farPlane) {
|
|
1031
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
1032
|
+
if (!uPCFEnabled) {
|
|
1033
|
+
return pointShadowCalculation(fragPos, lightPos, shadowMap, farPlane);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Calculate fragment-to-light vector (will be used as cubemap direction)
|
|
1037
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
1038
|
+
|
|
1039
|
+
// Get current distance from fragment to light
|
|
1040
|
+
float currentDepth = length(fragToLight);
|
|
1041
|
+
|
|
1042
|
+
// Normalize to [0,1] range using far plane
|
|
1043
|
+
currentDepth = currentDepth / farPlane;
|
|
1044
|
+
|
|
1045
|
+
// Apply bias, adjusted by softness
|
|
1046
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
1047
|
+
float bias = uShadowBias * softnessFactor;
|
|
1048
|
+
|
|
1049
|
+
// Set up PCF sampling
|
|
1050
|
+
float shadow = 0.0;
|
|
1051
|
+
int samples = 0;
|
|
1052
|
+
float diskRadius = 0.01 * softnessFactor; // Adjust based on softness and distance
|
|
1053
|
+
|
|
1054
|
+
// Generate a tangent space TBN matrix for sampling in a cone
|
|
1055
|
+
vec3 absFragToLight = abs(fragToLight);
|
|
1056
|
+
vec3 tangent, bitangent;
|
|
1057
|
+
|
|
1058
|
+
// Find least used axis to avoid precision issues
|
|
1059
|
+
if (absFragToLight.x <= absFragToLight.y && absFragToLight.x <= absFragToLight.z) {
|
|
1060
|
+
tangent = vec3(0.0, fragToLight.z, -fragToLight.y);
|
|
1061
|
+
} else if (absFragToLight.y <= absFragToLight.x && absFragToLight.y <= absFragToLight.z) {
|
|
1062
|
+
tangent = vec3(fragToLight.z, 0.0, -fragToLight.x);
|
|
1063
|
+
} else {
|
|
1064
|
+
tangent = vec3(fragToLight.y, -fragToLight.x, 0.0);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
tangent = normalize(tangent);
|
|
1068
|
+
bitangent = normalize(cross(fragToLight, tangent));
|
|
1069
|
+
|
|
1070
|
+
// Determine sample count based on PCF size
|
|
1071
|
+
int pcfRadius = uPCFSize / 2;
|
|
1072
|
+
int maxSamples = (pcfRadius * 2 + 1) * (pcfRadius * 2 + 1);
|
|
1073
|
+
|
|
1074
|
+
for (int i = 0; i < maxSamples; i++) {
|
|
1075
|
+
// Skip if we exceed the requested PCF size
|
|
1076
|
+
int x = (i % 9) - 4;
|
|
1077
|
+
int y = (i / 9) - 4;
|
|
1078
|
+
|
|
1079
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
1080
|
+
// Generate offset direction based on x, y grid position
|
|
1081
|
+
float angle = float(x) * (3.14159265359 / float(pcfRadius + 1)); // Convert x to angle
|
|
1082
|
+
float distance = float(y) + 0.1; // Add small offset to avoid zero
|
|
1083
|
+
|
|
1084
|
+
// Calculate offset direction in tangent space
|
|
1085
|
+
vec3 offset = tangent * (cos(angle) * distance * diskRadius) +
|
|
1086
|
+
bitangent * (sin(angle) * distance * diskRadius);
|
|
1087
|
+
|
|
1088
|
+
// Sample from the cubemap with offset
|
|
1089
|
+
float closestDepth = texture(shadowMap, normalize(fragToLight + offset)).r;
|
|
1090
|
+
|
|
1091
|
+
// Check if fragment is in shadow with bias
|
|
1092
|
+
shadow += currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
1093
|
+
samples++;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Average all samples
|
|
1098
|
+
shadow /= float(max(samples, 1));
|
|
1099
|
+
|
|
1100
|
+
return shadow;
|
|
1101
|
+
}`
|
|
1102
|
+
: `
|
|
1103
|
+
// Unpack depth from RGBA color
|
|
1104
|
+
float unpackDepth(vec4 packedDepth) {
|
|
1105
|
+
const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0));
|
|
1106
|
+
return dot(packedDepth, bitShift);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Shadow calculation for WebGL1
|
|
1110
|
+
float shadowCalculation(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
1111
|
+
// Perform perspective divide to get NDC coordinates
|
|
1112
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
1113
|
+
|
|
1114
|
+
// Transform to [0,1] range for texture lookup
|
|
1115
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
1116
|
+
|
|
1117
|
+
// Check if position is outside the shadow map bounds
|
|
1118
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
1119
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
1120
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
1121
|
+
return 1.0; // No shadow outside shadow map
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Get packed depth value
|
|
1125
|
+
vec4 packedDepth = texture2D(shadowMap, projCoords.xy);
|
|
1126
|
+
|
|
1127
|
+
// Unpack the depth value
|
|
1128
|
+
float closestDepth = unpackDepth(packedDepth);
|
|
1129
|
+
|
|
1130
|
+
// Get current depth value
|
|
1131
|
+
float currentDepth = projCoords.z;
|
|
1132
|
+
|
|
1133
|
+
// Apply bias from uniform to avoid shadow acne
|
|
1134
|
+
float bias = uShadowBias;
|
|
1135
|
+
|
|
1136
|
+
// Check if fragment is in shadow
|
|
1137
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
1138
|
+
|
|
1139
|
+
return shadow;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// PCF shadow calculation for WebGL1
|
|
1143
|
+
float shadowCalculationPCF(vec4 fragPosLightSpace, sampler2D shadowMap) {
|
|
1144
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
1145
|
+
if (!uPCFEnabled) {
|
|
1146
|
+
return shadowCalculation(fragPosLightSpace, shadowMap);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Perform perspective divide to get NDC coordinates
|
|
1150
|
+
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
1151
|
+
|
|
1152
|
+
// Transform to [0,1] range for texture lookup
|
|
1153
|
+
projCoords = projCoords * 0.5 + 0.5;
|
|
1154
|
+
|
|
1155
|
+
// Check if position is outside the shadow map bounds
|
|
1156
|
+
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
1157
|
+
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
1158
|
+
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
1159
|
+
return 1.0; // No shadow outside shadow map
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Get current depth value
|
|
1163
|
+
float currentDepth = projCoords.z;
|
|
1164
|
+
|
|
1165
|
+
// Apply bias from uniform - adjust using softness factor
|
|
1166
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
1167
|
+
float bias = uShadowBias * softnessFactor;
|
|
1168
|
+
|
|
1169
|
+
// Calculate PCF with explicit shadow map sampling
|
|
1170
|
+
float shadow = 0.0;
|
|
1171
|
+
float texelSize = 1.0 / uShadowMapSize;
|
|
1172
|
+
|
|
1173
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
1174
|
+
int pcfRadius = int(uPCFSize) / 2;
|
|
1175
|
+
float totalSamples = 0.0;
|
|
1176
|
+
|
|
1177
|
+
// WebGL1 has more limited loop support, so limit to max 9x9 kernel
|
|
1178
|
+
// We need fixed loop bounds in WebGL1
|
|
1179
|
+
for(int x = -4; x <= 4; ++x) {
|
|
1180
|
+
for(int y = -4; y <= 4; ++y) {
|
|
1181
|
+
// Skip samples outside the requested kernel radius
|
|
1182
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
1183
|
+
// Apply softness factor to sampling coordinates
|
|
1184
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
1185
|
+
|
|
1186
|
+
vec4 packedDepth = texture2D(shadowMap, projCoords.xy + offset);
|
|
1187
|
+
float pcfDepth = unpackDepth(packedDepth);
|
|
1188
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
1189
|
+
totalSamples += 1.0;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Average samples
|
|
1195
|
+
shadow /= max(1.0, totalSamples);
|
|
1196
|
+
|
|
1197
|
+
return shadow;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// For WebGL1 without cubemap support, calculate shadow from a single face
|
|
1201
|
+
float pointShadowCalculation(vec3 fragPos, vec3 lightPos, sampler2D shadowMap, float farPlane) {
|
|
1202
|
+
// We can't do proper cubemap in WebGL1, so this is just an approximation
|
|
1203
|
+
// using the first face of what would be a cubemap
|
|
1204
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
1205
|
+
|
|
1206
|
+
// Get current distance from fragment to light
|
|
1207
|
+
float currentDepth = length(fragToLight);
|
|
1208
|
+
|
|
1209
|
+
// Normalize to [0,1] range using far plane
|
|
1210
|
+
currentDepth = currentDepth / farPlane;
|
|
1211
|
+
|
|
1212
|
+
// Simple planar mapping for the single shadow map face
|
|
1213
|
+
// This is just a fallback - won't look great but better than nothing
|
|
1214
|
+
vec2 shadowCoord = vec2(
|
|
1215
|
+
(fragToLight.x / abs(fragToLight.x + 0.0001) + 1.0) * 0.25,
|
|
1216
|
+
(fragToLight.y / abs(fragToLight.y + 0.0001) + 1.0) * 0.25
|
|
1217
|
+
);
|
|
1218
|
+
|
|
1219
|
+
// Apply bias
|
|
1220
|
+
float bias = uShadowBias;
|
|
1221
|
+
|
|
1222
|
+
// Sample from shadow map
|
|
1223
|
+
vec4 packedDepth = texture2D(shadowMap, shadowCoord);
|
|
1224
|
+
float closestDepth = unpackDepth(packedDepth);
|
|
1225
|
+
|
|
1226
|
+
// Check if fragment is in shadow
|
|
1227
|
+
float shadow = currentDepth - bias > closestDepth ? 0.0 : 1.0;
|
|
1228
|
+
|
|
1229
|
+
return shadow;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Simplified PCF for WebGL1 single-face approximation
|
|
1233
|
+
float pointShadowCalculationPCF(vec3 fragPos, vec3 lightPos, sampler2D shadowMap, float farPlane) {
|
|
1234
|
+
// Check if PCF is disabled - fall back to basic shadow calculation
|
|
1235
|
+
if (!uPCFEnabled) {
|
|
1236
|
+
return pointShadowCalculation(fragPos, lightPos, shadowMap, farPlane);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Calculate fragment-to-light vector
|
|
1240
|
+
vec3 fragToLight = fragPos - lightPos;
|
|
1241
|
+
|
|
1242
|
+
// Get current distance from fragment to light
|
|
1243
|
+
float currentDepth = length(fragToLight);
|
|
1244
|
+
|
|
1245
|
+
// Normalize to [0,1] range using far plane
|
|
1246
|
+
currentDepth = currentDepth / farPlane;
|
|
1247
|
+
|
|
1248
|
+
// Simple planar mapping for the single shadow map face
|
|
1249
|
+
vec2 shadowCoord = vec2(
|
|
1250
|
+
(fragToLight.x / abs(fragToLight.x + 0.0001) + 1.0) * 0.25,
|
|
1251
|
+
(fragToLight.y / abs(fragToLight.y + 0.0001) + 1.0) * 0.25
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
// Apply bias
|
|
1255
|
+
float softnessFactor = max(0.1, uShadowSoftness); // Ensure minimum softness
|
|
1256
|
+
float bias = uShadowBias * softnessFactor;
|
|
1257
|
+
|
|
1258
|
+
// Set up PCF sampling
|
|
1259
|
+
float shadow = 0.0;
|
|
1260
|
+
float texelSize = 1.0 / uShadowMapSize;
|
|
1261
|
+
|
|
1262
|
+
// Determine PCF kernel radius based on uPCFSize
|
|
1263
|
+
int pcfRadius = int(uPCFSize) / 2;
|
|
1264
|
+
float totalSamples = 0.0;
|
|
1265
|
+
|
|
1266
|
+
// Limit loop size for WebGL1
|
|
1267
|
+
for(int x = -4; x <= 4; ++x) {
|
|
1268
|
+
for(int y = -4; y <= 4; ++y) {
|
|
1269
|
+
// Skip samples outside the requested kernel radius
|
|
1270
|
+
if (abs(x) <= pcfRadius && abs(y) <= pcfRadius) {
|
|
1271
|
+
// Apply softness factor to sampling coordinates
|
|
1272
|
+
vec2 offset = vec2(x, y) * texelSize * mix(1.0, 2.0, uShadowSoftness);
|
|
1273
|
+
|
|
1274
|
+
vec4 packedDepth = texture2D(shadowMap, shadowCoord + offset);
|
|
1275
|
+
float pcfDepth = unpackDepth(packedDepth);
|
|
1276
|
+
shadow += currentDepth - bias > pcfDepth ? 0.0 : 1.0;
|
|
1277
|
+
totalSamples += 1.0;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Average samples
|
|
1283
|
+
shadow /= max(1.0, totalSamples);
|
|
1284
|
+
|
|
1285
|
+
return shadow;
|
|
1286
|
+
}`;
|
|
1287
|
+
|
|
1288
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
1289
|
+
precision highp float;
|
|
1290
|
+
${isWebGL2 ? "precision mediump sampler2DArray;\n" : ""}
|
|
1291
|
+
|
|
1292
|
+
// Inputs from vertex shader
|
|
1293
|
+
${isWebGL2 ? "in" : "varying"} vec3 vNormal;
|
|
1294
|
+
${isWebGL2 ? "in" : "varying"} vec3 vWorldPos;
|
|
1295
|
+
${isWebGL2 ? "in" : "varying"} vec4 vFragPosLightSpace; // Added for shadow mapping
|
|
1296
|
+
${isWebGL2 ? "in" : "varying"} vec3 vFragPos; // THIS IS IMPORTANT - now we include vFragPos from the vertex shader
|
|
1297
|
+
|
|
1298
|
+
${isWebGL2 ? "in" : "varying"} vec3 vColor;
|
|
1299
|
+
${isWebGL2 ? "in" : "varying"} vec3 vViewDir;
|
|
1300
|
+
${isWebGL2 ? "flat in" : "varying"} float vTextureIndex;
|
|
1301
|
+
${isWebGL2 ? "in" : "varying"} vec2 vTexCoord;
|
|
1302
|
+
${isWebGL2 ? "flat in" : "varying"} float vUseTexture;
|
|
1303
|
+
|
|
1304
|
+
// Material properties - global defaults
|
|
1305
|
+
uniform float uRoughness;
|
|
1306
|
+
uniform float uMetallic;
|
|
1307
|
+
uniform float uBaseReflectivity;
|
|
1308
|
+
|
|
1309
|
+
// Material properties texture (each texel contains roughness, metallic, baseReflectivity)
|
|
1310
|
+
uniform sampler2D uMaterialPropertiesTexture;
|
|
1311
|
+
uniform bool uUsePerTextureMaterials;
|
|
1312
|
+
|
|
1313
|
+
// Light properties
|
|
1314
|
+
uniform vec3 uLightPos; // Used for directional light position
|
|
1315
|
+
uniform vec3 uPointLightPos; // Position for point light #0
|
|
1316
|
+
uniform vec3 uLightDir;
|
|
1317
|
+
uniform vec3 uCameraPos;
|
|
1318
|
+
uniform float uLightIntensity;
|
|
1319
|
+
uniform float uPointLightIntensity; // Separate uniform for point light intensity
|
|
1320
|
+
|
|
1321
|
+
// Shadow mapping
|
|
1322
|
+
uniform sampler2D uShadowMap;
|
|
1323
|
+
${isWebGL2 ? "uniform samplerCube uPointShadowMap;" : "uniform sampler2D uPointShadowMap;"}
|
|
1324
|
+
uniform bool uShadowsEnabled;
|
|
1325
|
+
uniform bool uPointShadowsEnabled; // Enable point light shadows
|
|
1326
|
+
uniform int uPointLightCount; // Number of point lights
|
|
1327
|
+
uniform float uLightRadius; // Point light radius
|
|
1328
|
+
|
|
1329
|
+
// Additional light textures and data
|
|
1330
|
+
uniform sampler2D uDirectionalLightData;
|
|
1331
|
+
uniform vec2 uDirectionalLightTextureSize;
|
|
1332
|
+
uniform sampler2D uPointLightData;
|
|
1333
|
+
uniform vec2 uPointLightTextureSize;
|
|
1334
|
+
uniform int uDirectionalLightCount;
|
|
1335
|
+
|
|
1336
|
+
// Additional point lights
|
|
1337
|
+
${isWebGL2 ? "uniform samplerCube uPointShadowMap1;" : "uniform sampler2D uPointShadowMap1;"}
|
|
1338
|
+
${isWebGL2 ? "uniform samplerCube uPointShadowMap2;" : "uniform sampler2D uPointShadowMap2;"}
|
|
1339
|
+
${isWebGL2 ? "uniform samplerCube uPointShadowMap3;" : "uniform sampler2D uPointShadowMap3;"}
|
|
1340
|
+
uniform bool uPointShadowsEnabled1; // Second point light shadows
|
|
1341
|
+
uniform bool uPointShadowsEnabled2; // Third point light shadows
|
|
1342
|
+
uniform bool uPointShadowsEnabled3; // Fourth point light shadows
|
|
1343
|
+
uniform float uPointLightIntensity1; // Second point light intensity
|
|
1344
|
+
uniform vec3 uPointLightPos1; // Second point light position
|
|
1345
|
+
|
|
1346
|
+
uniform vec3 uPointLightColor1; // Second point light color
|
|
1347
|
+
uniform float uPointLightRadius1; // Second point light radius
|
|
1348
|
+
uniform float uShadowBias; // Shadow bias uniform for controlling shadow acne
|
|
1349
|
+
uniform float uShadowMapSize; // Shadow map size for texture calculations
|
|
1350
|
+
uniform float uShadowSoftness; // Controls shadow edge softness (0-1)
|
|
1351
|
+
uniform int uPCFSize; // Controls PCF kernel size (1, 3, 5, 7, 9)
|
|
1352
|
+
uniform bool uPCFEnabled; // Controls whether PCF filtering is enabled
|
|
1353
|
+
uniform float uFarPlane; // Far plane for point light shadows
|
|
1354
|
+
uniform vec3 uLightColor; // Directional light color
|
|
1355
|
+
uniform vec3 uPointLightColor; // Point light color
|
|
1356
|
+
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
// Texture sampler
|
|
1360
|
+
uniform sampler2DArray uPBRTextureArray;
|
|
1361
|
+
|
|
1362
|
+
${isWebGL2 ? "out vec4 fragColor;" : ""}
|
|
1363
|
+
|
|
1364
|
+
// Constants for performance
|
|
1365
|
+
#define PI 3.14159265359
|
|
1366
|
+
#define RECIPROCAL_PI 0.31830988618
|
|
1367
|
+
|
|
1368
|
+
// Structures to hold light data
|
|
1369
|
+
struct DirectionalLight {
|
|
1370
|
+
vec3 position;
|
|
1371
|
+
vec3 direction;
|
|
1372
|
+
vec3 color;
|
|
1373
|
+
float intensity;
|
|
1374
|
+
bool shadowsEnabled;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
struct PointLight {
|
|
1378
|
+
vec3 position;
|
|
1379
|
+
vec3 color;
|
|
1380
|
+
float intensity;
|
|
1381
|
+
float radius;
|
|
1382
|
+
bool shadowsEnabled;
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
// Functions to extract light data from textures
|
|
1386
|
+
DirectionalLight getDirectionalLight(int index) {
|
|
1387
|
+
// Each light takes 3 pixels horizontally
|
|
1388
|
+
int basePixel = index * 3;
|
|
1389
|
+
|
|
1390
|
+
// Calculate UV coordinates for each pixel
|
|
1391
|
+
// First pixel: position + enabled
|
|
1392
|
+
float u1 = (float(basePixel) + 0.5) / uDirectionalLightTextureSize.x;
|
|
1393
|
+
// Second pixel: direction + shadowEnabled
|
|
1394
|
+
float u2 = (float(basePixel + 1) + 0.5) / uDirectionalLightTextureSize.x;
|
|
1395
|
+
// Third pixel: color + intensity
|
|
1396
|
+
float u3 = (float(basePixel + 2) + 0.5) / uDirectionalLightTextureSize.x;
|
|
1397
|
+
|
|
1398
|
+
// Use centered V coordinate (there's only one row)
|
|
1399
|
+
float v = 0.5 / uDirectionalLightTextureSize.y;
|
|
1400
|
+
|
|
1401
|
+
// Sample pixels from texture
|
|
1402
|
+
vec4 posData = texture(uDirectionalLightData, vec2(u1, v));
|
|
1403
|
+
vec4 dirData = texture(uDirectionalLightData, vec2(u2, v));
|
|
1404
|
+
vec4 colorData = texture(uDirectionalLightData, vec2(u3, v));
|
|
1405
|
+
|
|
1406
|
+
// Create and populate the light structure
|
|
1407
|
+
DirectionalLight light;
|
|
1408
|
+
light.position = posData.xyz;
|
|
1409
|
+
light.direction = dirData.xyz;
|
|
1410
|
+
light.color = colorData.rgb;
|
|
1411
|
+
light.intensity = colorData.a;
|
|
1412
|
+
light.shadowsEnabled = dirData.a > 0.5;
|
|
1413
|
+
|
|
1414
|
+
return light;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
PointLight getPointLight(int index) {
|
|
1418
|
+
// Each light takes 3 pixels horizontally
|
|
1419
|
+
int basePixel = index * 3;
|
|
1420
|
+
|
|
1421
|
+
// Calculate UV coordinates for each pixel
|
|
1422
|
+
// First pixel: position + enabled
|
|
1423
|
+
float u1 = (float(basePixel) + 0.5) / uPointLightTextureSize.x;
|
|
1424
|
+
// Second pixel: color + intensity
|
|
1425
|
+
float u2 = (float(basePixel + 1) + 0.5) / uPointLightTextureSize.x;
|
|
1426
|
+
// Third pixel: radius + shadowEnabled + padding
|
|
1427
|
+
float u3 = (float(basePixel + 2) + 0.5) / uPointLightTextureSize.x;
|
|
1428
|
+
|
|
1429
|
+
// Use centered V coordinate (there's only one row)
|
|
1430
|
+
float v = 0.5 / uPointLightTextureSize.y;
|
|
1431
|
+
|
|
1432
|
+
// Sample pixels from texture
|
|
1433
|
+
vec4 posData = texture(uPointLightData, vec2(u1, v));
|
|
1434
|
+
vec4 colorData = texture(uPointLightData, vec2(u2, v));
|
|
1435
|
+
vec4 radiusData = texture(uPointLightData, vec2(u3, v));
|
|
1436
|
+
|
|
1437
|
+
// Create and populate the light structure
|
|
1438
|
+
PointLight light;
|
|
1439
|
+
light.position = posData.xyz;
|
|
1440
|
+
light.color = colorData.rgb;
|
|
1441
|
+
light.intensity = colorData.a;
|
|
1442
|
+
light.radius = radiusData.r;
|
|
1443
|
+
light.shadowsEnabled = radiusData.g > 0.5;
|
|
1444
|
+
|
|
1445
|
+
return light;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// Shadow mapping functions
|
|
1449
|
+
${shadowFunctions}
|
|
1450
|
+
|
|
1451
|
+
// Optimized PBR function that combines GGX and Fresnel calculations
|
|
1452
|
+
// This is faster than separate function calls
|
|
1453
|
+
vec3 specularBRDF(vec3 N, vec3 L, vec3 V, vec3 F0, float roughness) {
|
|
1454
|
+
|
|
1455
|
+
vec3 H = normalize(V + L);
|
|
1456
|
+
float NdotH = max(dot(N, H), 0.0);
|
|
1457
|
+
float NdotV = max(dot(N, V), 0.0);
|
|
1458
|
+
float NdotL = max(dot(N, L), 0.0);
|
|
1459
|
+
float HdotV = max(dot(H, V), 0.0);
|
|
1460
|
+
|
|
1461
|
+
// Roughness terms
|
|
1462
|
+
float a = roughness * roughness;
|
|
1463
|
+
float a2 = a * a;
|
|
1464
|
+
|
|
1465
|
+
// Distribution
|
|
1466
|
+
float NdotH2 = NdotH * NdotH;
|
|
1467
|
+
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
|
|
1468
|
+
float D = a2 / (PI * denom * denom);
|
|
1469
|
+
|
|
1470
|
+
// Geometry
|
|
1471
|
+
float k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
1472
|
+
float G1_V = NdotV / (NdotV * (1.0 - k) + k);
|
|
1473
|
+
float G1_L = NdotL / (NdotL * (1.0 - k) + k);
|
|
1474
|
+
float G = G1_V * G1_L;
|
|
1475
|
+
|
|
1476
|
+
// Fresnel
|
|
1477
|
+
vec3 F = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
|
|
1478
|
+
|
|
1479
|
+
// Combined specular term
|
|
1480
|
+
return (D * G * F) / (4.0 * NdotV * NdotL + 0.001);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
void main() {
|
|
1484
|
+
vec3 N = normalize(vNormal);
|
|
1485
|
+
vec3 V = normalize(vViewDir);
|
|
1486
|
+
// Negate light direction to be consistent with shadow mapping convention
|
|
1487
|
+
vec3 L = normalize(-uLightDir); // Light direction (negated for consistency)
|
|
1488
|
+
float NdotL = max(dot(N, L), 0.0);
|
|
1489
|
+
|
|
1490
|
+
// Fast path for distance attenuation calculation
|
|
1491
|
+
float distanceToLight = length(vWorldPos - uLightPos);
|
|
1492
|
+
float distanceAttenuation = 1.0 / (1.0 + 0.0001 * distanceToLight * distanceToLight);
|
|
1493
|
+
|
|
1494
|
+
// Efficient texture sampling with conditional
|
|
1495
|
+
vec3 albedo = (vUseTexture > 0.5) ?
|
|
1496
|
+
texture(uPBRTextureArray, vec3(vTexCoord, vTextureIndex)).rgb :
|
|
1497
|
+
vColor;
|
|
1498
|
+
|
|
1499
|
+
// Get material properties based on texture index if using textures
|
|
1500
|
+
float roughness = uRoughness;
|
|
1501
|
+
float metallic = uMetallic;
|
|
1502
|
+
float baseReflectivity = uBaseReflectivity;
|
|
1503
|
+
|
|
1504
|
+
if (uUsePerTextureMaterials && vUseTexture > 0.5) {
|
|
1505
|
+
// Sample material properties from the material texture
|
|
1506
|
+
// Convert texture index to texture coordinates (0-1 range)
|
|
1507
|
+
float textureCoord = (vTextureIndex + 0.5) / float(textureSize(uMaterialPropertiesTexture, 0).x);
|
|
1508
|
+
vec4 materialProps = texture(uMaterialPropertiesTexture, vec2(textureCoord, 0.5));
|
|
1509
|
+
|
|
1510
|
+
// Extract material properties
|
|
1511
|
+
roughness = materialProps.r;
|
|
1512
|
+
metallic = materialProps.g;
|
|
1513
|
+
baseReflectivity = materialProps.b;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Base reflectivity with per-texture material mix
|
|
1517
|
+
vec3 baseF0 = mix(vec3(baseReflectivity), albedo, metallic);
|
|
1518
|
+
|
|
1519
|
+
// Calculate specular using our optimized function with per-texture roughness
|
|
1520
|
+
// Note: L is already negated above for consistency with shadow mapping
|
|
1521
|
+
vec3 specular = specularBRDF(N, L, V, baseF0, roughness);
|
|
1522
|
+
|
|
1523
|
+
// Efficient diffuse calculation with per-texture metallic
|
|
1524
|
+
vec3 kD = (vec3(1.0) - specular) * (1.0 - metallic);
|
|
1525
|
+
|
|
1526
|
+
// Calculate shadow factor if shadows are enabled
|
|
1527
|
+
float shadow = 1.0;
|
|
1528
|
+
if (uShadowsEnabled) {
|
|
1529
|
+
// Use the PCF shadow calculation for soft shadows
|
|
1530
|
+
// The shadow map
|
|
1531
|
+
float shadowFactor = shadowCalculationPCF(vFragPosLightSpace, uShadowMap);
|
|
1532
|
+
// Adjust shadow intensity for PBR - not completely black shadows
|
|
1533
|
+
shadow = 1.0 - (1.0 - shadowFactor) * 0.8;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Calculate directional light contribution (if active)
|
|
1537
|
+
vec3 directionalColor = vec3(0.0);
|
|
1538
|
+
|
|
1539
|
+
// Process all directional lights from the data texture
|
|
1540
|
+
if (uDirectionalLightCount > 0) {
|
|
1541
|
+
for (int i = 0; i < uDirectionalLightCount; i++) {
|
|
1542
|
+
if (i >= 100) break; // Reasonable safety limit
|
|
1543
|
+
|
|
1544
|
+
// Extract light data from texture
|
|
1545
|
+
DirectionalLight light = getDirectionalLight(i);
|
|
1546
|
+
|
|
1547
|
+
// Calculate diffuse component
|
|
1548
|
+
float lightDiffuse = max(0.0, dot(normalize(N), normalize(-light.direction)));
|
|
1549
|
+
|
|
1550
|
+
// Calculate shadow - currently only first light gets shadow mapping
|
|
1551
|
+
float lightShadow = 1.0;
|
|
1552
|
+
if (light.shadowsEnabled && i == 0 && uShadowsEnabled) {
|
|
1553
|
+
lightShadow = shadow;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// Calculate light-specific attenuation
|
|
1557
|
+
float lightDistance = length(vWorldPos - light.position);
|
|
1558
|
+
float lightAttenuation = 1.0 / (1.0 + 0.0001 * lightDistance * lightDistance);
|
|
1559
|
+
|
|
1560
|
+
// Add this light's contribution
|
|
1561
|
+
directionalColor += (kD * albedo * RECIPROCAL_PI + specular) * lightDiffuse *
|
|
1562
|
+
lightShadow * light.intensity * lightAttenuation * light.color;
|
|
1563
|
+
}
|
|
1564
|
+
} else if (uShadowsEnabled) {
|
|
1565
|
+
// Fallback to legacy directional light if no lights in texture
|
|
1566
|
+
// Apply intensity scaling for PBR
|
|
1567
|
+
float scaledLegacyIntensity = uLightIntensity * 0.00001;
|
|
1568
|
+
directionalColor = (kD * albedo * RECIPROCAL_PI + specular) * NdotL * scaledLegacyIntensity * distanceAttenuation * shadow * uLightColor;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Calculate point light contributions
|
|
1572
|
+
vec3 pointLightColors = vec3(0.0);
|
|
1573
|
+
|
|
1574
|
+
// Process all point lights from the data texture
|
|
1575
|
+
for (int i = 0; i < uPointLightCount; i++) {
|
|
1576
|
+
if (i >= 100) break; // Reasonable safety limit
|
|
1577
|
+
|
|
1578
|
+
// Extract light data from texture
|
|
1579
|
+
PointLight light = getPointLight(i);
|
|
1580
|
+
|
|
1581
|
+
// Direction from fragment to point light (we need to negate for consistent lighting)
|
|
1582
|
+
vec3 pointL = normalize(light.position - vWorldPos);
|
|
1583
|
+
float pointNdotL = max(dot(N, pointL), 0.0);
|
|
1584
|
+
|
|
1585
|
+
// Calculate distance attenuation for point light
|
|
1586
|
+
float pointDistance = length(vWorldPos - light.position);
|
|
1587
|
+
float pointAttenuation = 1.0 / (1.0 + (pointDistance * pointDistance) / (light.radius * light.radius));
|
|
1588
|
+
|
|
1589
|
+
// Calculate specular for point light
|
|
1590
|
+
vec3 pointSpecular = specularBRDF(N, pointL, V, baseF0, roughness);
|
|
1591
|
+
vec3 pointKD = (vec3(1.0) - pointSpecular) * (1.0 - metallic);
|
|
1592
|
+
|
|
1593
|
+
// Calculate shadow for point light
|
|
1594
|
+
float pointShadow = 1.0;
|
|
1595
|
+
if (light.shadowsEnabled) {
|
|
1596
|
+
// Handle first 4 shadow maps with a switch statement
|
|
1597
|
+
int lightIdx = i % 4; // Limit to 4 shadowed lights
|
|
1598
|
+
switch(lightIdx) {
|
|
1599
|
+
case 0:
|
|
1600
|
+
if (uPointShadowsEnabled) {
|
|
1601
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap, uFarPlane);
|
|
1602
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1603
|
+
}
|
|
1604
|
+
break;
|
|
1605
|
+
case 1:
|
|
1606
|
+
if (uPointShadowsEnabled1) {
|
|
1607
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap1, uFarPlane);
|
|
1608
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1609
|
+
}
|
|
1610
|
+
break;
|
|
1611
|
+
case 2:
|
|
1612
|
+
if (uPointShadowsEnabled2) {
|
|
1613
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap2, uFarPlane);
|
|
1614
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1615
|
+
}
|
|
1616
|
+
break;
|
|
1617
|
+
case 3:
|
|
1618
|
+
if (uPointShadowsEnabled3) {
|
|
1619
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, light.position, uPointShadowMap3, uFarPlane);
|
|
1620
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1621
|
+
}
|
|
1622
|
+
break;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Calculate point light color with proper PBR contribution
|
|
1627
|
+
pointLightColors += (pointKD * albedo * RECIPROCAL_PI + pointSpecular) * pointNdotL *
|
|
1628
|
+
light.intensity * pointAttenuation * pointShadow * light.color;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// Legacy point light handling - only use if no new lights are available
|
|
1632
|
+
if (uPointLightCount == 0) {
|
|
1633
|
+
// Backward compatibility for first point light
|
|
1634
|
+
vec3 pointL = normalize(uPointLightPos - vWorldPos);
|
|
1635
|
+
float pointNdotL = max(dot(N, pointL), 0.0);
|
|
1636
|
+
|
|
1637
|
+
float pointDistance = length(vWorldPos - uPointLightPos);
|
|
1638
|
+
float pointAttenuation = 1.0 / (1.0 + (pointDistance * pointDistance) / (uLightRadius * uLightRadius));
|
|
1639
|
+
|
|
1640
|
+
vec3 pointSpecular = specularBRDF(N, pointL, V, baseF0, roughness);
|
|
1641
|
+
vec3 pointKD = (vec3(1.0) - pointSpecular) * (1.0 - metallic);
|
|
1642
|
+
|
|
1643
|
+
float pointShadow = 1.0;
|
|
1644
|
+
if (uPointShadowsEnabled) {
|
|
1645
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, uPointLightPos, uPointShadowMap, uFarPlane);
|
|
1646
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// First light contribution
|
|
1650
|
+
pointLightColors += (pointKD * albedo * RECIPROCAL_PI + pointSpecular) * pointNdotL *
|
|
1651
|
+
uPointLightIntensity * pointAttenuation * pointShadow * uPointLightColor;
|
|
1652
|
+
|
|
1653
|
+
// Second point light - only if more than one light is available
|
|
1654
|
+
if (uPointLightCount > 1) {
|
|
1655
|
+
pointL = normalize(uPointLightPos1 - vWorldPos);
|
|
1656
|
+
pointNdotL = max(dot(N, pointL), 0.0);
|
|
1657
|
+
|
|
1658
|
+
pointDistance = length(vWorldPos - uPointLightPos1);
|
|
1659
|
+
pointAttenuation = 1.0 / (1.0 + (pointDistance * pointDistance) / (uPointLightRadius1 * uPointLightRadius1));
|
|
1660
|
+
|
|
1661
|
+
pointSpecular = specularBRDF(N, pointL, V, baseF0, roughness);
|
|
1662
|
+
pointKD = (vec3(1.0) - pointSpecular) * (1.0 - metallic);
|
|
1663
|
+
|
|
1664
|
+
pointShadow = 1.0;
|
|
1665
|
+
if (uPointShadowsEnabled1) {
|
|
1666
|
+
float pointShadowFactor = pointShadowCalculationPCF(vFragPos, uPointLightPos1, uPointShadowMap1, uFarPlane);
|
|
1667
|
+
pointShadow = 1.0 - (1.0 - pointShadowFactor) * 0.8;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Add second light contribution
|
|
1671
|
+
pointLightColors += (pointKD * albedo * RECIPROCAL_PI + pointSpecular) * pointNdotL *
|
|
1672
|
+
uPointLightIntensity1 * pointAttenuation * pointShadow * uPointLightColor1;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// Combine lighting with physically correct blending
|
|
1677
|
+
vec3 color = directionalColor;
|
|
1678
|
+
|
|
1679
|
+
// Blend point light if it exists
|
|
1680
|
+
if (uPointLightCount > 0) {
|
|
1681
|
+
// Direct addition of light contributions is physically accurate
|
|
1682
|
+
color = directionalColor + pointLightColors;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// Add ambient light (pre-computed constant)
|
|
1686
|
+
color += vec3(0.3) * albedo;
|
|
1687
|
+
|
|
1688
|
+
${isWebGL2 ? "fragColor" : "gl_FragColor"} = vec4(color, 1.0);
|
|
1689
|
+
}`;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
//--------------------------------------------------------------------------
|
|
1693
|
+
// VIRTUALBOY SHADER VARIANT
|
|
1694
|
+
//--------------------------------------------------------------------------
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* VirtualBoy vertex shader
|
|
1698
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
1699
|
+
* @returns {string} - Vertex shader source code
|
|
1700
|
+
*/
|
|
1701
|
+
getVirtualBoyVertexShader(isWebGL2) {
|
|
1702
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
1703
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aPosition;
|
|
1704
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aNormal;
|
|
1705
|
+
${isWebGL2 ? "in" : "attribute"} vec3 aColor;
|
|
1706
|
+
|
|
1707
|
+
uniform mat4 uProjectionMatrix;
|
|
1708
|
+
uniform mat4 uViewMatrix;
|
|
1709
|
+
uniform mat4 uModelMatrix;
|
|
1710
|
+
uniform vec3 uLightDir;
|
|
1711
|
+
|
|
1712
|
+
${
|
|
1713
|
+
isWebGL2
|
|
1714
|
+
? "flat out float vLighting;\nout vec3 vBarycentricCoord;"
|
|
1715
|
+
: "varying float vLighting;\nvarying vec3 vBarycentricCoord;"
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
void main() {
|
|
1719
|
+
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
|
|
1720
|
+
vec3 worldNormal = normalize(mat3(uModelMatrix) * aNormal);
|
|
1721
|
+
// Negate light direction to be consistent with other shaders
|
|
1722
|
+
vLighting = max(0.3, min(1.0, dot(worldNormal, normalize(-uLightDir))));
|
|
1723
|
+
|
|
1724
|
+
float id = float(gl_VertexID % 3);
|
|
1725
|
+
vBarycentricCoord = vec3(id == 0.0, id == 1.0, id == 2.0);
|
|
1726
|
+
}`;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* VirtualBoy fragment shader
|
|
1731
|
+
* @param {boolean} isWebGL2 - Whether WebGL2 is being used
|
|
1732
|
+
* @returns {string} - Fragment shader source code
|
|
1733
|
+
*/
|
|
1734
|
+
getVirtualBoyFragmentShader(isWebGL2) {
|
|
1735
|
+
return `${isWebGL2 ? "#version 300 es\n" : ""}
|
|
1736
|
+
precision mediump float;
|
|
1737
|
+
${
|
|
1738
|
+
isWebGL2
|
|
1739
|
+
? "flat in float vLighting;\nin vec3 vBarycentricCoord;\nout vec4 fragColor;"
|
|
1740
|
+
: "varying float vLighting;\nvarying vec3 vBarycentricCoord;"
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
void main() {
|
|
1744
|
+
float edgeWidth = 1.0;
|
|
1745
|
+
vec3 d = fwidth(vBarycentricCoord);
|
|
1746
|
+
vec3 a3 = smoothstep(vec3(0.0), d * edgeWidth, vBarycentricCoord);
|
|
1747
|
+
float edge = min(min(a3.x, a3.y), a3.z);
|
|
1748
|
+
|
|
1749
|
+
if (edge < 0.9) {
|
|
1750
|
+
${isWebGL2 ? "fragColor" : "gl_FragColor"} = vec4(1.0, 0.0, 0.0, 1.0) * vLighting;
|
|
1751
|
+
} else {
|
|
1752
|
+
${isWebGL2 ? "fragColor" : "gl_FragColor"} = vec4(0.0, 0.0, 0.0, 1.0);
|
|
1753
|
+
}
|
|
1754
|
+
}`;
|
|
1755
|
+
}
|
|
1756
|
+
}
|