From e83b2d19087b2f05d2f8f436e549d70e363c54de Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 21 Sep 2023 19:45:22 +0900 Subject: [PATCH] better skybox model for better sunset --- assets/clut/skybox.png | 4 +- src/net/torvald/parametricsky/Application.kt | 4 +- .../clut/GenerateSkyboxTextureAtlas.kt | 82 +++++++++++-------- .../torvald/terrarum/weather/WeatherMixer.kt | 3 +- src/shaders/blendSkyboxStars.frag | 31 ++++++- 5 files changed, 83 insertions(+), 41 deletions(-) diff --git a/assets/clut/skybox.png b/assets/clut/skybox.png index 14e524c22..acae8d70e 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:73beffb0a61a4b66c0bb14eb68b26316985bdba0998b71367e49cb5753c3e555 -size 745523 +oid sha256:0029e4dee48b7e08251a51d3018322a926f34541b1a429922c4db0cc9bd2e2a0 +size 1383648 diff --git a/src/net/torvald/parametricsky/Application.kt b/src/net/torvald/parametricsky/Application.kt index aa2fcfc10..b98f2f3f6 100644 --- a/src/net/torvald/parametricsky/Application.kt +++ b/src/net/torvald/parametricsky/Application.kt @@ -206,7 +206,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() { 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 gamma = if (y < halfHeight) Math.toRadians(cameraHeading) else Math.toRadians(cameraHeading + 180) val xyz = CIEXYZ( @@ -291,7 +291,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() { app.solarBearing = (it.value as Double) } } - val cameraHeading = JSpinner(SpinnerNumberModel(90.0, 0.0, 180.0, 1.0)).also { + val cameraHeading = JSpinner(SpinnerNumberModel(90.0, -360.0, 360.0, 1.0)).also { it.preferredSize = dialSize it.addChangeListener { _ -> app.cameraHeading = (it.value as Double) diff --git a/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt b/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt index 65e8d9163..ddf73b9e4 100644 --- a/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt +++ b/src/net/torvald/terrarum/clut/GenerateSkyboxTextureAtlas.kt @@ -11,6 +11,8 @@ import net.torvald.terrarum.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.cos /** * Created by minjaesong on 2023-08-01. @@ -23,6 +25,47 @@ fun main() { val TGA_HEADER_SIZE = 18 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) { + val elevationRad = Math.toRadians(elevationDeg) + /*val gamma = if (gammaPair == 0) HALF_PI else { + Math.toRadians(180 + 114 + 24 * cos(PI * elevationDeg / 40)) + }*/ + val gamma = Math.toRadians(115 + 25 * cos(PI * elevationDeg / 40)) + (gammaPair * PI) +// 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 - 10 + 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, gamma, 0).toFloat(), + ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(), + ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 2).toFloat() + ) + val xyz2 = xyz.scaleToFit(elevationDeg) + val rgb = xyz2.toRGB().toColor() + val colour = rgb.toIntBits().toLittle() + + for (i in 0..3) { + writefun(yp, i, colour[bytesLut[i]]) + } + } + } + // write header byteArrayOf( 0, // ID field @@ -45,7 +88,6 @@ fun main() { // write pixels for (gammaPair in 0..1) { - val gamma = if (gammaPair == 0) HALF_PI else 3* HALF_PI for (albedo0 in 0 until Skybox.albedoCnt) { val albedo = Skybox.albedos[albedo0] @@ -54,42 +96,13 @@ fun main() { 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 - 10 - 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, gamma, 0).toFloat(), - ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(), - ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 2).toFloat() - ) - val xyz2 = xyz.scaleToFit(elevationDeg) - val rgb = xyz2.toRGB().toColor() - val colour = rgb.toIntBits().toLittle() - + 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) - for (i in 0..3) { - bytes[fileOffset + i] = colour[bytesLut[i]] - } + bytes[fileOffset + i] = colour } } } @@ -101,4 +114,5 @@ fun main() { File("./assets/clut/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 \ No newline at end of file diff --git a/src/net/torvald/terrarum/weather/WeatherMixer.kt b/src/net/torvald/terrarum/weather/WeatherMixer.kt index 2b328425d..fd5bfdd5b 100644 --- a/src/net/torvald/terrarum/weather/WeatherMixer.kt +++ b/src/net/torvald/terrarum/weather/WeatherMixer.kt @@ -571,6 +571,7 @@ internal object WeatherMixer : RNGConsumer { gdxBlendNormalStraightAlpha() val oldNewBlend = weatherbox.weatherBlend.times(2f).coerceAtMost(1f) + 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) @@ -603,7 +604,7 @@ internal object WeatherMixer : RNGConsumer { shaderAstrum.setUniform4fv("uvG", uvs, 24, 4) shaderAstrum.setUniform4fv("uvH", uvs, 28, 4) shaderAstrum.setUniformf("texBlend1", turbTihsBlend, albThisBlend, turbOldBlend, albOldBlend) - shaderAstrum.setUniformf("texBlend2", oldNewBlend, 0f, 0f, 0f) + shaderAstrum.setUniformf("texBlend2", oldNewBlend, mornNoonBlend, 0f, 0f) shaderAstrum.setUniformf("astrumScroll", astrumOffX + astrumX, astrumOffY + astrumY) shaderAstrum.setUniformf("randomNumber", world.worldTime.TIME_T.div(+14.1f).plus(31L), diff --git a/src/shaders/blendSkyboxStars.frag b/src/shaders/blendSkyboxStars.frag index dd1b15bdb..ca56b18a1 100644 --- a/src/shaders/blendSkyboxStars.frag +++ b/src/shaders/blendSkyboxStars.frag @@ -12,6 +12,7 @@ uniform sampler2D tex1; // glow texture, SHOULD contain alpha of all 1.0 out vec4 fragColor; const vec2 boolean = vec2(0.0, 1.0); +const vec2 halfx = vec2(0.5, 0.0); uniform vec4 drawOffsetSize; // (gradX, gradY, gradW, gradH) uniform vec4 uvA; // (u, v, u2, v2) for now, turbLow, albLow @@ -23,11 +24,12 @@ uniform vec4 uvF; // (u, v, u2, v2) for old, turbLow, albHigh uniform vec4 uvG; // (u, v, u2, v2) for now, turbHigh, albHigh uniform vec4 uvH; // (u, v, u2, v2) for old, turbHigh, albHigh uniform vec4 texBlend1; // (turbidity now, albedo now, turbidity old, albedo old) -uniform vec4 texBlend2; // (old-now blend, unused, unused, unused) +uniform vec4 texBlend2; // (old-now blend, morn-noon blend, unused, 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); + vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } @@ -128,6 +130,15 @@ void main(void) { vec4 colorTexG = texture(u_texture, mix(uvG.xy, uvG.zw, v_texCoords)); vec4 colorTexH = texture(u_texture, mix(uvH.xy, uvH.zw, v_texCoords)); + vec4 colorTexA2 = texture(u_texture, mix(uvA.xy, uvA.zw, v_texCoords) + halfx); + vec4 colorTexB2 = texture(u_texture, mix(uvB.xy, uvB.zw, v_texCoords) + halfx); + vec4 colorTexC2 = texture(u_texture, mix(uvC.xy, uvC.zw, v_texCoords) + halfx); + vec4 colorTexD2 = texture(u_texture, mix(uvD.xy, uvD.zw, v_texCoords) + halfx); + vec4 colorTexE2 = texture(u_texture, mix(uvE.xy, uvE.zw, v_texCoords) + halfx); + vec4 colorTexF2 = texture(u_texture, mix(uvF.xy, uvF.zw, v_texCoords) + halfx); + vec4 colorTexG2 = texture(u_texture, mix(uvG.xy, uvG.zw, v_texCoords) + halfx); + vec4 colorTexH2 = texture(u_texture, mix(uvH.xy, uvH.zw, v_texCoords) + halfx); + 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 @@ -136,7 +147,7 @@ void main(void) { 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( + vec4 colorTex0Morn = mix( mix( // now-values mix(colorTexA, colorTexE, texBlend1.y), // c00 = c000..c100 mix(colorTexC, colorTexG, texBlend1.y), // c10 = c010..c110 @@ -150,6 +161,22 @@ void main(void) { texBlend2.x ); // c = c0..c1 + vec4 colorTex0Noon = mix( + mix( // now-values + mix(colorTexA2, colorTexE2, texBlend1.y), // c00 = c000..c100 + mix(colorTexC2, colorTexG2, texBlend1.y), // c10 = c010..c110 + texBlend1.x + ), // c0 = c00..c10 + mix( // old-values + mix(colorTexB2, colorTexF2, texBlend1.w), // c01 = c001..c101 + mix(colorTexD2, colorTexH2, texBlend1.w), // c11 = c011..c111 + texBlend1.z + ), // c1 = c01..c11 + texBlend2.x + ); // c = c0..c1 + + vec4 colorTex0 = mix(colorTex0Morn, colorTex0Noon, texBlend2.y); + // fragColor = (max(colorTex0, colorTex1) * boolean.yyyx) + boolean.xxxy; fragColor = fma(max(colorTex0, colorTex1), boolean.yyyx, boolean.xxxy);