diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java index 509def189..73d66f7d4 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java @@ -111,6 +111,11 @@ public Material applyToMaterial(Material material) { } else { material.remove(TextureAttribute.Diffuse); } + if (normalMap != null) { + material.set(new TextureAttribute(TextureAttribute.Normal, normalMap.getTexture())); + } else { + material.remove(TextureAttribute.Normal); + } material.set(new FloatAttribute(FloatAttribute.Shininess, shininess)); return material; @@ -138,7 +143,11 @@ public TextureAsset getNormalMap() { public void setNormalMap(TextureAsset normalMap) { this.normalMap = normalMap; - normalMapID = normalMap.getID(); + if (normalMap != null) { + this.normalMapID = normalMap.getID(); + } else { + this.normalMapID = null; + } } public TextureAsset getDiffuseTexture() { diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java index 743eacfba..cf3a6b7f6 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java @@ -46,6 +46,11 @@ public class TerrainAsset extends Asset { private TextureAsset splatG; private TextureAsset splatB; private TextureAsset splatA; + private TextureAsset splatBaseNormal; + private TextureAsset splatRNormal; + private TextureAsset splatBNormal; + private TextureAsset splatGNormal; + private TextureAsset splatANormal; private Terrain terrain; @@ -137,6 +142,71 @@ public void setSplatA(TextureAsset splatA) { } } + public TextureAsset getSplatBaseNormal() { + return splatBaseNormal; + } + + public void setSplatBaseNormal(TextureAsset splatBaseNormal) { + this.splatBaseNormal = splatBaseNormal; + if (splatBaseNormal == null) { + meta.getTerrain().setSplatBaseNormal(null); + } else { + meta.getTerrain().setSplatBaseNormal(splatBaseNormal.getID()); + } + } + + public TextureAsset getSplatRNormal() { + return splatRNormal; + } + + public void setSplatRNormal(TextureAsset splatRNormal) { + this.splatRNormal = splatRNormal; + if (splatRNormal == null) { + meta.getTerrain().setSplatRNormal(null); + } else { + meta.getTerrain().setSplatRNormal(splatRNormal.getID()); + } + } + + public TextureAsset getSplatBNormal() { + return splatBNormal; + } + + public void setSplatBNormal(TextureAsset splatBNormal) { + this.splatBNormal = splatBNormal; + if (splatBNormal == null) { + meta.getTerrain().setSplatBNormal(null); + } else { + meta.getTerrain().setSplatBNormal(splatBNormal.getID()); + } + } + + public TextureAsset getSplatGNormal() { + return splatGNormal; + } + + public void setSplatGNormal(TextureAsset splatGNormal) { + this.splatGNormal = splatGNormal; + if (splatGNormal == null) { + meta.getTerrain().setSplatGNormal(null); + } else { + meta.getTerrain().setSplatGNormal(splatGNormal.getID()); + } + } + + public TextureAsset getSplatANormal() { + return splatANormal; + } + + public void setSplatANormal(TextureAsset splatANormal) { + this.splatANormal = splatANormal; + if (splatANormal == null) { + meta.getTerrain().setSplatANormal(null); + } else { + meta.getTerrain().setSplatANormal(splatANormal.getID()); + } + } + public Terrain getTerrain() { return terrain; } @@ -210,6 +280,36 @@ public void resolveDependencies(Map assets) { if (id != null && assets.containsKey(id)) { setSplatA((TextureAsset) assets.get(id)); } + + // splat normal channel base + id = meta.getTerrain().getSplatBaseNormal(); + if (id != null && assets.containsKey(id)) { + setSplatBaseNormal((TextureAsset) assets.get(id)); + } + + // splat normal channel r + id = meta.getTerrain().getSplatRNormal(); + if (id != null && assets.containsKey(id)) { + setSplatRNormal((TextureAsset) assets.get(id)); + } + + // splat normal channel g + id = meta.getTerrain().getSplatGNormal(); + if (id != null && assets.containsKey(id)) { + setSplatGNormal((TextureAsset) assets.get(id)); + } + + // splat normal channel b + id = meta.getTerrain().getSplatBNormal(); + if (id != null && assets.containsKey(id)) { + setSplatBNormal((TextureAsset) assets.get(id)); + } + + // splat normal channel a + id = meta.getTerrain().getSplatANormal(); + if (id != null && assets.containsKey(id)) { + setSplatANormal((TextureAsset) assets.get(id)); + } } @Override @@ -247,6 +347,32 @@ public void applyDependencies() { terrainTexture.setSplatTexture(new SplatTexture(SplatTexture.Channel.A, splatA)); } + if (splatBaseNormal == null) { + terrainTexture.removeNormalTexture(SplatTexture.Channel.BASE); + } else { + terrainTexture.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.BASE, splatBaseNormal)); + } + if (splatRNormal == null) { + terrainTexture.removeNormalTexture(SplatTexture.Channel.R); + } else { + terrainTexture.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.R, splatRNormal)); + } + if (splatGNormal == null) { + terrainTexture.removeNormalTexture(SplatTexture.Channel.G); + } else { + terrainTexture.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.G, splatGNormal)); + } + if (splatBNormal == null) { + terrainTexture.removeNormalTexture(SplatTexture.Channel.B); + } else { + terrainTexture.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.B, splatBNormal)); + } + if (splatANormal == null) { + terrainTexture.removeNormalTexture(SplatTexture.Channel.A); + } else { + terrainTexture.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.A, splatANormal)); + } + terrain.update(); } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java index 866263197..518218fec 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java @@ -66,6 +66,11 @@ private void parseTerrain(Meta meta, JsonValue jsonTerrain) { terrain.setSplatG(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_G, null)); terrain.setSplatB(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_B, null)); terrain.setSplatA(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_A, null)); + terrain.setSplatBaseNormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_BASE_NORMAL, null)); + terrain.setSplatRNormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_R_NORMAL, null)); + terrain.setSplatGNormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_G_NORMAL, null)); + terrain.setSplatBNormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_B_NORMAL, null)); + terrain.setSplatANormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_A_NORMAL, null)); meta.setTerrain(terrain); } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java index f9c718f78..94d3df575 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java @@ -31,6 +31,11 @@ public class MetaTerrain { public static final String JSON_SPLAT_G = "g"; public static final String JSON_SPLAT_B = "b"; public static final String JSON_SPLAT_A = "a"; + public static final String JSON_SPLAT_BASE_NORMAL = "baseNorm"; + public static final String JSON_SPLAT_R_NORMAL = "rNorm"; + public static final String JSON_SPLAT_G_NORMAL = "gNorm"; + public static final String JSON_SPLAT_B_NORMAL = "bNorm"; + public static final String JSON_SPLAT_A_NORMAL = "aNorm"; public static final String JSON_UV_SCALE= "uv"; private int size; @@ -42,6 +47,11 @@ public class MetaTerrain { private String splatG; private String splatB; private String splatA; + private String splatBaseNormal; + private String splatRNormal; + private String splatGNormal; + private String splatBNormal; + private String splatANormal; public String getSplatmap() { return splatmap; @@ -115,6 +125,46 @@ public void setUv(float uv) { this.uv = uv; } + public String getSplatBaseNormal() { + return splatBaseNormal; + } + + public void setSplatBaseNormal(String splatBaseNormal) { + this.splatBaseNormal = splatBaseNormal; + } + + public String getSplatRNormal() { + return splatRNormal; + } + + public void setSplatRNormal(String splatRNormal) { + this.splatRNormal = splatRNormal; + } + + public String getSplatGNormal() { + return splatGNormal; + } + + public void setSplatGNormal(String splatGNormal) { + this.splatGNormal = splatGNormal; + } + + public String getSplatBNormal() { + return splatBNormal; + } + + public void setSplatBNormal(String splatBNormal) { + this.splatBNormal = splatBNormal; + } + + public String getSplatANormal() { + return splatANormal; + } + + public void setSplatANormal(String splatANormal) { + this.splatANormal = splatANormal; + } + @Override public String toString() { return "MetaTerrain{" + @@ -127,6 +177,11 @@ public String toString() { ", splatG='" + splatG + '\'' + ", splatB='" + splatB + '\'' + ", splatA='" + splatA + '\'' + + ", splatBaseNormal='" + splatBaseNormal + '\'' + + ", splatRNormal='" + splatRNormal + '\'' + + ", splatGNormal='" + splatGNormal + '\'' + + ", splatBNormal='" + splatBNormal + '\'' + + ", splatANormal='" + splatANormal + '\'' + '}'; } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/ModelShader.java b/commons/src/main/com/mbrlabs/mundus/commons/shaders/ModelShader.java index bba43d019..efc1f0b9e 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/ModelShader.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/ModelShader.java @@ -25,6 +25,7 @@ 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 com.mbrlabs.mundus.commons.env.Fog; import com.mbrlabs.mundus.commons.env.MundusEnvironment; import com.mbrlabs.mundus.commons.utils.ShaderUtils; @@ -41,11 +42,14 @@ public class ModelShader extends LightShader { // ============================ MATERIALS ============================ protected final int UNIFORM_MATERIAL_DIFFUSE_TEXTURE = register(new Uniform("u_diffuseTexture")); protected final int UNIFORM_MATERIAL_DIFFUSE_USE_TEXTURE = register(new Uniform("u_diffuseUseTexture")); + protected final int UNIFORM_MATERIAL_NORMAL_TEXTURE = register(new Uniform("u_normalTexture")); + protected final int UNIFORM_MATERIAL_NORMAL_USE_TEXTURE = register(new Uniform("u_useNormalMap")); // ============================ MATRICES & CAM POSITION ============================ protected final int UNIFORM_PROJ_VIEW_MATRIX = register(new Uniform("u_projViewMatrix")); protected final int UNIFORM_TRANS_MATRIX = register(new Uniform("u_transMatrix")); + protected final int UNIFORM_NORMAL_MATRIX = register(new Uniform("u_normalMatrix")); protected final int UNIFORM_CAM_POS = register(new Uniform("u_camPos")); // ============================ FOG ============================ @@ -53,6 +57,8 @@ public class ModelShader extends LightShader { protected final int UNIFORM_FOG_GRADIENT = register(new Uniform("u_fogGradient")); protected final int UNIFORM_FOG_COLOR = register(new Uniform("u_fogColor")); + private final Matrix3 tmpM = new Matrix3(); + private ShaderProgram program; public ModelShader() { @@ -97,9 +103,11 @@ public void render(Renderable renderable) { setLights(env); set(UNIFORM_TRANS_MATRIX, renderable.worldTransform); + set(UNIFORM_NORMAL_MATRIX, tmpM.set(renderable.worldTransform).inv().transpose()); // texture uniform TextureAttribute diffuseTexture = ((TextureAttribute) (renderable.material.get(TextureAttribute.Diffuse))); + TextureAttribute normalMap = ((TextureAttribute) (renderable.material.get(TextureAttribute.Normal))); ColorAttribute diffuseColor = ((ColorAttribute) (renderable.material.get(ColorAttribute.Diffuse))); if (diffuseTexture != null) { @@ -109,6 +117,13 @@ public void render(Renderable renderable) { set(UNIFORM_MATERIAL_DIFFUSE_USE_TEXTURE, 0); } + if (normalMap != null) { + set(UNIFORM_MATERIAL_NORMAL_TEXTURE, normalMap.textureDescription.texture); + set(UNIFORM_MATERIAL_NORMAL_USE_TEXTURE, 1); + } else { + set(UNIFORM_MATERIAL_NORMAL_USE_TEXTURE, 0); + } + set(UNIFORM_USE_MATERIAL, 1); // Use material for lighting set(UNIFORM_MATERIAL_DIFFUSE_COLOR, diffuseColor.color.r, diffuseColor.color.g, diffuseColor.color.b); diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainShader.java b/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainShader.java index 8f759656c..7b24420b2 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainShader.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainShader.java @@ -22,6 +22,7 @@ import com.badlogic.gdx.graphics.g3d.Shader; import com.badlogic.gdx.graphics.g3d.utils.RenderContext; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Vector2; import com.mbrlabs.mundus.commons.env.Fog; import com.mbrlabs.mundus.commons.env.MundusEnvironment; @@ -42,6 +43,7 @@ public class TerrainShader extends LightShader { // ============================ MATRICES & CAM POSITION ============================ protected final int UNIFORM_PROJ_VIEW_MATRIX = register(new Uniform("u_projViewMatrix")); protected final int UNIFORM_TRANS_MATRIX = register(new Uniform("u_transMatrix")); + protected final int UNIFORM_NORMAL_MATRIX = register(new Uniform("u_normalMatrix")); protected final int UNIFORM_CAM_POS = register(new Uniform("u_camPos")); // ============================ TEXTURE SPLATTING ============================ @@ -55,11 +57,25 @@ public class TerrainShader extends LightShader { protected final int UNIFORM_TEXTURE_HAS_SPLATMAP = register(new Uniform("u_texture_has_splatmap")); protected final int UNIFORM_TEXTURE_HAS_DIFFUSE = register(new Uniform("u_texture_has_diffuse")); + // Splat normals + protected final int UNIFORM_TEXTURE_HAS_NORMALS = register(new Uniform("u_texture_has_normals")); + protected final int UNIFORM_TEXTURE_BASE_NORMAL = register(new Uniform("u_texture_base_normal")); + protected final int UNIFORM_TEXTURE_BASE_NORMAL_PRESENT = register(new Uniform("u_texture_has_normal_base")); + protected final int UNIFORM_TEXTURE_R_NORMAL = register(new Uniform("u_texture_r_normal")); + protected final int UNIFORM_TEXTURE_R_NORMAL_PRESENT = register(new Uniform("u_texture_has_normal_r")); + protected final int UNIFORM_TEXTURE_G_NORMAL = register(new Uniform("u_texture_g_normal")); + protected final int UNIFORM_TEXTURE_G_NORMAL_PRESENT = register(new Uniform("u_texture_has_normal_g")); + protected final int UNIFORM_TEXTURE_B_NORMAL = register(new Uniform("u_texture_b_normal")); + protected final int UNIFORM_TEXTURE_B_NORMAL_PRESENT = register(new Uniform("u_texture_has_normal_b")); + protected final int UNIFORM_TEXTURE_A_NORMAL = register(new Uniform("u_texture_a_normal")); + protected final int UNIFORM_TEXTURE_A_NORMAL_PRESENT = register(new Uniform("u_texture_has_normal_a")); + // ============================ FOG ============================ protected final int UNIFORM_FOG_DENSITY = register(new Uniform("u_fogDensity")); protected final int UNIFORM_FOG_GRADIENT = register(new Uniform("u_fogGradient")); protected final int UNIFORM_FOG_COLOR = register(new Uniform("u_fogColor")); + private final Matrix3 tmpM = new Matrix3(); private final Vector2 terrainSize = new Vector2(); protected ShaderProgram program; @@ -108,6 +124,7 @@ public void render(Renderable renderable) { setLights(env); setTerrainSplatTextures(renderable); set(UNIFORM_TRANS_MATRIX, renderable.worldTransform); + set(UNIFORM_NORMAL_MATRIX, tmpM.set(renderable.worldTransform).inv().transpose()); // Fog final Fog fog = env.getFog(); @@ -129,11 +146,20 @@ protected void setTerrainSplatTextures(Renderable renderable) { .get(TerrainTextureAttribute.ATTRIBUTE_SPLAT0); final TerrainTexture terrainTexture = splatAttrib.terrainTexture; + // Does terrain have normal maps + boolean hasNormals = terrainTexture.hasNormalTextures(); + if (hasNormals) { + set(UNIFORM_TEXTURE_HAS_NORMALS, 1); + } else { + set(UNIFORM_TEXTURE_HAS_NORMALS, 0); + } + // base texture SplatTexture st = terrainTexture.getTexture(SplatTexture.Channel.BASE); if (st != null) { set(UNIFORM_TEXTURE_BASE, st.texture.getTexture()); set(UNIFORM_TEXTURE_HAS_DIFFUSE, 1); + setNormalTexture(terrainTexture, SplatTexture.Channel.BASE, UNIFORM_TEXTURE_BASE_NORMAL, UNIFORM_TEXTURE_BASE_NORMAL_PRESENT); } else { set(UNIFORM_TEXTURE_HAS_DIFFUSE, 0); } @@ -150,6 +176,15 @@ protected void setTerrainSplatTextures(Renderable renderable) { if (st != null) set(UNIFORM_TEXTURE_B, st.texture.getTexture()); st = terrainTexture.getTexture(SplatTexture.Channel.A); if (st != null) set(UNIFORM_TEXTURE_A, st.texture.getTexture()); + + // Normal maps + if (hasNormals) { + setNormalTexture(terrainTexture, SplatTexture.Channel.R, UNIFORM_TEXTURE_R_NORMAL, UNIFORM_TEXTURE_R_NORMAL_PRESENT); + setNormalTexture(terrainTexture, SplatTexture.Channel.G, UNIFORM_TEXTURE_G_NORMAL, UNIFORM_TEXTURE_G_NORMAL_PRESENT); + setNormalTexture(terrainTexture, SplatTexture.Channel.B, UNIFORM_TEXTURE_B_NORMAL, UNIFORM_TEXTURE_B_NORMAL_PRESENT); + setNormalTexture(terrainTexture, SplatTexture.Channel.A, UNIFORM_TEXTURE_A_NORMAL, UNIFORM_TEXTURE_A_NORMAL_PRESENT); + } + } else { set(UNIFORM_TEXTURE_HAS_SPLATMAP, 0); } @@ -160,6 +195,16 @@ protected void setTerrainSplatTextures(Renderable renderable) { set(UNIFORM_TERRAIN_SIZE, terrainSize); } + public void setNormalTexture(TerrainTexture terrainTexture, SplatTexture.Channel channel, int textureUniform, int uniformPresent) { + SplatTexture st = terrainTexture.getNormalTexture(channel); + if (st != null) { + set(uniformPresent, 1); + set(textureUniform, st.texture.getTexture()); + } else { + set(uniformPresent, 0); + } + } + @Override public void end() { context.end(); diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.frag.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.frag.glsl index 4676d0db3..a4268ff41 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.frag.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.frag.glsl @@ -36,12 +36,15 @@ varying vec3 v_normal; // diffuse material uniform sampler2D u_diffuseTexture; +uniform sampler2D u_normalTexture; uniform int u_diffuseUseTexture; +uniform int u_useNormalMap; // enviroment uniform MED vec4 u_fogColor; varying float v_clipDistance; +varying mat3 v_TBN; void main(void) { if ( v_clipDistance < 0.0 ) @@ -56,16 +59,25 @@ void main(void) { gl_FragColor = vec4(u_material.DiffuseColor, 1.0); } - vec4 totalLight = CalcDirectionalLight(v_normal); + vec3 normal; + + if (u_useNormalMap == 1) { + normal = texture2D(u_normalTexture, v_texCoord0).rgb; + normal = normalize(v_TBN * ((2.0 * normal - 1.0))); + } else { + normal = v_normal; + } + + vec4 totalLight = CalcDirectionalLight(normal); for (int i = 0 ; i < numPointLights ; i++) { if (i >= u_activeNumPointLights){break;} - totalLight += CalcPointLight(u_pointLights[i], v_normal); + totalLight += CalcPointLight(u_pointLights[i], normal); } for (int i = 0 ; i < numSpotLights; i++) { if (i >= u_activeNumSpotLights){break;} - totalLight += CalcSpotLight(u_spotLights[i], v_normal); + totalLight += CalcSpotLight(u_spotLights[i], normal); } gl_FragColor = max(gl_FragColor, AMBIENT); // TODO make ambient color a unifrom diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.vert.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.vert.glsl index 7e53430f6..27ac29b51 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.vert.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/model.vert.glsl @@ -21,9 +21,11 @@ precision highp float; attribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord0; +attribute vec4 a_tangent; uniform mat4 u_transMatrix; uniform mat4 u_projViewMatrix; +uniform mat3 u_normalMatrix; uniform vec3 u_camPos; // Fog @@ -34,6 +36,8 @@ varying float v_fog; varying vec2 v_texCoord0; varying vec3 v_normal; varying vec3 v_worldPos; +varying mat3 v_TBN; + // clipping plane varying float v_clipDistance; @@ -55,6 +59,13 @@ void main(void) { v_normal = normalize((u_transMatrix * vec4(a_normal, 0.0)).xyz); v_worldPos = worldPos.xyz; + // Logic for Tangent/Bi-tangent/Normal from gdx-gltf + vec3 tangent = a_tangent.xyz; + vec3 normalW = normalize(vec3(u_normalMatrix * a_normal.xyz)); + vec3 tangentW = normalize(vec3(u_transMatrix * vec4(tangent, 0.0))); + vec3 bitangentW = cross(normalW, tangentW) * a_tangent.w; + v_TBN = mat3(tangentW, bitangentW, normalW); + // ================================================================= // /Lighting // ================================================================= diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.frag.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.frag.glsl index f40b67afa..33df25a3c 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.frag.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.frag.glsl @@ -43,6 +43,19 @@ uniform sampler2D u_texture_splat; uniform int u_texture_has_splatmap; uniform int u_texture_has_diffuse; +// splat normal texture +uniform int u_texture_has_normals; +uniform int u_texture_has_normal_base; +uniform int u_texture_has_normal_r; +uniform int u_texture_has_normal_g; +uniform int u_texture_has_normal_b; +uniform int u_texture_has_normal_a; +uniform sampler2D u_texture_base_normal; +uniform sampler2D u_texture_r_normal; +uniform sampler2D u_texture_g_normal; +uniform sampler2D u_texture_b_normal; +uniform sampler2D u_texture_a_normal; + // mouse picking #ifdef PICKER uniform vec3 u_pickerPos; @@ -55,22 +68,27 @@ uniform MED vec4 u_fogColor; // light varying vec3 v_normal; +varying mat3 v_TBN; varying MED vec2 v_texCoord0; varying float v_fog; varying vec2 splatPosition; - - varying float v_clipDistance; void main(void) { if ( v_clipDistance < 0.0 ) discard; + vec3 normal; + // blend textures if(u_texture_has_diffuse == 1) { gl_FragColor = texture2D(u_texture_base, v_texCoord0); + + if (u_texture_has_normal_base == 1) { + normal = texture2D(u_texture_base_normal, v_texCoord0).rgb; + } } if(u_texture_has_splatmap == 1) { vec4 splat = texture2D(u_texture_splat, splatPosition); @@ -78,21 +96,45 @@ void main(void) { gl_FragColor = mix(gl_FragColor, texture2D(u_texture_g, v_texCoord0), splat.g); gl_FragColor = mix(gl_FragColor, texture2D(u_texture_b, v_texCoord0), splat.b); gl_FragColor = mix(gl_FragColor, texture2D(u_texture_a, v_texCoord0), splat.a); + + // Mix in splat map normals + if (u_texture_has_normals == 1) { + + if (u_texture_has_normal_r == 1) { + normal = mix(normal, texture2D(u_texture_r_normal, v_texCoord0).rgb, splat.r); + } + if (u_texture_has_normal_g == 1) { + normal = mix(normal, texture2D(u_texture_g_normal, v_texCoord0).rgb, splat.g); + } + if (u_texture_has_normal_b == 1) { + normal = mix(normal, texture2D(u_texture_b_normal, v_texCoord0).rgb, splat.b); + } + if (u_texture_has_normal_a == 1) { + normal = mix(normal, texture2D(u_texture_a_normal, v_texCoord0).rgb, splat.a); + } + + } + } + + if (u_texture_has_normals == 1) { + normal = normalize(v_TBN * ((2.0 * normal - 1.0))); + } else { + normal = normalize(v_TBN[2].xyz); } // ================================================================= // Lighting // ================================================================= - vec4 totalLight = CalcDirectionalLight(v_normal); + vec4 totalLight = CalcDirectionalLight(normal); for (int i = 0 ; i < numPointLights ; i++) { if (i >= u_activeNumPointLights){break;} - totalLight += CalcPointLight(u_pointLights[i], v_normal); + totalLight += CalcPointLight(u_pointLights[i], normal); } for (int i = 0; i < numSpotLights; i++) { if (i >= u_activeNumSpotLights){break;} - totalLight += CalcSpotLight(u_spotLights[i], v_normal); + totalLight += CalcSpotLight(u_spotLights[i], normal); } gl_FragColor *= totalLight; diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.vert.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.vert.glsl index b30c9ccb9..96c58df52 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.vert.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.vert.glsl @@ -21,10 +21,12 @@ precision highp float; attribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord0; +attribute vec4 a_tangent; uniform mat4 u_transMatrix; uniform mat4 u_projViewMatrix; uniform vec3 u_camPos; +uniform mat3 u_normalMatrix; // Fog uniform float u_fogDensity; @@ -37,6 +39,7 @@ varying vec2 splatPosition; varying float v_fog; varying vec3 v_normal; varying vec3 v_worldPos; +varying mat3 v_TBN; #ifdef PICKER varying vec3 v_pos; @@ -54,6 +57,13 @@ void main(void) { // normal for lighting v_normal = normalize((u_transMatrix * vec4(a_normal, 0.0)).xyz); + // Logic for Tangent/Bi-tangent/Normal from gdx-gltf + vec3 tangent = a_tangent.xyz; + vec3 normalW = normalize(vec3(u_normalMatrix * a_normal.xyz)); + vec3 tangentW = normalize(vec3(u_transMatrix * vec4(tangent, 0.0))); + vec3 bitangentW = cross(normalW, tangentW) * a_tangent.w; + v_TBN = mat3(tangentW, bitangentW, normalW); + // clipping plane v_clipDistance = dot(worldPos, u_clipPlane); diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java index f62515f7d..fddfe4c98 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java @@ -18,6 +18,7 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Mesh; +import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes; import com.badlogic.gdx.graphics.g3d.Material; import com.badlogic.gdx.graphics.g3d.Model; @@ -25,9 +26,9 @@ import com.badlogic.gdx.graphics.g3d.Renderable; import com.badlogic.gdx.graphics.g3d.RenderableProvider; import com.badlogic.gdx.graphics.g3d.model.MeshPart; -import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder; import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder; import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -36,6 +37,7 @@ import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Pool; import com.mbrlabs.mundus.commons.utils.MathUtils; +import net.mgsx.gltf.loaders.shared.geometry.MeshTangentSpaceGenerator; /** * @author Marcus Brummer @@ -60,13 +62,13 @@ public class Terrain implements RenderableProvider, Disposable { public int vertexResolution; // used for building the mesh - private VertexAttributes attribs; + private final VertexAttributes attribs; private Vector2 uvScale = new Vector2(DEFAULT_UV_SCALE, DEFAULT_UV_SCALE); - private float vertices[]; - private int stride; - private int posPos; - private int norPos; - private int uvPos; + private float[] vertices; + private final int stride; + private final int posPos; + private final int norPos; + private final int uvPos; // Textures private TerrainTexture terrainTexture; @@ -79,8 +81,13 @@ public class Terrain implements RenderableProvider, Disposable { private Terrain(int vertexResolution) { this.transform = new Matrix4(); - this.attribs = MeshBuilder.createAttributes(VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal - | VertexAttributes.Usage.TextureCoordinates); + this.attribs = new VertexAttributes( + VertexAttribute.Position(), + VertexAttribute.Normal(), + new VertexAttribute(VertexAttributes.Usage.Tangent, 4, ShaderProgram.TANGENT_ATTRIBUTE), + VertexAttribute.TexCoords(0) + ); + this.posPos = attribs.getOffset(VertexAttributes.Usage.Position, -1); this.norPos = attribs.getOffset(VertexAttributes.Usage.Normal, -1); this.uvPos = attribs.getOffset(VertexAttributes.Usage.TextureCoordinates, -1); @@ -188,7 +195,7 @@ public Vector3 getRayIntersection(Vector3 out, Ray ray) { private short[] buildIndices() { final int w = vertexResolution - 1; final int h = vertexResolution - 1; - short indices[] = new short[w * h * 6]; + short[] indices = new short[w * h * 6]; int i = -1; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { @@ -350,6 +357,16 @@ public void setTerrainTexture(TerrainTexture terrainTexture) { public void update() { buildVertices(); + + VertexAttribute normalMapUVs = null; + for(VertexAttribute a : attribs){ + if(a.usage == VertexAttributes.Usage.TextureCoordinates){ + normalMapUVs = a; + } + } + // Get tangents added to terrains vertices array for normal mapping + MeshTangentSpaceGenerator.computeTangentSpace(vertices, buildIndices(), attribs, false, true, normalMapUVs); + mesh.setVertices(vertices); } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java index ac39feec8..81cdcd3a2 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java @@ -25,18 +25,24 @@ */ public class TerrainTexture { - private Map textures; + private final Map textures; + private final Map normalTextures; private SplatMap splatmap; private Terrain terrain; public TerrainTexture() { - textures = new HashMap(5, 1); + textures = new HashMap<>(5, 1); + normalTextures = new HashMap<>(5, 1); } public SplatTexture getTexture(SplatTexture.Channel channel) { return textures.get(channel); } + public SplatTexture getNormalTexture(SplatTexture.Channel channel) { + return normalTextures.get(channel); + } + public void removeTexture(SplatTexture.Channel channel) { if (splatmap != null) { textures.remove(channel); @@ -45,10 +51,18 @@ public void removeTexture(SplatTexture.Channel channel) { } } + public void removeNormalTexture(SplatTexture.Channel channel) { + normalTextures.remove(channel); + } + public void setSplatTexture(SplatTexture tex) { textures.put(tex.channel, tex); } + public void setSplatNormalTexture(SplatTexture tex) { + normalTextures.put(tex.channel, tex); + } + public SplatTexture.Channel getNextFreeChannel() { // base SplatTexture st = textures.get(SplatTexture.Channel.BASE); @@ -97,4 +111,8 @@ public void setTerrain(Terrain terrain) { this.terrain = terrain; } + public boolean hasNormalTextures() { + return normalTextures.size() > 0; + } + } diff --git a/editor/CHANGES b/editor/CHANGES index 7a1e98891..16a7915d1 100644 --- a/editor/CHANGES +++ b/editor/CHANGES @@ -3,6 +3,7 @@ - Added new Light Component and accompanying Light Shader which prefixes shaders with additional shader lighting code. - Added new Add Component Dialog which only supports Light Components for now. - Added new Gizmo feature for displaying decal Icons over objects like Light sources. +- Added normal mapping for terrains and models - Update water shader to fade out water foam over distance, to minimize ugly 1 pixel white lines from a distance. [0.3.1] ~ 07/06/2022 diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt index 9dbf83bf1..4c1951e5a 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt @@ -82,6 +82,11 @@ class MetaSaver { if (terrain.splatG != null) json.writeValue(MetaTerrain.JSON_SPLAT_G, terrain.splatG) if (terrain.splatB != null) json.writeValue(MetaTerrain.JSON_SPLAT_B, terrain.splatB) if (terrain.splatA != null) json.writeValue(MetaTerrain.JSON_SPLAT_A, terrain.splatA) + if (terrain.splatBaseNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_BASE_NORMAL, terrain.splatBaseNormal) + if (terrain.splatRNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_R_NORMAL, terrain.splatRNormal) + if (terrain.splatGNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_G_NORMAL, terrain.splatGNormal) + if (terrain.splatBNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_B_NORMAL, terrain.splatBNormal) + if (terrain.splatANormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_A_NORMAL, terrain.splatANormal) json.writeObjectEnd() } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt index d5e42d7aa..f14350f4f 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt @@ -211,12 +211,15 @@ class TerrainPaintTab(private val parentWidget: TerrainComponentWidget) : Tab(fa private val removeTexture = MenuItem("Remove texture") private val changeTexture = MenuItem("Change texture") + private val addNormalMap = MenuItem("Add Normal Map") + private val removeNormalMap = MenuItem("Remove Normal Map") private var channel: SplatTexture.Channel? = null init { addItem(removeTexture) addItem(changeTexture) + addItem(addNormalMap) removeTexture.addListener(object : ClickListener() { override fun clicked(event: InputEvent?, x: Float, y: Float) { @@ -224,12 +227,16 @@ class TerrainPaintTab(private val parentWidget: TerrainComponentWidget) : Tab(fa val terrain = parentWidget.component.terrain if (channel == SplatTexture.Channel.R) { terrain.splatR = null + terrain.splatRNormal = null } else if (channel == SplatTexture.Channel.G) { terrain.splatG = null + terrain.splatGNormal = null } else if (channel == SplatTexture.Channel.B) { terrain.splatB = null + terrain.splatBNormal = null } else if (channel == SplatTexture.Channel.A) { terrain.splatA = null + terrain.splatANormal = null } else { UI.toaster.error("Can't remove the base texture") return @@ -272,6 +279,59 @@ class TerrainPaintTab(private val parentWidget: TerrainComponentWidget) : Tab(fa } }) + addNormalMap.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + if (channel != null) { + + UI.assetSelectionDialog.show(false, AssetTextureFilter(), object: AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + if (channel != null) { + val terrain = parentWidget.component.terrain + if (channel == SplatTexture.Channel.BASE) { + terrain.splatBaseNormal = asset as TextureAsset + } else if (channel == SplatTexture.Channel.R) { + terrain.splatRNormal = asset as TextureAsset + } else if (channel == SplatTexture.Channel.G) { + terrain.splatGNormal = asset as TextureAsset + } else if (channel == SplatTexture.Channel.B) { + terrain.splatBNormal = asset as TextureAsset + } else if (channel == SplatTexture.Channel.A) { + terrain.splatANormal = asset as TextureAsset + } + + terrain.applyDependencies() + projectManager.current().assetManager.addModifiedAsset(terrain) + } + } + }) + + } + } + }) + + removeNormalMap.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + if (channel != null) { + val terrain = parentWidget.component.terrain + if (channel == SplatTexture.Channel.BASE) { + terrain.splatBaseNormal = null + } + if (channel == SplatTexture.Channel.R) { + terrain.splatRNormal = null + } else if (channel == SplatTexture.Channel.G) { + terrain.splatGNormal = null + } else if (channel == SplatTexture.Channel.B) { + terrain.splatBNormal = null + } else if (channel == SplatTexture.Channel.A) { + terrain.splatANormal = null + } + + terrain.applyDependencies() + projectManager.current().assetManager.addModifiedAsset(terrain) + } + } + }) + } fun setChannel(channel: SplatTexture.Channel) { @@ -279,9 +339,34 @@ class TerrainPaintTab(private val parentWidget: TerrainComponentWidget) : Tab(fa } fun show() { + updateMenuVisibility() showMenu(UI, Gdx.input.x.toFloat(), (Gdx.graphics.height - Gdx.input.y).toFloat()) } + private fun updateMenuVisibility() { + // Show/Hide remove normal map button conditionally + var normalMapRemoveVisible = false + val terrain = parentWidget.component.terrain + if (channel == SplatTexture.Channel.BASE && terrain.splatBaseNormal != null) { + normalMapRemoveVisible = true + } else if (channel == SplatTexture.Channel.R && terrain.splatRNormal != null) { + normalMapRemoveVisible = true + } else if (channel == SplatTexture.Channel.G && terrain.splatGNormal != null) { + normalMapRemoveVisible = true + } else if (channel == SplatTexture.Channel.B && terrain.splatBNormal != null) { + normalMapRemoveVisible = true + } else if (channel == SplatTexture.Channel.A && terrain.splatANormal != null) { + normalMapRemoveVisible = true + } + + if (normalMapRemoveVisible && !removeNormalMap.hasParent()) { + addItem(removeNormalMap) + } else if (!normalMapRemoveVisible) { + removeNormalMap.remove() + } + pack() + } + } } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/MaterialWidget.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/MaterialWidget.kt index 1118b92c4..8f624738c 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/MaterialWidget.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/MaterialWidget.kt @@ -60,6 +60,7 @@ class MaterialWidget : VisTable() { private val matNameLabel: VisLabel = VisLabel() private val diffuseColorField: ColorPickerField = ColorPickerField() private val diffuseAssetField: AssetSelectionField = AssetSelectionField() + private val normalMapField: AssetSelectionField = AssetSelectionField() private val shininessField = VisTextField() private val projectManager: ProjectManager = Mundus.inject() @@ -73,6 +74,7 @@ class MaterialWidget : VisTable() { field = value diffuseColorField.selectedColor = value.diffuseColor diffuseAssetField.setAsset(value.diffuseTexture) + normalMapField.setAsset(value.normalMap) matNameLabel.setText(value.name) shininessField.text = value.shininess.toString() } @@ -112,6 +114,8 @@ class MaterialWidget : VisTable() { add(VisLabel("Diffuse texture")).grow().row() add(diffuseAssetField).growX().row() + add(VisLabel("Normal map")).grow().row() + add(normalMapField).growX().row() add(VisLabel("Diffuse color")).grow().row() add(diffuseColorField).growX().row() add(VisLabel("Shininess")).growX().row() @@ -134,6 +138,17 @@ class MaterialWidget : VisTable() { } } + // normal texture + normalMapField.assetFilter = AssetTextureFilter() + normalMapField.pickerListener = object: AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + material?.normalMap = asset as? TextureAsset + applyMaterialToModelAssets() + applyMaterialToModelComponents() + projectManager.current().assetManager.addModifiedAsset(material!!) + } + } + // diffuse color diffuseColorField.colorAdapter = object: ColorPickerAdapter() { override fun finished(newColor: Color) {