Shader Showcase Game Jam

Commenting on the initShaderEffects method, the problem persists.
By also commenting on the following methods the problem disappears.

// setCorrectShader(yBotSpatial);
// setCorrectShader(erikaSpatial);
    private void initScene() {

        addQuad(new Vector3f(0, 0, -2), Quaternion.IDENTITY, rootNode);
        addQuad(new Vector3f(0, 0, 0), new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), rootNode);

        yBotSpatial = assetManager.loadModel("Models/YBot/YBot.j3o");
        erikaSpatial = assetManager.loadModel("Models/Erika/Erika.j3o");

        rootNode.attachChild(erikaSpatial);
        rootNode.attachChild(yBotSpatial);

        yBotSpatial.move(1f, 0, 0);
        erikaSpatial.move(-1f, 0, 0);

//        setCorrectShader(yBotSpatial);
//        setCorrectShader(erikaSpatial);
    }

There is probably something wrong with the PBRCharacters.j3md file. Could you please look into it? :slight_smile:

1 Like

I still left a few things slightly disorganized in the .frag shader after refactoring the lighting from the param reads, so I suspect once I fix that it will solve the issue.

On the bright side, that is much easier to fix than if it were related to the tri-planar tangent calculation, so I’m glad that was not the cause of this issue

Shaders can be weird with how they handle some things differently on different GPUs, so there’s likely a small mistake I made that my own GPU is “fixing” for me and causing me to overlook the mistake, but on your GPU the problem is persisting.

What are your GPU specs on the device you’re using?

I’ll also go through and do the final cleanup on my refactoring work with the paramReads and lightingCalculation glsllibs, and hopefully that will eliminate the issue, otherwise if it still persists we can try to troubleshoot it some more until I find the mistake.

Edit: I made one quick change and submitted it as a PR to your repo. I’m not sure if that fixes it, but I also mentioned some other things in my PR description for you to check for that might help point me towards the source of the issue.

1 Like

Hi @yaRnMcDonuts , I took your changes and optimized the general classes of the project a bit. I added the animations to the test case. The problem still persists.

Source code here

Example 1:

    private void initScene() {

        addQuad(new Vector3f(0, 0, -2), Quaternion.IDENTITY, rootNode);
        addQuad(new Vector3f(0, 0, 0), new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), rootNode);

        yBotSpatial = loadModel("Models/YBot/YBot.j3o", new Vector3f(-1f, 0, 0), rootNode);
        erikaSpatial = loadModel("Models/Erika/Erika.j3o", new Vector3f(1f, 0, 0), rootNode);

        //setCharacterShader(yBotSpatial); // FIXME: ???
        //setCharacterShader(erikaSpatial); // FIXME: ???
        
        initMaterialEditor(erikaSpatial);
        initShaderEffects();
    }

Example 2:

    private void initScene() {

        addQuad(new Vector3f(0, 0, -2), Quaternion.IDENTITY, rootNode);
        addQuad(new Vector3f(0, 0, 0), new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), rootNode);

        yBotSpatial = loadModel("Models/YBot/YBot.j3o", new Vector3f(-1f, 0, 0), rootNode);
        erikaSpatial = loadModel("Models/Erika/Erika.j3o", new Vector3f(1f, 0, 0), rootNode);

        setCharacterShader(yBotSpatial); // FIXME: ???
        setCharacterShader(erikaSpatial); // FIXME: ???
        
        initMaterialEditor(erikaSpatial);
        initShaderEffects();
    }

At the moment I do not have my own pc. My current graphics card is this:

INFO: Running on jMonkeyEngine 3.6.1-stable
 * Branch: HEAD
 * Git Hash: 4de10c3
 * Build Date: 2023-06-23
feb 20, 2024 12:41:17 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: LWJGL 3.3.2+13 context running on thread jME3 Main
 * Graphics Adapter: GLFW 3.4.0 Win32 WGL Null EGL OSMesa VisualC DLL
feb 20, 2024 12:41:17 PM com.jme3.renderer.opengl.GLRenderer loadCapabilitiesCommon
INFO: OpenGL Renderer Information
 * Vendor: Intel
 * Renderer: Intel(R) Iris(R) Xe Graphics
 * OpenGL Version: 3.2.0 - Build 31.0.101.4032
 * GLSL Version: 1.50 - Build 31.0.101.4032
 * Profile: Core
2 Likes

Debugging shaders in cases where there’s no errors like this is always tricky, especially when I’m not able to reproduce the issue on my device.

Usually I’d place a bunch of discard() or gl.fragColor assignments to determine where the shader may be crashing or to see if it is returning an incorrect or empty value for a variable that goes into the lighting equation… but unlike debugging java code, the debug output from a shader is not in text/numbers , and the best you can do is output a variable to each pixel and see what it looks like when it gets rendered onto the model. So its hard to describe to someone else what code to change and what visual indicators to look for to determine if something is working corectly or not.

But aside from that there is only one more thing I can think of that you could easily check for me.

I code all my shaders to have an Int param called DebugValuesMode, and that renders the model with a a single un-lit color (either the final albedo, normal, roughness, metallic, ao, or emissive color) and that can let me know if it is an issue with one of those channels, or if the shader possibly is bugging out and silently crashing between the calculation of 2 layers.

Here’s an updated test case where you can press the O key to toggle which value gets output for debug purposes:

package com.github.shader.pack;

import BlendLayerEffect.BlendLayerEffect;
import java.util.LinkedList;
import java.util.Queue;

