skybox atlas texture generation

This commit is contained in:
minjaesong
2023-08-01 16:50:37 +09:00
parent 0c00b3b7cc
commit 451808cd1c
13 changed files with 218 additions and 93 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -107,13 +107,13 @@ import kotlin.test.assertEquals
object DatasetCIEXYZ { object DatasetCIEXYZ {
val datasetXYZ1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ1.bin") val datasetXYZ1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ1.bin")
val datasetXYZ2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ2.bin") val datasetXYZ2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ2.bin")
val datasetXYZ3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ3.bin") val datasetXYZ3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ3.bin")
val datasetXYZRad1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad1.bin") val datasetXYZRad1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad1.bin")
val datasetXYZRad2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad2.bin") val datasetXYZRad2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad2.bin")
val datasetXYZRad3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad3.bin") val datasetXYZRad3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad3.bin")
init { init {
assertEquals(1080, datasetXYZ2.size, "Dataset size mismatch: expected 1080, got ${datasetXYZ2.size}") assertEquals(1080, datasetXYZ2.size, "Dataset size mismatch: expected 1080, got ${datasetXYZ2.size}")

View File

@@ -107,13 +107,13 @@ import kotlin.test.assertEquals
object DatasetRGB { object DatasetRGB {
val datasetRGB1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB1.bin") val datasetRGB1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB1.bin")
val datasetRGB2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB2.bin") val datasetRGB2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB2.bin")
val datasetRGB3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB3.bin") val datasetRGB3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB3.bin")
val datasetRGBRad1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad1.bin") val datasetRGBRad1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad1.bin")
val datasetRGBRad2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad2.bin") val datasetRGBRad2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad2.bin")
val datasetRGBRad3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad3.bin") val datasetRGBRad3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad3.bin")
init { init {
assertEquals(1080, datasetRGB2.size, "Dataset size mismatch: expected 1080, got ${datasetRGB2.size}") assertEquals(1080, datasetRGB2.size, "Dataset size mismatch: expected 1080, got ${datasetRGB2.size}")

View File

@@ -108,41 +108,41 @@ import kotlin.test.assertEquals
object DatasetSpectral { object DatasetSpectral {
val dataset320 = readDatasetFromFile("./assets/clut/hosek/dataset320.bin") val dataset320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset320.bin")
val dataset360 = readDatasetFromFile("./assets/clut/hosek/dataset360.bin") val dataset360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset360.bin")
val dataset400 = readDatasetFromFile("./assets/clut/hosek/dataset400.bin") val dataset400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset400.bin")
val dataset440 = readDatasetFromFile("./assets/clut/hosek/dataset440.bin") val dataset440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset440.bin")
val dataset480 = readDatasetFromFile("./assets/clut/hosek/dataset480.bin") val dataset480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset480.bin")
val dataset520 = readDatasetFromFile("./assets/clut/hosek/dataset520.bin") val dataset520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset520.bin")
val dataset560 = readDatasetFromFile("./assets/clut/hosek/dataset560.bin") val dataset560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset560.bin")
val dataset600 = readDatasetFromFile("./assets/clut/hosek/dataset600.bin") val dataset600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset600.bin")
val dataset640 = readDatasetFromFile("./assets/clut/hosek/dataset640.bin") val dataset640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset640.bin")
val dataset680 = readDatasetFromFile("./assets/clut/hosek/dataset680.bin") val dataset680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset680.bin")
val dataset720 = readDatasetFromFile("./assets/clut/hosek/dataset720.bin") val dataset720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset720.bin")
val datasetRad320 = readDatasetFromFile("./assets/clut/hosek/datasetRad320.bin") val datasetRad320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad320.bin")
val datasetRad360 = readDatasetFromFile("./assets/clut/hosek/datasetRad360.bin") val datasetRad360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad360.bin")
val datasetRad400 = readDatasetFromFile("./assets/clut/hosek/datasetRad400.bin") val datasetRad400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad400.bin")
val datasetRad440 = readDatasetFromFile("./assets/clut/hosek/datasetRad440.bin") val datasetRad440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad440.bin")
val datasetRad480 = readDatasetFromFile("./assets/clut/hosek/datasetRad480.bin") val datasetRad480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad480.bin")
val datasetRad520 = readDatasetFromFile("./assets/clut/hosek/datasetRad520.bin") val datasetRad520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad520.bin")
val datasetRad560 = readDatasetFromFile("./assets/clut/hosek/datasetRad560.bin") val datasetRad560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad560.bin")
val datasetRad600 = readDatasetFromFile("./assets/clut/hosek/datasetRad600.bin") val datasetRad600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad600.bin")
val datasetRad640 = readDatasetFromFile("./assets/clut/hosek/datasetRad640.bin") val datasetRad640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad640.bin")
val datasetRad680 = readDatasetFromFile("./assets/clut/hosek/datasetRad680.bin") val datasetRad680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad680.bin")
val datasetRad720 = readDatasetFromFile("./assets/clut/hosek/datasetRad720.bin") val datasetRad720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad720.bin")
val solarDataset320 = readDatasetFromFile("./assets/clut/hosek/solarDataset320.bin") val solarDataset320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset320.bin")
val solarDataset360 = readDatasetFromFile("./assets/clut/hosek/solarDataset360.bin") val solarDataset360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset360.bin")
val solarDataset400 = readDatasetFromFile("./assets/clut/hosek/solarDataset400.bin") val solarDataset400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset400.bin")
val solarDataset440 = readDatasetFromFile("./assets/clut/hosek/solarDataset440.bin") val solarDataset440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset440.bin")
val solarDataset480 = readDatasetFromFile("./assets/clut/hosek/solarDataset480.bin") val solarDataset480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset480.bin")
val solarDataset520 = readDatasetFromFile("./assets/clut/hosek/solarDataset520.bin") val solarDataset520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset520.bin")
val solarDataset560 = readDatasetFromFile("./assets/clut/hosek/solarDataset560.bin") val solarDataset560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset560.bin")
val solarDataset600 = readDatasetFromFile("./assets/clut/hosek/solarDataset600.bin") val solarDataset600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset600.bin")
val solarDataset640 = readDatasetFromFile("./assets/clut/hosek/solarDataset640.bin") val solarDataset640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset640.bin")
val solarDataset680 = readDatasetFromFile("./assets/clut/hosek/solarDataset680.bin") val solarDataset680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset680.bin")
val solarDataset720 = readDatasetFromFile("./assets/clut/hosek/solarDataset720.bin") val solarDataset720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset720.bin")
init { init {
assertEquals(1080, dataset600.size, "Dataset size mismatch - expected 1080, got ${dataset600.size}") assertEquals(1080, dataset600.size, "Dataset size mismatch - expected 1080, got ${dataset600.size}")

View File

@@ -241,7 +241,8 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
uiContainer.add(uiRemoCon) uiContainer.add(uiRemoCon)
CommandDict // invoke CommandDict // invoke
Skybox // invoke Skybox.loadlut() // invoke
// Skybox.initiate() // invoke the lengthy calculation
// TODO add console here // TODO add console here

View File

@@ -0,0 +1,102 @@
package net.torvald.terrarum.modulebasegame.clut
import net.torvald.colourutil.CIEXYZ
import net.torvald.colourutil.toColor
import net.torvald.colourutil.toRGB
import net.torvald.parametricsky.ArHosekSkyModel
import net.torvald.terrarum.abs
import net.torvald.terrarum.modulebasegame.clut.Skybox.coerceInSmoothly
import net.torvald.terrarum.modulebasegame.clut.Skybox.mapCircle
import net.torvald.terrarum.modulebasegame.clut.Skybox.scaleToFit
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
import net.torvald.terrarum.serialise.toLittle
import java.io.File
import kotlin.math.PI
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 = 2 * Skybox.elevCnt * 5
val TGA_HEADER_SIZE = 18
val bytes = ByteArray(TGA_HEADER_SIZE + texw * texh * 4 + 26)
// 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()
}
println("Generating texture atlas ($texw x $texh)...")
// write pixels
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 elevationDeg = Skybox.elevationsD[elev0]
val elevationRad = Math.toRadians(elevationDeg)
// println("... Elevation: $elevationDeg")
val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
for (yp in 0 until Skybox.gradSize) {
val yi = yp - 3
val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)
// experiments visualisation: https://www.desmos.com/calculator/5crifaekwa
// if (elevationDeg < 0) yf *= 1.0 - pow(xf, 0.333)
// if (elevationDeg < 0) yf *= -2.0 * asin(xf - 1.0) / PI
if (elevationDeg < 0) yf *= Skybox.superellipsoidDecay(1.0 / 3.0, xf)
val theta = yf * HALF_PI
// vertical angle, where 0 is zenith, ±90 is ground (which is odd)
// println("$yp\t$theta")
val xyz = CIEXYZ(
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 0).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 1).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 2).toFloat()
)
val xyz2 = xyz.scaleToFit(elevationDeg)
val rgb = xyz2.toRGB().toColor()
val colour = rgb.toIntBits().toLittle()
val imgOffX = 2 * (albedo0 * Skybox.elevCnt + elev0)
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
for (i in 0..7) {
bytes[fileOffset + i] = colour[bytesLut[i]]
}
}
}
}
}
println("Atlas generation done!")
File("./assets/mods/basegame/weathers/main_skybox.tga").writeBytes(bytes)
}
private val bytesLut = arrayOf(2,1,0,3,2,1,0,3) // For some reason BGRA order is what makes it work

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.modulebasegame.clut
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.colourutil.CIEXYZ import net.torvald.colourutil.CIEXYZ
@@ -10,8 +11,10 @@ import net.torvald.colourutil.toRGB
import net.torvald.parametricsky.ArHosekSkyModel import net.torvald.parametricsky.ArHosekSkyModel
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.abs import net.torvald.terrarum.abs
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.* import kotlin.math.*
/** /**
@@ -21,29 +24,39 @@ object Skybox : Disposable {
const val gradSize = 64 const val gradSize = 64
private val gradTexBinLowAlbedo: Array<Texture> private lateinit var gradTexBinLowAlbedo: Array<TextureRegion>
private val gradTexBinHighAlbedo: Array<Texture> private lateinit var gradTexBinHighAlbedo: Array<TextureRegion>
operator fun get(elevationDeg: Double, turbidity: Double, highAlbedo: Boolean = false): Texture { private lateinit var tex: Texture
// if (elevationDeg !in elevationsD) { private lateinit var texRegions: TextureRegionPack
// throw IllegalArgumentException("Elevation not in ±75° (got $elevationDeg)")
// }
// if (turbidity !in turbiditiesD) {
// throw IllegalArgumentException("Turbidity not in 1..10 (got $turbidity)")
// }
val elev = elevationDeg.coerceIn(elevationsD).toInt() - elevations.first fun loadlut() {
val turb = ((turbidity.coerceIn(turbiditiesD) - turbiditiesD.start) / (turbidities.step / 10.0)).toInt() tex = Texture(ModMgr.getGdxFile("basegame", "weathers/main_skybox.png"))
tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
texRegions = TextureRegionPack(tex, 2, gradSize - 2, 0, 2, 0, 1)
}
// printdbg(this, "$elevationDeg $turbidity ; $elev $turb") // use internal LUT
/*operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double): TextureRegion {
val elev = elevationDeg.coerceIn(-75.0, 75.0).times(2.0).roundToInt().plus(150)
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(3.0).roundToInt()
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(5.0).roundToInt()
return gradTexBinLowAlbedo[elev * turbCnt + turb]
}*/
return (if (highAlbedo) gradTexBinHighAlbedo else gradTexBinLowAlbedo)[elev * turbCnt + turb] // use external LUT
operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double): TextureRegion {
val elev = elevationDeg.coerceIn(-75.0, 75.0).roundToInt().plus(75)
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(3.0).roundToInt()
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(5.0).roundToInt()
//printdbg(this, "elev $elevationDeg->$elev; turb $turbidity->$turb; alb $albedo->$alb")
return texRegions.get(alb * elevCnt + elev, turb)
} }
private fun Float.scaleFun() = private fun Float.scaleFun() =
(1f - 1f / 2f.pow(this/6f)) * 0.97f (1f - 1f / 2f.pow(this/6f)) * 0.97f
private fun CIEXYZ.scaleToFit(elevationDeg: Double): CIEXYZ { internal fun CIEXYZ.scaleToFit(elevationDeg: Double): CIEXYZ {
return if (elevationDeg >= 0) { return if (elevationDeg >= 0) {
CIEXYZ( CIEXYZ(
this.X.scaleFun(), this.X.scaleFun(),
@@ -67,20 +80,21 @@ object Skybox : Disposable {
} }
} }
private val elevations = (-75..75) //zw 151 val elevations = (0..150) //
private val elevationsD = (elevations.first.toDouble() .. elevations.last.toDouble()) val elevationsD = elevations.map { -75.0 + it } // -75, -74, -73, ..., 74, 75 // (specifically using whole number of angles because angle units any finer than 1.0 would make "hack" sunsut happen too fast)
private val turbidityStep = 5 val turbidities = (0..27) // 1, 1.333, 1.666, 2, 2,333, ... , 10.0
private val turbidities = (1_0..10_0 step turbidityStep) // (100 / turbidityStep) - 1 val turbiditiesD = turbidities.map { 1.0 + it / 3.0 }
private val turbiditiesD = (turbidities.first / 10.0..turbidities.last / 10.0) val albedos = arrayOf(0.1, 0.3, 0.5, 0.7, 0.9)
private val elevCnt = elevations.count() val elevCnt = elevations.count()
private val turbCnt = turbidities.count() val turbCnt = turbidities.count()
private val albedoLow = 0.1 val albedoCnt = albedos.size
private val albedoHight = 0.8 // for theoretical "winter wonderland"? val albedoLow = 0.1
private val gamma = HALF_PI val albedoHight = 0.8 // for theoretical "winter wonderland"?
val gamma = HALF_PI
private fun Double.mapCircle() = sin(HALF_PI * this) internal fun Double.mapCircle() = sin(HALF_PI * this)
init { internal fun initiate() {
printdbg(this, "Initialising skybox model") printdbg(this, "Initialising skybox model")
gradTexBinLowAlbedo = getTexturmaps(albedoLow) gradTexBinLowAlbedo = getTexturmaps(albedoLow)
@@ -97,7 +111,7 @@ object Skybox : Disposable {
* @param q polynomial degree. 2+. Larger value means sharper transition around the point p * @param q polynomial degree. 2+. Larger value means sharper transition around the point p
* @param x the 'x' value of the function, as in `y=f(x)`. 0.0..1.0 * @param x the 'x' value of the function, as in `y=f(x)`. 0.0..1.0
*/ */
private fun polynomialDecay(p: Double, q: Int, x: Double): Double { internal fun polynomialDecay(p: Double, q: Int, x: Double): Double {
val sign = if (q % 2 == 1) -1 else 1 val sign = if (q % 2 == 1) -1 else 1
val a1 = -1.0 / p val a1 = -1.0 / p
val a2 = 1.0 / (1.0 - p) val a2 = 1.0 / (1.0 - p)
@@ -108,7 +122,7 @@ object Skybox : Disposable {
sign * a2.pow(q - 1.0) * (x - 1.0).pow(q) sign * a2.pow(q - 1.0) * (x - 1.0).pow(q)
} }
private fun polynomialDecay2(p: Double, q: Int, x: Double): Double { internal fun polynomialDecay2(p: Double, q: Int, x: Double): Double {
val sign = if (q % 2 == 1) 1 else -1 val sign = if (q % 2 == 1) 1 else -1
val a1 = -1.0 / p val a1 = -1.0 / p
val a2 = 1.0 / (1.0 - p) val a2 = 1.0 / (1.0 - p)
@@ -119,11 +133,11 @@ object Skybox : Disposable {
sign * a2.pow(q - 1.0) * (x - 1.0).pow(q) + 1.0 sign * a2.pow(q - 1.0) * (x - 1.0).pow(q) + 1.0
} }
private fun superellipsoidDecay(p: Double, x: Double): Double { internal fun superellipsoidDecay(p: Double, x: Double): Double {
return 1.0 - (1.0 - (1.0 - x).pow(1.0 / p)).pow(p) return 1.0 - (1.0 - (1.0 - x).pow(1.0 / p)).pow(p)
} }
private fun Double.coerceInSmoothly(low: Double, high: Double): Double { internal fun Double.coerceInSmoothly(low: Double, high: Double): Double {
val x = this.coerceIn(low, high) val x = this.coerceIn(low, high)
val x2 = ((x - low) * (high - low).pow(-1.0)) val x2 = ((x - low) * (high - low).pow(-1.0))
// return FastMath.interpolateLinear(polynomialDecay2(0.5, 2, x2), low, high) // return FastMath.interpolateLinear(polynomialDecay2(0.5, 2, x2), low, high)
@@ -133,25 +147,26 @@ object Skybox : Disposable {
/** /**
* To get the idea what the fuck is going on here, please refer to https://www.desmos.com/calculator/snqglcu2wl * To get the idea what the fuck is going on here, please refer to https://www.desmos.com/calculator/snqglcu2wl
*/ */
private fun smoothLinear(p: Double, x0: Double): Double { internal fun smoothLinear(p: Double, x0: Double): Double {
val x = x0 - 0.5 val x = x0 - 0.5
val t = 0.5 * sqrt(1.0 - 2.0 * p) val p1 = sqrt(1.0 - 2.0 * p)
val t = 0.5 * p1
val y0 = if (x < -t) val y0 = if (x < -t)
(1.0 / p) * (x + 0.5).pow(2) - 0.5 (1.0 / p) * (x + 0.5).pow(2) - 0.5
else if (x > t) else if (x > t)
-(1.0 / p) * (x - 0.5).pow(2) + 0.5 -(1.0 / p) * (x - 0.5).pow(2) + 0.5
else else
x * 2.0 / (1.0 + sqrt(1.0 - 2.0 * p)) x * 2.0 / (1.0 + p1)
return y0 + 0.5 return y0 + 0.5
} }
private fun getTexturmaps(albedo: Double): Array<Texture> { private fun getTexturmaps(albedo: Double): Array<TextureRegion> {
return Array(elevCnt * turbCnt) { return Array(elevCnt * turbCnt) {
val elevationDeg = (it / turbCnt).plus(elevations.first).toDouble() val elevationDeg = elevationsD[it / turbCnt]
val elevationRad = Math.toRadians(elevationDeg) val elevationRad = Math.toRadians(elevationDeg)
val turbidity = 1.0 + (it % turbCnt) / (10.0 / turbidityStep) val turbidity = turbiditiesD[it % turbCnt]
val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs()) val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
val pixmap = Pixmap(1, gradSize, Pixmap.Format.RGBA8888) val pixmap = Pixmap(1, gradSize, Pixmap.Format.RGBA8888)
@@ -161,15 +176,17 @@ object Skybox : Disposable {
for (yp in 0 until gradSize) { for (yp in 0 until gradSize) {
val yi = yp - 3 val yi = yp - 3
val xf = -elevationDeg / 90.0 val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceInSmoothly(0.0, 0.95) var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)
// experiments visualisation: https://www.desmos.com/calculator/5crifaekwa // experiments visualisation: https://www.desmos.com/calculator/5crifaekwa
// if (elevationDeg < 0) yf *= 1.0 - pow(xf, 0.333) // if (elevationDeg < 0) yf *= 1.0 - pow(xf, 0.333)
// if (elevationDeg < 0) yf *= -2.0 * asin(xf - 1.0) / PI // if (elevationDeg < 0) yf *= -2.0 * asin(xf - 1.0) / PI
if (elevationDeg < 0) yf *= superellipsoidDecay(1.0 / 3.0, xf) if (elevationDeg < 0) yf *= superellipsoidDecay(1.0 / 3.0, xf)
val theta = (yf.mapCircle() * HALF_PI) val theta = yf * HALF_PI
// vertical angle, where 0 is zenith, ±90 is ground (which is odd) // vertical angle, where 0 is zenith, ±90 is ground (which is odd)
// println("$yp\t$theta")
val xyz = CIEXYZ( val xyz = CIEXYZ(
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 0).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 0).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(),
@@ -187,12 +204,13 @@ object Skybox : Disposable {
it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
} }
pixmap.dispose() pixmap.dispose()
texture TextureRegion(texture)
} }
} }
override fun dispose() { override fun dispose() {
gradTexBinLowAlbedo.forEach { it.dispose() } if (::gradTexBinLowAlbedo.isInitialized) gradTexBinLowAlbedo.forEach { it.texture.dispose() }
gradTexBinHighAlbedo.forEach { it.dispose() } if (::gradTexBinHighAlbedo.isInitialized) gradTexBinHighAlbedo.forEach { it.texture.dispose() }
if (::tex.isInitialized) tex.dispose()
} }
} }

View File

@@ -152,7 +152,7 @@ internal object WeatherMixer : RNGConsumer {
} }
var turbidity = 4.0; private set var turbidity = 4.0; private set
private var gH = (4f/3f) * App.scr.height private var gH = 1.4f * App.scr.height
// private var gH = 0.8f * App.scr.height // private var gH = 0.8f * App.scr.height
internal var parallaxPos = 0f; private set internal var parallaxPos = 0f; private set
@@ -195,13 +195,17 @@ internal object WeatherMixer : RNGConsumer {
gdxBlendNormalStraightAlpha() gdxBlendNormalStraightAlpha()
val degThis = if (timeNow < HALF_DAY) solarElev.floorToDouble() else solarElev.ceilToDouble() val degThis = if (timeNow < HALF_DAY)
solarElev.floorToDouble()
else
solarElev.ceilToDouble()
val degNext = degThis + if (timeNow < HALF_DAY) 1 else -1 // Skybox.get has internal coerceIn val degNext = degThis + if (timeNow < HALF_DAY) 1 else -1 // Skybox.get has internal coerceIn
val thisTurbidity = forceTurbidity ?: turbidity val thisTurbidity = forceTurbidity ?: turbidity
val texture1 = Skybox[degThis, thisTurbidity] // TODO trilinear with (deg, turb, alb)
val texture2 = Skybox[degNext, thisTurbidity] val texture1 = Skybox[degThis, thisTurbidity, 0.1]
val texture2 = Skybox[degNext, thisTurbidity, 0.1]
val lerpScale = (if (timeNow < HALF_DAY) solarElev - degThis else -(solarElev - degThis)).toFloat() val lerpScale = (if (timeNow < HALF_DAY) solarElev - degThis else -(solarElev - degThis)).toFloat()
// println("degThis=$degThis, degNext=$degNext, lerp=$lerpScale") // println("degThis=$degThis, degNext=$degNext, lerp=$lerpScale")
@@ -210,10 +214,10 @@ internal object WeatherMixer : RNGConsumer {
batch.inUse { batch.inUse {
batch.shader = null batch.shader = null
batch.color = Color.WHITE batch.color = Color.WHITE
batch.draw(texture1, 0f, gradY, App.scr.wf, gH) batch.draw(texture1, -App.scr.halfwf, gradY, 2f * App.scr.wf, gH)
batch.color = Color(1f, 1f, 1f, lerpScale) batch.color = Color(1f, 1f, 1f, lerpScale)
batch.draw(texture2, 0f, gradY, App.scr.wf, gH) batch.draw(texture2, -App.scr.halfwf, gradY, 2f * App.scr.wf, gH)
batch.color = Color.WHITE batch.color = Color.WHITE
} }