mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-10 12:34:11 +01:00
Throwing shade
- Add support for entity shadows. - Create instance type specifically for shadows. - Add ShadowComponent utility which emulates what EntityRenderDispatcher does, but based on top of our instance system. - Add some missing nullability annotations to MinecartVisual. - Create InstanceRecycler utility for managing dynamic numbers of instances per frame.
This commit is contained in:
parent
fb95556623
commit
a04c3f52b2
@ -66,6 +66,29 @@ public final class InstanceTypes {
|
||||
.cullShader(Flywheel.rl("instance/cull/oriented.glsl"))
|
||||
.register();
|
||||
|
||||
public static final InstanceType<ShadowInstance> SHADOW = SimpleInstanceType.builder(ShadowInstance::new)
|
||||
.layout(LayoutBuilder.create()
|
||||
.vector("pos", FloatRepr.FLOAT, 3)
|
||||
.vector("entityPosXZ", FloatRepr.FLOAT, 2)
|
||||
.vector("size", FloatRepr.FLOAT, 2)
|
||||
.scalar("alpha", FloatRepr.FLOAT)
|
||||
.scalar("radius", FloatRepr.FLOAT)
|
||||
.build())
|
||||
.writer((ptr, instance) -> {
|
||||
MemoryUtil.memPutFloat(ptr, instance.x);
|
||||
MemoryUtil.memPutFloat(ptr + 4, instance.y);
|
||||
MemoryUtil.memPutFloat(ptr + 8, instance.z);
|
||||
MemoryUtil.memPutFloat(ptr + 12, instance.entityX);
|
||||
MemoryUtil.memPutFloat(ptr + 16, instance.entityZ);
|
||||
MemoryUtil.memPutFloat(ptr + 20, instance.sizeX);
|
||||
MemoryUtil.memPutFloat(ptr + 24, instance.sizeZ);
|
||||
MemoryUtil.memPutFloat(ptr + 28, instance.alpha);
|
||||
MemoryUtil.memPutFloat(ptr + 32, instance.radius);
|
||||
})
|
||||
.vertexShader(Flywheel.rl("instance/shadow.vert"))
|
||||
.cullShader(Flywheel.rl("instance/cull/shadow.glsl"))
|
||||
.register();
|
||||
|
||||
private InstanceTypes() {
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.jozufozu.flywheel.lib.instance;
|
||||
|
||||
import com.jozufozu.flywheel.api.instance.InstanceHandle;
|
||||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
||||
|
||||
public class ShadowInstance extends AbstractInstance {
|
||||
|
||||
public float x, y, z;
|
||||
public float entityX, entityZ;
|
||||
public float sizeX, sizeZ;
|
||||
public float alpha;
|
||||
public float radius;
|
||||
|
||||
protected ShadowInstance(InstanceType<?> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
}
|
@ -33,11 +33,13 @@ import net.minecraft.world.phys.Vec3;
|
||||
public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVisual implements EntityVisual<T> {
|
||||
protected final T entity;
|
||||
protected final EntityVisibilityTester visibilityTester;
|
||||
protected final ShadowComponent shadow;
|
||||
|
||||
public AbstractEntityVisual(VisualizationContext ctx, T entity) {
|
||||
super(ctx, entity.level());
|
||||
this.entity = entity;
|
||||
visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin(), 1.5f);
|
||||
shadow = new ShadowComponent(ctx, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,4 +89,9 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
|
||||
public boolean isVisible(FrustumIntersection frustum) {
|
||||
return entity.noCulling || visibilityTester.check(frustum);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _delete() {
|
||||
shadow.delete();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package com.jozufozu.flywheel.lib.visual;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.jozufozu.flywheel.api.instance.Instance;
|
||||
|
||||
/**
|
||||
* A utility for recycling instances.
|
||||
* <br>
|
||||
* If the exact number of instances you need each frame is unknown, you can use this to manage
|
||||
* a pool of instances that will be dynamically created, deleted, or re-used as necessary.
|
||||
*
|
||||
* @param <I> The type of instance to recycle.
|
||||
*/
|
||||
public class InstanceRecycler<I extends Instance> {
|
||||
|
||||
private final Supplier<I> factory;
|
||||
|
||||
private final List<I> instances = new ArrayList<>();
|
||||
|
||||
private int count;
|
||||
|
||||
public InstanceRecycler(Supplier<I> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the count of instances.
|
||||
* <br>
|
||||
* Call this at before your first call to {@link #get()} each frame.
|
||||
*/
|
||||
public void resetCount() {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next instance in the pool, creating a new one if necessary.
|
||||
* <br>
|
||||
* The returned instance may not be in its default state.
|
||||
*
|
||||
* @return The next instance in the pool.
|
||||
*/
|
||||
public I get() {
|
||||
var lastCount = count++;
|
||||
if (lastCount < instances.size()) {
|
||||
return instances.get(lastCount);
|
||||
} else {
|
||||
var out = factory.get();
|
||||
instances.add(out);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any instances that were not used this frame.
|
||||
* <br>
|
||||
* Call this after your last call to {@link #get()} each frame.
|
||||
*/
|
||||
public void discardExtra() {
|
||||
for (int i = count; i < instances.size(); i++) {
|
||||
instances.get(i)
|
||||
.delete();
|
||||
}
|
||||
instances.subList(count, instances.size())
|
||||
.clear();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
instances.forEach(Instance::delete);
|
||||
instances.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
package com.jozufozu.flywheel.lib.visual;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector4fc;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
import com.jozufozu.flywheel.api.material.Transparency;
|
||||
import com.jozufozu.flywheel.api.material.WriteMask;
|
||||
import com.jozufozu.flywheel.api.model.Mesh;
|
||||
import com.jozufozu.flywheel.api.model.Model;
|
||||
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
|
||||
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
|
||||
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
|
||||
import com.jozufozu.flywheel.lib.instance.ShadowInstance;
|
||||
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
|
||||
import com.jozufozu.flywheel.lib.model.QuadMesh;
|
||||
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public class ShadowComponent {
|
||||
|
||||
private final VisualizationContext context;
|
||||
private final LevelReader level;
|
||||
private final Entity entity;
|
||||
private final InstanceRecycler<ShadowInstance> instances = new InstanceRecycler<>(this::instance);
|
||||
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
|
||||
private float radius = 0.5F;
|
||||
private float strength = 1.0F;
|
||||
private boolean enabled = true;
|
||||
|
||||
public ShadowComponent(VisualizationContext context, Entity entity) {
|
||||
this.context = context;
|
||||
this.level = entity.level();
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public void radius(float radius) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public void strength(float weight) {
|
||||
this.strength = weight;
|
||||
}
|
||||
|
||||
public void enabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
|
||||
if (!enabled) {
|
||||
instances.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void beginFrame(VisualFrameContext context) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
instances.resetCount();
|
||||
|
||||
double entityX = Mth.lerp(context.partialTick(), entity.xOld, entity.getX());
|
||||
double entityY = Mth.lerp(context.partialTick(), entity.yOld, entity.getY());
|
||||
double entityZ = Mth.lerp(context.partialTick(), entity.zOld, entity.getZ());
|
||||
float castDistance = Math.min(strength / 0.5F, radius);
|
||||
int minXPos = Mth.floor(entityX - (double) radius);
|
||||
int maxXPos = Mth.floor(entityX + (double) radius);
|
||||
int minYPos = Mth.floor(entityY - (double) castDistance);
|
||||
int maxYPos = Mth.floor(entityY);
|
||||
int minZPos = Mth.floor(entityZ - (double) radius);
|
||||
int maxZPos = Mth.floor(entityZ + (double) radius);
|
||||
|
||||
for (int z = minZPos; z <= maxZPos; ++z) {
|
||||
for (int x = minXPos; x <= maxXPos; ++x) {
|
||||
pos.set(x, 0, z);
|
||||
ChunkAccess chunkaccess = level.getChunk(pos);
|
||||
|
||||
for (int y = minYPos; y <= maxYPos; ++y) {
|
||||
pos.setY(y);
|
||||
float actualWeight = strength - (float) (entityY - pos.getY()) * 0.5F;
|
||||
maybeSetupShadowInstance(chunkaccess, (float) entityX, (float) entityZ, actualWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instances.discardExtra();
|
||||
}
|
||||
|
||||
private void maybeSetupShadowInstance(ChunkAccess pChunk, float entityX, float entityZ, float weight) {
|
||||
// TODO: cache this?
|
||||
var maxLocalRawBrightness = level.getMaxLocalRawBrightness(pos);
|
||||
if (maxLocalRawBrightness <= 3) {
|
||||
// Too dark to render.
|
||||
return;
|
||||
}
|
||||
float blockBrightness = LightTexture.getBrightness(level.dimensionType(), maxLocalRawBrightness);
|
||||
float alpha = weight * 0.5F * blockBrightness;
|
||||
if (!(alpha >= 0.0F)) {
|
||||
// Too far away/too weak to render.
|
||||
return;
|
||||
}
|
||||
if (alpha > 1.0F) {
|
||||
alpha = 1.0F;
|
||||
}
|
||||
|
||||
// Grab the AABB for the block below the current position.
|
||||
pos.setY(pos.getY() - 1);
|
||||
var aabb = getAabbForPos(pChunk, pos);
|
||||
if (aabb == null) {
|
||||
// No aabb means the block shouldn't receive a shadow.
|
||||
return;
|
||||
}
|
||||
|
||||
var renderOrigin = context.renderOrigin();
|
||||
int x = pos.getX() - renderOrigin.getX();
|
||||
int y = pos.getY() - renderOrigin.getY() + 1; // +1 since we moved the pos down.
|
||||
int z = pos.getZ() - renderOrigin.getZ();
|
||||
|
||||
double minX = x + aabb.minX;
|
||||
double minY = y + aabb.minY;
|
||||
double minZ = z + aabb.minZ;
|
||||
double maxX = x + aabb.maxX;
|
||||
double maxZ = z + aabb.maxZ;
|
||||
|
||||
var instance = instances.get();
|
||||
instance.x = (float) minX;
|
||||
instance.y = (float) minY;
|
||||
instance.z = (float) minZ;
|
||||
instance.entityX = entityX;
|
||||
instance.entityZ = entityZ;
|
||||
instance.sizeX = (float) (maxX - minX);
|
||||
instance.sizeZ = (float) (maxZ - minZ);
|
||||
instance.alpha = alpha;
|
||||
instance.radius = this.radius;
|
||||
instance.setChanged();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AABB getAabbForPos(ChunkAccess pChunk, BlockPos pos) {
|
||||
BlockState blockstate = pChunk.getBlockState(pos);
|
||||
if (blockstate.getRenderShape() == RenderShape.INVISIBLE) {
|
||||
return null;
|
||||
}
|
||||
if (!blockstate.isCollisionShapeFullBlock(pChunk, pos)) {
|
||||
return null;
|
||||
}
|
||||
VoxelShape voxelshape = blockstate.getShape(pChunk, pos);
|
||||
if (voxelshape.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return voxelshape.bounds();
|
||||
}
|
||||
|
||||
private ShadowInstance instance() {
|
||||
return context.instancerProvider()
|
||||
.instancer(InstanceTypes.SHADOW, ShadowModel.INSTANCE, RenderStage.AFTER_ENTITIES)
|
||||
.createInstance();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
instances.delete();
|
||||
}
|
||||
|
||||
private static class ShadowModel implements Model {
|
||||
public static final ShadowModel INSTANCE = new ShadowModel();
|
||||
public static final Material MATERIAL = SimpleMaterial.builder()
|
||||
.transparency(Transparency.TRANSLUCENT)
|
||||
.writeMask(WriteMask.COLOR)
|
||||
.polygonOffset(true) // vanilla shadows use "view offset" but this seems to work fine
|
||||
.texture(new ResourceLocation("minecraft", "textures/misc/shadow.png"))
|
||||
.build();
|
||||
private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0.5f, 0, 0.5f, (float) (Math.sqrt(2) * 0.5));
|
||||
private static final ImmutableMap<Material, Mesh> meshes = ImmutableMap.of(MATERIAL, ShadowMesh.INSTANCE);
|
||||
|
||||
private ShadowModel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Material, Mesh> meshes() {
|
||||
return meshes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4fc boundingSphere() {
|
||||
return BOUNDING_SPHERE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int vertexCount() {
|
||||
return ShadowMesh.INSTANCE.vertexCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A single quad extending from the origin to (1, 0, 1).
|
||||
* <br>
|
||||
* To be scaled and translated to the correct position and size.
|
||||
*/
|
||||
private static class ShadowMesh implements QuadMesh {
|
||||
public static final ShadowMesh INSTANCE = new ShadowMesh();
|
||||
|
||||
private ShadowMesh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int vertexCount() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(MutableVertexList vertexList) {
|
||||
writeVertex(vertexList, 0, 0, 0);
|
||||
writeVertex(vertexList, 1, 0, 1);
|
||||
writeVertex(vertexList, 2, 1, 1);
|
||||
writeVertex(vertexList, 3, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4fc boundingSphere() {
|
||||
return BOUNDING_SPHERE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
|
||||
}
|
||||
|
||||
// Magic numbers taken from:
|
||||
// net.minecraft.client.renderer.entity.EntityRenderDispatcher#shadowVertex
|
||||
private static void writeVertex(MutableVertexList vertexList, int i, float x, float z) {
|
||||
vertexList.x(i, x);
|
||||
vertexList.y(i, 0);
|
||||
vertexList.z(i, z);
|
||||
vertexList.r(i, 1);
|
||||
vertexList.g(i, 1);
|
||||
vertexList.b(i, 1);
|
||||
vertexList.u(i, 0);
|
||||
vertexList.v(i, 0);
|
||||
vertexList.light(i, 15728880);
|
||||
vertexList.normalX(i, 0);
|
||||
vertexList.normalY(i, 1);
|
||||
vertexList.normalZ(i, 0);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.jozufozu.flywheel.vanilla;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
||||
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
||||
@ -37,6 +39,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
private final ModelHolder bodyModel;
|
||||
|
||||
private TransformedInstance body;
|
||||
@Nullable
|
||||
private TransformedInstance contents;
|
||||
private BlockState blockState;
|
||||
private boolean active;
|
||||
@ -46,6 +49,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
public MinecartVisual(VisualizationContext ctx, T entity, ModelHolder bodyModel) {
|
||||
super(ctx, entity);
|
||||
this.bodyModel = bodyModel;
|
||||
shadow.radius(0.7f);
|
||||
}
|
||||
|
||||
private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) {
|
||||
@ -70,6 +74,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
.createInstance();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TransformedInstance createContentsInstance() {
|
||||
RenderShape shape = blockState.getRenderShape();
|
||||
|
||||
@ -94,7 +99,9 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
|
||||
if (displayBlockState != blockState) {
|
||||
blockState = displayBlockState;
|
||||
contents.delete();
|
||||
if (contents != null) {
|
||||
contents.delete();
|
||||
}
|
||||
contents = createContentsInstance();
|
||||
}
|
||||
|
||||
@ -103,6 +110,8 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
|
||||
@Override
|
||||
public void beginFrame(VisualFrameContext context) {
|
||||
shadow.beginFrame(context);
|
||||
|
||||
if (!isVisible(context.frustum())) {
|
||||
return;
|
||||
}
|
||||
@ -200,6 +209,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
|
||||
if (contents != null) {
|
||||
contents.delete();
|
||||
}
|
||||
super._delete();
|
||||
}
|
||||
|
||||
public static boolean shouldSkipRender(AbstractMinecart minecart) {
|
||||
|
@ -0,0 +1,5 @@
|
||||
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
||||
// We can just ignore the base center/radius.
|
||||
center = i.pos + vec3(i.size.x * 0.5, 0., i.size.y * 0.5);
|
||||
radius = max(i.size.x, i.size.y) * 0.5;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
void flw_instanceVertex(in FlwInstance i) {
|
||||
// Stretch the quad to the shape of the block the shadow is being cast on,
|
||||
// then move it to the correct position.
|
||||
flw_vertexPos.xyz = flw_vertexPos.xyz * vec3(i.size.x, 1., i.size.y) + i.pos;
|
||||
|
||||
// Uvs are calculated based on the distance to the entity.
|
||||
flw_vertexTexCoord = (flw_vertexPos.xz - i.entityPosXZ) * 0.5 / i.radius + 0.5;
|
||||
|
||||
flw_vertexColor.a = i.alpha;
|
||||
}
|
Loading…
Reference in New Issue
Block a user