[SOLVED] Creating my own custom buffers (for night and day prebaked light)

I’m looking to set multiple color buffers for prebaked light. I’m planning on setting 1 for the day level light and 1 as the night light level then setting a material level parameter of daylightIntensity and having the shader combine the two on the graphics card, for for example twilight. So I need to set this:

Mesh mapmesh=new Mesh();
mapmesh.setBuffer(VertexBuffer.Type.Color, 4, getVertexColourDay()); 
mapmesh.setBuffer(VertexBuffer.Type.Color2, 4, getVertexColourNight()); 

However there is no VertexBuffer.Type.Color2 so that’s obviously not going to work. There is Tangent which I could possibly misuse for this but it feels wrong and doesn’t really help if I need more buffers.

So my question is:
How do I define more buffers, in addition to the built in ones?

I don’t think you can define more buffers in jme, but you can use texCoord2 texCoord3, you pass 4 as lenght and you should be fine.

1 Like

Ah, I had assumed they had to be all 2 floats long. If I can pass 4 floats into them I doubt i’ll ever use up all the texCoords. Thanks!

1 Like

I’ve run through this and it all works very nicely. I’ll put the code for the java and shaders at the bottom of this post in case anyone else is trying to do this. I’ve also put in in a respository for the full working example Bitbucket. And I’ve done a more in the context of my game write up here https://richardonsoftware.wordpress.com/night-and-day-done-not-stupidly/

    package mygame;

    import com.jme3.app.SimpleApplication;
    import com.jme3.material.Material;
    import com.jme3.math.Vector2f;
    import com.jme3.math.Vector3f;
    import com.jme3.math.Vector4f;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.Mesh;
    import com.jme3.scene.VertexBuffer;
    import com.jme3.shader.VarType;
    import com.jme3.util.BufferUtils;

    public class Main extends SimpleApplication {

        private Material mat;
        private float dayNightPeriod = 4;
        private float time;
        
        public static void main(String[] args) {
            Main app = new Main();
            app.start();
        }

        @Override
        public void simpleInitApp() {
            
            //the points in 3d space where the geometry will be
            Vector3f [] vertices = new Vector3f[8];
            //one square
            vertices[0] = new Vector3f(0,0,0);
            vertices[1] = new Vector3f(3,0,0);
            vertices[2] = new Vector3f(0,3,0);
            vertices[3] = new Vector3f(3,3,0);
            //a second square
            vertices[4] = new Vector3f(3,0,0);
            vertices[5] = new Vector3f(6,0,0);
            vertices[6] = new Vector3f(3,3,0);
            vertices[7] = new Vector3f(6,3,0);
            
            //combine those vetexes into triangles 
            int [] indexes = { 
                //first square
                2,0,1,1,3,2, 
                //second square    
                6,4,5,5,7,6     
            };
            
            //we're not using a texture but if we were this would define whtich parts of the image are where    
            Vector2f[] texCoord = new Vector2f[8];
            texCoord[0] = new Vector2f(0,0);
            texCoord[1] = new Vector2f(1,0);
            texCoord[2] = new Vector2f(0,1);
            texCoord[3] = new Vector2f(1,1);
            texCoord[4] = new Vector2f(0,0);
            texCoord[5] = new Vector2f(1,0);
            texCoord[6] = new Vector2f(0,1);
            texCoord[7] = new Vector2f(1,1);
            
            
            //Set the first colour buffer, this will be the sunlight color
            Vector4f[] sunlightColour = new Vector4f[8]; //these are Vector4f because we have a red, green, blue and transparency per vertex
            //both the squares at full brightness in sunlight
            sunlightColour[0] = new Vector4f(1,1,1,1);
            sunlightColour[1] = new Vector4f(1,1,1,1);
            sunlightColour[2] = new Vector4f(1,1,1,1);
            sunlightColour[3] = new Vector4f(1,1,1,1);
            
            sunlightColour[4] = new Vector4f(1,1,1,1);
            sunlightColour[5] = new Vector4f(1,1,1,1);
            sunlightColour[6] = new Vector4f(1,1,1,1);
            sunlightColour[7] = new Vector4f(1,1,1,1);
            
            Vector4f[] sourceLightColour = new Vector4f[8];
            sourceLightColour[0] = new Vector4f(0.9f,0.9f,0.9f,1); //first square at 90% brightness in sourcelight
            sourceLightColour[1] = new Vector4f(0.9f,0.9f,0.9f,1);
            sourceLightColour[2] = new Vector4f(0.9f,0.9f,0.9f,1);
            sourceLightColour[3] = new Vector4f(0.9f,0.9f,0.9f,1);
            
            sourceLightColour[4] = new Vector4f(0.3f,0.3f,0.3f,1);//second square at 30% brightness in sourcelight
            sourceLightColour[5] = new Vector4f(0.3f,0.3f,0.3f,1);
            sourceLightColour[6] = new Vector4f(0.3f,0.3f,0.3f,1);
            sourceLightColour[7] = new Vector4f(0.3f,0.3f,0.3f,1);
            
            //now we have all the data we create the mesh
            //for more details https://jmonkeyengine.github.io/wiki/jme3/advanced/custom_meshes.html
            Mesh mesh = new Mesh();
            mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
            mesh.setBuffer(VertexBuffer.Type.Index,    3, BufferUtils.createIntBuffer(indexes));
            mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
            mesh.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(sunlightColour));
            //we're using the conventional Color buffer for sunlight and using one of the (usually) unused TexCoord5 to hold the source light
            mesh.setBuffer(VertexBuffer.Type.TexCoord5, 4, BufferUtils.createFloatBuffer(sourceLightColour));
            mesh.updateBound();
            
            Geometry geom = new Geometry("mesh", mesh);

            mat = new Material(assetManager, "MatDefs/RepeatingUnshaded.j3md");
            //mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md"); 
            mat.setBoolean("VertexColor", true);
            geom.setMaterial(mat);

            rootNode.attachChild(geom);
        }

        @Override
        public void simpleUpdate(float tpf) {
           time += tpf;
           if (time>dayNightPeriod){
              time = 0; 
           }
           mat.setParam("SunlightIntensity", VarType.Float,time/dayNightPeriod); 
        }
    }

