本文主要来讲几种描边的实现方法

1.法线外扩

一般期望的描边效果,就是在模型外面有一圈选边,因此我们可以把模型扩大一点点,利用这个扩大的边缘来实现描边效果。可以看出,扩大的方向其实就是法线的方向,边缘的法线和视线夹角基本成90度。所以我们可以在第一个pass 朝法线方向扩大模型。 要扩大模型,自然是在vertexshader阶段处理,我们实际仅需要扩大边缘,因此我们只需要渲染背面,因为我们第二个pass就会盖在第一个pass的结果上,仅留下扩大的边缘。

//把法线转换到视图空间

float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);

//把法线转换到投影空间

float2 pnormal_xy = mul((float2x2)UNITY_MATRIX_P,vnormal.xy);

//朝法线方向外扩

o.vertex.xy = o.vertex.xy + pnormal_xy * _Outline;

整个代码实现:

Shader "xjm/outline"

{

//法线外扩实现描边

Properties

{

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

_Outline("Outline",float) = 0.1

_OutlineColor("OutlineColor",Color) = (0,0,0,1)

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

//描边阶段,法线外扩,渲染背面

Pass

{

//只需要边缘外扩

Cull Front

ZWrite Off

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

struct appdata

{

float4 vertex : POSITION;

float3 normal : NORMAL;

};

struct v2f

{

float4 vertex : SV_POSITION;

};

float _Outline;

float4 _OutlineColor;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

//把法线转换到视图空间

float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);

//把法线转换到投影空间

float2 pnormal_xy = mul((float2x2)UNITY_MATRIX_P,vnormal.xy);

//朝法线方向外扩

o.vertex.xy = o.vertex.xy + pnormal_xy * _Outline;

return o;

}

fixed4 frag (v2f i) : SV_Target

{

return _OutlineColor;

}

ENDCG

}

//正常阶段

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

struct appdata

{

float4 vertex : POSITION;

float2 uv :TEXCOORD0;

};

struct v2f

{

float4 vertex : SV_POSITION;

float2 uv:TEXCOORD0;

};

sampler2D _MainTex;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv = v.uv;

return o;

}

fixed4 frag (v2f i) : SV_Target

{

return tex2D(_MainTex,i.uv);

}

ENDCG

}

}

}

实现的效果:

可以看得出,除了边缘有被描边之外,里面(模型的前面)也有描边的线条。如何去掉内部的线条呢?可以在第一个pass里关闭深度写入,关闭了深度写入之后,第二个pass就会完全盖住这个模型(除了外扩部分)

但是你再看看在Game视图下描边不起作用了!

看看FrameDebuger:

看到Unity在渲染完模型之后,再渲染了天空盒,因为描边pass没有写入深度,所以被天空盒覆盖了。

注:Unity的geometry类型的渲染顺序是从前往后的,而Transparent类型是从后往前的。天空盒的渲染顺序位于geometry之后,Transparent之前。因此我们把描边pass放在Transparent的渲染顺序就不会被geometry类型遮挡了。

因此把渲染队列改成Transparent就可以了。

后处理实现描边效果

​ 法线外扩的方法有一个缺点,就是当模型边缘比较菱角分明的时候,法线外扩后会出现不连续的现象。因此我们还可以用后处理来实现描边或者外发光的效果。

​ 想象一下,我们要实现描边,按法线外扩的做法,是在渲染正常画面之前先渲染了一张比模型边缘扩大了一点点的纯色图片。再用正常的渲染模型盖住纯色模型,只让纯色图片的外扩的边缘显示出来。

​ 在后处理阶段要怎么处理呢?

处理流程我们可以在OnPreRender函数里先渲染一张纯色照片。OnPreRender函数在渲染之间执行。在正常渲染之间,我们先用一个纯色shader 替换要描边物体的shader,把渲染出来的纯色图片渲染到纹理上。

