new lightmap: nice try but didn't work

This commit is contained in:
minjaesong
2020-02-21 03:40:37 +09:00
parent 9d51f419f5
commit 947224c290
6 changed files with 240 additions and 5 deletions

View File

@@ -70,6 +70,21 @@ class Cvec {
set(color)
}
/**
* Get RGBA Element using index, of which:
* - 0: R
* - 1: G
* - 2: B
* - 3: A
*/
fun getElem(index: Int) = when(index) {
0 -> r
1 -> g
2 -> b
3 -> a
else -> throw IndexOutOfBoundsException("Invalid index $index")
}
/** Sets this color to the given color.
*
* @param color the Cvec

View File

@@ -29,6 +29,18 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
fun setB(x: Int, y: Int, value: Float) { array.setFloat(toAddr(x, y) + 8, value) }
fun setA(x: Int, y: Int, value: Float) { array.setFloat(toAddr(x, y) + 12, value) }
/**
* @param channel 0 for R, 1 for G, 2 for B, 3 for A
*/
inline fun channelSet(x: Int, y: Int, channel: Int, value: Float) {
array.setFloat(toAddr(x, y) + 4L * channel, value)
}
/**
* @param channel 0 for R, 1 for G, 2 for B, 3 for A
*/
inline fun channelGet(x: Int, y: Int, channel: Int) = array.getFloat(toAddr(x, y) + 4L * channel)
fun max(x: Int, y: Int, other: Cvec) {
setR(x, y, maxOf(getR(x, y), other.r))
setG(x, y, maxOf(getG(x, y), other.g))

View File

@@ -19,6 +19,14 @@ class BlockProp {
lateinit var opacity: Cvec
fun getOpacity(channel: Int) = when (channel) {
0 -> shadeColR
1 -> shadeColG
2 -> shadeColB
3 -> shadeColA
else -> throw IllegalArgumentException("Invalid channel $channel")
}
var strength: Int = 0
var density: Int = 0
var viscosity: Int = 0
@@ -48,6 +56,16 @@ class BlockProp {
inline val luminosity: Cvec
get() = BlockPropUtil.getDynamicLumFunc(internalLumCol, dynamicLuminosityFunction)
fun getLum(channel: Int) = BlockPropUtil.getDynamicLumFuncByChan(
when (channel) {
0 -> lumColR
1 -> lumColG
2 -> lumColB
3 -> lumColA
else -> throw IllegalArgumentException("Invalid channel $channel")
}, dynamicLuminosityFunction, channel
)
var drop: Int = 0
var maxSupport: Int = -1 // couldn't use NULL at all...

View File

@@ -37,22 +37,31 @@ object BlockPropUtil {
private fun getTorchFlicker(baseLum: Cvec): Cvec {
val funcY = FastMath.interpolateLinear(flickerFuncX / flickerFuncDomain, flickerP0, flickerP1)
return alterBrightnessUniform(baseLum, funcY)
}
private fun getTorchFlicker(baseLum: Float): Float {
return baseLum + FastMath.interpolateLinear(flickerFuncX / flickerFuncDomain, flickerP0, flickerP1)
}
private fun getSlowBreath(baseLum: Cvec): Cvec {
val funcY = FastMath.sin(FastMath.PI * breathFuncX / breathCycleDuration) * breathRange
return alterBrightnessUniform(baseLum, funcY)
}
private fun getSlowBreath(baseLum: Float): Float {
return baseLum + FastMath.sin(FastMath.PI * breathFuncX / breathCycleDuration) * breathRange
}
private fun getPulsate(baseLum: Cvec): Cvec {
val funcY = FastMath.sin(FastMath.PI * pulsateFuncX / pulsateCycleDuration) * pulsateRange
return alterBrightnessUniform(baseLum, funcY)
}
private fun getPulsate(baseLum: Float): Float {
return baseLum + FastMath.sin(FastMath.PI * pulsateFuncX / pulsateCycleDuration) * pulsateRange
}
/**
* Using our own timer so that they flickers for same duration regardless of game's FPS
*/
@@ -94,6 +103,20 @@ object BlockPropUtil {
}
}
/**
* @param chan 0 for R, 1 for G, 2 for B, 3 for A
*/
fun getDynamicLumFuncByChan(baseLum: Float, type: Int, chan: Int): Float {
return when (type) {
1 -> getTorchFlicker(baseLum)
2 -> (Terrarum.ingame!!.world).globalLight.cpy().mul(LightmapRenderer.DIV_FLOAT).getElem(chan) // current global light
3 -> WeatherMixer.getGlobalLightOfTime(Terrarum.ingame!!.world, WorldTime.DAY_LENGTH / 2).cpy().mul(LightmapRenderer.DIV_FLOAT).getElem(chan) // daylight at noon
4 -> getSlowBreath(baseLum)
5 -> getPulsate(baseLum)
else -> baseLum
}
}
/**
* Darken or brighten colour by 'brighten' argument
*

View File

@@ -4,6 +4,9 @@ import com.badlogic.gdx.utils.Disposable
import net.torvald.UnsafeHelper
/**
* As the fast access to this LUT is critical for the performance because of the way light calculation work,
* even the IO can be a bottleneck so I use UnsafePointer.
*
* Created by Torvald on 2019-12-05.
*/
internal object LightmapHDRMap : Disposable {

View File

@@ -20,7 +20,10 @@ import net.torvald.terrarum.gameactors.Luminous
import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.ui.abs
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.worlddrawer.LightmapRenderer.convX
import net.torvald.terrarum.worlddrawer.LightmapRenderer.convY
import kotlin.system.exitProcess
/**
@@ -226,6 +229,38 @@ object LightmapRenderer {
// when disabled, light will "decay out" instead of "instantly out", which can have a cool effect
// but the performance boost is measly 0.1 ms on 6700K
lightmap.zerofill()
// pre-seed the lightmap with known value
for (x in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) {
for (y in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) {
val tile = world.getTileFromTerrain(x, y)
val wall = world.getTileFromWall(x, y)
val lightlevel = if (!BlockCodex[tile].isSolid && !BlockCodex[wall].isSolid)
sunLight.cpy()
else
colourNull.cpy()
// are you a light source?
lightlevel.add(BlockCodex[tile].luminosity)
// TODO: add lanterns
// subtract attenuation value
if (!lightlevel.nonZero()) {
lightlevel.sub(
BlockCodex[tile].opacity
)
}
val lx = x.convX(); val ly = y.convY()
lightmap.setR(lx, ly, lightlevel.r)
lightmap.setG(lx, ly, lightlevel.g)
lightmap.setB(lx, ly, lightlevel.b)
lightmap.setA(lx, ly, lightlevel.a)
}
}
}
// O((5*9)n) == O(n) where n is a size of the map.
// Because of inevitable overlaps on the area, it only works with MAX blend
@@ -240,7 +275,7 @@ object LightmapRenderer {
AppLoader.measureDebugTime("Renderer.LightTotal") {
// Round 2
for (y in for_y_end + overscan_open downTo for_y_start) {
/*for (y in for_y_end + overscan_open downTo for_y_start) {
// TODO multithread the following for loop duh
for (x in for_x_start - overscan_open..for_x_end) {
calculateAndAssign(lightmap, x, y)
@@ -266,6 +301,69 @@ object LightmapRenderer {
for (x in for_x_start - overscan_open..for_x_end) {
calculateAndAssign(lightmap, x, y)
}
}*/
// per-channel operation for bit more aggressive optimisation
repeat(4) { rgbaOffset ->
val visitedCells = setOf(-1L) // (x shl 32) or y
for (x in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) {
for (y in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) {
val lx = x.convX()
val ly = y.convY()
val hash = (x.toLong() shl 32) or y.toLong()
if (visitedCells.contains(hash)) continue
var thisLightValue = lightmap.channelGet(lx, ly, rgbaOffset)
// nearby tiles, namely a b c d e f g h
val tilea = world.getTileFromTerrain(x - 1, y - 1)
val tileb = world.getTileFromTerrain(x, y - 1)
val tilec = world.getTileFromTerrain(x + 1, y - 1)
val tiled = world.getTileFromTerrain(x - 1, y)
val tilee = world.getTileFromTerrain(x + 1, y)
val tilef = world.getTileFromTerrain(x - 1, y + 1)
val tileg = world.getTileFromTerrain(x, y + 1)
val tileh = world.getTileFromTerrain(x + 1, y + 1)
// max value from plus-shaped neighbouring tiles
thisLightValue = maxOf(thisLightValue, BlockCodex[tileb].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tiled].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tilee].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tileg].getLum(rgbaOffset))
// max vaule from x-shaped neighbouring tiles
thisLightValue = maxOf(thisLightValue, BlockCodex[tilea].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tilec].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tilef].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tileh].getLum(rgbaOffset) * 0.70710678f)
lightmap.channelSet(lx, ly, rgbaOffset, thisLightValue)
// recurse condition
if (lightmap.channelGet(lx - 1, ly - 1, rgbaOffset) < thisLightValue)
spread(x - 1, y - 1, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx, ly - 1, rgbaOffset) < thisLightValue)
spread(x, y - 1, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx + 1, ly - 1, rgbaOffset) < thisLightValue)
spread(x + 1, y - 1, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx - 1, ly, rgbaOffset) < thisLightValue)
spread(x - 1, y, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx + 1, ly, rgbaOffset) < thisLightValue)
spread(x + 1, y, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx - 1, ly + 1, rgbaOffset) < thisLightValue)
spread(x - 1, y + 1, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx, ly + 1, rgbaOffset) < thisLightValue)
spread(x, y + 1, rgbaOffset, visitedCells, 0)
if (lightmap.channelGet(lx + 1, ly + 1, rgbaOffset) < thisLightValue)
spread(x + 1, y + 1, rgbaOffset, visitedCells, 0)
}
}
}
}
}
@@ -311,6 +409,69 @@ object LightmapRenderer {
// TODO re-init at every resize
private lateinit var updateMessages: List<Array<ThreadedLightmapUpdateMessage>>
/**
* @param x x position in the world
* @param y y position in the world
*/
private fun spread(x: Int, y: Int, rgbaOffset: Int, visitedCells: Set<Long>, recurseCount: Int) {
val lx = x.convX()
val ly = y.convY()
val hash = (x.toLong() shl 32) or y.toLong()
// stop condition
if (visitedCells.contains(hash)) return
if (x !in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) return
if (y !in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) return
if (recurseCount > 128) return
var thisLightValue = lightmap.channelGet(lx, ly, rgbaOffset)
// nearby tiles, namely a b c d e f g h
val tilea = world.getTileFromTerrain(x - 1, y - 1)
val tileb = world.getTileFromTerrain(x, y - 1)
val tilec = world.getTileFromTerrain(x + 1, y - 1)
val tiled = world.getTileFromTerrain(x - 1, y)
val tilee = world.getTileFromTerrain(x + 1, y)
val tilef = world.getTileFromTerrain(x - 1, y + 1)
val tileg = world.getTileFromTerrain(x, y + 1)
val tileh = world.getTileFromTerrain(x + 1, y + 1)
// max value from plus-shaped neighbouring tiles
thisLightValue = maxOf(thisLightValue, BlockCodex[tileb].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tiled].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tilee].getLum(rgbaOffset))
thisLightValue = maxOf(thisLightValue, BlockCodex[tileg].getLum(rgbaOffset))
// max vaule from x-shaped neighbouring tiles
thisLightValue = maxOf(thisLightValue, BlockCodex[tilea].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tilec].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tilef].getLum(rgbaOffset) * 0.70710678f)
thisLightValue = maxOf(thisLightValue, BlockCodex[tileh].getLum(rgbaOffset) * 0.70710678f)
lightmap.channelSet(lx, ly, rgbaOffset, thisLightValue)
// recurse condition
if (lightmap.channelGet(lx - 1, ly - 1, rgbaOffset) < thisLightValue)
spread(x - 1, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx, ly - 1, rgbaOffset) < thisLightValue)
spread(x, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx + 1, ly - 1, rgbaOffset) < thisLightValue)
spread(x + 1, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx - 1, ly, rgbaOffset) < thisLightValue)
spread(x - 1, y, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx + 1, ly, rgbaOffset) < thisLightValue)
spread(x + 1, y, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx - 1, ly + 1, rgbaOffset) < thisLightValue)
spread(x - 1, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx, ly + 1, rgbaOffset) < thisLightValue)
spread(x, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
if (lightmap.channelGet(lx + 1, ly + 1, rgbaOffset) < thisLightValue)
spread(x + 1, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
}
private fun makeUpdateTaskList() {
val lightTaskArr = ArrayList<ThreadedLightmapUpdateMessage>()
@@ -786,7 +947,10 @@ object LightmapRenderer {
hdr(this.a.coerceIn(0f, 1f))
)
private fun Cvec.nonZero() = this.r + this.g + this.b + this.a > epsilon
private fun Cvec.nonZero() = this.r.abs() > epsilon &&
this.g.abs() > epsilon &&
this.b.abs() > epsilon &&
this.a.abs() > epsilon
val histogram: Histogram
get() {