I am trying to make "burning star" impression on android game I have been developing with little help of noise function (simplex noise in this case). Unfortunately I cant use 3d textures as they are in gles extension and android packages doesn't have them included.
Only option left for me is therefore calculating noise function in fragment shader. Code provided below runs smoothly or acceptably (20-60fps) on HTC Desire Z andLG optimus one. With same program on Motorola XOOM (which have tegra2 chipset) however I get fraction(1-3) of fps even when displaying only small part of object.
Thing we tried so far:
meddling with precision(lowp-higp), both in first line directive and specifying for each occurrence of float/vec separately
commenting parts of noise function - it seem that there isn't any particular bottleneck, its combination of all things together
googling problems related to tegra, floating point in shaders etc
This is stripped down part of code needed for reproduction of this behavior. Note that on XOOM there are some artifacts which we believe is caused by 16bit floating operations in tegra.
precision mediump float;
#define pi 3.141592653589793238462643383279
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x*34.0)+1.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v)
{
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
//Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
}
uniform vec3 color1;
uniform vec3 color2;
uniform float t;
varying vec3 vTextureCoord;
void main()
{
float t = 0.5; //mod(t, 3.0);
float x = (vTextureCoord.x)*2.0;
float y = -(vTextureCoord.y)*2.0;
float r = sqrt(x * x + y * y);
gl_FragColor = vec4(0.0,0.0,0.0,0.0);
if(r<= 1.0){
float n = snoise( vec3(vec2(x,y), Mr_T*3.3 ) );
gl_FragColor = vec4( mix(color1,color2, abs(n) ) ,1.0);
}
}
I was facing the same problem for tegra2 a while ago. Read 1.3 : http://www.opengl.org/wiki/GLSL_:_common_mistakes. and feel the pain. In my case fps went up twice but it still sucked.
Related
I have an OpenGL app with a simple shader that run well on an emulator device in Android Studio with API 30 but on my own hardware device (API 30) it doesn't.
The problem is in the fragment shader. This is the code:
#version 100
precision highp float;
struct DirLight {
int on;
vec3 direction;
vec3 ambientColor;
vec3 diffuseColor;
vec3 specularColor;
float specularExponent;
sampler2D shadowMap;
mat4 shadowVPMatrix;
int shadowEnabled;
};
struct PointLight {
int on;
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambientColor;
vec3 diffuseColor;
vec3 specularColor;
float specularExponent;
sampler2D shadowMap;
mat4 shadowVPMatrix;
int shadowEnabled;
};
#define MAX_NUM_POINT_LIGHTS 8
uniform DirLight uDirLight;
uniform PointLight uPointLights[MAX_NUM_POINT_LIGHTS];
uniform int uNumPointLights;
uniform vec3 uViewPos;
uniform sampler2D uTexture;
uniform int uIsTextured;
varying vec4 vColor;
varying vec4 vPosition;
varying vec3 vNormal;
varying vec2 vTexCoords;
const vec4 bitShifts = vec4(1.0 / (256.0*256.0*256.0), 1.0 / (256.0*256.0), 1.0 / 256.0, 1.0);
vec4 getColor(){
if (uIsTextured != 0){
return texture2D(uTexture,vTexCoords);
}
return vColor;
}
float unpack(vec4 color){
return dot(color, bitShifts);
}
// return 0.0 if in shadow.
// return 1.0 if not in shadow.
float calcShadow(sampler2D shadowMap, vec4 positionFromLight, int shadowEnabled){
if (shadowEnabled == 0){
return 1.0;
}
vec3 positionFromLight3 = positionFromLight.xyz / positionFromLight.w;
positionFromLight3 = (positionFromLight3 + 1.0) / 2.0;
float closestFragmentZ = unpack(texture2D(shadowMap, positionFromLight3.xy));
float currentFragmentZ = positionFromLight3.z;
return float(closestFragmentZ > currentFragmentZ);
}
float diffuseLighting(vec3 normal, vec3 lightDir){
return max(dot(normal, lightDir), 0.0);
}
float specularLighting(vec3 normal, vec3 lightDir, vec3 viewDir, float specularExponent){
vec3 reflectDir = reflect(-lightDir, normal);
return pow(max(dot(viewDir, reflectDir), 0.0), specularExponent);
}
vec4 calcDirLight(vec3 normal, vec3 viewDir){
vec3 lightDir = normalize(-uDirLight.direction);
float diff = diffuseLighting(normal, lightDir);
float spec = specularLighting(normal, lightDir, viewDir, uDirLight.specularExponent);
vec4 color = getColor();
vec4 ambient = vec4(uDirLight.ambientColor, 1.0) * color;
vec4 diffuse = vec4(uDirLight.diffuseColor * diff, 1.0) * color;
vec4 specular = vec4(uDirLight.specularColor * spec, 1.0) * vec4(0.5,0.5,0.5,1.0);
return ambient + (diffuse + specular) * calcShadow(uDirLight.shadowMap, uDirLight.shadowVPMatrix * vPosition, uDirLight.shadowEnabled);
}
float calcAttenuation(PointLight pointLight, float distance){
return 1.0 / (pointLight.constant + pointLight.linear * distance + pointLight.quadratic * (distance * distance));
}
vec4 calcPointLight(PointLight pointLight, vec3 normal, vec3 viewDir){
vec3 d = pointLight.position - vec3(vPosition);
vec3 lightDir = normalize(d);
float diff = diffuseLighting(normal, lightDir);
float spec = specularLighting(normal, lightDir, viewDir, pointLight.specularExponent);
float distance = length(d);
float attenuation = calcAttenuation(pointLight,distance);
vec4 color = getColor();
vec4 ambient = vec4(pointLight.ambientColor, 1.0) * color;
vec4 diffuse = vec4(pointLight.diffuseColor * diff, 1.0) * color;
vec4 specular = vec4(pointLight.specularColor * spec, 1.0) * vec4(0.5,0.5,0.5,1.0);
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return ambient + (diffuse + specular) * calcShadow(pointLight.shadowMap, pointLight.shadowVPMatrix * vPosition, pointLight.shadowEnabled);
}
void main() {
vec3 normal = normalize(vNormal);
vec3 viewDir = normalize(uViewPos - vec3(vPosition));
vec4 result = vec4(0.0);
if (uDirLight.on == 1){
result = calcDirLight(normal, viewDir);
}
for (int i = 0; i < uNumPointLights; i++){
if (uPointLights[i].on == 1){
result += calcPointLight(uPointLights[i], normal, viewDir);
}
}
gl_FragColor = result;
}
When I run the app on my device logcat shows the following lines
2021-06-24 17:49:14.032 2061-2096/com.outofbound.rhinoengine I/AdrenoGLES-0: Build Config : S P 10.0.7 AArch64
2021-06-24 17:49:14.032 2061-2096/com.outofbound.rhinoengine I/AdrenoGLES-0: Driver Path : /vendor/lib64/egl/libGLESv2_adreno.so
2021-06-24 17:49:14.036 2061-2096/com.outofbound.rhinoengine I/AdrenoGLES-0: PFP: 0x016ee190, ME: 0x00000000
2021-06-24 17:49:14.040 2061-2061/com.outofbound.rhinoengine D/SurfaceView: UPDATE null, mIsCastMode = false
2021-06-24 17:49:14.074 2061-2102/com.outofbound.rhinoengine I/AdrenoGLES-0: ERROR: 0:101: 'viewDir' : undeclared identifier
ERROR: 0:101: 'specularLighting' : no matching overloaded function found
ERROR: 2 compilation errors. No code generated.
2021-06-24 17:49:14.075 2061-2102/com.outofbound.rhinoengine I/AdrenoGLES-0: ERROR: 0:101: 'viewDir' : undeclared identifier
ERROR: 0:101: 'specularLighting' : no matching overloaded function found
ERROR: 2 compilation errors. No code generated.
2021-06-24 17:49:15.316 2061-2085/com.outofbound.rhinoengine W/System: A resource failed to call close.
BUT if I simply rename viewDir to v in main() function
void main() {
vec3 normal = normalize(vNormal);
vec3 v = normalize(uViewPos - vec3(vPosition));
vec4 result = vec4(0.0);
if (uDirLight.on == 1){
result = calcDirLight(normal, v);
}
for (int i = 0; i < uNumPointLights; i++){
if (uPointLights[i].on == 1){
result += calcPointLight(uPointLights[i], normal, v);
}
}
gl_FragColor = result;
}
the error above disappears but the app still doesn't work showing a black screen.
Any tips?
It looks to me that the viewDir issue is a driver bug where it's messed up trying to inline your code.
However, you should be aware that is not a simple shader by OpenGLES 2 standards. As Dpk implied, you cannot assume high precision is available in OpenGLES2.
Additionally, you cannot assume that there's anywhere near enough uniform space for your shader. Try using glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &maxFragmentUniforms); to see how many uniforms are supported. Devices are allowed to go as low as 16 vec4s, but your shader uses 100s.
I'd suggest you consider switching to OpenGLES 3 or 3.1 if you don't want to worry about some of the tight limits of GLES2. If you persist with OpenGLES2 then maybe cut the shader right back to literally nothing (just return a colour) and gradually build up the functionality.
Also, make sure you are checking for errors on shader compilation and linking and all OpenGLES calls, it can save a lot of time.
try
//#version 100
//precision highp float;
precision mediump float;
and try this
opengles20 may not support INT in param see doc
float on;
//if (uDirLight.on == 1){
if (uDirLight.on == 1.0){
I think the error is related to the array of uniform uniform PointLight uPointLights[MAX_NUM_POINT_LIGHTS];. So I solved using one point light
uniform PointLight uPointLight;.
Anyway I'll try if defining multiple uniform PointLight uPointLightN; with 0 <= N < MAX_NUM_POINT_LIGHTS it still works.
The following is the GLSL Fragment Shader code I'm concerned about:
#extension GL_OES_EGL_image_external : require
precision lowp float;
varying highp vec2 v_TexCoordinate;
uniform samplerExternalOES u_Texture;
uniform float uParamValue1; // hue
uniform float uParamValue2; // hue of replacing color
const float delta = 0.1;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy);
vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx);
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main()
{
vec3 texel = texture2D(u_Texture, v_TexCoordinate).rgb;
vec3 texelHsv = rgb2hsv(texel);
if(!(abs(texelHsv.x - uParamValue1) < delta))
{
texel = vec3(dot(vec3(0.299, 0.587, 0.114), texel));
//texel = vec3(texture2D(inputImageTexture2, vec2(texel.r, .16666)).r);
}
else
{
texelHsv.x = uParamValue2;
texel = hsv2rgb(texelHsv);
}
gl_FragColor = vec4(texel, 1.0);
}
The value of uParamValue1 and uParamValue2 is changed via two seekbars.
When I checked the uniform locations of uParamValue1 and uParamValue2, they returned valid uniforms on both my Galaxy S6 and Xiaomi Mi 3W, 1 and 2 respectively.
However, when I move the slider that corresponds to uParamValue1, the shader doesn't seem to respond to the changes in Xiaomi Mi 3W, whereas in the Galaxy S6, it works fine.
Why is this, and how can I prevent it from happening?
I am developing OpenGLES app for Android and using Perlin noise from Stefan Gustavson. It's animated 2D noise so I use 3D Perlin with time variable as third dimension. And it was all looking good on my Samsung Galaxy Young (API 10), but when I tested it with ASUS MEMO tablet (API 17) I got this glitchy thing with even elipses and square-like areas:-
What could cause such differences between various devices?
Fragment shader code:
precision mediump float;
varying vec2 screenPosition;
uniform vec4 colorFilter;
uniform float time;
vec4 permute(vec4 x)
{
return mod(((x*34.0)+1.0)*x, 289.0);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
vec3 fade(vec3 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float noise(vec3 P)
{
vec3 Pi0 = floor(P); // Integer part for indexing
vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
Pi0 = mod(Pi0, 289.0);
Pi1 = mod(Pi1, 289.0);
vec3 Pf0 = fract(P); // Fractional part for interpolation
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 gx0 = ixy0 / 7.0;
vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
vec4 gx1 = ixy1 / 7.0;
vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
g000 *= norm0.x;
g010 *= norm0.y;
g100 *= norm0.z;
g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
g001 *= norm1.x;
g011 *= norm1.y;
g101 *= norm1.z;
g111 *= norm1.w;
float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
float n111 = dot(g111, Pf1);
vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
return 2.2 * n_xyz;
}
void main() {
float valuer = (noise(vec3(screenPosition.x*4.0, screenPosition.y*3.0, time)));
float val1 = 0.25*(sign(valuer+0.2)-sign(valuer-0.2));
float val2 = 0.125*(sign(valuer+0.4)-sign(valuer-0.4));
outputColor = colorFilter*(val1+val2+0.25);
}
Most likely the Samsung Galaxy Young is executing the math at highp, and the ASUS MEMO tablet is executing at mediump.
OpenGLES devices are not required to support highp in fragment shaders, but the above code looks very much like it will be required.
Is that even a full fragment shader? I thought GLES fragment shaders required you to specify a default precision or required you to specify precision on a per-variable basis. I see neither, so perhaps there is some information omitted.
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?
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):
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.
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!
I´m doing my own game engine. Now, the next step is to build my fragment shader for multiple lighting sources.
I found a very strange behaviour I can't understand. In my Moto G 2014 with an 305 Adreno video chip a glsl length function call gives me an incorrect value over a ambient lighting uniform, resulting in wrong scene lighting.
Let's see for fist the fragment code:
#define numLights x
#pragma glsl
precision lowp float;
struct LightSourceParameters {
vec3 ambient;
vec3 lightColor;
vec4 position;
float spotExponent;
float spotCutoff; // (range: [0.0,90.0], 180.0)
vec3 spotDirection;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
};
uniform LightSourceParameters LightSource[numLights];
struct MaterialParameters {
vec4 emission;
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
bool hasDiffuseTexture;
bool hasSpecularTexture;
bool hasEmissionTexture;
bool hasAmbientTexture;
bool hasNormalTexture;
sampler2D emissionTexture;
sampler2D diffuseTexture;
sampler2D specularTexture;
sampler2D ambientTexture;
sampler2D normalTexture;
};
uniform MaterialParameters Material;
precision lowp float;
varying vec2 varyingTextcood;
varying vec3 varyingNormalDirection;
varying vec3 varyingViewDirection;
varying vec3 outLightVector[numLights];
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection = normalize(varyingViewDirection);
vec3 lightDirection;
float attenuation;
// initialize total lighting with ambient lighting
vec4 totalLighting;
vec4 emissionTerm;
if ((length(Material.emission) != 0.0) || (Material.hasEmissionTexture)) {
/* El material tiene un termino emisivo, es decir, emite luz. Lo andimos al total de color calculado */
if (!Material.hasEmissionTexture) {
emissionTerm = Material.emission.rgba;
}
else {
emissionTerm = texture2D(Material.emissionTexture, varyingTextcood).rgba;
}
if (emissionTerm.a > 0.0){
totalLighting = emissionTerm;
}
}
for (int index = 0; index < numLights; index++) // for all light sources
{
vec4 ambientalTerm;
vec4 specularReflection;
vec4 diffuseReflection;
if (length(LightSource[index].ambient.rgb) > 0.0){
// es luz ambiental
if (Material.hasAmbientTexture){
ambientalTerm = vec4(LightSource[index].ambient, 1.0) * texture2D(Material.ambientTexture, varyingTextcood);
}
else {
ambientalTerm = vec4(LightSource[index].ambient, 1.0) * vec4(Material.ambient);
}
//totalLighting = vec4(0.0,1.0,0.0,1.0);
}
else {
if (0.0 == LightSource[index].position.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(outLightVector[index]);
}
else // point light or spotlight (or other kind of light)
{
vec3 positionToLightSource = outLightVector[index];
float distance = length(positionToLightSource);
lightDirection = normalize(positionToLightSource);
attenuation = 1.0 / (LightSource[index].constantAttenuation
+ LightSource[index].linearAttenuation * distance
+ LightSource[index].quadraticAttenuation * distance * distance);
if (LightSource[index].spotCutoff <= 90.0) // spotlight?
{
float clampedCosine = max(0.0, dot(-lightDirection, normalize(LightSource[index].spotDirection)));
if (clampedCosine < cos(radians(LightSource[index].spotCutoff))) // outside of spotlight cone?
{
attenuation = 0.0;
}
else
{
attenuation = attenuation * pow(clampedCosine, LightSource[index].spotExponent);
}
}
}
vec4 diffuseTerm;
if (Material.hasDiffuseTexture){
diffuseTerm = texture2D(Material.diffuseTexture,varyingTextcood);
}
else {
diffuseTerm = Material.diffuse;
}
if (diffuseTerm.a > 0.0){
diffuseReflection = attenuation
* vec4(LightSource[index].lightColor, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
}
if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
{
specularReflection = vec4(0.0, 0.0, 0.0, 0.0); // no specular reflection
}
else // light source on the right side
{
vec4 specularTerm;
if (Material.hasSpecularTexture){
specularTerm = texture2D(Material.specularTexture,varyingTextcood);
}
else {
specularTerm = Material.specular;
}
if (specularTerm.a > 0.0){
/* OPCION SIN HALFVECTOR
specularReflection = attenuation * vec4(LightSource[index].lightColor,1.0) * specularTerm
* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), Material.shininess);
*/
// OPCION CON HALFVECTOR
vec3 light_half_vector = normalize(outLightVector[index] + viewDirection);
specularReflection = attenuation * vec4(LightSource[index].lightColor,1.0) * specularTerm
* pow(max(dot(light_half_vector, normalDirection), 0.0), Material.shininess);
}
}
}
totalLighting = totalLighting + ambientalTerm + diffuseReflection + specularReflection;
}
gl_FragColor = clamp(totalLighting, 0.0, 1.0);
}
Well, inside the main function, if we look at the foor loop, we have this line:
if (length(LightSource[index].ambient.rgb) > 0.0){
I found that debbuging with my Moto G, the shader always enter inside this statement, no matter that the scene hasn't an ambient light source. I can test this easyly writing totalLighting = vec4(1.0) inside the if branch.
This is not happening in the Adreno Profiler. I can't understand what's wrong because the profiler connects to the Moto G GPU and retrieves all the uniforms value, and ambient is a vec3 of 0.0 values. Even if I take a screen capture of the same device in the profiler I get the expected lighting behaviour in the profiler and a wrong behaviour in the phone. Shouldn't it be the same in both sites if they are connected?
As a curiosity, if I change the order of the declarations inside the LightSourceParameters I get very diferent results, and I can't understand why. For example, take a look at he screen capture I upload, all the scene gets red that is the color I'm using to clear the screen when the scene has been rendered with
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
It's red for debbuging prupourses.
This is the original image in the moto g phone, in the usual declaration of the uniforms I have:
The next one is the capture I get if I move the vec3 ambient declaration to the end of the LightSourceParameter struct:
And this is the profiler capture where you can see the value of the uniform. Doesn't matter if the ambient declaration is at the begining of the struct or the end, the result is the same, as I would expect in the phone:
Does somebody knows what is wrong here or what I'm missunderstanding ?
Edit 1:
Commenting out the if statement of the ambient light:
//if (0.0 < length(LightSource[index].ambient)){
I allow the flow to go ahead and calculate the diffuse and specular light/material data. This is not optimal, but it´s a way to debug so I´m going to use it for now. The scene keeps black except the yellow sun rays (like the first image) until I substitute the light color in the diffuse calculation with a vec3(1.0) in this way:
/*
diffuseReflection = attenuation
* vec4(LightSource[index].lightColor, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
*/
diffuseReflection = attenuation
* vec4(1.0,1.0,1.0, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
This way the diffuse term is calculated as if It was being done in the Adreno profiler and the image is rendered well. So my supossition is that the full array of light struct has garbage in it or wrong data, but I cant understand why this is only happening in the moto g.