怎么把这张纯色照片的边缘外扩的呢? 可以选择模糊的方法。高斯模糊的具体实现在这里就不多说了。我们模糊了之后的照片,边缘就会往外扩散。

得到模糊的照片后,我们用模糊照片的颜色减去纯色照片的颜色,就会得到一个边缘照片。

用正常渲染的图,叠加边缘图片就可以得到一个外发光或者描边的效果了。

2.1 普通后处理实现

上面的4个流程中,在Unity5之前,我们可以额外增加一个摄像机,摄像机只渲染要描边的物体。而这个摄像机渲染到RenderTexture上。这个摄像机用来渲染纯色图片。 仅渲染Player 层,创建一个RenderTexture

m_outlineCamera.cullingMask = 1 << LayerMask.NameToLayer("Player");

int width = m_outlineCamera.pixelWidth >> m_downSampler;

int height = m_outlineCamera.pixelHeight >> m_downSampler;

m_renderTexture = RenderTexture.GetTemporary(width, height, 0);

在渲染之间,用RenderWithShader函数替换shader。这里的意思是在此刻,OutlineCamera摄像机下的所有物体都用outlineShader渲染。

private void OnPreRender()

{

//先渲染到RT if (m_outlineCamera.enabled)

{

m_outlineCamera.targetTexture = m_renderTexture;

m_outlineCamera.RenderWithShader(m_outlinePreShader, "");//渲染了一张纯色RT }

}

渲染完后,在OnRenderImage阶段:

private void OnRenderImage(RenderTexture source, RenderTexture destination)

{

int rtW = source.width >> m_downSampler;

int rtH = source.height >> m_downSampler;

var temp1 = RenderTexture.GetTemporary(rtW, rtH, 0);

var temp2 = RenderTexture.GetTemporary(rtW, rtH, 0);

// 先模糊纯色的图片 Graphics.Blit(m_renderTexture, temp1);

// 模糊迭代 for (int i = 0; i < blurIterator; ++i)

{

Graphics.Blit(temp1, temp2, outlineMaterial, 0);

Graphics.Blit(temp2, temp1, outlineMaterial, 1);

}

if (hardSide)

{

outlineMaterial.EnableKeyword("_Hard_Side");

}

else

{

outlineMaterial.DisableKeyword("_Hard_Side");

}

outlineMaterial.SetTexture("_BlurTex", temp2);

outlineMaterial.SetTexture("_SrcTex", m_renderTexture);

outlineMaterial.SetColor("_OutlineColor", outlineColor);

Graphics.Blit(source, destination, outlineMaterial, 2);

RenderTexture.ReleaseTemporary(temp1);

RenderTexture.ReleaseTemporary(temp2);

}

来看看shader部分,纯色shader, 就返回一个边缘色就行了。非常简单:

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

float4 _OutlineColor;

struct appdata

{

float4 vertex : POSITION;

};

struct v2f

{

float4 vertex : SV_POSITION;

};

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

return _OutlineColor;

}

ENDCG

​ 描边shader:

/// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "xjm/outline_postEffect"

