【unity】网格描边方法

news2024/11/27 7:34:40

【unity】网格描边方法

介绍对模型四边网格的三种描边方法:包括纯Shader方法、创建网格方法和后处理方法。于增强场景中3D模型的轮廓,使其在视觉上更加突出和清晰。这种效果可以用于增强三维场景中的物体、角色或环境,使其在视觉上更加吸引人。

网格描边方法资源

Shader方法

使用GeometryShader方法对三角网进行计算,目的是保留距离最短的两条边。在进行计算时,首先需要建立一个float2 dist来储存点的信息。在进行插值后,需要保留边的dist,其中一个数值为0,以此为依据来绘制边。下图展示了顶点dist的赋值情况。

这种方法可以让我们在渲染三角网时,根据点之间的距离信息来动态地调整边的绘制,从而实现更加真实和精细的渲染效果。

实现效果

实现shader

Shader "Unlit/WireframeMesh"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" { }
        _WireColor("WireColor", Color) = (1, 0, 0, 1)
        _FillColor("FillColor", Color) = (1, 1, 1, 1)
        _WireWidth("WireWidth", Range(0, 1)) = 1

    }
        SubShader
        {
            Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
            LOD 100
            AlphaToMask On // 为此通道启用减法混合
            Pass
            {
                Blend SrcAlpha OneMinusSrcAlpha
                Cull Off
                CGPROGRAM

                #pragma vertex vert
                #pragma geometry geom //添加几何阶段
                #pragma fragment frag

                #include "UnityCG.cginc"

                struct appdata
                {
                    float4 vertex: POSITION;
                    float2 uv: TEXCOORD0;
                };

                struct v2g 
                {
                    float2 uv: TEXCOORD0;
                    float4 vertex: SV_POSITION;
                };

                struct g2f
                {
                    float2 uv: TEXCOORD0;
                    float4 vertex: SV_POSITION;
                    float2 dist: TEXCOORD1;
                    float maxlenght : TEXCOORD2;
                };

                sampler2D _MainTex;
                float4 _MainTex_ST;

                float4 _FillColor, _WireColor;
                float _WireWidth, _Clip, _Lerp, _WireLerpWidth;
				//视口到几何
                v2g vert(appdata v) 
                {
                    v2g o;
                    o.vertex = v.vertex;
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
				//几何到片元
                [maxvertexcount(3)]
                void geom(triangle v2g IN[3], inout TriangleStream < g2f > triStream)
                {
                    //读取三角网各个顶点
                    float3 p0 = IN[0].vertex;
                    float3 p1 = IN[1].vertex;
                    float3 p2 = IN[2].vertex;
					//计算三角网每一边的长度
                    float v0 = length(p1 - p2);
                    float v1 = length( p2 - p0);
                    float v2 = length( p0 - p1);
					//求出最长边
                    float v_max = max(v2,max(v0, v1));
					//每一边减最长边,小于0时为0,等于0时为1
                    float f0 = step(0, v0 - v_max);
                    float f1 = step(0, v1 - v_max);
                    float f2 = step(0, v2 - v_max);

                    //赋值传到片元操作
                    g2f OUT;
                    OUT.vertex = UnityObjectToClipPos(IN[0].vertex);
                    OUT.uv = IN[0].uv;
                    OUT.maxlenght = v_max;
                
                    OUT.dist = float2(f1, f2);
                    triStream.Append(OUT);

                    OUT.vertex = UnityObjectToClipPos( IN[1].vertex);
                    OUT.uv = IN[1].uv;
                    OUT.maxlenght = v_max;
                    OUT.dist = float2(f2, f0);
                    triStream.Append(OUT);

                    OUT.vertex = UnityObjectToClipPos( IN[2].vertex);
                    OUT.maxlenght = v_max;
                    OUT.uv = IN[2].uv;
                    OUT.dist = float2(f0, f1);
                    triStream.Append(OUT);
                }
				//片元阶段
                fixed4 frag(g2f i) : SV_Target
                {
                    fixed4 col = tex2D(_MainTex, i.uv );
                    fixed4 col_Wire= col* _FillColor;
                    //取dist最小值
                    float d =  min(i.dist.x, i.dist.y);
                    //d小于线宽是赋值线颜色,否则赋值背景颜色
                    col_Wire = d < _WireWidth ? _WireColor : col_Wire;
                    fixed4 col_Tex = tex2D(_MainTex, i.uv);
                    return col_Wire;
                }
                ENDCG

            }
        }
}

