Shader知识普及之:水墨效果的实现

本篇文章我们一起来学习下如何用shader将场景处理为水墨风格。


水墨作品(摘自百度百科


我们观察水墨画作,可以构思下水墨化风格实现的大致思路:将原始图像进行高斯模糊处理,用深度算法算出边缘并进行描边,最后用画笔效果的滤波来完成最终的图像。


我们先观察一下未开启水墨风格效果和开启水墨风格效果在Unity中的画面区别:


未开启水墨效果

开启水墨效果


可以感觉的到,开启了水墨效果场景顿时变得中国风满满,接下来我们一起查阅一下shader水墨效果的代码实现过程:

Shader "Unlit/ChineseInk"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

_BlurTex("Blur",2D) = "white"{}

_PaintTex("PaintTex",2D)="white"{}

}

CGINCLUDE

#include "UnityCG.cginc"

sampler2D _CameraDepthNormalsTexture;

sampler2D _MainTex;

sampler2D _BlurTex;

sampler2D _PaintTex;

sampler2D _NoiseTex;

float4 _BlurTex_TexelSize;

float4 _MainTex_ST;

float4 _MainTex_TexelSize;

float4 _PaintTex_TexelSize;

float4 _offsets;

float _EdgeWidth;

float _Sensitive;

int _PaintFactor;

float luminance(fixed3 color) {

return 0.2125*color.r + 0.7154*color.g + 0.0721*color.b;

}

struct v2f_blur

{

float2 uv : TEXCOORD0;

float4 vertex : SV_POSITION;

float4 uv01:TEXCOORD1;

float4 uv23:TEXCOORD2;

float4 uv45:TEXCOORD3;

};

v2f_blur vert_blur(appdata_img v)

{

v2f_blur o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord.xy;

_offsets *= _MainTex_TexelSize.xyxy;

o.uv01 = v.texcoord.xyxy + _offsets.xyxy*float4(1, 1, -1, -1);

o.uv23 = v.texcoord.xyxy + _offsets.xyxy*float4(1, 1, -1, -1)*2.0;

o.uv45 = v.texcoord.xyxy + _offsets.xyxy*float4(1, 1, -1, -1)*3.0;

return o;

}

float4 frag_blur(v2f_blur i) : SV_Target

{

float4 color = float4(0,0,0,0);

color += 0.40*tex2D(_MainTex, i.uv);

color += 0.15*tex2D(_MainTex, i.uv01.xy);

color += 0.15*tex2D(_MainTex, i.uv01.zw);

color += 0.10*tex2D(_MainTex, i.uv23.xy);

color += 0.10*tex2D(_MainTex, i.uv23.zw);

color += 0.05*tex2D(_MainTex, i.uv45.xy);

color += 0.05*tex2D(_MainTex, i.uv45.zw);

return color;

}

struct v2f_edge{

float2 uv:TEXCOORD0;

float4 vertex:SV_POSITION;

};

v2f_edge vert_edge(appdata_img v){

v2f_edge o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;

return o;

}

float4 frag_edge(v2f_edge i):SV_Target{

float n = tex2D(_NoiseTex,i.uv).r;

float3 col0 = tex2D(_CameraDepthNormalsTexture, i.uv + _EdgeWidth * _BlurTex_TexelSize.xy*float2(1,1)).xyz;

float3 col1 = tex2D(_CameraDepthNormalsTexture, i.uv + _EdgeWidth * _BlurTex_TexelSize.xy*float2(1,-1)).xyz;

float3 col2 = tex2D(_CameraDepthNormalsTexture, i.uv + _EdgeWidth * _BlurTex_TexelSize.xy*float2(-1, 1)).xyz;

float3 col3 = tex2D(_CameraDepthNormalsTexture, i.uv + _EdgeWidth * _BlurTex_TexelSize.xy*float2(-1,-1)).xyz;

float edge = luminance(pow(col0 - col3, 2) + pow(col1 - col2, 2));

edge = pow(edge, 0.2);

if (edge<_Sensitive)

{

edge = 0;

}

else

{

edge = n;

}

float3 color = tex2D(_BlurTex, i.uv);

float3 finalColor = edge * float3(0, 0, 0) + (1 - edge)*color*(0.95+0.1*n);

return float4(finalColor, 1.0);

}

