cloud colour dependent on the skybox and daylight

This commit is contained in:
minjaesong
2023-09-22 17:03:44 +09:00
parent e83b2d1908
commit 556c21e82a
5 changed files with 256 additions and 77 deletions

BIN
assets/clut/skyboxavr.png LFS Normal file

Binary file not shown.

View File

@@ -73,6 +73,10 @@ class GdxColorMap {
fun get(x: Int, y: Int): Color = dataGdxColor[y * width + x]
operator fun get(x: Int): Color = if (is2D) throw UnsupportedOperationException("This is 2D color map") else dataGdxColor[x]
fun getCvec(x: Int, y: Int): Cvec = dataCvec[y * width + x]
fun getCvec(x: Int): Cvec = if (is2D) throw UnsupportedOperationException("This is 2D color map") else dataCvec[x]
fun getRaw(x: Int, y: Int): RGBA8888 = dataRaw[y * width + x]
fun getRaw(x: Int): RGBA8888 = if (is2D) throw UnsupportedOperationException("This is 2D color map") else dataRaw[x]

View File

@@ -10,27 +10,30 @@ import net.torvald.terrarum.clut.Skybox.mapCircle
import net.torvald.terrarum.clut.Skybox.scaleToFit
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
import net.torvald.terrarum.serialise.toLittle
import net.torvald.terrarum.serialise.toUint
import net.torvald.terrarum.sqrt
import java.io.File
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.roundToInt
/**
* Created by minjaesong on 2023-08-01.
*/
fun main() {
// y: increasing turbidity (1.0 .. 10.0, in steps of 0.333)
// x: elevations (-75 .. 75 in steps of 1, then albedo of [0.1, 0.3, 0.5, 0.7, 0.9])
val texh = Skybox.gradSize * Skybox.turbCnt
val texw = Skybox.elevCnt * Skybox.albedoCnt * 2
val TGA_HEADER_SIZE = 18
class GenerateSkyboxTextureAtlas {
val bytes = ByteArray(TGA_HEADER_SIZE + texw * texh * 4 + 26)
fun generateStrip(gammaPair: Int, albedo: Double, turbidity: Double, elevationDeg: Double, writefun: (Int, Int, Byte) -> Unit) {
fun generateStrip(
gammaPair: Int,
albedo: Double,
turbidity: Double,
elevationDeg: Double,
writefun: (Int, Int, Byte) -> Unit
) {
val elevationRad = Math.toRadians(elevationDeg)
/*val gamma = if (gammaPair == 0) HALF_PI else {
Math.toRadians(180 + 114 + 24 * cos(PI * elevationDeg / 40))
}*/
Math.toRadians(180 + 114 + 24 * cos(PI * elevationDeg / 40))
}*/
val gamma = Math.toRadians(115 + 25 * cos(PI * elevationDeg / 40)) + (gammaPair * PI)
// println("... Elevation: $elevationDeg")
@@ -66,53 +69,147 @@ fun main() {
}
}
// write header
byteArrayOf(
0, // ID field
0, // colour map (none)
2, // colour type (unmapped RGB)
0,0,0,0,0, // colour map spec (empty)
0,0, // x origin (0)
0,0, // y origin (0)
(texw and 255).toByte(),(texw.ushr(8) and 255).toByte(), // width
(texh and 255).toByte(),(texh.ushr(8) and 255).toByte(), // height
32, // bits-per-pixel (8bpp RGBA)
8 // image descriptor
).forEachIndexed { i,b -> bytes[i] = b }
// write footer
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000TRUEVISION-XFILE\u002E\u0000".forEachIndexed { i, c -> bytes[18 + texw * texh * 4 + i] =
c.code.toByte()
}
// y: increasing turbidity (1.0 .. 10.0, in steps of 0.333)
// x: elevations (-75 .. 75 in steps of 1, then albedo of [0.1, 0.3, 0.5, 0.7, 0.9])
val TGA_HEADER_SIZE = 18
val texh = Skybox.gradSize * Skybox.turbCnt
val texh2 = Skybox.turbCnt
val texw = Skybox.elevCnt * Skybox.albedoCnt * 2
val bytesSize = texw * texh
val bytes2Size = texw * texh2
val bytes = ByteArray(TGA_HEADER_SIZE + bytesSize * 4 + 26)
val bytes2 = ByteArray(TGA_HEADER_SIZE + texw * bytes2Size * 4 + 26)
println("Generating texture atlas ($texw x $texh)...")
fun generateMainFile() {
// write header
byteArrayOf(
0, // ID field
0, // colour map (none)
2, // colour type (unmapped RGB)
0, 0, 0, 0, 0, // colour map spec (empty)
0, 0, 0, 0, // unused for modern purposes
(texw and 255).toByte(), (texw.ushr(8) and 255).toByte(), // width
(texh and 255).toByte(), (texh.ushr(8) and 255).toByte(), // height
32, // bits-per-pixel (8bpp RGBA)
8 // image descriptor (32bpp, bottom-left origin)
).forEachIndexed { i, b -> bytes[i] = b }
// write footer
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000TRUEVISION-XFILE\u002E\u0000".forEachIndexed { i, c ->
bytes[TGA_HEADER_SIZE + bytesSize * 4 + i] =
c.code.toByte()
}
// write pixels
for (gammaPair in 0..1) {
println("Generating texture atlas ($texw x $texh)...")
for (albedo0 in 0 until Skybox.albedoCnt) {
val albedo = Skybox.albedos[albedo0]
println("Albedo=$albedo")
for (turb0 in 0 until Skybox.turbCnt) {
val turbidity = Skybox.turbiditiesD[turb0]
println("....... Turbidity=$turbidity")
for (elev0 in 0 until Skybox.elevCnt) {
var elevationDeg = Skybox.elevationsD[elev0]
if (elevationDeg == 0.0) elevationDeg = 0.5 // dealing with the edge case
generateStrip(gammaPair, albedo, turbidity, elevationDeg) { yp, i, colour ->
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
bytes[fileOffset + i] = colour
// write pixels
for (gammaPair in 0..1) {
for (albedo0 in 0 until Skybox.albedoCnt) {
val albedo = Skybox.albedos[albedo0]
println("Albedo=$albedo")
for (turb0 in 0 until Skybox.turbCnt) {
val turbidity = Skybox.turbiditiesD[turb0]
println("....... Turbidity=$turbidity")
for (elev0 in 0 until Skybox.elevCnt) {
var elevationDeg = Skybox.elevationsD[elev0]
if (elevationDeg == 0.0) elevationDeg = 0.5 // dealing with the edge case
generateStrip(gammaPair, albedo, turbidity, elevationDeg) { yp, i, colour ->
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
bytes[fileOffset + i] = colour
}
}
}
}
}
println("Atlas generation done!")
File("./assets/clut/skybox.tga").writeBytes(bytes)
}
println("Atlas generation done!")
private val gradSizes = (0 until Skybox.gradSize)
File("./assets/clut/skybox.tga").writeBytes(bytes)
private fun getByte(gammaPair: Int, albedo0: Int, turb0: Int, elev0: Int, yp: Int, channel: Int): Byte {
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
return bytes[fileOffset + channel]
}
fun generateCloudColourmap() {
if (bytes[TGA_HEADER_SIZE].toInt() == 0) throw IllegalStateException("Atlas not generated")
// write header
byteArrayOf(
0, // ID field
0, // colour map (none)
2, // colour type (unmapped RGB)
0, 0, 0, 0, 0, // colour map spec (empty)
0, 0, 0, 0, // unused for modern purposes
(texw and 255).toByte(), (texw.ushr(8) and 255).toByte(), // width
(texh2 and 255).toByte(), (texh2.ushr(8) and 255).toByte(), // height
32, // bits-per-pixel (8bpp RGBA)
8 // image descriptor (32bpp, bottom-left origin)
).forEachIndexed { i, b -> bytes2[i] = b }
// write footer
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000TRUEVISION-XFILE\u002E\u0000".forEachIndexed { i, c ->
bytes2[TGA_HEADER_SIZE + bytes2Size * 4 + i] =
c.code.toByte()
}
println("Generating cloud colour atlas ($texw x $texh2)...")
for (gammaPair in 0..1) {
for (albedo0 in 0 until Skybox.albedoCnt) {
val albedo = Skybox.albedos[albedo0]
println("Albedo=$albedo")
for (turb0 in 0 until Skybox.turbCnt) {
val turbidity = Skybox.turbiditiesD[turb0]
println("....... Turbidity=$turbidity")
for (elev0 in 0 until Skybox.elevCnt) {
val pixelValueAvrB = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 0).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().times(255.0).roundToInt().toByte()
val pixelValueAvrG = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 1).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().times(255.0).roundToInt().toByte()
val pixelValueAvrR = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 2).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().times(255.0).roundToInt().toByte()
val pixelValueAvrA = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 3).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().times(255.0).roundToInt().toByte()
val colour = arrayOf(pixelValueAvrB, pixelValueAvrG, pixelValueAvrR, pixelValueAvrA)
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
val imgOffY = texh2 - 1 - turb0
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
for (i in 0..3) {
bytes2[fileOffset + i] = colour[i]
}
}
}
}
}
println("Colourmap generation done!")
File("./assets/clut/skyboxavr.tga").writeBytes(bytes2)
}
fun invoke() {
generateMainFile()
generateCloudColourmap()
}
private fun Double.srgbLinearise(): Double {
return if (this > 0.0031308)
1.055 * this.pow(1 / 2.4) - 0.055
else
this * 12.92
}
private val bytesLut = arrayOf(2, 1, 0, 3, 2, 1, 0, 3) // For some reason BGRA order is what makes it work
}
private val bytesLut = arrayOf(2,1,0,3,2,1,0,3) // For some reason BGRA order is what makes it work
fun main() {
GenerateSkyboxTextureAtlas().invoke()
}

