mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
2k skybox tex; trilinear blending of atmos vars
This commit is contained in:
BIN
assets/clut/skybox.png
LFS
BIN
assets/clut/skybox.png
LFS
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user