diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index b24ab75201..b6593ff62f 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -53,6 +53,7 @@ import rs117.hd.config.TextureResolution; import rs117.hd.config.UIScalingMode; import rs117.hd.config.VanillaShadowMode; +import rs117.hd.config.SkyboxTheme; import static rs117.hd.HdPlugin.MAX_DISTANCE; import static rs117.hd.HdPlugin.MAX_FOG_DEPTH; @@ -797,12 +798,36 @@ default boolean characterDisplacement() { default boolean pohThemeEnvironments() { return true; } + /*====== 3D Skybox settings ======*/ + + @ConfigSection( + name = "3D Skybox", + description = "Settings for high-resolution 3D panoramic skybox rendering templates.", + position = 4, + closedByDefault = true + ) + String skyboxSettings = "skyboxSettings"; + + String KEY_SELECTED_SKYBOX_THEME = "selectedSkyboxTheme"; + @ConfigItem( + keyName = KEY_SELECTED_SKYBOX_THEME, + name = "Skybox Style", + description = "Select the panoramic 3D skybox style.
Select 'None' to use default 117 skybox.", + position = 0, + section = skyboxSettings + ) + default SkyboxTheme selectedSkyboxTheme() + { + return SkyboxTheme.PARTLY_CLOUDY; + } + + /*====== Miscellaneous settings ======*/ @ConfigSection( name = "Miscellaneous", description = "Miscellaneous settings", - position = 4, + position = 5, closedByDefault = true ) String miscellaneousSettings = "miscellaneousSettings"; @@ -956,7 +981,7 @@ default boolean windowsHdrCorrection() { @ConfigSection( name = "Legacy", description = "Legacy options. If you dislike a change, you might find an option to change it back here.", - position = 5, + position = 6, closedByDefault = true ) String legacySettings = "legacySettings"; @@ -1109,7 +1134,7 @@ default boolean legacyTzHaarReskin() { @ConfigSection( name = "Experimental", description = "Experimental features - if you're experiencing issues you should consider disabling these.", - position = 6, + position = 7, closedByDefault = true ) String experimentalSettings = "experimentalSettings"; diff --git a/src/main/java/rs117/hd/config/SkyboxTheme.java b/src/main/java/rs117/hd/config/SkyboxTheme.java new file mode 100644 index 0000000000..4874ab1c54 --- /dev/null +++ b/src/main/java/rs117/hd/config/SkyboxTheme.java @@ -0,0 +1,22 @@ +package rs117.hd.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SkyboxTheme { + NONE("None (Vanilla/117)", null), + CLEAR_DAY("Clear Day (Autumn Field)", "/rs117/hd/skybox3d/autumn_field_puresky_4k.png"), + CLOUDY("Cloudy Overcast (Soil)", "/rs117/hd/skybox3d/overcast_soil_puresky_4k.png"), + STARRY_NIGHT("Starry Night (Qwantani)", "/rs117/hd/skybox3d/qwantani_night_puresky_4k.png"), + PARTLY_CLOUDY("Partly Cloudy (Sunflowers)", "/rs117/hd/skybox3d/sunflowers_puresky_4k.png"); + + private final String name; + private final String resourcePath; + + @Override + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index be35d19b5c..ffc1bea921 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -6,10 +6,10 @@ * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -91,6 +91,13 @@ public class ZoneRenderer implements Renderer { private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_SKYBOX = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + private int skyboxShaderProgramId; + private int customSkyboxTextureId; + private rs117.hd.config.SkyboxTheme loadedSkyboxTheme = rs117.hd.config.SkyboxTheme.NONE; + private int lastCameraYaw = -1; + private float continuousSkyboxYaw = 0f; + private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; @@ -233,6 +240,10 @@ public void initializeShaders(ShaderIncludes includes) throws ShaderException, I sceneProgram.compile(includes); fastShadowProgram.compile(includes); detailedShadowProgram.compile(includes); + + skyboxShaderProgramId = compileSkyboxShader(); + // Pass the absolute resource path string to your loading method + customSkyboxTextureId = loadSkyboxTexture("/rs117/hd/skybox3d/sunflowers_puresky_4k.png"); } @Override @@ -287,7 +298,6 @@ public void preSceneDraw( try { WorldViewContext ctx = sceneManager.getContext(scene); if (ctx == null || !sceneManager.isRoot(ctx) && ctx.isLoading) { - // When triggering plugin restarts in rapid succession, it can end up in a state where no scene is loaded initially if (scene.getWorldViewId() == WorldView.TOPLEVEL && client.getGameState() == GameState.LOGGED_IN) clientThread.invokeLater(() -> client.setGameState(GameState.LOADING)); return; @@ -332,7 +342,6 @@ private void preSceneDrawTopLevel( scene.setDrawDistance(plugin.getDrawDistance()); - // Ensure that the previous frames commands have finished flushing frameTimer.begin(Timer.DRAW_FLUSH); glFlush(); frameTimer.end(Timer.DRAW_FLUSH); @@ -363,7 +372,6 @@ private void preSceneDrawTopLevel( if (plugin.orthographicProjection) zoom *= ORTHOGRAPHIC_ZOOM; - // Calculate the viewport dimensions before scaling in order to include the extra padding sceneCamera.setOrthographic(plugin.orthographicProjection); sceneCamera.setPosition(plugin.cameraPosition); sceneCamera.setOrientation(plugin.cameraOrientation); @@ -374,7 +382,6 @@ private void preSceneDrawTopLevel( sceneCamera.setNearPlane(plugin.orthographicProjection ? -40000 : NEAR_PLANE); sceneCamera.setZoom(zoom); - // Calculate view matrix, view proj & inv matrix boolean hasSceneCameraChanged = sceneCamera.isViewDirty() || sceneCamera.isProjDirty(); sceneCamera.getViewMatrix(plugin.viewMatrix); sceneCamera.getViewProjMatrix(plugin.viewProjMatrix); @@ -404,8 +411,8 @@ private void preSceneDrawTopLevel( boolean hasDirectionalCameraChanged = directionalCamera.isViewDirty() || directionalCamera.isProjDirty(); if (plugin.configShadowsEnabled && - (hasSceneCameraChanged || hasDirectionalCameraChanged) && - !sceneCamera.isOrthographic() + (hasSceneCameraChanged || hasDirectionalCameraChanged) && + !sceneCamera.isOrthographic() ) { int shadowDrawDistance = 90 * LOCAL_TILE_SIZE; @@ -417,7 +424,6 @@ private void preSceneDrawTopLevel( add(sceneCenter, sceneCenter, corner); divide(sceneCenter, sceneCenter, (float) volumeCorners.length); - // Reset position before transforming points directionalCamera.setPosition(0, 0, 0); float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY; @@ -439,24 +445,19 @@ private void preSceneDrawTopLevel( maxZ = max(maxZ, corner[2]); } - // Offset the Directional Camera by the radius of the scene float[] directionalFwd = directionalCamera.getForwardDirection(); multiply(directionalFwd, directionalFwd, radius); add(sceneCenter, sceneCenter, directionalFwd); - // Calculate directional size from the AABB of the scene frustum corners - // Then snap to the nearest multiple of `LOCAL_HALF_TILE_SIZE` to prevent shimmering int directionalSize = (int) max(abs(maxY - minY), abs(maxX - minX), abs(maxZ - minZ)); directionalSize = Math.round(directionalSize / (float) LOCAL_HALF_TILE_SIZE) * LOCAL_HALF_TILE_SIZE; - directionalSize = max(8000, directionalSize); // Clamp the size to prevent going too small at reduced draw distances + directionalSize = max(8000, directionalSize); - // Ignore directional size changes below the change threshold to avoid inducing shimmering int previousDirectionalSize = directionalCamera.getViewportWidth(); - float changeThreshold = previousDirectionalSize * 0.05f; // 10% of the previous directional size + float changeThreshold = previousDirectionalSize * 0.05f; if (abs(directionalSize - previousDirectionalSize) < changeThreshold) directionalSize = previousDirectionalSize; - // Snap Position to Shadow Texel Grid to prevent shimmering directionalCamera.transformPoint(sceneCenter, sceneCenter); float texelSize = (float) directionalSize / plugin.shadowMapResolution; @@ -485,7 +486,6 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.invProjectionMatrix.set(plugin.invViewProjMatrix); if (plugin.configDynamicLights != DynamicLights.NONE) { - // Update lights UBO assert ctx.sceneContext.numVisibleLights <= UBOLights.MAX_LIGHTS; frameTimer.begin(Timer.UPDATE_LIGHTS); @@ -507,10 +507,9 @@ private void preSceneDrawTopLevel( plugin.uboLights.setLight(i, lightPosition, lightColor); if (plugin.configTiledLighting) { - // Pre-calculate the view space position of the light, to save having to do the multiplication in the culling shader lightPosition[3] = 1.0f; Mat4.mulVec(lightPosition, plugin.viewMatrix, lightPosition); - lightPosition[3] = lightRadiusSq; // Restore lightRadiusSq + lightPosition[3] = lightRadiusSq; plugin.uboLightsCulling.setLight(i, lightPosition, lightColor); } } @@ -522,7 +521,6 @@ private void preSceneDrawTopLevel( } } - // Upon logging in, the client will draw some frames with zero geometry before it hides the login screen if (client.getGameState().getState() >= GameState.LOGGED_IN.getState()) plugin.hasLoggedIn = true; @@ -589,7 +587,6 @@ private void preSceneDrawTopLevel( environmentManager.currentGroundFogOpacity : 0); - // Lights & lightning plugin.uboGlobal.lightningBrightness.set(environmentManager.getLightningBrightness()); plugin.uboGlobal.saturation.set(config.saturation() / 100f); @@ -609,7 +606,6 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.upload(); - // Reset buffers for the next frame indirectDrawCmdsStaging.clear(); sceneCmd.reset(); directionalCmd.reset(); @@ -649,13 +645,11 @@ private void postDrawTopLevel() { sceneFboValid = true; - // Upload world views before rendering uboWorldViews.upload(); if (eboAlphaWriter != null) eboAlphaWriter.flush(); - // Scene draw state to apply before all recorded commands if (indirectDrawCmdsStaging.position() > 0) { indirectDrawCmdsStaging.flip(); indirectDrawCmds.orphan(); @@ -666,7 +660,6 @@ private void postDrawTopLevel() { frameTimer.begin(Timer.RENDER_FRAME); shouldRenderScene = true; - // TODO: Add proper support for stat tracking to the FrameTimer or elsewhere plugin.drawnDynamicRenderableCount += modelStreamingManager.getDrawnDynamicRenderableCount(); checkGLErrors(); @@ -713,7 +706,6 @@ private void directionalShadowPass() { environmentManager.currentDirectionalStrength > 0; if (shouldRenderShadows || shouldClearShadowFbo) { - // Render to the shadow depth map renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboShadowMap); renderState.viewport.set(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); renderState.apply(); @@ -747,8 +739,8 @@ private void directionalShadowPass() { private void scenePass() { sceneProgram.use(); - frameTimer.begin(Timer.DRAW_SCENE); + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); if (plugin.msaaSamples > 1) { renderState.enable.set(GL_MULTISAMPLE); @@ -759,43 +751,100 @@ private void scenePass() { renderState.ido.set(indirectDrawCmds.id); renderState.apply(); - // Clear scene frameTimer.begin(Timer.CLEAR_SCENE); - float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); - glClearColor( - gammaCorrectedFogColor[0], - gammaCorrectedFogColor[1], - gammaCorrectedFogColor[2], - 1f - ); + glClearColor(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f); glClearDepth(0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); frameTimer.end(Timer.CLEAR_SCENE); - frameTimer.begin(Timer.RENDER_SCENE); + // --- DYNAMIC SKYBOX THEME CONFIGURATION TRACKER --- + rs117.hd.config.SkyboxTheme currentTheme = config.selectedSkyboxTheme(); + boolean hasActiveSkyboxTheme = currentTheme != rs117.hd.config.SkyboxTheme.NONE; + + if (hasActiveSkyboxTheme && environmentManager.isOverworld()) { + + // INTERCEPT: Check if the user changed the dropdown selection mid-game + if (currentTheme != loadedSkyboxTheme) { + // Free the old texture handle from GPU memory if it exists + if (customSkyboxTextureId != 0) { + glDeleteTextures(customSkyboxTextureId); + customSkyboxTextureId = 0; + } + + // Stream the new asset path specified by the active enum selection + customSkyboxTextureId = loadSkyboxTexture(currentTheme.getResourcePath()); + loadedSkyboxTheme = currentTheme; + } + + // Only run drawing commands if the selected asset file loaded successfully + if (customSkyboxTextureId != 0) { + glDisable(GL_DEPTH_TEST); + glDepthMask(false); + glUseProgram(skyboxShaderProgramId); + + // [ Your original texture coordinate math stays exactly here ] + float horizontalSpeed = 1.0f; + float verticalSpeed = 1.0f; + int currentYaw = client.getCameraYaw(); + if (lastCameraYaw == -1) { + lastCameraYaw = currentYaw; + continuousSkyboxYaw = currentYaw; + } + + int deltaYaw = currentYaw - lastCameraYaw; + if (deltaYaw > 1024) { + deltaYaw -= 2048; + } else if (deltaYaw < -1024) { + deltaYaw += 2048; + } + + continuousSkyboxYaw += (deltaYaw * horizontalSpeed); + continuousSkyboxYaw = (continuousSkyboxYaw % 2048.0f + 2048.0f) % 2048.0f; + lastCameraYaw = currentYaw; + + float yawRadians = (float) ((continuousSkyboxYaw * 2.0 * Math.PI / 2048.0)); + float pitchRadians = (float) ((client.getCameraPitch() * 2.0 * Math.PI / 2048.0) * verticalSpeed); + + float[] projMatrix = sceneCamera.getProjectionMatrix(); + + glUniform1f(glGetUniformLocation(skyboxShaderProgramId, "cameraYaw"), yawRadians); + glUniform1f(glGetUniformLocation(skyboxShaderProgramId, "cameraPitch"), pitchRadians); + glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgramId, "projectionMatrix"), false, projMatrix); + + glActiveTexture(GL_TEXTURE8); + glBindTexture(GL_TEXTURE_2D, customSkyboxTextureId); + glUniform1i(glGetUniformLocation(skyboxShaderProgramId, "skyboxTexture"), 8); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glEnable(GL_DEPTH_TEST); + glDepthMask(true); + sceneProgram.use(); + } else { + sceneProgram.use(); + } + } else { + sceneProgram.use(); + } + + frameTimer.begin(Timer.RENDER_SCENE); renderState.enable.set(GL_BLEND); renderState.enable.set(GL_CULL_FACE); renderState.enable.set(GL_DEPTH_TEST); renderState.depthFunc.set(GL_GEQUAL); renderState.blendFunc.set(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - // Render the scene sceneCmd.execute(); - - // TODO: Filler tiles frameTimer.end(Timer.RENDER_SCENE); glBindVertexArray(0); - - // Done rendering the scene renderState.disable.set(GL_BLEND); renderState.disable.set(GL_CULL_FACE); renderState.disable.set(GL_DEPTH_TEST); renderState.apply(); - frameTimer.end(Timer.DRAW_SCENE); } @@ -926,7 +975,6 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i final boolean hasAlpha = z.sizeA != 0 || !z.alphaModels.isEmpty(); if (hasAlpha) { final int offset = ctx.sceneContext.sceneOffset >> 3; - // Only sort if the alpha will be directly visible, since shadows don't require sorting if (level == 0 && (!sceneManager.isRoot(ctx) || z.inSceneFrustum)) z.alphaSort(zx - offset, zz - offset, sceneCamera); @@ -978,20 +1026,16 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (sceneManager.isRoot(ctx)) frameTimer.end(Timer.UNMAP_ROOT_CTX); - // Draw opaque ctx.drawAll(VAO_OPAQUE, ctx.vaoSceneCmd); ctx.drawAll(VAO_OPAQUE, ctx.vaoDirectionalCmd); ctx.drawAll(VAO_PLAYER, ctx.vaoDirectionalCmd); - // Draw shadow-only models ctx.drawAll(VAO_SHADOW, ctx.vaoDirectionalCmd); - // Draw players with sorted alpha, without writing depth ctx.vaoSceneCmd.DepthMask(false); ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); ctx.vaoSceneCmd.DepthMask(true); - // Redraw players, this time only writing depth, for correct ordering with the background ctx.vaoSceneCmd.ColorMask(false, false, false, false); ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); ctx.vaoSceneCmd.ColorMask(true, true, true, true); @@ -1066,8 +1110,6 @@ public void draw(int overlayColor) { try { plugin.prepareInterfaceTexture(); } catch (Exception ex) { - // Fixes: https://github.com/runelite/runelite/issues/12930 - // Gracefully Handle loss of opengl buffers and context log.warn("prepareInterfaceTexture exception", ex); plugin.restartPlugin(); return; @@ -1083,7 +1125,6 @@ public void draw(int overlayColor) { if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); if (plugin.fboSceneResolve != 0) { - // Blit from the scene FBO to the multisample resolve FBO glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboSceneResolve); glBlitFramebuffer( 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], @@ -1093,7 +1134,6 @@ public void draw(int overlayColor) { glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneResolve); } - // Blit from the resolved FBO to the default FBO glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); glBlitFramebuffer( 0, @@ -1127,12 +1167,9 @@ public void draw(int overlayColor) { frameTimer.end(Timer.SWAP_BUFFERS); drawManager.processDrawComplete(plugin::screenshot); } catch (RuntimeException ex) { - // this is always fatal if (!plugin.canvas.isValid()) { - // this might be AWT shutting down on VM shutdown, ignore it return; } - log.error("Unable to swap buffers:", ex); } @@ -1152,8 +1189,6 @@ public void draw(int overlayColor) { public void onGameStateChanged(GameStateChanged gameStateChanged) { GameState state = gameStateChanged.getGameState(); if (state.getState() < GameState.LOADING.getState()) { - // this is to avoid scene fbo blit when going from =loading, - // but keep it when doing >loading to loading sceneFboValid = false; } } @@ -1218,4 +1253,126 @@ public void swapScene(Scene scene) { plugin.stopPlugin(); } } -} + + private int compileSkyboxShader() { + int vShader = glCreateShader(GL_VERTEX_SHADER); + int fShader = glCreateShader(GL_FRAGMENT_SHADER); + + String vSrc = "#version 330\n" + + "out vec2 screenUV;\n" + + "void main() {\n" + + " vec2 pos[3] = vec2[3](vec2(-1, -1), vec2(3, -1), vec2(-1, 3));\n" + + " gl_Position = vec4(pos[gl_VertexID], 1.0, 1.0);\n" + + " screenUV = pos[gl_VertexID] * 0.5 + 0.5;\n" + + "}"; + + String fSrc = "#version 330\n" + + "in vec2 screenUV;\n" + + "out vec4 FragColor;\n" + + "uniform sampler2D skyboxTexture;\n" + + "uniform float cameraYaw;\n" + + "uniform float cameraPitch;\n" + + "uniform mat4 projectionMatrix;\n" + + "void main() {\n" + + " float xClip = screenUV.x * 2.0 - 1.0;\n" + + " float yClip = screenUV.y * 2.0 - 1.0;\n" + + " \n" + + " vec3 viewDir = vec3(\n" + + " xClip / projectionMatrix[0][0],\n" + + " yClip / projectionMatrix[1][1],\n" + + " -1.0\n" + + " );\n" + + " \n" + + " float cosP = cos(cameraPitch);\n" + + " float sinP = sin(cameraPitch);\n" + + " vec3 pitchedDir = vec3(\n" + + " viewDir.x,\n" + + " viewDir.y * cosP - viewDir.z * sinP,\n" + + " viewDir.y * sinP + viewDir.z * cosP\n" + + " );\n" + + " \n" + + " float cosY = cos(cameraYaw);\n" + + " float sinY = sin(cameraYaw);\n" + + " vec3 finalDir = vec3(\n" + + " pitchedDir.x * cosY + pitchedDir.z * sinY,\n" + + " pitchedDir.y,\n" + + " -pitchedDir.x * sinY + pitchedDir.z * cosY\n" + + " );\n" + + " \n" + + " vec3 dir = normalize(finalDir);\n" + + " \n" + + " // --- APPLY VERTICAL SHIFT HERE ---\n" + + " // Slightly offsetting the Y lookup pushes the panorama horizon downwards\n" + + " float shiftedY = dir.y - 0.12;\n" + + " \n" + + " // Map UV coordinates using our shifted position variable\n" + + " vec2 uv = vec2(atan(dir.x, -dir.z) / (2.0 * 3.14159265) + 0.5, 1.0 - (acos(clamp(shiftedY, -1.0, 1.0)) / 3.14159265));\n" + + " \n" + + " FragColor = texture(skyboxTexture, uv);\n" + + "}"; + + glShaderSource(vShader, vSrc); + glCompileShader(vShader); + if (glGetShaderi(vShader, GL_COMPILE_STATUS) == GL_FALSE) { + log.error("Vertex Shader Error: " + glGetShaderInfoLog(vShader)); + } + + glShaderSource(fShader, fSrc); + glCompileShader(fShader); + if (glGetShaderi(fShader, GL_COMPILE_STATUS) == GL_FALSE) { + log.error("Fragment Shader Error: " + glGetShaderInfoLog(fShader)); + } + + int program = glCreateProgram(); + glAttachShader(program, vShader); + glAttachShader(program, fShader); + glLinkProgram(program); + return program; + } + + private int loadSkyboxTexture(String resourcePath) { + int texId = 0; + try { + java.io.InputStream is = getClass().getResourceAsStream(resourcePath); + if (is == null) { + log.error("IMAGE IS NULL: Could not find resource at: " + resourcePath); + return 0; + } + + // Fully qualified variable initialization: + java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(is); + is.close(); + + int width = img.getWidth(); + int height = img.getHeight(); + int[] pixels = new int[width * height]; + img.getRGB(0, 0, width, height, pixels, 0, width); + + java.nio.ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(width * height * 4); + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + int pixel = pixels[y * width + x]; + buffer.put((byte) ((pixel >> 16) & 0xFF)); + buffer.put((byte) ((pixel >> 8) & 0xFF)); + buffer.put((byte) (pixel & 0xFF)); + buffer.put((byte) ((pixel >> 24) & 0xFF)); + } + } + buffer.flip(); + + texId = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, texId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + return texId; + } catch (Exception e) { + log.error("Failed to load custom skybox texture from resources.", e); + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/scene/EnvironmentManager.java b/src/main/java/rs117/hd/scene/EnvironmentManager.java index b19eb18f64..cf6ff3e402 100644 --- a/src/main/java/rs117/hd/scene/EnvironmentManager.java +++ b/src/main/java/rs117/hd/scene/EnvironmentManager.java @@ -500,4 +500,6 @@ public boolean isUnderwater() { public boolean allowRoofShadows() { return currentEnvironment.allowRoofShadows; } + + public boolean isOverworld() { return currentEnvironment.isOverworld; } } diff --git a/src/main/resources/rs117/hd/skybox3d/autumn_field_puresky_4k.png b/src/main/resources/rs117/hd/skybox3d/autumn_field_puresky_4k.png new file mode 100644 index 0000000000..2d0d22edbb Binary files /dev/null and b/src/main/resources/rs117/hd/skybox3d/autumn_field_puresky_4k.png differ diff --git a/src/main/resources/rs117/hd/skybox3d/overcast_soil_puresky_4k.png b/src/main/resources/rs117/hd/skybox3d/overcast_soil_puresky_4k.png new file mode 100644 index 0000000000..2dc7f171fb Binary files /dev/null and b/src/main/resources/rs117/hd/skybox3d/overcast_soil_puresky_4k.png differ diff --git a/src/main/resources/rs117/hd/skybox3d/qwantani_night_puresky_4k.png b/src/main/resources/rs117/hd/skybox3d/qwantani_night_puresky_4k.png new file mode 100644 index 0000000000..888c632136 Binary files /dev/null and b/src/main/resources/rs117/hd/skybox3d/qwantani_night_puresky_4k.png differ diff --git a/src/main/resources/rs117/hd/skybox3d/sunflowers_puresky_4k.png b/src/main/resources/rs117/hd/skybox3d/sunflowers_puresky_4k.png new file mode 100644 index 0000000000..1f1cb76287 Binary files /dev/null and b/src/main/resources/rs117/hd/skybox3d/sunflowers_puresky_4k.png differ