Unity3d游戏角色描边
本文发布于游戏程序员刘宇的个人博客,欢迎转载,请注明来源https://www.cnblogs.com/xiaohutu/p/10834491.html
游戏里经常需要在角色上做描边,这里总结一下平时几种常见的描边做法。
一,两批次法:
优点是简单,效果直接,性价比高。
1. 定点对着法线方向外移,缺点是可以看出顶点之间有断裂
Shader "ly/Outline_2Pass_1" { Properties { _MainTex("Texture", 2D) = "white"{} _Outline(, )) = 0.02 _OutlineColor(,,,) } SubShader { //第一个批次,画描边 Pass { //Cull掉前面的一半,只让描边显示在后面 Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" fixed _Outline; fixed4 _OutlineColor; struct v2f { float4 pos : SV_POSITION; float4 color : COLOR; }; v2f vert (appdata_full v) { v2f o; //源顶点位置添加法线方向乘以参数的偏移量 v.vertex.xyz += v.normal * _Outline; //位置从自身坐标系转换到投影空间 //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex); o.pos = UnityObjectToClipPos(v.vertex); //描边颜色 o.color = _OutlineColor; return o; } float4 frag (v2f i) : COLOR { return i.color; //描边 } ENDCG } //第二个批次 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; half4 _MainTex_ST; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; fixed4 color : COLOR; }; v2f vert(appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; o.color = fixed4(, , , ); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
2. 得到法线在投影空间上的xy轴,作为偏移方向将顶点外移,得到的结果类似1,也有断裂
3. 顶点的位置作为方向矢量,则不会因为方向差距较大而断裂
Shader "ly/Outline_2Pass_2" { Properties { _MainTex("Texture", 2D) = "white"{} _Outline(, )) = 0.02 _OutlineColor(,,,) } SubShader { //第一个批次,画描边 Pass { //Cull掉前面的一半,只让描边显示在后面 Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" fixed _Outline; fixed4 _OutlineColor; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; }; v2f vert (appdata_full v) { v2f o; //位置从自身坐标系转换到投影空间 //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex); o.pos = UnityObjectToClipPos(v.vertex); //方式二,扩张顶点位置 //法线变换到投影空间 //float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); //得到投影空间的偏移 //float2 offset = TransformViewToProjection(normal.xy); ////方式三,把顶点当做方向矢量,在方向矢量的方向偏移 float3 dir = normalize(v.vertex.xyz); dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir); float2 offset = TransformViewToProjection(dir.xy); //有一些情况下,侧边看不到,所以把方式一和二的算法相结合 //float3 dir = normalize(v.vertex.xyz); //float3 dir2 = v.normal; //float D = dot(dir, dir2); //D = (1 + D / _Outline) / (1 + 1 / _Outline); //dir = lerp(dir2, dir, D); //dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir); //float2 offset = TransformViewToProjection(dir.xy); //offset = normalize(offset); //在xy两个方向上偏移顶点的位置 o.pos.xy += offset * o.pos.z * _Outline; return o; } float4 frag (v2f i) : COLOR { return _OutlineColor; //描边 } ENDCG } //第二个批次,略 }
二,边缘光
顶点的视角dir和法线dir点乘,得出偏离度,越靠近边缘,颜色的强度越高。
优点是节约批次。
v2f vert (appdata_full v) { v2f o; //略 //_RimColor边缘光颜色 //_RimPower边缘光强度 float3 viewDir = normalize(ObjSpaceViewDir(v.vertex)); - dot(normalize(v.normal), viewDir); fixed3 rimCol = smoothstep( - _RimPower, 1.0, dotProduct) * _RimColor; o.color = rimCol; //略 return o; }
三,后处理方式来画描边
优点是效果完美,缺点是消耗性能。
摄像机上挂一个脚本,处理后处理的步骤,outlineCamera 为临时摄像机,参数与主摄像机相同,看着同样的Unit层。
临时摄像机渲染到RT上,先画剪影,然后用自定义的描边shader画上去。
using UnityEngine; using UnitySampleAssets.ImageEffects; [RequireComponent(typeof(Camera))] [AddComponentMenu("Image Effects/Other/Post Effect Outline")] class PostEffectOutline : PostEffectsBase { public enum OutLineMethod { eIteration, eScale, } private Camera attachCamera; private Camera outlineCamera; private Shader simpleShader; private Shader postOutlineShader; private Material postOutlineMat; private RenderTexture mTempRT; , 1f, , 1f);// Color.green; [Range(, )] ; [Range(, )] ; public OutLineMethod outlineMethod = OutLineMethod.eIteration; void Awake() { FindShaders(); } void FindShaders() { if (!simpleShader) simpleShader = Shader.Find("ly/DrawSimple"); if (outlineMethod == OutLineMethod.eIteration) { if (!postOutlineShader) postOutlineShader = Shader.Find("ly/PostOutlineIteration"); } else { if (!postOutlineShader) postOutlineShader = Shader.Find("ly/PostOutlineScale"); } } protected override void Start() { base.Start(); attachCamera = GetComponent<Camera>(); if (outlineCamera == null) { outlineCamera = new GameObject().AddComponent<Camera>(); outlineCamera.enabled = false; outlineCamera.transform.parent = attachCamera.transform; outlineCamera.name = "outlineCam"; } postOutlineMat = new Material(postOutlineShader); } public override bool CheckResources() { CheckSupport(false); if (!isSupported) ReportAutoDisable(); return isSupported; } private void OnRenderImage(RenderTexture source, RenderTexture destination) { if (CheckResources() == false) { Graphics.Blit(source, destination); return; } outlineCamera.CopyFrom(attachCamera); outlineCamera.clearFlags = CameraClearFlags.Color; outlineCamera.backgroundColor = Color.black; outlineCamera.cullingMask = << LayerMask.NameToLayer("Unit"); if (mTempRT == null) mTempRT = RenderTexture.GetTemporary(source.width, source.height, source.depth); mTempRT.Create(); outlineCamera.targetTexture = mTempRT; outlineCamera.RenderWithShader(simpleShader, ""); postOutlineMat.SetTexture("_SceneTex", source); postOutlineMat.SetColor("_Color", outlineColor); postOutlineMat.SetInt("_Width", outlineWidth); postOutlineMat.SetInt("_Iterations", iterations); //画描边混合材质 Graphics.Blit(mTempRT, destination, postOutlineMat); mTempRT.Release(); } }
先用简单的shader画出剪影
Shader "ly/DrawSimple" { FallBack OFF }
然后就是这个自定义的描边shader画的过程。
第一种是类似高斯模糊的方式来迭代,迭代次数越多则越细腻。
// ly 类似高斯模糊方式迭代循环处理描边 Shader "ly/PostOutlineIteration" { Properties { _MainTex("Main Texture", 2D) = "black"{} //画完物体面积后的纹理 _SceneTex("Scene Texture", 2D) = "black"{} //原场景纹理 _Color(,,,0.8) //描边颜色 _Width( //描边宽度 _Iterations( //描边迭代次数(越多越平滑,消耗越高) } SubShader { Pass { CGPROGRAM sampler2D _MainTex; float2 _MainTex_TexelSize; sampler2D _SceneTex; fixed4 _Color; float _Width; int _Iterations; #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy; return o; } half4 frag(v2f i) : COLOR { //迭代次数为奇数,保证对称 + ; float ColorIntensityInRadius; float Tx_x = _MainTex_TexelSize.x * _Width; float Tx_y = _MainTex_TexelSize.y * _Width; //计算是否大于0,则此像素属于外边的范围内 ; k<iterations; k+=) { ; j<iterations; j+=) { ColorIntensityInRadius += tex2D(_MainTex, i.uv.xy + float2((k - iterations / ) * Tx_x, (j - iterations / ) * Tx_y)); } } //如果有颜色,或者不在外边的范围内,则渲染原场景。否则,在外边内,渲染描边。 || ColorIntensityInRadius == ) return tex2D(_SceneTex, i.uv); else - _Color.a)*tex2D(_SceneTex, i.uv); } ENDCG } } }
第二种方法简单些,直接把剪影的部分uv扩大,再把原图叠上去。
// ly 扩张剪影uv来填充描边 Shader "ly/PostOutlineScale" { Properties { _MainTex("Main Texture", 2D) = "black"{} //画完物体面积后的纹理 _SceneTex("Scene Texture", 2D) = "black"{} //原场景纹理 _Color(,,,) //描边颜色 _Width( //描边宽度 } SubShader { Pass { CGPROGRAM sampler2D _MainTex; sampler2D _SceneTex; float2 _SceneTex_TexelSize; fixed4 _Color; float _Width; #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; half2 uv[] : TEXCOORD0; half2 uv2[] : TEXCOORD2; }; v2f vert(appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv[] = v.texcoord.xy; o.uv[] = v.texcoord.xy; half2 offs = _SceneTex_TexelSize.xy * _Width; o.uv2[].x = v.texcoord.x - offs.x; o.uv2[].y = v.texcoord.y - offs.y; o.uv2[].x = v.texcoord.x + offs.x; o.uv2[].y = v.texcoord.y - offs.y; o.uv2[].x = v.texcoord.x + offs.x; o.uv2[].y = v.texcoord.y + offs.y; o.uv2[].x = v.texcoord.x - offs.x; o.uv2[].y = v.texcoord.y + offs.y; ) { o.uv[].y = - o.uv[].y; o.uv2[].y = - o.uv2[].y; o.uv2[].y = - o.uv2[].y; o.uv2[].y = - o.uv2[].y; o.uv2[].y = - o.uv2[].y; } return o; } half4 frag(v2f i) : COLOR { fixed4 stencil = tex2D(_MainTex, i.uv[]); // 有剪影的部分,显示原图 if (any(stencil.rgb)) { fixed4 framebuffer = tex2D(_SceneTex, i.uv[]); return framebuffer; } // 没有剪影的部分,先把剪影扩张,扩张出颜色的部分用剪影,没有颜色的用原图 else { fixed4 color1 = tex2D(_MainTex, i.uv2[]); fixed4 color2 = tex2D(_MainTex, i.uv2[]); fixed4 color3 = tex2D(_MainTex, i.uv2[]); fixed4 color4 = tex2D(_MainTex, i.uv2[]); fixed4 color; color.rgb = max(color1.rgb, color2.rgb); color.rgb = max(color.rgb, color3.rgb); color.rgb = max(color.rgb, color4.rgb); if (any(color.rgb)) { return _Color; //color.a = (color1.a + color2.a + color3.a + color4.a) * 0.25; //return color; } else { fixed4 framebuffer = tex2D(_SceneTex, i.uv[]); return framebuffer; } } } ENDCG } } SubShader { Pass { SetTexture[_MainTex]{} } } Fallback Off }
Unity3d游戏角色描边的更多相关文章
- Unity3D 游戏开发构架篇 ——角色类的设计与持久化
在游戏开发中,游戏角色占了很大的篇幅,可以说游戏中所有的内容都是由主角所带动.这里就介绍一下角色类的设计和持久化. 一.角色类应用场景和设计思想 游戏中的角色类型不一而足,有不同的技能,有不同的属性等 ...
- Unity3D游戏开发从零单排(四) - 制作一个iOS游戏
提要 此篇是一个国外教程的翻译,尽管有点老,可是适合新手入门. 自己去写代码.debug,布置场景,能够收获到非常多.游戏邦上已经有前面两部分的译文,这里翻译的是游戏的最后一个部分. 欢迎回来 在第一 ...
- [Unity3D]Unity3D游戏开发3D选择场景中的对象,并显示轮廓效果强化版
大家好,我是秦培,欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei. 在上一篇文章中,我们通过自己定义着色器实现了一个简单的在3D游戏中选取.显示物体轮廓的实例. 在文章 ...
- [Unity3D]Unity3D游戏开发之跑酷游戏项目解说
大家好,我是秦元培.我參加了CSDN2014博客之星的评选,欢迎大家为我投票,同一时候希望在新的一年里大家能继续支持我的博客. 大家晚上好.我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.c ...
- [Unity3D]Unity3D游戏开发之伤害数值显示
大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei.众所周知,在RPG游戏策划中最为重要的一个环节是数值策划.数值策划是一个关于游戏平衡方面的概念 ...
- [Unity3D]Unity3D游戏开发之怪物AI
大家好.欢迎大家关注由我为大家带来的Unity3D游戏开发系列文章,我的博客地址为:http://blog.csdn.net/qinyuanpei. 在上一篇文章中,我们基本上实现了一个 ...
- Unity3D游戏开发之C#编程中常见数据结构的比较
一.前言 Unity3D是如今最火爆的游戏开发引擎,它可以让我们能轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型的互动内容.它支持2D/3D游戏开发,据不完全统计,目前国内80%的手机游戏都 ...
- Unity3D游戏开发初探—2.初步了解3D模型基础
一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...
- Unity3D游戏在iOS上因为trampolines闪退的原因与解决办法
http://7dot9.com/?p=444 http://whydoidoit.com/2012/08/20/unity-serializer-mono-and-trampolines/ 确定具体 ...
随机推荐
- java 面试题之交通灯管理系统
需求: 交通灯管理系统的项目需求 Ø 异步随机生成按照各个路线行驶的车辆. 例如: 由南向而来去往北向的车辆 ---- 直行车辆 由西向而来去往南向的车辆 ---- 右转车辆 由东向而来去往南向的车辆 ...
- vue中动态class写法
<div class="wrap" :class="{active:index==current}"></div> <div cl ...
- Linux 内核硬件资源
一个 ISA 设备可配备有 I/O 端口, 内存区, 和中断线. 尽管 x86 处理器支持 64 KB I/O 端口内存(即, 处理器有 16 条地址线), 一些老 PC 硬 件仅解码最低的 10 位 ...
- [Linux] 使用awk比较两个文件的内容
当需要比较A , B两个文件 , A文件中存在 , 并且把也在B文件中存在的行去除掉 , 可以使用这个awk的用法来 awk '{if(ARGIND==1) {val[$0]}else{if($0 ...
- (转载)window安装mysql
一.MYSQL的安装 1.打开下载的mysql安装文件mysql-5.5.27-win32.zip,双击解压缩,运行“setup.exe”. 2.选择安装类型,有“Typical(默认)”.“Comp ...
- python3 实现删除数组中相同的元素
# #把数组中相同的元素去除 # #第一种方式: def del_repeatnum(s=[1,1,1,2,2,3,3,4]): s1=[] for i in s: print(i) if i not ...
- 18.函数复习,文件处理b模式(二进制处理),文件处理其他高级玩法
1.函数复习 # map # l = [1,2,3,4,5] # print(list(map(str,l))) # reduce # l = [1,2,3,4,5] # from functools ...
- 0006 列表(ul、ol、dl)
1. 列表标签(重点) 学习目标 理解 无序列表的应用场景 自定义列表的应用场景 应用 无序列表语法 自定义列表语法 问? 前面我们知道表格一般用于数据展示的,但是网页中还是有很多跟表格类似的布局,如 ...
- 最小化centos7.4系统静默安装oracle12.2
一 orace简介 ORACLE(甲骨文)公司.Oracle Database (甲骨文数据库) 是一个具有对象和可扩展标记语言(XML)功能的关系数据库,提供的以分布式数据库为核心的一组软件产品,是 ...
- python+selenium+Chrome options参数
python+selenium+Chrome options参数 Chrome Options常用的行为一般有以下几种: 禁止图片和视频的加载:提升网页加载速度. 添加代理:用于翻墙访问某些页面,或者 ...