HeightBasedTerrain with lighting support

Hey guys n girls.

This submission is more of a merging of existing code than a personal write. Essentially, it is the same HeightBasedTerrain material that already exists in JME, with the added bonus of ambient, directional, point and spot light support. My apologies for my c# style layout - it just strikes me as easier to read than the traditional java brace standard.

Note: For those copy-pasting, you need to put the appropriate path and name of the vertex and fragment shaders in the first technique of the .jm3d file.

I guess screenshots are a little pointless, but anyways…





.j3md material file
[java]
MaterialDef Height Based Terrain Lighting
{
MaterialParameters
{
Texture2D DiffuseMap
Texture2D DiffuseMap_1
Texture2D DiffuseMap_2
Texture2D DiffuseMap_3
Texture2D SlopeDiffuseMap
Float slopeTileFactor
Float terrainSize
Vector3 region1
Vector3 region2
Vector3 region3
Vector3 region4
}

Technique
{
    LightMode MultiPass

    VertexShader GLSL100:   MatDefs/CustomTerrain/CustomTerrain.vert
    FragmentShader GLSL100: MatDefs/CustomTerrain/CustomTerrain.frag

    WorldParameters
    {
        WorldViewProjectionMatrix
        WorldViewMatrix
        WorldMatrix
        NormalMatrix
        ViewMatrix
    }

    Defines
    {
        DIFFUSEMAP : DiffuseMap
    }
}

Technique PreShadow {

    VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert
    FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag

    WorldParameters {
        WorldViewProjectionMatrix
        WorldViewMatrix
    }

    Defines {
        DIFFUSEMAP_ALPHA : DiffuseMap
    }

    RenderState {
        FaceCull Off
        DepthTest On
        DepthWrite On
        PolyOffset 5 0
        ColorWrite Off
    }
}

Technique PreNormalPass {

    VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert
    FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

    WorldParameters {
        WorldViewProjectionMatrix
        WorldViewMatrix
        NormalMatrix
    }

    Defines {
        DIFFUSEMAP_ALPHA : DiffuseMap
    }

    RenderState {

    }

}

Technique GBuf {

    VertexShader GLSL100:   Common/MatDefs/Light/GBuf.vert
    FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag

    WorldParameters {
        WorldViewProjectionMatrix
        WorldMatrix
    }

    Defines {
        VERTEX_COLOR : UseVertexColor
        MATERIAL_COLORS : UseMaterialColors
        V_TANGENT : VTangent
        MINNAERT  : Minnaert
        WARDISO   : WardIso

        DIFFUSEMAP : DiffuseMap
        NORMALMAP : NormalMap
        SPECULARMAP : SpecularMap
        PARALLAXMAP : ParallaxMap
    }
}

Technique {
    LightMode FixedPipeline
}

Technique Glow {

    VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert
    FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

    WorldParameters {
        WorldViewProjectionMatrix
    }

    Defines {
        HAS_GLOWMAP : GlowMap
        HAS_GLOWCOLOR : GlowColor
    }
}

}
[/java]

.vert shader
[java]
uniform float m_tilingFactor;
uniform float m_terrainSize;

uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldMatrix;
uniform mat3 g_NormalMatrix;
uniform mat4 g_ViewMatrix;

uniform vec4 g_LightColor;
uniform vec4 g_LightPosition;
uniform vec4 g_AmbientLightColor;

attribute vec3 inNormal;
attribute vec3 inPosition;

varying vec3 hbNormal;
varying vec4 hbPosition;

varying vec3 vNormal;
varying vec3 vPosition;
varying vec3 vViewDir;
varying vec4 vLightDir;

varying vec3 lightVec;

varying vec4 AmbientSum;
varying vec4 DiffuseSum;
varying vec4 SpecularSum;

// JME3 lights in world space
void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir)
{
float posLight = step(0.5, color.w);
vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);
lightVec.xyz = tempVec;
float dist = length(tempVec);
lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);
lightDir.xyz = tempVec / vec3(dist);
}

