【unity】网格描边方法

【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
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Javaweb开发 利用servlet+jsp+jdbc+tomcat数据库实现登录功能

前言&#xff1a;很久没更新了&#xff0c;今天给大家分享一个Java web的小案例&#xff0c;是一个登录页面&#xff0c;利用Login控制类和JDBC连接数据库&#xff0c;并判断用户名密码是否正确&#xff0c;项目最终部署在Tomcat上。 先看效果 正文 一、前期工作 1.首先我们…

云课五分钟-02第一个代码复现-终端甜甜圈C++

前篇 云课五分钟-01课程在哪里-无需安装网页直达- 代码复现通过云课&#xff0c;会非常快捷。 视频 云课五分钟-02第一个代码复现-终端甜甜圈C 文本 如何使用g 使用g编译和链接C程序的基本步骤如下&#xff1a; 编写源代码&#xff1a;首先&#xff0c;你需要编写C源代码&…

Docker学习——⑧

文章目录 1、什么是 Docker Compose(容器编排)2、为什么要 Docker Compose&#xff1f;3、Docker Compose 的安装4、Docker Compose 的功能和使用场景5、Docker Compose 文件&#xff08;docker-compose.yml&#xff09;5.1 文件语法版本5.2 文件基本结构及常见指令 6、Docker …

【C#学习】串口编程

文章目录 第一步&#xff1a;加入串口控件第二步&#xff1a;加入模块第三步&#xff1a;编写相关函数功能获取所有串口资源设置和打开关闭串口发送字符串&#xff08;string&#xff09;发送byte检查串口状态接受byte查询所有可用串口 第一步&#xff1a;加入串口控件 第二步&…

Elasticsearch7 入门 进阶

1、全文检索 1.1、数据分类 按数据分类的话&#xff0c;主要可以分为以下三类&#xff1a; 结构化数据&#xff1a;固定格式、有限长度&#xff0c;比如mysql存的数据非结构化数据&#xff1a;不定长、无固定格式&#xff0c;比如邮件、Word文档、日志等半结构化数据&#xf…

Git 工作流程、工作区、暂存区和版本库

目录 Git 工作流程 Git 工作区、暂存区和版本库 基本概念 Git 工作流程 本章节我们将为大家介绍 Git 的工作流程。 一般工作流程如下&#xff1a; 克隆 Git 资源作为工作目录。在克隆的资源上添加或修改文件。 如果其他人修改了&#xff0c;你可以更新资源。在提交前查看…

抖音小程序开发全攻略:如何规划项目和选择合适的开发团队

在数字化时代&#xff0c;抖音小程序成为企业推广和服务的重要渠道。本文将为您提供抖音小程序开发的全面攻略&#xff0c;重点介绍如何规划项目和选择合适的开发团队&#xff0c;并附有一些关键的技术代码示例。 1. 项目规划 在开始抖音小程序开发之前&#xff0c;详细的项…

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

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

读写分离(基于mycat)和全同步复制

一、mycat实现读写分离&#xff08;VIP机制&#xff09; &#xff08;一&#xff09;配置主从复制 &#xff08;二&#xff09;部署mycat 1、安装Java 2、下载mycat安装包 3、解压mycat包 4、设置变量环境 5、启动mycat &#xff08;三&#xff09;客户端连接数据库 1、安装…

(论文阅读32/100)Flowing convnets for human pose estimation in videos

32.文献阅读笔记 简介 题目 Flowing convnets for human pose estimation in videos 作者 Tomas Pfister, James Charles, and Andrew Zisserman, ICCV, 2015. 原文链接 https://arxiv.org/pdf/1506.02897.pdf 关键词 Human Pose Estimation in Videos 研究问题 视频…

火车头采集器如何设置代理IP

火车头采集器作为一种强大的数据抓取工具&#xff0c;已经被很多人熟知&#xff0c;它最大的优势就是设置代理IP确保采集过程的顺利进行。 今天我们就来说说&#xff0c;火车头采集器是怎么设置代理IP的。 1.打开火车头采集器软件&#xff0c;在打开的界面中点击http二级代理…

使用select实现定时任务

selectOutOfTime.c里边的代码如下&#xff1a; #include<stdio.h> #include<sys/time.h> #include<sys/types.h> #include<unistd.h> #include <string.h>#define BUF_SIZE 100int main(void){fd_set reads;struct timeval tv;int errorNum;cha…

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

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

AI生成图片教程(基于DALL-E3)

目录 前言new bingImage Creator 前言 今天登录GPT时发现openai的官网显示其有生成图片的模型DALL-E3&#xff0c;于是想试一试其效果如何。 奈何ChatGPT只能在付费版上使用&#xff0c;但是这个DALL-E3其实免费使用。 使用途径有两个&#xff1a; new bing 进入new bing 很…

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

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

sinc 函数

See https://wuli.wiki/online/sinc.html 公式(3)的证明见 https://wuli.wiki/online/JdLem.html#ex_JdLem_1 百度百科

5G车载网关让医院无人配送车“灵活“起来

​ 5G车载网关应用于无人医院配送车 随着社会老龄化加剧和医疗需求增长,为患者提供及时、便捷的药品配送服务成为医院的一项重要任务。传统的人工配送方式效率低下,无法满足患者的实时配送需求。针对这一痛点,5G车载网关为无人医院配送车提供了有力的技术支撑。 5G车载网关集成…

ubuntu小技巧30--23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer

ubuntu小技巧30-- 23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer 介绍解決方法说明 介绍 近期在电脑上安装了 ubuntu 23.10桌面版本, 安装最新版钉钉后无法正常打开软件&#xff0c;报错 undefined symbol: FT_Get_Color_Glyph_Layer &#xff0c;具…

安装部署PowerDNS--实现内网DNS解析(use)

使用PowerDNS实现内网DNS解析_powerdns-admin-CSDN博客 https://www.cnblogs.com/guangdelw/p/17348982.html 一、概念介绍 PowerDNS是一个域名解析服务&#xff0c;官网提供了三个组件&#xff1a;Authoritative、Recursor、dnsdist&#xff0c;分别用来作为权威服务器、域名递…

C++ VS2015安装教程,下载和安装(下载地址+图解+详细步骤)

说明&#xff1a;VS2015的三个版本分别为&#xff1a; Visual Studio Community(社区版)&#xff1a;满足大部分程序员的需求&#xff08;推荐&#xff09; Visual Studio Professional(专业版) Visual Studio Enterprise(企业版) 1、下载地址(这里只提供Community版) htt…