View File

@@ -20,6 +20,8 @@ import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH
import net.torvald.terrarum.RNGConsumer
import net.torvald.terrarum.clut.Skybox
import net.torvald.terrarum.clut.Skybox.elevCnt
import net.torvald.terrarum.spriteassembler.ADPropertyObject
import net.torvald.terrarum.utils.JsonFetcher
import net.torvald.terrarum.utils.forEachSiblings
import net.torvald.terrarum.weather.WeatherObjectCloud.Companion.ALPHA_ROLLOFF_Z
@@ -94,7 +96,7 @@ internal object WeatherMixer : RNGConsumer {
var forceTurbidity: Double? = null
// doesn't work if the png is in greyscale/indexed mode
val starmapTex: TextureRegion = TextureRegion(Texture(Gdx.files.internal("./assets/graphics/astrum.png"))).also {
val starmapTex: TextureRegion = TextureRegion(Texture(Gdx.files.internal("assets/graphics/astrum.png"))).also {
it.texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
it.texture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat)
}
@@ -105,7 +107,6 @@ internal object WeatherMixer : RNGConsumer {
private var astrumOffX = 0f
private var astrumOffY = 0f
// Clouds are merely a response to the current Weatherbox status //
private val clouds = SortedArrayList<WeatherObjectCloud>()
@@ -115,6 +116,10 @@ internal object WeatherMixer : RNGConsumer {
val cloudSpawnMax: Int
get() = 256 shl (App.getConfigInt("maxparticles") / 256)
private val skyboxavr = GdxColorMap(Gdx.files.internal("assets/clut/skyboxavr.png"))
override fun loadFromSave(ingame: IngameInstance, s0: Long, s1: Long) {
super.loadFromSave(ingame, s0, s1)
internalReset(s0, s1)
@@ -498,6 +503,10 @@ internal object WeatherMixer : RNGConsumer {
private var turbidity1 = 1.0
/** Interpolated value, controlled by the weatherbox */
var turbidity = 1.0; private set
/** Controlled by todo: something that monitors ground tile compisition */
var albedo = 1.0; private set
private var gH = 1.8f * App.scr.height
// private var gH = 0.8f * App.scr.height
@@ -520,6 +529,10 @@ internal object WeatherMixer : RNGConsumer {
batch.color = Color.WHITE
}
/**
* Dependent on the `drawSkybox(camera, batch, world)` for the `cloudDrawColour`
*
*/
private fun drawClouds(batch: SpriteBatch) {
batch.inUse { _ ->
batch.shader = shaderClouds
@@ -546,11 +559,9 @@ internal object WeatherMixer : RNGConsumer {
val daylightClut = currentWeather.daylightClut
// calculate global light
val moonSize = (-(2.0 * world.worldTime.moonPhase - 1.0).abs() + 1.0).toFloat()
val globalLightBySun: Cvec = getGradientColour2(daylightClut, solarElev, timeNow, GradientColourMode.DAYLIGHT)
val globalLightBySun: Cvec = getGradientColour2(daylightClut, solarElev, timeNow)
val globalLightByMoon: Cvec = moonlightMax * moonSize
val cloudCol = getGradientColour2(daylightClut, solarElev, timeNow, GradientColourMode.CLOUD_COLOUR)
globalLightNow.set(globalLightBySun max globalLightByMoon)
cloudDrawColour.set(cloudCol max globalLightByMoon)
/* (copied from the shader source)
UV mapping coord.y
@@ -574,16 +585,27 @@ internal object WeatherMixer : RNGConsumer {
val mornNoonBlend = (1f/4000f * (timeNow - 43200) + 0.5f).coerceIn(0f, 1f) // 0.0 at T41200; 0.5 at T43200; 1.0 at T45200;
turbidity0 = (world.weatherbox.oldWeather.json.getDouble("atmoTurbidity") + turbidityCoeff * 2.5).coerceIn(1.0, 10.0)
turbidity1 = (currentWeather.json.getDouble("atmoTurbidity") + turbidityCoeff * 2.5).coerceIn(1.0, 10.0)
turbidity1 = (currentWeather.json.getDouble("atmoTurbidity") + turbidityCoeff * 2.5).coerceIn(1.0, 10.0)
turbidity = FastMath.interpolateLinear(oldNewBlend.toDouble(), turbidity0, turbidity1)
val oldTurbidity = forceTurbidity ?: turbidity0
val thisTurbidity = forceTurbidity ?: turbidity1
albedo = 0.3 // TODO() depends on the ground tile composition
val oldAlbedo = forceTurbidity ?: turbidity0
val thisAlbedo = forceTurbidity ?: turbidity1
val cloudCol1 = getGradientCloud(skyboxavr, solarElev, mornNoonBlend.toDouble(), turbidity, albedo)
cloudDrawColour.set(lerp(0.5, cloudCol1, globalLightNow))
val gradY = -(gH - App.scr.height) * ((parallax + 1f) / 2f)
val (tex, uvs, turbTihsBlend, albThisBlend, turbOldBlend, albOldBlend) = Skybox.getUV(solarElev, oldTurbidity, 0.3, thisTurbidity, 0.3)
val (tex, uvs, turbTihsBlend, albThisBlend, turbOldBlend, albOldBlend) = Skybox.getUV(solarElev, oldTurbidity, oldAlbedo, thisTurbidity, thisAlbedo)
starmapTex.texture.bind(1)
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
@@ -672,7 +694,7 @@ internal object WeatherMixer : RNGConsumer {
return Cvec(newCol)
}
fun getGradientColour2(colorMap: GdxColorMap, solarAngleInDeg: Double, timeOfDay: Int, mode: GradientColourMode): Cvec {
fun getGradientColour2(colorMap: GdxColorMap, solarAngleInDeg: Double, timeOfDay: Int): Cvec {
val pNowRaw = (solarAngleInDeg + 75.0) / 150.0 * colorMap.width
val pStartRaw = pNowRaw.floorToInt()
@@ -695,27 +717,80 @@ internal object WeatherMixer : RNGConsumer {
var scale = (pNowRaw - pStartRaw).toFloat()
if (timeOfDay >= HALF_DAY) scale = 1f - scale
return when (mode) {
GradientColourMode.DAYLIGHT -> {
val colourThisRGB = colorMap.get(pSx, pSy)
val colourNextRGB = colorMap.get(pNx, pNy)
val colourThisUV = colorMap.get(pSx, pSy + 2)
val colourNextUV = colorMap.get(pNx, pNy + 2)
val colourThisRGB = colorMap.get(pSx, pSy)
val colourNextRGB = colorMap.get(pNx, pNy)
val colourThisUV = colorMap.get(pSx, pSy + 2)
val colourNextUV = colorMap.get(pNx, pNy + 2)
val newColRGB = colourThisRGB.cpy().lerp(colourNextRGB, scale)//CIELuvUtil.getGradient(scale, colourThis, colourNext)
val newColUV = colourThisUV.cpy().lerp(colourNextUV, scale)//CIELuvUtil.getGradient(scale, colourThis, colourNext)
val newColRGB = colourThisRGB.cpy().lerp(colourNextRGB, scale)//CIELuvUtil.getGradient(scale, colourThis, colourNext)
val newColUV = colourThisUV.cpy().lerp(colourNextUV, scale)//CIELuvUtil.getGradient(scale, colourThis, colourNext)
Cvec(newColRGB, newColUV.r)
}
GradientColourMode.CLOUD_COLOUR -> {
val colourThisRGB = colorMap.get(pSx, pSy + 4)
val colourNextRGB = colorMap.get(pNx, pNy + 4)
return Cvec(newColRGB, newColUV.r)
}
val newColRGB = colourThisRGB.cpy().lerp(colourNextRGB, scale)//CIELuvUtil.getGradient(scale, colourThis, colourNext)
fun getGradientCloud(colorMap: GdxColorMap, solarAngleInDeg: Double, mornNoonBlend: Double, turbidity: Double, albedo: Double): Cvec {
// fine-grained
val angleX1 = solarAngleInDeg.toInt() + 75
val angleX2 = (angleX1 + 1).coerceAtMost(150)
val ax = solarAngleInDeg % 1.0
// fine-grained
val turbY = turbidity.coerceIn(Skybox.turbiditiesD.first(), Skybox.turbiditiesD.last()).minus(1.0).times(Skybox.turbDivisor)
val turbY1 = turbY.floorToInt()
val turbY2 = (turbY1).coerceAtMost(Skybox.turbCnt - 1)
val tx = turbY - turbY1
// coarse-grained
val albX = albedo.coerceIn(Skybox.albedos.first(), Skybox.albedos.last()).times(5.0) * Skybox.elevCnt // 0*151..5*151
val albX1 = albX.floorToInt()
val albX2 = (albX1 + elevCnt).coerceAtMost(5 * Skybox.elevCnt)
val bx = albX - albX1
Cvec(newColRGB)
}
}
val a1t1b1A = colorMap.getCvec(albX1 * elevCnt + angleX1, turbY1)
val a2t1b1A = colorMap.getCvec(albX1 * elevCnt + angleX2, turbY1)
val a1t2b1A = colorMap.getCvec(albX1 * elevCnt + angleX1, turbY2)
val a2t2b1A = colorMap.getCvec(albX1 * elevCnt + angleX2, turbY2)
val a1t1b2A = colorMap.getCvec(albX2 * elevCnt + angleX1, turbY1)
val a2t1b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY1)
val a1t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX1, turbY2)
val a2t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY2)
val a1t1b1B = colorMap.getCvec(albX1 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY1)
val a2t1b1B = colorMap.getCvec(albX1 * elevCnt + angleX2 + Skybox.albedoCnt * elevCnt, turbY1)
val a1t2b1B = colorMap.getCvec(albX1 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY2)
val a2t2b1B = colorMap.getCvec(albX1 * elevCnt + angleX2 + Skybox.albedoCnt * elevCnt, turbY2)
val a1t1b2B = colorMap.getCvec(albX2 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY1)
val a2t1b2B = colorMap.getCvec(albX2 * elevCnt + angleX2 + Skybox.albedoCnt * elevCnt, turbY1)
val a1t2b2B = colorMap.getCvec(albX2 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY2)
val a2t2b2B = colorMap.getCvec(albX2 * elevCnt + angleX2 + Skybox.albedoCnt * elevCnt, turbY2)
val t1b1A = lerp(ax, a1t1b1A, a2t1b1A)
val t2b1A = lerp(ax, a1t2b1A, a2t2b1A)
val t1b2A = lerp(ax, a1t1b2A, a2t1b2A)
val t2b2A = lerp(ax, a1t2b2A, a2t2b2A)
val b1A = lerp(tx, t1b1A, t2b1A)
val b2A = lerp(tx, t1b2A, t2b2A)
val A = lerp(bx, b1A, b2A)
val t1b1B = lerp(ax, a1t1b1B, a2t1b1B)
val t2b1B = lerp(ax, a1t2b1B, a2t2b1B)
val t1b2B = lerp(ax, a1t1b2B, a2t1b2B)
val t2b2B = lerp(ax, a1t2b2B, a2t2b2B)
val b1B = lerp(tx, t1b1B, t2b1B)
val b2B = lerp(tx, t1b2B, t2b2B)
val B = lerp(bx, b1B, b2B)
return lerp(mornNoonBlend, A, B)
}
private fun lerp(x: Double, c1: Cvec, c2: Cvec): Cvec {
val r = (((1.0 - x) * c1.r) + (x * c2.r)).toFloat()
val g = (((1.0 - x) * c1.g) + (x * c2.g)).toFloat()
val b = (((1.0 - x) * c1.b) + (x * c2.b)).toFloat()
val a = (((1.0 - x) * c1.a) + (x * c2.a)).toFloat()
return Cvec(r, g, b, a)
}
fun getWeatherList(classification: String) = weatherDB[classification]!!