import com.github.mat.editor.MatPropertyPanelBuilder;
import com.github.tools.GameObject;
import com.github.tools.editor.AbstractEditor;
import com.github.tools.editor.ReflectedEditorBuilder;
import com.github.tools.editor.ReflectedEditorBuilder.StringFilterMode;
import com.jme3.anim.AnimComposer;
import com.jme3.anim.SkinningControl;
import com.jme3.app.Application;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.LightProbe;
import com.jme3.material.MatParam;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitorAdapter;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.shader.VarType;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.system.AppSettings;
import com.jme3.util.SkyFactory;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
import com.simsilica.lemur.Axis;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.FillMode;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.RollupPanel;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.component.SpringGridLayout;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.props.PropertyPanel;
import com.simsilica.lemur.style.BaseStyles;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.beans.IntrospectionException;
import java.util.ArrayList;

/**
 * 
 * @author capdevon
 */
public class Test_CharacterEffects1 extends SimpleApplication {
    
    private static int width, height;
    
    public BlendLayerEffect iceEffect, stoneEffect, shieldEffect;
    
    private ArrayList<BlendLayerEffect> activeEffectsInOrder = new ArrayList<>();
    private ArrayList<Slider> blendValSliders = new ArrayList<>();
    private ArrayList<VersionedReference> sliderVersionedReferences = new ArrayList<>();
    
    
    private int shaderDebugMode = -1;

    /**
    *
    * @param args
    */
    
   public static void main(String[] args) {
       Test_CharacterEffects1 app = new Test_CharacterEffects1();
       AppSettings settings = new AppSettings(true);
       
       
               Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        width = (int) (screenSize.getWidth() * .99f);        
        height = (int) (screenSize.getHeight()  * .93f);
        
        settings.put("Width", width);
        settings.put("Height", height);


       app.setSettings(settings);
       app.setShowSettings(false);
       app.setPauseOnLostFocus(false);
       app.start();
   }
   
   private static final String MODEL_ASSET_PATH = "Models/Erika/Erika.j3o"; //"Models/YBot/YBot.j3o";
   
   private BitmapText hud;
   private BitmapText shaderDebugModeText;
   private Spatial model;
   private AnimComposer animComposer;
   private SkinningControl skinningControl;
   private ArmatureDebugAppState armatureDebugState;
   private final Queue<String> animsQueue = new LinkedList<>();

   @Override
   public void simpleInitApp() {
       armatureDebugState = new ArmatureDebugAppState();
       stateManager.attach(armatureDebugState);
       stateManager.attach(new DebugGridState());
       
       hud = makeLabelUI("", ColorRGBA.Blue, guiNode);
       hud.setLocalTranslation(10f, settings.getHeight() - 10f, 0);
       
       shaderDebugModeText = makeLabelUI("Debug Mode: -1", ColorRGBA.White, guiNode);
       shaderDebugModeText.setLocalTranslation(10f, settings.getHeight() - 30f, 0);
       
       
       rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
       
       initLemur(this);
       configureCamera();
       setupCamera();
       setupSky();
       initFilters();
       loadModel();
       initEffects();
       initMaterialEditor();
       initKeys();
       
       
   }
   
   private void initEffects(){
       
        iceEffect = new BlendLayerEffect("Freeze", 0, model);
   //     iceEffect.addMaterialsFromSpatial(erikaSpatial);
        iceEffect.setBaseColorMap(assetManager.loadTexture("Models/Cracked_Ice/DefaultMaterial_baseColor.png"));
        iceEffect.setNormalMap(assetManager.loadTexture("Models/Cracked_Ice/DefaultMaterial_normal.png"));
        iceEffect.setMetallicRoughnessAoMap(assetManager.loadTexture("Models/Cracked_Ice/DefaultMaterial_occlusionRoughnessMetallic.png"));

        stoneEffect = new BlendLayerEffect("Petrify", 1, model);
    //    stoneEffect.addMaterialsFromSpatial(yBotSpatial);
        stoneEffect.setBaseColorMap(assetManager.loadTexture("Models/Cracked_Stone/DefaultMaterial_baseColor.png"));
        stoneEffect.setNormalMap(assetManager.loadTexture("Models/Cracked_Stone/DefaultMaterial_normal.png"));
        stoneEffect.setMetallicRoughnessAoMap(assetManager.loadTexture("Models/Cracked_Stone/DefaultMaterial_occlusionRoughnessMetallic.png"));
        
        shieldEffect = new BlendLayerEffect("Shield", 2, model);
    //    shieldEffect.addMaterialsFromSpatial(yBotSpatial);
        shieldEffect.setBaseColorMap(assetManager.loadTexture("Models/Shield_Armor/DefaultMaterial_baseColor.png"));
        shieldEffect.setNormalMap(assetManager.loadTexture("Models/Shield_Armor/DefaultMaterial_normal.png"));
        shieldEffect.setMetallicRoughnessAoMap(assetManager.loadTexture("Models/Shield_Armor/DefaultMaterial_occlusionRoughnessMetallic.png"));
        shieldEffect.setEmissiveMap(assetManager.loadTexture("Models/Shield_Armor/DefaultMaterial_emissive.png"));
        shieldEffect.setBlendAlpha(true);
        
        registerBlendEffectToSlider(iceEffect);
        registerBlendEffectToSlider(stoneEffect);
        registerBlendEffectToSlider(shieldEffect);
                
   }
   

    @Override
    public void simpleUpdate(float tpf) {
        super.simpleUpdate(tpf); 
        
     //   float frameScaleAmt = tpf * 0.05f;
      //  model.setLocalScale(model.getLocalScale().add(frameScaleAmt, frameScaleAmt, frameScaleAmt));

        for(int i = 0; i < sliderVersionedReferences.size(); i++){
            VersionedReference sliderVersionedReference = sliderVersionedReferences.get(i);
            Slider slider = blendValSliders.get(i);
            if(sliderVersionedReference.update()){
                float val = (float) slider.getModel().getValue();
                BlendLayerEffect blendEffect = activeEffectsInOrder.get(i);
                blendEffect.setBlendValue(val);
            }    
        }
        
    }
   
