diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl index 9362c693d..a839a81c2 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl @@ -1,3 +1,9 @@ +// TODO: Add config for light smoothness. Should work at a compile flag level + +/// Get the light at the given world position from the given normal. +/// This may be interpolated for smooth lighting. +bool flw_light(vec3 worldPos, vec3 normal, out vec2 light); + /// Get the light at the given world position. /// This may be interpolated for smooth lighting. bool flw_light(vec3 worldPos, out vec2 light); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl index f79b5a20a..0b80c6714 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -125,3 +125,110 @@ bool flw_light(vec3 worldPos, out vec2 lightCoord) { return true; } +uint _flw_lightIndex(in uvec3 p) { + return p.x + p.z * 3u + p.y * 9u; +} + +/// Premtively collect all light in a 3x3x3 area centered on our block. +/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches. +vec2[27] _flw_lightFetch3x3x3(uint sectionOffset, ivec3 blockInSectionPos) { + vec2[27] lights; + + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + for (int x = -1; x <= 1; x++) { + lights[_flw_lightIndex(uvec3(x + 1, y + 1, z + 1))] = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); + } + } + } + + return lights; +} + +/// Calculate the light for a direction by averaging the light at the corners of the block. +/// +/// To make this reusable across directions, c00..c11 choose what values relative to each corner to use. +/// e.g. (0, 0, 0) (0, 0, 1) (0, 1, 0) (0, 1, 1) would give you the light coming from -x at each corner. +/// In general, to get the light for a particular direction, you fix the x, y, or z coordinate of the c values, and permutate 0 and 1 for the other two. +/// Fixing the x coordinate to 0 gives you the light from -x, 1 gives you the light from +x. +/// +/// @param lights The light data for the 3x3x3 area. +/// @param interpolant The position within the center block. +/// @param c00..c11 4 offsets to determine which "direction" we are averaging. +vec2 _flw_lightForDirection(in vec2[27] lights, in vec3 interpolant, in uvec3 c00, in uvec3 c01, in uvec3 c10, in uvec3 c11) { + + vec2 light000 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 0u))]; + vec2 light001 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 1u))]; + vec2 light010 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 0u))]; + vec2 light011 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 1u))]; + vec2 light100 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 0u))]; + vec2 light101 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 1u))]; + vec2 light110 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 0u))]; + vec2 light111 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 1u))]; + + vec2 light00 = mix(light000, light001, interpolant.z); + vec2 light01 = mix(light010, light011, interpolant.z); + vec2 light10 = mix(light100, light101, interpolant.z); + vec2 light11 = mix(light110, light111, interpolant.z); + + vec2 light0 = mix(light00, light01, interpolant.y); + vec2 light1 = mix(light10, light11, interpolant.y); + + // Divide by 60 (15 * 4) to normalize. + return mix(light0, light1, interpolant.x) / 60.; +} + +bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { + // Always use the section of the block we are contained in to ensure accuracy. + // We don't want to interpolate between sections, but also we might not be able + // to rely on the existence neighboring sections, so don't do any extra rounding here. + ivec3 blockPos = ivec3(floor(worldPos)); + + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { + return false; + } + // The offset of the section in the light buffer. + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + // The block's position in the section adjusted into 18x18x18 space + ivec3 blockInSectionPos = (blockPos & 0xF) + 1; + + // Fetch everything in a 3x3x3 area centered around the block. + vec2[27] lights = _flw_lightFetch3x3x3(sectionOffset, blockInSectionPos); + + vec3 interpolant = fract(worldPos); + + vec2 lightX; + if (normal.x > 0) { + lightX = _flw_lightForDirection(lights, interpolant, uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u)); + } else if (normal.x < 0) { + lightX = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u)); + } else { + lightX = vec2(0.); + } + + vec2 lightZ; + if (normal.z > 0) { + lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 1u), uvec3(0u, 1u, 1u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 1u)); + } else if (normal.z < 0) { + lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 1u, 0u), uvec3(1u, 0u, 0u), uvec3(1u, 1u, 0u)); + } else { + lightZ = vec2(0.); + } + + vec2 lightY; + // Average the light in relevant directions at each corner. + if (normal.y > 0.) { + lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u)); + } else if (normal.y < 0.) { + lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u)); + } else { + lightY = vec2(0.); + } + + lightCoord = (lightX * abs(normal.x) + lightY * abs(normal.y) + lightZ * abs(normal.z)); + + return true; +} + diff --git a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag index 42b1ac1fc..f072c1849 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag +++ b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag @@ -1,7 +1,7 @@ void flw_materialFragment() { #ifdef FLW_EMBEDDED vec2 embeddedLight; - if (flw_light(flw_vertexPos.xyz, embeddedLight)) { + if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, embeddedLight)) { flw_fragLight = max(flw_fragLight, embeddedLight); } #endif