struct v2f_paint {

float2 uv:TEXCOORD0;

float4 vertex:SV_POSITION;

};

v2f_paint vert_paint(appdata_img v) {

v2f_paint o;

o.uv = v.texcoord;

o.vertex = UnityObjectToClipPos(v.vertex);

return o;

}

float4 frag_paint(v2f_paint i):SV_Target{

float3 m0 = 0.0;

float3 m1 = 0.0;

float3 s0 = 0.0;

float3 s1 = 0.0;

float3 c = 0.0;

int radius = _PaintFactor;

int r = (radius + 1)*(radius + 1);

for (int j = -radius; j <= 0; ++j)

{

for (int k = -radius; k <= 0; ++k)

{

c = tex2D(_PaintTex, i.uv + _PaintTex_TexelSize.xy * float2(k, j)).xyz;

m0 += c;

s0 += c * c;

}

}

for (int j = 0; j <= radius; ++j)

{

for (int k = 0; k <= radius; ++k)

{

c = tex2D(_PaintTex, i.uv + _PaintTex_TexelSize.xy * float2(k, j)).xyz;

m1 += c;

s1 += c * c;

}

}

float4 finalFragColor = 0.;

float min_sigma2 = 1e+2;

m0 /= r;

s0 = abs(s0 / r - m0 * m0);

float sigma2 = s0.r + s0.g + s0.b;

if (sigma2 < min_sigma2)

{

min_sigma2 = sigma2;

finalFragColor = float4(m0, 1.0);

}

m1 /= r;

s1 = abs(s1 / r - m1 * m1);

sigma2 = s1.r + s1.g + s1.b;

if (sigma2 < min_sigma2)

{

min_sigma2 = sigma2;

finalFragColor = float4(m1, 1.0);

}

return finalFragColor;

}

ENDCG

SubShader

{

Pass

{

CGPROGRAM

#pragma vertex vert_blur

#pragma fragment frag_blur

ENDCG

}

Pass

{

CGPROGRAM

#pragma vertex vert_edge

#pragma fragment frag_edge

ENDCG

}

Pass

{

CGPROGRAM

#pragma vertex vert_paint

#pragma fragment frag_paint

ENDCG

}

}

}

我们将代码结构拆分理解:首先声明 shader的三张贴图属性:

原始画面:_MainTex ("Texture", 2D) = "white" {}

高斯模糊画面:_BlurTex("Blur",2D) = "white"{}

水墨画面:_PaintTex("PaintTex",2D)="white"{}

声明深度法线图和一些其他参数信息:

sampler2D _CameraDepthNormalsTexture;

sampler2D _MainTex;

.......

float _Sensitive;

int _PaintFactor;

提取灰度信息:

float luminance(fixed3 color) {

return 0.2125*color.r + 0.7154*color.g + 0.0721*color.b;

}

定义高斯模糊的结构体:

struct v2f_blur

{

float2 uv : TEXCOORD0;

float4 vertex : SV_POSITION;

float4 uv01:TEXCOORD1;

float4 uv23:TEXCOORD2;

float4 uv45:TEXCOORD3;

};

高斯模糊方法体:

顶点函数:v2f_blur vert_blur(篇幅有限不展开显示)

片元函数:float4 frag_blur(v2f_blur i) : SV_Target

定义边缘检测的结构体:

struct v2f_edge{

float2 uv:TEXCOORD0;

float4 vertex:SV_POSITION;

};

边缘检测的方法体:

顶点函数:v2f_edge vert_edge(appdata_img v)

片元函数:float4 frag_edge(v2f_edge i):SV_Target

定义画笔滤波部分的结构体:

struct v2f_paint {

float2 uv:TEXCOORD0;

float4 vertex:SV_POSITION;

};

画笔滤波部分的方法体:

顶点函数:v2f_paint vert_paint(appdata_img v)

片元函数:float4 frag_paint(v2f_paint i):SV_Target




免责声明:本网站内容来自作者投稿或互联网转载,目的在于传递更多信息,不代表本网赞同其观点或证实其内容的真实性。文章内容及配图如有侵权或对文章观点有异议,请联系我们处理。如转载本网站文章,务必保留本网注明的稿件来源,并自行承担法律责任。联系电话:0535-6792776