      private void registerBlendEffectToSlider(BlendLayerEffect blendLayerEffect){
        activeEffectsInOrder.add(blendLayerEffect);
        
        
        //make container for tweaking this blend effect (currently only has a slider for blendVal)
        Container blendLayerContainer = new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.Even));
        
        Label label = new Label(blendLayerEffect.getName());
        Slider slider = new Slider();
        DefaultRangedValueModel sizeSliderModel = new DefaultRangedValueModel(0, 1.0, 0);
        slider.setModel(sizeSliderModel);
        
        blendValSliders.add(slider);
        sliderVersionedReferences.add(sizeSliderModel.createReference());
        
        
        float boxWidth = width * 0.15f;
        float xOffset = (boxWidth * 0.1f) + (blendValSliders.size() * boxWidth);
        blendLayerContainer.setLocalTranslation(xOffset,height * 0.94f, 1);
        
  //      blendLayerContainer.setPreferredSize(new Vector3f(boxWidth,50,1));
        slider.setPreferredSize(new Vector3f(boxWidth,25,1));
        
        blendLayerContainer.addChild(label);
        blendLayerContainer.addChild(slider);
        
        MatPropertyPanelBuilder matPropertyPanelForLayer = new MatPropertyPanelBuilder(StringFilterMode.IncludeStartingWith, blendLayerEffect.getParamStringPrefix());
        
        Container matPropertyContainer = matPropertyPanelForLayer.buildPanel(model);
        
        ReflectedEditorBuilder builder = new ReflectedEditorBuilder();
        Container layerParamsContainer = builder.buildPanel(blendLayerEffect);
   
        blendLayerContainer.addChild(layerParamsContainer);
        
   //     layerParamsContainer.setLocalTranslation(xOffset,height * 0.94f, 1);
        
        guiNode.attachChild(blendLayerContainer);
    }
     
       
    private void setCorrectShader(Spatial spatial){
        spatial.breadthFirstTraversal(new SceneGraphVisitorAdapter() {
            @Override
            public void visit(Geometry geom) {
                Material oldMat = geom.getMaterial();
                
                Material characterMat = new Material(assetManager, "MatDefs/PBRCharacters.j3md");
                
                ArrayList<MatParam> matParams = new ArrayList<>(oldMat.getParams());
                for(int p = 0; p < matParams.size(); p++){
                    MatParam matParam = matParams.get(p);
                    if(matParam.getValue() != null){
                        Object val = matParam.getValue();
                        characterMat.setParam(matParam.getName(), matParam.getVarType(), val);     
                    }

                }
                geom.setMaterial(characterMat);
                
                
            }
        });
    }
   
   private void configureCamera() {
       float aspect = (float) cam.getWidth() / cam.getHeight();
       cam.setFrustumPerspective(45, aspect, 0.01f, 1000f);
       
//       flyCam.setDragToRotate(true);
//       flyCam.setMoveSpeed(25);
   }
   
   private void setupCamera() {
       // disable the default 1st-person flyCam!
       flyCam.setEnabled(false);

       Node target = new Node("CamTarget");
       target.move(0, 1, 0);

       ChaseCameraAppState chaseCam = new ChaseCameraAppState();
       chaseCam.setTarget(target);
       stateManager.attach(chaseCam);
       chaseCam.setInvertHorizontalAxis(true);
       chaseCam.setInvertVerticalAxis(true);
       chaseCam.setZoomSpeed(0.5f);
       chaseCam.setMinDistance(1);
       chaseCam.setMaxDistance(10);
       chaseCam.setDefaultDistance(3);
       chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
       chaseCam.setRotationSpeed(3);
       chaseCam.setDefaultVerticalRotation(0.3f);
   }
   
   /**
    * a sky as background
    */
   private void setupSky() {
       Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
       sky.setShadowMode(RenderQueue.ShadowMode.Off);
       rootNode.attachChild(sky);
   }

   private void initFilters() {
       DirectionalLight sun = new DirectionalLight();
       sun.setDirection(new Vector3f(-0.2f, -1, -0.3f).normalizeLocal());
       sun.setDirection(new Vector3f(0.0f, -1.0f, 0.0f).normalizeLocal());
       rootNode.addLight(sun);

       AmbientLight ambient = new AmbientLight();
       ambient.setColor(new ColorRGBA(0.25f, 0.25f, 0.25f, 1));
//       rootNode.addLight(ambient);

       // add a PBR probe.
       Spatial probeModel = assetManager.loadModel("Scenes/defaultProbe.j3o");
       LightProbe lightProbe = (LightProbe) probeModel.getLocalLightList().get(0);
       lightProbe.getArea().setRadius(100);
       rootNode.addLight(lightProbe);

       FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
       viewPort.addProcessor(fpp);

       DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 4096, 3);
       dlsf.setLight(sun);
       dlsf.setShadowIntensity(0.4f);
       dlsf.setShadowZExtend(256);
       dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
       fpp.addFilter(dlsf);

       FXAAFilter fxaa = new FXAAFilter();
       fpp.addFilter(fxaa);
   }
   
   private void loadModel() {
       model = assetManager.loadModel(MODEL_ASSET_PATH);
       setCorrectShader(model);
       
       rootNode.attachChild(model);

       animComposer = GameObject.getComponentInChildren(model, AnimComposer.class);
       animsQueue.addAll(animComposer.getAnimClipsNames());
       animsQueue.forEach(System.out::println);

       skinningControl = GameObject.getComponentInChildren(model, SkinningControl.class);
       armatureDebugState.addArmatureFrom(skinningControl);
       
    //   skinningControl.setHardwareSkinningPreferred(false);
   }
   
   private BitmapText makeLabelUI(String text, ColorRGBA color, Node parent) {
       BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
       BitmapText bmp = new BitmapText(font);
       bmp.setName("MyLabel");
       bmp.setText(text);
       bmp.setColor(color);
       bmp.setSize(font.getCharSet().getRenderedSize());
       parent.attachChild(bmp);
       return bmp;
   }
   
   private void initLemur(Application app) {
       // initialize lemur
       GuiGlobals.initialize(app);
       BaseStyles.loadGlassStyle();
       GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
       
       
   }

   private void initMaterialEditor() {
       MatPropertyPanelBuilder builder = new MatPropertyPanelBuilder(StringFilterMode.IgnoreStartingWith, "BlendLayer");
       Container container = builder.buildPanel(model);
       container.setLocalTranslation(settings.getWidth() - settings.getWidth() / 4f, settings.getHeight() - 10f, 1);
       guiNode.attachChild(container);
   }
   
   /**
    * Mapping a named action to a key input.
    */
   private void initKeys() {
       addMapping("Speed+", new KeyTrigger(KeyInput.KEY_U));
       addMapping("Speed-", new KeyTrigger(KeyInput.KEY_I));
       addMapping("Next", new KeyTrigger(KeyInput.KEY_N));
       addMapping("ToggleArmature", new KeyTrigger(KeyInput.KEY_M));
       
       addMapping("ToggleShaderDebugMode", new KeyTrigger(KeyInput.KEY_O));
       addMapping("ToggleShaderDebugMode_Normals", new KeyTrigger(KeyInput.KEY_L));
   }

   private void addMapping(String mappingName, Trigger... triggers) {
       inputManager.addMapping(mappingName, triggers);
       inputManager.addListener(actionListener, mappingName);
   }

   /**
    * Defining the named action that can be triggered by key inputs.
    */
   private final ActionListener actionListener = new ActionListener() {
       @Override
       public void onAction(String name, boolean keyPressed, float tpf) {
           if (!keyPressed) {
               return;
           }
           
           if (name.equals("Next")) {
               String anim = animsQueue.poll();
               animsQueue.add(anim);
               animComposer.setCurrentAction(anim);
               hud.setText(anim);
               
           } else if (name.equals("Speed+")) {
               float glSpeed = animComposer.getGlobalSpeed();
               animComposer.setGlobalSpeed(glSpeed + 0.1f);
               
           } else if (name.equals("Speed-")) {
               float glSpeed = animComposer.getGlobalSpeed();
               animComposer.setGlobalSpeed(glSpeed - 0.1f);
               
           } else if (name.equals("ToggleArmature")) {
               armatureDebugState.setEnabled(!armatureDebugState.isEnabled());
           } else if (name.equals("ToggleShaderDebugMode")) {
               shaderDebugMode++;
               if(shaderDebugMode > 5){
                   shaderDebugMode = -1;
               }
               iceEffect.setParam("DebugValuesMode", VarType.Int, shaderDebugMode);
               
               shaderDebugModeText.setText("Shader Debug Mode: " + shaderDebugMode);
           }else if (name.equals("ToggleShaderDebugMode_Normals")) {
               if(shaderDebugMode == 1){
                   shaderDebugMode = -1;
               }else{
                   shaderDebugMode = 1;
               }
               iceEffect.setParam("DebugValuesMode", VarType.Int, shaderDebugMode);
               
               shaderDebugModeText.setText("Shader Debug Mode: " + shaderDebugMode);
           }
       }
   };

}

