【UnityShader】常用效果内外发光、 描边

这篇博客记录一下常用的几种效果:内发光、外发光以及描边的几种实现方法

内发光
实现方法
一般实现方法就是使用Empricial菲涅尔近似公式来实现:

F (v,n)=saturate(base + pow(scale * (1 - v·n), power))
1
base为基础反射率,scale为菲涅尔效应强弱,power为菲涅尔效应作用角度范围大小(即power越大,反射率会在越小的范围内迅速变为1)
在计算菲涅尔效应时,使用F对反射和基础颜色值进行插值混合,在这里计算内发光,就对发光颜色和基础颜色进行插值混合。
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
1
2
内发光

实现代码
Shader “Custom/Effect/Shine_Out”
{
Properties
{
_Color (“Main Color”, Color) = (1,1,1,1)
_ShineColor (“Shine Color”, Color) = (1,1,1,1)
_ShineScale (“Shine Scale”, Range(0, 1)) = 0.5
_ShinePower (“Shine Power”, Range(0, 1)) = 0.5
}
SubShader
{
Tags { “Queue”=“Transparent” }

    LOD 200

    Pass
    {
		Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"
		#include "Lighting.cginc"

		half4 _Color;
		half4 _ShineColor;
		float _ShineScale;
		float _ShinePower;

        struct a2v
        {
            float4 vertex : POSITION;
			float3 normal : NORMAL;
        };

        struct v2f
        {
			float4 pos : SV_POSITION;
			float3 normal : TEXCOORD0;
			float3 worldPos : TEXCOORD1;
        };


        v2f vert (a2v v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
			o.normal = UnityObjectToWorldNormal(v.normal);
			o.worldPos = mul(unity_ObjectToWorld, v.vertex);

            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
			half3 normal = normalize(i.normal);
			half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

			fixed4 col = fixed4(0,0,0,1);
			col.rgb = _LightColor0.rgb * _Color.rgb * (dot(lightDir, normal)*0.5+0.5);
			half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
			col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
			
            return col;
        }
        ENDCG
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
描边
内发光方法实现
沿用上文写的内发光实现方法,仅在内发光的值上做mask或者数值限制。这里仅使用step做限制,需要更风格化的效果需要制作mask做描边的限制。
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
shine = step(_Threshold, shine);
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
1
2
3
阈值为新增变量,当内发光的渐变范围值大于阈值时,显示为描边颜色,小于阈值则显示为原颜色
为了显示清楚描边,图中去掉了原本的光照计算
描边
优点
实现简单,可以在一个Pass里实现,性能消耗小
缺点
只能向内描边,且描边宽度受视角与法线方向影响,不易控制
沿法线外扩背面方法实现
这种方法比较常用,用两个Pass实现,一个Pass只渲染背面,且把物体背面沿法线方向向外扩张,第二个Pass正常渲染物体。

v2f vert (appdata v)
{
v2f o;
float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);

viewNormal.z = -0.5;//防止内凹模型背面扩张后遮挡正面
float3 normal = normalize(viewNormal);
viewPos += float4(normal, 1.0) * _OutlineWidth;

o.pos = mul(UNITY_MATRIX_P, viewPos);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);

return o;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
效果图如下
描边

优点

实现简单,大部分模型的描边效果都很不错
缺点

对于例如正方体这样平整且转折锐利的物体无法使用
多使用了一个Pass,渲染性能消耗提高了不少
全部代码如下

Shader “Custom/Effect/Outline”
{
Properties
{
_Color (“Main Color”, Color) = (1,1,1,1)
[HDR]_OutlineColor (“Outline Color”, Color) = (1,1,1,1)
_OutlineWidth (“Outline Width”, Range(0, 2)) = 1
}
SubShader
{
Tags { “Queue”=“Transparent” }

    LOD 200

    Pass
    {
		Cull Front
		Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
			float3 normal : NORMAL;
        };

        struct v2f
        {
            float4 pos : SV_POSITION;
        };

		half4 _OutlineColor;
		float _OutlineWidth;

        v2f vert (appdata v)
        {
            v2f o;
			float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
			float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);

			viewNormal.z = -0.5;
			float3 normal = normalize(viewNormal);
			viewPos += float4(normal, 1.0) * _OutlineWidth;

			o.pos = mul(UNITY_MATRIX_P, viewPos);

            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            return _OutlineColor;
        }
        ENDCG
    }

	Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
        };

        struct v2f
        {
            float4 pos : SV_POSITION;
        };

        half4 _Color;

        v2f vert (appdata v)
        {
            v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);

            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 col = _Color;

            return col;
        }
        ENDCG
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
图像处理方法实现
基于后处理图像处理的边缘检测方法:常用的一般是Sobel算子和Roberts算子,边缘处的梯度绝对值会较大,也就是计算每个像素的梯度,并用梯度对初始颜色和描边颜色进行插值,也就完成了描边的操作。

