mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-13 20:14:05 +09:00
new cloud colour model
This commit is contained in:
BIN
assets/clut/skyboxavr.png
LFS
BIN
assets/clut/skyboxavr.png
LFS
Binary file not shown.
@@ -1,8 +1,10 @@
|
|||||||
package net.torvald.terrarum.clut
|
package net.torvald.terrarum.clut
|
||||||
|
|
||||||
import net.torvald.colourutil.CIEXYZ
|
import net.torvald.colourutil.CIEXYZ
|
||||||
|
import net.torvald.colourutil.HUSLColorConverter
|
||||||
import net.torvald.colourutil.toColor
|
import net.torvald.colourutil.toColor
|
||||||
import net.torvald.colourutil.toRGB
|
import net.torvald.colourutil.toRGB
|
||||||
|
import net.torvald.gdx.graphics.Cvec
|
||||||
import net.torvald.parametricsky.ArHosekSkyModel
|
import net.torvald.parametricsky.ArHosekSkyModel
|
||||||
import net.torvald.terrarum.abs
|
import net.torvald.terrarum.abs
|
||||||
import net.torvald.terrarum.clut.Skybox.coerceInSmoothly
|
import net.torvald.terrarum.clut.Skybox.coerceInSmoothly
|
||||||
@@ -11,7 +13,6 @@ import net.torvald.terrarum.clut.Skybox.scaleToFit
|
|||||||
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
|
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
|
||||||
import net.torvald.terrarum.serialise.toLittle
|
import net.torvald.terrarum.serialise.toLittle
|
||||||
import net.torvald.terrarum.serialise.toUint
|
import net.torvald.terrarum.serialise.toUint
|
||||||
import net.torvald.terrarum.sqrt
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
@@ -170,19 +171,26 @@ class GenerateSkyboxTextureAtlas {
|
|||||||
println("....... Turbidity=$turbidity")
|
println("....... Turbidity=$turbidity")
|
||||||
for (elev0 in 0 until Skybox.elevCnt) {
|
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 avrB = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 0).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().toFloat()
|
||||||
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 avrG = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 1).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().toFloat()
|
||||||
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 avrR = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 2).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().toFloat()
|
||||||
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 avrA = (gradSizes.sumOf { getByte(gammaPair, albedo0, turb0, elev0, it, 3).toUint() }.toDouble() / Skybox.gradSize).div(255.0).srgbLinearise().toFloat()
|
||||||
|
|
||||||
val colour = arrayOf(pixelValueAvrB, pixelValueAvrG, pixelValueAvrR, pixelValueAvrA)
|
val colour = Cvec(avrR, avrG, avrB, avrA).saturate(1.6666667f)
|
||||||
|
|
||||||
|
val colourBytes = arrayOf(
|
||||||
|
colour.b.times(255f).roundToInt().coerceIn(0..255).toByte(),
|
||||||
|
colour.g.times(255f).roundToInt().coerceIn(0..255).toByte(),
|
||||||
|
colour.r.times(255f).roundToInt().coerceIn(0..255).toByte(),
|
||||||
|
colour.a.times(255f).roundToInt().coerceIn(0..255).toByte()
|
||||||
|
)
|
||||||
|
|
||||||
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
|
val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
|
||||||
val imgOffY = texh2 - 1 - turb0
|
val imgOffY = texh2 - 1 - turb0
|
||||||
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
|
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
|
||||||
|
|
||||||
for (i in 0..3) {
|
for (i in 0..3) {
|
||||||
bytes2[fileOffset + i] = colour[i]
|
bytes2[fileOffset + i] = colourBytes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -207,6 +215,16 @@ class GenerateSkyboxTextureAtlas {
|
|||||||
this * 12.92
|
this * 12.92
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Cvec.saturate(intensity: Float): Cvec {
|
||||||
|
val luv = HUSLColorConverter.rgbToHsluv(floatArrayOf(this.r, this.g, this.b))
|
||||||
|
luv[1] *= intensity
|
||||||
|
val rgb = HUSLColorConverter.hsluvToRgb(luv)
|
||||||
|
this.r = rgb[0]
|
||||||
|
this.g = rgb[1]
|
||||||
|
this.b = rgb[2]
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -549,6 +549,13 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
private val parallaxDomainSize = 550f
|
private val parallaxDomainSize = 550f
|
||||||
private val turbidityDomainSize = parallaxDomainSize * 1.3333334f
|
private val turbidityDomainSize = parallaxDomainSize * 1.3333334f
|
||||||
|
|
||||||
|
private val CLOUD_SOLARDEG_OFFSET = 0.9f
|
||||||
|
|
||||||
|
private val globalLightBySun = Cvec()
|
||||||
|
private val globalLightByMoon = Cvec()
|
||||||
|
private val cloudCol1 = Cvec()
|
||||||
|
private val cloudCol2 = Cvec()
|
||||||
|
|
||||||
private fun drawSkybox(camera: OrthographicCamera, batch: FlippingSpriteBatch, world: GameWorld) {
|
private fun drawSkybox(camera: OrthographicCamera, batch: FlippingSpriteBatch, world: GameWorld) {
|
||||||
val weatherbox = world.weatherbox
|
val weatherbox = world.weatherbox
|
||||||
val currentWeather = world.weatherbox.currentWeather
|
val currentWeather = world.weatherbox.currentWeather
|
||||||
@@ -559,8 +566,8 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
val daylightClut = currentWeather.daylightClut
|
val daylightClut = currentWeather.daylightClut
|
||||||
// calculate global light
|
// calculate global light
|
||||||
val moonSize = (-(2.0 * world.worldTime.moonPhase - 1.0).abs() + 1.0).toFloat()
|
val moonSize = (-(2.0 * world.worldTime.moonPhase - 1.0).abs() + 1.0).toFloat()
|
||||||
val globalLightBySun: Cvec = getGradientColour2(daylightClut, solarElev, timeNow)
|
globalLightBySun.set(getGradientColour2(daylightClut, solarElev, timeNow))
|
||||||
val globalLightByMoon: Cvec = moonlightMax * moonSize
|
globalLightByMoon.set(moonlightMax * moonSize)
|
||||||
globalLightNow.set(globalLightBySun max globalLightByMoon)
|
globalLightNow.set(globalLightBySun max globalLightByMoon)
|
||||||
|
|
||||||
/* (copied from the shader source)
|
/* (copied from the shader source)
|
||||||
@@ -598,8 +605,9 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
val cloudCol1 = getGradientCloud(skyboxavr, solarElev, mornNoonBlend.toDouble(), turbidity, albedo)
|
cloudCol1.set(getGradientCloud(skyboxavr, solarElev, mornNoonBlend.toDouble(), turbidity, albedo))
|
||||||
cloudDrawColour.set(lerp(0.5, cloudCol1, globalLightNow))
|
cloudCol2.set(getGradientColour2(daylightClut, solarElev + CLOUD_SOLARDEG_OFFSET, timeNow) max globalLightByMoon)
|
||||||
|
cloudDrawColour.set(lerp(0.5, cloudCol1, cloudCol2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -728,11 +736,14 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
return Cvec(newColRGB, newColUV.r)
|
return Cvec(newColRGB, newColUV.r)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGradientCloud(colorMap: GdxColorMap, solarAngleInDeg: Double, mornNoonBlend: Double, turbidity: Double, albedo: Double): Cvec {
|
fun getGradientCloud(colorMap: GdxColorMap, solarAngleInDeg0: Double, mornNoonBlend: Double, turbidity: Double, albedo: Double): Cvec {
|
||||||
|
val solarAngleInDeg = solarAngleInDeg0 + CLOUD_SOLARDEG_OFFSET // add a small offset
|
||||||
|
val solarAngleInDegInt = solarAngleInDeg.floorToInt()
|
||||||
|
|
||||||
// fine-grained
|
// fine-grained
|
||||||
val angleX1 = solarAngleInDeg.toInt() + 75
|
val angleX1 = (solarAngleInDegInt + 75).coerceAtMost(150)
|
||||||
val angleX2 = (angleX1 + 1).coerceAtMost(150)
|
val angleX2 = (angleX1 + 1).coerceAtMost(150)
|
||||||
val ax = solarAngleInDeg % 1.0
|
val ax = solarAngleInDeg - solarAngleInDegInt
|
||||||
// fine-grained
|
// fine-grained
|
||||||
val turbY = turbidity.coerceIn(Skybox.turbiditiesD.first(), Skybox.turbiditiesD.last()).minus(1.0).times(Skybox.turbDivisor)
|
val turbY = turbidity.coerceIn(Skybox.turbiditiesD.first(), Skybox.turbiditiesD.last()).minus(1.0).times(Skybox.turbDivisor)
|
||||||
val turbY1 = turbY.floorToInt()
|
val turbY1 = turbY.floorToInt()
|
||||||
@@ -752,7 +763,6 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
val a2t1b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY1)
|
val a2t1b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY1)
|
||||||
val a1t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX1, turbY2)
|
val a1t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX1, turbY2)
|
||||||
val a2t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY2)
|
val a2t2b2A = colorMap.getCvec(albX2 * elevCnt + angleX2, turbY2)
|
||||||
|
|
||||||
val a1t1b1B = colorMap.getCvec(albX1 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY1)
|
val a1t1b1B = colorMap.getCvec(albX1 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY1)
|
||||||
val a2t1b1B = colorMap.getCvec(albX1 * elevCnt + angleX2 + 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 a1t2b1B = colorMap.getCvec(albX1 * elevCnt + angleX1 + Skybox.albedoCnt * elevCnt, turbY2)
|
||||||
@@ -766,20 +776,17 @@ internal object WeatherMixer : RNGConsumer {
|
|||||||
val t2b1A = lerp(ax, a1t2b1A, a2t2b1A)
|
val t2b1A = lerp(ax, a1t2b1A, a2t2b1A)
|
||||||
val t1b2A = lerp(ax, a1t1b2A, a2t1b2A)
|
val t1b2A = lerp(ax, a1t1b2A, a2t1b2A)
|
||||||
val t2b2A = lerp(ax, a1t2b2A, a2t2b2A)
|
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 t1b1B = lerp(ax, a1t1b1B, a2t1b1B)
|
||||||
val t2b1B = lerp(ax, a1t2b1B, a2t2b1B)
|
val t2b1B = lerp(ax, a1t2b1B, a2t2b1B)
|
||||||
val t1b2B = lerp(ax, a1t1b2B, a2t1b2B)
|
val t1b2B = lerp(ax, a1t1b2B, a2t1b2B)
|
||||||
val t2b2B = lerp(ax, a1t2b2B, a2t2b2B)
|
val t2b2B = lerp(ax, a1t2b2B, a2t2b2B)
|
||||||
|
|
||||||
|
val b1A = lerp(tx, t1b1A, t2b1A)
|
||||||
|
val b2A = lerp(tx, t1b2A, t2b2A)
|
||||||
val b1B = lerp(tx, t1b1B, t2b1B)
|
val b1B = lerp(tx, t1b1B, t2b1B)
|
||||||
val b2B = lerp(tx, t1b2B, t2b2B)
|
val b2B = lerp(tx, t1b2B, t2b2B)
|
||||||
|
|
||||||
|
val A = lerp(bx, b1A, b2A)
|
||||||
val B = lerp(bx, b1B, b2B)
|
val B = lerp(bx, b1B, b2B)
|
||||||
|
|
||||||
return lerp(mornNoonBlend, A, B)
|
return lerp(mornNoonBlend, A, B)
|
||||||
|
|||||||
Reference in New Issue
Block a user