Presing the O key will cycle through each debug mode like in this example video (I also did a quick preview of the height scan effect so far since thats the last part I was working on):

Can you run that test case and take a video (or a series of screenshots) showing what each debug output looks like with all 6 different modes?

If just one of those debug layers is broken, then that would be a good indicator of where things are going wrong. And if they all work, then that is an indicator that the issue is in the final lighting equation. And if none work, then something is going wrong very early on in the shader code.


I also should’ve mentioned to check that your GPU drivers are up to date, although I highly doubt that’s the issue otherwise you probably would’ve had a ton of other issues with jme before this point. But that is usually the first important thing I would tell a non jme-dev to check if they were testing my app and had a similar issue where a shader is breaking without crashing or giving a catchable error.

3 Likes

I just remembered one other thing that is likely the source of the issue.

I forgot that shaders do not like having values like 1 assigned to a float, and instead it needs to be 1.0 in decimal form. Its been a long time since I did this much shader dev, but IIRC that mistake has caused some of my shaders to not work on certain GPUs that are more picky. I went through and replaced my 0 and 1 floats with 0.0 and 1.0 so I’ll upload a fixed version and I’m hoping that will solve the issue.


I have however unfortunately encountered a handful of other issues trying to get triplanar mapping working with animated models… maybe someone who’s knowledgable about this can chime and give me some advice:

Using a model’s pre-baked tangents does not appear to work for triplanar mapping, and causes very visibly inaccurate lighting.

So I had to calculate tangents in-shader using the worldNormal and worldPosition values. But now when I have an animated model, the tangents break, unless I turn hardware skinning off so that the shader knows the true location of worldPosition during animations.

But when hardware skinning is disabled, this causes the triplanar mappin to break and appear as though the texture is moving because the animation is constantly changing the value of modelPos and worldPos in the shader (and those values are what are used for triplanar mapping…)

So on the bright side none of this matters if you just use a pre-made texture atlas and turn triplanar off. But that takes a lot more work and ideally I would like to get tri-planar working so pre-made texture atlases aren’t necissary.

But it seems like no matter what I do, there will be some type of issue…

Even if I turn hardware skinning off so that the shader knows about vertex positions post-animation to calculate the tangents properly, then the triplanar mapping is incorrect because the coordinates being used are moving with the animation (and I don’t want to rely on hardware skinning being off anyways because that causes the framerate to drop substantially in my game with more than a few NPCs loaded, so I consider the hardwareSkinning setting to be one that should always be left true)