{

Properties

{

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

_BlurSize("Blur Size",float) = 1

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

_SrcTex("SrcTex", 2D) = "white"{}

_OutlineColor("OutLine Color",Color) = (1,1,1,1)

}

CGINCLUDE

#include "UnityCG.cginc"

uniform half4 _MainTex_TexelSize;

// 边缘分成了硬边和软边两种。

#pragma shader_feature _Hard_Side

float _BlurSize;

sampler2D _MainTex;

sampler2D _BlurTex;

sampler2D _SrcTex;

float4 _OutlineColor;

// 高斯模糊部分 ---{

//高斯模糊权重

static const half4 GaussWeight[7] =

{

half4(0.0205,0.0205,0.0205,0),

half4(0.0855,0.0855,0.0855,0),

half4(0.232,0.232,0.232,0),

half4(0.324,0.324,0.324,1),

half4(0.232,0.232,0.232,0),

half4(0.0855,0.0855,0.0855,0),

half4(0.0205,0.0205,0.0205,0)

};

struct v2f_Blur

{

float4 pos:SV_POSITION;

half2 uv:TEXCOORD0;

half2 offset:TEXCOORD1;

};

// 水平方向的高斯模糊

v2f_Blur vert_blur_Horizonal(appdata_img v)

{

v2f_Blur o;

o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;

o.offset = _MainTex_TexelSize.xy * half2(1,0)*_BlurSize;

return o;

}

// 垂直方向的高斯模糊

v2f_Blur vert_blur_Vertical(appdata_img v)

{

v2f_Blur o;

o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;

o.offset = _MainTex_TexelSize.xy * half2(0,1)*_BlurSize;

return o;

}

half4 frag_blur(v2f_Blur i):COLOR

{

half2 uv_withOffset = i.uv - i.offset * 3;

half4 color = 0;

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

{

half4 texcol = tex2D(_MainTex,uv_withOffset);

color += texcol * GaussWeight[j];

uv_withOffset += i.offset;

}

return color;

}

// }--- 高斯模糊部分

//add

struct v2f_add

{

float4 pos : SV_POSITION;

float2 uv : TEXCOORD0;

float2 uv1 : TEXCOORD1;

};

v2f_add vert_add(appdata_img v)

{

v2f_add o;

o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;

o.uv1 = o.uv;

#if UNITY_UV_STARTS_AT_TOP

o.uv.y = 1- o.uv.y;

#endif

return o;

}

half4 frag_add(v2f_add i):COLOR

{

//获取屏幕

fixed4 scene = tex2D(_MainTex, i.uv1);

fixed4 blurCol = tex2D(_BlurTex,i.uv1);

fixed4 srcCol = tex2D(_SrcTex,i.uv1);

#if _Hard_Side

// 如果是硬边描边,就用模糊纹理-原来的纹理得到边缘

fixed4 outlineColor = saturate(blurCol - srcCol);

// all(outlineColor.rgb) 三个分量都不等于0,返回1,否则返回0.类似&&运算

// any(outlineColor.rgb);rgb 任意不为 0,则返回 true。类似||运算

// 如果rgb都不为0(硬边部分)就显示硬边,否则都显示scene部分。

return scene * (1 - all(outlineColor.rgb)) + _OutlineColor * any(outlineColor.rgb);

#else

return saturate(blurCol - srcCol) + scene;

#endif

}

//add

ENDCG

SubShader

{

ZTest Always

ZWrite Off

Fog{ Mode Off }

//0

Pass

{

ZTest Always

Cull Off

CGPROGRAM

#pragma vertex vert_blur_Horizonal

#pragma fragment frag_blur

ENDCG

}

//1

Pass

{

ZTest Always

Cull Off

CGPROGRAM

#pragma vertex vert_blur_Vertical

#pragma fragment frag_blur

ENDCG

}

//2

Pass

{

ZTest Off

Cull Off

CGPROGRAM

#pragma vertex vert_add

#pragma fragment frag_add

ENDCG

}

}

}

软边(外发光):

Pass0 和Pass1 分别是水平和垂直方向的模糊,在Pass 2 里进行相减。

blurCol - srcCol 后的图像是这样子的

再叠加正常图像,可以得到:

得到外发光的描边。

saturate(blurCol - srcCol) + scene;

要实现硬边的效果,还在再处理一下,由上面那张相减之后的图片可以看到边缘是一个渐变的过程。边缘色越外越接近黑色RGB(0,0,0)。因此我们只选取RGB都大于0的部分像素就可以剔除渐变部分,只留下硬边部分。

saturate(blurCol - srcCol); 把结果限制为[0,1],因为相减可能会导致负值,从而描边在模型内部染色。

// 如果是硬边描边,就用模糊纹理-原来的纹理得到边缘 fixed4 outlineColor = saturate(blurCol - srcCol);

