Shader 轮廓线(描边)总结 |
您所在的位置:网站首页 › 线描怎么做 › Shader 轮廓线(描边)总结 |
在《Real Time Rendering, third edition》一书中,作者把轮廓线的实现方法分成5种类型 基于观察角度和表面法线的轮廓线渲染过程式几何轮廓线渲染,使用两个Pass渲染基于图像处理的轮廓线渲染(屏幕后处理)基于轮廓边检测的轮廓线渲染混合了上述的几种渲染方法 基于观察角度和表面法线的轮廓线渲染原理:法线和视线垂直的地方认为是边缘,这种方法和实现边缘光类似,可以参考这篇文章,Shader边缘光 优点:这种方法简单快速,可以在一个Pass中就得到渲染结果 缺点:局限性很大,轮廓效果很难控制,很多模型渲染出来的描边效果都不尽如人意 过程式几何轮廓线渲染,使用两个Pass渲染 顶点外扩原理:第一个Pass渲染背面的面片,并使用某些技术让它的轮廓可见;第二个Pass再正常渲染正面的面片 优点:快速有效,并且适用于绝大多数表面平滑的模型 缺点:不适合表面法线不连续的模型,如立方体轮廓线会出现断裂的情况。
断裂解决方法:在dcc软件里, 将模型连续的法线烘焙到模型的顶点色里,在进行外扩描外扩时,朝顶点色映射后的方向扩 模板缓冲实现描边参考 Stencil Test 模板测试 基于图像处理的轮廓线渲染(屏幕后处理)原理:该方法细分为两种边缘检测,一种是基于颜色的边缘检测,根据相邻像素颜色变化来判断,变化越大越可能是边缘点。另一种是基于法线和深度值的边缘检测,相邻位置差值越大,越可能是边缘。 优点:可以适用于任何种类的模型。 缺点:基于颜色的边缘检测会产生很多我们不希望得到的边缘线,物体的纹理、阴影等位置也被描上黑边。 基于法线和深度值的边缘检测,一些深度和法线变化很小的轮廓无法被检测出来,例如桌子上的纸张。 基于颜色的边缘检测原理是利用一些边缘检测算子对图像进行卷积(convolution)操作,卷积操作指的就是使用一个卷积核(kernel)对一张图像中的每个像素进行一系列操作。卷积核通常是一个四方形网格结构(例如2×2、3×3的方形区域),该区域内每个方格都有一个权重值。 为了实现屏幕后处理,需要把脚本绑定到相机上,获取相机看到的图像再进行处理。 首先定义屏幕后处理基类,检查一系列条件是否满足 using UnityEngine; using System.Collections; //编辑器状态下也可以执行该脚本来查看效果 [ExecuteInEditMode] [RequireComponent (typeof(Camera))] public class PostEffectsBase : MonoBehaviour { // Called when start protected void CheckResources() { bool isSupported = CheckSupport(); if (isSupported == false) NotSupported(); } // Called in CheckResources to check support on this platform protected bool CheckSupport() { if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) { Debug.LogWarning("This platform does not support image effects or render textures."); return false; } return true; } // Called when the platform doesn't support this effect protected void NotSupported() { enabled = false; } protected void Start() { CheckResources(); } // Called when need to create the material used by this effect protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) { if (shader == null) return null; if (shader.isSupported && material && material.shader == shader) return material; if (!shader.isSupported) return null; material = new Material(shader); material.hideFlags = HideFlags.DontSave; if (material) return material; return null; } }实际的边缘检测脚本,主要是给shader传递参数的 using UnityEngine; using System.Collections; public class EdgeDetection : PostEffectsBase { //该效果需要用的Shader public Shader edgeDetectShader; private Material edgeDetectMaterial = null; public Material material { get { edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial); return edgeDetectMaterial; } } //调整边缘线强度,当edgesOnly值为0时,边缘将会叠加在原渲染图像上; //当edgesOnly值为1时,则会只显示边缘,不显示原渲染图像。 [Range(0.0f, 1.0f)] public float edgesOnly = 0.0f; public Color edgeColor = Color.black; public Color backgroundColor = Color.white; /// /// Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数中的一系列操作后, /// 再把目标渲染纹理,即第二个参数对应的渲染纹理显示到屏幕上,OnRenderImage函数会在所有的 /// 不透明和透明的Pass执行完毕后被调用 /// void OnRenderImage (RenderTexture src, RenderTexture dest) { if (material != null) { material.SetFloat("_EdgeOnly", edgesOnly); material.SetColor("_EdgeColor", edgeColor); material.SetColor("_BackgroundColor", backgroundColor); //使用材质对src纹理进行处理,src纹理会被传递给Shader中名为_MainTex的纹理属性。 Graphics.Blit(src, dest, material); } else { Graphics.Blit(src, dest); } } }使用Sobel算子来实现边缘检测 Shader "MyCustom/Edge Detection" { Properties { _MainTex ("[纹理] Base (RGB)", 2D) = "white" {} //当edgesOnly值为0时,边缘将会叠加在原图上;当值为1时,则会只显示边缘,不显示原渲染图像 _EdgeOnly ("[边缘线强度] Edge Only", Float) = 1.0 _EdgeColor ("[描边颜色] Edge Color", Color) = (0, 0, 0, 1) _BackgroundColor ("[背景颜色] Background Color", Color) = (1, 1, 1, 1) } SubShader { Pass { //屏幕后处理实际上是在场景中绘制了一个与屏幕同宽同高的四边形面片,关闭深度写入, //是为了防止它“挡住”在其后面被渲染的物体。这些设置可以认为是用于屏幕后处理的标配 ZTest Always Cull Off ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert #pragma fragment fragSobel sampler2D _MainTex; //xxx_TexelSize是Unity为我们提供的访问xxx纹理对应的每个纹素的大小 //例如,一张512×512大小的纹理,该值大约为0.001953(即1/512) uniform half4 _MainTex_TexelSize; fixed _EdgeOnly; fixed4 _EdgeColor; fixed4 _BackgroundColor; struct v2f { float4 pos : SV_POSITION; half2 uv[9] : TEXCOORD0; }; v2f vert(appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); half2 uv = v.texcoord; o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1); //当前像素左下角 o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1); o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1); o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0); o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0); o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0); o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1); o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1); o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 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, 0, 1, -2, 0, 2, -1, 0, 1}; const half Gy[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; half texColor; half edgeX = 0; half edgeY = 0; for (int it = 0; it |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |