因为项目中有换装系统,换装系统中有大量的可替换的脸部数据,比如眉毛,睫毛,腮红,眼影,口红等,需要十多个贴图通道。所以我们的shader中需要有十多张贴图通道
跑再android机器上时,一切都正常显示。但跑再iphone8时,就会发现材质丢失了
然后查到的报错信息是采样次数过多,机器不支持这个采样次数。iphone8是9个
基于这个问题,有两种解决方案:
第一种:
把多个贴图合并,也就是再到gpu之前先把所有贴图合并成一个贴图,然后给gpu渲染一次。
var pixels = tex.GetPixels32();
for (int j = 0; j < pixels.Length; j++)
{
if (pixels[j].a != 0)
{
if (j < colors.Length)
{
var realJ = j;
var newPixel = pixels[realJ] * (color * 255);
var curColors = colors[realJ];
var dstColors = Color32.Lerp(curColors, newPixel, (float)newPixel.a / 255.0f);
colors[realJ] = new Color32((byte)(curColors.r > 0 ? (dstColors.r) : newPixel.r),
(byte)(curColors.g > 0 ? (dstColors.g) : newPixel.g),
(byte)(curColors.b > 0 ? (dstColors.b) : newPixel.b),
(byte)(curColors.a > 0 ? (dstColors.a) : newPixel.a));
}
}
}
写入shader:
var faceOtherShader = Shader.PropertyToID("_FaceOtherMap");
bodymat.SetTexture(faceOtherShader, texture2d);
优点:这样减少了贴图数量,消耗也更少了,带宽消耗自然也更小了。
缺点:但这样做的问题是每次换装时都需要合并一次贴图,会有明显的卡顿,效果也并不是很好。
第二种:
分两个材质放贴图
也就是第一个做光照模型的渲染,第二个做贴图渲染
尽量让两个材质的贴图采样个数都小于9个。让光照模型材质(AvatarMat)先渲染,贴图渲染(AvatarFaceTexture)后渲染。
然后跑到iphone8上,确实不会出现材质丢失的紫色了。
但是出现了一个问题:
就是腮红,嘴唇等都会闪烁。
然后看了下这张rt的alpha通道
越白说明alpha越高,可以看到眼睛旁边和嘴巴旁边都有半透明的效果。
查找原因:
看了下shader发现
Blend SrcAlpha OneMinusSrcAlpha
混合模式只用了这个。因为这个blend方式是也会把alpha通道做混合的。
然后我们进行分析
先渲染的avatarmat中是不透明的
但后面处理的avatarfacetexture里是有透明
然后混合用Blend SrcAlpha OneMinusSrcAlpha混合公式后得到的是一个带透明的效果,因为后面在处理的face时,因为alpha是不为1的,所以frontcolor*alpha+backcolor*(1-alpha)肯定得到的是一个半透明的值。
比如:
facetexture当前像素是(0.8,0.8,0.8,0.8)avatarmat的像素是(1,1,1,1)
计算过程:(0.8,0.8,0.8,0.8)*0.8+(1,1,1,1)(1-0.8)=(0.84,0.84,0.84,0.84)
也就是最后的alpha值是0.84,所以是透明的,所以会显示背景而导致半透明区域是闪烁的。
那么解决办法也很简单,就是区分color的混合和alpha的混合。
因为blend有一种模式可以区分color和alpha的混合。
Blend SrcFactor DstFactor, SrcFactorA DstFactorA
也就是前面两个是颜色混合,后面两个是颜色混合。正是我们想要的。所以最后的混合方式是
Blend SrcAlpha OneMinusSrcAlpha,One One
问题就解决了
当然这里还要注意FaceTextureshader要做小量的深度偏移,不然会跟身体重叠比较严重,导致闪烁发生
优点:切换贴图不会卡顿,顺畅过度
缺点:贴图采样数量还是一样多,所以会产生带宽过高的问题
以后的优化点可以考虑部分贴图合并减少贴图数量。