koca2000
koca2000

Reputation: 69

1 FPS when using shader in Libgdx on Android

I am creating game in Libgdx and I'm using OpenGL water shader.

On desktop everything works fine (60 fps without V-Sync), but on Android I have only 1 FPS (tested on Samsung Galaxy S3 Neo and HTC One).

My fragment shader:

#ifdef GL_ES
precision mediump float;
#endif

uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iGlobalTime;           // shader playback time (in seconds)

const int NUM_STEPS = 8;
const float PI      = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM   = 0.1 / iResolution.x;

// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 4.0;
const float SEA_SPEED = 0.8;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
float SEA_TIME = iGlobalTime * SEA_SPEED;
mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);

// math
mat3 fromEuler(vec3 ang) {
    vec2 a1 = vec2(sin(ang.x),cos(ang.x));
    vec2 a2 = vec2(sin(ang.y),cos(ang.y));
    vec2 a3 = vec2(sin(ang.z),cos(ang.z));
    mat3 m;
    m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
    m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
    m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
    return m;
}
float hash( vec2 p ) {
    float h = dot(p,vec2(127.1,311.7)); 
    return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
    vec2 i = floor( p );
    vec2 f = fract( p );    
    vec2 u = f*f*(3.0-2.0*f);
    return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), hash( i + vec2(1.0,0.0) ), u.x), mix( hash( i + vec2(0.0,1.0) ), hash( i + vec2(1.0,1.0) ), u.x), u.y);
}

// lighting
float diffuse(vec3 n,vec3 l,float p) {
    return pow(dot(n,l) * 0.4 + 0.6,p);
}
float specular(vec3 n,vec3 l,vec3 e,float s) {    
    float nrm = (s + 8.0) / (3.1415 * 8.0);
    return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}

// sky
vec3 getSkyColor(vec3 e) {
    e.y = max(e.y,0.0);
    vec3 ret;
    ret.x = pow(1.0-e.y,2.0);
    ret.y = 1.0-e.y;
    ret.z = 0.6+(1.0-e.y)*0.4;
    return ret;
}
// sea
float sea_octave(vec2 uv, float choppy) {
    uv += noise(uv);        
    vec2 wv = 1.0-abs(sin(uv));
    vec2 swv = abs(cos(uv));    
    wv = mix(wv,swv,wv);
    return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}

float map(vec3 p) {
    float freq = SEA_FREQ;
    float amp = SEA_HEIGHT;
    float choppy = SEA_CHOPPY;
    vec2 uv = p.xz; uv.x *= 0.75;

    float d, h = 0.0;    
    for(int i = 0; i < ITER_GEOMETRY; i++) {        
        d = sea_octave((uv+SEA_TIME)*freq,choppy);
        d += sea_octave((uv-SEA_TIME)*freq,choppy);
        h += d * amp;        
        uv *= octave_m; freq *= 1.9; amp *= 0.22;
        choppy = mix(choppy,1.0,0.2);
    }
    return p.y - h;
}

float map_detailed(vec3 p) {
    float freq = SEA_FREQ;
    float amp = SEA_HEIGHT;
    float choppy = SEA_CHOPPY;
    vec2 uv = p.xz; uv.x *= 0.75;

    float d, h = 0.0;    
    for(int i = 0; i < ITER_FRAGMENT; i++) {        
        d = sea_octave((uv+SEA_TIME)*freq,choppy);
        d += sea_octave((uv-SEA_TIME)*freq,choppy);
        h += d * amp;        
        uv *= octave_m; freq *= 1.9; amp *= 0.22;
        choppy = mix(choppy,1.0,0.2);
    }
    return p.y - h;
}

vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {  
    float fresnel = 1.0 - max(dot(n,-eye),0.0);
    fresnel = pow(fresnel,3.0) * 0.65;

    vec3 reflected = getSkyColor(reflect(eye,n));    
    vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12; 

    vec3 color = mix(refracted,reflected,fresnel);

    float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
    color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;

    color += vec3(specular(n,l,eye,60.0));

    return color;
}

// tracing
vec3 getNormal(vec3 p, float eps) {
    vec3 n;
    n.y = map_detailed(p);    
    n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
    n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
    n.y = eps;
    return normalize(n);
}

