线框着色器:如何仅显示四边形而非三角形?
如何把三角形线框着色器改成只显示四边形线框?
我来帮你搞定这个需求!你的现有着色器是基于三角形拓扑渲染线框的,所以会把每个四边形拆成两个三角形的线框显示(也就是你说的左侧立方体效果)。要改成只显示四边形的外围边,我们需要利用Unity的邻接三角形信息来过滤掉内部的共享边,只保留四边形的四条边。
先把你的原始代码补全方便参考:
Shader "Custom/Wireframe" { Properties { _WireColor("WireColor", Color) = (1,0,0,1) _Color("Color", Color) = (1,1,1,1) } SubShader { Pass { CGPROGRAM #include "UnityCG.cginc" #pragma target 5.0 #pragma vertex vert #pragma geometry geom #pragma fragment frag half4 _WireColor, _Color; struct v2g { float4 pos : SV_POSITION; }; struct g2f { float4 pos : SV_POSITION; float3 dist : TEXCOORD0; }; v2g vert(appdata_base v) { v2g o; o.pos = UnityObjectToClipPos(v.vertex); return o; } [maxvertexcount(3)] void geom(triangle v2g input[3], inout TriangleStream<g2f> triStream) { float2 p0 = input[0].pos.xy / input[0].pos.w; float2 p1 = input[1].pos.xy / input[1].pos.w; float2 p2 = input[2].pos.xy / input[2].pos.w; float2 v0 = p2 - p1; float2 v1 = p2 - p0; float2 v2 = p1 - p0; float area = abs(v1.x * v2.y - v1.y * v2.x); g2f o; o.pos = input[0].pos; o.dist = float3(area / length(v0), 0, 0); triStream.Append(o); o.pos = input[1].pos; o.dist = float3(0, area / length(v1), 0); triStream.Append(o); o.pos = input[2].pos; o.dist = float3(0, 0, area / length(v2)); triStream.Append(o); } half4 frag(g2f i) : SV_Target { float minDist = min(i.dist.x, min(i.dist.y, i.dist.z)); float edgeFactor = smoothstep(0, 1, minDist); return lerp(_WireColor, _Color, edgeFactor); } ENDCG } } }
修改后的完整着色器代码
下面是调整后的版本,专门针对四边形线框优化:
Shader "Custom/QuadWireframe" { Properties { _WireColor("WireColor", Color) = (1,0,0,1) _Color("Color", Color) = (1,1,1,1) _WireThickness("Wire Thickness", Range(0.0, 2.0)) = 1.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #include "UnityCG.cginc" #pragma target 5.0 #pragma vertex vert #pragma geometry geom #pragma fragment frag half4 _WireColor, _Color; float _WireThickness; struct v2g { float4 pos : SV_POSITION; float3 worldPos : TEXCOORD0; }; struct g2f { float4 pos : SV_POSITION; float dist : TEXCOORD0; }; v2g vert(appdata_base v) { v2g o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } // 用triangleadj获取邻接三角形信息,每个三角形会传入6个顶点:3个自身顶点+3个邻接顶点 [maxvertexcount(12)] void geom(triangleadj v2g input[6], inout LineStream<g2f> lineStream) { // 当前三角形的三个顶点 float3 p0 = input[0].worldPos; float3 p1 = input[1].worldPos; float3 p2 = input[2].worldPos; // 对应每条边的邻接顶点 float3 p0adj = input[3].worldPos; float3 p1adj = input[4].worldPos; float3 p2adj = input[5].worldPos; // 辅助函数:判断两条边是否为同一条(用来检测内部共享边) bool IsSameEdge(float3 a, float3 b, float3 c, float3 d) { // 检查两种顶点配对的距离总和,只要其中一种足够小就是同一条边 float dist1 = distance(a, c) + distance(b, d); float dist2 = distance(a, d) + distance(b, c); return min(dist1, dist2) < 0.001; // 微小阈值避免浮点精度问题 } // 判断每条边是否是四边形的外围边(不是内部共享边) bool isEdge01 = !IsSameEdge(p0, p1, p2adj, p2); bool isEdge12 = !IsSameEdge(p1, p2, p0adj, p0); bool isEdge20 = !IsSameEdge(p2, p0, p1adj, p1); // 辅助函数:绘制单条线框边 void DrawLine(float4 pos0, float4 pos1) { g2f o; o.pos = pos0; o.dist = 0; lineStream.Append(o); o.pos = pos1; o.dist = 1; lineStream.Append(o); lineStream.RestartStrip(); } // 只绘制外围边 if (isEdge01) DrawLine(input[0].pos, input[1].pos); if (isEdge12) DrawLine(input[1].pos, input[2].pos); if (isEdge20) DrawLine(input[2].pos, input[0].pos); } half4 frag(g2f i) : SV_Target { // 基于屏幕高度适配线框粗细,保证不同分辨率下显示一致 float edgeFactor = smoothstep(_WireThickness - 0.5, _WireThickness + 0.5, abs(i.dist - 0.5) * _ScreenParams.y); return lerp(_WireColor, _Color, edgeFactor); } ENDCG } } }
关键改动说明
- 邻接三角形输入:把几何着色器的输入从
triangle改成triangleadj,这样能拿到每个三角形每条边对应的邻接顶点,帮我们识别内部共享边。 - 边过滤逻辑:通过对比邻接顶点和当前三角形顶点的位置,判断一条边是否是四边形的内部边——如果是就跳过,只画外围的边。
- 输出类型切换:从
TriangleStream改成LineStream,因为现在我们直接绘制线条来构建线框,不再依赖三角形的距离场。 - 新增线框粗细控制:加了
_WireThickness参数,而且在片元着色器里结合屏幕高度计算,保证不同分辨率下线框粗细一致。
使用注意
- 你的模型得是四边形拓扑的网格(不能是纯三角化的),这样邻接信息才能正确识别内部边。如果模型有三角化的部分,那些区域还是会显示三角形线框,这是正常的,因为它们本身不是四边形结构。
内容的提问来源于stack exchange,提问作者user1546493
相关产品推荐
相关产品推荐

