mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-16 19:05:02 -04:00
ambient occlusion: on demand
This is way faster now and almost disappeared in the cpu profiling. It also renders ao only on touching faces
This commit is contained in:
parent
b174638aaf
commit
1b869e4761
@ -34,11 +34,12 @@ import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
|
||||
import de.bixilon.minosoft.data.world.chunk.light.SectionLight
|
||||
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
|
||||
import de.bixilon.minosoft.data.world.positions.BlockPosition
|
||||
import de.bixilon.minosoft.data.world.positions.InChunkSectionPosition
|
||||
import de.bixilon.minosoft.gui.rendering.RenderContext
|
||||
import de.bixilon.minosoft.gui.rendering.chunk.entities.BlockEntityRenderer
|
||||
import de.bixilon.minosoft.gui.rendering.chunk.entities.renderer.RenderedBlockEntity
|
||||
import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes
|
||||
import de.bixilon.minosoft.gui.rendering.light.AmbientOcclusionUtil
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusion
|
||||
import de.bixilon.minosoft.gui.rendering.models.block.state.render.WorldRenderProps
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
import java.util.*
|
||||
@ -68,6 +69,7 @@ class SolidSectionMesher(
|
||||
val entities: ArrayList<BlockEntityRenderer<*>> = ArrayList(section.blockEntities.count)
|
||||
|
||||
val position = BlockPosition()
|
||||
val inSectionPosition = InChunkSectionPosition()
|
||||
val neighbourBlocks: Array<BlockState?> = arrayOfNulls(Directions.SIZE)
|
||||
val light = ByteArray(Directions.SIZE + 1) // last index (6) for the current block
|
||||
|
||||
@ -79,15 +81,17 @@ class SolidSectionMesher(
|
||||
|
||||
val floatOffset = FloatArray(3)
|
||||
|
||||
val ao = Array(Directions.SIZE) { IntArray(4) }
|
||||
val ao = if (ambientOcclusion) AmbientOcclusion(section) else null
|
||||
|
||||
val props = WorldRenderProps(position, floatOffset, mesh, random, neighbourBlocks, light, ao)
|
||||
val props = WorldRenderProps(position, inSectionPosition, floatOffset, mesh, random, neighbourBlocks, light, ao)
|
||||
|
||||
for (y in blocks.minPosition.y..blocks.maxPosition.y) {
|
||||
inSectionPosition.y = y
|
||||
position.y = offsetY + y
|
||||
floatOffset[1] = (position.y - cameraOffset.y).toFloat()
|
||||
val fastBedrock = y == 0 && isLowestSection && fastBedrock
|
||||
for (x in blocks.minPosition.x..blocks.maxPosition.x) {
|
||||
inSectionPosition.x = x
|
||||
position.x = offsetX + x
|
||||
floatOffset[0] = (position.x - cameraOffset.x).toFloat()
|
||||
for (z in blocks.minPosition.z..blocks.maxPosition.z) {
|
||||
@ -104,6 +108,7 @@ class SolidSectionMesher(
|
||||
|
||||
light[SELF_LIGHT_INDEX] = section.light[index]
|
||||
position.z = offsetZ + z
|
||||
inSectionPosition.z = z
|
||||
floatOffset[2] = (position.z - cameraOffset.z).toFloat()
|
||||
|
||||
val maxHeight = chunk.light.heightmap[baseIndex]
|
||||
@ -136,9 +141,7 @@ class SolidSectionMesher(
|
||||
floatOffset[2] += offset.z
|
||||
}
|
||||
|
||||
if (ambientOcclusion) {
|
||||
AmbientOcclusionUtil.apply(section, x, y, z, ao)
|
||||
}
|
||||
ao?.clear()
|
||||
|
||||
|
||||
val tints = tints.getBlockTint(state, chunk, x, position.y, z)
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2025 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.light.ao
|
||||
|
||||
import de.bixilon.minosoft.data.direction.Directions
|
||||
import de.bixilon.minosoft.data.world.chunk.ChunkSection
|
||||
import de.bixilon.minosoft.data.world.positions.BlockPosition
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyBottom
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyEast
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyNorth
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applySouth
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyTop
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyWest
|
||||
|
||||
class AmbientOcclusion(
|
||||
val section: ChunkSection,
|
||||
) {
|
||||
private var input = Array(Directions.SIZE) { IntArray(AmbientOcclusionUtil.LEVELS) }
|
||||
private var output = Array(Directions.SIZE) { AmbientOcclusionUtil.EMPTY }
|
||||
private var dirty = 0x00
|
||||
|
||||
|
||||
fun apply(direction: Directions, position: BlockPosition) = apply(direction, position.x, position.y, position.z)
|
||||
|
||||
fun apply(direction: Directions, x: Int, y: Int, z: Int): IntArray {
|
||||
val mask = 1 shl direction.ordinal
|
||||
if (dirty and mask != 0) return output[direction.ordinal] // already calculated
|
||||
|
||||
val input = input[direction.ordinal]
|
||||
|
||||
dirty = dirty or mask
|
||||
val output = when (direction) {
|
||||
Directions.DOWN -> applyBottom(section, x, y, z, input)
|
||||
Directions.UP -> applyTop(section, x, y, z, input)
|
||||
|
||||
Directions.NORTH -> applyNorth(section, x, y, z, input)
|
||||
Directions.SOUTH -> applySouth(section, x, y, z, input)
|
||||
|
||||
Directions.WEST -> applyWest(section, x, y, z, input)
|
||||
Directions.EAST -> applyEast(section, x, y, z, input)
|
||||
}
|
||||
this.output[direction.ordinal] = output
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
dirty = 0x00
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.light
|
||||
package de.bixilon.minosoft.gui.rendering.light.ao
|
||||
|
||||
import de.bixilon.minosoft.data.direction.Directions.Companion.O_DOWN
|
||||
import de.bixilon.minosoft.data.direction.Directions.Companion.O_EAST
|
||||
@ -24,11 +24,14 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
|
||||
object AmbientOcclusionUtil {
|
||||
const val LEVELS = 4
|
||||
const val LEVEL_NONE = 0
|
||||
const val LEVEL_1 = 1
|
||||
const val LEVEL_2 = 2
|
||||
const val LEVEL_3 = 3
|
||||
|
||||
val EMPTY = IntArray(LEVELS) { LEVEL_NONE }
|
||||
|
||||
fun ChunkSection?.trace(x: Int, y: Int, z: Int): Int {
|
||||
if (this == null) return 0
|
||||
var x = x
|
||||
@ -37,6 +40,8 @@ object AmbientOcclusionUtil {
|
||||
|
||||
var section: ChunkSection? = this
|
||||
|
||||
if (x < -1 || x > 16 || y < -1 || y > 16 || z < -1 || z > 16) throw IllegalArgumentException("x=$x, y=$y, z=$z")
|
||||
|
||||
if (x < 0) {
|
||||
section = section?.neighbours?.get(O_WEST)
|
||||
x = ProtocolDefinition.SECTION_MAX_X
|
||||
@ -74,16 +79,9 @@ object AmbientOcclusionUtil {
|
||||
return side1 + side2 + corner
|
||||
}
|
||||
|
||||
private fun IntArray.clear() {
|
||||
this[0] = LEVEL_NONE
|
||||
this[1] = LEVEL_NONE
|
||||
this[2] = LEVEL_NONE
|
||||
this[3] = LEVEL_NONE
|
||||
}
|
||||
|
||||
fun setY(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) {
|
||||
ao.clear()
|
||||
if (section == null || section.blocks.isEmpty) return
|
||||
fun setY(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray {
|
||||
if (section == null || section.blocks.isEmpty) return EMPTY
|
||||
|
||||
val west = section.trace(x - 1, y, z + 0)
|
||||
val north = section.trace(x + 0, y, z - 1)
|
||||
@ -94,9 +92,11 @@ object AmbientOcclusionUtil {
|
||||
ao[2] = calculateLevel(east, south, section.trace(x + 1, y, z + 1))
|
||||
ao[if (flip) 3 else 1] = calculateLevel(north, east, section.trace(x + 1, y, z - 1))
|
||||
ao[if (flip) 1 else 3] = calculateLevel(south, west, section.trace(x - 1, y, z + 1))
|
||||
|
||||
return ao
|
||||
}
|
||||
|
||||
fun applyBottom(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applyBottom(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var y = y - 1
|
||||
if (y < 0) {
|
||||
@ -104,10 +104,10 @@ object AmbientOcclusionUtil {
|
||||
y = ProtocolDefinition.SECTION_MAX_Y
|
||||
}
|
||||
|
||||
setY(section, x, y, z, true, ao)
|
||||
return setY(section, x, y, z, true, ao)
|
||||
}
|
||||
|
||||
fun applyTop(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applyTop(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var y = y + 1
|
||||
if (y > ProtocolDefinition.SECTION_MAX_Y) {
|
||||
@ -115,12 +115,11 @@ object AmbientOcclusionUtil {
|
||||
y = 0
|
||||
}
|
||||
|
||||
setY(section, x, y, z, false, ao)
|
||||
return setY(section, x, y, z, false, ao)
|
||||
}
|
||||
|
||||
fun setZ(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) {
|
||||
ao.clear()
|
||||
if (section == null || section.blocks.isEmpty) return
|
||||
fun setZ(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray {
|
||||
if (section == null || section.blocks.isEmpty) return EMPTY
|
||||
|
||||
val down = section.trace(x + 0, y - 1, z)
|
||||
val west = section.trace(x - 1, y + 0, z)
|
||||
@ -131,10 +130,12 @@ object AmbientOcclusionUtil {
|
||||
ao[if (flip) 3 else 1] = calculateLevel(west, up, section.trace(x - 1, y + 1, z))
|
||||
ao[2] = calculateLevel(up, east, section.trace(x + 1, y + 1, z))
|
||||
ao[if (flip) 1 else 3] = calculateLevel(east, down, section.trace(x + 1, y - 1, z))
|
||||
|
||||
return ao
|
||||
}
|
||||
|
||||
|
||||
fun applyNorth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applyNorth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var z = z - 1
|
||||
if (z < 0) {
|
||||
@ -142,10 +143,10 @@ object AmbientOcclusionUtil {
|
||||
z = ProtocolDefinition.SECTION_MAX_Z
|
||||
}
|
||||
|
||||
setZ(section, x, y, z, true, ao)
|
||||
return setZ(section, x, y, z, true, ao)
|
||||
}
|
||||
|
||||
fun applySouth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applySouth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var z = z + 1
|
||||
if (z > ProtocolDefinition.SECTION_MAX_Z) {
|
||||
@ -153,12 +154,11 @@ object AmbientOcclusionUtil {
|
||||
z = 0
|
||||
}
|
||||
|
||||
setZ(section, x, y, z, false, ao)
|
||||
return setZ(section, x, y, z, false, ao)
|
||||
}
|
||||
|
||||
fun setX(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) {
|
||||
ao.clear()
|
||||
if (section == null || section.blocks.isEmpty) return
|
||||
fun setX(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray {
|
||||
if (section == null || section.blocks.isEmpty) return EMPTY
|
||||
|
||||
val down = section.trace(x + 0, y - 1, z)
|
||||
val north = section.trace(x, y + 0, z - 1)
|
||||
@ -169,9 +169,11 @@ object AmbientOcclusionUtil {
|
||||
ao[if (flip) 3 else 1] = calculateLevel(north, up, section.trace(x, y + 1, z - 1))
|
||||
ao[2] = calculateLevel(up, south, section.trace(x, y + 1, z + 1))
|
||||
ao[if (flip) 1 else 3] = calculateLevel(south, down, section.trace(x, y - 1, z + 1))
|
||||
|
||||
return ao
|
||||
}
|
||||
|
||||
fun applyWest(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applyWest(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var x = x - 1
|
||||
if (x < 0) {
|
||||
@ -179,10 +181,10 @@ object AmbientOcclusionUtil {
|
||||
x = ProtocolDefinition.SECTION_MAX_X
|
||||
}
|
||||
|
||||
setX(section, x, y, z, false, ao)
|
||||
return setX(section, x, y, z, false, ao)
|
||||
}
|
||||
|
||||
fun applyEast(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) {
|
||||
fun applyEast(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray {
|
||||
var section: ChunkSection? = section
|
||||
var x = x + 1
|
||||
if (x > ProtocolDefinition.SECTION_MAX_X) {
|
||||
@ -190,17 +192,6 @@ object AmbientOcclusionUtil {
|
||||
x = 0
|
||||
}
|
||||
|
||||
setX(section, x, y, z, true, ao)
|
||||
}
|
||||
|
||||
fun apply(section: ChunkSection, x: Int, y: Int, z: Int, ao: Array<IntArray>) {
|
||||
applyBottom(section, x, y, z, ao[O_DOWN])
|
||||
applyTop(section, x, y, z, ao[O_UP])
|
||||
|
||||
applyNorth(section, x, y, z, ao[O_NORTH])
|
||||
applySouth(section, x, y, z, ao[O_SOUTH])
|
||||
|
||||
applyWest(section, x, y, z, ao[O_WEST])
|
||||
applyEast(section, x, y, z, ao[O_EAST])
|
||||
return setX(section, x, y, z, true, ao)
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import de.bixilon.minosoft.data.direction.Directions
|
||||
import de.bixilon.minosoft.data.entities.block.BlockEntity
|
||||
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
|
||||
import de.bixilon.minosoft.gui.rendering.chunk.mesh.BlockVertexConsumer
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil
|
||||
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.FaceCulling
|
||||
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties
|
||||
import de.bixilon.minosoft.gui.rendering.models.block.state.render.BlockRender
|
||||
@ -56,16 +57,22 @@ class BakedModel(
|
||||
|
||||
for ((directionIndex, faces) in faces.withIndex()) {
|
||||
val neighbour = neighbours[directionIndex]
|
||||
val direction = Directions.VALUES[directionIndex].inverted
|
||||
val direction = Directions.VALUES[directionIndex]
|
||||
val inverted = direction.inverted
|
||||
|
||||
val ao = ao[directionIndex]
|
||||
|
||||
for (face in faces) {
|
||||
if (FaceCulling.canCull(state, face.properties, direction, neighbour)) {
|
||||
if (FaceCulling.canCull(state, face.properties, inverted, neighbour)) {
|
||||
continue
|
||||
}
|
||||
|
||||
face.render(offset, mesh, light, tints, ao)
|
||||
var aoRaw = AmbientOcclusionUtil.EMPTY
|
||||
|
||||
if (ao != null && face.properties != null) {
|
||||
aoRaw = ao.apply(direction, props.inSectionPosition)
|
||||
}
|
||||
|
||||
face.render(offset, mesh, light, tints, aoRaw)
|
||||
|
||||
rendered = true
|
||||
}
|
||||
|
@ -15,13 +15,17 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.render
|
||||
|
||||
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
|
||||
import de.bixilon.minosoft.data.world.positions.BlockPosition
|
||||
import de.bixilon.minosoft.data.world.positions.InChunkSectionPosition
|
||||
import de.bixilon.minosoft.gui.rendering.chunk.mesh.BlockVertexConsumer
|
||||
import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusion
|
||||
import java.util.*
|
||||
|
||||
class WorldRenderProps(
|
||||
@JvmField
|
||||
val position: BlockPosition,
|
||||
@JvmField
|
||||
val inSectionPosition: InChunkSectionPosition,
|
||||
@JvmField
|
||||
val offset: FloatArray,
|
||||
@JvmField
|
||||
val mesh: BlockVertexConsumer,
|
||||
@ -32,5 +36,5 @@ class WorldRenderProps(
|
||||
@JvmField
|
||||
val light: ByteArray,
|
||||
@JvmField
|
||||
var ao: Array<IntArray>,
|
||||
val ao: AmbientOcclusion?,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user