Skip to content

Commit

Permalink
Implement entity lighting (just simple flat lighting that respects sk…
Browse files Browse the repository at this point in the history
…y and block light levels, no normals yet)
  • Loading branch information
stackotter committed Jun 8, 2024
1 parent 4727441 commit f887f84
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 122 deletions.
67 changes: 7 additions & 60 deletions Sources/Core/Renderer/Entity/EntityRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ public struct EntityRenderer: Renderer {
private var blockRenderPipelineState: MTLRenderPipelineState
/// The buffer containing the uniforms for all rendered entities.
private var instanceUniformsBuffer: MTLBuffer?
/// The buffer containing the hit box vertices. They form a basic cube and instanced rendering is used to render the cube once for each entity.
private var vertexBuffer: MTLBuffer
/// The buffer containing the index windings for the template hit box (see ``vertexBuffer``.
private var indexBuffer: MTLBuffer
/// The number of indices in ``indexBuffer``.
private var indexCount: Int

private var entityTexturePalette: MetalTexturePalette
private var blockTexturePalette: MetalTexturePalette
Expand Down Expand Up @@ -83,26 +77,6 @@ public struct EntityRenderer: Renderer {
blendingEnabled: true
)

// Create hitbox geometry (hitboxes are rendered using instancing)
var geometry = Self.createHitBoxGeometry(color: Self.hitBoxColor)
indexCount = geometry.indices.count

vertexBuffer = try MetalUtil.makeBuffer(
device,
bytes: &geometry.vertices,
length: geometry.vertices.count * MemoryLayout<EntityVertex>.stride,
options: .storageModeShared,
label: "entityHitBoxVertices"
)

indexBuffer = try MetalUtil.makeBuffer(
device,
bytes: &geometry.indices,
length: geometry.indices.count * MemoryLayout<UInt32>.stride,
options: .storageModeShared,
label: "entityHitBoxIndices"
)

entityTexturePalette = try MetalTexturePalette(
palette: client.resourcePack.vanillaResources.entityTexturePalette,
device: device,
Expand Down Expand Up @@ -174,13 +148,15 @@ public struct EntityRenderer: Renderer {
kindIdentifier = Identifier(name: "dragon")
}

let lightLevel = client.game.world.getLightLevel(at: position.block)
buildEntityMesh(
entity: entity,
entityKindIdentifier: kindIdentifier,
position: Vec3f(position.smoothVector),
pitch: rotation.smoothPitch,
yaw: rotation.smoothYaw,
hitbox: hitbox.aabb(at: position.smoothVector),
lightLevel: lightLevel,
into: &geometry,
blockGeometry: &blockGeometry,
translucentBlockGeometry: &translucentBlockGeometry
Expand All @@ -200,13 +176,15 @@ public struct EntityRenderer: Renderer {
let block = chunk.getBlock(at: blockEntity.position.relativeToChunk)
let direction = block.stateProperties.facing ?? .south

let lightLevel = client.game.world.getLightLevel(at: blockEntity.position)
buildEntityMesh(
entity: nil,
entityKindIdentifier: blockEntity.identifier,
position: position,
pitch: 0,
yaw: Self.blockEntityYaw(toFace: direction),
hitbox: AxisAlignedBoundingBox(position: .zero, size: Vec3d(1, 1, 1)),
lightLevel: lightLevel,
into: &geometry,
blockGeometry: &blockGeometry,
translucentBlockGeometry: &translucentBlockGeometry
Expand Down Expand Up @@ -255,6 +233,7 @@ public struct EntityRenderer: Renderer {
pitch: Float,
yaw: Float,
hitbox: AxisAlignedBoundingBox,
lightLevel: LightLevel,
into geometry: inout Geometry<EntityVertex>,
blockGeometry: inout Geometry<BlockVertex>,
translucentBlockGeometry: inout SortableMesh
Expand All @@ -271,7 +250,8 @@ public struct EntityRenderer: Renderer {
blockModelPalette: blockModelPalette,
entityTexturePalette: entityTexturePalette.palette,
blockTexturePalette: blockTexturePalette.palette,
hitbox: hitbox
hitbox: hitbox,
lightLevel: lightLevel
).build(
into: &geometry,
blockGeometry: &blockGeometry,
Expand All @@ -298,37 +278,4 @@ public struct EntityRenderer: Renderer {
public mutating func setVisibleChunks(_ visibleChunks: Set<ChunkPosition>) {
self.visibleChunks = visibleChunks
}

/// Creates a coloured and shaded cube to be rendered using instancing as entities' hitboxes.
private static func createHitBoxGeometry(color: DeltaCore.RGBColor) -> Geometry<EntityVertex> {
var vertices: [EntityVertex] = []
var indices: [UInt32] = []

for direction in Direction.allDirections {
let faceVertices = CubeGeometry.faceVertices[direction.rawValue]
for position in faceVertices {
let color = color.floatVector * CubeGeometry.shades[direction.rawValue]
vertices.append(
EntityVertex(
x: position.x,
y: position.y,
z: position.z,
r: color.x,
g: color.y,
b: color.z,
u: 0,
v: 0,
textureIndex: nil
)
)
}

let offset = UInt32(indices.count / 6 * 4)
for value in CubeGeometry.faceWinding {
indices.append(value + offset)
}
}

return Geometry(vertices: vertices, indices: indices)
}
}
24 changes: 15 additions & 9 deletions Sources/Core/Renderer/Entity/EntityVertex.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
/// The vertex format used by the entity shader.
public struct EntityVertex {
public let x: Float
public let y: Float
public let z: Float
public let r: Float
public let g: Float
public let b: Float
public let u: Float
public let v: Float
public var x: Float
public var y: Float
public var z: Float
public var r: Float
public var g: Float
public var b: Float
public var u: Float
public var v: Float
public var skyLightLevel: UInt8
public var blockLightLevel: UInt8
/// ``UInt16/max`` indicates that no texture is to be used. I would usually use
/// an optional to model that, but this type needs to be compatible with C as we
/// pass it off to the shaders for rendering.
public let textureIndex: UInt16
public var textureIndex: UInt16

public init(
x: Float,
Expand All @@ -22,6 +24,8 @@ public struct EntityVertex {
b: Float,
u: Float,
v: Float,
skyLightLevel: UInt8,
blockLightLevel: UInt8,
textureIndex: UInt16?
) {
self.x = x
Expand All @@ -32,6 +36,8 @@ public struct EntityVertex {
self.b = b
self.u = u
self.v = v
self.skyLightLevel = skyLightLevel
self.blockLightLevel = blockLightLevel
self.textureIndex = textureIndex ?? .max
}
}
3 changes: 2 additions & 1 deletion Sources/Core/Renderer/GUI/GUIRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ public final class GUIRenderer: Renderer {
blockModelPalette: blockModelPalette,
entityTexturePalette: entityTexturePalette,
blockTexturePalette: blockTexturePalette,
hitbox: AxisAlignedBoundingBox(position: .zero, size: Vec3d(1, 1, 1))
hitbox: AxisAlignedBoundingBox(position: .zero, size: Vec3d(1, 1, 1)),
lightLevel: .default // Doesn't matter cause the GUI doesn't use light levels
).build(
into: &geometry,
blockGeometry: &blockGeometry,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Renderer/Mesh/BlockMeshBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct BlockMeshBuilder {
}

let faceLightLevel = LightLevel.max(
neighbourLightLevels[face.actualDirection] ?? LightLevel(),
neighbourLightLevels[face.actualDirection] ?? .default,
lightLevel
)

Expand Down
31 changes: 23 additions & 8 deletions Sources/Core/Renderer/Mesh/EntityMeshBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public struct EntityMeshBuilder {
public let entityTexturePalette: TexturePalette
public let blockTexturePalette: TexturePalette
public let hitbox: AxisAlignedBoundingBox
public let lightLevel: LightLevel

static let colors: [Vec3f] = [
[1, 0, 0],
Expand All @@ -47,10 +48,20 @@ public struct EntityMeshBuilder {
) {
if let model = entityModelPalette.models[entityKind] {
buildModel(model, into: &geometry)
} else if let itemMetadata = entity?.get(component: EntityMetadata.self)?.itemMetadata,
let itemStack = itemMetadata.slot.stack,
let itemModel = itemModelPalette.model(for: itemStack.itemId)
{
} else if let itemMetadata = entity?.get(component: EntityMetadata.self)?.itemMetadata {
guard let itemStack = itemMetadata.slot.stack else {
// If there's no stack, then we're still waiting for the server to send
// the item's metadata so don't render anything yet (to avoid seeing the
// 'missing entity' hitbox for a split second whenever a new item entity
// spawns in).
return
}

guard let itemModel = itemModelPalette.model(for: itemStack.itemId) else {
buildAABB(hitbox, into: &geometry)
return
}

// TODO: Figure out why these bobbing constants and hardcoded translations are so weird
// (they're even still slightly off vanilla, there must be a different order of transformations
// that makes these numbers nice or something).
Expand Down Expand Up @@ -89,10 +100,9 @@ public struct EntityMeshBuilder {
return
}

// TODO: Don't just use dummy lighting
var neighbourLightLevels: [Direction: LightLevel] = [:]
for direction in Direction.allDirections {
neighbourLightLevels[direction] = LightLevel(sky: 15, block: 0)
neighbourLightLevels[direction] = lightLevel
}

// TODO: Try using the transformation code from the GUIRenderer and see if that cleans things up a bit.
Expand All @@ -103,13 +113,14 @@ public struct EntityMeshBuilder {
* MatrixUtil.translationMatrix(Vec3f(0, 7.0 / 32.0, 0))
* MatrixUtil.rotationMatrix(y: yaw + .pi)
* MatrixUtil.translationMatrix(position)

let builder = BlockMeshBuilder(
model: blockModel,
position: .zero,
modelToWorld: transformation,
culledFaces: [],
lightLevel: LightLevel(sky: 15, block: 0),
neighbourLightLevels: [:],
lightLevel: lightLevel,
neighbourLightLevels: neighbourLightLevels,
tintColor: Vec3f(1, 1, 1),
blockTexturePalette: blockTexturePalette
)
Expand Down Expand Up @@ -154,6 +165,8 @@ public struct EntityMeshBuilder {
b: color.z,
u: 0,
v: 0,
skyLightLevel: UInt8(lightLevel.sky),
blockLightLevel: UInt8(lightLevel.block),
textureIndex: nil
)
geometry.vertices.append(vertex)
Expand Down Expand Up @@ -355,6 +368,8 @@ public struct EntityMeshBuilder {
b: textureIndex == nil ? color.z : 1,
u: uv.x,
v: uv.y,
skyLightLevel: UInt8(lightLevel.sky),
blockLightLevel: UInt8(lightLevel.block),
textureIndex: textureIndex.map(UInt16.init)
)
geometry.vertices.append(vertex)
Expand Down
18 changes: 17 additions & 1 deletion Sources/Core/Renderer/Shader/EntityShaders.metal
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ struct EntityVertex {
float b;
float u;
float v;
uint8_t skyLightLevel;
uint8_t blockLightLevel;
uint16_t textureIndex;
};

struct EntityRasterizerData {
float4 position [[position]];
float4 color;
float2 uv;
uint8_t skyLightLevel;
uint8_t blockLightLevel;
uint16_t textureIndex;
};

Expand All @@ -32,14 +36,17 @@ vertex EntityRasterizerData entityVertexShader(constant EntityVertex *vertices [
out.color = float4(in.r, in.g, in.b, 1.0);
out.uv = float2(in.u, in.v);
out.textureIndex = in.textureIndex;
out.skyLightLevel = in.skyLightLevel;
out.blockLightLevel = in.blockLightLevel;

return out;
}

constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest, mip_filter::linear);

fragment float4 entityFragmentShader(EntityRasterizerData in [[stage_in]],
texture2d_array<float, access::sample> textureArray [[texture(0)]]) {
texture2d_array<float, access::sample> textureArray [[texture(0)]],
constant uint8_t *lightMap [[buffer(0)]]) {
float4 color;
if (in.textureIndex == 65535) {
color = in.color;
Expand All @@ -49,5 +56,14 @@ fragment float4 entityFragmentShader(EntityRasterizerData in [[stage_in]],
if (color.a < 0.3) {
discard_fragment();
}

int index = in.skyLightLevel * 16 + in.blockLightLevel;
float4 brightness;
brightness.r = (float)lightMap[index * 4];
brightness.g = (float)lightMap[index * 4 + 1];
brightness.b = (float)lightMap[index * 4 + 2];
brightness.a = 255;
color *= brightness / 255.0;

return color;
}
Loading

0 comments on commit f887f84

Please sign in to comment.