该方法不支持webGL,原因webGL不支持GeometryShader。

介绍一个根据uv创建网格的方法,虽然支持webGL,但是局限性太大,不做详细介绍,附上shader

Shader "Unlit/WireframeUV"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _FillColor("FillColor", Color) = (1, 1, 1, 1)
       [HDR] _WireColor("WireColor", Color) = (1, 0, 0, 1)
        _WireWidth("WireWidth", Range(0, 1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        AlphaToMask On
        Pass
        {
             Tags { "RenderType" = "TransparentCutout" }
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off
            CGPROGRAM
       
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _FillColor;
            fixed4 _WireColor;
            float _WireWidth;


            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed2 uv2 = abs(i.uv - fixed2(0.5f, 0.5f));
                float minUV = max(uv2.x, uv2.y);
                col = minUV < 0.5- _WireWidth ? col* _FillColor : _WireColor;
                return col;
            }
            ENDCG
        }
    }
}

创建网格方法

这个方法支持在内置built-in管线中使用,实现原理和shader方法类似,不同的是需要构建线网格。根据原有三角网格抽取其中最短两条重新绘制。

实现效果

因为CommandBuffer方法暂时无法设置线宽,用了一些后处理方法

实现的方法


using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class CamerDrawMeshDemo : MonoBehaviour
{
    [SerializeField]
    MeshFilter meshFilter;
    CommandBuffer cmdBuffer;
    [SerializeField]
    Material cmdMat1;

    // Start is called before the first frame update
    void Start()
    {
        //创建一个CommandBuffer
        cmdBuffer = new CommandBuffer() { name = "CameraCmdBuffer" };
        Camera.main.AddCommandBuffer(CameraEvent.AfterForwardOpaque, cmdBuffer);
        DarwMesh();
    }
    //绘制网格
    void DarwMesh()
    {
        cmdBuffer.Clear();
        Mesh  m_grid0Mesh = meshFilter.mesh;//读取原有网格,这里需要开启网格可读写
        cmdBuffer.DrawMesh(CreateGridMesh(m_grid0Mesh), Matrix4x4.identity, cmdMat1);
    }
    //创建网格
    Mesh CreateGridMesh(Mesh TargetMesh)
    {
     
        Vector3[] vectors= getNewVec(TargetMesh.vertices);
        //模型坐标转换到世界坐标
        Vector3[] getNewVec(Vector3[] curVec)
        {
            int count = curVec.Length;
            Vector3[] vec = new Vector3[count];
            for (int i = 0; i < count; i++)
            {
                //坐标转型,乘上变化矩阵
                vec[i] =(Vector3)(transform.localToWorldMatrix* curVec[i])+transform.position;
            }
            return vec;
        }
        int[] triangles = TargetMesh.triangles;
        List<int> indicesList = new List<int>(2);
        //筛选绘制边
        for (int i = 0; i < triangles.Length; i+=3)
        {
            Vector3 vec;
            int a = triangles[i];
            int b = triangles[i+1];
            int c = triangles[i+2];
            vec.x = Vector3.Distance(vectors[a], vectors[b]);
            vec.y = Vector3.Distance(vectors[b], vectors[c]);
            vec.z = Vector3.Distance(vectors[c], vectors[a]);
            addList(vec, a,b,c);
        }
        void addList(Vector3 vec,int a,int b,int c)
        {
            if (vec.x< vec.y|| vec.x <vec.z)
            {
                indicesList.Add(a);
                indicesList.Add(b);
            }
            if (vec.y < vec.x || vec.y < vec.z)
            {
                indicesList.Add(b);
                indicesList.Add(c);
            }
            if (vec.z < vec.x || vec.z < vec.y)
            {
                indicesList.Add(c);
                indicesList.Add(a);
            }
        }
        int[] indices = indicesList.ToArray();
        //创建网格
        Mesh mesh = new Mesh();
        mesh.name = "Grid ";
        mesh.vertices = vectors;
        mesh.SetIndices(indices, MeshTopology.Lines, 0);
        return mesh;
    }
}