half Sobel(v2f i)
{
const half Gx[9] = { -1,-2,-1,
0, 0, 0,
1, 2, 1 };

const half Gy[9] = { -1, 0, 1,
					 -2, 0, 2,
					 -1, 0, 1 };

half texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++)
{
	texColor = luminance(tex2D(_MainTex, i.uv[it]));
	edgeX += texColor * Gx[it];
	edgeY += texColor * Gy[it];
}

half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);

return edge;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
效果如下
描边

优点

适用于任何模型
不用增加一个Pass的渲染性能消耗
缺点

增加了后处理的性能消耗
无法单独对一个物体进行描边
一些变化很小的变换难以检测
全部代码如下

Shader “Custom/Postprocess/EdgeDetection”
{
Properties
{
_MainTex (“Texture”, 2D) = “white” {}
_EdgeColor (“EdgeColor”, Color) = (1,0,0,1)
_BackgroundColor (“BackgroundColor”, Color) = (1,1,1,1)
_EdgeOnly (“Edge Only”, Range(0,1)) = 0.5
_EdgeWidth (“Edge Width”, Range(0,1)) = 1
}
SubShader
{

	Pass
	{
		ZTest Always Cull Off ZWrite Off

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		
		#include "UnityCG.cginc"

		struct v2f
		{				
			float4 vertex : SV_POSITION;
			half2 uv[9] : TEXCOORD0;
		};

		sampler2D _MainTex;
		float2 _MainTex_TexelSize;
		fixed4 _EdgeColor;
		fixed4 _BackgroundColor;
		fixed _EdgeOnly;
		fixed _EdgeWidth;
	
		v2f vert (appdata_img v)
		{
			v2f o;
			o.vertex = UnityObjectToClipPos(v.vertex);
			half2 uv = v.texcoord;

			o.uv[0] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y *-1);
			o.uv[1] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y *-1);
			o.uv[2] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y *-1);
			o.uv[3] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 0);
			o.uv[4] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 0);
			o.uv[5] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 0);
			o.uv[6] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 1);
			o.uv[7] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 1);
			o.uv[8] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 1);

			return o;
		}
		
		fixed luminance(fixed4 color)
		{
			return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
		}

		half Sobel(v2f i)
		{
			const half Gx[9] = { -1,-2,-1,
								  0, 0, 0,
								  1, 2, 1 };

			const half Gy[9] = { -1, 0, 1,
								 -2, 0, 2,
								 -1, 0, 1 };

			half texColor;
			half edgeX = 0;
			half edgeY = 0;
			for (int it = 0; it < 9; it++)
			{
				texColor = luminance(tex2D(_MainTex, i.uv[it]));
				edgeX += texColor * Gx[it];
				edgeY += texColor * Gy[it];
			}

			half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);

			return edge;
		}

		fixed4 frag (v2f i) : SV_Target
		{
			half edge = Sobel(i);

			fixed4 withEdgeColor = lerp(tex2D(_MainTex, i.uv[4]), _EdgeColor, edge);
			fixed4 onlyEdgeColor = lerp(_BackgroundColor, _EdgeColor, edge);

			half4 col = lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);

			return col;
		}
		ENDCG
	}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