// all(outlineColor.rgb) 三个分量都不等于0,返回1,否则返回0.类似&&运算 // any(outlineColor.rgb);rgb 任意不为 0,则返回 true。类似||运算 // 如果rgb都大于0(硬边部分)就显示硬边,否则都显示scene部分。 return scene * (1 - all(outlineColor.rgb)) + _OutlineColor * any(outlineColor.rgb);

结果:

2.2 Command Buffer 实现

​ 除了用额外的摄像机之外,在Unity5 还可以用Command Buffer来实现,代码会更简单。

因为Command Buffer 可以在RenderImage 里执行。所以可以在初始化的时候,设置Command Buffer的renderTexture,设置要描边物体的shader。然后在后处理的时候再执行这个Command。就可以得到纯色图像了。

​ 初始化部分:

m_commandBuffer = new CommandBuffer();

if (m_renderTexture == null)

m_renderTexture = RenderTexture.GetTemporary(Screen.width >> m_downSampler, Screen.height >> m_downSampler, 0);

m_commandBuffer.SetRenderTarget(m_renderTexture);

m_commandBuffer.ClearRenderTarget(true, true, Color.black);

foreach (var renderer in Renderers)

{

m_commandBuffer.DrawRenderer(renderer, outlinePreMaterial)

}

​ 在后处理阶段:

int rtW = source.width >> m_downSampler;

int rtH = source.height >> m_downSampler;

// 插入commandbuffer,绘制出纯色的图片 m_outlinePreMat.SetColor("_OutlineColor", outlineColor);

Graphics.ExecuteCommandBuffer(m_commandBuffer);

var temp1 = RenderTexture.GetTemporary(rtW, rtH, 16);

var temp2 = RenderTexture.GetTemporary(rtW, rtH, 16);

Graphics.Blit(m_renderTexture, temp1, outlineMaterial, 0);

Graphics.Blit(temp1, temp2, outlineMaterial, 1);

for (int i = 0; i < blurIterator; ++i)

{

Graphics.Blit(temp2, temp1, outlineMaterial, 0);

Graphics.Blit(temp1, temp2, outlineMaterial, 1);

}

if (onlyShowBlur)

{

Graphics.Blit(temp2, destination);

RenderTexture.ReleaseTemporary(temp1);

RenderTexture.ReleaseTemporary(temp2);

return;

}

//add //把模糊的边框加到原来的照片中 if (hardSide)

{

outlineMaterial.EnableKeyword("_Hard_Side");

outlineMaterial.SetColor("_OutlineColor", outlineColor);

}

else

{

outlineMaterial.DisableKeyword("_Hard_Side");

}

outlineMaterial.SetTexture("_BlurTex", temp2);

outlineMaterial.SetTexture("_SrcTex", m_renderTexture);

outlineMaterial.SetFloat("_BlurSize", blurSize);

Graphics.Blit(source, destination, outlineMaterial, 2);

RenderTexture.ReleaseTemporary(temp1);

RenderTexture.ReleaseTemporary(temp2);

}

​ 不用额外增加camera,非常方便。