后处理方法

利用深度纹理和法线纹理来比较相邻像素之间的相似性,以判断它们是否位于物体的边缘,并进而实现描边效果。具体而言,该算法会对相邻像素的深度值和法线值进行比较,若它们之间的差异超过一定阈值,则认为这两个像素位于物体的边缘上。通过这一方法,我们可以在渲染时对边缘进行特殊处理,以实现描边效果。

实现效果

实现方法

建立后渲染脚本挂载在主相机上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneOnlineDemo : MonoBehaviour
{
   public  Shader OnlineShader;
    Material material;
    [ColorUsage(true, true)]
    public Color ColorLine;
    public Vector2 vector;
    public float LineWide;
    // Start is called before the first frame update
    void Start()
    {
        material = new Material(OnlineShader);

        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
    }

    void Update()
    {
        
    }
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            material.SetVector("_ColorLine", ColorLine);
            material.SetVector("_Sensitivity", vector);
            material.SetFloat("_SampleDistance", LineWide);
            Graphics.Blit(src, dest, material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

后处理shader挂载在SceneOnlineDemo 脚本上

Shader "Unlit/SceneOnlineShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
      [HDR] _ColorLine("ColorLine", Color) = (1,1,1,1)   //颜色,一般用fixed4
        _Sensitivity("Sensitivity", Vector) = (1, 1, 1, 1)    //xy分量分别对应法线和深度的检测灵敏度,zw分量没有实际用途
        _SampleDistance("Sample Distance", Float) = 1.0
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            ZTest Always Cull Off ZWrite Off
      
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            sampler2D _MainTex;
            half4 _MainTex_TexelSize;
            sampler2D _CameraDepthNormalsTexture;    //深度+法线纹理
            sampler2D _CameraDepthTexture;
            fixed4 _ColorLine;
            float _SampleDistance;
            half4 _Sensitivity;

            struct v2f
            {
                half2 uv[5]: TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_img v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                half2 uv = v.texcoord;
                o.uv[0] = uv;
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    uv.y = 1 - uv.y;
                #endif
                //建立相邻向量数组
                o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1, 1) * _SampleDistance;
                o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1, -1) * _SampleDistance;
                o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 1) * _SampleDistance;
                o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1, -1) * _SampleDistance;
                return o;
            }
            //检查是否相似
            half CheckSame(half4 center, half4 sample) {
                half2 centerNormal = center.xy;
                float centerDepth = DecodeFloatRG(center.zw);
                half2 sampleNormal = sample.xy;
                float sampleDepth = DecodeFloatRG(sample.zw);

                // 法线相差
                half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
                int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
                // 深度相差
                float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
                // 按距离缩放所需的阈值
                int isSameDepth = diffDepth < 0.1 * centerDepth;

                // return:
                // 1 - 如果法线和深度足够相似
                // 0 - 相反
                return isSameNormal * isSameDepth ? 1.0 : 0.0;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                    fixed4 col = tex2D(_MainTex,  i.uv[0]);
                    half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
                    half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
                    half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
                    half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);
                    half edge = 1.0;
                    edge *= CheckSame(sample1, sample2);
                    edge *= CheckSame(sample3, sample4);
                    fixed4 withEdgeColor = lerp(_ColorLine, col, edge);
               

                 return withEdgeColor;
            }
            ENDCG
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1207774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wordcloud出现ValueError: Only supported for TrueType fonts