And if I try using the local normal and local vertexPosition values (inNormal and inPosition in their original local space) then the triplanar works and the tangents calculate correctly, but as soon as the animations start the lighting does not change and becomes inaccurate because it is always treating the model as if it is in the bind-pose even when animations are playing.

So I feel like the shader is stuck in a position where something is going to be causing inaccurate lighting one way or another. Right now I guess I’ll just pick the route that renders the least incorrect, and hopefully a proper solution can be found eventually…

But right now I’m just confused how the lighting on animated models is ever correct with hardwarSkinning true… it seems like the shader knows about the true-value of inNormal accounting for animation, but does not account for it in regards to inPosition… and since the tangent equation im using relies on both of these values, it is using a worldPos that is not modified by the animation frame in conjuction with a worldNormal that IS somehow modified by the animation frame… and that breaks the equation completely.

I see in the .vert shader these values are put from local > world space with this code:

    wPosition = (g_WorldMatrix * vec4(inPosition, 1.0)).xyz;
    wNormal  = TransformWorldNormal(modelSpaceNorm);

And it seems like (regardless of hardware skinning) the wNormal value always accoutns for animation (otherwise lighting would not be correct), while wPosition will always treat the model like its in its bind pose.

Then something is wrong right here. If the tangents are accurate (worth checking) then the calculations should work… if they are correct.

Everything else is just introducing new sources of error.

Calculating tangents on the fly for world-orientation shapes like terrain is really trivial.

Calculating tangents on the fly for an animated model is essentially impossible. The shader does not have enough information. It would need to know the actual texture base vectors to even begin to know what “X vector” means with respect to the normal. Non-trivial in the best case, truly impossible in reality.

Precalculated tangents (either loaded or with the tangent generator) is the only way to go. Then the tangents have to be skin-transformed like the normals, etc…

And then you need to keep close track of whether you are in ‘tangent space’ or ‘world space’ or ‘model space’… whatever ‘space’ you want triplanar to be calculated in, it’s certainly not going to be ‘tangent space’. Proabably, normal, tangent, and bi-tangent need to be transformed into model space (based on skinning parameters).

…lots of clever color-based debugging to make sure that is all correct before trying to do triplanar.

1 Like

It sounds like I should go back to trying to get this to work then.

I am using 2 models in this test-case and used the MikktSpace tangent generator and still got the same issue on both models, so I was just assuming pre-generated tangents weren’t able to work with tri-planar mapping even though it seems like it should.

I don’t think that it would be likely that both models have broken tangents, but its probably still worthwhile testing it out with a 3rd character model for my game to make sure.

I did have issues using the pre-generated tangents for terrains in the past (which is why I did the tangent generation in shader for that). But that was a different issue and didn’t look anything like this issue (I think terrains just clear the tangent buffer on-save to reduce the file size, but I never foudn any code to determine this for sure…)

So I guess I’ll have to do some more testing to try to get the pre-calculated tangents to work properly.

Here’s the code I’m using to get the triplanar normal value and then multiply by tbnMat (the same way its done in PBRLighting.frag for the base m_NormalMap)

               #ifdef BLENDLAYER_$i_TRIPLANAR                    
                    newNormal = getTriPlanarBlend(wPosition, blend, m_BlendLayer_$i_NormalMap, triScale ).xyz;
                  //  newNormal = normalize((newNormal.xyz * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0))) * vec3(1.0);
                    newNormal = normalize(tbnMat * newNormal);
                    
                #else
                    newNormal = texture2D(m_BlendLayer_$i_NormalMap, texCoord).xyz; //non triplanar use for pre-made texture atlases
                    newNormal = normalize((newNormal.xyz * vec3(2.0,  2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)));
                    newNormal = normalize(tbnMat * newNormal);
                    
                #endif
                
                normal = mix(normal, newNormal, blendPercent);

And here is the buggy output that made me think using the tangentBuffer like this would not work:

I’m also using the same instance of tbnMat that is calculated and multiplied by the base m_NormalMap in the stock PBRLighting.frag shader like this:

//putting inTangent in world space in PBRLighting.vert:

wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w);

..
..

//normal mapping code from PBRLighting.frag:
    vec3 tan = normalize(wTangent.xyz);
    tbnMat = mat3(tan, wTangent.w * cross( (norm), (tan)), norm); 


    #if defined(NORMALMAP)
        vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
        // Note we invert directx style normal maps to opengl style
        #ifdef NORMALSCALE
            normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)) * vec3(m_NormalScale, m_NormalScale, 1.0));
        #else
            normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)));
        #endif
        normal = normalize(tbnMat * normal);
        //normal = normalize(normal * inverse(tbnMat));
    #endif


So I’ve been assuming that tbnMat has been correctly calculated (but maybe there’s something wrong there with how tbnMat is calculated?)

Hi @yaRnMcDonuts,
I think you are close to the solution! I made a video with all the required information.
I hope that it will be helpful to you in understanding where the problem lies.

Yes I know that testing shaders is not easy, thanks for your patience.

Yes I learned this detail in a review by @RiccardoBlb . It is worth correcting the float variables. Let me know when the PR is ready or if you need me to do more testing. Thanks.

As always, the source code is available here if anyone wants to help.

1 Like

I’m not sure about PBR… but I think in Lighting that the TBN is in “view space” and not model or world space. I think maybe PBR does it in world space but it’s worth confirming with some color debugging.

I’m not sure exactly what you are trying to triplanar map so I can’t say which space you are ultimately needing (world or model).

1 Like

I’ve made some changes and added a few more small features, so I’ll upload a new version shortly and hopefully that will work for you this time. I added a basic dissolve effect that uses noise, and can also be used for a rust effect it you blend to a rust texture rather than to a transparent color like you would for dissolve.