float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {  
    float tm = 0.0;
    float tx = 10000.0;
    float hx = map(ori + dir * tx);
    if(hx > 0.0) return tx;   
    float hm = map(ori + dir * tm);    
    float tmid = 0.0;
    for(int i = 0; i < NUM_STEPS; i++) {
        tmid = mix(tm,tx, hm/(hm-hx));                   
        p = ori + dir * tmid;                   
        float hmid = map(p);
        if(hmid < 0.0) {
            tx = tmid;
            hx = hmid;
        } else {
            tm = tmid;
            hm = hmid;
        }
    }
    return tmid;
}

// main
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
    vec2 uv = fragCoord.xy / iResolution.xy;
    uv = uv * 2.0 - 1.65;
    uv.x *= iResolution.x / iResolution.y;    
    float time = iGlobalTime * 0.3;

    // ray
    vec3 ang = vec3(0,0,0);
    vec3 ori = vec3(0.0,20,0);
    vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;
    dir = normalize(dir) * fromEuler(ang);

    // tracing
    vec3 p;
    heightMapTracing(ori,dir,p);
    vec3 dist = p - ori;
    vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
    vec3 light = normalize(vec3(0.0,1.0,0.8)); 

    // color
    vec3 color = mix(
        getSkyColor(dir),
        getSeaColor(p,n,light,dir,dist),
        pow(smoothstep(0.0,-0.05,dir.y),.3));

    // post
    fragColor = vec4(pow(color,vec3(0.75)), 1.0);
}

void main() {
    vec4 color;
    mainImage(color, gl_FragCoord.xy);

    color.w = 1.0;
    gl_FragColor = color;
}

My vertex shader:

uniform mat4 u_projTrans;
varying vec4 v_color;
varying vec2 v_texCoords;
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;

void main() {
  v_color = a_color;
  v_texCoords = a_texCoord0;
  gl_Position = u_projTrans * a_position;
}

My Libgdx code:

private Mesh mesh;
private ShaderProgram shader;
private float time = 0;
private OrthographicCamera camera =  new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

@Override
public void show() {
    shader = new ShaderProgram(Gdx.files.internal("seaVertex.txt"), Gdx.files.internal("seaFragment.txt"));
    shader.pedantic = false;
    mesh = new Mesh(true, 4, 6, new VertexAttribute(Usage.Position, 2, "a_position"));
    mesh.setVertices(new float[]{-Gdx.graphics.getWidth() / 2, -Gdx.graphics.getHeight() / 2,
                                 Gdx.graphics.getWidth() / 2, -Gdx.graphics.getHeight() / 2,
                                 -Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2,
                                 Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2});
    mesh.setIndices(new short[]{0, 2, 3, 0, 3, 1});

@Override
public void render(float delta) {
    Gdx.gl.glClearColor(0, 0, 1, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    time += delta;
    if (shader.isCompiled()){
        shader.begin();
        shader.setUniformMatrix("u_projTrans", camera.combined);
        shader.setUniformf("iGlobalTime", time);
        shader.setUniformf("iResolution", new Vector3(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), 0));
        mesh.render(shader, GL20.GL_TRIANGLES);
        shader.end();
    }
}

Any ideas?

Upvotes: 1

Views: 338

Answers (1)

F. Oliveira
F. Oliveira

Reputation: 31

I am not familiar with Libgdx but I have a basic knowledge of OpenGL and graphics rendering in general(and I worked with this shader once) so here are my thoughts (I'm guessing you don't have a deep understand of the method this shader uses to generate water):

  1. Your current "water shader" uses Raymarch wich is a very expensive way to make graphics and is usually used for demo scenes and some basic elements in most graphics scenes. Noise functions are very expensive and you are currently calling it a bunch of times for every ray, wich is also expensive because the raymarch procedure needs to call it a bunch of times to detect intersection with the water.
  2. You are making very hard to create a game using this approach since combination of raymarched elements with tradicional OpenGL rendering system is not trivial, unless you are willing to work the hole game in procedural form, wich is also difficult.

I suggest you try other ways to render water that are not procedural, take a look at "thinMatrix" series on generating water in OpenGL, he uses Java but it should not be difficult to port it : https://www.youtube.com/watch?v=HusvGeEDU_U&list=PLRIWtICgwaX23jiqVByUs0bqhnalNTNZh

Good Luck!

Upvotes: 1

Related Questions