void main()
{
hbNormal = normalize(inNormal);
hbPosition = g_WorldMatrix * vec4(inPosition, 0.0);

vec4 pos = vec4(inPosition, 1.0);

gl_Position = g_WorldViewProjectionMatrix * pos;

vec3 wvPosition = (g_WorldViewMatrix * pos).xyz;
vec3 wvNormal  = normalize(g_NormalMatrix * inNormal);
vec3 viewDir = normalize(-wvPosition);

vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0)));
wvLightPos.w = g_LightPosition.w;
vec4 lightColor = g_LightColor;

vNormal = wvNormal;

vPosition = wvPosition;
vViewDir = viewDir;

lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);

AmbientSum  = vec4(0.2, 0.2, 0.2, 1.0) * g_AmbientLightColor; // Default: ambient color is dark gray
DiffuseSum  = lightColor;
SpecularSum = lightColor;

}
[/java]

.frag shader
[java]
uniform vec4 g_LightDirection;

uniform vec3 m_region1;
uniform vec3 m_region2;
uniform vec3 m_region3;
uniform vec3 m_region4;

uniform sampler2D m_DiffuseMap;
uniform sampler2D m_DiffuseMap_1;
uniform sampler2D m_DiffuseMap_2;
uniform sampler2D m_DiffuseMap_3;
uniform sampler2D m_SlopeDiffuseMap;

uniform float m_slopeTileFactor;
uniform float m_terrainSize;

varying vec3 hbNormal;
varying vec4 hbPosition;

varying vec4 AmbientSum;
varying vec4 DiffuseSum;
varying vec4 SpecularSum;

varying vec3 vNormal;
varying vec3 vPosition;
varying vec3 vViewDir;
varying vec4 vLightDir;
varying vec3 lightVec;

vec4 GenerateTerrainColor()
{
float height = hbPosition.y;
vec4 p = hbPosition / m_terrainSize;

vec3 blend = abs( hbNormal );
blend = (blend -0.2) * 0.7;
blend = normalize(max(blend, 0.00001));      // Force weights to sum to 1.0 (very important!)
float b = (blend.x + blend.y + blend.z);
blend /= vec3(b, b, b);

vec4 terrainColor = vec4(0.0, 0.0, 0.0, 1.0);

float m_regionMin = 0.0;
float m_regionMax = 0.0;
float m_regionRange = 0.0;
float m_regionWeight = 0.0;

vec4 slopeCol1 = texture2D(m_SlopeDiffuseMap, p.yz * m_slopeTileFactor);
vec4 slopeCol2 = texture2D(m_SlopeDiffuseMap, p.xy * m_slopeTileFactor);

// Terrain m_region 1.
m_regionMin = m_region1.x;
m_regionMax = m_region1.y;
m_regionRange = m_regionMax - m_regionMin;
m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;
m_regionWeight = max(0.0, m_regionWeight);
terrainColor += m_regionWeight * texture2D(m_DiffuseMap, p.xz * m_region1.z);

// Terrain m_region 2.
m_regionMin = m_region2.x;
m_regionMax = m_region2.y;
m_regionRange = m_regionMax - m_regionMin;
m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;
m_regionWeight = max(0.0, m_regionWeight);
terrainColor += m_regionWeight * (texture2D(m_DiffuseMap_1, p.xz * m_region2.z));

// Terrain m_region 3.
m_regionMin = m_region3.x;
m_regionMax = m_region3.y;
m_regionRange = m_regionMax - m_regionMin;
m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;
m_regionWeight = max(0.0, m_regionWeight);
terrainColor += m_regionWeight * texture2D(m_DiffuseMap_2, p.xz * m_region3.z);

// Terrain m_region 4.
m_regionMin = m_region4.x;
m_regionMax = m_region4.y;
m_regionRange = m_regionMax - m_regionMin;
m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;
m_regionWeight = max(0.0, m_regionWeight);
terrainColor += m_regionWeight * texture2D(m_DiffuseMap_3, p.xz * m_region4.z);

return (blend.y * terrainColor + blend.x * slopeCol1 + blend.z * slopeCol2);

}

float tangDot(in vec3 v1, in vec3 v2)
{
float d = dot(v1,v2);
return d;
}

float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir)
{
return max(0.0, dot(norm, lightdir));
}