I also just realized that I included a new feature from an un-tested PR ( Adding support for Specular-AA by JohnLKkk · Pull Request #2141 · jMonkeyEngine/jmonkeyengine · GitHub) and that feature is not in 3.6 yet, and is still only available on jme-master, so there is a chance that code is what is breaking things, and it likely hasn’t been tested by many other jme users aside from the author of the PR and us now. In hindsight I probably should’ve not included this new feature until it made it to an alpha or beta branch.

It appears that the SpecularAA param for enabling the feature also gets enabled by default in the .j3md file.

So could you try clearing the UseSpecularAA matParam with this code after swapping to PBRCharacters.j3md:

material.clearParam("UseSpecularAA");

and let me know if that solves the issue.

2 Likes

I modified the code of the BlendEffectState class to as you suggested, but the problem still persists.

Code fragment:

    private void setCharacterShader(Spatial spatial) {
        spatial.breadthFirstTraversal(new SceneGraphVisitorAdapter() {
            @Override
            public void visit(Geometry geom) {
                Material oldMat = geom.getMaterial();
                Material newMat = new Material(assetManager, "MatDefs/PBRCharacters.j3md");

                for (MatParam matParam : oldMat.getParams()) {
                    if (matParam.getValue() != null) {
                        newMat.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue());
                    }
                }
                newMat.clearParam("UseSpecularAA");
                geom.setMaterial(newMat);
                MikktspaceTangentGenerator.generate(geom);
            }
        });
    }

All the Materials of Erika’s geometries do not have the UseSpecularAA parameter.

Name: Erika_Archer_Body_Mesh (Geometry)
Triangle: 6328
Vertex: 3909
LOD: 0
MeshMode: Triangles
MatDef: PBRCharacters
[jME3 Main] INFO com.github.tools.editor.MaterialEditorBuilder - 
Material MyMaterial : MatDefs/PBRCharacters.j3md {
    MaterialParameters {
        BlendLayer_3_TriPlanar : true
        BlendLayer_5_Metallic : 1.0
        Roughness : 0.5345225
        BlendLayer_0_BlendVec : 0.0 0.0 0.0 0.0
        BlendLayer_5_Roughness : 1.0
        BlendLayer_0_NormalMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Ice/DefaultMaterial_normal.png
        BlendLayer_3_NormalIntensity : 1.0
        Glossiness : 1.0
        BlendLayer_4_EmissivePower : 0.0
        NormalType : 1.0
        Emissive : 0.0 0.0 0.0 1.0
        BlendLayer_2_Metallic : 1.0
        BlendLayer_4_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_5_EmissiveColor : 1.0 1.0 1.0 1.0
        BlendLayer_2_Roughness : 1.0
        BlendLayer_2_MetallicRoughnessAoMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Shield_Armor/DefaultMaterial_occlusionRoughnessMetallic.png
        EmissivePower : 3.0
        BackfaceShadows : false
        BlendLayer_1_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_4_TriPlanar : true
        Color : 1.0 1.0 1.0 1.0
        EmissiveIntensity : 2.0
        BlendLayer_4_Metallic : 1.0
        BlendLayer_3_Metallic : 1.0
        Metallic : 0.4
        BlendLayer_3_EmissivePower : 0.0
        BlendLayer_4_NormalIntensity : 1.0
        BlendLayer_0_TriPlanar : true
        BlendLayer_5_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_1_NormalIntensity : 1.0
        BlendLayer_1_EmissivePower : 0.0
        BlendLayer_3_Roughness : 1.0
        BlendLayer_2_EmissiveColor : 1.0 1.0 1.0 1.0
        BlendLayer_2_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_2_EmissiveMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Shield_Armor/DefaultMaterial_emissive.png
        BlendLayer_1_BlendVec : 0.0 0.0 0.0 0.0
        BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_1_EmissiveColor : 1.0 1.0 1.0 1.0
        ParallaxHeight : 0.05
        BlendLayer_1_TriPlanar : true
        BlendLayer_0_Roughness : 1.0
        BlendLayer_0_MetallicRoughnessAoMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Ice/DefaultMaterial_occlusionRoughnessMetallic.png
        NormalMap : WrapRepeat_T WrapRepeat_S MagBilinear MinBilinearNoMipMaps Models/Erika/Erika_Archer_Clothes_normal.png
        BaseColorMap : WrapRepeat_T WrapRepeat_S MagBilinear MinBilinearNoMipMaps Models/Erika/Erika_Archer_Clothes_diffuse.png
        BlendLayer_2_NormalMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Shield_Armor/DefaultMaterial_normal.png
        BlendLayer_2_BlendAlpha : true
        BlendLayer_2_EmissivePower : 0.0
        Specular : 1.0 1.0 1.0 1.0
        BlendLayer_3_EmissiveColor : 1.0 1.0 1.0 1.0
        BlendLayer_1_Metallic : 1.0
        BlendLayer_2_BlendVec : 0.0 0.0 0.0 0.0
        BlendLayer_5_NormalIntensity : 1.0
        BlendLayer_0_BaseColorMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Ice/DefaultMaterial_baseColor.png
        BlendLayer_1_NormalMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Stone/DefaultMaterial_normal.png
        BlendLayer_2_NormalIntensity : 1.0
        BlendLayer_0_EmissivePower : 0.0
        BlendLayer_2_TriPlanar : true
        BlendLayer_3_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_1_BaseColorMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Stone/DefaultMaterial_baseColor.png
        BlendLayer_0_NormalIntensity : 1.0
        BlendLayer_4_Roughness : 1.0
        BlendLayer_0_EmissiveColor : 1.0 1.0 1.0 1.0
        BlendLayer_2_BaseColorMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Shield_Armor/DefaultMaterial_baseColor.png
        BlendLayer_0_Metallic : 1.0
        BlendLayer_5_EmissivePower : 0.0
        BlendLayer_1_Roughness : 1.0
        BlendLayer_0_BaseColor : 1.0 1.0 1.0 1.0
        BlendLayer_4_EmissiveColor : 1.0 1.0 1.0 1.0
        BlendLayer_5_TriPlanar : true
        BlendLayer_1_MetallicRoughnessAoMap : Flip WrapRepeat_T WrapRepeat_S MagBilinear MinTrilinear Models/Cracked_Stone/DefaultMaterial_occlusionRoughnessMetallic.png
    }
    AdditionalRenderState {
    }
}
MatName: null
1 Like

