From 014306c209420bcb5767e3f6abf4aa56d83acec5 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 7 Aug 2023 13:57:58 +0900 Subject: [PATCH] 2k skybox tex; trilinear blending of atmos vars --- assets/clut/skybox.png | 4 +- src/net/torvald/parametricsky/Application.kt | 22 ++++-- .../clut/GenerateSkyboxTextureAtlas.kt | 2 +- src/net/torvald/terrarum/clut/Skybox.kt | 78 ++++++++++++++----- .../torvald/terrarum/weather/WeatherMixer.kt | 25 ++++-- src/shaders/blendSkyboxStars.frag | 41 +++++++--- 6 files changed, 126 insertions(+), 46 deletions(-) diff --git a/assets/clut/skybox.png b/assets/clut/skybox.png index 32ec50324..b54e6cbbb 100644 --- a/assets/clut/skybox.png +++ b/assets/clut/skybox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c2b1edd3d64d619d97b29caef0fef718aa75a29f65ff5ad90624cab62088135 -size 2094774 +oid sha256:1cf64f5b875b595b5ef2234248b975c66957e53d756eee7f4c9c53b08b5f43fc +size 641256 diff --git a/src/net/torvald/parametricsky/Application.kt b/src/net/torvald/parametricsky/Application.kt index 62902fa9f..aa2fcfc10 100644 --- a/src/net/torvald/parametricsky/Application.kt +++ b/src/net/torvald/parametricsky/Application.kt @@ -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() 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) diff --git a/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt b/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt index 1b1f10ecb..511ec01b8 100644 --- a/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt +++ b/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt @@ -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) diff --git a/src/net/torvald/terrarum/clut/Skybox.kt b/src/net/torvald/terrarum/clut/Skybox.kt index 7cb721279..7efe058d3 100644 --- a/src/net/torvald/terrarum/clut/Skybox.kt +++ b/src/net/torvald/terrarum/clut/Skybox.kt @@ -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 private lateinit var gradTexBinHighAlbedo: Array @@ -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 { - 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) diff --git a/src/net/torvald/terrarum/weather/WeatherMixer.kt b/src/net/torvald/terrarum/weather/WeatherMixer.kt index 86e5d679c..1f83bfcd5 100644 --- a/src/net/torvald/terrarum/weather/WeatherMixer.kt +++ b/src/net/torvald/terrarum/weather/WeatherMixer.kt @@ -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(), diff --git a/src/shaders/blendSkyboxStars.frag b/src/shaders/blendSkyboxStars.frag index a4aae766b..8bf696b84 100644 --- a/src/shaders/blendSkyboxStars.frag +++ b/src/shaders/blendSkyboxStars.frag @@ -14,9 +14,15 @@ out vec4 fragColor; const vec2 boolean = vec2(0.0, 1.0); uniform vec4 drawOffsetSize; // (gradX, gradY, gradW, gradH) -uniform vec4 skyboxUV1; // (u, v, u2, v2) for the skybox drawing (morning) -uniform vec4 skyboxUV2; // (u, v, u2, v2) for the skybox drawing (afternoon) -uniform float texBlend; +uniform vec4 uvA; // (u, v, u2, v2) for morn, turbLow, albLow +uniform vec4 uvB; // (u, v, u2, v2) for noon, turbLow, albLow +uniform vec4 uvC; // (u, v, u2, v2) for morn, turbHigh, albLow +uniform vec4 uvD; // (u, v, u2, v2) for noon, turbHigh, albLow +uniform vec4 uvE; // (u, v, u2, v2) for morn, turbLow, albHigh +uniform vec4 uvF; // (u, v, u2, v2) for noon, turbLow, albHigh +uniform vec4 uvG; // (u, v, u2, v2) for morn, turbHigh, albHigh +uniform vec4 uvH; // (u, v, u2, v2) for noon, turbHigh, albHigh +uniform vec4 texBlend; // (morn/noon, turbidity, albedo, unused) uniform vec2 tex1Size = vec2(4096.0); uniform vec2 astrumScroll = vec2(0.0); uniform vec4 randomNumber = vec4(1.0, -2.0, 3.0, -4.0); @@ -112,19 +118,36 @@ vec4 random(vec2 p) { // draw call to this function must use UV coord of (0,0,1,1)! void main(void) { - vec2 skyboxTexCoordMorning = mix(skyboxUV1.xy, skyboxUV1.zw, v_texCoords); - vec2 skyboxTexCoordAfternoon = mix(skyboxUV2.xy, skyboxUV2.zw, v_texCoords); + vec4 colorTexA = texture(u_texture, mix(uvA.xy, uvA.zw, v_texCoords)); + vec4 colorTexB = texture(u_texture, mix(uvB.xy, uvB.zw, v_texCoords)); + vec4 colorTexC = texture(u_texture, mix(uvC.xy, uvC.zw, v_texCoords)); + vec4 colorTexD = texture(u_texture, mix(uvD.xy, uvD.zw, v_texCoords)); + vec4 colorTexE = texture(u_texture, mix(uvE.xy, uvE.zw, v_texCoords)); + vec4 colorTexF = texture(u_texture, mix(uvF.xy, uvF.zw, v_texCoords)); + vec4 colorTexG = texture(u_texture, mix(uvG.xy, uvG.zw, v_texCoords)); + vec4 colorTexH = texture(u_texture, mix(uvH.xy, uvH.zw, v_texCoords)); + vec2 astrumTexCoord = (v_texCoords * drawOffsetSize.zw + drawOffsetSize.xy + astrumScroll) / tex1Size; - vec4 randomness = snoise4((gl_FragCoord.xy - astrumScroll) * 0.16) * 2.0; // multiply by 2 so that the "density" of the stars would be same as the non-random version - vec4 colorTex0Morining = texture(u_texture, skyboxTexCoordMorning); - vec4 colorTex0Afternoon = texture(u_texture, skyboxTexCoordAfternoon); - vec4 colorTex0 = mix(colorTex0Morining, colorTex0Afternoon, texBlend); vec4 colorTex1 = texture(tex1, astrumTexCoord) * randomness; + // notations used: https://en.wikipedia.org/wiki/File:Enclosing_points.svg and https://en.wikipedia.org/wiki/File:3D_interpolation2.svg + vec4 colorTex0 = mix( + mix( + mix(colorTexA, colorTexE, texBlend.z), // c00 = c000..c100 + mix(colorTexC, colorTexG, texBlend.z), // c10 = c010..c110 + texBlend.y + ), // c0 = c00..c10 + mix( + mix(colorTexB, colorTexF, texBlend.z), // c01 = c001..c101 + mix(colorTexD, colorTexH, texBlend.z), // c11 = c011..c111 + texBlend.y + ), // c1 = c01..c11 + texBlend.x + ); // c = c0..c1 fragColor = (max(colorTex0, colorTex1) * boolean.yyyx) + boolean.xxxy;