OpenGL ES着色器在较旧的手机上出现巨大的性能问题
我正在使用我的光手在一些手机上面临一些性能问题。 着色器根据正常图计算线条和点光线。 这也表明命中目标(这是一个2D自上而下的射手游戏)。OpenGL ES着色器在较旧的手机上出现巨大的性能问题
我正在开发索尼Xperia Z5 Compact,其中一切正常 就好。然后我在我非常古老的三星Galaxy S2上试了一下,其中 真的很慢。由于电话的年龄我不在乎。
现在我尝试了Galaxy S4和Galaxy S5,但它似乎并没有比Galaxy S2更快地运行 。我也有编辑时间(大约90秒),但我设法通过优化代码将它简化为几秒钟(尽管我认为它仍然很杂乱,但我并不真正注意着色器)。
我真的不知道这里的瓶颈是什么,以及为什么它没有在S4和S5上跑得更快。
这是我的着色器:
#ifdef GL_ES
#define LOWP lowp
precision highp float;
#else
#define LOWP
#endif
#define MAX_NUM_POINTLIGHTS 16
uniform vec3 lpos[MAX_NUM_POINTLIGHTS]; // Light position
uniform vec3 foff[MAX_NUM_POINTLIGHTS]; // Light falloff
uniform vec4 acol[MAX_NUM_POINTLIGHTS]; // Ambient color
uniform vec4 lcol[MAX_NUM_POINTLIGHTS]; // Light color
//textures
uniform sampler2D u_texture1; // diffuse texture
uniform sampler2D u_texture2; // normalmap
varying vec4 vColor;
varying vec2 vTexCoord;
varying float vFlags;
uniform vec2 Resolution; //resolution of screen
const float WORLD_WIDTH = 1440.0;
const float WORLD_HEIGHT = 2560.0;
vec3 getPointLightColor(const vec4);
vec3 rotateVector(const vec3 vector, const float angle);
vec2 screenCoordToWorldCoord(const vec2 screencoord);
vec3 calculatePointLight(const vec4 DiffuseColor, vec3 LightPos, const vec3 Falloff, const vec4 LightColor, const vec4 AmbientColor);
const float stdratio = WORLD_HEIGHT/WORLD_WIDTH;
vec2 worldFragCoord;
const float worldRatio_W_DIV_H = WORLD_WIDTH/WORLD_HEIGHT;
const vec2 worldSize = vec2(WORLD_WIDTH, WORLD_HEIGHT);
// Light variables
vec3 NormalMap;
vec2 worldFragCoordNormalized;
vec3 N;
void main() {
worldFragCoord = screenCoordToWorldCoord(gl_FragCoord.xy);
// Common light calculations
NormalMap = texture2D(u_texture2, vTexCoord).rgb;
worldFragCoordNormalized = worldFragCoord/vec2(1440.0, 2560.0);
N = normalize(NormalMap * 2.0 - 1.0);
vec4 DiffuseColor = texture2D(u_texture1, vTexCoord);
vec2 fragcoord = gl_FragCoord.xy;
vec3 pointcolor = getPointLightColor(DiffuseColor);
vec4 finalColor;
// green channel of vColor indicates hit
if (vColor.g > 0.0 && vColor.a == 0.0) {
vec4 fragcol = vec4(pointcolor, DiffuseColor.a);
vec4 addColor;
if (vColor.g > 0.67)
addColor = vec4(1.0,1.0,1.0, DiffuseColor.a*vColor.g);
else if (vColor.g > 0.52)
addColor = vec4(1.0,0.0,0.0, DiffuseColor.a*vColor.g);
else if (vColor.g > 0.37)
addColor = vec4(0.0,0.0,1.0, DiffuseColor.a*vColor.g);
else if (vColor.g > 0.22)
addColor = vec4(1.0,1.0,0.0, DiffuseColor.a*vColor.g);
else
addColor = vec4(0.0,1.0,1.0, DiffuseColor.a*vColor.g);
finalColor = addColor*addColor.a + fragcol*(1.0-addColor.a);
}
else
finalColor = vec4(pointcolor, DiffuseColor.a);
gl_FragColor = finalColor;
}
vec3 rotateVector(const vec3 vector, const float angle){
float degree = radians(360.0*angle); // Angle is normalized to 0 - 1
float cos_ = cos(degree);
float sin_ = sin(degree);
return vec3(vector.x*cos_ - vector.y*sin_, vector.x*sin_ + vector.y*cos_, vector.z);
}
vec3 calculatePointLight(const vec4 DiffuseColor, vec3 LightPos, const vec3 Falloff, const vec4 LightColor, const vec4 AmbientColor){
if (LightPos.x == 0.0 && LightPos.y == 0.0)
return vec3(0.0);
LightPos.xy = LightPos.xy/worldSize;
//The delta position of light
vec3 LightDir = vec3(LightPos.xy - worldFragCoordNormalized, LightPos.z);
//Correct for aspect ratio
LightDir.x *= worldRatio_W_DIV_H;
//Determine distance (used for attenuation) BEFORE we normalize our LightDir
float D = length(LightDir);
//normalize our vectors
vec3 L = normalize(LightDir);
vec3 NN = N;
if (vColor.a == 0.0)
NN = normalize(rotateVector(NN, vColor.r));
//Pre-multiply light color with intensity
//Then perform "NN dot L" to determine our diffuse term
vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(NN, L), 0.0);
//pre-multiply ambient color with intensity
vec3 Ambient = AmbientColor.rgb * AmbientColor.a;
//calculate attenuation
float Attenuation = 1.0/(Falloff.x + (Falloff.y*D) + (Falloff.z*D*D));
//the calculation which brings it all together
vec3 Intensity = Ambient + Diffuse * Attenuation;
vec3 FinalColor = DiffuseColor.rgb * Intensity;
return FinalColor;
}
vec3 getPointLightColor(const vec4 DiffuseColor){
vec3 sum = vec3(0.0);
for (int i = 0; i < MAX_NUM_POINTLIGHTS; i++)
{
sum += calculatePointLight(DiffuseColor, lpos[i], foff[i], lcol[i], acol[i]);
}
return sum;
}
vec2 screenCoordToWorldCoord(const vec2 screencoord){
float ratio = Resolution.y/Resolution.x;
vec2 resCoord;
if (ratio == stdratio){
// Ratio is standard
resCoord = screencoord * (WORLD_HEIGHT/Resolution.y);
} else if (ratio > stdratio) {
// Screen gets extended vertically (black bars top/bottom)
float screenheight = Resolution.x * stdratio;
float bottom = (Resolution.y - screenheight)/2.0;
resCoord = vec2(screencoord.x, screencoord.y - bottom);
resCoord *= (WORLD_WIDTH/Resolution.x);
} else {
// Screen gets extended horizontally (black bars left/right)
float screenwidth = Resolution.y/stdratio;
float left = (Resolution.x - screenwidth)/2.0;
resCoord = vec2(screencoord.x - left, screencoord.y);
resCoord *= (WORLD_HEIGHT/Resolution.y);
}
return resCoord;
}
这是我该怎么做才能解决它是什么:
删除
screenCoordToWorldCoord
。这是一个简单的转换,你可以用矩阵乘或者几个点积来完成,或者更好的是,将工作移动到顶点着色器,并将结果传递给变量而不是从gl_FragCoord
构造。为每个光照计数编译不同版本的着色器,并展开
for
循环。您也可以将if
放在calculatePointLight
的顶部。删除所有剩余的
if
语句 - 某些设备讨厌条件。用数学来代替逻辑,step函数有帮助。有什么方法可以丢弃
rotateVector
?我无法弄清楚它在做什么,但它很昂贵,并且觉得它在片段着色器中不应该是必需的。至少,它不需要在内部循环中,因为无论光线如何,结果都是相同的。使用某种矩阵乘法可能会更好,而不是使用sin
/cos
。正确使用精度。有些设备可以在
lowp
/mediump
比在highp
更快地做数学。经验法则 - 颜色为lowp
,法线为mediump
,职位为highp
。在CPU上做一些光线剔除。我想不是所有的灯都会影响每个像素。如果你可以将场景切成瓦片,只让最重要的灯光数过去,那么你可以做更少的工作。
LightPos.xy/worldSize
看起来你可以在CPU上做一次,而不是每个像素一次。
没有快速修复恐怕。
听起来很合理,谢谢。我会尽力改变这一切。函数rotateVector正在旋转法线贴图的法线信息 – Draz
我不知道任何实现'lowp'的现代设备与'mediump'有什么不同 - 它们都倾向于映射到fp16硬件。 – solidpixel
将尝试找到时间来消化其他地方比我的手机,但作为一般规则:循环和条件是麻烦,往往甚至与做不必要的工作,并通过一些组合夹具,步骤等有效地丢弃它。 – Tommy
我有这些循环展开之前,但我读了循环与常量就好了。你认为这些条件是否会导致这种情况? – Draz
是的 - 假设编译器很聪明(而且相当安全),如果需要的话,带有常量的循环会自动展开。添加一个'if(我
Tommy