|
<hr/>Unity之磨皮美白功能实现
马上要毕业实习了,希望大佬能给个内推。坐标:福州、重庆、成都
联系方式:QQ(321545536)
声明
抱着“取之与百度,还之与百度。”的守恒思想。因此写下这篇文章。有点激动,目前百度上还没有unity磨皮+美白处理的相关文章。我应该是第一个。
转载请注明作者:QKuang
个人主页:Qkuang的技术分享:https://qkuang.gitee.io/
感谢
这次接了个单子,要在Unity上实现磨皮美白的功能。于是开发了此功能。个人感觉效果非常不错。处理速度PC可以实时计算。Android可以1秒内磨皮美白。(一键美颜)
我把百度相关搜索的前十页文章都详细读了一边。才有了这个功能的实现。感谢所有相关文章的作者。(2021年8月1日)
重点感谢下面文章的作者:
对皮肤美白算法的一些研究
高斯模糊的原理是什么,怎样在界面中实现?——答主:祥子
数字图像处理-高反差保留算法
高反差保留算法
O(1)效率的表面模糊算法优化
表面模糊
图像处理中表面模糊算法改进的讨论
Delphi图像处理 -- 表面模糊
Surface Blur(表面模糊滤波)算法与实现
图像美容之眼睛放大算法
皮肤美白算法
保留细节的磨皮之C#程序实现 ——重点感谢
Unity Shader 实现磨皮效果
基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用
正文
思路:表面模糊+高斯模糊+高反差+线性光混合+logarithmic Curve
效果:
思路可以参考我“感谢”中的文章(重点感谢那篇)这里为了节约文字,就不详细写了。
下面直接粘贴我的源码。如果想看思路,可以看C#版本。注释多。思路清晰。如果想直接白嫖,可以使用Shader版本。复制即可使用。
Csharp版本
由于CPU处理图像是弱势的,因此我加入了多线程辅助计算。但是处理一张图片还是要8s左右。
核心文件:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using UnityEngine;
#region 图片转化、高反差保留
public class PhotoOperating
{
#region 基础转化
/// <summary>
/// 将Texture转化为QColor数组,返回新的QColor
/// </summary>
/// <param name=&#34;src&#34;></param>
/// <returns></returns>
public QColor[,] ImputTexture(Texture2D src)
{
QColor[,] value = new QColor[src.width, src.height];
for (int y = 0; y < src.height; y++)
{
for (int x = 0; x < src.width; x++)
{
value[x, y] = Change2Qcolor(src.GetPixel(x, y));
}
}
return value;
}
/// <summary>
/// 转为 texture2d 返回新的texture……
/// </summary>
/// <returns></returns>
public Texture2D OutTexture(QColor[,] src)
{
var Width = src.GetLength(0);
var Height = src.GetLength(1);
Texture2D value = new Texture2D(Width, Height);
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
value.SetPixel(x, y, Change2Color(src[x, y]));
}
}
value.Apply();
Debug.Log(&#34;转化完毕&#34;);
return value;
}
#endregion
#region 两种颜色相互转化
public QColor Change2Qcolor(Color c)
{
var r = Convert.ToUInt16(c.r * 255);
var g = Convert.ToUInt16(c.g * 255);
var b = Convert.ToUInt16(c.b * 255);
return new QColor(r, g, b);
}
public Color Change2Color(QColor c)
{
return new Color(c.r / 255f, c.g / 255f, c.b / 255f);
}
/// <summary>
/// 拷贝一份QColor
/// </summary>
/// <param name=&#34;src&#34;></param>
/// <returns></returns>
public static QColor[,] QColorSCopy(QColor[,] src)
{
var width = src.GetLength(0);
var height = src.GetLength(1);
QColor[,] tempData = new QColor[width, height];
//拷贝一份临时
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
tempData[x, y] = src[x, y];
}
}
return tempData;
}
#endregion
/// <summary>
/// 高反差保留,对传入的QColor进行处理。并返回引用。
/// </summary>
/// <param name=&#34;src&#34;></param>
/// <param name=&#34;radius&#34;>高斯模糊的半径,默认1.不要设置太大</param>
public static QColor[,] HighPass(QColor[,] src,int radius=1)
{
var width = src.GetLength(0);
var height = src.GetLength(1);
Gaussi gaussi = new Gaussi();
QColor[,] tempData = QColorSCopy(src);
gaussi.GaussiBlur(tempData, radius);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
var r = (src[x, y].r - tempData[x, y].r + 127).Clamp2Short(0, 255);
var g = (src[x, y].g - tempData[x, y].g + 127).Clamp2Short(0, 255);
var b = (src[x, y].b - tempData[x, y].b + 127).Clamp2Short(0, 255);
src[x, y] = new QColor(r, g, b);
}
}
return src;
}
/// <summary>
/// 线性光混合,并返回一个新的QColors
/// </summary>
/// <param name=&#34;baseSrc&#34;></param>
/// <param name=&#34;mixSrc&#34;></param>
/// <returns></returns>
public static QColor[,] ModeLinearLight(QColor[,] baseSrc, QColor[,] mixSrc)
{
int width = baseSrc.GetLength(0);
int height = baseSrc.GetLength(1);
QColor[,] value = new QColor[width, height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
UInt16 r = ((ModeLinearLight(baseSrc[x, y].r, mixSrc[x, y].r) + baseSrc[x, y].r) / 2).Clamp2Short(0, 255);
UInt16 g = ((ModeLinearLight(baseSrc[x, y].g, mixSrc[x, y].g) + baseSrc[x, y].g) / 2).Clamp2Short(0, 255);
UInt16 b = ((ModeLinearLight(baseSrc[x, y].b, mixSrc[x, y].b) + baseSrc[x, y].b) / 2).Clamp2Short(0, 255);
value[x, y] = new QColor(r, g, b);
}
}
return value;
}
/// <summary>
/// 线性光混合
/// </summary>
/// <param name=&#34;basePixel&#34;></param>
/// <param name=&#34;mixPixel&#34;></param>
/// <returns></returns>
public static int ModeLinearLight(UInt16 basePixel, UInt16 mixPixel)
{
return basePixel + 2 * mixPixel - 255;
}
}
#endregion
# region 基础颜色
public struct QColor
{
public UInt16 r;
public UInt16 g;
public UInt16 b;
public QColor(UInt16 r, UInt16 g, UInt16 b)
{
this.r = r;
this.g = g;
this.b = b;
}
//求颜色的对值
//public static QColor ABS(QColor c)
//{
// return new QColor(Mathf.Abs(c.r), Mathf.Abs(c.g), Mathf.Abs(c.b));
//}
//public static QColor operator +(QColor left, QColor right)
//{
// return new QColor(left.r + right.r, left.g + right.g, left.b + right.b);
//}
//public static QColor operator -(QColor left, QColor right)
//{
// return new QColor(left.r - right.r, left.g - right.g, left.b - right.b);
//}
//public static QColor operator *(QColor left, QColor right)
//{
// return new QColor(left.r * right.r, left.g * right.g, left.b * right.b);
//}
//public static QColor operator -(float left, QColor right)
//{
// return new QColor(left - right.r, left - right.g, left - right.b);
//}
//public static QColor operator /(QColor left, float right)
//{
// return new QColor(left.r / right, left.g / right, left.b / right);
//}
}
#endregion
# region 表面模糊
public class SurfaceBlurTool2
{
/// <summary>
/// 计算的结果。由于采用多线程辅助计算,因此单独使用一块内存存储。避免出错。
/// </summary>
public QColor[,] src { get; private set; }
private QColor[,] tempData;
public int Width { get; }
public int Height { get; }
public int ThreadCount { get; private set; }
public float maxcolor = 0.5f;
public float mincolor = 0.2f;
/// <summary>
/// [矩阵分量,当前分量]。空间换时间_颜色提前计算
/// </summary>
public UInt16[,] ComputerTable_Color;
/// <summary>
/// [幅度值,小分母值]。幅度空间表_幅度提前计算,计算当小分母(0,255)内的不同值,避免重复计算。由于提前确定,因此幅度限定在[0,30]
/// </summary>
public UInt32[,] ComputerTable_threshold;
/// <summary>
/// 构造表面模糊算法对象,并传入QColor 数组,方便后面进行像素计算。
/// 传入的 图片数据只用于构造。内部不会修改,可以通过src 属性获取计算结果。
/// </summary>
/// <param name=&#34;src&#34;></param>
public SurfaceBlurTool2(QColor[,] src)
{
this.Width = src.GetLength(0);
this.Height = src.GetLength(1);
this.src = new QColor[this.Width, this.Height];
this.tempData = new QColor[this.Width, this.Height];
for (int i = 0; i < this.Height; i++)
{
for (int j = 0; j < this.Width; j++)
{
this.tempData[j, i] = src[j, i];
}
}
ComputerAhead_Color();
ComputerAhead_threshold();
Debug.Log(&#34;转化完毕……&#34;);
}
/// <summary>
/// 优化算法,提前计算。用空间换时间。颜色。全局只需要计算一次。后面无论再怎么弄,都不需要再计算。
/// </summary>
private void ComputerAhead_Color()
{
if (ComputerTable_Color != null)
return;
this.ComputerTable_Color = new UInt16[256, 256];
for (int xi = 0; xi < 256; xi++)
{
for (int x1 = 0; x1 < 256; x1++)
{
ComputerTable_Color[xi, x1] = Convert.ToUInt16(Math.Abs(xi - x1));
}
}
}
/// <summary>
/// 提前计算,计算权重。后面无论再怎么弄,都不需要再计算。这个结果就是K值。
/// k值最大1,避免浮点计算,放大100倍。求和分子最大256*100。
///ushort 65536 最大,限制 模糊半径20以内。你那么 最大求和值为:4.3千万内.用uInt16不够。uint32足够。避免类型转化,直接使用uint32
/// </summary>
private void ComputerAhead_threshold()
{
if (this.ComputerTable_threshold != null)
return;
this.ComputerTable_threshold = new UInt32[31, 256];
for (int i = 0; i < 256; i++)
{
for (int t = 1; t < 31; t++)
{
var k =(1 - (i / (2.5f * t)))*100;
this.ComputerTable_threshold[t, i] = Convert.ToUInt32(Mathf.Max(0, k));
}
}
//对分母为零的情况下特殊处理
for (int i = 0; i < 256; i++)
{
this.ComputerTable_threshold[0, i] = 0;
}
}
/// <summary>
/// 对Qcolor数组里面的颜色进行表面模糊计算。
/// </summary>
/// <param name=&#34;radius&#34;></param>
/// <param name=&#34;threshold&#34;></param>
/// <param name=&#34;head&#34;></param>
/// <param name=&#34;tail&#34;></param>
public void F_SurfaceBlur(int radius, int threshold,int head,int tail)
{
if (src == null )
{
Debug.LogError(&#34;范围有误&#34;);
return;
}
//Debug.Log(&#34;22&#34;);
UInt32 sumr = 0, sumrw = 0, sumg = 0, sumgw = 0, sumb = 0, sumbw = 0, k = 0;
for (int y = head; y < tail; y++)
{
for (int x = 0; x < Width; x++)
{
//每次计算当前像素前,清零。
sumr = 0;
sumrw = 0;
sumg = 0;
sumgw = 0;
sumb = 0;
sumbw = 0;
for (int n = -radius; n <= radius; n++)
{
for (int m = -radius; m <= radius; m++)
{
int x1 = Mathf.Clamp(x + m, 0, Width - 1);
int y1 = Mathf.Clamp(y + n, 0, Height - 1);
// 根据分值计算权重,
//k = 1.0f - (Mathf.Abs(tempData[x1, y1].r - tempData[x, y].r) / (2.5f * threshold));
//k = Mathf.Max(0, k);
k = this.ComputerTable_threshold[threshold, this.ComputerTable_Color[tempData[x1, y1].r, tempData[x, y].r]];
sumr += k * tempData[x1, y1].r;
sumrw += k;
//k = 1.0f - (Mathf.Abs(tempData[x1, y1].g - tempData[x, y].g) / (2.5f * threshold));
//k = Mathf.Max(0, k);
k = this.ComputerTable_threshold[threshold, this.ComputerTable_Color[tempData[x1, y1].g, tempData[x, y].g]];
sumg += k * tempData[x1, y1].g;
sumgw += k;
//k = 1.0f - (Mathf.Abs(tempData[x1, y1].b - tempData[x, y].b) / (2.5f * threshold));
//k = Mathf.Max(0, k);
k = this.ComputerTable_threshold[threshold, this.ComputerTable_Color[tempData[x1, y1].b, tempData[x, y].b]];
sumb += k * tempData[x1, y1].b;
sumbw += k;
}
}
var r = sumrw == 0 ? src[x, y].r : (sumr / sumrw).Clamp2UShort(0, 255);
var g = sumgw == 0 ? src[x, y].g : (sumg / sumgw).Clamp2UShort(0, 255);
var b = sumbw == 0 ? src[x, y].b : (sumb / sumbw).Clamp2UShort(0, 255);
src[x, y] = new QColor(r, g, b);
//src[x, y].r =
//src[x, y].g =
//src[x, y].b =
//src[x, y].r = sumrw == 0 ? src[x, y].r : Mathf.Clamp(sumr / sumrw, 0, 255);
//src[x, y].g = sumgw == 0 ? src[x, y].g : Mathf.Clamp(sumg / sumgw, 0, 255);
//src[x, y].b = sumbw == 0 ? src[x, y].b : Mathf.Clamp(sumb / sumbw, 0, 255);
//if(sumrw == 0 || sumr / sumrw > 255 || sumr / sumrw < 0|| sumgw == 0 || sumg / sumgw > 255 || sumg / sumgw < 0|| sumbw == 0 || sumg / sumgw > 255 || sumg / sumgw<0)
//{
//}
//else
//{
// src[x, y].r= sumr / sumrw;
// src[x, y].g = sumg / sumgw;
// src[x, y].b= sumb / sumbw;
//}
//src[x, y].r = sumrw == 0|| sumr / sumrw>255|| sumr / sumrw<0 ? src[x, y].r : sumr / sumrw;
//src[x, y].g = sumgw == 0|| sumg / sumgw>255|| sumg / sumgw<0 ? src[x, y].g : sumg / sumgw;
//src[x, y].b = sumbw == 0 || sumg / sumgw >255|| sumg / sumgw <0? src[x, y].b : sumb / sumbw;
}
}
}
//多线程优化,以行为划分
public void ThreadingStart(int number, int radius, int threshold)
{
this.ThreadCount = 0;
int h = Height / number;
ParallelLoopResult result = Parallel.For(0, number, async i =>
{
//Debug.Log($&#34;多线程:……{i}&#34;);
await Task.Delay(10);
F_SurfaceBlur(radius, threshold,i*h,(i+1)*h);
this.ThreadCount++;
//Debug.Log($&#34;[{this.ThreadCount}]多线程调用完毕:……{i}&#34;);
}
);
}
//记录最值。
public void ValueRecord(float v)
{
if (v > this.maxcolor)
{
this.maxcolor = v;
}
else if (v < mincolor)
{
this.mincolor = v;
}
}
}
# endregion
# region 高斯模糊
/// <summary>
/// 高斯模糊,将传入的Qcolor处理,并返回。
/// </summary>
public class Gaussi
{
public int r { get; private set; } //模糊的半径
private float[,] matrix; //模糊矩阵 ,[2*r+1]
private float zelta; // 标准差
private float matrixSum; // 通过总和计算保证最后之和为1.
private void SetRaduis(int r)
{
this.r = r;
matrix = new float[2 * r + 1, 2 * r + 1];
//for(int x = 0; x < 2 * r + 1; x++)
//{
// for(int y = 0; y < 2 * r + 1; y++)
// {
// matrix[x, y] = 1;
// }
//}
double sum = 0;
for (int i = -r; i <= r; i++)
{
sum += Math.Pow(i, 2);
}
zelta = Convert.ToSingle(Math.Sqrt(sum / (2 * r + 1)));
}
private void ComputerMatrix()
{
this.matrixSum = 0;
// 一次计算,计算出正态分布值。
for (int y = -r; y <= r; y++)
{
for (int x = -r; x <= r; x++)
{
if (zelta == 0)
return;
var v = (Math.Pow(x, 2) + Math.Pow(y, 2)) / (2 * Math.Pow(zelta, 2));
var v2 = Convert.ToSingle((1 / (2 * Math.PI * Math.Pow(zelta, 2)) * Math.Pow(Math.E, -v)));
//按理说计算出来都是大于零,但是保险还是加个限制。
matrix[x + r, y + r] = v2;
this.matrixSum += v2;
}
}
//二次计算。保证和为1
for (int x = 0; x < 2 * r + 1; x++)
{
for (int y = 0; y < 2 * r + 1; y++)
{
matrix[x, y] = matrix[x, y] / this.matrixSum;
}
}
}
public QColor[,] GaussiBlur(QColor[,] src, int Radius)
{
var width = src.GetLength(0);
var height = src.GetLength(1);
SetRaduis(Radius);
ComputerMatrix();
QColor[,] tempData = new QColor[width, height];
double sumr = 0, sumg = 0, sumb = 0;
//拷贝一份临时
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
tempData[x, y] = src[x, y];
}
}
// 开始计算
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
sumr = 0;
sumg = 0;
sumb = 0;
for (int n = -r; n <= r; n++)
{
for (int m = -r; m <= r; m++)
{
var x1 = (x + m).Clamp(0, width - 1);
var y1 = (y + n).Clamp(0, height - 1);
var k = matrix[m + r, n + r];
sumr += tempData[x1, y1].r * k;
sumg += tempData[x1, y1].g * k;
sumb += tempData[x1, y1].b * k;
}
}
var c_r = Convert.ToUInt16(sumr).Clamp2UShort(0, 255);
var c_g = Convert.ToUInt16(sumg).Clamp2UShort(0, 255);
var c_b = Convert.ToUInt16(sumb).Clamp2UShort(0, 255);
src[x, y] = new QColor(c_r, c_g, c_b);
}
}
return src;
}
/// <summary>
/// 输出高斯模糊矩阵
/// </summary>
/// <returns></returns>
public override string ToString()
{
var str = &#34;&#34;;
for (int y = 0; y < matrix.GetLength(1); y++)
{
for (int x = 0; x < matrix.GetLength(0); x++)
{
str += this.matrix[x, y].ToString() + &#34;,&#34;;
}
str += &#34;\n&#34;;
}
return base.ToString();
}
}
# endregion
#region 扩展函数
public static class ExternMethod
{
//值限制
public static int Clamp(this int value, int min, int max)
{
return Math.Min(Math.Max(min, value), max);
}
public static UInt16 Clamp2UShort(this UInt32 value, UInt32 min, UInt32 max)
{
return Convert.ToUInt16( Math.Min(Math.Max(min, value), max));
}
public static UInt16 Clamp2UShort(this UInt16 value, int min, int max)
{
return Convert.ToUInt16(Math.Min(Math.Max(min, value), max));
}
public static UInt16 Clamp2Short(this int value, int min, int max)
{
return Convert.ToUInt16(Math.Min(Math.Max(min, value), max));
}
public static Texture2D Clone(this Texture2D src)
{
Texture2D v = new Texture2D(src.width, src.height);
for (int i = 0; i < src.height; i++)
{
for (int j = 0; j < src.width; j++)
{
v.SetPixel(j, i, src.GetPixel(j, i));
}
}
v.Apply();
return v;
}
}
#endregion
美白部分我用的shader计算。
Shader &#34;Unlit/NewUnlitShader&#34;
{
Properties
{
_MainTex (&#34;Texture&#34;, 2D) = &#34;white&#34; {}
_Rvalue(&#34;Rvalue&#34;,range(-1,1))=0
_Gvalue(&#34;Gvalue&#34;,range(-1,1))=0
_BvaluP(&#34;Bvalue&#34;,range(-1,1))=0
_Vl(&#34;Vl&#34;,range(2,6))=2
}
SubShader
{
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include &#34;UnityCG.cginc&#34;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float _Rvalue;
float _Gvalue;
float _Bvalue;
float _Vl;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
//col.r+=_Rvalue;
//col.g+=_Gvalue;
//col.b+= _Bvalue;
int qv = floor(_Vl);
col.rgb = log(col.rgb*(qv-1)+1)/log(qv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
调用文件:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class demo : MonoBehaviour
{
public Material r_matera;
public Slider r_slider;
public Texture2D r_src;
public RawImage r_image;
public Text r_text;
public int number;
public int R ;
public int r_gaussi;
public int Threshold;
private SurfaceBlurTool2 sfb;
private PhotoOperating po;
private Gaussi gaussi;
private QColor[,] texture_src;
private QColor[,] texture_suf;
private QColor[,] texture_high;
// Start is called before the first frame update
void Start()
{
po = new PhotoOperating();
texture_src = po.ImputTexture(r_src);
texture_suf = PhotoOperating.QColorSCopy(texture_src);
sfb = new SurfaceBlurTool2(texture_suf);
}
public void Button_Click()
{
Debug.Log(&#34;按钮&#34;);
//sfb.F_SurfaceBlur(R,Threshold,0,sfb.Height);
sfb.ThreadingStart(number, R, Threshold);
}
public void Over_Click()
{
// r_image.texture =po.OutTexture( sfb.src);
Debug.Log(&#34;刷新&#34;);
texture_high = PhotoOperating.QColorSCopy(texture_src);
PhotoOperating.HighPass(texture_high, r_gaussi);
r_image.texture =po.OutTexture( PhotoOperating.ModeLinearLight(sfb.src, texture_high));
//r_image.texture = po.OutTexture(sfb.src);
}
// Update is called once per frame
void Update()
{
r_text.text = $&#34;磨皮进度:{(sfb.ThreadCount / number) * 100}%&#34;;
r_matera.SetFloat(&#34;_Vl&#34;, r_slider.value);
}
}
Shader版本
GPU处理图片的速度就是快……
Shader &#34;Unlit/NewUnlitShader 1&#34;
{
Properties
{
_MainTex (&#34;Texture&#34;, 2D) = &#34;white&#34; {}
_Radius(&#34;Radius&#34;, int) = 1
_Threshold(&#34;threshold&#34;, int) = 1
// _Gauss(&#34;isgauss&#34;,range(0,3))=1
_White(&#34;Vl&#34;,int)=2
}
SubShader
{
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include &#34;UnityCG.cginc&#34;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float2 _MainTex_TexelSize;
int _Radius; //表面模糊半径
int _Threshold; //表面模糊幅度
int _White; //美白值
fixed3 ModeLinearLight(fixed3 base,fixed3 mixPixel){
return base+2*mixPixel-1;
}
fixed4 PhotoOpera(v2f i)
{
//********常量定义
float _delta =0.8164966; // 高斯模糊矩阵方差
float _pi = 3.1415926;
float _e = 2.718281828459;
float3x3 _Matrix=float3x3(
0.058998134f,0.124899052f,0.058998134f,
0.124899052f,0.264411271f,0.124899052f,
0.058998134f,0.124899052f,0.058998134f
); //高斯模糊矩阵,r=1
fixed4 src = tex2D(_MainTex, i.uv); //原始颜色
//**********end
//图像处理:表面模糊+高斯模糊+高反差+线性光混合+美白
//**********表面模糊 Start
fixed4 center_suf = src; //中心点颜色_表面模糊计算使用
float3 sum_up; // 分子 //
float3 sum_down; //分母
float3 k; // 小k
//遍历模糊矩阵 ——表面
for(int n=-_Radius;n<=_Radius;n++){
for(int m = -_Radius;m<=_Radius;m++){
//当前uv
float2 uv_new = clamp( i.uv+_MainTex_TexelSize.xy*float2(m,n),0,1);
//当前颜色
fixed3 currentColor = tex2D(_MainTex, uv_new);
k = 1-(abs(currentColor.rgb-center_suf.rgb)/(0.01*_Threshold));
k = max(0,k);
sum_down+=k;
sum_up +=k*currentColor;
}
}
center_suf = fixed4(sum_up/sum_down,center_suf.a);
//*****************表面模糊End
//********高反差
fixed4 center_high = src; //中心点颜色,高反差计算。
float3 sum_h ; //求和后的颜色。
for(int n_h =-1;n_h <=1;n_h ++){
for(int m_h =-1;m_h <=1;m_h ++){
float2 h_xy = float2(m_h+1,n_h+1); //对应矩阵位置
float2 uv_new = clamp( i.uv+_MainTex_TexelSize.xy*float2(m_h,n_h),0,1); //对应uv坐标
fixed3 currentColor = tex2D(_MainTex, uv_new); //对应颜色
//float left = (1/(2*_pi*pow(_delta,2)));
//float h_up = (pow(m_h,2)+pow(n_h,2))/(2*pow(_delta,2));
//float right = pow(_e,-h_up);
//float h = left*right; //权重。由于是固定模糊大小。因此不用动态求加权。
sum_h.rgb+=currentColor.rgb*_Matrix[h_xy.r][h_xy.g];
}
}
center_high = fixed4( center_high.rgb-sum_h.rgb+0.5,center_high.a);
//**********高反差end
//**********线性光混合
fixed3 c_linerTemp = (ModeLinearLight(center_suf,center_high)+center_suf)/2;
fixed4 linerLight = fixed4(c_linerTemp,center_suf.a);
//******************end
//**********美白
fixed3 c_white = log(linerLight.rgb*(_White-1)+1)/log(_White);
//************end
fixed4 outColor = fixed4(c_white,center_suf.a);//center_high;
return outColor;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
//fixed4 col = tex2D(_MainTex, i.uv);
fixed4 col = PhotoOpera(i);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
由于Android手机性能不足,而这个算法消耗颇大。因此不能每帧都计算,否则卡成ppt。
简单实现一个,单击按钮再计算磨皮美白的功能。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SahderV1Demo : MonoBehaviour
{
// Start is called before the first frame update
public Slider r_s1; //值范围(0,20)
public Slider r_s2; //值范围(1,30)
public Slider r_s3; //值范围(2,10)
public RawImage r_t1;
public RawImage r_t2;
public RawImage r_t3;
public Material r_material;
private Texture2D t1;
private Texture2D t2;
private Texture2D t3;
public void Start()
{
t1 = r_t1.texture as Texture2D;
t2 = r_t2.texture as Texture2D;
t3 = r_t3.texture as Texture2D;
}
public void Apply()
{
SetMaterial(r_material, Convert.ToInt32(r_s1.value), Convert.ToInt32(r_s2.value), Convert.ToInt32(r_s3.value));
r_t1.texture = ComputerTexture(t1, r_material);
r_t2.texture = ComputerTexture(t2, r_material);
r_t3.texture = ComputerTexture(t3, r_material);
}
/// <summary>
/// 通过材质计算出新的 Texture2D,并返回计算结果
/// </summary>
/// <param name=&#34;src&#34;></param>
/// <param name=&#34;m&#34;></param>
/// <returns></returns>
private Texture2D ComputerTexture(Texture2D src,Material m)
{
if (src == null || m == null)
return Texture2D.whiteTexture;
Texture2D value = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false);
RenderTexture temp = new RenderTexture(src.width, src.height,0);
Graphics.Blit(src, temp, m);
RenderTexture.active = temp;
value.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0);
value.Apply();
return value;
}
/// <summary>
/// 设置材质的三个参数:模糊半径、模糊阈值、美白强度
/// </summary>
/// <param name=&#34;m&#34;></param>
/// <param name=&#34;radius&#34;></param>
/// <param name=&#34;threshold&#34;></param>
/// <param name=&#34;VI&#34;></param>
private void SetMaterial(Material m,int radius, int threshold,int VI)
{
if (m == null)
return;
m.SetInt(&#34;_Radius&#34;, radius);
m.SetInt(&#34;_Threshold&#34;, threshold);
m.SetInt(&#34;_White&#34;, VI);
}
}
补充:
事后想了一下,美白的算法有点问题,并不完美。以后有空再更新一篇文章。
结语
马上要毕业实习了,希望大佬能给个内推。坐标:福州、重庆、成都
写作不易,点赞三连。(以后还会更新更多文章,点关注,不迷路。)
联系方式:QQ(321545536) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|