Vertex shader

    #import "Common/ShaderLib/GLSLCompat.glsllib"
    #import "Common/ShaderLib/Skinning.glsllib"
    #import "Common/ShaderLib/Instancing.glsllib"

    attribute vec3 inPosition;

#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
    #define NEED_TEXCOORD1
#endif

attribute vec2 inTexCoord;
attribute vec2 inTexCoord2;
attribute vec4 inColor;

varying vec2 texCoord1;
varying vec2 texCoord2;
attribute vec4 inTexCoord5; //!changed! this is the source light that is passed to us
varying vec4 texCoord5; //!changed! and this is how we'll pass it through to the fragment shader

varying vec4 vertColor;

void main(){
    #ifdef NEED_TEXCOORD1
        texCoord1 = inTexCoord;
    #endif

    #ifdef SEPARATE_TEXCOORD
        texCoord2 = inTexCoord2;
    #endif

    #ifdef HAS_VERTEXCOLOR
        vertColor = inColor;
    #endif

    vec4 modelSpacePos = vec4(inPosition, 1.0);
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif

    texCoord5 = inTexCoord5; //!changed! its the fragment shader that wants this so we just pass it on


    gl_Position = TransformWorldViewProjection(modelSpacePos);
}

Fragment shader
    #import "Common/ShaderLib/GLSLCompat.glsllib"

    #if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
        #define NEED_TEXCOORD1
    #endif

    #if defined(DISCARD_ALPHA)
        uniform float m_AlphaDiscardThreshold;
    #endif

    uniform float m_SunlightIntensity; //!changed!
    uniform vec4 m_Color;
    uniform sampler2D m_ColorMap;
    uniform sampler2D m_LightMap;

    varying vec2 texCoord1;
    varying vec2 texCoord2;

    varying vec4 vertColor;

    varying vec4 texCoord5; //!changed!this is the source color passed through to us

    void main(){
        vec4 color = vec4(1.0);

        #ifdef HAS_COLORMAP
            color *= texture2D(m_ColorMap, texCoord1);     
        #endif

        #ifdef HAS_VERTEXCOLOR
            //!changed! This is the actual combining of the two colours
            color.x *= max(vertColor.x * m_SunlightIntensity,texCoord5.x);
            color.y *= max(vertColor.y * m_SunlightIntensity,texCoord5.y);
            color.z *= max(vertColor.z * m_SunlightIntensity,texCoord5.z);
            color.a *= vertColor.a;
        #endif

        #ifdef HAS_COLOR
            color *= m_Color;
        #endif

        #ifdef HAS_LIGHTMAP
            #ifdef SEPARATE_TEXCOORD
                color.rgb *= texture2D(m_LightMap, texCoord2).rgb;
            #else
                color.rgb *= texture2D(m_LightMap, texCoord1).rgb;
            #endif
        #endif

        #if defined(DISCARD_ALPHA)
            if(color.a < m_AlphaDiscardThreshold){
               discard;
            }
        #endif

        gl_FragColor = color;
    }