float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny)
{
if (shiny <= 1.0)
{
return 0.0;
}

vec3 R = reflect(-lightdir, norm);
return pow(max(tangDot(R, viewdir), 0.0), shiny);

}

vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir)
{
float shininess = 0.0;

float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);
float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, shininess);
specularFactor *= step(1.0, shininess);

float att = vLightDir.w;

return vec2(diffuseFactor, specularFactor) * vec2(att);

}

void main()
{
vec4 diffuseColor = GenerateTerrainColor();
float spotFallOff = 1.0;

if(g_LightDirection.w!=0.0)
{
    vec3 L = normalize(lightVec.xyz);
    vec3 spotdir = normalize(g_LightDirection.xyz);
    float curAngleCos = dot(-L, spotdir);
    float innerAngleCos = floor(g_LightDirection.w) * 0.001;
    float outerAngleCos = fract(g_LightDirection.w);
    float innerMinusOuter = innerAngleCos - outerAngleCos;

    spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;

    if(spotFallOff &lt;= 0.0)
    {
        gl_FragColor = AmbientSum * diffuseColor;
        return;
    }
    else
    {
        spotFallOff = clamp(spotFallOff, 0.0, 1.0);
    }
}

vec3 normal = vNormal;
vec4 lightDir = vLightDir;
lightDir.xyz = normalize(lightDir.xyz);
vec2 light = computeLighting(vPosition, normal, vViewDir.xyz, lightDir.xyz)*spotFallOff;
vec4 specularColor = vec4(1.0);

gl_FragColor =  AmbientSum * diffuseColor +
                DiffuseSum * diffuseColor  * light.x +
                SpecularSum * specularColor * light.y;

}

[/java]

finally, the useage is different only by the name of the textures since lighting now affects the material:

[java]
float scale = 8f;

Material terrainMat = new Material(gameManager.getAssetManager(), “MatDefs/CustomTerrain/CustomTerrain.j3md”);

Texture tex_region1 = gameManager.getAssetManager().loadTexture(“Textures/Terrain/sandstone_top.png”);
tex_region1.setWrap(WrapMode.Repeat);

Texture tex_region2 = gameManager.getAssetManager().loadTexture(“Textures/Terrain/realdirt.png”);
tex_region2.setWrap(WrapMode.Repeat);

Texture tex_region3 = gameManager.getAssetManager().loadTexture(“Textures/Terrain/Grass_1.png”);
tex_region3.setWrap(WrapMode.Repeat);

Texture tex_region4 = gameManager.getAssetManager().loadTexture(“Textures/Terrain/realsnow.png”);
tex_region4.setWrap(WrapMode.Repeat);

terrainMat.setTexture(“DiffuseMap”, tex_region1);
terrainMat.setVector3(“region1”, new Vector3f(0, 88, scale));

terrainMat.setTexture(“DiffuseMap_1”, tex_region2);
terrainMat.setVector3(“region2”, new Vector3f(88, 130, scale));

terrainMat.setTexture(“DiffuseMap_2”, tex_region3);
terrainMat.setVector3(“region3”, new Vector3f(130, 270, scale));

terrainMat.setTexture(“DiffuseMap_3”, tex_region4);
terrainMat.setVector3(“region4”, new Vector3f(270, 384, scale));

// slope
Texture rock = gameManager.getAssetManager().loadTexture(“Textures/Terrain/Tundra.jpg”);
rock.setWrap(WrapMode.Repeat);

terrainMat.setTexture(“SlopeDiffuseMap”, rock);
terrainMat.setFloat(“slopeTileFactor”, 2f);

terrainMat.setFloat(“terrainSize”, (GlobalSettings.BLOCK_SIZE - 1) / 4);
[/java]

6 Likes

Looking good XD

Hi, this is my first post :smiley: Thank you so much for this contribution to JME3. It is running very smoothly on my computer and it’s looking good indeed!

Hi @jayfella !

Thank you for contribution! But i think the terrain with many light sources has sense only for deffered shading system.
As it’s forward shading in JME at present. Every additional light increases polygons count twice.
And terrain is pretty polygonal.

IMHO. But anyway it’s good to have this feature when we need it.