外发光
Bloom
后处理非常常用的Bloom效果,也是最为常用的外发光渲染方案。
基本思路为,提取图像中的亮部,渲染为RT,再对这张RT进行模糊,最后再把模糊后的亮部RT叠加回原图,就得到了发光物体外一层光晕的效果。
HDR可以让颜色亮度高于1,与Bloom配合可以让暗部不会也随着Bloom效果一起发光,仅仅让亮部有Bloom效果
实现代码有很多,之前的一篇写后处理的博客也有写过,就不贴代码了
效果图
Bloom
沿法线外扩背面方法实现
在上文的沿法线外扩背面实现的描边方法基础上,给背面的alpha像内发光一样,乘以视角与法线点积。不过要稍作改动,让背面从中心向外逐渐透明,模拟光晕的效果。

float ndotv = max(pow(-dot(normal, viewDir),_OutlineFeather), 0.001);
fixed4 col = _OutlineColor;
col.a *= ndotv;
1
2
3
外发光

注意:

因为是背面的法线,所以点积的范围在[-1,0],要乘以-1让它在0到1之间。
内发光是从中心到边缘逐渐变大,而此处是需要让alpha从中心到边缘逐渐变小,所以之前是1-ndotv,现在直接使用ndotv计算
全部代码如下

Shader “Custom/Effect/Shine_In”
{
Properties
{
_Color (“Main Color”, Color) = (1,1,1,1)
[HDR]_ShineColor (“Shine Color”, Color) = (1,1,1,1)
_ShineWidth (“Shine Width”, Range(0, 2)) = 1
_ShineFeather (“Shine Feather”, Range(0, 10)) = 0
}
SubShader
{
Tags { “Queue”=“Transparent” }

    LOD 200

    Pass
    {
		Cull Front
		Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
			float3 normal : NORMAL;
        };

        struct v2f
        {
            float4 pos : SV_POSITION;
			float3 worldPos : TEXCOORD0;
			float3 normal : TEXCOORD1;
        };

		half4 _ShineColor;
		float _ShineWidth;
		float _ShineFeather;

        v2f vert (appdata v)
        {
            v2f o;
			float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
			float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);

			viewNormal.z = -0.5;
			float3 normal = normalize(viewNormal);
			viewPos += float4(normal, 1.0) * _ShineWidth;

			o.pos = mul(UNITY_MATRIX_P, viewPos);
			o.worldPos = mul(unity_ObjectToWorld, v.vertex);
			o.normal = UnityObjectToWorldNormal(v.normal);

            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
			float3 normal = normalize(i.normal);
			float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

			float ndotv = max(pow(-dot(normal, viewDir),_ShineFeather), 0.001);
            fixed4 col = _ShineColor;
			col.a *= ndotv;

            return col;
        }
        ENDCG
    }

	Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
        };

        struct v2f
        {
            float4 pos : SV_POSITION;
        };

        half4 _Color;

        v2f vert (appdata v)
        {
            v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);

            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 col = _Color;

            return col;
        }
        ENDCG
    }
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
在实现TextView描边的过程中,可以使用drawable文件来定义边框效果。你可以创建一个名为edge.xml的drawable文件,并在其中设置背景色、圆角和边框。例如,可以使用以下代码来定义一个具有描边效果的TextView: ```xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 设置背景色 --> <!-- <solid android:color="@color/colorGrey" /> --> <!-- 设置四周圆角 --> <corners android:topLeftRadius="5dp" android:topRightRadius="5dp" android:bottomRightRadius="5dp" android:bottomLeftRadius="5dp"/> <!-- 设置边框 --> <stroke android:width="1dp" android:color="#FFFFFFFF" /> </shape> ``` 然后,在你的布局文件中,将这个drawable文件作为TextView的背景即可实现描边效果。例如: ```xml <androidx.cardview.widget.CardView android:layout_width="210dp" android:layout_height="wrap_content" android:background="@drawable/edge" app:cardElevation="1dp"> <!-- 这里自由发挥,写上你需要填充的控件 --> </androidx.cardview.widget.CardView> ``` 这样,你就可以在TextView周围添加描边效果了。 #### 引用[.reference_title] - *1* *3* [Android 控件描边、加阴影](https://blog.csdn.net/weixin_54723630/article/details/126907993)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [TextView描边、渐变、阴影效果](https://blog.csdn.net/qq_21154101/article/details/103043829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值