方法1&#xff1a;换一个版本的wordcloud即可&#xff0c;实测windows在1.8.2.2版本上不会报错。 方法2&#xff1a;还可以尝试其他博客中的解决方案&#xff0c;自己下个字体的TTF文件&#xff0c;让后font_path直接写字体文件路径。&#xff08;此方法未见效&#xff09;

11月14日星期二今日早报简报微语报早读

11月14日星期二&#xff0c;农历十月初二&#xff0c;早报微语早读。 1、江西南城县&#xff1a;限时发放购房补贴政策&#xff0c;三孩家庭每平方米最高补贴500元&#xff1b; 2、2023年中国内地电影市场累计票房突破500亿元&#xff1b; 3、市场监管总局&#xff1a;在全国…

21.计算老师的工资

&#xff08;定义结构体数组存放教师的财务信息&#xff08;教工卡号&#xff0c;应发工资&#xff0c;个人所得税&#xff0c;实发工资&#xff09;具体要求如下从键盘输入5个教师的教工卡号&#xff0c;应发工资 计算每人的个人所得税&#xff08;应发工资10%&#xff09;&am…

好心提醒下,幼师姐妹们要知道啊

幼师家人们在不在&#xff1f;在不在&#xff1f; 不会还有姐妹在自己写教案&#xff0c;写总结&#xff0c;写评语啥的吧&#xff0c;这个好东西真的要知道啊&#xff01;&#xff01; 只要输入关键词&#xff0c;马上就能得到你想要的内容&#xff0c;真的很强啊&#xff0…

设计模式系列之最终篇:大盘点

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概4000多字&#xff0c;预计阅读时间长需要3分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&#x…

NVMe 2.0 简介

随着全新一代PCIe 5.0 SSD的发布&#xff0c;新的NVMe 2.0规范协议也是如期而至。作为PCIe SSD最为重要的底层协议&#xff0c;NVMe 2.0都有哪些重要调整&#xff1f;又有哪些值得我们关注的新功能&#xff1f;本文简单解答。 NVMe 演化史 NVMe自诞生以来&#xff0c;其关注的…

Blazor 附件上传和下载功能

效果图 page "/uploadFile" inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment WebHostEnvironment inject ToastService ToastService inject DownloadService DownloadService<h3>UploadFile</h3><Button OnClick"ButtonClick" C…

cmd窗口中文乱码

问题&#xff1a;今天电脑重装了&#xff0c;跑程序的时候&#xff0c;cmd窗口汉字出现乱码&#xff0c;看起来很不舒服 解决方案&#xff1a;在注册表中修改cmd的属性设置&#xff0c;实现一次修改永远有效 1. 打开 winR 输入 regedit 进入注册表 2. 找到 HKEY_CURRENT_USER…

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(六):FREERTOS移植

前言: 一般的GUI工程都需要一个操作系统,可能是linux,重量级的,也可能是FreeRTOS,轻量级的。 一句话理解那就是工程就是FreeRTOS task任务的集合。 一个main函数可以看到大框架: 很显然,除了第一个是硬件配置的初始化,中间最重要的部分就是要创建任务。而一个任务主…

STM32H750之FreeRTOS学习--------(六)FreeRTOS的列表和列表项

六、FreeRTOS的列表和列表项 文章目录 六、FreeRTOS的列表和列表项列表相关结构体列表项相关结构体迷你列表项列表相关API函数介绍初始化列表vListInitialise()函数vListInitialiseItem()函数vListInsert()函数 vListInsertEnd()函数 uxListRemove() 列表就是一个双向链表&…

