mirror of
https://github.com/FabricMC/fabric.git
synced 2025-02-16 19:59:56 -05:00
Merge pull request #228 from grondag/master
Improve lighting of exterior vertices
This commit is contained in:
commit
b882f0e925
5 changed files with 103 additions and 79 deletions
|
@ -35,6 +35,11 @@ import java.util.Properties;
|
|||
public class Indigo implements ClientModInitializer {
|
||||
public static final boolean ALWAYS_TESSELATE_INDIGO;
|
||||
public static final AoConfig AMBIENT_OCCLUSION_MODE;
|
||||
/** Set true in dev env to confirm results match vanilla when they should */
|
||||
public static final boolean DEBUG_COMPARE_LIGHTING;
|
||||
public static final boolean FIX_SMOOTH_LIGHTING_OFFSET;
|
||||
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static boolean asBoolean(String property, boolean defValue) {
|
||||
|
@ -99,6 +104,9 @@ public class Indigo implements ClientModInitializer {
|
|||
|
||||
ALWAYS_TESSELATE_INDIGO = asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
|
||||
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "enhanced"), AoConfig.ENHANCED);
|
||||
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false);
|
||||
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
|
||||
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
|
||||
|
||||
try (FileOutputStream stream = new FileOutputStream(configFile)) {
|
||||
properties.store(stream, "Indigo properties file");
|
||||
|
|
|
@ -74,8 +74,6 @@ public class AoCalculator {
|
|||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
// TODO: make this actually configurable?
|
||||
private static final boolean fixSmoothLighting = true;
|
||||
|
||||
private final VanillaAoCalc vanillaCalc;
|
||||
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
|
||||
|
@ -112,12 +110,8 @@ public class AoCalculator {
|
|||
completionFlags = 0;
|
||||
}
|
||||
|
||||
/** Set true in dev env to confirm results match vanilla when they should */
|
||||
private static final boolean DEBUG = Boolean.valueOf(System.getProperty("fabric.debugAoLighting", "false"));
|
||||
|
||||
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
|
||||
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
|
||||
|
||||
boolean shouldMatch = false;
|
||||
|
||||
switch(config) {
|
||||
|
@ -127,7 +121,7 @@ public class AoCalculator {
|
|||
|
||||
case EMULATE:
|
||||
calcFastVanilla(quad);
|
||||
shouldMatch = DEBUG && isVanilla;
|
||||
shouldMatch = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
|
||||
break;
|
||||
|
||||
case HYBRID:
|
||||
|
@ -164,7 +158,7 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void calcVanilla(MutableQuadViewImpl quad) {
|
||||
vanillaCalc.compute(blockInfo, quad, ao, light);
|
||||
}
|
||||
|
@ -173,28 +167,15 @@ public class AoCalculator {
|
|||
int flags = quad.geometryFlags();
|
||||
|
||||
// force to block face if shape is full cube - matches vanilla logic
|
||||
if(((flags & LIGHT_FACE_FLAG) == 0) && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
|
||||
if((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
|
||||
flags |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
|
||||
switch(flags) {
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaFullFace(quad, true);
|
||||
break;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaPartialFace(quad, true);
|
||||
break;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
|
||||
vanillaFullFace(quad, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
case AXIS_ALIGNED_FLAG:
|
||||
vanillaPartialFace(quad, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if((flags & CUBIC_FLAG) == 0) {
|
||||
vanillaPartialFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
|
||||
} else {
|
||||
vanillaFullFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** returns true if should match vanilla results */
|
||||
|
@ -202,11 +183,11 @@ public class AoCalculator {
|
|||
switch(quad.geometryFlags()) {
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaFullFace(quad, true);
|
||||
return DEBUG;
|
||||
return Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaPartialFace(quad, true);
|
||||
return DEBUG;
|
||||
return Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
|
||||
blendedFullFace(quad);
|
||||
|
@ -222,12 +203,12 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
private void vanillaFullFace(MutableQuadViewImpl quad, boolean isOnLightFace) {
|
||||
private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
computeFace(lightFace, isOnLightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
|
||||
}
|
||||
|
||||
private void vanillaPartialFace(MutableQuadViewImpl quad, boolean isOnLightFace) {
|
||||
private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = computeFace(lightFace, isOnLightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
|
@ -239,7 +220,7 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
/** used in {@link #blendedInsetFace(VertexEditorImpl, Direction)} as return variable to avoid new allocation */
|
||||
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation */
|
||||
AoFaceData tmpFace = new AoFaceData();
|
||||
|
||||
/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face */
|
||||
|
@ -250,7 +231,7 @@ public class AoCalculator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Like {@link #blendedInsetFace(VertexEditorImpl, Direction)} but optimizes if depth is 0 or 1.
|
||||
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
|
||||
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
|
||||
*/
|
||||
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
|
||||
|
@ -265,12 +246,12 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
private void blendedFullFace(MutableQuadViewImpl quad) {
|
||||
private void blendedFullFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
blendedInsetFace(quad, 0, lightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
|
||||
}
|
||||
|
||||
private void blendedPartialFace(MutableQuadViewImpl quad) {
|
||||
private void blendedPartialFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
|
||||
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
|
||||
|
@ -383,16 +364,16 @@ public class AoCalculator {
|
|||
// vanilla was further offsetting these in the direction of the light face
|
||||
// but it was actually mis-sampling and causing visible artifacts in certain situation
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);//.setOffset(lightFace);
|
||||
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear0 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);//.setOffset(lightFace);
|
||||
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear1 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);//.setOffset(lightFace);
|
||||
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear2 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
|
||||
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);//.setOffset(lightFace);
|
||||
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
|
||||
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
|
||||
final boolean isClear3 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
|
||||
|
||||
// c = corner - values at corners of face
|
||||
|
@ -469,7 +450,7 @@ public class AoCalculator {
|
|||
* value from all four samples.
|
||||
*/
|
||||
private static int meanBrightness(int a, int b, int c, int d) {
|
||||
if(fixSmoothLighting) {
|
||||
if(Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
|
||||
return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d);
|
||||
} else {
|
||||
return vanillaMeanBrightness(a, b, c, d);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package net.fabricmc.indigo.renderer.aocalc;
|
||||
|
||||
import static net.minecraft.util.math.Direction.*;
|
||||
|
||||
import static net.fabricmc.indigo.renderer.aocalc.AoVertexClampFunction.CLAMP_FUNC;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.util.SystemUtil;
|
||||
|
@ -30,55 +30,55 @@ import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
|||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
enum AoFace {
|
||||
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> q.y(i),
|
||||
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.y(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.x(i);
|
||||
final float v = q.z(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1-u) * v;
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = u * (1-v);
|
||||
w[3] = u * v;
|
||||
}),
|
||||
AOF_UP(new Direction[]{EAST, WEST, NORTH, SOUTH}, (q, i) -> 1 - q.y(i),
|
||||
AOF_UP(new Direction[]{EAST, WEST, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.y(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.x(i);
|
||||
final float v = q.z(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.x(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1-v);
|
||||
w[2] = (1-u) * (1-v);
|
||||
w[3] = (1-u) * v;
|
||||
}),
|
||||
AOF_NORTH(new Direction[]{UP, DOWN, EAST, WEST}, (q, i) -> q.z(i),
|
||||
AOF_NORTH(new Direction[]{UP, DOWN, EAST, WEST}, (q, i) -> CLAMP_FUNC.clamp(q.z(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.y(i);
|
||||
final float v = q.x(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1-v);
|
||||
w[1] = u * v;
|
||||
w[2] = (1-u) * v;
|
||||
w[3] = (1-u) * (1-v);
|
||||
}),
|
||||
AOF_SOUTH(new Direction[]{WEST, EAST, DOWN, UP}, (q, i) -> 1 - q.z(i),
|
||||
AOF_SOUTH(new Direction[]{WEST, EAST, DOWN, UP}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.z(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.y(i);
|
||||
final float v = q.x(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.x(i));
|
||||
w[0] = u * (1-v);
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = (1-u) * v;
|
||||
w[3] = u * v;
|
||||
}),
|
||||
AOF_WEST(new Direction[]{UP, DOWN, NORTH, SOUTH}, (q, i) -> q.x(i),
|
||||
AOF_WEST(new Direction[]{UP, DOWN, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.x(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.y(i);
|
||||
final float v = q.z(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = u * v;
|
||||
w[1] = u * (1-v);
|
||||
w[2] = (1-u) * (1-v);
|
||||
w[3] = (1-u) * v;
|
||||
}),
|
||||
AOF_EAST(new Direction[]{DOWN, UP, NORTH, SOUTH}, (q, i) -> 1 - q.x(i),
|
||||
AOF_EAST(new Direction[]{DOWN, UP, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.x(i)),
|
||||
(q, i, w) -> {
|
||||
final float u = q.y(i);
|
||||
final float v = q.z(i);
|
||||
final float u = CLAMP_FUNC.clamp(q.y(i));
|
||||
final float v = CLAMP_FUNC.clamp(q.z(i));
|
||||
w[0] = (1-u) * v;
|
||||
w[1] = (1-u) * (1-v);
|
||||
w[2] = u * (1-v);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.aocalc;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@FunctionalInterface
|
||||
interface AoVertexClampFunction {
|
||||
float clamp(float x);
|
||||
|
||||
AoVertexClampFunction CLAMP_FUNC = Indigo.FIX_EXTERIOR_VERTEX_LIGHTING ? x -> x < 0f ? 0f : (x > 1f ? 1f : x) : x -> x;
|
||||
}
|
|
@ -39,7 +39,10 @@ public abstract class GeometryHelper {
|
|||
|
||||
/** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */
|
||||
public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1;
|
||||
|
||||
|
||||
private static final float EPS_MIN = 0.0001f;
|
||||
private static final float EPS_MAX = 1.0f - EPS_MIN;
|
||||
|
||||
private GeometryHelper() {}
|
||||
|
||||
/**
|
||||
|
@ -56,10 +59,10 @@ public abstract class GeometryHelper {
|
|||
if(isParallelQuadOnFace(lightFace, quad)) {
|
||||
bits |= LIGHT_FACE_FLAG;
|
||||
}
|
||||
if(isQuadCubic(lightFace, quad)) {
|
||||
bits |= CUBIC_FLAG;
|
||||
}
|
||||
}
|
||||
if(isQuadCubic(lightFace, quad)) {
|
||||
bits |= CUBIC_FLAG;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
|
@ -80,17 +83,17 @@ public abstract class GeometryHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* True if quad - already known to be parallel to a face - is actually coplanar with it.<p>
|
||||
* True if quad - already known to be parallel to a face - is actually coplanar with it.
|
||||
* For compatibility with vanilla resource packs, also true if quad is outside the face.<p>
|
||||
*
|
||||
* Test will be unreliable if not already parallel, use {@link #isQuadParallel(Direction, QuadView)}
|
||||
* Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)}
|
||||
* for that purpose. Expects convex quads with all points co-planar.<p>
|
||||
*/
|
||||
public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) {
|
||||
if(lightFace == null)
|
||||
return false;
|
||||
final int coordinateIndex = lightFace.getAxis().ordinal();
|
||||
final float expectedValue = lightFace.getDirection() == AxisDirection.POSITIVE ? 1 : 0;
|
||||
return equalsApproximate(quad.posByIndex(0, coordinateIndex), expectedValue);
|
||||
final float x = quad.posByIndex(0, lightFace.getAxis().ordinal());
|
||||
return lightFace.getDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +104,7 @@ public abstract class GeometryHelper {
|
|||
* quad vertices are coplanar with each other. <p>
|
||||
*
|
||||
* Expects convex quads with all points co-planar.<p>
|
||||
*
|
||||
*
|
||||
* @param lightFace MUST be non-null.
|
||||
*/
|
||||
public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
|
||||
|
@ -134,10 +137,13 @@ public abstract class GeometryHelper {
|
|||
|
||||
return confirmSquareCorners(a, b, quad);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used by {@link #isQuadCubic(Direction, int[], int, QuadSerializer)}.
|
||||
* True if quad touches all four corners of unit square.
|
||||
* Used by {@link #isQuadCubic(Direction, QuadView)}.
|
||||
* True if quad touches all four corners of unit square.<p>
|
||||
*
|
||||
* For compatibility with resource packs that contain models with quads exceeding
|
||||
* block boundaries, considers corners outside the block to be at the corners.
|
||||
*/
|
||||
private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) {
|
||||
int flags = 0;
|
||||
|
@ -146,18 +152,18 @@ public abstract class GeometryHelper {
|
|||
final float a = quad.posByIndex(i, aCoordinate);
|
||||
final float b = quad.posByIndex(i, bCoordinate);
|
||||
|
||||
if(equalsApproximate(a, 0)) {
|
||||
if(equalsApproximate(b, 0)) {
|
||||
if(a <= EPS_MIN) {
|
||||
if(b <= EPS_MIN) {
|
||||
flags |= 1;
|
||||
} else if(equalsApproximate(b, 1)) {
|
||||
} else if(b >= EPS_MAX) {
|
||||
flags |= 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if(equalsApproximate(a, 1)) {
|
||||
if(equalsApproximate(b, 0)) {
|
||||
} else if(a >= EPS_MAX) {
|
||||
if(b <= EPS_MIN) {
|
||||
flags |= 4;
|
||||
} else if(equalsApproximate(b, 1)) {
|
||||
} else if(b >= EPS_MAX) {
|
||||
flags |= 8;
|
||||
} else {
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue