From 4145c3c9559d94d8ffbdfc585437f398df0b3e18 Mon Sep 17 00:00:00 2001 From: crykn <crykn@users.noreply.github.com> Date: Thu, 15 Oct 2020 21:29:05 +0200 Subject: [PATCH] Initial commit. --- README.md | 61 +++ build.gradle | 81 ++++ .../gdx/utils/reflect/ReflectionUtils.java | 39 ++ .../commons/core/EskalonApplication.java | 286 ++++++++++++++ .../eskalon/commons/misc/EventQueueBus.java | 60 +++ .../screens/AbstractAssetLoadingScreen.java | 118 ++++++ .../commons/audio/DesktopSoundInstance.java | 97 +++++ .../commons/audio/DesktopSoundManager.java | 75 ++++ .../commons/graphics/ForwardRenderer.java | 41 ++ .../eskalon/commons/graphics/IRenderer.java | 31 ++ .../commons/graphics/PBRTextureAttribute.java | 104 +++++ .../de/eskalon/commons/graphics/Scene.java | 71 ++++ .../eskalon/commons/graphics/WorldObject.java | 114 ++++++ .../deferredrendering/AmbientLightPass.java | 94 +++++ .../deferredrendering/DebugLightPass.java | 362 ++++++++++++++++++ .../DefaultGeometryPassShader.java | 158 ++++++++ .../deferredrendering/DeferredRenderer.java | 112 ++++++ .../deferredrendering/GeometryPass.java | 115 ++++++ .../graphics/deferredrendering/LightPass.java | 34 ++ 19 files changed, 2053 insertions(+) create mode 100644 README.md create mode 100644 build.gradle create mode 100644 core/src/main/java/com/badlogic/gdx/utils/reflect/ReflectionUtils.java create mode 100644 core/src/main/java/de/eskalon/commons/core/EskalonApplication.java create mode 100644 core/src/main/java/de/eskalon/commons/misc/EventQueueBus.java create mode 100644 core/src/main/java/de/eskalon/commons/screens/AbstractAssetLoadingScreen.java create mode 100644 desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundInstance.java create mode 100644 desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundManager.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/ForwardRenderer.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/IRenderer.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/PBRTextureAttribute.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/Scene.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/WorldObject.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/AmbientLightPass.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DebugLightPass.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DefaultGeometryPassShader.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DeferredRenderer.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/GeometryPass.java create mode 100644 g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/LightPass.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..da829b2 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Pancake + +Pancake is the basis for all libGDX games developed by eskalon. + +## Technical + +Pancake uses the following libraries and frameworks : + +- [libGDX](https://github.com/libgdx/libgdx) +- [libgdx-screenmanager](https://github.com/crykn/libgdx-screenmanager) +- [guacamole](https://github.com/crykn/guacamole) +- [guava](https://github.com/google/guava) (for its EventBus) and [reflections](https://github.com/ronmamo/reflections) (for the classpath scanning in AbstractAssetLoadingScreen) + +## Content +A selection of what Pancake offers: + +### <u>core:</u> +- **AnnotationAssetManager** + - `@Asset("cool_texture.jpg")`, `@Asset(value = "ui/skin/skin.json", params = "ui/skin/skin.atlas")` + - `#loadAnnotatedAssets(Class<T> clazz)` + - `#injectAssets(Class<T> clazz, @Nullable T instance)` + - `#registerAssetLoaderParametersFactory(Class<T> clazz, AssetLoaderParametersFactory<T> factory)` +- **DefaultSoundManager & Playlist** + - `#playSoundEffect(String name)` + - `#playMusic(String playlistName)` + - Playlist files: +```java +{ + name: "best_playlist_ever", + shuffle: true, + repeat: true, + music: [ + [ + "My favourite song", + "my_favourite_song.mp3" + ], + ] + } + ``` +- **EskalonApplication:** the core application, which keeps a sprite batch, an asset manager, a sound manager, etc. +- **PostProcessingPipeline:** A simple post processing pipeline [WIP] +- **Lang, ILocalizable & ILocalized** + - `Lang#get(String key, Object... args)` +- **DebugInfoRenderer:** renders some debug information, including a fps graph +- **AbstractAssetLoadingScreen:** a screen loading all assets annotated with `@Asset` in a specified package +- **AbstractEskalonUIScreen:** a screen rendering a background image & a stage +- **AbstractImageScreen:** a simple screen rendering one image +- **EskalonSplashScreen:** a splash screen showing the eskalon logo as well as loading some internal assets +- **EskalonSettings & KeyBinding:** provides settings for different sound volumes & makes handling changeable key bindings very simple +- **RandomUtils:** contains various methods dealing with random values +- **GL32CMacIssueHandler:** provides updated versions of the default shaders that are compatible with OpenGL 3.2 + +### desktop +- Audio implementations that support spatial audio + +### g3d: +- A **deferred renderer** [WIP] + + +## Collaboration +A list of all of our current contributors and the used external assets can be found in [CONTRIBUTORS.md](https://github.com/eskalon/pancake/blob/master/CONTRIBUTORS.md). diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..cea57b3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,81 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + jcenter() + } + dependencies { + } +} + +allprojects { + apply plugin: "eclipse" + apply plugin: "idea" + + version = "0.1.0" + ext { + appName = "pancake" + gdxVersion = "1.9.11" + guavaVersion = "29.0-jre" // is used for the EventBus + junitVersion = "5.6.0" + mockitoVersion = "3.4.4" + reflectionsVersion = "0.9.11" //0.9.12 doesn't work, see https://github.com/ronmamo/reflections/issues/273 + screenmanagerVersion = "0.6.2" + } + + repositories { + mavenLocal() + mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "https://oss.sonatype.org/content/repositories/releases/" } + maven { url "https://jitpack.io" } + jcenter() + } +} + +project(":core") { + apply plugin: "java-library" + + dependencies { + implementation "com.badlogicgames.gdx:gdx:$gdxVersion" + implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" + implementation "com.google.guava:guava:$guavaVersion" + implementation "org.reflections:reflections:$reflectionsVersion" + api "com.github.crykn:libgdx-screenmanager:$screenmanagerVersion" + + testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + testImplementation "org.mockito:mockito-core:$mockitoVersion" + testImplementation "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion" + testImplementation "com.badlogicgames.gdx:gdx:$gdxVersion" + testImplementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" + testImplementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" + testImplementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" + } + + sourceSets.main { + resources.srcDirs = ["src/main/assets"] + } + + sourceSets.test { + resources.srcDirs = ["src/test/assets"] + } +} + +project(":g3d") { + apply plugin: "java-library" + + dependencies { + api project(":core") + implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" + } +} + +project(":desktop") { + apply plugin: "java-library" + + dependencies { + api project(":core") + implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" + } +} diff --git a/core/src/main/java/com/badlogic/gdx/utils/reflect/ReflectionUtils.java b/core/src/main/java/com/badlogic/gdx/utils/reflect/ReflectionUtils.java new file mode 100644 index 0000000..4c5be88 --- /dev/null +++ b/core/src/main/java/com/badlogic/gdx/utils/reflect/ReflectionUtils.java @@ -0,0 +1,39 @@ +package com.badlogic.gdx.utils.reflect; + +import javax.annotation.Nullable; + +/** + * Reflection utils for fields. + * + * @author damios + */ +public class ReflectionUtils { + + private ReflectionUtils() { + throw new UnsupportedOperationException(); + } + + public static Field convertFieldObject(java.lang.reflect.Field field) { + return new Field(field); + } + + /** + * Creates a class via libGDX reflection by using its name. Returns null, if the + * reflection or instantiation fails. + * + * @param <T> + * @param className + * @param clazz + * @return + */ + @SuppressWarnings("unchecked") + @Nullable + public static <T> T newInstance(String className, Class<T> clazz) { + try { + return (T) ClassReflection.newInstance(ClassReflection.forName(className)); + } catch (ReflectionException e) { + return null; + } + } + +} diff --git a/core/src/main/java/de/eskalon/commons/core/EskalonApplication.java b/core/src/main/java/de/eskalon/commons/core/EskalonApplication.java new file mode 100644 index 0000000..b571c97 --- /dev/null +++ b/core/src/main/java/de/eskalon/commons/core/EskalonApplication.java @@ -0,0 +1,286 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.core; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGeneratorLoader; +import com.badlogic.gdx.graphics.g2d.freetype.FreetypeFontLoader; +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.utils.reflect.ReflectionUtils; + +import de.damios.guacamole.annotations.GwtIncompatible; +import de.damios.guacamole.gdx.Log; +import de.damios.guacamole.gdx.assets.Text; +import de.damios.guacamole.gdx.assets.TextLoader; +import de.eskalon.commons.asset.AnnotationAssetManager; +import de.eskalon.commons.asset.BitmapFontAssetLoaderParametersFactory; +import de.eskalon.commons.asset.PlaylistDefinition; +import de.eskalon.commons.asset.PlaylistDefinitionLoader; +import de.eskalon.commons.audio.ISoundManager; +import de.eskalon.commons.misc.DebugInfoRenderer; +import de.eskalon.commons.misc.EskalonGameInputProcessor; +import de.eskalon.commons.misc.EskalonLogger; +import de.eskalon.commons.misc.EventQueueBus; +import de.eskalon.commons.screen.transition.ScreenTransition; +import de.eskalon.commons.screen.transition.impl.BlankTimedTransition; +import de.eskalon.commons.screen.transition.impl.BlendingTransition; +import de.eskalon.commons.screens.AbstractEskalonScreen; +import de.eskalon.commons.screens.BlankEskalonScreen; +import de.eskalon.commons.screens.EskalonSplashScreen; +import de.eskalon.commons.screens.EskalonSplashScreen.EskalonCommonsAssets; +import de.eskalon.commons.utils.ScreenshotUtils; +import de.eskalon.commons.utils.graphics.GL32CMacIssueHandler; + +/** + * A basic game application. Takes care of setting some convenience variables + * and constants. Furthermore, adds: + * <ul> + * <li>an {@linkplain #getAssetManager() asset manager}</li> + * <li>a {@linkplain #getSoundManager() sound manager}</li> + * <li>an {@linkplain #getEventBus() event bus}</li> + * <li>a {@linkplain #getSpriteBatch() batch} and an {@linkplain #getUICamera() + * ui camera}</li> + * <li>support for setting an {@linkplain #uiSkin UI skin}</li> + * </ul> + * When the application is created, a {@link EskalonSplashScreen} is pushed. + * + * @author damios + * @see BasicScreenManager + */ +public abstract class EskalonApplication extends ManagedGame<AbstractEskalonScreen, ScreenTransition> { + + @GwtIncompatible + public final boolean IN_DEV_ENV = EskalonApplication.class.getPackage().getImplementationVersion() == null; + @GwtIncompatible + public final String VERSION = IN_DEV_ENV ? "Development Build" + : EskalonApplication.class.getPackage().getImplementationVersion(); + + private DebugInfoRenderer debugInfoRenderer; + + protected AnnotationAssetManager assetManager = new AnnotationAssetManager(new InternalFileHandleResolver()); + + protected ISoundManager soundManager; + + private EskalonGameInputProcessor applicationInputProcessor = new EskalonGameInputProcessor(); + + protected EventQueueBus eventBus = new EventQueueBus(); + + protected SpriteBatch batch; + protected OrthographicCamera uiCamera; + + protected Skin uiSkin; + + private boolean debugLogging; + private boolean hasDepth; + + protected EskalonApplication() { + this(false, false); + } + + protected EskalonApplication(boolean debugLogging, boolean hasDepth) { + this.debugLogging = debugLogging; + this.hasDepth = hasDepth; + } + + @Override + public final void create() { + // Log stuff + Gdx.app.setApplicationLogger(new EskalonLogger()); + + if (debugLogging) + Log.showAll(); + else + Log.showInfoAndErrors(); + + Log.info("Start ", "Version: '%s' | App Type: '%s' | OS: '%s'", VERSION, Gdx.app.getType(), + System.getProperty("os.name")); + Log.debug("Start ", "GL30 Available: '%b' | Renderer: '%s'", Gdx.graphics.isGL30Available(), + Gdx.graphics.getGLVersion().getRendererString()); + + // Initialize managed game + super.create(); + + // Add input listener + getInputMultiplexer().addProcessor(applicationInputProcessor); + + // Configure asset manager + this.assetManager.setLoader(FreeTypeFontGenerator.class, + new FreeTypeFontGeneratorLoader(this.assetManager.getFileHandleResolver())); + this.assetManager.setLoader(BitmapFont.class, ".ttf", + new FreetypeFontLoader(this.assetManager.getFileHandleResolver())); + this.assetManager.setLoader(Text.class, new TextLoader(this.assetManager.getFileHandleResolver())); + this.assetManager.setLoader(PlaylistDefinition.class, + new PlaylistDefinitionLoader(this.assetManager.getFileHandleResolver())); + this.assetManager.registerAssetLoaderParametersFactory(BitmapFont.class, + new BitmapFontAssetLoaderParametersFactory()); + + // Sound manager + this.soundManager = ReflectionUtils.newInstance("de.eskalon.commons.audio.DesktopSoundManager", + ISoundManager.class); + + // Create sprite batch & camera + this.batch = new SpriteBatch(1000, + GL32CMacIssueHandler.doUse32CShader() ? GL32CMacIssueHandler.createSpriteBatchShader() : null); + + this.uiCamera = new OrthographicCamera(viewportWidth, viewportHeight); + this.uiCamera.combined.setToOrtho2D(0, 0, getWidth(), getHeight()); + + // Configure screen manager + this.getScreenManager().setHasDepth(hasDepth); + + // Debug info renderer + debugInfoRenderer = new DebugInfoRenderer(batch, debugLogging, VERSION, soundManager); + + // Splash Screen + this.screenManager.addScreen("blank", new BlankEskalonScreen(this)); + this.screenManager.addScreen("splash", new EskalonSplashScreen(this, (param) -> { + // Enable stuff depending on commons assets + applicationInputProcessor.enable(); + debugInfoRenderer.initialize(getWidth(), getHeight(), + assetManager.get(EskalonCommonsAssets.DEFAULT_FONT_NAME)); + + // Push second screen (usually asset loading) + screenManager.pushScreen("blank", "splashOutTransition1"); + screenManager.pushScreen("blank", "splashOutTransition2"); + screenManager.pushScreen(initApp(), "splashOutTransition3"); + })); + + BlendingTransition splashBlendingTransition = new BlendingTransition(batch, 0.25F, Interpolation.exp10In); + screenManager.addScreenTransition("splashInTransition", splashBlendingTransition); + BlendingTransition splashOutTransition1 = new BlendingTransition(batch, 0.18F, Interpolation.fade); + screenManager.addScreenTransition("splashOutTransition1", splashOutTransition1); + BlankTimedTransition splashOutTransition2 = new BlankTimedTransition(0.22F); + screenManager.addScreenTransition("splashOutTransition2", splashOutTransition2); + BlendingTransition splashOutTransition3 = new BlendingTransition(batch, 0.35F, Interpolation.pow2In); + screenManager.addScreenTransition("splashOutTransition3", splashOutTransition3); + + // Push the splash screen + screenManager.pushScreen("splash", "splashInTransition"); + } + + /** + * Takes care of initializing the application. + * + * @return the name of the screen that should be pushed after the splash screen + */ + protected abstract String initApp(); + + @Override + public void render() { + /* + * Takes care of posting the events in the rendering thread + */ + eventBus.distributeEvents(); + + /* + * Render the screen + */ + super.render(); + + /* + * Update fps counter + */ + debugInfoRenderer.update(Gdx.graphics.getDeltaTime()); + + /* + * Debug overlay + */ + if (applicationInputProcessor.isDebugOverlayEnabled()) { + debugInfoRenderer.render(); + } + /* + * Take a screenshot + */ + if (applicationInputProcessor.takeScreenshot()) { + ScreenshotUtils.takeAndSaveScreenshot(); + soundManager.playSoundEffect(EskalonCommonsAssets.SHUTTER_SOUND_NAME); + applicationInputProcessor.setTakeScreenshot(false); + } + } + + @Override + public void resize(int width, int height) { + super.resize(width, height); + + debugInfoRenderer.resize(width, height); + uiCamera.combined.setToOrtho2D(0, 0, getWidth(), getHeight()); + } + + /** + * @return the asset manager used by the game + */ + public AnnotationAssetManager getAssetManager() { + return this.assetManager; + } + + /** + * @return the sound manager used to play the game's audio + */ + public ISoundManager getSoundManager() { + return soundManager; + } + + /** + * @return the sprite batch to render 2D stuff with + */ + public SpriteBatch getSpriteBatch() { + return batch; + } + + /** + * @return the camera used by the UI screens + */ + public OrthographicCamera getUICamera() { + return this.uiCamera; + } + + /** + * @return the events bus; events are queued first and then processed in the + * rendering thread; see {@link EventQueueBus} + */ + public EventQueueBus getEventBus() { + return eventBus; + } + + public void setUISkin(Skin skin) { + this.uiSkin = skin; + } + + /** + * @return the application's UI skin; has to be set via {@link #setUISkin(Skin)} + * beforehand + */ + public Skin getUISkin() { + return uiSkin; + } + + @Override + public void dispose() { + super.dispose(); + assetManager.dispose(); + batch.dispose(); + + // if (uiSkin != null) + // uiSkin.dispose(); + } + +} diff --git a/core/src/main/java/de/eskalon/commons/misc/EventQueueBus.java b/core/src/main/java/de/eskalon/commons/misc/EventQueueBus.java new file mode 100644 index 0000000..3617076 --- /dev/null +++ b/core/src/main/java/de/eskalon/commons/misc/EventQueueBus.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.misc; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import com.google.common.eventbus.EventBus; + +/** + * This event bus queues events and only posts them to the subscribers when + * {@link #distributeEvents()} is called. This can be useful if events have to + * get handled in a certain thread. + * + * @author damios + */ +public class EventQueueBus extends EventBus { + + /** + * Queue of posted events. Is taken care of when {@link #distributeEvents()} is + * called. + */ + private Queue<Object> eventQueue = new ConcurrentLinkedQueue<>(); + + /** + * After this method is called the {@linkplain #eventQueue queued events} get + * posted to their respective subscribers. + */ + public void distributeEvents() { + Object event = eventQueue.poll(); + while (event != null) { + super.post(event); + event = eventQueue.poll(); + } + } + + /** + * {@inheritDoc} + * <p> + * The events get queued until {@link #distributeEvents()} is called. + */ + @Override + public void post(Object event) { + this.eventQueue.add(event); + } + +} diff --git a/core/src/main/java/de/eskalon/commons/screens/AbstractAssetLoadingScreen.java b/core/src/main/java/de/eskalon/commons/screens/AbstractAssetLoadingScreen.java new file mode 100644 index 0000000..3de3f81 --- /dev/null +++ b/core/src/main/java/de/eskalon/commons/screens/AbstractAssetLoadingScreen.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.screens; + +import java.lang.reflect.Field; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.reflections.Reflections; +import org.reflections.scanners.FieldAnnotationsScanner; + +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.utils.reflect.ReflectionUtils; + +import de.damios.guacamole.annotations.GwtIncompatible; +import de.eskalon.commons.asset.AnnotationAssetManager; +import de.eskalon.commons.asset.AnnotationAssetManager.Asset; +import de.eskalon.commons.core.EskalonApplication; + +/** + * This screen takes care of loading all assets for an + * {@link EskalonApplication}. Assets have to be in a package below the + * specified root package and be annotated with {@link Asset}. + * <p> + * Afterwards, the loaded assets can be injected in the respective fields via + * {@link AnnotationAssetManager#injectAssets(Object)}. + * + * @author damios + */ +@GwtIncompatible +public abstract class AbstractAssetLoadingScreen extends AbstractEskalonScreen { + + protected EskalonApplication application; + @Nullable + private String packageRoot; + + private float progress; + private boolean isDone = false; + + public AbstractAssetLoadingScreen(EskalonApplication application, + @Nullable String packageRoot) { + this.application = application; + this.packageRoot = packageRoot; + } + + @Override + protected void create() { + loadOwnAssets(); + + if (packageRoot != null) { + Reflections reflections = new Reflections(packageRoot, + new FieldAnnotationsScanner()); + Set<Field> assetFields = reflections + .getFieldsAnnotatedWith(Asset.class); + + for (Field f : assetFields) { + application.getAssetManager() + .loadAnnotatedAsset(ReflectionUtils.convertFieldObject(f)); + } + } + } + + @Override + public void show() { + super.show(); + this.isDone = false; + } + + /** + * Loads the assets used in the loading screen itself. + */ + protected abstract void loadOwnAssets(); + + /** + * This method is responsible for finishing up the loaded assets (e.g. + * create the skin) and pushing the next screen. + */ + protected abstract void onFinishedLoading(); + + @Override + public void render(float delta) { + progress = Interpolation.linear + .apply(application.getAssetManager().getProgress()); + // make sure that the final few progress values are 1, which is more + // pleasant to look at + progress = progress > 0.98F ? 1 : progress + 0.02F; + + // Check if the asset manager is done + if (!isDone && application.getAssetManager().update()) { + isDone = true; + onFinishedLoading(); + } + + render(delta, progress); + } + + @Override + protected EskalonApplication getApplication() { + return application; + } + + public abstract void render(float delta, float progress); + +} diff --git a/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundInstance.java b/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundInstance.java new file mode 100644 index 0000000..252b195 --- /dev/null +++ b/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundInstance.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.audio; + +import org.lwjgl.openal.AL10; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio; +import com.badlogic.gdx.math.Vector3; + +/** + * @author damios + */ +public class DesktopSoundInstance extends DefaultSoundInstance { + + private int sourceId = -1; + + public DesktopSoundInstance(Sound sound, long soundId) { + super(sound, soundId); + sourceId = ((OpenALLwjgl3Audio) Gdx.audio).getSourceId(soundId); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundOrientation(float x, float y, float z) { + AL10.alSource3f(sourceId, AL10.AL_ORIENTATION, x, y, z); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundOrientation(Vector3 dir) { + super.setSoundOrientation(dir); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundVelocity(float x, float y, float z) { + AL10.alSource3f(sourceId, AL10.AL_VELOCITY, x, y, z); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundVelocity(Vector3 dir) { + super.setSoundVelocity(dir); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundPosition(float x, float y, float z) { + AL10.alSource3f(sourceId, AL10.AL_POSITION, x, y, z); + } + + /** + * {@inheritDoc} + * <p> + * Only supports mono sounds! + */ + @Override + public void setSoundPosition(Vector3 dir) { + super.setSoundPosition(dir); + } + +} diff --git a/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundManager.java b/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundManager.java new file mode 100644 index 0000000..4a6e5ef --- /dev/null +++ b/desktop/src/main/java/de/eskalon/commons/audio/DesktopSoundManager.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.audio; + +import java.nio.FloatBuffer; +import java.util.NoSuchElementException; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL10; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.utils.GdxRuntimeException; + +/** + * @author damios + */ +public class DesktopSoundManager extends DefaultSoundManager { + + @Override + public ISoundInstance playSoundEffect(String name, boolean stopIfPlaying, + float pitch) { + Sound effect = soundEffects.get(name); + + if (effect == null) + throw new NoSuchElementException( + "There is no sound effect with the name '" + name + + "' registered"); + + if (stopIfPlaying) + effect.stop(); + + long id = effect.play(getEffectiveVolume(effectVolume)); + + if (id == -1) + throw new GdxRuntimeException( + "Some error occurred playing the sound"); + + effect.setPitch(id, pitch); + + return new DesktopSoundInstance(effect, id); + } + + @Override + public void setListenerOrientation(float lookX, float lookY, float lookZ, + float upX, float upY, float upZ) { + AL10.alListenerfv(AL10.AL_ORIENTATION, + (FloatBuffer) BufferUtils.createFloatBuffer(6) + .put(new float[] { lookX, lookY, lookZ, upX, upY, upZ }) + .flip()); + } + + @Override + public void setListenerVelocity(float x, float y, float z) { + AL10.alListener3f(AL10.AL_VELOCITY, x, y, z); + } + + @Override + public void setListenerPosition(float x, float y, float z) { + AL10.alListener3f(AL10.AL_POSITION, x, y, z); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/ForwardRenderer.java b/g3d/src/main/java/de/eskalon/commons/graphics/ForwardRenderer.java new file mode 100644 index 0000000..1f9f446 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/ForwardRenderer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.utils.Array; + +/** + * This class should implement forward rendering. + * + * @author Sarroxxie + */ +public class ForwardRenderer implements IRenderer { + + @Override + public void render(Array<ModelInstance> objects) { + // TODO Auto-generated method stub + + } + + @Override + public void setCamera(Camera camera) { + // TODO Auto-generated method stub + + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/IRenderer.java b/g3d/src/main/java/de/eskalon/commons/graphics/IRenderer.java new file mode 100644 index 0000000..6bd6229 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/IRenderer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.utils.Array; + +/** + * @author Sarroxxie + */ +public interface IRenderer { + + public void render(Array<ModelInstance> objects); + + public void setCamera(Camera camera); + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/PBRTextureAttribute.java b/g3d/src/main/java/de/eskalon/commons/graphics/PBRTextureAttribute.java new file mode 100644 index 0000000..f12e327 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/PBRTextureAttribute.java @@ -0,0 +1,104 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute; +import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor; + +/** + * Adds additional identifier for Metallic, AmbientOcclusion and Roughness + * Textures to use in + * <a href="https://en.wikipedia.org/wiki/Physically_based_rendering"> + * Physically Based Rendering</a>. + * + * @author Sarroxxie + */ +public class PBRTextureAttribute extends TextureAttribute { + + public final static String AlbedoAlias = "albedoTexture"; + public final static long Albedo = register(AlbedoAlias); + public final static String MetallicAlias = "metallicTexture"; + public final static long Metallic = register(MetallicAlias); + public final static String RoughnessAlias = "roughnessTexture"; + public final static long Roughness = register(RoughnessAlias); + + static { + Mask |= Albedo | Metallic | Roughness; + } + + public static PBRTextureAttribute createAlbedo(final Texture texture) { + return new PBRTextureAttribute(Albedo, texture); + } + + public static PBRTextureAttribute createAlbedo(final TextureRegion region) { + return new PBRTextureAttribute(Albedo, region); + } + + public static PBRTextureAttribute createMetallic(final Texture texture) { + return new PBRTextureAttribute(Metallic, texture); + } + + public static PBRTextureAttribute createMetallic( + final TextureRegion region) { + return new PBRTextureAttribute(Metallic, region); + } + + public static PBRTextureAttribute createRoughness(final Texture texture) { + return new PBRTextureAttribute(Roughness, texture); + } + + public static PBRTextureAttribute createRoughness( + final TextureRegion region) { + return new PBRTextureAttribute(Roughness, region); + } + + public PBRTextureAttribute(final long type) { + super(type); + } + + public <T extends Texture> PBRTextureAttribute(final long type, + final TextureDescriptor<T> textureDescription) { + super(type, textureDescription); + } + + public <T extends Texture> PBRTextureAttribute(final long type, + final TextureDescriptor<T> textureDescription, float offsetU, + float offsetV, float scaleU, float scaleV, int uvIndex) { + super(type, textureDescription, offsetU, offsetV, scaleU, scaleV, + uvIndex); + } + + public <T extends Texture> PBRTextureAttribute(final long type, + final TextureDescriptor<T> textureDescription, float offsetU, + float offsetV, float scaleU, float scaleV) { + super(type, textureDescription, offsetU, offsetV, scaleU, scaleV); + } + + public PBRTextureAttribute(final long type, final Texture texture) { + super(type, texture); + } + + public PBRTextureAttribute(final long type, final TextureRegion region) { + super(type, region); + } + + public PBRTextureAttribute(final TextureAttribute copyFrom) { + super(copyFrom); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/Scene.java b/g3d/src/main/java/de/eskalon/commons/graphics/Scene.java new file mode 100644 index 0000000..a310509 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/Scene.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics; + +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; + +import de.eskalon.commons.core.EskalonApplication; +import de.eskalon.commons.graphics.deferredrendering.DeferredRenderer; + +/** + * This class contains all information needed for rendering a 3D Scene. + * + * @author Sarroxxie + */ +public class Scene implements Disposable { + + private Array<ModelInstance> instances; + private IRenderer renderer; + private EskalonApplication game; + + private Camera camera; + + /** + * + * @param width + * viewport width + * @param height + * viewport height + */ + public Scene(EskalonApplication game, int width, int height) { + this.game = game; + this.instances = new Array<ModelInstance>(); + this.renderer = new DeferredRenderer(this.game); + } + + public void render() { + this.renderer.render(this.instances); + } + + public void setCamera(Camera camera) { + this.camera = camera; + this.renderer.setCamera(this.camera); + } + + public void addInstance(ModelInstance instance) { + this.instances.add(instance); + } + + @Override + public void dispose() { + if (renderer instanceof Disposable) + ((Disposable) renderer).dispose(); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/WorldObject.java b/g3d/src/main/java/de/eskalon/commons/graphics/WorldObject.java new file mode 100644 index 0000000..21d744a --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/WorldObject.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics; + +import com.badlogic.gdx.graphics.g3d.Model; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.BoundingBox; + +import de.damios.guacamole.Preconditions; + +/** + * Adds a bit of functionality to the + * {@linkplain com.badlogic.gdx.graphics.g3d.ModelInstance model instance} and + * stores a corresponding + * {@linkplain com.badlogic.gdx.math.collision.BoundingBox bounding box}. + * + * @author Sarroxxie + */ +public class WorldObject extends ModelInstance { + + // TODO: Add functionality for animations + + // TODO: missing constructors, super from model instance + + /** + * Stored {@linkplain com.badlogic.gdx.math.collision.BoundingBox Bounding + * Box}. This contains necessary information for collision tests and + * <a href= + * "https://en.wikipedia.org/wiki/Hidden-surface_determination#Viewing-frustum_culling"> + * View Frustum Culling</a>. + */ + private final BoundingBox bounds; + + /** + * Constructs a new WorldObject and calculates its bounding box. + * + * @param model + * The {@link com.badlogic.gdx.graphics.g3d.Model Model} to + * create an instance of. + */ + public WorldObject(Model model) { + super(model); + this.bounds = new BoundingBox(); + this.calculateBoundingBox(this.bounds); + } + + /** + * Calculates the center of the {@linkplain #bounds Bounding Box}. + * + * @return Center of the {@linkplain #bounds Bounding Box} in Object-Space. + */ + public Vector3 getBoundingCenter() { + return this.bounds.getCenter(new Vector3()); // TODO cache? + } + + /** + * Calculates the minimal radius from the {@linkplain #bounds Bounding Box} + * so a sphere with this radius around the {@linkplain #getBoundingCenter() + * center} of the {@linkplain #bounds Bounding Box} fully contains the + * geometry. + * + * @return Radius of the {@linkplain #bounds Bounding Box} in Object-Space. + */ + public float getBoundingRadius() { + return this.bounds.getDimensions(new Vector3()).len() / 2f; + } + + /** + * Sets the scaling of the World Object by modifying its + * {@linkplain com.badlogic.gdx.graphics.g3d.ModelInstance#transform world + * transform}. + * + * @param scaleX + * The x-coordinate of the new scale vector. + * @param scaleY + * The y-coordinate of the new scale vector. + * @param scaleZ + * The z-coordinate of the new scale vector. + */ + public void setScale(float scaleX, float scaleY, float scaleZ) { + Preconditions.checkArgument(scaleX != 0 && scaleY != 0 && scaleZ != 0, + "Neither coordinate of the scale vector is allowed to be 0."); + Vector3 oldScale = new Vector3(); + this.transform.getScale(oldScale); + this.transform.scale(scaleX / oldScale.x, scaleY / oldScale.y, + scaleZ / oldScale.z); + } + + /** + * Sets the scaling of the World Object by modifying its + * {@linkplain com.badlogic.gdx.graphics.g3d.ModelInstance#transform world + * transform}. + * + * @param scale + * The new scale vector. + */ + public void setScale(Vector3 scale) { + this.setScale(scale.x, scale.y, scale.z); + } +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/AmbientLightPass.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/AmbientLightPass.java new file mode 100644 index 0000000..c975443 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/AmbientLightPass.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Mesh; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Vector3; + +import de.damios.guacamole.gdx.graphics.QuadMeshGenerator; +import de.damios.guacamole.gdx.graphics.ShaderProgramFactory; + +/** + * @author Sarroxxie + */ +public class AmbientLightPass extends LightPass { + + private ShaderProgram program; + private Camera orthoCam; + public Vector3 ambientColor = new Vector3(0.6f, 0.6f, 0.6f); + + private Mesh screenQuad; + + // TODO: Write missing shader! + private final FileHandle vert = Gdx.files + .internal("resources/shaders/image.vert"); + private final FileHandle frag = Gdx.files + .internal("resources/shaders/ambient.frag"); + + public AmbientLightPass(DeferredRenderer renderer) { + super(renderer); + this.orthoCam = this.renderer.game.getUICamera(); + this.program = ShaderProgramFactory.fromFile(vert, frag); + this.screenQuad = QuadMeshGenerator.createFullScreenQuad( + renderer.game.getWidth(), renderer.game.getHeight(), true); + } + + @Override + public void render() { + this.renderer.context.begin(); + + this.renderer.gBuffer.getTextureAttachments() + .get(DeferredRenderer.MATERIAL_ATTACHMENT_INDEX) + .setFilter(TextureFilter.Nearest, TextureFilter.Nearest); + + this.renderer.gBuffer.getTextureAttachments() + .get(DeferredRenderer.ALBEDO_ATTACHMENT_INDEX) + .setFilter(TextureFilter.Nearest, TextureFilter.Nearest); + + this.program.bind(); + this.program.setUniformMatrix("u_projTrans", this.orthoCam.combined); + this.program.setUniformf("u_ambient_color", ambientColor.x, + ambientColor.y, ambientColor.z); + this.program.setUniformi("u_ambient_occlusion", + this.renderer.context.textureBinder + .bind(this.renderer.gBuffer.getTextureAttachments().get( + DeferredRenderer.MATERIAL_ATTACHMENT_INDEX))); + this.program.setUniformi("u_albedo", + this.renderer.context.textureBinder + .bind(this.renderer.gBuffer.getTextureAttachments().get( + DeferredRenderer.ALBEDO_ATTACHMENT_INDEX))); + this.screenQuad.render(this.program, GL20.GL_TRIANGLE_STRIP); + + this.renderer.context.end(); + } + + @Override + public void dispose() { + this.program.dispose(); + } + + public void resize() { + this.screenQuad = QuadMeshGenerator.createFullScreenQuad( + renderer.game.getWidth(), renderer.game.getHeight(), true); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DebugLightPass.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DebugLightPass.java new file mode 100644 index 0000000..555600f --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DebugLightPass.java @@ -0,0 +1,362 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Mesh; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.FrameBuffer; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; + +import de.damios.guacamole.gdx.graphics.NestableFrameBuffer; +import de.damios.guacamole.gdx.graphics.QuadMeshGenerator; +import de.damios.guacamole.gdx.graphics.ShaderProgramFactory; +import de.eskalon.commons.screens.EskalonSplashScreen.EskalonCommonsAssets; +import de.eskalon.commons.utils.graphics.GL32CMacIssueHandler; + +/** + * Renders the content of the g-buffer for debug purposes. + * + * Layout: \ albedo \ normal \ depth \ ambient occlusion \ shaded result \ + * (empty) \ metallic \ roughness \ (empty) + * + * @author Sarroxxie + */ +public class DebugLightPass extends LightPass { + + private Camera orthoCam; + private Map<String, Mesh> quads = new ConcurrentHashMap<String, Mesh>(); + private BitmapFont defaultFont; + private ShapeRenderer shapeRenderer; + private SpriteBatch sBatch; + + private FrameBuffer finalRenderBuffer; + private LightPass ambientLightPass; + + private int viewportWidth; + private int viewportHeight; + + /** + * Paths to the needed shader files. + */ + private final FileHandle vert = Gdx.files + .internal("resources/shaders/image.vert"); + private final FileHandle imageFrag = Gdx.files + .internal("resources/shaders/image.frag"); + private final FileHandle depthFrag = Gdx.files + .internal("resources/shaders/imageDepth.frag"); + private final FileHandle ambientFrag = Gdx.files + .internal("resources/shaders/imageBlue.frag"); + private final FileHandle roughnessFrag = Gdx.files + .internal("resources/shaders/imageGreen.frag"); + private final FileHandle metallicFrag = Gdx.files + .internal("resources/shaders/imageRed.frag"); + + /** + * Contains the line positions like such: {left, right, bottom, top} + */ + private float[] linePositions = new float[4]; + + /** + * Contains the text positions like such: {left collumn, mid collumn, right + * collumn, bottom row, mid row, top row} + */ + private float[] textPositions = new float[6]; + + /** + * This is the percentage of the screen height that the text will be offset + * on the negative Y-axis (downwards). + */ + public float yOffsetPct = 1 / 50f; + + /** + * This is the thickness of the lines between the boxes. + */ + public float lineWidth = 1f; + + private ShaderProgram program, depth, metallic, roughness, ambient; + + public DebugLightPass(DeferredRenderer renderer) { + super(renderer); + this.orthoCam = this.renderer.game.getUICamera(); + this.sBatch = this.renderer.game.getSpriteBatch(); + this.shapeRenderer = new ShapeRenderer(5000, GL32CMacIssueHandler + .createImmediateModeRenderer20DefaultShader(false, true, 0)); + + this.ambientLightPass = new AmbientLightPass(this.renderer); + + this.viewportWidth = this.renderer.game.getWidth(); + this.viewportHeight = this.renderer.game.getHeight(); + + this.calculatePositions(viewportWidth, viewportHeight); + + this.finalRenderBuffer = new NestableFrameBuffer(Format.RGBA8888, + viewportWidth, viewportHeight, false); + + this.addQuads(); + + // all necessary shader programs are compiled + this.program = ShaderProgramFactory.fromFile(vert, imageFrag); + this.depth = ShaderProgramFactory.fromFile(vert, depthFrag); + this.ambient = ShaderProgramFactory.fromFile(vert, ambientFrag); + this.roughness = ShaderProgramFactory.fromFile(vert, roughnessFrag); + this.metallic = ShaderProgramFactory.fromFile(vert, metallicFrag); + } + + /** + * Calculates the positions for the lines and the texts using the given + * viewport width and height. + * + * @param viewportWidth + * screen width. + * @param viewportHeight + * screen height. + */ + private void calculatePositions(int viewportWidth, int viewportHeight) { + // calculates the positions of the lines + this.linePositions[0] = viewportWidth / 3f; + this.linePositions[1] = linePositions[0] * 2f; + this.linePositions[2] = viewportHeight / 3f; + this.linePositions[3] = linePositions[2] * 2f; + + float yOffset = viewportHeight * this.yOffsetPct + this.lineWidth / 2; + + // calculates the positions of the debug texts + this.textPositions[0] = viewportWidth / 6f; + this.textPositions[1] = textPositions[0] + linePositions[0]; + this.textPositions[2] = textPositions[1] + linePositions[0]; + + this.textPositions[3] = linePositions[2] - yOffset; + this.textPositions[4] = linePositions[3] - yOffset; + this.textPositions[5] = viewportHeight - yOffset; + } + + /** + * Registers all the used quads to the {@linkplain #quads map}. + */ + private void addQuads() { + this.quads.put("albedo", QuadMeshGenerator.createQuadFromCoordinates(0, + linePositions[3], linePositions[0], viewportHeight, true)); + this.quads.put("normal", + QuadMeshGenerator.createQuadFromCoordinates(linePositions[0], + linePositions[3], linePositions[1], viewportHeight, + true)); + this.quads.put("depth", + QuadMeshGenerator.createQuadFromCoordinates(linePositions[1], + linePositions[3], viewportWidth, viewportHeight, true)); + this.quads.put("final render", + QuadMeshGenerator.createQuadFromCoordinates(linePositions[0], + linePositions[2], linePositions[1], linePositions[3], + true)); + this.quads.put("ambient occlusion", + QuadMeshGenerator.createQuadFromCoordinates(0, 0, + linePositions[0], linePositions[2], true)); + this.quads.put("roughness", QuadMeshGenerator.createQuadFromCoordinates( + linePositions[0], 0, linePositions[1], linePositions[2], true)); + this.quads.put("metallic", QuadMeshGenerator.createQuadFromCoordinates( + linePositions[1], 0, viewportWidth, linePositions[2], true)); + } + + @Override + public void render() { + this.renderFinalRender(); + this.renderer.context.begin(); + + Gdx.gl.glClearColor(0f, 0f, 0f, 0f); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + + // renders the content of the g-buffer onto quads + this.renderQuad(program, "albedo", this.renderer.gBuffer, + DeferredRenderer.ALBEDO_ATTACHMENT_INDEX); + this.renderQuad(program, "normal", this.renderer.gBuffer, + DeferredRenderer.NORMAL_ATTACHMENT_INDEX); + this.renderQuad(depth, "depth", this.renderer.gBuffer, + DeferredRenderer.DEPTH_ATTACHMENT_INDEX); + this.renderQuad(program, "final render", finalRenderBuffer, 0); + this.renderQuad(ambient, "ambient occlusion", this.renderer.gBuffer, + DeferredRenderer.MATERIAL_ATTACHMENT_INDEX); + this.renderQuad(roughness, "roughness", this.renderer.gBuffer, + DeferredRenderer.MATERIAL_ATTACHMENT_INDEX); + this.renderQuad(metallic, "metallic", this.renderer.gBuffer, + DeferredRenderer.MATERIAL_ATTACHMENT_INDEX); + + this.renderer.context.end(); + + this.renderDebugLines(); + this.renderDebugText(); + } + + /** + * Renders a texture onto a given quad at a given attachment index using a + * given {@link ShaderProgram}. Note, that this method uses + * {@linkplain de.eskalon.commons.graphics.deferredrendering.DeferredRenderer#context + * this render context}, so in order to work correctly, + * {@linkplain com.badlogic.gdx.graphics.g3d.utils.RenderContext#begin() + * begin()} should be called before calling this method and + * {@linkplain com.badlogic.gdx.graphics.g3d.utils.RenderContext#end() + * end()} should be called after calling this method. + * + * @param program + * The {@link ShaderProgram} that renders the quad. + * @param quadID + * ID of the quad in the {@linkplain #quads map}. + * @param attachmentIndex + * The index of the texture attachment in the + * {@linkplain de.eskalon.commons.graphics.deferredrendering.DeferredRenderer#gBuffer + * g-Buffer}. + */ + private void renderQuad(ShaderProgram program, String quadID, + FrameBuffer buffer, int attachmentIndex) { + this.renderer.gBuffer.getTextureAttachments().get(attachmentIndex) + .setFilter(TextureFilter.Linear, TextureFilter.Linear); + program.bind(); + program.setUniformMatrix("u_projTrans", this.orthoCam.combined); + program.setUniformi("u_texture", this.renderer.context.textureBinder + .bind(buffer.getTextureAttachments().get(attachmentIndex))); + this.quads.get(quadID).render(program, GL20.GL_TRIANGLE_STRIP); + } + + private void renderFinalRender() { + this.finalRenderBuffer.begin(); + this.ambientLightPass.render(); + this.finalRenderBuffer.end(); + } + + /** + * Renders a debug text for each of the boxes. + */ + protected void renderDebugText() { + if (this.defaultFont == null) { + this.defaultFont = this.renderer.game.getAssetManager() + .get(EskalonCommonsAssets.DEFAULT_FONT_NAME); + } + this.sBatch.begin(); + + this.renderCenteredString("Albedo", this.textPositions[0], + this.textPositions[5]); + this.renderCenteredString("Normal", this.textPositions[1], + this.textPositions[5]); + this.renderCenteredString("Depth", this.textPositions[2], + this.textPositions[5]); + this.renderCenteredString("Final Render (ambient lighting only)", + this.textPositions[1], this.textPositions[4]); + this.renderCenteredString("Ambient Occlusion", this.textPositions[0], + this.textPositions[3]); + this.renderCenteredString("Roughness", this.textPositions[1], + this.textPositions[3]); + this.renderCenteredString("Metallic", this.textPositions[2], + this.textPositions[3]); + + this.sBatch.end(); + } + + /** + * Renders a given text at a given position. The text will be x-centered. + * + * @param text + * @param x + * x-coordinate where the center of the text will be. + * @param y + * y-coordinate where the center of the text will be. + */ + private void renderCenteredString(String text, float x, float y) { + this.defaultFont.draw(this.sBatch, text, + x - this.getCenteredFontOffset(this.defaultFont, text), y); + } + + /** + * Renders lines with a certain {@linkplain #lineWidth thickness} to + * seperate the boxes. + */ + protected void renderDebugLines() { + this.shapeRenderer.begin(ShapeType.Filled); + this.shapeRenderer.setColor(Color.LIGHT_GRAY); + + // MIDDLE LINES + // left line + this.shapeRenderer.rectLine(this.linePositions[0], 0f, + this.linePositions[0], this.renderer.game.getHeight(), + this.lineWidth); + // right line + this.shapeRenderer.rectLine(this.linePositions[1], 0f, + this.linePositions[1], this.renderer.game.getHeight(), + this.lineWidth); + // bottom line + this.shapeRenderer.rectLine(0f, this.linePositions[2], + this.renderer.game.getWidth(), this.linePositions[2], + this.lineWidth); + // top line + this.shapeRenderer.rectLine(0f, this.linePositions[3], + this.renderer.game.getWidth(), this.linePositions[3], + this.lineWidth); + + // SCREEN BORDER LINES + if (this.lineWidth > 1f) { + // left line + this.shapeRenderer.rectLine(0f, 0f, 0f, this.viewportHeight, + this.lineWidth); + // right line + this.shapeRenderer.rectLine(this.viewportWidth, 0f, + this.viewportWidth, this.viewportHeight, this.lineWidth); + // bottom line + this.shapeRenderer.rectLine(0f, 0f, this.viewportWidth, 0f, + this.lineWidth); + // top line + this.shapeRenderer.rectLine(0f, this.viewportHeight, + this.viewportWidth, this.viewportHeight, this.lineWidth); + } + + this.shapeRenderer.end(); + } + + /** + * Calculates the necessary x-offset so the BitmapFont can be rendered + * centered. + * + * @param bitmapFont + * Used Font. + * @param text + * @return x-offset + */ + private float getCenteredFontOffset(BitmapFont bitmapFont, String text) { + GlyphLayout glyphLayout = new GlyphLayout(); + glyphLayout.setText(bitmapFont, text); + return glyphLayout.width / 2; + } + + @Override + public void dispose() { + this.program.dispose(); + this.depth.dispose(); + this.metallic.dispose(); + this.roughness.dispose(); + this.ambient.dispose(); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DefaultGeometryPassShader.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DefaultGeometryPassShader.java new file mode 100644 index 0000000..fdd19de --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DefaultGeometryPassShader.java @@ -0,0 +1,158 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import java.lang.reflect.Field; + +import org.lwjgl.opengl.GL32C; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.VertexAttributes; +import com.badlogic.gdx.graphics.g3d.Material; +import com.badlogic.gdx.graphics.g3d.Renderable; +import com.badlogic.gdx.graphics.g3d.Shader; +import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute; +import com.badlogic.gdx.graphics.g3d.utils.RenderContext; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Matrix3; + +import de.damios.guacamole.gdx.graphics.ShaderProgramFactory; +import de.eskalon.commons.graphics.PBRTextureAttribute; + +/** + * @author Sarroxxie + */ +public class DefaultGeometryPassShader implements Shader { + + private ShaderProgram program; + private RenderContext context; + + static final long Mask = PBRTextureAttribute.Albedo + | PBRTextureAttribute.Normal | PBRTextureAttribute.Metallic + | PBRTextureAttribute.Roughness | PBRTextureAttribute.Ambient; + + private Matrix3 matrix3 = new Matrix3(); + + @Override + public void dispose() { + this.program.dispose(); + } + + @Override + public void init() { + this.program = ShaderProgramFactory.fromFile( + Gdx.files.internal("resources/shaders/defaultGeometryPassShader.vert"), + Gdx.files.internal("resources/shaders/defaultGeometryPassShader.frag")); + + int handle = 0; + // TODO use program.getHandle(); + try { + Field field = ShaderProgram.class.getDeclaredField("program"); + field.setAccessible(true); + handle = (int) field.getInt(program); + } catch (NoSuchFieldException | SecurityException + | IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + System.out.println(handle); + GL32C.glBindFragDataLocation(handle, 0, "albedoOut"); + GL32C.glBindFragDataLocation(handle, 1, "normalOut"); + GL32C.glBindFragDataLocation(handle, 2, "materialOut"); + Gdx.gl20.glLinkProgram(handle); + } + + @Override + public int compareTo(Shader other) { + // TODO: find a suitable comparison + return 0; + } + + @Override + public boolean canRender(Renderable instance) { + if (instance.meshPart.mesh.getVertexAttribute( + VertexAttributes.Usage.TextureCoordinates) == null) { + return false; + } + if (instance.meshPart.mesh + .getVertexAttribute(VertexAttributes.Usage.Normal) == null) { + return false; + } + return instance.material.has(Mask); + } + + @Override + public void begin(Camera camera, RenderContext context) { + this.context = context; + this.program.bind(); + this.program.setUniformMatrix("u_projViewTrans", camera.combined); + context.setDepthTest(GL20.GL_LEQUAL); + context.setCullFace(GL20.GL_BACK); + } + + @Override + public void render(Renderable renderable) { + Material material = renderable.material; + + TextureAttribute albedoTexture = (TextureAttribute) material + .get(PBRTextureAttribute.Albedo); + TextureAttribute normalTexture = (TextureAttribute) material + .get(PBRTextureAttribute.Normal); + TextureAttribute metallicTexture = (TextureAttribute) material + .get(PBRTextureAttribute.Metallic); + TextureAttribute ambientTexture = (TextureAttribute) material + .get(PBRTextureAttribute.Ambient); + TextureAttribute roughnessTexture = (TextureAttribute) material + .get(PBRTextureAttribute.Roughness); + + if (albedoTexture != null) { + this.program.setUniformi("u_albedoTexture", context.textureBinder + .bind(albedoTexture.textureDescription.texture)); + } + if (normalTexture != null) { + this.program.setUniformi("u_normalTexture", context.textureBinder + .bind(normalTexture.textureDescription.texture)); + } + if (metallicTexture != null) { + this.program.setUniformi("u_metallicTexture", context.textureBinder + .bind(metallicTexture.textureDescription.texture)); + } + if (roughnessTexture != null) { + this.program.setUniformi("u_roughnessTexture", context.textureBinder + .bind(roughnessTexture.textureDescription.texture)); + } + if (ambientTexture != null) { + this.program.setUniformi("u_ambientTexture", context.textureBinder + .bind(ambientTexture.textureDescription.texture)); + } + + this.program.setUniformMatrix("u_worldTrans", + renderable.worldTransform); + + // TODO: keep an eye on this thing here, so no transformations are wrong + // in the end + this.program.setUniformMatrix("u_normalWorldTrans", + matrix3.set(renderable.worldTransform).inv().transpose()); + + renderable.meshPart.render(this.program); + } + + @Override + public void end() { + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DeferredRenderer.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DeferredRenderer.java new file mode 100644 index 0000000..0a530db --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/DeferredRenderer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Camera; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.graphics.g3d.utils.DefaultTextureBinder; +import com.badlogic.gdx.graphics.g3d.utils.RenderContext; +import com.badlogic.gdx.graphics.glutils.FrameBuffer; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; + +import de.damios.guacamole.gdx.graphics.NestableFrameBuffer.NestableFrameBufferBuilder; +import de.eskalon.commons.core.EskalonApplication; +import de.eskalon.commons.graphics.IRenderer; + +/** + * @author Sarroxxie + */ +public class DeferredRenderer implements IRenderer, Disposable { + + protected EskalonApplication game; + private GeometryPass geometryPass; + private LightPass lightPass; + + /** + * Stores the base color in 24 bit. + */ + public static final int ALBEDO_ATTACHMENT_INDEX = 0; + + /** + * Stores the world normal in 24 bit. + */ + public static final int NORMAL_ATTACHMENT_INDEX = 1; + + /** + * Stores the metalness, roughness and ambient occlusion in 8bit each. + */ + public static final int MATERIAL_ATTACHMENT_INDEX = 2; + + /** + * Stores the depth in 32 bit. + */ + // TODO: depth as 24 bit? + public static final int DEPTH_ATTACHMENT_INDEX = 3; + + protected RenderContext context; + protected FrameBuffer gBuffer; + protected Camera camera; + + public DeferredRenderer(EskalonApplication game) { + this.game = game; + this.context = new RenderContext( + new DefaultTextureBinder(DefaultTextureBinder.ROUNDROBIN)); + + NestableFrameBufferBuilder builder = new NestableFrameBufferBuilder( + Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + builder.addColorTextureAttachment(GL30.GL_RGB8, GL30.GL_RGB, + GL30.GL_UNSIGNED_BYTE); + builder.addColorTextureAttachment(GL30.GL_RGB8, GL30.GL_RGB, + GL30.GL_UNSIGNED_BYTE); + builder.addColorTextureAttachment(GL30.GL_RGB8, GL30.GL_RGB, + GL30.GL_UNSIGNED_BYTE); + builder.addDepthTextureAttachment(GL30.GL_DEPTH_COMPONENT, + GL30.GL_UNSIGNED_SHORT); + this.gBuffer = builder.build(); + + // TODO: this bliting will be required inside the light (shading) pass + // Gdx.gl30.glReadBuffer(gBuffer.getFramebufferHandle()); + // Gdx.gl30.glBlitFramebuffer(0, 0, game.getWidth(), game.getHeight(), + // 0, + // 0, game.getWidth(), game.getHeight(), GL30.GL_DEPTH_BUFFER_BIT, + // GL30.GL_NEAREST); + + this.geometryPass = new GeometryPass(this); + this.lightPass = new DebugLightPass(this); + } + + @Override + public void render(Array<ModelInstance> objects) { + this.geometryPass.render(objects); // add camera and context and gBuffer + this.lightPass.render(); + } + + @Override + public void setCamera(Camera camera) { + this.camera = camera; + } + + @Override + public void dispose() { + this.gBuffer.dispose(); + this.geometryPass.dispose(); + this.lightPass.dispose(); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/GeometryPass.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/GeometryPass.java new file mode 100644 index 0000000..6c8c584 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/GeometryPass.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g3d.ModelCache; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.graphics.g3d.Renderable; +import com.badlogic.gdx.graphics.g3d.Shader; +import com.badlogic.gdx.graphics.g3d.utils.BaseShaderProvider; +import com.badlogic.gdx.graphics.g3d.utils.DefaultRenderableSorter; +import com.badlogic.gdx.graphics.g3d.utils.RenderableSorter; +import com.badlogic.gdx.graphics.g3d.utils.ShaderProvider; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; + +/** + * @author Sarroxxie + */ +public class GeometryPass implements Disposable { + + private DeferredRenderer renderer; + + private ModelCache modelCache; + private Array<Renderable> renderables = new Array<>(); + + private ShaderProvider shaderProvider; + private RenderableSorter renderableSorter; + + public GeometryPass(DeferredRenderer renderer) { + this.renderer = renderer; + this.modelCache = new ModelCache(); + + this.shaderProvider = new BaseShaderProvider() { + @Override + protected Shader createShader(Renderable renderable) { + DefaultGeometryPassShader shader = new DefaultGeometryPassShader(); + shader.init(); + return shader; + } + }; + this.renderableSorter = new DefaultRenderableSorter() { + @Override + public int compare(Renderable o1, Renderable o2) { + return o1.material.compareTo(o2.material); + } + }; + } + + public void render(Array<ModelInstance> objects) { + /* + * Combine models; set shaders; sort renderables. + */ + this.renderables.clear(); + + this.modelCache.begin(this.renderer.camera); + this.modelCache.add(objects); + this.modelCache.end(); + this.modelCache.getRenderables(this.renderables, null); + + for (Renderable renderable : this.renderables) { + renderable.shader = shaderProvider.getShader(renderable); + } + + this.renderableSorter.sort(this.renderer.camera, this.renderables); + + /* + * Fills all the specified buffers for the #gBuffer. + */ + this.renderer.context.begin(); + this.renderer.gBuffer.begin(); + + Gdx.gl.glClearColor(0f, 0f, 0f, 0f); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + + Shader currentShader = null; + for (int i = 0; i < this.renderables.size; i++) { + final Renderable renderable = this.renderables.get(i); + if (currentShader != renderable.shader) { + if (currentShader != null) + currentShader.end(); + currentShader = renderable.shader; + currentShader.begin(this.renderer.camera, + this.renderer.context); + } + currentShader.render(renderable); + } + if (currentShader != null) + currentShader.end(); + + this.renderer.gBuffer.end(); + this.renderer.context.end(); + } + + @Override + public void dispose() { + modelCache.dispose(); + shaderProvider.dispose(); + } + +} diff --git a/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/LightPass.java b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/LightPass.java new file mode 100644 index 0000000..e654126 --- /dev/null +++ b/g3d/src/main/java/de/eskalon/commons/graphics/deferredrendering/LightPass.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 eskalon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.eskalon.commons.graphics.deferredrendering; + +import com.badlogic.gdx.utils.Disposable; + +/** + * @author Sarroxxie + */ +public abstract class LightPass implements Disposable { + + protected DeferredRenderer renderer; + + public LightPass(DeferredRenderer renderer) { + this.renderer = renderer; + } + + public void render() { + }; + +}