From c55717141e5de9a0939dc6171784bf642c0638dd Mon Sep 17 00:00:00 2001 From: Nehon Date: Tue, 4 Nov 2014 20:09:14 +0100 Subject: [PATCH 01/13] Single pass lighting implementation. Along with some light shaders refactoring and clean up --- .../main/java/com/jme3/material/Material.java | 151 +++-- .../java/com/jme3/material/Technique.java | 11 +- .../java/com/jme3/renderer/RenderManager.java | 30 + .../Common/MatDefs/Light/Lighting.frag | 132 +--- .../Common/MatDefs/Light/Lighting.j3md | 81 ++- .../Common/MatDefs/Light/Lighting.vert | 113 +--- .../Common/MatDefs/Light/SPLighting.frag | 218 +++++++ .../Common/MatDefs/Light/SPLighting.vert | 172 +++++ .../Common/ShaderLib/Lighting.glsllib | 62 +- .../Common/ShaderLib/PhongLighting.glsllib | 29 + .../jme3test/light/TestManyLightsSingle.java | 255 ++++++++ .../MatDefs/Terrain/SPTerrainLighting.frag | 615 ++++++++++++++++++ .../MatDefs/Terrain/SPTerrainLighting.vert | 66 ++ .../MatDefs/Terrain/TerrainLighting.frag | 56 +- .../MatDefs/Terrain/TerrainLighting.j3md | 64 ++ .../MatDefs/Terrain/TerrainLighting.vert | 49 +- 16 files changed, 1696 insertions(+), 408 deletions(-) create mode 100644 jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag create mode 100644 jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert create mode 100644 jme3-core/src/main/resources/Common/ShaderLib/PhongLighting.glsllib create mode 100644 jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java create mode 100644 jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag create mode 100644 jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 20fcd1e9a..489139ef5 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -42,7 +42,6 @@ import com.jme3.material.TechniqueDef.LightMode; import com.jme3.material.TechniqueDef.ShadowMode; import com.jme3.math.*; import com.jme3.renderer.Caps; -import com.jme3.renderer.GL1Renderer; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.RendererException; @@ -52,7 +51,6 @@ import com.jme3.scene.Mesh; import com.jme3.scene.instancing.InstancedGeometry; import com.jme3.shader.Shader; import com.jme3.shader.Uniform; -import com.jme3.shader.UniformBindingManager; import com.jme3.shader.VarType; import com.jme3.texture.Texture; import com.jme3.texture.image.ColorSpace; @@ -697,12 +695,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { setParam(name, VarType.Vector4, value); } - private ColorRGBA getAmbientColor(LightList lightList) { + private ColorRGBA getAmbientColor(LightList lightList, boolean removeLights) { ambientLightColor.set(0, 0, 0, 1); for (int j = 0; j < lightList.size(); j++) { Light l = lightList.get(j); if (l instanceof AmbientLight) { ambientLightColor.addLocal(l.getColor()); + if(removeLights){ + lightList.remove(l); + } } } ambientLightColor.a = 1.0f; @@ -741,75 +742,106 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * g_LightPosition.w is the inverse radius (1/r) of the light (for * attenuation)

*/ - protected void updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights) { + protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) { if (numLights == 0) { // this shader does not do lighting, ignore. - return; + return 0; } - Uniform lightColor = shader.getUniform("g_LightColor"); - Uniform lightPos = shader.getUniform("g_LightPosition"); - Uniform lightDir = shader.getUniform("g_LightDirection"); - lightColor.setVector4Length(numLights); - lightPos.setVector4Length(numLights); - lightDir.setVector4Length(numLights); - + Uniform lightData = shader.getUniform("g_LightData"); + lightData.setVector4Length(numLights * 3);//8 lights * max 3 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); - - int lightIndex = 0; + - for (int i = 0; i < numLights; i++) { - if (lightList.size() <= i) { - lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); - lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); - } else { - Light l = lightList.get(i); + if (startIndex != 0) { + // apply additive blending for 2nd and future passes + rm.getRenderer().applyRenderState(additiveLight); + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + }else{ + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList,true)); + } + + int lightDataIndex = 0; + TempVars vars = TempVars.get(); + Vector4f tmpVec = vars.vect4f1; + int curIndex; + int endIndex = numLights + startIndex; + for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { + + + Light l = lightList.get(curIndex); + if(l.getType() == Light.Type.Ambient){ + endIndex++; + continue; + } ColorRGBA color = l.getColor(); - lightColor.setVector4InArray(color.getRed(), + //Color + lightData.setVector4InArray(color.getRed(), color.getGreen(), color.getBlue(), l.getType().getId(), - i); - + lightDataIndex); + lightDataIndex++; + switch (l.getType()) { case Directional: DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); - lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex); + Vector3f dir = dl.getDirection(); + //Data directly sent in view space to avoid a matrix mult for each pixel + tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); +// tmpVec.divideLocal(tmpVec.w); +// tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; break; case Point: PointLight pl = (PointLight) l; Vector3f pos = pl.getPosition(); float invRadius = pl.getInvRadius(); - lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex); + tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + //tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; break; - case Spot: + case Spot: SpotLight sl = (SpotLight) l; Vector3f pos2 = sl.getPosition(); Vector3f dir2 = sl.getDirection(); float invRange = sl.getInvSpotRange(); float spotAngleCos = sl.getPackedAngleCos(); - - lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex); - lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex); - break; - case Ambient: - // skip this light. Does not increase lightIndex - continue; + tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + // tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); + lightDataIndex++; + + //We transform the spot direction in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happens now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); + lightDataIndex++; + break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); } - } - - lightIndex++; - } - - while (lightIndex < numLights) { - lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); - lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); - - lightIndex++; } + vars.release(); + //Padding of unsued buffer space + while(lightDataIndex < numLights * 3) { + lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); + lightDataIndex++; + } + return curIndex; } protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) { @@ -830,7 +862,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (isFirstLight) { // set ambient color for first light only - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false)); isFirstLight = false; isSecondLight = true; } else if (isSecondLight) { @@ -885,9 +917,9 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); lightPos.setValue(VarType.Vector4, tmpLightPosition); - //We transform the spot directoin in view space here to save 5 varying later in the lighting shader + //We transform the spot direction in view space here to save 5 varying later in the lighting shader //one vec4 less and a vec4 that becomes a vec3 - //the downside is that spotAngleCos decoding happen now in the frag shader. + //the downside is that spotAngleCos decoding happens now in the frag shader. tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); @@ -906,7 +938,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (isFirstLight && lightList.size() > 0) { // There are only ambient lights in the scene. Render // a dummy "normal light" so we can see the ambient - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false)); lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); lightPos.setValue(VarType.Vector4, nullDirLight); r.setShader(shader); @@ -955,9 +987,12 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { for (TechniqueDef techDef : techDefs) { if (rendererCaps.containsAll(techDef.getRequiredCaps())) { // use the first one that supports all the caps - tech = new Technique(this, techDef); + tech = new Technique(this, techDef); techniques.put(name, tech); - break; + if(tech.getDef().getLightMode() == renderManager.getPreferredLightMode() || + tech.getDef().getLightMode() == LightMode.Disable){ + break; + } } lastTech = techDef; } @@ -990,7 +1025,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } technique = tech; - tech.makeCurrent(def.getAssetManager(), true, rendererCaps); + tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager); // shader was changed sortingId = -1; @@ -1000,7 +1035,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (technique == null) { selectTechnique("Default", rm); } else { - technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps()); + technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm); } } @@ -1162,8 +1197,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { r.setLighting(null); break; case SinglePass: - updateLightListUniforms(shader, geom, lights, 4); - break; + int nbRenderedLights = 0; + resetUniformsNotSetByCurrent(shader); + while(nbRenderedLights < lights.size()){ + nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights); + r.setShader(shader); + renderMeshFromGeometry(r, geom); + } + return; case FixedPipeline: r.setLighting(lights); break; diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index b99cbc3bb..024311551 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -33,8 +33,8 @@ package com.jme3.material; import com.jme3.asset.AssetManager; import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; import com.jme3.shader.*; -import com.jme3.util.ListMap; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -172,7 +172,7 @@ public class Technique /* implements Savable */ { * * @param assetManager The asset manager to use for loading shaders. */ - public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps) { + public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps, RenderManager rm) { if (!def.isUsingShaders()) { // No shaders are used, no processing is neccessary. return; @@ -182,6 +182,13 @@ public class Technique /* implements Savable */ { if (defines.update(owner.getParamsMap(), def)) { needReload = true; } + if(getDef().getLightMode()== TechniqueDef.LightMode.SinglePass){ + defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, true); + defines.set("NB_LIGHTS", VarType.Int, rm.getSinglePassLightBatchSize()*3 ); + }else{ + defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, null); + } + } if (needReload) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index b8aa705fb..592ecf3cf 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -38,6 +38,7 @@ import com.jme3.material.Material; import com.jme3.material.MaterialDef; import com.jme3.material.RenderState; import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; import com.jme3.math.*; import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; @@ -89,6 +90,8 @@ public class RenderManager { private boolean handleTranlucentBucket = true; private AppProfiler prof; private LightFilter lightFilter = new DefaultLightFilter(); + private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass; + private int singlePassLightBatchSize = 1; /** * Create a high-level rendering interface over the @@ -780,6 +783,33 @@ public class RenderManager { vp.getQueue().clear(); } + /** + * Sets the light filter to use when rendering Lighted Geometries + * + * @see LightFilter + * @param lightFilter The light filter tose. Set it to null if you want all lights to be rendered + */ + public void setLightFilter(LightFilter lightFilter) { + this.lightFilter = lightFilter; + } + + public void setPreferredLightMode(TechniqueDef.LightMode preferredLightMode) { + this.preferredLightMode = preferredLightMode; + } + + public TechniqueDef.LightMode getPreferredLightMode() { + return preferredLightMode; + } + + public int getSinglePassLightBatchSize() { + return singlePassLightBatchSize; + } + + public void setSinglePassLightBatchSize(int singlePassLightBatchSize) { + this.singlePassLightBatchSize = singlePassLightBatchSize; + } + + /** * Render the given viewport queues. *

diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag index e4773c8cf..370c62bc9 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag @@ -1,7 +1,9 @@ #import "Common/ShaderLib/Parallax.glsllib" #import "Common/ShaderLib/Optics.glsllib" -#define ATTENUATION -//#define HQ_ATTENUATION +#ifndef VERTEX_LIGHTING + #import "Common/ShaderLib/PhongLighting.glsllib" + #import "Common/ShaderLib/Lighting.glsllib" +#endif varying vec2 texCoord; #ifdef SEPARATE_TEXCOORD @@ -58,82 +60,14 @@ varying vec3 SpecularSum; uniform float m_AlphaDiscardThreshold; #ifndef VERTEX_LIGHTING -uniform float m_Shininess; - -#ifdef HQ_ATTENUATION -uniform vec4 g_LightPosition; -#endif - -#ifdef USE_REFLECTION - uniform float m_ReflectionPower; - uniform float m_ReflectionIntensity; - varying vec4 refVec; - - uniform ENVMAP m_EnvMap; -#endif - -float tangDot(in vec3 v1, in vec3 v2){ - float d = dot(v1,v2); - #ifdef V_TANGENT - d = 1.0 - d*d; - return step(0.0, d) * sqrt(d); - #else - return d; - #endif -} - -float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){ - #ifdef MINNAERT - float NdotL = max(0.0, dot(norm, lightdir)); - float NdotV = max(0.0, dot(norm, viewdir)); - return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5; - #else - return max(0.0, dot(norm, lightdir)); - #endif -} + uniform float m_Shininess; + #ifdef USE_REFLECTION + uniform float m_ReflectionPower; + uniform float m_ReflectionIntensity; + varying vec4 refVec; -float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){ - // NOTE: check for shiny <= 1 removed since shininess is now - // 1.0 by default (uses matdefs default vals) - #ifdef LOW_QUALITY - // Blinn-Phong - // Note: preferably, H should be computed in the vertex shader - vec3 H = (viewdir + lightdir) * vec3(0.5); - return pow(max(tangDot(H, norm), 0.0), shiny); - #elif defined(WARDISO) - // Isotropic Ward - vec3 halfVec = normalize(viewdir + lightdir); - float NdotH = max(0.001, tangDot(norm, halfVec)); - float NdotV = max(0.001, tangDot(norm, viewdir)); - float NdotL = max(0.001, tangDot(norm, lightdir)); - float a = tan(acos(NdotH)); - float p = max(shiny/128.0, 0.001); - return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL))); - #else - // Standard Phong - vec3 R = reflect(-lightdir, norm); - return pow(max(tangDot(R, viewdir), 0.0), shiny); + uniform ENVMAP m_EnvMap; #endif -} - -vec2 computeLighting(in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){ - float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir); - float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess); - - #ifdef HQ_ATTENUATION - float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0); - #else - float att = vLightDir.w; - #endif - - if (m_Shininess <= 1.0) { - specularFactor = 0.0; // should be one instruction on most cards .. - } - - specularFactor *= diffuseFactor; - - return vec2(diffuseFactor, specularFactor) * vec2(att); -} #endif void main(){ @@ -176,36 +110,7 @@ void main(){ discard; } - #ifndef VERTEX_LIGHTING - float spotFallOff = 1.0; - - #if __VERSION__ >= 110 - // allow use of control flow - if(g_LightDirection.w != 0.0){ - #endif - - 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 __VERSION__ >= 110 - if(spotFallOff <= 0.0){ - gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; - gl_FragColor.a = alpha; - return; - }else{ - spotFallOff = clamp(spotFallOff, 0.0, 1.0); - } - } - #else - spotFallOff = clamp(spotFallOff, step(g_LightDirection.w, 0.001), 1.0); - #endif - #endif - // *********************** // Read from textures // *********************** @@ -257,8 +162,23 @@ void main(){ vec4 lightDir = vLightDir; lightDir.xyz = normalize(lightDir.xyz); vec3 viewDir = normalize(vViewDir); + float spotFallOff = 1.0; + + #if __VERSION__ >= 110 + // allow use of control flow + if(g_LightDirection.w != 0.0){ + #endif + spotFallOff = computeSpotFalloff(g_LightDirection, lightVec); + #if __VERSION__ >= 110 + if(spotFallOff <= 0.0){ + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; + gl_FragColor.a = alpha; + return; + } + } + #endif - vec2 light = computeLighting(normal, viewDir, lightDir.xyz) * spotFallOff; + vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess) ; #ifdef COLORRAMP diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 779242445..e5613f040 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -6,34 +6,12 @@ MaterialDef Phong Lighting { // For better performance Boolean VertexLighting - // Use more efficent algorithms to improve performance - Boolean LowQuality - - // Improve quality at the cost of performance - Boolean HighQuality - - // Output alpha from the diffuse map - Boolean UseAlpha - // Alpha threshold for fragment discarding Float AlphaDiscardThreshold (AlphaTestFallOff) - // Normal map is in BC5/ATI2n/LATC/3Dc compression format - Boolean LATC - // Use the provided ambient, diffuse, and specular colors Boolean UseMaterialColors - // Activate shading along the tangent, instead of the normal - // Requires tangent data to be available on the model. - Boolean VTangent - - // Use minnaert diffuse instead of lambert - Boolean Minnaert - - // Use ward specular instead of phong - Boolean WardIso - // Use vertex color as an additional diffuse color. Boolean UseVertexColor @@ -133,9 +111,48 @@ MaterialDef Phong Lighting { Int NumberOfBones Matrix4Array BoneMatrices + //For instancing Boolean UseInstancing } + Technique { + LightMode SinglePass + + VertexShader GLSL100: Common/MatDefs/Light/SPLighting.vert + FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag + + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + WorldViewMatrix + ViewMatrix + CameraPosition + WorldMatrix + ViewProjectionMatrix + } + + Defines { + VERTEX_COLOR : UseVertexColor + VERTEX_LIGHTING : VertexLighting + MATERIAL_COLORS : UseMaterialColors + DIFFUSEMAP : DiffuseMap + NORMALMAP : NormalMap + SPECULARMAP : SpecularMap + PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax + ALPHAMAP : AlphaMap + COLORRAMP : ColorRamp + LIGHTMAP : LightMap + SEPARATE_TEXCOORD : SeparateTexCoord + DISCARD_ALPHA : AlphaDiscardThreshold + USE_REFLECTION : EnvMap + SPHERE_MAP : SphereMap + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + } + } + Technique { LightMode MultiPass @@ -154,17 +171,9 @@ MaterialDef Phong Lighting { } Defines { - LATC : LATC VERTEX_COLOR : UseVertexColor - VERTEX_LIGHTING : VertexLighting - ATTENUATION : Attenuation + VERTEX_LIGHTING : VertexLighting MATERIAL_COLORS : UseMaterialColors - V_TANGENT : VTangent - MINNAERT : Minnaert - WARDISO : WardIso - LOW_QUALITY : LowQuality - HQ_ATTENUATION : HighQuality - DIFFUSEMAP : DiffuseMap NORMALMAP : NormalMap SPECULARMAP : SpecularMap @@ -175,16 +184,16 @@ MaterialDef Phong Lighting { COLORRAMP : ColorRamp LIGHTMAP : LightMap SEPARATE_TEXCOORD : SeparateTexCoord - + DISCARD_ALPHA : AlphaDiscardThreshold USE_REFLECTION : EnvMap SPHERE_MAP : SphereMap - - NUM_BONES : NumberOfBones - + NUM_BONES : NumberOfBones INSTANCING : UseInstancing } } + + Technique PreShadow { VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert @@ -373,4 +382,4 @@ MaterialDef Phong Lighting { } } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index 737786fb4..1901ebc67 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -1,8 +1,10 @@ #import "Common/ShaderLib/Instancing.glsllib" -#define ATTENUATION -//#define HQ_ATTENUATION - #import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#ifdef VERTEX_LIGHTING + #import "Common/ShaderLib/PhongLighting.glsllib" +#endif + uniform vec4 m_Ambient; uniform vec4 m_Diffuse; @@ -28,7 +30,6 @@ attribute vec2 inTexCoord; attribute vec3 inNormal; varying vec3 lightVec; -//varying vec4 spotVec; #ifdef VERTEX_COLOR attribute vec4 inColor; @@ -39,8 +40,7 @@ varying vec3 lightVec; #ifndef NORMALMAP varying vec3 vNormal; - #endif - //varying vec3 vPosition; + #endif varying vec3 vViewDir; varying vec4 vLightDir; #else @@ -77,57 +77,6 @@ varying vec3 lightVec; } #endif -// 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 = tempVec; - #ifdef ATTENUATION - float dist = length(tempVec); - lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0); - lightDir.xyz = tempVec / vec3(dist); - #else - lightDir = vec4(normalize(tempVec), 1.0); - #endif -} - -#ifdef VERTEX_LIGHTING - float lightComputeDiffuse(in vec3 norm, in vec3 lightdir){ - 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; - } - #ifndef LOW_QUALITY - vec3 H = (viewdir + lightdir) * vec3(0.5); - return pow(max(dot(H, norm), 0.0), shiny); - #else - return 0.0; - #endif - } - -vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec4 wvLightPos){ - vec4 lightDir; - lightComputeDir(wvPos, g_LightColor, wvLightPos, lightDir); - 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 = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0); - } - float diffuseFactor = lightComputeDiffuse(wvNorm, lightDir.xyz); - float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, lightDir.xyz, m_Shininess); - //specularFactor *= step(0.01, diffuseFactor); - return vec2(diffuseFactor, specularFactor) * vec2(lightDir.w)*spotFallOff; - } -#endif - void main(){ vec4 modelSpacePos = vec4(inPosition, 1.0); vec3 modelSpaceNorm = inNormal; @@ -154,11 +103,6 @@ void main(){ vec3 wvNormal = normalize(TransformNormal(modelSpaceNorm));//normalize(g_NormalMatrix * modelSpaceNorm); vec3 viewDir = normalize(-wvPosition); - //vec4 lightColor = g_LightColor[gl_InstanceID]; - //vec4 lightPos = g_LightPosition[gl_InstanceID]; - //vec4 wvLightPos = (g_ViewMatrix * vec4(lightPos.xyz, lightColor.w)); - //wvLightPos.w = lightPos.w; - 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; @@ -166,41 +110,24 @@ void main(){ #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) vec3 wvTangent = normalize(TransformNormal(modelSpaceTan)); vec3 wvBinormal = cross(wvNormal, wvTangent); - mat3 tbnMat = mat3(wvTangent, wvBinormal * inTangent.w,wvNormal); - - //vPosition = wvPosition * tbnMat; - //vViewDir = viewDir * tbnMat; + vViewDir = -wvPosition * tbnMat; - lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; #elif !defined(VERTEX_LIGHTING) vNormal = wvNormal; - - //vPosition = wvPosition; vViewDir = viewDir; - - lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); - - #ifdef V_TANGENT - vNormal = normalize(TransformNormal(inTangent.xyz)); - vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal); - #endif + lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); #endif - //computing spot direction in view space and unpacking spotlight cos -// spotVec = (g_ViewMatrix * vec4(g_LightDirection.xyz, 0.0) ); -// spotVec.w = floor(g_LightDirection.w) * 0.001; -// lightVec.w = fract(g_LightDirection.w); - - lightColor.w = 1.0; #ifdef MATERIAL_COLORS AmbientSum = (m_Ambient * g_AmbientLightColor).rgb; - DiffuseSum = m_Diffuse * lightColor; + DiffuseSum = m_Diffuse * vec4(lightColor.rgb, 1.0); SpecularSum = (m_Specular * lightColor).rgb; #else - AmbientSum = vec3(0.2, 0.2, 0.2) * g_AmbientLightColor.rgb; // Default: ambient color is dark gray - DiffuseSum = lightColor; + AmbientSum = g_AmbientLightColor.rgb; // Default: ambient color is dark gray + DiffuseSum = vec4(lightColor.rgb, 1.0); SpecularSum = vec3(0.0); #endif @@ -210,10 +137,22 @@ void main(){ #endif #ifdef VERTEX_LIGHTING - vertexLightValues = computeLighting(wvPosition, wvNormal, viewDir, wvLightPos); + float spotFallOff = 1.0; + vec4 vLightDir; + lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + spotFallOff = computeSpotFalloff(g_LightDirection, lightVec); + #if __VERSION__ >= 110 + } + #endif + + vertexLightValues = computeLighting(wvNormal, viewDir, vLightDir.xyz, vLightDir.w * spotFallOff, m_Shininess); #endif - #ifdef USE_REFLECTION + #ifdef USE_REFLECTION computeRef(modelSpacePos); #endif } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag new file mode 100644 index 000000000..09478c6e8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -0,0 +1,218 @@ +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Optics.glsllib" +#ifndef VERTEX_LIGHTING + #import "Common/ShaderLib/PhongLighting.glsllib" + #import "Common/ShaderLib/Lighting.glsllib" +#endif + +varying vec2 texCoord; +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +#ifndef VERTEX_LIGHTING + uniform mat4 g_ViewMatrix; + uniform vec4 g_LightData[NB_LIGHTS]; + varying vec3 vPos; +#else + varying vec3 specularAccum; + varying vec4 diffuseAccum; +#endif + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif + +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + +#ifdef PARALLAXMAP + uniform sampler2D m_ParallaxMap; +#endif +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + uniform float m_ParallaxHeight; +#endif + +#ifdef LIGHTMAP + uniform sampler2D m_LightMap; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; + varying vec3 vTangent; + varying vec3 vBinormal; +#endif +varying vec3 vNormal; + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif + +#ifdef COLORRAMP + uniform sampler2D m_ColorRamp; +#endif + +uniform float m_AlphaDiscardThreshold; + +#ifndef VERTEX_LIGHTING +uniform float m_Shininess; + + #ifdef USE_REFLECTION + uniform float m_ReflectionPower; + uniform float m_ReflectionIntensity; + varying vec4 refVec; + + uniform ENVMAP m_EnvMap; + #endif +#endif + +void main(){ + vec2 newTexCoord; + + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + + #ifdef STEEP_PARALLAX + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #else + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #endif + #else + newTexCoord = texCoord; + #endif + + #ifdef DIFFUSEMAP + vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord); + #else + vec4 diffuseColor = vec4(1.0); + #endif + + float alpha = DiffuseSum.a * diffuseColor.a; + + #ifdef ALPHAMAP + alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r; + #endif + + #ifdef DISCARD_ALPHA + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + // *********************** + // Read from textures + // *********************** + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); + //Note the -2.0 and -1.0. We invert the green channel of the normal map, + //as it's complient with normal maps generated with blender. + //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 + //for more explanation. + vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0))); + #elif !defined(VERTEX_LIGHTING) + vec3 normal = normalize(vNormal); + #endif + + #ifdef SPECULARMAP + vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); + #else + vec4 specularColor = vec4(1.0); + #endif + + #ifdef LIGHTMAP + vec3 lightMapColor; + #ifdef SEPARATE_TEXCOORD + lightMapColor = texture2D(m_LightMap, texCoord2).rgb; + #else + lightMapColor = texture2D(m_LightMap, texCoord).rgb; + #endif + specularColor.rgb *= lightMapColor; + diffuseColor.rgb *= lightMapColor; + #endif + + #ifdef VERTEX_LIGHTING + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb + +diffuseAccum.rgb *diffuseColor.rgb + +specularAccum.rgb * specularColor.rgb; + gl_FragColor.a=1.0; + #else + + int i = 0; + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; + + #ifdef USE_REFLECTION + vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz); + #endif + + #ifdef NORMALMAP + mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); + #endif + + for( int i = 0;i < NB_LIGHTS; i+=3){ + vec4 lightColor = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir,lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + spotFallOff = computeSpotFalloff(g_LightData[i+2], lightVec); + #if __VERSION__ >= 110 + } + #endif + + #ifdef NORMALMAP + //Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + vec3 viewDir = normalize(-vPos.xyz * tbnMat); + #else + //no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + vec3 viewDir = normalize(-vPos.xyz); + #endif + + vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff , m_Shininess); + + #ifdef COLORRAMP + diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; + specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; + #endif + + // Workaround, since it is not possible to modify varying variables + vec4 SpecularSum2 = vec4(SpecularSum, 1.0); + #ifdef USE_REFLECTION + // Interpolate light specularity toward reflection color + // Multiply result by specular map + specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor; + + SpecularSum2 = vec4(1.0); + light.y = 1.0; + #endif + + gl_FragColor.rgb += DiffuseSum.rgb * lightColor.rgb * diffuseColor.rgb * vec3(light.x) + + SpecularSum2.rgb * specularColor.rgb * vec3(light.y); + } + + #endif + gl_FragColor.a = alpha; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert new file mode 100644 index 000000000..81ea869b6 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -0,0 +1,172 @@ +#import "Common/ShaderLib/Instancing.glsllib" +#import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#ifdef VERTEX_LIGHTING + #import "Common/ShaderLib/PhongLighting.glsllib" +#endif + + +uniform vec4 m_Ambient; +uniform vec4 m_Diffuse; +uniform vec4 m_Specular; +uniform float m_Shininess; + +#if defined(VERTEX_LIGHTING) + uniform vec4 g_LightData[NB_LIGHTS]; +#endif +uniform vec4 g_AmbientLightColor; +varying vec2 texCoord; + +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; + attribute vec2 inTexCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +attribute vec3 inPosition; +attribute vec2 inTexCoord; +attribute vec3 inNormal; + +#ifdef VERTEX_COLOR + attribute vec4 inColor; +#endif + +#ifndef VERTEX_LIGHTING + varying vec3 vNormal; + varying vec3 vPos; + #ifdef NORMALMAP + attribute vec4 inTangent; + varying vec3 vTangent; + varying vec3 vBinormal; + #endif +#else + varying vec3 specularAccum; + varying vec4 diffuseAccum; +#endif + +#ifdef USE_REFLECTION + uniform vec3 g_CameraPosition; + uniform vec3 m_FresnelParams; + varying vec4 refVec; + + /** + * Input: + * attribute inPosition + * attribute inNormal + * uniform g_WorldMatrix + * uniform g_CameraPosition + * + * Output: + * varying refVec + */ + void computeRef(in vec4 modelSpacePos){ + // vec3 worldPos = (g_WorldMatrix * modelSpacePos).xyz; + vec3 worldPos = TransformWorld(modelSpacePos).xyz; + + vec3 I = normalize( g_CameraPosition - worldPos ).xyz; + // vec3 N = normalize( (g_WorldMatrix * vec4(inNormal, 0.0)).xyz ); + vec3 N = normalize( TransformWorld(vec4(inNormal, 0.0)).xyz ); + + refVec.xyz = reflect(I, N); + refVec.w = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + dot(I, N), m_FresnelParams.z); + } +#endif + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + vec3 modelSpaceNorm = inNormal; + + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vec3 modelSpaceTan = inTangent.xyz; + #endif + + #ifdef NUM_BONES + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Skinning_Compute(modelSpacePos, modelSpaceNorm); + #endif + #endif + + gl_Position = TransformWorldViewProjection(modelSpacePos); + texCoord = inTexCoord; + #ifdef SEPARATE_TEXCOORD + texCoord2 = inTexCoord2; + #endif + + vec3 wvPosition = TransformWorldView(modelSpacePos).xyz; + vec3 wvNormal = normalize(TransformNormal(modelSpaceNorm)); + vec3 viewDir = normalize(-wvPosition); + + + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vTangent = TransformNormal(modelSpaceTan); + vBinormal = cross(wvNormal, vTangent)* inTangent.w; + vNormal = wvNormal; + vPos = wvPosition; + #elif !defined(VERTEX_LIGHTING) + vNormal = wvNormal; + vPos = wvPosition; + #endif + + #ifdef MATERIAL_COLORS + AmbientSum = m_Ambient.rgb * g_AmbientLightColor.rgb; + SpecularSum = m_Specular.rgb; + DiffuseSum = m_Diffuse; + #else + AmbientSum = g_AmbientLightColor.rgb; + SpecularSum = vec3(0.0); + DiffuseSum = vec4(1.0); + #endif + #ifdef VERTEX_COLOR + AmbientSum *= inColor.rgb; + DiffuseSum *= inColor; + #endif + #ifdef VERTEX_LIGHTING + int i = 0; + diffuseAccum = vec4(0.0); + specularAccum = vec3(0.0); + vec4 diffuseColor; + vec3 specularColor; + for (int i =0;i < NB_LIGHTS; i+=3){ + vec4 lightColor = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + DiffuseSum = vec4(1.0); + #ifdef MATERIAL_COLORS + diffuseColor = m_Diffuse * vec4(lightColor.rgb, 1.0); + specularColor = m_Specular.rgb * lightColor.rgb; + #else + diffuseColor = vec4(lightColor.rgb, 1.0); + specularColor = vec3(0.0); + #endif + + vec4 lightDir; + vec3 lightVec; + lightComputeDir(wvPosition, lightColor.w, lightData1, lightDir, lightVec); + // lightDir = normalize(lightDir); + // lightVec = normalize(lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + vec4 lightDirection = g_LightData[i+2]; + spotFallOff = computeSpotFalloff(lightDirection, lightVec); + #if __VERSION__ >= 110 + } + #endif + vec2 v = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess); + diffuseAccum +=v.x * diffuseColor; + specularAccum += v.y * specularColor; + } + #endif + + + #ifdef USE_REFLECTION + computeRef(modelSpacePos); + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib index 4d1b40436..3b8863b6c 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib @@ -1,48 +1,30 @@ -#ifndef NUM_LIGHTS - #define NUM_LIGHTS 4 -#endif +/*Common function for light calculations*/ -uniform mat4 g_ViewMatrix; -uniform vec4 g_LightPosition[NUM_LIGHTS]; -uniform vec4 g_g_LightColor[NUM_LIGHTS]; -uniform float m_Shininess; -float Lighting_Diffuse(vec3 norm, vec3 lightdir){ - return max(0.0, dot(norm, lightdir)); -} - -float Lighting_Specular(vec3 norm, vec3 viewdir, vec3 lightdir, float shiny){ - vec3 refdir = reflect(-lightdir, norm); - return pow(max(dot(refdir, viewdir), 0.0), shiny); -} - -void Lighting_Direction(vec3 worldPos, vec4 color, vec4 position, out vec4 lightDir){ - float posLight = step(0.5, color.w); +/* +* Computes light direction +* lightType should be 0.0,1.0,2.0, repectively for Directional, point and spot lights. +* Outputs the light direction and the light half vector. +*/ +void lightComputeDir(in vec3 worldPos, in float ligthType, in vec4 position, out vec4 lightDir, out vec3 lightVec){ + float posLight = step(0.5, ligthType); vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight); + lightVec = tempVec; float dist = length(tempVec); - lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0); - lightDir.xyz = tempVec / dist; + lightDir.xyz = tempVec / vec3(dist); } -void Lighting_ComputePS(vec3 tanNormal, mat3 tbnMat, - int lightCount, out vec3 outDiffuse, out vec3 outSpecular){ - // find tangent view dir & vert pos - vec3 tanViewDir = viewDir * tbnMat; - - for (int i = 0; i < lightCount; i++){ - // find light dir in tangent space, works for point & directional lights - vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition[i].xyz, g_LightColor[i].w)); - wvLightPos.w = g_LightPosition[i].w; - - vec4 tanLightDir; - Lighting_Direction(wvPosition, g_LightColor[i], wvLightPos, tanLightDir); - tanLightDir.xyz = tanLightDir.xyz * tbnMat; - - vec3 lightScale = g_LightColor[i].rgb * tanLightDir.w; - float specular = Lighting_Specular(tanNormal, tanViewDir, tanLightDir.xyz, m_Shininess); - float diffuse = Lighting_Diffuse(tanNormal, tanLightDir.xyz); - outSpecular += specular * lightScale * step(0.01, diffuse) * g_LightColor[i].rgb; - outDiffuse += diffuse * lightScale * g_LightColor[i].rgb; - } +/* +* Computes the spot falloff for a spotlight +*/ +float computeSpotFalloff(in vec4 lightDirection, in vec3 lightVector){ + vec3 L=normalize(lightVector); + vec3 spotdir = normalize(lightDirection.xyz); + float curAngleCos = dot(-L, spotdir); + float innerAngleCos = floor(lightDirection.w) * 0.001; + float outerAngleCos = fract(lightDirection.w); + float innerMinusOuter = innerAngleCos - outerAngleCos; + return clamp((curAngleCos - outerAngleCos) / innerMinusOuter, step(lightDirection.w, 0.001), 1.0); } + diff --git a/jme3-core/src/main/resources/Common/ShaderLib/PhongLighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/PhongLighting.glsllib new file mode 100644 index 000000000..8ebeb9924 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/PhongLighting.glsllib @@ -0,0 +1,29 @@ +/*Standard Phong ligting*/ + +/* +* Computes diffuse factor +*/ +float lightComputeDiffuse(in vec3 norm, in vec3 lightdir){ + return max(0.0, dot(norm, lightdir)); +} + +/* +* Computes specular factor +*/ +float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){ + vec3 R = reflect(-lightdir, norm); + return pow(max(dot(R, viewdir), 0.0), shiny); +} + +/* +* Computes diffuse and specular factors and pack them in a vec2 (x=diffuse, y=specular) +*/ +vec2 computeLighting(in vec3 norm, in vec3 viewDir, in vec3 lightDir, in float attenuation, in float shininess){ + float diffuseFactor = lightComputeDiffuse(norm, lightDir); + float specularFactor = lightComputeSpecular(norm, viewDir, lightDir, shininess); + if (shininess <= 1.0) { + specularFactor = 0.0; // should be one instruction on most cards .. + } + specularFactor *= diffuseFactor; + return vec2(diffuseFactor, specularFactor) * vec2(attenuation); +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java new file mode 100644 index 000000000..acc0037b8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.BasicProfilerState; +import com.jme3.app.SimpleApplication; +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.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.LightNode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.shape.Box; + +public class TestManyLightsSingle extends SimpleApplication { + + public static void main(String[] args) { + TestManyLightsSingle app = new TestManyLightsSingle(); + app.start(); + } + TechniqueDef.LightMode lm = TechniqueDef.LightMode.MultiPass; + int lightNum = 6 ; + + @Override + public void simpleInitApp() { + renderManager.setPreferredLightMode(lm); + renderManager.setSinglePassLightBatchSize(lightNum); + + + flyCam.setMoveSpeed(10); + + Node scene = (Node) assetManager.loadModel("Scenes/ManyLights/Main.scene"); + rootNode.attachChild(scene); + Node n = (Node) rootNode.getChild(0); + LightList lightList = n.getWorldLightList(); + Geometry g = (Geometry) n.getChild("Grid-geom-1"); + + g.getMaterial().setColor("Ambient", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); + + /* A colored lit cube. Needs light source! */ + Box boxMesh = new Box(1f, 1f, 1f); + Geometry boxGeo = new Geometry("Colored Box", boxMesh); + Material boxMat = g.getMaterial().clone(); + boxMat.setBoolean("UseMaterialColors", true); + boxMat.setColor("Ambient", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); + boxMat.setColor("Diffuse", ColorRGBA.Blue); + boxGeo.setMaterial(boxMat); + + int nb = 0; + for (Light light : lightList) { + nb++; + PointLight p = (PointLight) light; + if (nb >60) { + n.removeLight(light); + } else { + + LightNode ln = new LightNode("l", light); + n.attachChild(ln); + ln.setLocalTranslation(p.getPosition()); + int rand = FastMath.nextRandomInt(0, 3); + switch (rand) { + case 0: + light.setColor(ColorRGBA.Red); + // ln.addControl(new MoveControl(5f)); + break; + case 1: + light.setColor(ColorRGBA.Yellow); + // ln.addControl(new MoveControl(5f)); + break; + case 2: + light.setColor(ColorRGBA.Green); + //ln.addControl(new MoveControl(-5f)); + break; + case 3: + light.setColor(ColorRGBA.Orange); + //ln.addControl(new MoveControl(-5f)); + break; + } + } + Geometry b = boxGeo.clone(); + n.attachChild(b); + b.setLocalTranslation(p.getPosition().x, 2, p.getPosition().z); + + } + + +// cam.setLocation(new Vector3f(3.1893547f, 17.977385f, 30.8378f)); +// cam.setRotation(new Quaternion(0.14317635f, 0.82302624f, -0.23777823f, 0.49557027f)); + + cam.setLocation(new Vector3f(-1.8901939f, 29.34097f, 73.07533f)); + cam.setRotation(new Quaternion(0.0021000702f, 0.971012f, -0.23886925f, 0.008527749f)); + + + BasicProfilerState profiler = new BasicProfilerState(true); + profiler.setGraphScale(1000f); + + // getStateManager().attach(profiler); +// guiNode.setCullHint(CullHint.Always); + + + + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(50); + + + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("toggle") && isPressed) { + if (lm == TechniqueDef.LightMode.SinglePass) { + lm = TechniqueDef.LightMode.MultiPass; + } else { + lm = TechniqueDef.LightMode.SinglePass; + } + renderManager.setPreferredLightMode(lm); + } + if (name.equals("lightsUp") && isPressed) { + lightNum++; + renderManager.setSinglePassLightBatchSize(lightNum); + helloText.setText("nb lights per batch : " + lightNum); + } + if (name.equals("lightsDown") && isPressed) { + lightNum--; + renderManager.setSinglePassLightBatchSize(lightNum); + helloText.setText("nb lights per batch : " + lightNum); + } + } + }, "toggle", "lightsUp", "lightsDown"); + + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("lightsUp", new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("lightsDown", new KeyTrigger(KeyInput.KEY_DOWN)); + + + SpotLight spot = new SpotLight(); + spot.setDirection(new Vector3f(-1f, -1f, -1f).normalizeLocal()); + spot.setColor(ColorRGBA.Blue.mult(5)); + spot.setSpotOuterAngle(FastMath.DEG_TO_RAD * 20); + spot.setSpotInnerAngle(FastMath.DEG_TO_RAD * 5); + spot.setPosition(new Vector3f(10, 10, 20)); + rootNode.addLight(spot); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, 1)); + rootNode.addLight(dl); + + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); + rootNode.addLight(al); + + + /** + * Write text on the screen (HUD) + */ + guiNode.detachAllChildren(); + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + helloText = new BitmapText(guiFont, false); + helloText.setSize(guiFont.getCharSet().getRenderedSize()); + helloText.setText("nb lights per batch : " + lightNum); + helloText.setLocalTranslation(300, helloText.getLineHeight(), 0); + guiNode.attachChild(helloText); + + + } + BitmapText helloText; + long time; + long nbFrames; + long startTime = 0; + + @Override + public void simpleUpdate(float tpf) { +// if (nbFrames == 4000) { +// startTime = System.nanoTime(); +// } +// if (nbFrames > 4000) { +// time = System.nanoTime(); +// float average = ((float) time - (float) startTime) / ((float) nbFrames - 4000f); +// helloText.setText("Average = " + average); +// } +// nbFrames++; + } + + class MoveControl extends AbstractControl { + + float direction; + Vector3f origPos = new Vector3f(); + + public MoveControl(float direction) { + this.direction = direction; + } + + @Override + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); //To change body of generated methods, choose Tools | Templates. + origPos.set(spatial.getLocalTranslation()); + } + float time = 0; + + @Override + protected void controlUpdate(float tpf) { + time += tpf; + spatial.setLocalTranslation(origPos.x + FastMath.cos(time) * direction, origPos.y, origPos.z + FastMath.sin(time) * direction); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + } +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag new file mode 100644 index 000000000..9556f256d --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag @@ -0,0 +1,615 @@ +#import "Common/ShaderLib/PhongLighting.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" + +uniform float m_Shininess; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +uniform mat4 g_ViewMatrix; +uniform vec4 g_LightData[NB_LIGHTS]; +varying vec3 vTangent; +varying vec3 vBinormal; +varying vec3 vPos; +varying vec3 vNormal; +varying vec2 texCoord; + + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif +#ifdef DIFFUSEMAP_1 + uniform sampler2D m_DiffuseMap_1; +#endif +#ifdef DIFFUSEMAP_2 + uniform sampler2D m_DiffuseMap_2; +#endif +#ifdef DIFFUSEMAP_3 + uniform sampler2D m_DiffuseMap_3; +#endif +#ifdef DIFFUSEMAP_4 + uniform sampler2D m_DiffuseMap_4; +#endif +#ifdef DIFFUSEMAP_5 + uniform sampler2D m_DiffuseMap_5; +#endif +#ifdef DIFFUSEMAP_6 + uniform sampler2D m_DiffuseMap_6; +#endif +#ifdef DIFFUSEMAP_7 + uniform sampler2D m_DiffuseMap_7; +#endif +#ifdef DIFFUSEMAP_8 + uniform sampler2D m_DiffuseMap_8; +#endif +#ifdef DIFFUSEMAP_9 + uniform sampler2D m_DiffuseMap_9; +#endif +#ifdef DIFFUSEMAP_10 + uniform sampler2D m_DiffuseMap_10; +#endif +#ifdef DIFFUSEMAP_11 + uniform sampler2D m_DiffuseMap_11; +#endif + + +#ifdef DIFFUSEMAP_0_SCALE + uniform float m_DiffuseMap_0_scale; +#endif +#ifdef DIFFUSEMAP_1_SCALE + uniform float m_DiffuseMap_1_scale; +#endif +#ifdef DIFFUSEMAP_2_SCALE + uniform float m_DiffuseMap_2_scale; +#endif +#ifdef DIFFUSEMAP_3_SCALE + uniform float m_DiffuseMap_3_scale; +#endif +#ifdef DIFFUSEMAP_4_SCALE + uniform float m_DiffuseMap_4_scale; +#endif +#ifdef DIFFUSEMAP_5_SCALE + uniform float m_DiffuseMap_5_scale; +#endif +#ifdef DIFFUSEMAP_6_SCALE + uniform float m_DiffuseMap_6_scale; +#endif +#ifdef DIFFUSEMAP_7_SCALE + uniform float m_DiffuseMap_7_scale; +#endif +#ifdef DIFFUSEMAP_8_SCALE + uniform float m_DiffuseMap_8_scale; +#endif +#ifdef DIFFUSEMAP_9_SCALE + uniform float m_DiffuseMap_9_scale; +#endif +#ifdef DIFFUSEMAP_10_SCALE + uniform float m_DiffuseMap_10_scale; +#endif +#ifdef DIFFUSEMAP_11_SCALE + uniform float m_DiffuseMap_11_scale; +#endif + + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; +#endif +#ifdef NORMALMAP_1 + uniform sampler2D m_NormalMap_1; +#endif +#ifdef NORMALMAP_2 + uniform sampler2D m_NormalMap_2; +#endif +#ifdef NORMALMAP_3 + uniform sampler2D m_NormalMap_3; +#endif +#ifdef NORMALMAP_4 + uniform sampler2D m_NormalMap_4; +#endif +#ifdef NORMALMAP_5 + uniform sampler2D m_NormalMap_5; +#endif +#ifdef NORMALMAP_6 + uniform sampler2D m_NormalMap_6; +#endif +#ifdef NORMALMAP_7 + uniform sampler2D m_NormalMap_7; +#endif +#ifdef NORMALMAP_8 + uniform sampler2D m_NormalMap_8; +#endif +#ifdef NORMALMAP_9 + uniform sampler2D m_NormalMap_9; +#endif +#ifdef NORMALMAP_10 + uniform sampler2D m_NormalMap_10; +#endif +#ifdef NORMALMAP_11 + uniform sampler2D m_NormalMap_11; +#endif + + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + + +#ifdef ALPHAMAP + + vec4 calculateDiffuseBlend(in vec2 texCoord) { + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord * m_DiffuseMap_0_scale); + diffuseColor *= alphaBlend.r; + #ifdef DIFFUSEMAP_1 + vec4 diffuseColor1 = texture2D(m_DiffuseMap_1, texCoord * m_DiffuseMap_1_scale); + diffuseColor = mix( diffuseColor, diffuseColor1, alphaBlend.g ); + #endif + #ifdef DIFFUSEMAP_2 + vec4 diffuseColor2 = texture2D(m_DiffuseMap_2, texCoord * m_DiffuseMap_2_scale); + diffuseColor = mix( diffuseColor, diffuseColor2, alphaBlend.b ); + #endif + #ifdef DIFFUSEMAP_3 + vec4 diffuseColor3 = texture2D(m_DiffuseMap_3, texCoord * m_DiffuseMap_3_scale); + diffuseColor = mix( diffuseColor, diffuseColor3, alphaBlend.a ); + #endif + + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + vec4 diffuseColor4 = texture2D(m_DiffuseMap_4, texCoord * m_DiffuseMap_4_scale); + diffuseColor = mix( diffuseColor, diffuseColor4, alphaBlend1.r ); + #endif + #ifdef DIFFUSEMAP_5 + vec4 diffuseColor5 = texture2D(m_DiffuseMap_5, texCoord * m_DiffuseMap_5_scale); + diffuseColor = mix( diffuseColor, diffuseColor5, alphaBlend1.g ); + #endif + #ifdef DIFFUSEMAP_6 + vec4 diffuseColor6 = texture2D(m_DiffuseMap_6, texCoord * m_DiffuseMap_6_scale); + diffuseColor = mix( diffuseColor, diffuseColor6, alphaBlend1.b ); + #endif + #ifdef DIFFUSEMAP_7 + vec4 diffuseColor7 = texture2D(m_DiffuseMap_7, texCoord * m_DiffuseMap_7_scale); + diffuseColor = mix( diffuseColor, diffuseColor7, alphaBlend1.a ); + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + vec4 diffuseColor8 = texture2D(m_DiffuseMap_8, texCoord * m_DiffuseMap_8_scale); + diffuseColor = mix( diffuseColor, diffuseColor8, alphaBlend2.r ); + #endif + #ifdef DIFFUSEMAP_9 + vec4 diffuseColor9 = texture2D(m_DiffuseMap_9, texCoord * m_DiffuseMap_9_scale); + diffuseColor = mix( diffuseColor, diffuseColor9, alphaBlend2.g ); + #endif + #ifdef DIFFUSEMAP_10 + vec4 diffuseColor10 = texture2D(m_DiffuseMap_10, texCoord * m_DiffuseMap_10_scale); + diffuseColor = mix( diffuseColor, diffuseColor10, alphaBlend2.b ); + #endif + #ifdef DIFFUSEMAP_11 + vec4 diffuseColor11 = texture2D(m_DiffuseMap_11, texCoord * m_DiffuseMap_11_scale); + diffuseColor = mix( diffuseColor, diffuseColor11, alphaBlend2.a ); + #endif + #endif + + return diffuseColor; + } + + vec3 calculateNormal(in vec2 texCoord) { + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + #ifdef NORMALMAP + n = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #endif + + #ifdef NORMALMAP_5 + n = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #endif + + #ifdef NORMALMAP_6 + n = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #endif + + #ifdef NORMALMAP_7 + n = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #endif + + #ifdef NORMALMAP_9 + n = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale); + normal += n * alphaBlend2.g; + #endif + + #ifdef NORMALMAP_10 + n = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale); + normal += n * alphaBlend2.b; + #endif + + #ifdef NORMALMAP_11 + n = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale); + normal += n * alphaBlend2.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + + #ifdef TRI_PLANAR_MAPPING + + vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in float scale) { + vec4 col1 = texture2D( map, coords.yz * scale); + vec4 col2 = texture2D( map, coords.xz * scale); + vec4 col3 = texture2D( map, coords.xy * scale); + // blend the results of the 3 planar projections. + vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; + return tex; + } + + vec4 calculateTriPlanarDiffuseBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + + // blend the results of the 3 planar projections. + vec4 tex0 = getTriPlanarBlend(coords, blending, m_DiffuseMap, m_DiffuseMap_0_scale); + + #ifdef DIFFUSEMAP_1 + // blend the results of the 3 planar projections. + vec4 tex1 = getTriPlanarBlend(coords, blending, m_DiffuseMap_1, m_DiffuseMap_1_scale); + #endif + #ifdef DIFFUSEMAP_2 + // blend the results of the 3 planar projections. + vec4 tex2 = getTriPlanarBlend(coords, blending, m_DiffuseMap_2, m_DiffuseMap_2_scale); + #endif + #ifdef DIFFUSEMAP_3 + // blend the results of the 3 planar projections. + vec4 tex3 = getTriPlanarBlend(coords, blending, m_DiffuseMap_3, m_DiffuseMap_3_scale); + #endif + #ifdef DIFFUSEMAP_4 + // blend the results of the 3 planar projections. + vec4 tex4 = getTriPlanarBlend(coords, blending, m_DiffuseMap_4, m_DiffuseMap_4_scale); + #endif + #ifdef DIFFUSEMAP_5 + // blend the results of the 3 planar projections. + vec4 tex5 = getTriPlanarBlend(coords, blending, m_DiffuseMap_5, m_DiffuseMap_5_scale); + #endif + #ifdef DIFFUSEMAP_6 + // blend the results of the 3 planar projections. + vec4 tex6 = getTriPlanarBlend(coords, blending, m_DiffuseMap_6, m_DiffuseMap_6_scale); + #endif + #ifdef DIFFUSEMAP_7 + // blend the results of the 3 planar projections. + vec4 tex7 = getTriPlanarBlend(coords, blending, m_DiffuseMap_7, m_DiffuseMap_7_scale); + #endif + #ifdef DIFFUSEMAP_8 + // blend the results of the 3 planar projections. + vec4 tex8 = getTriPlanarBlend(coords, blending, m_DiffuseMap_8, m_DiffuseMap_8_scale); + #endif + #ifdef DIFFUSEMAP_9 + // blend the results of the 3 planar projections. + vec4 tex9 = getTriPlanarBlend(coords, blending, m_DiffuseMap_9, m_DiffuseMap_9_scale); + #endif + #ifdef DIFFUSEMAP_10 + // blend the results of the 3 planar projections. + vec4 tex10 = getTriPlanarBlend(coords, blending, m_DiffuseMap_10, m_DiffuseMap_10_scale); + #endif + #ifdef DIFFUSEMAP_11 + // blend the results of the 3 planar projections. + vec4 tex11 = getTriPlanarBlend(coords, blending, m_DiffuseMap_11, m_DiffuseMap_11_scale); + #endif + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec4 diffuseColor = tex0 * alphaBlend.r; + #ifdef DIFFUSEMAP_1 + diffuseColor = mix( diffuseColor, tex1, alphaBlend.g ); + #endif + #ifdef DIFFUSEMAP_2 + diffuseColor = mix( diffuseColor, tex2, alphaBlend.b ); + #endif + #ifdef DIFFUSEMAP_3 + diffuseColor = mix( diffuseColor, tex3, alphaBlend.a ); + #endif + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + diffuseColor = mix( diffuseColor, tex4, alphaBlend1.r ); + #endif + #ifdef DIFFUSEMAP_5 + diffuseColor = mix( diffuseColor, tex5, alphaBlend1.g ); + #endif + #ifdef DIFFUSEMAP_6 + diffuseColor = mix( diffuseColor, tex6, alphaBlend1.b ); + #endif + #ifdef DIFFUSEMAP_7 + diffuseColor = mix( diffuseColor, tex7, alphaBlend1.a ); + #endif + #endif + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + diffuseColor = mix( diffuseColor, tex8, alphaBlend2.r ); + #endif + #ifdef DIFFUSEMAP_9 + diffuseColor = mix( diffuseColor, tex9, alphaBlend2.g ); + #endif + #ifdef DIFFUSEMAP_10 + diffuseColor = mix( diffuseColor, tex10, alphaBlend2.b ); + #endif + #ifdef DIFFUSEMAP_11 + diffuseColor = mix( diffuseColor, tex11, alphaBlend2.a ); + #endif + #endif + + return diffuseColor; + } + + vec3 calculateNormalTriPlanar(in vec3 wNorm, in vec4 wVert,in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's world-space normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + #ifdef NORMALMAP + n = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_5 + n = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_6 + n = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_7 + n = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_9 + n = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale).xyz; + normal += n * alphaBlend2.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_10 + n = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale).xyz; + normal += n * alphaBlend2.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_11 + n = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale).xyz; + normal += n * alphaBlend2.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + #endif + +#endif + +void main(){ + + //---------------------- + // diffuse calculations + //---------------------- + #ifdef DIFFUSEMAP + #ifdef ALPHAMAP + #ifdef TRI_PLANAR_MAPPING + vec4 diffuseColor = calculateTriPlanarDiffuseBlend(wNormal, wVertex, texCoord); + #else + vec4 diffuseColor = calculateDiffuseBlend(texCoord); + #endif + #else + vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord); + #endif + #else + vec4 diffuseColor = vec4(1.0); + #endif + + + //--------------------- + // normal calculations + //--------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + #ifdef TRI_PLANAR_MAPPING + vec3 normal = calculateNormalTriPlanar(wNormal, wVertex, texCoord); + #else + vec3 normal = calculateNormal(texCoord); + #endif + mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); + #else + vec3 normal = vNormal; + #endif + + + //----------------------- + // lighting calculations + //----------------------- + gl_FragColor = AmbientSum * diffuseColor; + for( int i = 0;i < NB_LIGHTS; i+=3){ + vec4 lightColor = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir, lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + spotFallOff = computeSpotFalloff(g_LightData[i+2], lightVec); + #if __VERSION__ >= 110 + } + #endif + + #ifdef NORMALMAP + //Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + vec3 viewDir = normalize(-vPos.xyz * tbnMat); + #else + //no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + vec3 viewDir = normalize(-vPos.xyz); + #endif + + vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess); + gl_FragColor.rgb += DiffuseSum.rgb * lightColor.rgb * diffuseColor.rgb * vec3(light.x) + + SpecularSum.rgb * vec3(light.y); + } + +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert new file mode 100644 index 000000000..78036c93c --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert @@ -0,0 +1,66 @@ +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldViewMatrix; +uniform mat3 g_NormalMatrix; +uniform mat4 g_ViewMatrix; + +uniform vec4 g_AmbientLightColor; + +attribute vec3 inPosition; +attribute vec3 inNormal; +attribute vec2 inTexCoord; +attribute vec4 inTangent; + +varying vec3 vNormal; +varying vec2 texCoord; +varying vec3 vPos; +varying vec3 vTangent; +varying vec3 vBinormal; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + + + +void main(){ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = g_WorldViewProjectionMatrix * pos; + #ifdef TERRAIN_GRID + texCoord = inTexCoord * 2.0; + #else + texCoord = inTexCoord; + #endif + + vec3 wvPosition = (g_WorldViewMatrix * pos).xyz; + vec3 wvNormal = normalize(g_NormalMatrix * inNormal); + + //-------------------------- + // specific to normal maps: + //-------------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + vTangent = g_NormalMatrix * inTangent.xyz; + vBinormal = cross(wvNormal, vTangent)* inTangent.w; + #endif + + //------------------------- + // general to all lighting + //------------------------- + vNormal = wvNormal; + vPos = wvPosition; + + AmbientSum = g_AmbientLightColor; + DiffuseSum = vec4(1.0); + SpecularSum = vec4(0.0); + + +#ifdef TRI_PLANAR_MAPPING + wVertex = vec4(inPosition,0.0); + wNormal = inNormal; +#endif + +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag index 68bd2d236..8ab9b993b 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/PhongLighting.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" uniform float m_Shininess; uniform vec4 g_LightDirection; @@ -145,54 +147,6 @@ varying vec3 lightVec; varying vec3 wNormal; #endif - - -float tangDot(in vec3 v1, in vec3 v2){ - float d = dot(v1,v2); - #ifdef V_TANGENT - d = 1.0 - d*d; - return step(0.0, d) * sqrt(d); - #else - return d; - #endif -} - - -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){ - #ifdef WARDISO - // Isotropic Ward - vec3 halfVec = normalize(viewdir + lightdir); - float NdotH = max(0.001, tangDot(norm, halfVec)); - float NdotV = max(0.001, tangDot(norm, viewdir)); - float NdotL = max(0.001, tangDot(norm, lightdir)); - float a = tan(acos(NdotH)); - float p = max(shiny/128.0, 0.001); - return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL))); - #else - // Standard Phong - vec3 R = reflect(-lightdir, norm); - return pow(max(tangDot(R, viewdir), 0.0), shiny); - #endif -} - -vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){ - float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir); - float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess); - - if (m_Shininess <= 1.0) { - specularFactor = 0.0; // should be one instruction on most cards .. - } - - float att = vLightDir.w; - - return vec2(diffuseFactor, specularFactor) * vec2(att); -} - - #ifdef ALPHAMAP vec4 calculateDiffuseBlend(in vec2 texCoord) { @@ -355,7 +309,7 @@ vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 w vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in float scale) { vec4 col1 = texture2D( map, coords.yz * scale); vec4 col2 = texture2D( map, coords.xz * scale); - vec4 col3 = texture2D( map, coords.xy * scale); + vec4 col3 = texture2D( map, coords.xy * scale); // blend the results of the 3 planar projections. vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; return tex; @@ -627,7 +581,7 @@ void main(){ spotFallOff = clamp(spotFallOff, 0.0, 1.0); } } - + //--------------------- // normal calculations //--------------------- @@ -648,7 +602,7 @@ void main(){ vec4 lightDir = vLightDir; lightDir.xyz = normalize(lightDir.xyz); - vec2 light = computeLighting(vPosition, normal, vViewDir.xyz, lightDir.xyz)*spotFallOff; + vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,m_Shininess); vec4 specularColor = vec4(1.0); diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md index 04470ca26..48c404da4 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -163,6 +163,70 @@ MaterialDef Terrain Lighting { } } + + Technique { + + LightMode SinglePass + + VertexShader GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.vert + FragmentShader GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.frag + + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + WorldViewMatrix + ViewMatrix + } + + Defines { + TRI_PLANAR_MAPPING : useTriPlanarMapping + TERRAIN_GRID : isTerrainGrid + WARDISO : WardIso + + DIFFUSEMAP : DiffuseMap + DIFFUSEMAP_1 : DiffuseMap_1 + DIFFUSEMAP_2 : DiffuseMap_2 + DIFFUSEMAP_3 : DiffuseMap_3 + DIFFUSEMAP_4 : DiffuseMap_4 + DIFFUSEMAP_5 : DiffuseMap_5 + DIFFUSEMAP_6 : DiffuseMap_6 + DIFFUSEMAP_7 : DiffuseMap_7 + DIFFUSEMAP_8 : DiffuseMap_8 + DIFFUSEMAP_9 : DiffuseMap_9 + DIFFUSEMAP_10 : DiffuseMap_10 + DIFFUSEMAP_11 : DiffuseMap_11 + NORMALMAP : NormalMap + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + SPECULARMAP : SpecularMap + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + DIFFUSEMAP_0_SCALE : DiffuseMap_0_scale + DIFFUSEMAP_1_SCALE : DiffuseMap_1_scale + DIFFUSEMAP_2_SCALE : DiffuseMap_2_scale + DIFFUSEMAP_3_SCALE : DiffuseMap_3_scale + DIFFUSEMAP_4_SCALE : DiffuseMap_4_scale + DIFFUSEMAP_5_SCALE : DiffuseMap_5_scale + DIFFUSEMAP_6_SCALE : DiffuseMap_6_scale + DIFFUSEMAP_7_SCALE : DiffuseMap_7_scale + DIFFUSEMAP_8_SCALE : DiffuseMap_8_scale + DIFFUSEMAP_9_SCALE : DiffuseMap_9_scale + DIFFUSEMAP_10_SCALE : DiffuseMap_10_scale + DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale + } + } + + Technique PreShadow { VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert index a3a1cc24e..e03f03f7f 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/Lighting.glsllib" + uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_WorldViewMatrix; uniform mat3 g_NormalMatrix; @@ -34,16 +36,6 @@ varying vec4 SpecularSum; varying vec3 wNormal; #endif -// 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(){ vec4 pos = vec4(inPosition, 1.0); @@ -66,35 +58,30 @@ void main(){ // specific to normal maps: //-------------------------- #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) - vec3 wvTangent = normalize(g_NormalMatrix * inTangent.xyz); - vec3 wvBinormal = cross(wvNormal, wvTangent); + vec3 wvTangent = normalize(g_NormalMatrix * inTangent.xyz); + vec3 wvBinormal = cross(wvNormal, wvTangent); - mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal); + mat3 tbnMat = mat3(wvTangent, wvBinormal * inTangent.w,wvNormal); - vPosition = wvPosition * tbnMat; - vViewDir = viewDir * tbnMat; - lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); - vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; - #else + vPosition = wvPosition * tbnMat; + vViewDir = viewDir * tbnMat; - //------------------------- - // general to all lighting - //------------------------- - vNormal = wvNormal; + lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); + vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; + #else + //------------------------- + // general to all lighting + //------------------------- + vNormal = wvNormal; - vPosition = wvPosition; - vViewDir = viewDir; + vPosition = wvPosition; + vViewDir = viewDir; - lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + lightComputeDir(wvPosition, lightColor.w, wvLightPos, vLightDir, lightVec); #endif - //computing spot direction in view space and unpacking spotlight cos - // spotVec=(g_ViewMatrix *vec4(g_LightDirection.xyz,0.0) ); - // spotVec.w=floor(g_LightDirection.w)*0.001; - // lightVec.w = fract(g_LightDirection.w); - - AmbientSum = vec4(0.2, 0.2, 0.2, 1.0) * g_AmbientLightColor; // Default: ambient color is dark gray + AmbientSum = g_AmbientLightColor; // Default: ambient color is dark gray DiffuseSum = lightColor; SpecularSum = lightColor; From e225e6ff89de79da03741c4e6914ac65f6f41ed2 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 14:11:16 -0500 Subject: [PATCH 02/13] * Move certain render context specific fields from Renderer into RenderContext --- .../java/com/jme3/renderer/RenderContext.java | 17 ++++++++ .../jme3/renderer/lwjgl/LwjglRenderer.java | 41 ++++++++----------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index ea89ab58c..8fa479e44 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -35,6 +35,7 @@ import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.shader.Shader; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; @@ -138,11 +139,21 @@ public class RenderContext { * @see Renderer#setShader(com.jme3.shader.Shader) */ public int boundShaderProgram; + + /** + * @see Renderer#setShader(com.jme3.shader.Shader) + */ + public Shader boundShader; /** * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) */ public int boundFBO = 0; + + /** + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public FrameBuffer boundFB; /** * Currently bound Renderbuffer @@ -279,6 +290,10 @@ public class RenderContext { */ public RenderState.TestFunction alphaFunc = RenderState.TestFunction.Greater; + + public int initialDrawBuf; + public int initialReadBuf; + /** * Reset the RenderContext to default GL state */ @@ -298,7 +313,9 @@ public class RenderContext { blendMode = RenderState.BlendMode.Off; wireframe = false; boundShaderProgram = 0; + boundShader = null; boundFBO = 0; + boundFB = null; boundRB = 0; boundDrawBuf = -1; boundReadBuf = -1; diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index fdc908347..1a616fc92 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -100,10 +100,7 @@ public class LwjglRenderer implements Renderer { private final RenderContext context = new RenderContext(); private final NativeObjectManager objManager = new NativeObjectManager(); private final EnumSet caps = EnumSet.noneOf(Caps.class); - // current state - private Shader boundShader; - private int initialDrawBuf, initialReadBuf; - private int glslVer; + private int vertexTextureUnits; private int fragTextureUnits; private int vertexUniforms; @@ -119,7 +116,6 @@ public class LwjglRenderer implements Renderer { private int maxTriCount; private int maxColorTexSamples; private int maxDepthTexSamples; - private FrameBuffer lastFb = null; private FrameBuffer mainFbOverride = null; private final Statistics statistics = new Statistics(); private int vpX, vpY, vpW, vpH; @@ -180,7 +176,6 @@ public class LwjglRenderer implements Renderer { versionStr = glGetString(GL_SHADING_LANGUAGE_VERSION); } if (versionStr == null || versionStr.equals("")) { - glslVer = -1; throw new UnsupportedOperationException("GLSL and OpenGL2 is " + "required for the LWJGL " + "renderer!"); @@ -188,8 +183,8 @@ public class LwjglRenderer implements Renderer { // Fix issue in TestRenderToMemory when GL_FRONT is the main // buffer being used. - initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); - initialReadBuf = glGetInteger(GL_READ_BUFFER); + context.initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + context.initialReadBuf = glGetInteger(GL_READ_BUFFER); // XXX: This has to be GL_BACK for canvas on Mac // Since initialDrawBuf is GL_FRONT for pbuffer, gotta @@ -203,7 +198,7 @@ public class LwjglRenderer implements Renderer { } float version = Float.parseFloat(versionStr); - glslVer = (int) (version * 100); + int glslVer = (int) (version * 100); switch (glslVer) { default: @@ -409,11 +404,8 @@ public class LwjglRenderer implements Renderer { public void invalidateState() { context.reset(); - boundShader = null; - lastFb = null; - - initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); - initialReadBuf = glGetInteger(GL_READ_BUFFER); + context.initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + context.initialReadBuf = glGetInteger(GL_READ_BUFFER); } public void resetGLObjects() { @@ -821,7 +813,7 @@ public class LwjglRenderer implements Renderer { if (context.boundShaderProgram != shaderId) { glUseProgram(shaderId); statistics.onShaderUse(shader, true); - boundShader = shader; + context.boundShader = shader; context.boundShaderProgram = shaderId; } else { statistics.onShaderUse(shader, false); @@ -1531,16 +1523,16 @@ public class LwjglRenderer implements Renderer { fb = mainFbOverride; } - if (lastFb == fb) { + if (context.boundFB == fb) { if (fb == null || !fb.isUpdateNeeded()) { return; } } // generate mipmaps for last FB if needed - if (lastFb != null) { - for (int i = 0; i < lastFb.getNumColorBuffers(); i++) { - RenderBuffer rb = lastFb.getColorBuffer(i); + if (context.boundFB != null) { + for (int i = 0; i < context.boundFB.getNumColorBuffers(); i++) { + RenderBuffer rb = context.boundFB.getColorBuffer(i); Texture tex = rb.getTexture(); if (tex != null && tex.getMinFilter().usesMipMapLevels()) { @@ -1564,15 +1556,15 @@ public class LwjglRenderer implements Renderer { } // select back buffer if (context.boundDrawBuf != -1) { - glDrawBuffer(initialDrawBuf); + glDrawBuffer(context.initialDrawBuf); context.boundDrawBuf = -1; } if (context.boundReadBuf != -1) { - glReadBuffer(initialReadBuf); + glReadBuffer(context.initialReadBuf); context.boundReadBuf = -1; } - lastFb = null; + context.boundFB = null; } else { if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { throw new IllegalArgumentException("The framebuffer: " + fb @@ -1641,7 +1633,7 @@ public class LwjglRenderer implements Renderer { assert fb.getId() >= 0; assert context.boundFBO == fb.getId(); - lastFb = fb; + context.boundFB = fb; try { checkFrameBufferError(); @@ -2210,8 +2202,9 @@ public class LwjglRenderer implements Renderer { } int programId = context.boundShaderProgram; + if (programId > 0) { - Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + Attribute attrib = context.boundShader.getAttribute(vb.getBufferType()); int loc = attrib.getLocation(); if (loc == -1) { return; // not defined From 4d7dcc17bbbdbdfeb413794d23238b6b737653e8 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 16:27:14 -0500 Subject: [PATCH 03/13] * Don't depend on ContextCapabilities for determining OGL / GLSL versions --- .../jme3/renderer/lwjgl/LwjglRenderer.java | 91 +++++++++++-------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index 1a616fc92..f3ef9055c 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -148,18 +148,56 @@ public class LwjglRenderer implements Renderer { return caps; } + private static int extractVersion(String prefixStr, String versionStr) { + if (versionStr != null) { + int spaceIdx = versionStr.indexOf(" ", prefixStr.length()); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(prefixStr.length(), spaceIdx).trim(); + } else { + versionStr = versionStr.substring(prefixStr.length()).trim(); + } + // Some device have ":" at the end of the version. + versionStr = versionStr.replaceAll("\\:", ""); + + // Pivot on first point. + int firstPoint = versionStr.indexOf("."); + + // Remove everything after second point. + int secondPoint = versionStr.indexOf(".", firstPoint + 1); + + if (secondPoint != -1) { + versionStr = versionStr.substring(0, secondPoint); + } + + String majorVerStr = versionStr.substring(0, firstPoint); + String minorVerStr = versionStr.substring(firstPoint + 1); + + if (minorVerStr.endsWith("0") && minorVerStr.length() > 1) { + minorVerStr = minorVerStr.substring(0, minorVerStr.length() - 1); + } + + int majorVer = Integer.parseInt(majorVerStr); + int minorVer = Integer.parseInt(minorVerStr); + + return majorVer * 100 + minorVer * 10; + } else { + return -1; + } + } + @SuppressWarnings("fallthrough") public void initialize() { - ctxCaps = GLContext.getCapabilities(); - if (ctxCaps.OpenGL20) { + int oglVer = extractVersion("", glGetString(GL_VERSION)); + + if (oglVer >= 200) { caps.add(Caps.OpenGL20); - if (ctxCaps.OpenGL21) { + if (oglVer >= 210) { caps.add(Caps.OpenGL21); - if (ctxCaps.OpenGL30) { + if (oglVer >= 300) { caps.add(Caps.OpenGL30); - if (ctxCaps.OpenGL31) { + if (oglVer >= 310) { caps.add(Caps.OpenGL31); - if (ctxCaps.OpenGL32) { + if (oglVer >= 320) { caps.add(Caps.OpenGL32); } } @@ -167,20 +205,6 @@ public class LwjglRenderer implements Renderer { } } - //workaround, always assume we support GLSL100 - //some cards just don't report this correctly - caps.add(Caps.GLSL100); - - String versionStr = null; - if (ctxCaps.OpenGL20) { - versionStr = glGetString(GL_SHADING_LANGUAGE_VERSION); - } - if (versionStr == null || versionStr.equals("")) { - throw new UnsupportedOperationException("GLSL and OpenGL2 is " - + "required for the LWJGL " - + "renderer!"); - } - // Fix issue in TestRenderToMemory when GL_FRONT is the main // buffer being used. context.initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); @@ -192,23 +216,15 @@ public class LwjglRenderer implements Renderer { // initialDrawBuf = GL_BACK; // initialReadBuf = GL_BACK; - int spaceIdx = versionStr.indexOf(" "); - if (spaceIdx >= 1) { - versionStr = versionStr.substring(0, spaceIdx); - } - - float version = Float.parseFloat(versionStr); - int glslVer = (int) (version * 100); - + int glslVer = extractVersion("", glGetString(GL_SHADING_LANGUAGE_VERSION)); + switch (glslVer) { default: if (glslVer < 400) { break; } - - // so that future OpenGL revisions wont break jme3 - - // fall through intentional + // so that future OpenGL revisions wont break jme3 + // fall through intentional case 400: case 330: case 150: @@ -225,12 +241,13 @@ public class LwjglRenderer implements Renderer { caps.add(Caps.GLSL100); break; } + + // Workaround, always assume we support GLSL100. + // Some cards just don't report this correctly. + caps.add(Caps.GLSL100); - if (!caps.contains(Caps.GLSL100)) { - logger.log(Level.WARNING, "Force-adding GLSL100 support, since OpenGL2 is supported."); - caps.add(Caps.GLSL100); - } - + ctxCaps = GLContext.getCapabilities(); + glGetInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); vertexTextureUnits = intBuf16.get(0); logger.log(Level.FINER, "VTF Units: {0}", vertexTextureUnits); From 7860ccca5288e8ef51f5e0cb3f09c988795d22c6 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 16:40:09 -0500 Subject: [PATCH 04/13] * Use mipmap minification modes only if mipmaps are actually available --- .../jme3/renderer/lwjgl/LwjglRenderer.java | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index f3ef9055c..c23919ded 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -1757,22 +1757,37 @@ public class LwjglRenderer implements Renderer { } } - private int convertMinFilter(Texture.MinFilter filter) { - switch (filter) { - case Trilinear: - return GL_LINEAR_MIPMAP_LINEAR; - case BilinearNearestMipMap: - return GL_LINEAR_MIPMAP_NEAREST; - case NearestLinearMipMap: - return GL_NEAREST_MIPMAP_LINEAR; - case NearestNearestMipMap: - return GL_NEAREST_MIPMAP_NEAREST; - case BilinearNoMipMaps: - return GL_LINEAR; - case NearestNoMipMaps: - return GL_NEAREST; - default: - throw new UnsupportedOperationException("Unknown min filter: " + filter); + private int convertMinFilter(Texture.MinFilter filter, boolean haveMips) { + if (haveMips){ + switch (filter) { + case Trilinear: + return GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GL_LINEAR; + case NearestNoMipMaps: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } else { + switch (filter) { + case Trilinear: + case BilinearNearestMipMap: + case BilinearNoMipMaps: + return GL_LINEAR; + case NearestLinearMipMap: + case NearestNearestMipMap: + case NearestNoMipMaps: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } } } @@ -1798,8 +1813,14 @@ public class LwjglRenderer implements Renderer { Image image = tex.getImage(); int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); + boolean haveMips = true; + + if (image != null) { + haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps(); + } + // filter things - int minFilter = convertMinFilter(tex.getMinFilter()); + int minFilter = convertMinFilter(tex.getMinFilter(), haveMips); int magFilter = convertMagFilter(tex.getMagFilter()); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter); From 22ab7c11c7740ac340c788bff857a0ef89a0b1c6 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 16:47:51 -0500 Subject: [PATCH 05/13] * Ensure cubemaps have square dimensions before uploading --- .../src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index c23919ded..a9fa42034 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -1955,6 +1955,9 @@ public class LwjglRenderer implements Renderer { if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); } + if (img.getWidth() != img.getHeight()) { + throw new RendererException("Cubemaps must have square dimensions"); + } } else { if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); From 4050b7cbbf6df96b38bb5c77f887b33a96bdcea4 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 16:52:17 -0500 Subject: [PATCH 06/13] * Require alpha discard define to be set in order to enable the alpha discard feature (for multi pass lighting shader) --- .../src/main/resources/Common/MatDefs/Light/Lighting.frag | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag index 370c62bc9..a1f58b898 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag @@ -106,9 +106,11 @@ void main(){ #ifdef ALPHAMAP alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r; #endif - if(alpha < m_AlphaDiscardThreshold){ - discard; - } + #ifdef DISCARD_ALPHA + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif // *********************** From 393009ec2d02663644721d4cd07ed183b0c76911 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 22:07:02 -0500 Subject: [PATCH 07/13] Fix OpenAL Soft for Android * Upgrade to latest OpenAL Soft (1.16) * Don't specify TARGET_PLATFORM on command line as that causes weird issues * Specify target platform via APP_PLATFORM in Application.mk and TARGET_PLATFORM in Android.mk --- jme3-android-native/openalsoft.gradle | 23 +- .../src/native/jme_openalsoft/Android.mk | 110 +++++----- .../src/native/jme_openalsoft/Application.mk | 4 +- .../src/native/jme_openalsoft/config.h | 206 +++++++++++------- 4 files changed, 203 insertions(+), 140 deletions(-) diff --git a/jme3-android-native/openalsoft.gradle b/jme3-android-native/openalsoft.gradle index 1027f44ef..d32a804b8 100644 --- a/jme3-android-native/openalsoft.gradle +++ b/jme3-android-native/openalsoft.gradle @@ -1,9 +1,5 @@ -// OpenAL Soft r1.15.1 -//String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/9b6a226da55a987cb883f425eeb568776ea12c8d.zip' -// OpenAL Soft r1.15.1 + Android OpenSL Support -String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/be25e6802dacad78876c6fa1d6a5c63797b8a9ed.zip' -// OpenAL Soft r1.15.1 latest build (at the time) -//String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/3f5914e0949ee12b504ee7254990e007ff8057ef.zip' +// OpenAL Soft r1.16 +String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/e5016f814a265ed592a88acea95cf912c4bfdf12.zip' String openALSoftZipFile = 'OpenALSoft.zip' // OpenAL Soft directory the download is extracted into @@ -81,22 +77,23 @@ task generateOpenAlSoftHeaders(dependsOn: copyJmeOpenALSoft) << { String classes = "" .concat("com.jme3.audio.android.AndroidOpenALSoftAudioRenderer, ") // println "openalsoft classes = " + classes -// println "openalsoft destDir = " + destDir +// println "openalsoft destDir = " + destDirPath // println "openalsoft classpath = " + project.projectClassPath - ant.javah( - classpath: project.projectClassPath, - destdir: destDirPath, - class: classes - ) + exec { + executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') + args '-d', destDirPath + args '-classpath', project.projectClassPath + args "com.jme3.audio.android.AndroidOpenALSoftAudioRenderer" + } } task buildOpenAlSoftNativeLib(type: Exec, dependsOn: generateOpenAlSoftHeaders) { // println "openalsoft build dir: " + openalsoftBuildDir // println "ndkCommandPath: " + project.ndkCommandPath - args 'TARGET_PLATFORM=android-9' workingDir openalsoftBuildDir executable rootProject.ndkCommandPath + args '-j8' } task updatePreCompiledOpenAlSoftLibs(type: Copy, dependsOn: buildOpenAlSoftNativeLib) { diff --git a/jme3-android-native/src/native/jme_openalsoft/Android.mk b/jme3-android-native/src/native/jme_openalsoft/Android.mk index 428008320..fa620078e 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Android.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Android.mk @@ -1,58 +1,68 @@ -TARGET_PLATFORM := android-9 +TARGET_PLATFORM=android-9 -ROOT_PATH := $(call my-dir) - -######################################################################################################## +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := openalsoftjme -LOCAL_ARM_MODE := arm -LOCAL_PATH := $(ROOT_PATH) -LOCAL_C_INCLUDES := $(LOCAL_PATH) $(LOCAL_PATH)/include $(LOCAL_PATH)/OpenAL32/Include - -LOCAL_CFLAGS := -ffast-math -DAL_BUILD_LIBRARY -DAL_ALEXT_PROTOTYPES -LOCAL_LDLIBS := -llog -Wl,-s -LOCAL_LDLIBS += -lOpenSLES -# LOCAL_CFLAGS += -DPOST_FROYO #-I$(ANDROID_NDK_ROOT)/platforms/android-9/arch-arm/usr/include/ -# LOCAL_LDLIBS += -ldl -L$(ANDROID_NDK_ROOT)/platforms/android-9/arch-arm/usr/lib/ - -LOCAL_SRC_FILES := OpenAL32/alAuxEffectSlot.c \ - OpenAL32/alBuffer.c \ - OpenAL32/alEffect.c \ - OpenAL32/alError.c \ - OpenAL32/alExtension.c \ - OpenAL32/alFilter.c \ - OpenAL32/alListener.c \ - OpenAL32/alSource.c \ - OpenAL32/alState.c \ - OpenAL32/alThunk.c \ - Alc/ALc.c \ - Alc/ALu.c \ - Alc/alcConfig.c \ - Alc/alcDedicated.c \ - Alc/alcEcho.c \ - Alc/alcModulator.c \ - Alc/alcReverb.c \ - Alc/alcRing.c \ - Alc/alcThread.c \ - Alc/bs2b.c \ - Alc/helpers.c \ - Alc/panning.c \ - Alc/hrtf.c \ - Alc/mixer.c \ - Alc/mixer_c.c \ - Alc/backends/loopback.c \ - Alc/backends/null.c \ - Alc/backends/opensl.c \ - com_jme3_audio_android_AndroidOpenALSoftAudioRenderer.cpp -# Alc/backends/alsa.c \ -# Alc/backends/android.c \ -# Alc/alcChorus.c \ -# Alc/alcFlanger.c \ -# Alc/mixer_c.c \ -# Alc/backends/loopback.c \ -# Alc/backends/null.c \ + +LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/OpenAL32/Include $(LOCAL_PATH)/Alc + +LOCAL_CFLAGS := -std=c99 -ffast-math -DAL_BUILD_LIBRARY -DAL_ALEXT_PROTOTYPES +LOCAL_LDLIBS := -lOpenSLES -llog -Wl,-s + +LOCAL_SRC_FILES := Alc/backends/opensl.c \ + Alc/backends/loopback.c \ + Alc/backends/wave.c \ + Alc/backends/base.c \ + Alc/backends/null.c \ + Alc/ALc.c \ + Alc/helpers.c \ + Alc/bs2b.c \ + Alc/alcRing.c \ + Alc/effects/chorus.c \ + Alc/effects/flanger.c \ + Alc/effects/dedicated.c \ + Alc/effects/reverb.c \ + Alc/effects/distortion.c \ + Alc/effects/autowah.c \ + Alc/effects/equalizer.c \ + Alc/effects/modulator.c \ + Alc/effects/echo.c \ + Alc/effects/compressor.c \ + Alc/effects/null.c \ + Alc/alcConfig.c \ + Alc/ALu.c \ + Alc/mixer_c.c \ + Alc/panning.c \ + Alc/hrtf.c \ + Alc/mixer.c \ + Alc/midi/soft.c \ + Alc/midi/sf2load.c \ + Alc/midi/dummy.c \ + Alc/midi/fluidsynth.c \ + Alc/midi/base.c \ + common/uintmap.c \ + common/atomic.c \ + common/threads.c \ + common/rwlock.c \ + OpenAL32/alBuffer.c \ + OpenAL32/alPreset.c \ + OpenAL32/alListener.c \ + OpenAL32/alEffect.c \ + OpenAL32/alExtension.c \ + OpenAL32/alThunk.c \ + OpenAL32/alMidi.c \ + OpenAL32/alSoundfont.c \ + OpenAL32/alFontsound.c \ + OpenAL32/alAuxEffectSlot.c \ + OpenAL32/alError.c \ + OpenAL32/alFilter.c \ + OpenAL32/alSource.c \ + OpenAL32/alState.c \ + OpenAL32/sample_cvt.c \ + com_jme3_audio_android_AndroidOpenALSoftAudioRenderer.cpp include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_openalsoft/Application.mk b/jme3-android-native/src/native/jme_openalsoft/Application.mk index fcdc69644..fbc028f84 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Application.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Application.mk @@ -1,3 +1,3 @@ +APP_PLATFORM := android-9 APP_OPTIM := release -APP_ABI := all -#APP_ABI := armeabi-v7a +APP_ABI := all \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_openalsoft/config.h b/jme3-android-native/src/native/jme_openalsoft/config.h index bf3ee85db..f9de99528 100644 --- a/jme3-android-native/src/native/jme_openalsoft/config.h +++ b/jme3-android-native/src/native/jme_openalsoft/config.h @@ -1,147 +1,203 @@ -#ifndef CONFIG_H -#define CONFIG_H +/* API declaration export attribute */ +#define AL_API __attribute__((visibility("protected"))) +#define ALC_API __attribute__((visibility("protected"))) /* Define to the library version */ -#define ALSOFT_VERSION "1.15.1" +#define ALSOFT_VERSION "1.16.0" -#define ALIGN(x) __attribute__ ((aligned(x))) +#ifdef IN_IDE_PARSER +/* KDevelop's parser doesn't recognize the C99-standard restrict keyword, but + * recent versions (at least 4.5.1) do recognize GCC's __restrict. */ +#define restrict __restrict +#endif -/* Define if we have the Android backend */ -/* #define HAVE_ANDROID 1 */ +/* Define any available alignment declaration */ +#define ALIGN(x) __attribute__((aligned(x))) + +/* Define if we have the C11 aligned_alloc function */ +/* #undef HAVE_ALIGNED_ALLOC */ + +/* Define if we have the posix_memalign function */ +/* #undef HAVE_POSIX_MEMALIGN */ + +/* Define if we have the _aligned_malloc function */ +/* #undef HAVE__ALIGNED_MALLOC */ + +/* Define if we have SSE CPU extensions */ +/* #undef HAVE_SSE */ +/* #undef HAVE_SSE2 */ +/* #undef HAVE_SSE4_1 */ + +/* Define if we have ARM Neon CPU extensions */ +/* #undef HAVE_NEON */ + +/* Define if we have FluidSynth support */ +/* #undef HAVE_FLUIDSYNTH */ /* Define if we have the ALSA backend */ -/* #define HAVE_ALSA */ +/* #undef HAVE_ALSA */ /* Define if we have the OSS backend */ -/* #cmakedefine HAVE_OSS */ +/* #undef HAVE_OSS */ /* Define if we have the Solaris backend */ -/* #cmakedefine HAVE_SOLARIS */ +/* #undef HAVE_SOLARIS */ /* Define if we have the SndIO backend */ -/* #cmakedefine HAVE_SNDIO */ +/* #undef HAVE_SNDIO */ + +/* Define if we have the QSA backend */ +/* #undef HAVE_QSA */ /* Define if we have the MMDevApi backend */ -/* #cmakedefine HAVE_MMDEVAPI */ +/* #undef HAVE_MMDEVAPI */ /* Define if we have the DSound backend */ -/* #cmakedefine HAVE_DSOUND */ +/* #undef HAVE_DSOUND */ /* Define if we have the Windows Multimedia backend */ -/* #cmakedefine HAVE_WINMM */ +/* #undef HAVE_WINMM */ /* Define if we have the PortAudio backend */ -/* #cmakedefine HAVE_PORTAUDIO */ +/* #undef HAVE_PORTAUDIO */ /* Define if we have the PulseAudio backend */ -/* #cmakedefine HAVE_PULSEAUDIO */ +/* #undef HAVE_PULSEAUDIO */ /* Define if we have the CoreAudio backend */ -/* #cmakedefine HAVE_COREAUDIO */ +/* #undef HAVE_COREAUDIO */ /* Define if we have the OpenSL backend */ -#define HAVE_OPENSL /* THIS BACKEND WORKS ON >=2.3 Android!! */ +#define HAVE_OPENSL /* Define if we have the Wave Writer backend */ -/* #cmakedefine HAVE_WAVE */ - -/* Define if we have dlfcn.h */ -#define HAVE_DLFCN_H +#define HAVE_WAVE /* Define if we have the stat function */ #define HAVE_STAT -/* Define if we have the powf function */ -/* #define HAVE_POWF 1 */ +/* Define if we have the lrintf function */ +#define HAVE_LRINTF -/* Define if we have the sqrtf function */ -/* #define HAVE_SQRTF 1 */ +/* Define if we have the strtof function */ +/* #undef HAVE_STRTOF */ -/* Define if we have the cosf function */ -/* #define HAVE_COSF 1 */ +/* Define if we have the __int64 type */ +/* #undef HAVE___INT64 */ -/* Define if we have the sinf function */ -/* #define HAVE_SINF 1 */ +/* Define to the size of a long int type */ +#define SIZEOF_LONG 4 -/* Define if we have the acosf function */ -/* #define HAVE_ACOSF 1 */ +/* Define to the size of a long long int type */ +#define SIZEOF_LONG_LONG 8 -/* Define if we have the asinf function */ -/* #define HAVE_ASINF 1 */ +/* Define if we have C99 variable-length array support */ +#define HAVE_C99_VLA -/* Define if we have the atanf function */ -/* #define HAVE_ATANF 1 */ +/* Define if we have C99 _Bool support */ +#define HAVE_C99_BOOL -/* Define if we have the atan2f function */ -/* #define HAVE_ATAN2F 1 */ +/* Define if we have C11 _Static_assert support */ +#define HAVE_C11_STATIC_ASSERT -/* Define if we have the fabsf function */ -/* #define HAVE_FABSF 1 */ +/* Define if we have C11 _Alignas support */ +/* #undef HAVE_C11_ALIGNAS */ -/* Define if we have the log10f function */ -/* #define HAVE_LOG10F 1 */ +/* Define if we have C11 _Atomic support */ +/* #undef HAVE_C11_ATOMIC */ -/* Define if we have the floorf function */ -/* #define HAVE_FLOORF 1 */ +/* Define if we have GCC's destructor attribute */ +#define HAVE_GCC_DESTRUCTOR -/* Define if we have the strtof function */ -#define HAVE_STRTOF +/* Define if we have GCC's format attribute */ +#define HAVE_GCC_FORMAT /* Define if we have stdint.h */ #define HAVE_STDINT_H -/* Define if we have the __int64 type */ -/* #cmakedefine HAVE___INT64 */ +/* Define if we have stdbool.h */ +#define HAVE_STDBOOL_H -/* Define to the size of a long int type */ -#define SIZEOF_LONG 4 +/* Define if we have stdalign.h */ +/* #undef HAVE_STDALIGN_H */ -/* Define to the size of a long long int type */ -#define SIZEOF_LONG_LONG 8 - -/* Define if we have GCC's destructor attribute */ -#define HAVE_GCC_DESTRUCTOR +/* Define if we have windows.h */ +/* #undef HAVE_WINDOWS_H */ -/* Define if we have GCC's format attribute */ -#define HAVE_GCC_FORMAT +/* Define if we have dlfcn.h */ +#define HAVE_DLFCN_H /* Define if we have pthread_np.h */ -/* #cmakedefine HAVE_PTHREAD_NP_H */ +/* #undef HAVE_PTHREAD_NP_H */ -/* Define if we have arm_neon.h */ -/* #cmakedefine HAVE_ARM_NEON_H */ +/* Define if we have alloca.h */ +/* #undef HAVE_ALLOCA_H */ -/* Define if we have guiddef.h */ -/* #cmakedefine HAVE_GUIDDEF_H */ +/* Define if we have malloc.h */ +#define HAVE_MALLOC_H + +/* Define if we have ftw.h */ +/* #undef HAVE_FTW_H */ + +/* Define if we have io.h */ +/* #undef HAVE_IO_H */ + +/* Define if we have strings.h */ +#define HAVE_STRINGS_H + +/* Define if we have cpuid.h */ +/* #undef HAVE_CPUID_H */ + +/* Define if we have intrin.h */ +/* #undef HAVE_INTRIN_H */ + +/* Define if we have sys/sysconf.h */ +#define HAVE_SYS_SYSCONF_H /* Define if we have guiddef.h */ -/* #cmakedefine HAVE_INITGUID_H */ +/* #undef HAVE_GUIDDEF_H */ + +/* Define if we have initguid.h */ +/* #undef HAVE_INITGUID_H */ /* Define if we have ieeefp.h */ -/* #cmakedefine HAVE_IEEEFP_H */ +/* #undef HAVE_IEEEFP_H */ /* Define if we have float.h */ -/* #cmakedefine HAVE_FLOAT_H */ - -/* Define if we have fpu_control.h */ -/* #cmakedefine HAVE_FPU_CONTROL_H */ +#define HAVE_FLOAT_H /* Define if we have fenv.h */ #define HAVE_FENV_H -/* Define if we have fesetround() */ -/* #cmakedefine HAVE_FESETROUND */ +/* Define if we have GCC's __get_cpuid() */ +/* #undef HAVE_GCC_GET_CPUID */ + +/* Define if we have the __cpuid() intrinsic */ +/* #undef HAVE_CPUID_INTRINSIC */ /* Define if we have _controlfp() */ -/* #cmakedefine HAVE__CONTROLFP */ +/* #undef HAVE__CONTROLFP */ + +/* Define if we have __control87_2() */ +/* #undef HAVE___CONTROL87_2 */ + +/* Define if we have ftw() */ +/* #undef HAVE_FTW */ + +/* Define if we have _wfindfirst() */ +/* #undef HAVE__WFINDFIRST */ /* Define if we have pthread_setschedparam() */ #define HAVE_PTHREAD_SETSCHEDPARAM -/* Define if we have the restrict keyword */ -/* #cmakedefine HAVE_RESTRICT 1 */ +/* Define if we have pthread_setname_np() */ +#define HAVE_PTHREAD_SETNAME_NP + +/* Define if we have pthread_set_name_np() */ +/* #undef HAVE_PTHREAD_SET_NAME_NP */ -/* Define if we have the __restrict keyword */ -#define RESTRICT __restrict +/* Define if we have pthread_mutexattr_setkind_np() */ +/* #undef HAVE_PTHREAD_MUTEXATTR_SETKIND_NP */ -#endif +/* Define if we have pthread_mutex_timedlock() */ +/* #undef HAVE_PTHREAD_MUTEX_TIMEDLOCK */ \ No newline at end of file From 756497fb92191ab8fe2ecaa5ca48f345e1b3a70a Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 22:18:19 -0500 Subject: [PATCH 08/13] Specify TARGET_PLATFORM properly (:= instead of =) --- jme3-android-native/src/native/jme_openalsoft/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-android-native/src/native/jme_openalsoft/Android.mk b/jme3-android-native/src/native/jme_openalsoft/Android.mk index fa620078e..cac2e4df8 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Android.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Android.mk @@ -1,4 +1,4 @@ -TARGET_PLATFORM=android-9 +TARGET_PLATFORM := android-9 LOCAL_PATH := $(call my-dir) From 883ff7da71d59d26d8baf24c0a182a9ee526905e Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 22:19:18 -0500 Subject: [PATCH 09/13] Apply same fixes from OpenAL Soft to STB image loader. Also make sure it works with the latest version from the website (which is downloaded automatically). --- .../src/native/jme_stbi/Android.mk | 16 +++++++++------- .../src/native/jme_stbi/Application.mk | 4 ++-- ...e3_texture_plugins_AndroidNativeImageLoader.c | 5 +++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/jme3-android-native/src/native/jme_stbi/Android.mk b/jme3-android-native/src/native/jme_stbi/Android.mk index ba8a4e64e..469c63e4d 100644 --- a/jme3-android-native/src/native/jme_stbi/Android.mk +++ b/jme3-android-native/src/native/jme_stbi/Android.mk @@ -1,13 +1,15 @@ +TARGET_PLATFORM := android-9 + LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) LOCAL_MODULE := stbijme -LOCAL_C_INCLUDES := $(LOCAL_PATH) -LOCAL_CFLAGS += -O2 -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) -LOCAL_SRC_FILES := $(subst $(LOCAL_PATH)/,, $(wildcard $(LOCAL_PATH)/*.c)) - -#adds zlib -LOCAL_LDLIBS += -lz -llog + +LOCAL_C_INCLUDES += $(LOCAL_PATH) + +LOCAL_LDLIBS := -lz -llog -Wl,-s + +LOCAL_SRC_FILES := com_jme3_texture_plugins_AndroidNativeImageLoader.c include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_stbi/Application.mk b/jme3-android-native/src/native/jme_stbi/Application.mk index fcdc69644..fbc028f84 100644 --- a/jme3-android-native/src/native/jme_stbi/Application.mk +++ b/jme3-android-native/src/native/jme_stbi/Application.mk @@ -1,3 +1,3 @@ +APP_PLATFORM := android-9 APP_OPTIM := release -APP_ABI := all -#APP_ABI := armeabi-v7a +APP_ABI := all \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c b/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c index 45dbed0b0..a4064a622 100644 --- a/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c +++ b/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c @@ -6,8 +6,9 @@ #include #include #include -#define STBI_HEADER_FILE_ONLY -#include "stb_image.c" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" typedef unsigned int uint32; From e1a3d2e79fd18c599b0bf76b006477ae5d23a4e6 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 22:20:17 -0500 Subject: [PATCH 10/13] Updated STB image build script --- jme3-android-native/stb_image.gradle | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/jme3-android-native/stb_image.gradle b/jme3-android-native/stb_image.gradle index e5a0c3af7..61286d0af 100644 --- a/jme3-android-native/stb_image.gradle +++ b/jme3-android-native/stb_image.gradle @@ -1,6 +1,6 @@ // stb_image url for download -String stbiUrl = 'http://www.nothings.org/stb_image.c' -String stbiDownloadTarget = 'stb_image.c' +String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h' +String stbiDownloadTarget = 'stb_image.h' // stb_image is not downloaded. The single source file is included in the repo String stbiFolder = 'stb_image' @@ -63,20 +63,21 @@ task generateStbiHeaders(dependsOn: copyStbiJmeFiles) << { // println "stb_image destDir = " + destDir // println "stb_image classpath = " + project.projectClassPath - ant.javah( - classpath: project.projectClassPath, - destdir: destDirPath, - class: classes - ) - + exec { + executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') + args '-d', destDirPath + args '-classpath', project.projectClassPath + args "com.jme3.texture.plugins.AndroidNativeImageLoader" + } } task buildStbiNativeLib(type: Exec, dependsOn: generateStbiHeaders) { -// println "stb_image build dir: " + buildLibDir -// println "ndkCommandPath: " + project.ndkCommandPath - args 'TARGET_PLATFORM=android-9' +// println "stb_image build dir: " + stbiBuildDir +// println "ndkCommandPath: " + rootProject.ndkCommandPath + workingDir stbiBuildDir executable rootProject.ndkCommandPath + args '-j8' } task updatePreCompiledStbiLibs(type: Copy, dependsOn: buildStbiNativeLib) { From 26d8ae8942ad943fba8288e748a8fe77bd8b36db Mon Sep 17 00:00:00 2001 From: shadowislord Date: Tue, 4 Nov 2014 22:25:13 -0500 Subject: [PATCH 11/13] android natives: add updated builds --- .../openalsoft/arm64-v8a/libopenalsoftjme.so | Bin 0 -> 510048 bytes .../armeabi-v7a/libopenalsoftjme.so | Bin 284192 -> 263740 bytes .../openalsoft/armeabi/libopenalsoftjme.so | Bin 304668 -> 280120 bytes .../libs/openalsoft/mips/libopenalsoftjme.so | Bin 415276 -> 565248 bytes .../openalsoft/mips64/libopenalsoftjme.so | Bin 0 -> 734248 bytes .../libs/openalsoft/x86/libopenalsoftjme.so | Bin 304964 -> 407436 bytes .../openalsoft/x86_64/libopenalsoftjme.so | Bin 0 -> 498056 bytes .../libs/stb_image/arm64-v8a/libstbijme.so | Bin 0 -> 79368 bytes .../libs/stb_image/armeabi-v7a/libstbijme.so | Bin 46256 -> 42164 bytes .../libs/stb_image/armeabi/libstbijme.so | Bin 54444 -> 50352 bytes .../libs/stb_image/mips/libstbijme.so | Bin 138988 -> 139104 bytes .../libs/stb_image/mips64/libstbijme.so | Bin 0 -> 115704 bytes .../libs/stb_image/x86/libstbijme.so | Bin 58564 -> 58560 bytes .../libs/stb_image/x86_64/libstbijme.so | Bin 0 -> 83656 bytes 14 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 jme3-android-native/libs/openalsoft/arm64-v8a/libopenalsoftjme.so mode change 100644 => 100755 jme3-android-native/libs/openalsoft/armeabi-v7a/libopenalsoftjme.so mode change 100644 => 100755 jme3-android-native/libs/openalsoft/armeabi/libopenalsoftjme.so mode change 100644 => 100755 jme3-android-native/libs/openalsoft/mips/libopenalsoftjme.so create mode 100755 jme3-android-native/libs/openalsoft/mips64/libopenalsoftjme.so mode change 100644 => 100755 jme3-android-native/libs/openalsoft/x86/libopenalsoftjme.so create mode 100755 jme3-android-native/libs/openalsoft/x86_64/libopenalsoftjme.so create mode 100755 jme3-android-native/libs/stb_image/arm64-v8a/libstbijme.so mode change 100644 => 100755 jme3-android-native/libs/stb_image/armeabi-v7a/libstbijme.so mode change 100644 => 100755 jme3-android-native/libs/stb_image/armeabi/libstbijme.so mode change 100644 => 100755 jme3-android-native/libs/stb_image/mips/libstbijme.so create mode 100755 jme3-android-native/libs/stb_image/mips64/libstbijme.so mode change 100644 => 100755 jme3-android-native/libs/stb_image/x86/libstbijme.so create mode 100755 jme3-android-native/libs/stb_image/x86_64/libstbijme.so diff --git a/jme3-android-native/libs/openalsoft/arm64-v8a/libopenalsoftjme.so b/jme3-android-native/libs/openalsoft/arm64-v8a/libopenalsoftjme.so new file mode 100755 index 0000000000000000000000000000000000000000..b85db58ecb9dd1c0bdd930bd671296b5eed923b0 GIT binary patch literal 510048 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

VZ{gMcQjLl~{LT2;F$*y4yJa+2~rY%q6tTnCoie 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