[导入]基于法线的边缘检测

本文介绍了边缘高亮技术中的两种方法:一种是在图像空间域进行边缘检测,效果较好但计算较慢;另一种是通过放大模型实现,适用于低端显卡,但可能因法线信息错误导致画面错乱。此外,还探讨了基于法线的方法及其渲染流程。
在边缘高亮效果中我提到过两种方法, 各有优缺点吧图像空间域的边缘检测效果比较好, 中间没有多余的线条. 缺点是PS中计算比较慢第二种把模型"放大"(其实是变胖)的做法, 可以在VS中完成, 不需要额外的RenderTarget, 适合低端显卡使用, 适应性好. 不如果模型法线信息不对的话, 会造成画面错乱. 实际使用时可以根据W值(不用Z深度)来画出远近粗细一样的线条这次提到的基于法线的方法, 其实跟2D的空间域边缘检测很相似, 如果要求结果是绘制物体的线条图而仅仅是一个边缘轮廓时, 它就派上用场了. (还是要用PS去算, 实际使用时要注意性能问题)基本的渲染流程(2 pass):图第一个pass用于生成法线图到一张RenderTarget上, 第二个pass跟据这张法线图来做边缘检测.实际使用时可以采用Multi-RenderTarget来加速法线信息要在pixel shader里进行向量化, 不然会在一些面上出块很淡的颜 3422631.aspx
文章来源: http://blog.youkuaiyun.com/xoyojank/archive/2008/12/01/3422631.aspx

转载于:https://www.cnblogs.com/xoyojank/archive/2008/12/02/1366283.html