5 Likes

Really liked your example richtea. Managed to adapt it to my own hacked version of Lighting.j3md, so now I can combine directional lights with both day and night baked lighting. Great stuff.

1 Like

I find your work inspiring.

And here’s the result: I live in the northern hemisphere.
Let us normalize 24 h as 2 Pi.
My day = my night = Pi only 2 times at year (beginning of spring and autumn) In winter my day is shorter, in summer longer than Pi.

Surfaces sloping to the east notice more dawn.
Surfaces sloping to the west get more of the sunset.
Surfaces sloping more south will get more of both.

So if you were not only using TexCoord5, but TexCoord4 for east-west brightness intensity correction and TexCoord5 for zenith-south brightness intensity correction, and in
SunlightIntensity4 = Math.max (0, sin (time) - season correction)
(Because the sinus curve is always flatter at the top, the redness also becomes longer / slower) and
SunlightIntensity5 = (1- cos (time)) / 2
(or vice versa) and in the shader would pass both as running variables, the time of day could become even more realistic.

2 Likes

Hi! I read this thread and I thought it could be done just by using predefined jme3 lighting instead of messing with shaders and mesh buffers. I got my hands dirty for a while and I’ve just implemented a simple similar effect with custom light colors depending on the time frame (maybe chosen the colors are not the better option).

In case you’re interested the code is at github: GitHub - joliver82/jME3-simple-sunlight and a demo video at youtube: https://youtu.be/57tMoVnATXo

I think that uses point light rather than pre baked light. A fair thing for some uses but prebaked allows for light to go round corners (or whatever the pre baking algorithm wants to do)

Ooo! I love that idea. I might have to look into implementing that

Hi @richtea! The point light is to replicate in a quick way the lighting of the same scene video you posted. About the sunlight, I thought it was cleaner and quicker to implement it with just lighting instead. As any other techniques, it depends on the result you want to achieve which one to use.

Offopic: I just saw you published a game on steam (One Million Worlds) which is great, congratulations! I’m planning on releasing my game for both mobiles and desktop, so steam is an option for me. Is it hard to get a game accepted in the platform? Are you using steamworks4j or any other steam wrapper for java I may not be aware of? Thanks

1 Like

Thanks!

It wasn’t too hard. They’ve changed the process so there’s a $100 application fee replacing the old greenlight program (but acceptance for a real game is much much more likely).

They go through the store page very thoroughly but less so the application itself (I think they just care that its not malware).

My game isn’t integrated with steam, just uses steam for distribution and launch (so no achievements or other steam stuff) so I’m not sure about support for that.

I use steam pipe to deploy to steam, which is pretty well documented. The only annoying thing is you can’t use an ephemeral build server to deploy to it because it has to be two factor authenticated, but I was able to get a persistent Jenkins to use it.

5 Likes