This commit is contained in:
2026-05-13 20:38:59 +03:00
parent 9055c9c93c
commit 5620324b2e
4904 changed files with 391162 additions and 0 deletions
@@ -0,0 +1,7 @@
Sable's sub-levels each contain their own lighting sections, and lighting data.
We need Flywheel shaders to be aware of this, so we change and override the lighting storage, LUT, and shaders to respect an additional "scene ID".
I'm not happy with the large amounts of duplicated shader code in these overrides, but it's the route we are going with for now.
Reference https://github.com/Engine-Room/Flywheel/tree/1.21.1/dev for the original shaders and lighting code that these overrides are based on.
@@ -0,0 +1,31 @@
#include "flywheel:internal/material.glsl"
#include "flywheel:internal/api_impl.glsl"
#include "flywheel:internal/uniforms/uniforms.glsl"
in vec4 flw_vertexPos;
in vec4 flw_vertexColor;
in vec2 flw_vertexTexCoord;
flat in ivec2 flw_vertexOverlay;
in vec2 flw_vertexLight;
in vec3 flw_vertexNormal;
in float flw_distance;
#ifdef FLW_EMBEDDED
flat in float flw_skyLightScale;
flat in uint flw_vertexLightingSceneId;
in vec4 flw_vertexLightingPos;
#endif
vec4 flw_sampleColor;
FlwMaterial flw_material;
bool flw_fragDiffuse;
vec4 flw_fragColor;
ivec2 flw_fragOverlay;
vec2 flw_fragLight;
uniform sampler2D flw_diffuseTex;
uniform sampler2D flw_overlayTex;
uniform sampler2D flw_lightTex;
@@ -0,0 +1,26 @@
struct FlwLightAo {
vec2 light;
float ao;
};
/// Get the light at the given world position relative to flw_renderOrigin from the given normal.
/// This may be interpolated for smooth lighting.
bool flw_light(uint scene, vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light);
/// Fetches the light value at the given block position.
/// Returns false if the light for the given block is not available.
bool flw_lightFetch(uint scene, ivec3 blockPos, out vec2 light);
// Backwards compatible overloads
/// Get the light at the given world position relative to flw_renderOrigin from the given normal.
/// This may be interpolated for smooth lighting.
bool flw_light(vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light) {
return flw_light(0u, worldPos, normal, renderOrigin, light);
}
/// Fetches the light value at the given block position.
/// Returns false if the light for the given block is not available.
bool flw_lightFetch(ivec3 blockPos, out vec2 light) {
return flw_lightFetch(0u, blockPos, light);
}
@@ -0,0 +1,112 @@
#include "flywheel:internal/fog_distance.glsl"
#ifdef _FLW_CRUMBLING
out vec2 _flw_crumblingTexCoord;
const int DOWN = 0;
const int UP = 1;
const int NORTH = 2;
const int SOUTH = 3;
const int WEST = 4;
const int EAST = 5;
// based on net.minecraftforge.client.ForgeHooksClient.getNearestStable
int getNearestFacing(vec3 normal) {
float maxAlignment = -2;
int face = 2;
// Calculate the alignment of the normal vector with each axis.
// Note that `-dot(normal, axis) == dot(normal, -axis)`.
vec3 alignment = vec3(
dot(normal, vec3(1., 0., 0.)),
dot(normal, vec3(0., 1., 0.)),
dot(normal, vec3(0., 0., 1.))
);
if (-alignment.y > maxAlignment) {
maxAlignment = -alignment.y;
face = DOWN;
}
if (alignment.y > maxAlignment) {
maxAlignment = alignment.y;
face = UP;
}
if (-alignment.z > maxAlignment) {
maxAlignment = -alignment.z;
face = NORTH;
}
if (alignment.z > maxAlignment) {
maxAlignment = alignment.z;
face = SOUTH;
}
if (-alignment.x > maxAlignment) {
maxAlignment = -alignment.x;
face = WEST;
}
if (alignment.x > maxAlignment) {
maxAlignment = alignment.x;
face = EAST;
}
return face;
}
vec2 getCrumblingTexCoord() {
switch (getNearestFacing(flw_vertexNormal)) {
case DOWN: return vec2(flw_vertexPos.x, -flw_vertexPos.z);
case UP: return vec2(flw_vertexPos.x, flw_vertexPos.z);
case NORTH: return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
case SOUTH: return vec2(flw_vertexPos.x, -flw_vertexPos.y);
case WEST: return vec2(-flw_vertexPos.z, -flw_vertexPos.y);
case EAST: return vec2(flw_vertexPos.z, -flw_vertexPos.y);
}
// default to north
return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
}
#endif
#ifdef FLW_EMBEDDED
mat4 _flw_modelMatrix;
mat3 _flw_normalMatrix;
uint _flw_lightingSceneId;
float _flw_skyLightScale;
mat4 _flw_lightingSceneMatrix;
flat out uint flw_vertexLightingSceneId;
flat out float flw_skyLightScale;
out vec4 flw_vertexLightingPos;
#endif
#ifdef _FLW_DEBUG
flat out uvec2 _flw_ids;
#endif
void _flw_main(in FlwInstance instance, in uint stableInstanceID, in uint baseVertex) {
flw_vertexId = gl_VertexID - baseVertex;
_flw_layoutVertex();
flw_instanceVertex(instance);
flw_materialVertex();
#ifdef _FLW_CRUMBLING
_flw_crumblingTexCoord = getCrumblingTexCoord();
#endif
#ifdef FLW_EMBEDDED
flw_vertexLightingPos = _flw_lightingSceneMatrix * flw_vertexPos;
flw_vertexPos = _flw_modelMatrix * flw_vertexPos;
flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal;
flw_vertexLightingSceneId = _flw_lightingSceneId;
flw_skyLightScale = _flw_skyLightScale;
#endif
flw_vertexNormal = normalize(flw_vertexNormal);
flw_distance = fogDistance(flw_vertexPos.xyz, flw_cameraPos, flw_fogShape);
gl_Position = flw_viewProjection * flw_vertexPos;
#ifdef _FLW_DEBUG
_flw_ids = uvec2(stableInstanceID, baseVertex);
#endif
}
@@ -0,0 +1,55 @@
#include "flywheel:internal/common.vert"
#include "flywheel:internal/packed_material.glsl"
#include "flywheel:internal/indirect/buffer_bindings.glsl"
#include "flywheel:internal/indirect/draw_command.glsl"
#include "flywheel:internal/indirect/light.glsl"
#include "flywheel:internal/indirect/matrices.glsl"
layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
uint _flw_instanceIndices[];
};
layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer DrawBuffer {
MeshDrawCommand _flw_drawCommands[];
};
#ifdef FLW_EMBEDDED
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer {
Matrices _flw_matrices[];
};
#endif
uniform uint _flw_baseDraw;
flat out uvec2 _flw_packedMaterial;
#if __VERSION__ < 460
#define flw_baseInstance gl_BaseInstanceARB
#define flw_drawId gl_DrawIDARB
#else
#define flw_baseInstance gl_BaseInstance
#define flw_drawId gl_DrawID
#endif
void main() {
uint drawIndex = flw_drawId + _flw_baseDraw;
MeshDrawCommand draw = _flw_drawCommands[drawIndex];
uint packedMaterialProperties = draw.packedMaterialProperties;
_flw_unpackMaterialProperties(packedMaterialProperties, flw_material);
_flw_packedMaterial = uvec2(draw.packedFogAndCutout, packedMaterialProperties);
#ifdef FLW_EMBEDDED
_flw_unpackMatrices(_flw_matrices[draw.matrixIndex], _flw_modelMatrix, _flw_normalMatrix, _flw_lightingSceneId, _flw_skyLightScale, _flw_lightingSceneMatrix);
#endif
#ifdef _FLW_CRUMBLING
uint instanceIndex = flw_baseInstance;
#else
uint instanceIndex = _flw_instanceIndices[flw_baseInstance + gl_InstanceID];
#endif
FlwInstance instance = _flw_unpackInstance(instanceIndex);
_flw_main(instance, instanceIndex, draw.vertexOffset);
}
@@ -0,0 +1,19 @@
struct Matrices {
mat4 pose;
vec4 normalA;
vec4 normalB;
vec4 normalC;
float skyLightScale;
uint sceneID;
float _padding1;
float _padding2;
mat4 lightingSceneMatrix;
};
void _flw_unpackMatrices(in Matrices mats, out mat4 pose, out mat3 normal, out uint lightingSceneId, out float skyLightScale, out mat4 lightingSceneMatrix) {
pose = mats.pose;
normal = mat3(mats.normalA.xyz, mats.normalB.xyz, mats.normalC.xyz);
lightingSceneId = mats.sceneID;
skyLightScale = mats.skyLightScale;
lightingSceneMatrix = mats.lightingSceneMatrix;
}
@@ -0,0 +1,32 @@
#include "flywheel:internal/common.vert"
#include "flywheel:internal/packed_material.glsl"
#include "flywheel:internal/instancing/light.glsl"
uniform uvec2 _flw_packedMaterial;
uniform int _flw_baseInstance = 0;
#ifdef FLW_EMBEDDED
uniform mat4 _flw_modelMatrixUniform;
uniform mat3 _flw_normalMatrixUniform;
uniform uint _flw_lightingSceneUniform;
uniform float _flw_lightingSkyLightScaleUniform;
uniform mat4 _flw_lightingSceneMatrixUniform;
#endif
uniform uint _flw_baseVertex;
void main() {
_flw_unpackMaterialProperties(_flw_packedMaterial.y, flw_material);
FlwInstance instance = _flw_unpackInstance(_flw_baseInstance + gl_InstanceID);
#ifdef FLW_EMBEDDED
_flw_modelMatrix = _flw_modelMatrixUniform;
_flw_normalMatrix = _flw_normalMatrixUniform;
_flw_lightingSceneMatrix = _flw_lightingSceneMatrixUniform;
_flw_lightingSceneId = _flw_lightingSceneUniform;
_flw_skyLightScale = _flw_lightingSkyLightScaleUniform;
#endif
_flw_main(instance, uint(gl_InstanceID), _flw_baseVertex);
}
@@ -0,0 +1,427 @@
const uint _FLW_BLOCKS_PER_SECTION = 18u * 18u * 18u;
const uint _FLW_LIGHT_SIZE_BYTES = _FLW_BLOCKS_PER_SECTION;
const uint _FLW_SOLID_SIZE_BYTES = ((_FLW_BLOCKS_PER_SECTION + 31u) / 32u) * 4u;
const uint _FLW_LIGHT_START_BYTES = _FLW_SOLID_SIZE_BYTES;
const uint _FLW_LIGHT_SECTION_SIZE_BYTES = _FLW_SOLID_SIZE_BYTES + _FLW_LIGHT_SIZE_BYTES;
const uint _FLW_SOLID_START_INTS = 0u;
const uint _FLW_LIGHT_START_INTS = _FLW_SOLID_SIZE_BYTES / 4u;
const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4u;
const uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu;
const float _FLW_EPSILON = 1e-5;
const uint _FLW_LOWER_10_BITS = 0x3FFu;
const uint _FLW_UPPER_10_BITS = 0xFFF00000u;
const float _FLW_LIGHT_NORMALIZER = 1. / 16.;
uint _flw_indexLut(uint index);
uint _flw_indexLight(uint index);
/// Find the index for the next step in the LUT.
/// @param base The base index in the LUT, should point to the start of a coordinate span.
/// @param coord The coordinate to look for.
/// @param next Output. The index of the next step in the LUT.
/// @return true if the coordinate is not in the span.
bool _flw_nextLut(uint base, int coord, out uint next) {
// The base coordinate.
int start = int(_flw_indexLut(base));
// The width of the coordinate span.
uint size = _flw_indexLut(base + 1u);
// Index of the coordinate in the span.
int i = coord - start;
if (i < 0 || i >= int(size)) {
// We missed.
return true;
}
next = _flw_indexLut(base + 2u + uint(i));
return false;
}
bool _flw_chunkCoordToSectionIndex(uint sceneId, ivec3 sectionPos, out uint index) {
uint scene;
if (_flw_nextLut(0u, int(sceneId), scene) || scene == 0u) {
return true;
}
uint first;
if (_flw_nextLut(scene, sectionPos.y, first) || first == 0u) {
return true;
}
uint second;
if (_flw_nextLut(first, sectionPos.x, second) || second == 0u) {
return true;
}
uint sectionIndex;
if (_flw_nextLut(second, sectionPos.z, sectionIndex) || sectionIndex == 0u) {
return true;
}
// The index is written as 1-based so we can properly detect missing sections.
index = sectionIndex - 1u;
return false;
}
uvec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) {
uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u;
uint uintOffset = byteOffset >> 2u;
uint bitOffset = (byteOffset & 3u) << 3;
uint raw = _flw_indexLight(sectionOffset + _FLW_LIGHT_START_INTS + uintOffset);
uint block = (raw >> bitOffset) & 0xFu;
uint sky = (raw >> (bitOffset + 4u)) & 0xFu;
return uvec2(block, sky);
}
bool _flw_isSolid(uint sectionOffset, uvec3 blockInSectionPos) {
uint bitOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u;
uint uintOffset = bitOffset >> 5u;
uint bitInWordOffset = bitOffset & 31u;
uint word = _flw_indexLight(sectionOffset + _FLW_SOLID_START_INTS + uintOffset);
return (word & (1u << bitInWordOffset)) != 0u;
}
bool flw_lightFetch(uint scene, ivec3 blockPos, out vec2 lightCoord) {
uint lightSectionIndex;
if (_flw_chunkCoordToSectionIndex(scene, blockPos >> 4, lightSectionIndex)) {
return false;
}
// The offset of the section in the light buffer.
uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
uvec3 blockInSectionPos = uvec3((blockPos & 0xF) + 1);
lightCoord = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) * _FLW_LIGHT_NORMALIZER;
return true;
}
uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
uint ret = 0u;
// The formatter does NOT like these macros
// @formatter:off
#define _FLW_FETCH_SOLID(x, y, z, i) { \
bool flag = _flw_isSolid(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); \
ret |= uint(flag) << i; \
}
/// fori y, z, x: unrolled
_FLW_FETCH_SOLID(-1, -1, -1, 0)
_FLW_FETCH_SOLID(0, -1, -1, 1)
_FLW_FETCH_SOLID(1, -1, -1, 2)
_FLW_FETCH_SOLID(-1, -1, 0, 3)
_FLW_FETCH_SOLID(0, -1, 0, 4)
_FLW_FETCH_SOLID(1, -1, 0, 5)
_FLW_FETCH_SOLID(-1, -1, 1, 6)
_FLW_FETCH_SOLID(0, -1, 1, 7)
_FLW_FETCH_SOLID(1, -1, 1, 8)
_FLW_FETCH_SOLID(-1, 0, -1, 9)
_FLW_FETCH_SOLID(0, 0, -1, 10)
_FLW_FETCH_SOLID(1, 0, -1, 11)
_FLW_FETCH_SOLID(-1, 0, 0, 12)
_FLW_FETCH_SOLID(0, 0, 0, 13)
_FLW_FETCH_SOLID(1, 0, 0, 14)
_FLW_FETCH_SOLID(-1, 0, 1, 15)
_FLW_FETCH_SOLID(0, 0, 1, 16)
_FLW_FETCH_SOLID(1, 0, 1, 17)
_FLW_FETCH_SOLID(-1, 1, -1, 18)
_FLW_FETCH_SOLID(0, 1, -1, 19)
_FLW_FETCH_SOLID(1, 1, -1, 20)
_FLW_FETCH_SOLID(-1, 1, 0, 21)
_FLW_FETCH_SOLID(0, 1, 0, 22)
_FLW_FETCH_SOLID(1, 1, 0, 23)
_FLW_FETCH_SOLID(-1, 1, 1, 24)
_FLW_FETCH_SOLID(0, 1, 1, 25)
_FLW_FETCH_SOLID(1, 1, 1, 26)
// @formatter:on
return ret;
}
/// 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.
/// Only fetching what we'll actually use using a bitmask turned out significantly slower, but perhaps a less
/// granular approach could see wins.
///
/// The output is a 3-component vector <blockLight, skyLight, valid ? 1 : 0> packed into a single uint to save
/// memory and ALU ops later on. 10 bits are used for each component. This allows 4 such packed ints to be added
/// together with room to spare before overflowing into the next component.
uint[27] _flw_fetchLight3x3x3(uint sectionOffset, ivec3 blockInSectionPos, uint solidMask) {
uint[27] lights;
// @formatter:off
#define _FLW_FETCH_LIGHT(_x, _y, _z, i) { \
uvec2 light = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(_x, _y, _z))); \
lights[i] = (light.x) | ((light.y) << 10) | (uint((solidMask & (1u << i)) == 0u) << 20); \
}
/// fori y, z, x: unrolled
_FLW_FETCH_LIGHT(-1, -1, -1, 0)
_FLW_FETCH_LIGHT(0, -1, -1, 1)
_FLW_FETCH_LIGHT(1, -1, -1, 2)
_FLW_FETCH_LIGHT(-1, -1, 0, 3)
_FLW_FETCH_LIGHT(0, -1, 0, 4)
_FLW_FETCH_LIGHT(1, -1, 0, 5)
_FLW_FETCH_LIGHT(-1, -1, 1, 6)
_FLW_FETCH_LIGHT(0, -1, 1, 7)
_FLW_FETCH_LIGHT(1, -1, 1, 8)
_FLW_FETCH_LIGHT(-1, 0, -1, 9)
_FLW_FETCH_LIGHT(0, 0, -1, 10)
_FLW_FETCH_LIGHT(1, 0, -1, 11)
_FLW_FETCH_LIGHT(-1, 0, 0, 12)
_FLW_FETCH_LIGHT(0, 0, 0, 13)
_FLW_FETCH_LIGHT(1, 0, 0, 14)
_FLW_FETCH_LIGHT(-1, 0, 1, 15)
_FLW_FETCH_LIGHT(0, 0, 1, 16)
_FLW_FETCH_LIGHT(1, 0, 1, 17)
_FLW_FETCH_LIGHT(-1, 1, -1, 18)
_FLW_FETCH_LIGHT(0, 1, -1, 19)
_FLW_FETCH_LIGHT(1, 1, -1, 20)
_FLW_FETCH_LIGHT(-1, 1, 0, 21)
_FLW_FETCH_LIGHT(0, 1, 0, 22)
_FLW_FETCH_LIGHT(1, 1, 0, 23)
_FLW_FETCH_LIGHT(-1, 1, 1, 24)
_FLW_FETCH_LIGHT(0, 1, 1, 25)
_FLW_FETCH_LIGHT(1, 1, 1, 26)
// @formatter:on
return lights;
}
#define _flw_index3x3x3(x, y, z) ((x) + (z) * 3u + (y) * 9u)
#define _flw_validCountToAo(validCount) (1. - (4. - (validCount)) * 0.2)
/// 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.
/// @param oppositeMask A bitmask telling this function which bit to flip to get the opposite index for a given corner
vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uint c00, uint c01, uint c10, uint c11, uint oppositeMask) {
// Sum up the light and number of valid blocks in each corner for this direction
uint[8] summed;
// @formatter:off
#define _FLW_SUM_CORNER(_x, _y, _z, i) { \
const uint corner = _flw_index3x3x3(_x, _y, _z); \
summed[i] = lights[c00 + corner] + lights[c01 + corner] + lights[c10 + corner] + lights[c11 + corner]; \
}
_FLW_SUM_CORNER(0u, 0u, 0u, 0)
_FLW_SUM_CORNER(1u, 0u, 0u, 1)
_FLW_SUM_CORNER(0u, 0u, 1u, 2)
_FLW_SUM_CORNER(1u, 0u, 1u, 3)
_FLW_SUM_CORNER(0u, 1u, 0u, 4)
_FLW_SUM_CORNER(1u, 1u, 0u, 5)
_FLW_SUM_CORNER(0u, 1u, 1u, 6)
_FLW_SUM_CORNER(1u, 1u, 1u, 7)
// @formatter:on
// The final light and number of valid blocks for each corner.
vec3[8] adjusted;
#ifdef _FLW_INNER_FACE_CORRECTION
// If the current corner has no valid blocks, use the opposite
// corner's light based on which direction we're evaluating.
// Because of how our corners are indexed, moving along one axis is the same as flipping a bit.
#define _FLW_CORNER_INDEX(i) ((summed[i] & _FLW_UPPER_10_BITS) == 0u ? i ^ oppositeMask : i)
#else
#define _FLW_CORNER_INDEX(i) i
#endif
// Division and branching (to avoid dividing by zero) are both kinda expensive, so use this table for the valid block normalization
const float[5] normalizers = float[](0., 1., 1. / 2., 1. / 3., 1. / 4.);
// @formatter:off
#define _FLW_ADJUST_CORNER(i) { \
uint corner = summed[_FLW_CORNER_INDEX(i)]; \
uint validCount = corner >> 20u; \
adjusted[i].xy = vec2(corner & _FLW_LOWER_10_BITS, (corner >> 10u) & _FLW_LOWER_10_BITS) * normalizers[validCount]; \
adjusted[i].z = float(validCount); \
}
_FLW_ADJUST_CORNER(0)
_FLW_ADJUST_CORNER(1)
_FLW_ADJUST_CORNER(2)
_FLW_ADJUST_CORNER(3)
_FLW_ADJUST_CORNER(4)
_FLW_ADJUST_CORNER(5)
_FLW_ADJUST_CORNER(6)
_FLW_ADJUST_CORNER(7)
// @formatter:on
// Trilinear interpolation, including valid count
vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.x);
vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.x);
vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.x);
vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.x);
vec3 light0 = mix(light00, light01, interpolant.z);
vec3 light1 = mix(light10, light11, interpolant.z);
vec3 light = mix(light0, light1, interpolant.y);
// Normalize the light coords
light.xy *= _FLW_LIGHT_NORMALIZER;
// Calculate the AO multiplier from the number of valid blocks
light.z = _flw_validCountToAo(light.z);
return light;
}
bool flw_light(uint scene, vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light) {
// 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)) + renderOrigin;
uint lightSectionIndex;
if (_flw_chunkCoordToSectionIndex(scene, 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;
// Directly trilerp as if sampling a texture
#if _FLW_LIGHT_SMOOTHNESS == 1
// The lowest corner of the 2x2x2 area we'll be trilinear interpolating.
// The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are.
uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5));
// The distance our fragment is from the center of the lowest corner.
vec3 interpolant = fract(worldPos - 0.5);
// Fetch everything for trilinear interpolation
// Hypothetically we could re-order these and do some calculations in-between fetches
// to help with latency hiding, but the compiler should be able to do that for us.
vec2 light000 = vec2(_flw_lightAt(sectionOffset, lowestCorner));
vec2 light100 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)));
vec2 light001 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)));
vec2 light101 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)));
vec2 light010 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)));
vec2 light110 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)));
vec2 light011 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)));
vec2 light111 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)));
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);
light.light = mix(light0, light1, interpolant.x) * _FLW_LIGHT_NORMALIZER;
light.ao = 1.;
// Lighting and AO accurate to chunk baking
#elif _FLW_LIGHT_SMOOTHNESS == 2
uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos);
if (solid == _FLW_COMPLETELY_SOLID) {
// No point in doing any work if the entire 3x3x3 volume around us is filled.
// Kinda rare but this may happen if our fragment is in the middle of a lot of tinted glass
light.light = vec2(0.);
light.ao = _flw_validCountToAo(0.);
return true;
}
// Fetch everything in a 3x3x3 area centered around the block.
uint[27] lights = _flw_fetchLight3x3x3(sectionOffset, blockInSectionPos, solid);
vec3 interpolant = fract(worldPos);
// Average the light in relevant directions at each corner, skipping directions that would have no influence
vec3 lightX;
if (normal.x > _FLW_EPSILON) {
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 1u);
} else if (normal.x < -_FLW_EPSILON) {
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), 1u);
} else {
lightX = vec3(0.);
}
vec3 lightZ;
if (normal.z > _FLW_EPSILON) {
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 1u), 2u);
} else if (normal.z < -_FLW_EPSILON) {
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 1u, 0u), 2u);
} else {
lightZ = vec3(0.);
}
vec3 lightY;
if (normal.y > _FLW_EPSILON) {
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 4u);
} else if (normal.y < -_FLW_EPSILON) {
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), 4u);
} else {
lightY = vec3(0.);
}
vec3 n2 = normal * normal;
vec3 lightAo = lightX * n2.x + lightY * n2.y + lightZ * n2.z;
light.light = lightAo.xy;
light.ao = lightAo.z;
// Entirely flat lighting, the lowest setting and a fallback in case an invalid option is set
#else
light.light = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) * _FLW_LIGHT_NORMALIZER;
light.ao = 1.;
#endif
return true;
}
@@ -0,0 +1,28 @@
void flw_shaderLight() {
vec2 embeddedLight;
uint sceneId = 0;
vec4 vertexLightingPos;
ivec3 renderOrigin;
#ifdef FLW_EMBEDDED
renderOrigin = flw_renderOrigin;
sceneId = flw_vertexLightingSceneId;
vertexLightingPos = flw_vertexLightingPos;
if (sceneId != 0) {
renderOrigin = ivec3(0);
}
#else
renderOrigin = flw_renderOrigin;
vertexLightingPos = flw_vertexPos;
#endif
if (flw_lightFetch(sceneId, ivec3(floor(vertexLightingPos.xyz)) + renderOrigin, embeddedLight)) {
flw_fragLight = max(flw_fragLight, embeddedLight);
}
#ifdef FLW_EMBEDDED
flw_fragLight.y *= flw_skyLightScale;
#endif
}
@@ -0,0 +1,31 @@
void flw_shaderLight() {
uint sceneId = 0;
vec4 vertexLightingPos;
ivec3 renderOrigin;
#ifdef FLW_EMBEDDED
renderOrigin = flw_renderOrigin;
sceneId = flw_vertexLightingSceneId;
vertexLightingPos = flw_vertexLightingPos;
if (sceneId != 0) {
renderOrigin = ivec3(0);
}
#else
renderOrigin = flw_renderOrigin;
vertexLightingPos = flw_vertexPos;
#endif
FlwLightAo light;
if (flw_light(sceneId, vertexLightingPos.xyz, flw_vertexNormal, renderOrigin, light)) {
flw_fragLight = max(flw_fragLight, light.light);
if (flw_material.ambientOcclusion) {
flw_fragColor.rgb *= light.ao;
}
}
#ifdef FLW_EMBEDDED
flw_fragLight.y *= flw_skyLightScale;
#endif
}
@@ -0,0 +1,20 @@
void flw_shaderLight() {
#ifdef FLW_EMBEDDED
ivec3 renderOrigin = flw_renderOrigin;
if (flw_vertexLightingSceneId != 0) {
renderOrigin = ivec3(0);
}
FlwLightAo light;
if (flw_light(flw_vertexLightingSceneId, flw_vertexLightingPos.xyz, flw_vertexNormal, renderOrigin, light)) {
flw_fragLight = max(flw_fragLight, light.light);
if (flw_material.ambientOcclusion) {
flw_fragColor.rgb *= light.ao;
}
}
flw_fragLight.y *= flw_skyLightScale;
#endif
}
@@ -0,0 +1,148 @@
{
"menu.savingSubLevels": "Saving sub-levels",
"commands.sable.helper.missing_sub_level_container": "Couldn't find sub-level container for this level!",
"commands.sable.helper.missing_physics_system": "Couldn't find sub-level physics system for this level!",
"commands.sable.sub_level": "sub-level",
"commands.sable.sub_levels": "%s sub-levels",
"commands.sable.physics.global": "global",
"commands.sable.physics.local": "local",
"commands.sable.spawn.success": "Spawned %s",
"commands.sable.spawn.clone.success": "Cloned sublevel",
"commands.sable.physics.impulse.angular.success": "Applied %s angular impulse to %s of %s",
"commands.sable.physics.impulse.linear.success": "Applied %s linear impulse to %s of %s",
"commands.sable.physics.rotation.add.success": "Added %s rotation to %s of %s",
"commands.sable.physics.rotation.set.success": "Set rotation of %s to %s",
"commands.sable.physics.translation.add.success": "Added %s translation to %s of %s",
"commands.sable.physics.translation.set.success": "Set translation of %s to %s",
"commands.sable.joint.missing_sublevel_target": "Failed to find sub-level for joint",
"commands.sable.joint.success": "Successfully created joint",
"commands.sable.sub_level.set_name.success_singular": "Set name of sub-level to %s",
"commands.sable.sub_level.set_name.success_multiple": "Set name of %s sub-levels to %s",
"commands.sable.sub_level.get_name.success": "Name of sub-level is %s",
"commands.sable.sub_level.get_name.failure_unnamed": "Sub-level has no name",
"commands.sable.sub_level.clear_name.success_singular": "Cleared name of sub-level",
"commands.sable.sub_level.clear_name.success_multiple": "Cleared name of %s sub-levels",
"commands.sable.sub_level.teleport_with_orientation.success": "Teleported %s to %.2f, %.2f, %.2f facing %.2f, %.2f",
"commands.sable.sub_level.teleport.success": "Teleported %s to %.2f, %.2f, %.2f",
"commands.sable.sub_level.remove.success": "Removed %s",
"commands.sable.sub_level.assemble.no_blocks": "Couldn't assemble sub-level, no valid blocks found",
"commands.sable.sub_level.assemble.connected.too_many_blocks": "Couldn't assemble sub-level, too many blocks (maximum %s)",
"commands.sable.sub_level.shatter.no_blocks": "Couldn't shatter into sub-levels, no valid blocks found",
"commands.sable.sub_level.shatter.connected.too_many_blocks": "Couldn't shatter into sub-levels, too many blocks (maximum %s)",
"commands.sable.sub_level.shatter.region.success": "Shattered a region with %s blocks into new sub-levels",
"commands.sable.sub_level.shatter.connected.success": "Shattered %s connected blocks into new sub-levels",
"commands.sable.sub_level.shatter.range.success": "Shattered a range with %s blocks into new sub-levels",
"commands.sable.sub_level.shatter.radius.success": "Shattered a radius with %s blocks into new sub-levels",
"commands.sable.sub_level.shatter.sub_level.success": "Shattered %s into %s new sub-levels",
"commands.sable.sub_level.shatter.sub_level.only_single_block": "Can't shatter single-block sub-levels",
"commands.sable.sub_level.assemble.region.success": "Assembled a region %s blocks to a new sub-level",
"commands.sable.sub_level.assemble.connected.success": "Assembled %s connected blocks to a new sub-level",
"commands.sable.sub_level.assemble.radius.success": "Assembled a radius with %s blocks to a new sub-level",
"commands.sable.sub_level.assemble.range.success": "Assembled a range with %s blocks to a new sub-level",
"commands.sable.physics.paused.success": "Set physics paused to be %s",
"commands.sable.physics.paused_toggled.success": "Toggled physics paused to be %s",
"commands.sable.place_schematic.failure": "Couldn't find schematic!",
"commands.sable.place_schematic.success": "Placed schematic!",
"commands.sable.fail.not_inside_sub_level": "Position outside of sub-level plot",
"commands.sable.fail.no_sub_levels": "No sub-levels found",
"commands.sable.fail.unmodified": "No sub-levels were modified",
"commands.sable.fail.no_axis_for_rotation": "No axis for rotation",
"commands.sable.info.count": "Found %s sub-levels:",
"commands.sable.info.name": "%s:",
"commands.sable.info.name.tooltip": "Serialization Pointer: %s",
"commands.sable.info.position": " Position: %.2f %.2f %.2f",
"commands.sable.info.orientation": " Orientation: %.2f %.2f %.2f %.2f",
"commands.sable.info.linear_velocity": " Linear Velocity: %.2f %.2f %.2f",
"commands.sable.info.angular_velocity": " Angular Velocity: %.2f %.2f %.2f",
"commands.sable.info.mass": " Mass: %.2f",
"commands.sable.info.world_bounds": " World Bounds: %.2f x %.2f x %.2f",
"commands.data.sub_level.get": "%s on sub-level %s after scale factor of %s is %s",
"commands.data.sub_level.modified": "Modified sub-level auxiliary data of %s",
"commands.data.sub_level.query": "%s has the following auxiliary sub-level data: %s",
"argument.sable.body.selector.all": "All sub-levels",
"argument.sable.body.selector.nearest": "Nearest sub-level",
"argument.sable.body.selector.random": "Random sub-level",
"argument.sable.body.selector.viewed": "Viewed sub-level",
"argument.sable.body.selector.latest": "Latest sub-level",
"argument.sable.body.selector.tracking": "Tracking sub-level",
"argument.sable.body.selector.inside": "Inside sub-level",
"argument.sable.body.static_world": "The static world",
"argument.sable.sub_level.modifier.distance": "Distance to sub-level",
"argument.sable.sub_level.modifier.x": "x position",
"argument.sable.sub_level.modifier.y": "y position",
"argument.sable.sub_level.modifier.z": "z position",
"argument.sable.sub_level.modifier.dx": "Sub-levels between x and x + dx",
"argument.sable.sub_level.modifier.dy": "Sub-levels between y and y + dy",
"argument.sable.sub_level.modifier.dz": "Sub-levels between z and z + dz",
"argument.sable.sub_level.modifier.vx": "x velocity",
"argument.sable.sub_level.modifier.vy": "y velocity",
"argument.sable.sub_level.modifier.vz": "z velocity",
"argument.sable.sub_level.modifier.speed": "Sub-level speed",
"argument.sable.sub_level.modifier.mass": "Sub-level weight",
"argument.sable.sub_level.modifier.volume": "Volume of sub-level bounding box",
"argument.sable.sub_level.modifier.width": "X axis size of sub-level bounding box",
"argument.sable.sub_level.modifier.height": "Y axis size of sub-level bounding box",
"argument.sable.sub_level.modifier.length": "Z axis size of sub-level bounding box",
"argument.sable.sub_level.modifier.limit": "Maximum number of sub-levels to return",
"argument.sable.sub_level.modifier.name": "Sub-level name",
"argument.sable.sub_level.modifier.sort": "Sort the sub-levels by distance",
"argument.sable.unexpected_end_of_input": "Unexpected end of input",
"argument.sable.single_sub_level_required": "Only one sub-level is allowed, but the provided selector allows more than one",
"argument.sable.sub_level.invalid": "Invalid sub-level selector",
"argument.sable.sub_level.expected_end_of_modifier": "Expected end of modifier",
"argument.sable.sub_level.expected_positive_integer": "Expected a positive integer",
"argument.sable.sub_level.expected_positive_decimal": "Expected a positive decimal",
"argument.sable.sub_level.expected_positive_range": "Expected a positive range",
"argument.sable.sub_level.expected_sorting": "Expected sorting type of either nearest or furthest",
"inspector.sable.sub_level_container.title": "Sub-level Container",
"attribute.name.player.sub_level_punch_strength": "Push Strength",
"attribute.name.player.sub_level_punch_cooldown": "Push Cooldown",
"options.sable_menu": "Sub-Level Settings... ",
"options.physics_steps": "Physics Steps",
"options.physics_steps_template": "%s steps / second",
"options.physics_steps.tooltip": "How many times the physics simulation is stepped in every second. Higher values will be significantly more performance intensive, but will have higher accuracy.",
"sub_level.toast.checkLog": "See log for more details",
"sub_level.toast.loadFailure": "Failed to load sub-level at %s",
"sub_level.toast.saveFailure": "Failed to save sub-level at %s",
"sub_level.toast.physicsFailure": "Physics failure for sub-level at %s",
"sub_level.toast.attemptingRecovery": "Attempting recovery",
"camera_type.sub_level_view": "Entering Contraption Camera",
"camera_type.sub_level_view_unlocked": "Entering Unlocked Contraption Camera",
"force_group.sable.gravity": "Gravity",
"force_group.sable.drag": "Drag",
"force_group.sable.levitation": "Levitation",
"force_group.sable.balloon_lift": "Balloon Lift",
"force_group.sable.propulsion": "Propulsion",
"force_group.sable.lift": "Lift",
"force_group.sable.magnetic_force": "Magnetic",
"schematic.sable.mirror_not_supported": "Cannot mirror schematics containing sub-levels!",
"death.attack.fall.from_sublevel": "%1$s fell from %2$s",
"sable.create.mechanical_arm.points_removed_sublevel_and_range": "%1$s selected interaction point(s) removed due to range limitations or not being anchored.",
"sable.create.remove.points_removed_sublevel" : "%1$s selected interaction point(s) removed due to not being anchored"
}
@@ -0,0 +1,61 @@
layout(location = 0) in vec3 QuadPosition;
layout(location = 1) in vec3 SableNormal;
layout(location = 2) in uvec2 SableData;
layout(std140) uniform SableSprites {
vec4 sableSprites[2 * SABLE_TEXTURE_CACHE_SIZE];
};
uniform mat4 SableTransform;
vec3 Position;
vec3 Normal;
vec4 Color;
vec2 UV0;
ivec2 UV2;
void _sable_unpack() {
uint vertexIndex = uint(gl_VertexID) & 0x3u;
// Packed data format:
// TTTTTTTTTTTTLLLLLLLLZZZZYYYYXXXX
// T = Texture ID
// L = Packed Light
// Z = Relative Z position
// Y = Relative Y position
// X = Relative X position
uint posX = SableData.x & 15u;
uint posY = (SableData.x >> 4) & 15u;
uint posZ = (SableData.x >> 8) & 15u;
uint packedLight = (SableData.x >> 12) & 255u;
uint textureId = SableData.x >> 20u;
// Packed data format:
// AAAAAAAAYYYYYYYYZZZZZZZZXXXXXXXX
// A = Ambient Occlusion
// Y = Section Y
// Z = Section Z
// X = Section X
uint xOffset = (SableData.y) & 0xFFu;
uint yOffset = (SableData.y >> 8) & 0xFFu;
uint zOffset = (SableData.y >> 16) & 0xFFu;
uint ambientOcclusion = (SableData.y >> (24u + (vertexIndex << 1u))) & 0x3u;
// 0,0 == 0b00
// 0,1 == 0b01
// 1,1 == 0b10
// 1,0 == 0b11
uint lower = uint(gl_VertexID) & 1u;
uint upper = (uint(gl_VertexID) >> 1) & 1u;
vec2 uv = vec2(float(upper), float(lower ^ upper));
uint textureOffset = vertexIndex << 3u;
vec4 textureU = sableSprites[(textureId << 1u)];
vec4 textureV = sableSprites[(textureId << 1u) + 1u];
Position = (SableTransform * vec4(QuadPosition + vec3(float((xOffset << 4u) + posX), float((yOffset << 4u) + posY), float((zOffset << 4u) + posZ)), 1.0)).xyz;
Normal = (SableTransform * vec4(SableNormal, 0.0)).xyz;
Color = vec4(1.0, 1.0, 1.0, 1.0) * vec4(vec3(1.0 - 0.2 * float(ambientOcclusion)), 1.0);
UV0 = vec2(textureU[vertexIndex], textureV[vertexIndex]);
UV2 = ivec2(packedLight & 0xF0u, (packedLight << 4) & 0xF0u);
}