You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

线框着色器:如何仅显示四边形而非三角形?

如何把三角形线框着色器改成只显示四边形线框?

我来帮你搞定这个需求!你的现有着色器是基于三角形拓扑渲染线框的,所以会把每个四边形拆成两个三角形的线框显示(也就是你说的左侧立方体效果)。要改成只显示四边形的外围边,我们需要利用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
        }
    }
}

关键改动说明

  1. 邻接三角形输入:把几何着色器的输入从triangle改成triangleadj,这样能拿到每个三角形每条边对应的邻接顶点,帮我们识别内部共享边。
  2. 边过滤逻辑:通过对比邻接顶点和当前三角形顶点的位置,判断一条边是否是四边形的内部边——如果是就跳过,只画外围的边。
  3. 输出类型切换:从TriangleStream改成LineStream,因为现在我们直接绘制线条来构建线框,不再依赖三角形的距离场。
  4. 新增线框粗细控制:加了_WireThickness参数,而且在片元着色器里结合屏幕高度计算,保证不同分辨率下线框粗细一致。

使用注意

  • 你的模型得是四边形拓扑的网格(不能是纯三角化的),这样邻接信息才能正确识别内部边。如果模型有三角化的部分,那些区域还是会显示三角形线框,这是正常的,因为它们本身不是四边形结构。

内容的提问来源于stack exchange,提问作者user1546493

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:40:46