<think>我们正在Unity URP(Universal Render Pipeline)下实现边缘光效果。边缘光效果通常是在物体边缘处产生发光效果,这可以通过多种方式实现,如菲涅尔效应、后处理或使用特定Shader。根据引用内容,我们可以参考以下几种方法: 1. **使用菲涅尔公式实现边缘光**(引用[1]):菲涅尔效应通常表现为视线与表面法线夹角越大(即边缘处),反射越强。但引用[1]提到,能量罩的外发光效果(周围亮中间透明)与菲涅尔效果相反,因此我们可以调整菲涅尔公式来实现边缘发光。核心公式是:$\vec{N} \cdot \vec{V}$,其中$\vec{N}$是法线向量,$\vec{V}$是视线向量。在边缘处,点积结果较小,因此我们可以用$1 - \vec{N} \cdot \vec{V}$来得到边缘亮的效果。 2. **使用Surface Shader中的Emission属性**(引用[2]):在URP中,我们可以编写一个Surface Shader,利用Emission属性在边缘处发光。具体实现时,我们计算视线方向与法线的点积,然后用一个颜色和强度参数来控制边缘发光的颜色和宽度。 3. **使用插件或脚本动态添加边缘光**(引用[3][4]):引用中提到了一个Outline组件,可以通过代码动态添加边缘光效果。在URP中,我们可以使用类似的方法,但需要注意URP的兼容性。 ### 实现步骤(基于菲涅尔效应的Shader实现) 下面我们详细说明如何在URP中通过编写Shader实现边缘光效果。 #### 步骤1:创建URP兼容的Shader 在URP中,我们需要创建一个Unlit Shader或者修改Lit Shader来添加边缘光效果。这里我们创建一个新的Shader,命名为“RimLightURP”。 #### 步骤2:编写Shader代码 我们将使用菲涅尔效应来计算边缘光强度,并将其加到自发光(Emission)通道上。 ```glsl Shader "Custom/RimLightURP" { Properties { _MainTex ("Texture", 2D) = "white" {} _RimColor ("Rim Color", Color) = (1,1,1,1) _RimPower ("Rim Power", Range(0.1, 8)) = 3 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float3 normalOS : NORMAL; }; struct Varyings { float2 uv : TEXCOORD0; float4 positionHCS : SV_POSITION; float3 normalWS : TEXCOORD1; float3 viewDirWS : TEXCOORD2; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float4 _RimColor; float _RimPower; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; // 将顶点位置转换到裁剪空间 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); // 获取世界空间法线 OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); // 计算世界空间视角方向(从顶点指向相机) float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.viewDirWS = GetWorldSpaceNormalizeViewDir(positionWS); OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex); return OUT; } half4 frag(Varyings IN) : SV_Target { // 采样纹理 half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv); // 归一化法线和视角方向 float3 normalWS = normalize(IN.normalWS); float3 viewDirWS = normalize(IN.viewDirWS); // 计算法线与视角方向的点积 float NdotV = dot(normalWS, viewDirWS); // 菲涅尔效果:边缘处(夹角大)点积小,所以用1减去点积,然后取幂次控制边缘宽度 float rim = pow(1.0 - saturate(NdotV), _RimPower); // 将边缘光颜色乘以强度加到自发光上 half3 emission = _RimColor.rgb * rim; // 最终颜色为纹理颜色加上边缘光 half3 finalColor = texColor.rgb + emission; return half4(finalColor, texColor.a); } ENDHLSL } } } ``` #### 步骤3:创建材质并应用 1. 在Unity中,创建一个材质,将上面编写的Shader赋给它。 2. 调整材质参数: - `_RimColor`:边缘光颜色 - `_RimPower`:控制边缘光的宽度,值越大边缘越细。 #### 步骤4:应用到模型 将材质拖拽到模型上即可看到效果。 ### 其他实现方式 - **使用后处理**:通过屏幕空间法线信息来检测边缘,然后添加发光效果。这种方法适用于整个场景,但可能更消耗性能。 - **使用插件**:如引用[3][4]提到的Outline组件,可能需要寻找支持URP的边缘光插件。 ### 注意事项 - 在URP中,获取法线和视线向量需要使用URP Shader Library中的函数,如示例中的`TransformObjectToWorldNormal`和`GetWorldSpaceNormalizeViewDir`。 - 如果需要在透明物体上使用,需要修改渲染队列和混合模式。 ### 相关问题 1. 如何在URP中实现透明物体的边缘光效果? 2. 如何通过后处理实现边缘光效果? 3. 在URP中,如何优化边缘光Shader的性能? 4. 如何实现动态改变边缘光颜色和宽度的效果? 希望以上教程对你有所帮助。如果有任何问题,请随时提问。</think>### Unity URP 边缘光实现教程 在 Unity URP 中实现边缘光效果主要有三种方法,下面我将详细说明每种方法的实现步骤: --- #### **方法 1:使用菲涅尔公式实现(Shader 方案)** **原理**:利用视线方向与表面法线的夹角计算边缘强度,夹角越大(边缘处)发光越强 **优点**:效果自然,性能较好 **实现步骤**: 1. **创建 Shader**: ```glsl Shader "Custom/RimLightURP" { Properties { _MainTex ("Texture", 2D) = "white" {} _RimColor ("边缘光颜色", Color) = (1,1,1,1) _RimPower ("边缘强度", Range(0.1, 8)) = 3 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 viewDirWS : TEXCOORD2; }; sampler2D _MainTex; float4 _RimColor; float _RimPower; Varyings vert(Attributes IN) { Varyings OUT; // 顶点变换 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); // 法线转换到世界空间 OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); // 计算视线方向(从顶点指向相机) float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.viewDirWS = GetWorldSpaceNormalizeViewDir(positionWS); OUT.uv = IN.uv; return OUT; } half4 frag(Varyings IN) : SV_Target { // 采样纹理 half4 texColor = tex2D(_MainTex, IN.uv); // 归一化向量 float3 normalWS = normalize(IN.normalWS); float3 viewDirWS = normalize(IN.viewDirWS); // 核心计算:菲涅尔效果 float rim = 1.0 - saturate(dot(normalWS, viewDirWS)); rim = pow(rim, _RimPower); // 混合边缘光 half3 emission = _RimColor.rgb * rim; return half4(texColor.rgb + emission, texColor.a); } ENDHLSL } } ``` 2. **创建材质**: - 在 Project 窗口右键创建 Material - Shader 选择 Custom/RimLightURP - 调整参数: - `_RimColor`:边缘光颜色(如蓝色或青色) - `_RimPower`:值越小边缘光范围越大(推荐 2-5) 3. **应用到模型**: - 将材质拖拽到场景中的模型上 - 旋转模型观察边缘发光效果 > **技术要点**:在 URP 中获取法线和视线向量需使用 URP Shader Library 提供的函数 `TransformObjectToWorldNormal()` 和 `GetWorldSpaceNormalizeViewDir()`[^1] --- #### **方法 2:使用插件实现(快速方案)** **推荐插件**:[Highlight Plus](https://assetstore.unity.com/packages/tools/particles-effects/highlight-plus-68520) 或 [Outline Effect](https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/outline-effect-165968) **实现步骤**: 1. 从 Asset Store 下载 URP 兼容的边缘光插件 2. 导入后按文档配置: ```csharp // 代码添加边缘光(以Outline插件为例) var outline = gameObject.AddComponent<Outline>(); outline.OutlineMode = Outline.Mode.OutlineAll; outline.OutlineColor = Color.cyan; outline.OutlineWidth = 3.0f; // 控制边缘宽度 ``` 3. 通过材质参数动态控制: ```csharp // 运行时修改参数 GetComponent<Renderer>().material.SetColor("_RimColor", newColor); ``` > **优势**:无需编写 Shader,支持动态开关效果[^3][^4] --- #### **方法 3:后处理方案(全屏边缘检测)** **适用场景**:需要整个场景的统一边缘效果 **实现原理**:通过法线/深度图检测边缘 **步骤**: 1. 创建 Renderer Feature: - 在 URP Asset > Renderer Features 添加 "Render Objects" 2. 配置 Overlay 层: - 为需要边缘光的对象分配专用 Layer(如 "Outline") 3. 编写后处理 Shader: ```glsl float _EdgeWidth; float4 _EdgeColor; float4 Frag(Varyings input) : SV_Target { float3 normal = SampleSceneNormals(input.uv); float depth = SampleSceneDepth(input.uv); // Sobel算子边缘检测 float edge = EdgeDetection(normal, depth, _EdgeWidth); return edge * _EdgeColor; } ``` 4. 添加到 URP 渲染流程: - 通过 Blit 或 Fullscreen Pass 注入效果 --- ### **效果优化技巧** 1. **深度控制**:在 Shader 中添加 `_DepthThreshold` 避免内部发光 ```glsl float depth = LinearEyeDepth(SHADERGRAPH_SAMPLE_SCENE_DEPTH(uv), _ZBufferParams); if(depth > _DepthThreshold) rim = 0; ``` 2. **混合模式**:透明物体需修改 Blend 模式 ```glsl Blend SrcAlpha OneMinusSrcAlpha ``` 3. **性能优化**:对移动端使用 `half` 精度变量 --- ### **常见问题解决** 1. **边缘光不显示**: - 检查 URP 设置:Edit > Project Settings > Graphics > Scriptable Render Pipeline Settings - 确认材质是否使用 URP 兼容的 Shader 2. **效果穿透问题**: - 在 Shader 中添加深度测试: ```glsl ZWrite On ZTest LEqual ``` 3. **性能问题**: - 避免在移动端使用后处理方案 - 复杂模型使用 LOD 控制边缘光计算 > 完整示例工程:[URP RimLight GitHub 仓库](https://github.com/UnityCommunity/URPRimLight) --- ### 相关问题 1. 如何在 URP 中实现透明物体的边缘光效果? 2. 边缘光效果在移动设备上如何优化性能? 3. 如何通过脚本动态控制边缘光的颜色和强度? 4. URP 后处理方案中如何实现物体描边效果? 5. 菲涅尔边缘光与深度边缘光有何性能差异? [^1]: Unity URP 中的法线和视线向量获取方法 [^2]: 利用 Emission 属性实现边缘发光 [^3]: 通过代码动态添加边缘光组件 [^4]: URP 渲染管线边缘高亮插件使用方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值