2k skybox tex; trilinear blending of atmos vars

This commit is contained in:
minjaesong
2023-08-07 13:57:58 +09:00
parent 30fb57eca3
commit 014306c209
6 changed files with 126 additions and 46 deletions

View File

@@ -13,6 +13,9 @@ import net.torvald.unicode.EMDASH
import net.torvald.colourutil.*
import net.torvald.parametricsky.datasets.DatasetCIEXYZ
import net.torvald.terrarum.abs
import net.torvald.terrarum.clut.Skybox
import net.torvald.terrarum.clut.Skybox.coerceInSmoothly
import net.torvald.terrarum.clut.Skybox.mapCircle
import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
import java.awt.BorderLayout
@@ -131,7 +134,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
}
val outTexWidth = 1
val outTexHeight = 256
val outTexHeight = 128
private fun Float.scaleFun() =
(1f - 1f / 2f.pow(this/6f)) * 0.97f
@@ -185,6 +188,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val ys2 = ArrayList<Float>()
val halfHeight = oneScreen.height * 0.5
val elevationDeg = Math.toDegrees(elevation)
for (x in 0 until oneScreen.width) {
for (y in 0 until oneScreen.height) {
@@ -196,17 +200,19 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val theta = sqrt(xf*xf + yf*yf) * HALF_PI*/
// AM-PM mapping (use with WIDTH=1)
var yf = (y * 2.0 / oneScreen.height) % 1.0
if (elevation < 0) yf *= 1.0 - pow(-elevation / HALF_PI, 0.333)
val yp = y % (oneScreen.height / 2)
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)
if (elevationDeg < 0) yf *= Skybox.superellipsoidDecay(1.0 / 3.0, xf)
val theta = yf * HALF_PI
val gamma = if (y < halfHeight) HALF_PI else 3 * HALF_PI
val theta = yf.mapCircle() * HALF_PI
val xyz = CIEXYZ(
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, 2).toFloat()
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 2).toFloat(),
)
val xyz2 = xyz.scaleToFit(elevation)
ys.add(xyz.Y)
@@ -214,12 +220,12 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val rgb = xyz2.toRGB().toColor()
rgb.a = 1f
val rgb2 = Color(
/*val rgb2 = Color(
((rgb.r * 255f).roundToInt() xor 0xAA) / 255f,
((rgb.g * 255f).roundToInt() xor 0xAA) / 255f,
((rgb.b * 255f).roundToInt() xor 0xAA) / 255f,
rgb.a
)
)*/
oneScreen.setColor(rgb)
oneScreen.drawPixel(x, y)

View File

@@ -60,7 +60,7 @@ fun main() {
ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
for (yp in 0 until Skybox.gradSize) {
val yi = yp - 3
val yi = yp - 10
val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)

View File

@@ -13,6 +13,7 @@ import net.torvald.parametricsky.ArHosekSkyModel
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.abs
import net.torvald.terrarum.floorToInt
import net.torvald.terrarum.toInt
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.*
@@ -26,7 +27,7 @@ object Skybox : Disposable {
private const val PI = 3.141592653589793
private const val TWO_PI = 6.283185307179586
const val gradSize = 64
const val gradSize = 78
private lateinit var gradTexBinLowAlbedo: Array<TextureRegion>
private lateinit var gradTexBinHighAlbedo: Array<TextureRegion>
@@ -38,6 +39,7 @@ object Skybox : Disposable {
fun loadlut() {
tex = Texture(Gdx.files.internal("assets/clut/skybox.png"))
tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
tex.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat)
texRegions = TextureRegionPack(tex, 2, gradSize - 2, 0, 2, 0, 1)
texStripRegions = TextureRegionPack(tex, elevCnt, gradSize - 2, 0, 2, 0, 1)
}
@@ -55,20 +57,63 @@ object Skybox : Disposable {
TODO()
}
fun getUV(elevationDeg: Double, turbidity: Double, albedo: Double): Pair<Texture, FloatArray> {
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(turbDivisor).roundToInt()
val alb = albedo.coerceIn(0.0, 1.0).times(turbDivisor).roundToInt()
val region1 = texStripRegions.get(alb + albedoCnt * 0, turb) // left half of the sheet
val region2 = texStripRegions.get(alb + albedoCnt * 1, turb) // right half of the sheet
data class SkyboxRenderInfo(
val texture: Texture,
val uvs: FloatArray,
val turbidityPoint: Float,
val albedoPoint: Float,
)
fun getUV(elevationDeg: Double, turbidity: Double, albedo: Double): SkyboxRenderInfo {
val turb = turbidity.coerceIn(turbiditiesD.first(), turbiditiesD.last()).minus(1.0).times(turbDivisor)
val turbLo = turb.floorToInt()
val turbHi = min(turbCnt - 1, turbLo + 1)
val alb = albedo.coerceIn(albedos.first(), albedos.last()).times(5.0)
val albLo = alb.floorToInt()
val albHi = min(albedoCnt - 1, albLo + 1)
val elev = elevationDeg.coerceIn(-elevMax, elevMax).plus(elevMax).div(elevations.last.toDouble()).div(albedoCnt * 2).times((elevCnt - 1.0) / elevCnt)
val uA = region1.u + (0.5f / tex.width) + elev.toFloat() // because of the nature of bilinear interpolation, half pixels from the edges must be discarded
val uB = region2.u + (0.5f / tex.width) + elev.toFloat() // because of the nature of bilinear interpolation, half pixels from the edges must be discarded
// A: morn, turbLow, albLow
// B: noon, turbLow, albLow
// C: morn, turbHigh, albLow
// D: noon, turbHigh, albLow
// E: morn, turbLow, albHigh
// F: noon, turbLow, albHigh
// G: morn, turbHigh, albHigh
// H: noon, turbHigh, albHigh
return tex to floatArrayOf(
uA, region1.v, uA, region1.v2,
uB, region2.v, uB, region2.v2,
val regionA = texStripRegions.get(albLo + albedoCnt * 0, turbLo)
val regionB = texStripRegions.get(albLo + albedoCnt * 1, turbLo)
val regionC = texStripRegions.get(albLo + albedoCnt * 0, turbHi)
val regionD = texStripRegions.get(albLo + albedoCnt * 1, turbHi)
val regionE = texStripRegions.get(albHi + albedoCnt * 0, turbLo)
val regionF = texStripRegions.get(albHi + albedoCnt * 1, turbLo)
val regionG = texStripRegions.get(albHi + albedoCnt * 0, turbHi)
val regionH = texStripRegions.get(albHi + albedoCnt * 1, turbHi)
// (0.5f / tex.width): because of the nature of bilinear interpolation, half pixels from the edges must be discarded
val uA = regionA.u + (0.5f / tex.width) + elev.toFloat()
val uB = regionB.u + (0.5f / tex.width) + elev.toFloat()
val uC = regionC.u + (0.5f / tex.width) + elev.toFloat()
val uD = regionD.u + (0.5f / tex.width) + elev.toFloat()
val uE = regionE.u + (0.5f / tex.width) + elev.toFloat()
val uF = regionF.u + (0.5f / tex.width) + elev.toFloat()
val uG = regionG.u + (0.5f / tex.width) + elev.toFloat()
val uH = regionH.u + (0.5f / tex.width) + elev.toFloat()
return SkyboxRenderInfo(
tex,
floatArrayOf(
uA, regionA.v, uA, regionA.v2,
uB, regionB.v, uB, regionB.v2,
uC, regionC.v, uC, regionC.v2,
uD, regionD.v, uD, regionD.v2,
uE, regionE.v, uE, regionE.v2,
uF, regionF.v, uF, regionF.v2,
uG, regionG.v, uG, regionG.v2,
uH, regionH.v, uH, regionH.v2,
),
(turb - turbLo).toFloat(),
(alb - albLo).toFloat(),
)
}
@@ -107,15 +152,13 @@ object Skybox : Disposable {
val elevations = (0..150)
val elevMax = elevations.last / 2.0
val elevationsD = elevations.map { -elevMax + 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)
val turbidities = (0..45) // 1, 1.2, 1.4, 1.6, ..., 10.0
val turbidities = (0..25) // 1, 1.2, 1.4, 1.6, ..., 6.0
val turbDivisor = 5.0
val turbiditiesD = turbidities.map { 1.0 + it / turbDivisor }
val albedos = arrayOf(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0)
val albedos = arrayOf(0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
val elevCnt = elevations.count()
val turbCnt = turbidities.count()
val albedoCnt = albedos.size
val albedoLow = 0.1
val albedoHight = 0.8 // for theoretical "winter wonderland"?
val gamma = HALF_PI
internal fun Double.mapCircle() = sin(HALF_PI * this)
@@ -123,8 +166,7 @@ object Skybox : Disposable {
internal fun initiate() {
printdbg(this, "Initialising skybox model")
gradTexBinLowAlbedo = getTexturmaps(albedoLow)
gradTexBinHighAlbedo = getTexturmaps(albedoHight)
TODO()
App.disposables.add(this)
@@ -200,7 +242,7 @@ object Skybox : Disposable {
// printdbg(this, "elev $elevationDeg turb $turbidity")
for (yp in 0 until gradSize) {
val yi = yp - 3
val yi = yp - 10
val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)

View File

@@ -154,7 +154,7 @@ internal object WeatherMixer : RNGConsumer {
}
var turbidity = 4.0; private set
var turbidity = 1.0; private set
private var gH = 1.4f * App.scr.height
// private var gH = 0.8f * App.scr.height
@@ -168,9 +168,11 @@ internal object WeatherMixer : RNGConsumer {
drawSkybox(camera, batch, world)
}
private val parallaxDomainSize = 400f
private val turbidityDomainSize = 533.3333f
private fun drawSkybox(camera: Camera, batch: FlippingSpriteBatch, world: GameWorld) {
val parallaxZeroPos = (world.height / 3f)
val parallaxDomainSize = 300f
// we will not care for nextSkybox for now
val timeNow = (forceTimeAt ?: world.worldTime.TIME_T.toInt()) % WorldTime.DAY_LENGTH
@@ -199,19 +201,20 @@ internal object WeatherMixer : RNGConsumer {
-+ <- 0.0 =
*/
val parallax = ((parallaxZeroPos - WorldCamera.gdxCamY.div(TILE_SIZEF)) / parallaxDomainSize).times(-1f).coerceIn(-1f, 1f)
val turbidityCoeff = ((parallaxZeroPos - WorldCamera.gdxCamY.div(TILE_SIZEF)) / turbidityDomainSize).times(-1f).coerceIn(-1f, 1f)
parallaxPos = parallax
// println(parallax) // parallax value works as intended.
gdxBlendNormalStraightAlpha()
turbidity = (3.5 + turbidityCoeff * 2.5).coerceIn(1.0, 6.0)
val thisTurbidity = forceTurbidity ?: turbidity
// TODO trilinear with (deg, turb, alb)
val gradY = -(gH - App.scr.height) * ((parallax + 1f) / 2f)
val (tex, uvs) = Skybox.getUV(solarElev, thisTurbidity, 0.3)
val (tex, uvs, turbX, albX) = Skybox.getUV(solarElev, thisTurbidity, 0.3)
val texBlend = (1f/4000f * (timeNow - 43200) + 0.5f).coerceIn(0f, 1f) // 0.0 at T41200; 0.5 at T43200; 1.0 at T45200;
val mornNoonBlend = (1f/4000f * (timeNow - 43200) + 0.5f).coerceIn(0f, 1f) // 0.0 at T41200; 0.5 at T43200; 1.0 at T45200;
starmapTex.texture.bind(1)
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
@@ -223,9 +226,15 @@ internal object WeatherMixer : RNGConsumer {
batch.shader = shaderBlendMax
shaderBlendMax.setUniformi("tex1", 1)
shaderBlendMax.setUniformf("drawOffsetSize", 0f, gradY, App.scr.wf, gH)
shaderBlendMax.setUniform4fv("skyboxUV1", uvs, 0, 4)
shaderBlendMax.setUniform4fv("skyboxUV2", uvs, 4, 4)
shaderBlendMax.setUniformf("texBlend", texBlend)
shaderBlendMax.setUniform4fv("uvA", uvs, 0, 4)
shaderBlendMax.setUniform4fv("uvB", uvs, 4, 4)
shaderBlendMax.setUniform4fv("uvC", uvs, 8, 4)
shaderBlendMax.setUniform4fv("uvD", uvs, 12, 4)
shaderBlendMax.setUniform4fv("uvE", uvs, 16, 4)
shaderBlendMax.setUniform4fv("uvF", uvs, 20, 4)
shaderBlendMax.setUniform4fv("uvG", uvs, 24, 4)
shaderBlendMax.setUniform4fv("uvH", uvs, 28, 4)
shaderBlendMax.setUniformf("texBlend", mornNoonBlend, turbX, albX, 0f)
shaderBlendMax.setUniformf("astrumScroll", astrumOffX + astrumX, astrumOffY + astrumY)
shaderBlendMax.setUniformf("randomNumber",
// (world.worldTime.TIME_T.plus(31L) xor 1453L + 31L).and(1023).toFloat(),