unity描边发光shader_unity shader实例#1 轮廓渲染-描边相关推荐

  1. 5.8 使用轮廓化描边命令制作心形艺术图标 [Illustrator CC教程]

    原文:http://coolketang.com/staticDesign/5a97b8c39f54542163da6f43.html 1. 本节课将为您演示轮廓化命令的使用.首先选择文档中的心形图形 ...

  2. ai描边工具怎么打开_ai轮廓化描边在哪 轮廓化描边快捷键,需要技巧

    学习ai的朋友,经常会用到轮廓化描边.Adobe illustrator中的轮廓化描边在哪里,AI中轮廓化描边的快捷键是什么? 跟夏夏一起来看下吧! 工具/材料 电脑 ai软件(说明:夏夏使用的是ai ...

  3. 【shaderforge小实例】 轮廓内发光

    本篇使用unity shaderforge插件和编写unity shader code两种方式来实现轮廓内发光 发光效果 内发光:发光效果不能超出模型的范围,模型中心最暗向着边缘方向逐渐变亮 内发光效 ...

  4. D3D9 Shader实例教程

    啥是Shader? Shader是一段运行在GPU上的小程序,是运行在GPU上的Pipeline上的特定的可编程单元的小程序. 从D3D9 API层面学习Shader编程 随着Unity3D的流行,很 ...

  5. unity 3d物体描边效果_从零开始的卡通渲染描边篇

    序言: 一直对卡通渲染非常感兴趣,前后翻找了不少的文档,做了一些工作.前段时间<从零开始>的手游上线了,试着渲染了一下的其中模型,觉得效果很不错.打算写一个专栏记录其中的渲染技术.在后面的 ...

  6. shader实例:实现类似宝可梦 Pokemon 的战斗转场

    宝可梦游戏在进入战斗前会有类似这样转场动画. 例子中使用的纹理质量较差,边缘比较模糊,和shader无关. 这个UI是盖在所有UI最前面的.可以使用shader来完成这个工作,而不是复杂的动画. 使用 ...

  7. unity 全息和xRay shader

    unity 全息和xRay shader 这个是网上的效果,科幻的感觉是不是很强烈. 下面是我们去实现的效果. 先看下效果图,左边的是Xray的效果,右边是全息的效果.都有着异曲同工的妙处. 全息的效 ...

  8. Unity 3D 环境特效||Unity 3D 游戏场景设计实例

    Unity 3D 环境特效 一般情况下,要在游戏场景中添加雾特效和水特效较为困难,因为需要开发人员懂得着色器语言且能够熟练地使用它进行编程. Unity 3D 游戏开发引擎为了能够简单地还原真实世界中 ...

  9. Shader实例(流光实现)

    Shader实例(流光实现) 流光效果 首先来看一下流光效果.流光效果是一个非常常见的效果,不仅仅是游戏,一些广告之类的也都会有这种效果.流光的原理还是比较简单的:首先就是需要一张流光图,这张流光图的 ...

最新文章

  1. Linux系统文件类型
  2. java读取数据,2,2,1方式读取
  3. 020.2.2 runtime类
  4. Mathemmatica 新函数
  5. python找房源_Python租房信息分析!找到最适合自己的房源信息!
  6. YBTOJ洛谷P4551:最长异或路径(trie树)
  7. 机器学习入门学习视频和书籍(笔记保存)
  8. 华为Y9s海外官网上架:升降式全面屏+侧面指纹识别
  9. easyUI +datagirdview加载本地json的方式 笔记
  10. stacking模型融合_算法实践七:模型融合
  11. 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
  12. node.js以及读取写入文件
  13. 2021辽宁省大学生程序设计竞赛 C D E F G I L
  14. 微信小程序九宫格抽奖
  15. 信号完整性分析学习--12--IBIS模型
  16. 基于Android studio个人财务记账管理系统
  17. fluent p1模型_Fluent辐射传热模型理论以及相关设置(一)
  18. hadoop Permission denied (publickey,password,keyboard-interactive).
  19. SA markdown
  20. pxc wsrep_sst_method均配置为xtrabackup-v2报错

热门文章

  1. 【Linux使用】Centos 7设置时区与时钟(chrony / ntp /systemd)
  2. Intel 64/x86_64/IA-32/x86处理器 - 锁原子操作(1) - 处理器保证的原子操作
  3. core微型计算机,UPC-CHT01 | 专业创客微型计算机板_UP Core - AAEON
  4. VS2008中C++打开Excel(MFC)
  5. qt QMessageBox 中文乱码的问题
  6. 各个JSON技术的比较(Jackson,Gson,Fastjson)的对比
  7. GoldenGate 之 Bounded Recovery说明
  8. 用fuser或者lsof解决无法umount问题(device is busy)
  9. android market 选择
  10. 山寨一个ini文件的解析器