Then it looks like SpecularAA is working fine and it must be something else with my code that is causing a weird bug.

I was going to upload a new version before going to bed last night, but figured I should wait til today to ensure I didn’t make any small mistakes that could break things even more. So I’ll check over the code and fix a few last things then upload it later tonight.

I also didn’t get to add quite as many new features as I’d like yet because jme 3.6 is still limited to 64 defines per shader, and any new define for this shader actaully adds an extra define for each of the 6 layers, so its hard to stay within that 64 limit.

But thankfully that’s fixed in a recent pr (Increase MAX_DEFINES in DefineList (re-do) by stephengold · Pull Request #2116 · jMonkeyEngine/jmonkeyengine · GitHub) That PR is still is only available on master, so until the shader test case is using a version of jme with those updates I’ll have to keep things a bit more simple than I’d like. But until then I’ve just been removing other defines in the shader that aren’t as necessary (like the SpecularAA one we jsut set false) and then I’ll go back and re-add them once the 64 limit is no longer there.

3 Likes

Hi @yaRnMcDonuts ,
good news! The effects are now working. Well done!
Unfortunately I can’t merge your latest PR because the java classes differ too much from the current versions. I took the changes in each file and integrated them manually. I have optimized the classes and added new features to the project to ease our development journey.

I recommend that you download this latest version of the project before continuing.

I added the DefaultSceneState class, which contains instructions for building a simple sandbox with a floor, lights, filters and dynamic sky. I think it is useful for testing shaders with various combinations of lights, filters, transparencies etc.

I optimized the BlendLayerEffect class to be able to edit more parameters with the Lemur editor. Now you can edit the debugValuesMode parameter for each effect with its editor.

I created the BlendEffectState class to separate the configuration of the BlendLayerEffects from the code regarding the construction of the scene and animated models.

You can use the ConfigurationBuilder class to configure constraints and SpinnerModels for each editor.

Here are some useful examples:,

        for (BlendLayerEffect effect : effects) {
            ConfigurationBuilder config = new ConfigurationBuilder();
            //config.addConstraint("linearBlendVal", new SpinnerFloatModel(0f, 1f, 0.1f));
            config.addConstraint("debugMode", new SpinnerIntegerModel(-1, 5, 1));

            ReflectedEditorBuilder builder = new ReflectedEditorBuilder(config);
            Panel panel = builder.buildPanel(effect);

            RollupPanel rollup = new RollupPanel(effect.getName(), panel, "glass");
            rollup.setAlpha(0, false);
            rollup.setOpen(false);
            container.addChild(rollup);
        }
    private void initMaterialEditor(Spatial spatial) {
        MatPropertyPanelBuilder builder = new MatPropertyPanelBuilder();
        Predicate<MatParam> ignoreParamFilter = mp -> mp.getName().startsWith("BlendLayer");
        builder.setIgnoreParamFilter(ignoreParamFilter); // <----
        
        Container container = builder.buildPanel(spatial);
        container.setLocalTranslation(settings.getWidth() * 0.75f, settings.getHeight() - 10f, 1);
        guiNode.attachChild(container);
    }

If you think it is better, I can modify the build.gradle file to use the jme-master version with all the latest PR.

Test_CharacterEffects2 - snapshots:

4 Likes

Yeah I had noticed that you changed some of the project structure and reorganized some classes/directories since I had last sycned my fork, and I wasn’t sure if there would be a way for me to easily sync those changes to my fork without overwriting my code and needing to re-add my changes, or without having to go in and manually update things to put them in the right directory with the right package declaration and imports.

If possible it would be best to finalize the project structure as much as you can before I check out the repo again, so that way its easier to keep a checked-out fork in sync with any changes you make in the meantime, and that will reduce the amount of manual updating required to relocate my forked files to new locations if it gets moved in the project. But I also don’t think I’m using github in the most efficient way (as I mentioned in a post earlier in this thread) so I’m also probably still making things harder on my self by using the website to upload things manually as of now.

But I appreciate you manually implementing the code and fixing the things I had left out of sync. I was just trying to rush to get the new changes to the shader uploaded to see if the bug was fixed. I tend to work in a less-organized and rushed manner when working on something that is new and has lots of bugs, I find that gives the most room for focusing on the important things and the functionality. Then once I’m sure everything is working how I expect, then I’ll usually start focusing more on polishing and organizing and refactoring things.

But that’s great to hear the last changes fixed the error! I made 3 (potentially) significant changes that could have fixed it, so I’m still not entirely certain which of those things did it:

  • First I went back and made sure no variables were declared as 0 that should’ve been in 0.0 format. There were less of these than I thought and I think some were within #ifdef blocks that weren’t even running, so I’m not sure if that was the issue or not.

  • Secondly I changed the order of the imports for the glsllibs and the variable declarations. Now those imports are declared after every variable gets declared in the main .frag shader (even variables that I didn’t think were necessary)

  • And third I also changed my import for “PBR.glsllib” to use the version on 3.6 stable (Rather than the version from master that I had previously downloaded and was using in this project). I don’t think this is what solved it, because (I think) the only change to that file was the inclusion of a SpecularAA lighting equation (which we disabled anyways), and I don’t think there were any other significant changes to that file. But if there is an issue in PBR.glsllib on master, then I suspect you (and others) may run into that issue again if you try using PBRLighting.j3md that is currently on master (but I think this is unlikely)

I didn’t want to keep uploading new versions with only 1 change at a time, especially since I still don’t think I’m using github correctly, so I just did all those things at once. And it looks like one of them worked, but I guess for right now there’s no way to know which it was :laughing:

I also left a lot of the interface sloppy and didn’t implement the configurationBuilder yet, since I was mostly focused on fixing the bug and the shader’s base functionality. But now that the bug squashing is done and the foundation for the shader’s functionality is all set I will focus more on keeping things organized and polished with the test cases.

Yeah if you want to do that then I could keep adding more features and minor params for tweaking things in this shader without having to wait for the next jme release for >64 defines. Its possible we might run into some new bugs from other un-tested stuff on master, but in that case we can always just revert the project to 3.6 again.

1 Like

Hi @yaRnMcDonuts ,
I have updated the build.gradle file to use jme-3.7.0-SNAPSHOT. Now you can use all the latest merged features in the master.

ext.jmeVersion = '3.7.0-SNAPSHOT' // from mavenLocal or SonaType

repositories {
    // to find public snapshots of libraries
    maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' }
}

configurations.all {
    // to disable caching of snapshots
    resolutionStrategy.cacheChangingModulesFor 0, 'seconds' 
}
dependencies {
    // jMonkeyEngine
    implementation 'org.jmonkeyengine:jme3-core:' + jmeVersion
    implementation 'org.jmonkeyengine:jme3-desktop:' + jmeVersion
    runtimeOnly 'org.jmonkeyengine:jme3-lwjgl3:' + jmeVersion
}

Sorry if I restructured the project packages often during editor development, but I think it is stable now.
I don’t plan to make any further changes to the project structure, so you can take your time working on shader optimization. I hope I have given you all the tools you need.
I will wait for your next PR before making any major changes. :wink:

4 Likes

I have a few new features/changes that I’ll be uploading in the next few days, but I figured I’d outline my plans to get feedback from you (or anyone else who is interested in this shader) in the meantime.

I’m going to rename BlendLayerEffect.java to ShaderBlendLayer.java, and then I’m going to make a new class called BlendLayerEffect.java that will have an update() loop and that will be where all of the param-tweaking code for making cool effects will be.

ShaderBlendLayer will then be able to have 1 or more BlendLayerEffect registered at a time, and these registered effects will automatically tweak the params of that ShaderBlendLayer for the duration of the effect, and can also be set to remove the blend layer at the end of the effect.

This way it will be easy to reuse a few common effects (like a height-scan or color flash effect) across different layers, and it will also be easy to make a new custom effect for more specific things.

And then I also will be working on some type of Manager class that handles keeping track of which layer goes where. In a game setting like my own, I will likely end up using the first 2-3 blend layers for non-changing things, and the blend layers after those will be for temporary effects and will be ordered based on priority or duration.

So in my game for example, the first blend layer will likely be used for a simple color-blend in HSV space to change clothing and skin color dynamically, and the second blend layer will be for applying a green plagued texture for infected enemies in my game. Then the rest of the layers would be used for temporary status-effects and would have their order shuffled based on a priority system, so that the more important ones or the ones with the longest remaining duration will always render overtop of the others.

This will also be important for optimization, so that layers with a finite duration can automatically be removed at the end of their duration to make room for more effects.

Your PropertyPanel has also been very useful, and it will definitely make it easier to implement all of this more complex stuff into a test-case to demonstrate the features. Usually I feel like I lose focus when I have to hard code an interface for every new variable/feature I want to tweak, especially during the iteration process when I’m still adding/removing new variables. So its nice to have the debug interface setup automatically :slightly_smiling_face:

2 Likes

I am curious to see the new features! Go ahead and send me the PR. I will help you improve the code if needed. I am glad to have given you some useful tools and to collaborate with you. It’s always amazing how many interesting ideas come out of collaboration with other passionate developers. :rocket: :wink:

6 Likes

I submitted a new PR to your repo with some new features and bug fixes for the Blend Shader:

Notable features added:

  • Params for adjusting noise values for dissolve blending
  • Gradient color for fading edges of dissolve-blending or height-blending
  • New BlendLayerEffect class, and 3 basic effects that can be added to a blend layer: TimedBlendEffect, HeightScanEffect, and HsvCycleEffect
  • Added support for scaling a layer’s hue/saturation/brightness in hsv space with a new HsvScalar Vector3f paramater
  • New ShaderBlendLayerManager class that handles dynamically adding and swapping layers, and will eventually handle prioritizing the render order as well as culling covered layers
  • A new test case that lets you dynamically add/remove layers and effects (shown in the attached video). The interface is functional but still slightly sloppy and could still be improved
  • Added a multiplicative blending mode that gives the option to combine the blend layer with the previous layer, rather than replacing it

And I also fixed a bunch of other small issues including the issue with tri-planar mode’s normal maps.

The only unresolved issue is related to using the BloomFilter, since the normal PBRGlow frag shader won’t work in this case. So unfortunately there is no bloom in this video. Otherwise I’m happy to say mostly everything else has ended up working out as planned.

Hopefully you’ll all enjoy this new demo video, and any feedback or testing of the source code is greatly appreciated :slightly_smiling_face:

6 Likes

Here’s an occlusion filter I’ve been working on:

This filter showcases 3 ideas:

  1. Shading where a geometry is occluded from the camera by comparing depth textures. (Thanks @zzuegg :slightly_smiling_face: )

  2. A glsllib that detects the distance to the closest volume. This is producing the slight glow around the edges of the effect.

  3. This filter can be applied to any geometry by simply adding a userdata entry containing a color to the geometry. No special matdef techniques required.

If anyone wants more details on any these, I’d be more than happy to provide them. :wink:

9 Likes

Hi @codex ,
tell us something interesting about how you achieved the effects you showed us.
Can you show us a game in which these techniques are used?

1 Like