简介:
在效果开发过程中,我们会遇到各种问题导致所开发的效果性能不尽人意,不同效果的针对优化策略不同,这里就不过于赘述,选取两个比较典型的较为复杂和消耗性能的效果,毛玻璃马赛克和磨砂玻璃马赛克效果进行举例优化
一.毛玻璃效果
该效果实现如下,实现具体原理这里不描述,有兴趣的读者可以私信,可以写一篇关于这种效果的实现
precision highp float;
varying vec2 texcoordOut;
uniform sampler2D texSampler;
const float pi = 3.1415926
// 生成具有周期性的2D向量噪声
vec2 hash( vec2 p ) { p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))); return fract(sin(p)*18.5453); }
// 使用 Worley noise 方法生成基于网格的2D噪声
float simplegridnoise(vec2 v)
{
float s = 1. / 256.;
vec2 fl = floor(v), fr = fract(v);
float mindist = 1e9;
for(int y = -1; y <= 1; y++)
for(int x = -1; x <= 1; x++)
{
vec2 offset = vec2(x, y);
vec2 pos = .5 + .5 * cos(2. * pi * (1.0*.1 + hash(fl+offset)) + vec2(0,1.6));
mindist = min(mindist, length(pos+offset -fr));
}
return mindist;
}
// 生成更平滑的噪声效果,通过控制s参数可以调整噪声的尖锐程度
float blobnoise(vec2 v, float s)
{
return pow(.5 + .5 * cos(pi * clamp(simplegridnoise(v)*2., 0., 1.)), s);
}
// 生成带有噪声的法线向量。这用于在纹理扰动中增强光照效果
vec3 blobnoisenrm(vec2 v, float s)
{
vec2 e = vec2(.01,0);
return normalize(
vec3(blobnoise(v + e.xy, s) - blobnoise(v -e.xy, s),
blobnoise(v + e.yx, s) - blobnoise(v -e.yx, s),
1.0));
}
// 一种组合方法,根据多次运算的结果生成更自然和复杂的噪声效果
float blobnoises(vec2 uv, float s)
{
float h = 0.0;
const float n = 3.0;
for(float i = 0.0; i < n; i++)
{
vec2 p = vec2(0.0, 1.0 * 1.0 * (i + 1.0) / n) + 1.0 * uv;
h += pow(0.5 + 0.5 * cos(pi * clamp(simplegridnoise(p * (i + 1.0)) * 2.0, 0.0, 1.0)), s);
}
return h / n;
}
// 针对组合噪声效果的法线生成方法
vec3 blobnoisenrms(vec2 uv, float s)
{
float d = 0.01;
return normalize(
vec3(blobnoises(uv + vec2( d, 0.0), s) - blobnoises(uv + vec2( -d, 0.0), s),
blobnoises(uv + vec2(0.0, d), s) - blobnoises(uv + vec2(0.0, -d), s),
d));
}
void main() {
vec2 uv = texcoordOut;
vec3 n = blobnoisenrms(55.0 * uv, 1.);
gl_FragColor = texture2D(texSampler, uv + 0.01 * n.xy);
} ;
分析:其实不难看出,几乎所有都操作都是在计算坐标,而没有对 rgb 值进行修改,分析得出卡顿的原因是由于计算次数过多,导致性能受到影响,而由于原图的坐标是固定的,因此这些计算其实是可以避免的因此有以下两种方案:
1.我们只需要再使用一个着色器,先对这些坐标进行一次计算,然后以 framebuffer 的形式保存,执行效果的时候每次传入计算好的坐标,即可省去大量重复的计算
2.采取空间换时间,直接在外部计算好这些坐标,生成一张纹理图,这样就可以省去所有计算次数
对比之后发现,1.2 两种方法在相同情况下的时间消耗差别不大,2 会略胜 1,但是直接使用生成的纹理图不仅会增加空间成本,还会由于高画质和屏幕适配比问题导致效果收到一定影响,因此考虑选用方案一,优化如下:
1.先对该效果进行预处理,保存需要处理的坐标:
varying vec2 texcoordOut;
uniform sampler2D texture;
const float pi = 3.1415926;
vec2 hash( vec2 p )
{
p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3)));
return fract(sin(p)*18.5453);
}
float simplegridnoise(vec2 v)
{
float s = 1. / 256.;
vec2 fl = floor(v);
vec2 fr = fract(v);
float mindist = 1e9;
for(int y = -1; y <= 1; y++)
for(int x = -1; x <= 1; x++)
{
vec2 offset = vec2(x, y);
vec2 pos = .5 + .5 * cos(2. * pi * (1.0*.1 + hash(fl+offset)) + vec2(0,1.6));
mindist = min(mindist, length(pos+offset -fr));
}
return mindist;
}
float blobnoise(vec2 v, float s)
{
return pow(.5 + .5 * cos(pi * clamp(simplegridnoise(v)*2., 0., 1.)), s);
}
vec3 blobnoisenrm(vec2 v, float s)
{
vec2 e = vec2(.01,0);
return normalize(vec3(blobnoise(v + e.xy, s) - blobnoise(v -e.xy, s),blobnoise(v + e.yx, s) - blobnoise(v -e.yx, s),1.0));
}
float blobnoises(vec2 uv, float s)
{
float h = 0.0;
const float n = 3.0;
for(float i = 0.0; i < n; i++)
{
vec2 p = vec2(0.0, 1.0 * 1.0 * (i + 1.0) / n) + 1.0 * uv;
h += pow(0.5 + 0.5 * cos(pi * clamp(simplegridnoise(p * (i + 1.0)) * 2.0, 0.0, 1.0)), s);
}
return h / n;
}
vec3 blobnoisenrms(vec2 uv, float s)
{
float d = 0.01;
return normalize(
vec3(blobnoises(uv + vec2( d, 0.0), s) - blobnoises(uv + vec2( -d, 0.0), s),blobnoises(uv + vec2(0.0, d), s) - blobnoises(uv + vec2(0.0, -d), s),d));
}
void main() {
vec4 temp = texture2D(texture,texcoordOut);
vec2 uv = texcoordOut;
vec3 n = blobnoisenrms(55.0 * uv, 1.);
gl_FragColor = vec4(n,1.0);
}
2.然后将该坐标保存成纹理,再做效果
varying vec2 texcoordOut;
uniform sampler2D texture;
uniform sampler2D texture1;
void main() {
vec2 uv = texcoordOut;
vec3 n = texture2D(texture1,texcoordOut).rgb;//计算好的坐标
gl_FragColor =vec4(texture2D(texture, uv + 0.01 * n.xy).rgb, 1.0);
}
经测试,该效果在 4k的分辨率的图片下,性能提升能达到 10倍以上,2k 情况下能达到 500%
二.磨砂玻璃效果:
该效果实现 shader 如下:
precision highp float;
varying vec2 texcoordOut;
uniform sampler2D texture;
uniform float width;
uniform float height;
void main()
{
float one_inv_width = 1.0/width;
float one_inv_height = 1.0/height;
vec4 textureColor = texture2D(texture, texcoordOut);
float this_weight = (textureColor.r + textureColor.g + textureColor.b) / 3.0;
vec4 total_color = vec4(0.0);
float total_weight = 0.0;
int radius = 3;
int radius_2 = 7;//radius*2 + 1
float i = 0.0;
float j = 0.0;
for(int k=0; k<49; k++)
{
i = floor(float(k)/7.0);
j = float(k) - i*7.0;
i -= float(3.0);
j -= float(3.0);
vec2 coord = texcoordOut + vec2(float(i)*one_inv_width, float(j)*one_inv_height);
vec4 color = texture2D(texture, coord);
float weight= exp((i*i + j*j));
total_color += color * weight;
total_weight += weight;
}
gl_FragColor = total_color/total_weight;
}
分析:通过查看着色器,我们可以发现,其实着色器内容并不是很多,在思考后,发现着色器内部可优化的空间较小,每次计算都是有意义的,只能考虑改进 for 循环的次数,或者合并部分计算公式,但是尝试后发现优化效果都不明显,优化幅度只能达到 3%-5%,再次分析卡顿原因,发现应该是导入的图片 size 过大,导致需要计算的像素点过多,计算次数量大,当几个相同效果叠加时,会导致卡顿,由于效果是马赛克,原图作用后是有模糊感的,于是考虑 针对性优化,当画质较高的时候,可以采用压缩 framebuffer,当采用当画质大于 2K时,将原图绘制在 压缩后的 buffrer上,这样即可在牺牲小部分效果的情况下,换区极大幅度的性能提升
总结:
本文简单描述了针对部分较为复杂的效果,针对其实现方式和原理,我们可以使用不同的方法和思路进行优化,这仅仅只是针对 2.0 的效果,对于一些外部内部优化空间都小的情况,在设备支持的情况下,我们可以使用 OPENGL3.0,并使用GLSL3.0语法代替 ,3.0 具有很多特性(比如inout,MRT 等),这里只介绍两个基本优化思路,后续读者有兴趣,我写一篇 GL3.0的文章分享给大家