RT-DETR算法优化改进:Backbone改进 | LSKNet:遥感旋转目标检测新SOTA | ICCV 2023

💡💡💡本文独家改进:LSKNet 助力RT-DETR ,替换backbone,Large Selective Kernel Network (LSKNet),可以动态地调整其大空间感受野,以更好地建模遥感场景中各种物体的测距的场景。 推荐指数:五星 RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/cat…

Windows装机必装软件|每款都好用到起飞!

1.GeekUninstaller&#xff1a;这款软件是一款功能强大的卸载工具&#xff0c;可以完全删除无用的程序和插件&#xff0c;并清理与之相关的文件夹。它没有广告或捆绑软件&#xff0c;非常干净。 2.PotPlayer&#xff1a;作为一款纯粹的播放器&#xff0c;PotPlayer可以流畅播放…

【数据结构】面试OJ题——带环链表(数学推论)

目录 1.环形链表Ⅰ ​编辑 思路 &#xff1a; 思路拓展 问题一&#xff1a; 问题二&#xff1a; 总结&#xff1a; 问题三&#xff1a; 证明总结第三点 总结&#xff1a; 2. 环形链表Ⅱ 思路一 思路二 3.相交链表 思路&#xff1a; 1.环形链表Ⅰ 141. 环形链…

ebSocket connection to ‘wss://xxx.xxxxxxx.xxx/‘ failed:

目录 1&#xff1a;网络连接问题&#xff1a;检查您是否已连接到互联网&#xff0c;您的网络是否稳定。您还可以尝试重置您的Internet连接或切换到另一个网络。 排除方法&#xff1a;直接打开个网址就知道了&#xff0c;这应该不用教了吧 2&#xff1a;防火墙或代理设置&…

桌面便签软件用哪个?10款全球好用的便签软件推荐,告别杂论无章!

在如今的快节奏社会中&#xff0c;我们的生活和工作节奏越来越快&#xff0c;每天面对的信息成倍地增长。有时候&#xff0c;我们需要随手记下一些重要的事情&#xff0c;或者是一些突然的灵感&#xff0c;这时候就需要一款好用的桌面便签软件。 桌面便签软件可以帮助我们更好…

【机器学习5】无监督学习聚类

相比于监督学习&#xff0c; 非监督学习的输入数据没有标签信息&#xff0c; 需要通过算法模型来挖掘数据内在的结构和模式。 非监督学习主要包含两大类学习方法&#xff1a; 数据聚类和特征变量关联。 1 K均值聚类及优化及改进模型 1.1 K-means 聚类是在事先并不知道任何样…

为什么说美味、珍贵的高贵甜酒是“来自大自然的礼物”?

当德国葡萄酒产区的主要葡萄收获季节成功结束时&#xff0c;人们可以观察到在某些地区个别葡萄园还没有收获。它们没有被酿酒师遗忘&#xff0c;而是在等待成为高贵甜美的佳肴。在秋天的好天气里&#xff0c;葡萄继续生长&#xff0c;每天自然都会增加它们的甜度。来自云仓酒庄…

负公差轧钢测径仪 多规格可定制 普通智能随意选择

负公差轧制的意义&#xff1a; 轧钢厂生产的螺纹钢是按理论重量销&#xff0c;因此稳定的高负差产品极具市场竞争力。负差率即实际重量与理论重量的差值&#xff0c;除以理论重量&#xff0c;乘100%。以螺纹12为例&#xff0c;不按负差生产&#xff0c;在坯重2450kg的情况下&am…

React Hooks实战:Web开发与设计

目录 前言 一、React Hooks 简介 二、React Hooks 的基本用法 1. 使用 useState 创建状态 2. 使用 useEffect 添加副作用 3. 使用 useContext 获取上下文 三、React Hooks 的常见问题 1. 循环引用问题 2. 副作用问题 四、React Hooks 实战案例 1. 使用 useState钩子…

竞赛选题 深度学习的智能中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…