Unity 模型描边的几种方法. (Shader、GL、代码生成描绘边)

2024-04-19 23:38

本文主要是介绍Unity 模型描边的几种方法. (Shader、GL、代码生成描绘边),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

1、前段时间工作,需要给模型描边,由于对Shader不熟悉,就直接网上找了描边Shader文件,无奈项目发布环境是WebGL,WebGL对Shader的需求比较特殊,故无法使用。

2、因为项目需要描边的物体并不多,所以萌生出,动态生成整个模型所有的边(线条),给各个边附上需要的材质球即可。(当然,也可以直接请美术在模型上描边,但这样不能实现泛光之类的效果)

3、现写下三种实现模型描边的方法,方便日后查看学习与使用。


实现

1、GL描边

在这里插入图片描述


原理比较简单,就是获取到模型的所有顶点,然后使用GL连线.

以下是GL描边代码,在项目的GLSingleWireFrameScene场景中有演示.

/// <summary>
/// 描绘单个模型线框.
/// </summary>
public class GLSingleWireFrame : MonoBehaviour
{public Material lineMaterial;private Mesh m_mesh;private Vector3[] m_vertices;private int[] m_triangles;private Transform m_transform;void Awake(){m_mesh = gameObject.GetComponent<MeshFilter>().mesh;m_vertices = m_mesh.vertices;m_triangles = m_mesh.triangles;m_transform = transform;}public void OnRenderObject(){lineMaterial.SetPass(0);    //GLSingleWireFrame材质球Shader是 Unlit/Color.GL.PushMatrix();GL.MultMatrix(m_transform.localToWorldMatrix);GL.Begin(GL.LINES);for (int cnt = 0; cnt < m_triangles.Length; cnt += 3){GL.Vertex(m_vertices[m_triangles[cnt]]);GL.Vertex(m_vertices[m_triangles[cnt + 1]]);GL.Vertex(m_vertices[m_triangles[cnt + 1]]);GL.Vertex(m_vertices[m_triangles[cnt + 2]]);GL.Vertex(m_vertices[m_triangles[cnt + 2]]);GL.Vertex(m_vertices[m_triangles[cnt]]);}GL.End();GL.PopMatrix();}
}


2、Shader描边

在这里插入图片描述


Shader描边实现的方法也有很多,上图的是法线外拓方法。使用两个pass,第一个pass让顶点沿着法线方向延伸出去,使得模型变大一圈。第二个pass正常渲染,让正常渲染的模型挡在第一个pass之上,这样就会露出去的部分就是描绘的边。

网上有很多描边的例子,这里不详细介绍. 例如,csdn博客、unity官方问答、stackoverflow

以下是Shader代码,在项目的UtilizeShaderScene场景中有演示.

Shader "Shaders/ToonBound"
{Properties{_Color ("Color Tint", Color) = (1, 1, 1, 1)_MainTex ("Main Tex", 2D) = "white" {}_Ramp ("Ramp Texture", 2D) = "white" {}                  //控制漫反射色调的渐变纹理_Outline ("Outline", Range(0, 1)) = 0.1                  //控制轮廓线宽度_OutlineColor ("Outline Color", Color) = (1, 0, 0, 1) //轮廓线颜色_Specular ("Specular", Color) = (1, 1, 1, 1)          //高光反色颜色_SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01 //高光反射系数阈值}SubShader{Tags { "RenderType"="Opaque" "Queue"="Geometry"}LOD 100Pass{//命名Pass块,以便复用NAME "OUTLINE"//剔除正面Cull FrontCGPROGRAM#pragma vertex vert#pragma fragment frag// make fog work//#pragma multi_compile_fog#include "UnityCG.cginc"float _Outline;fixed4 _OutlineColor;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;}; struct v2f {float4 pos : SV_POSITION;};v2f vert (a2v v) {v2f o;//让描边在观察空间达到最好的效果float4 pos = mul(UNITY_MATRIX_MV, v.vertex); float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);  normal.z = -0.5;pos = pos + float4(normalize(normal), 0) * _Outline;//将顶点从视角空间变换到裁剪空间o.pos = mul(UNITY_MATRIX_P, pos);return o;}float4 frag(v2f i) : SV_Target { //轮廓线颜色渲染整个背面return float4(_OutlineColor.rgb, 1);               }ENDCG}Pass {Tags { "LightMode"="ForwardBase" }//渲染正面Cull BackCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"#include "UnityShaderVariables.cginc"fixed4 _Color;sampler2D _MainTex;float4 _MainTex_ST;sampler2D _Ramp;fixed4 _Specular;fixed _SpecularScale;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;float4 tangent : TANGENT;}; struct v2f {float4 pos : POSITION;float2 uv : TEXCOORD0;float3 worldNormal : TEXCOORD1;float3 worldPos : TEXCOORD2;SHADOW_COORDS(3)};v2f vert (a2v v) {v2f o;o.pos = UnityObjectToClipPos( v.vertex);o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);o.worldNormal  = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;TRANSFER_SHADOW(o);return o;}float4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);fixed4 c = tex2D (_MainTex, i.uv);fixed3 albedo = c.rgb * _Color.rgb; //计算材质反射率fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; //计算环境光UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //计算当前世界坐标下的阴影值fixed diff =  dot(worldNormal, worldLightDir);//计算半兰伯特漫反射系数diff = (diff * 0.5 + 0.5) * atten;//对渐变纹理_Ramp进行采样fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;fixed spec = dot(worldNormal, worldHalfDir);fixed w = fwidth(spec) * 2.0; //抗锯齿处理fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Diffuse"
}


3、代码生成描边物体

①动态生成:
在项目DynamicDrawWireFrameScene场景中有演示.

在这里插入图片描述


②使用编辑器拓展生成:
在项目EditorDrawWireFrameScene场景中有演示.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


③原理分析:

原理比较简单,就是获取该模型所有的顶点位置,使用LineRenderer连接两个点,即生成一个边。如果全部连接,就会生成跟GL描边一样的效果。

(题外话:连点成面,连面成网,模型网格是由许多个三角面构成的,而两个三角面即形成一个四边形,至于如何构造三角面形成网格,可以看我之前的文章)

如何剔除掉模型一个面不需要的描边,我们可以使用叉乘,分别获得两个相邻三角面的法线向量,然后对两个法线向量点乘,获得角度,如果两个三角面平行,即它们相交的边可以剔除掉。

在这里插入图片描述

在这里插入图片描述


配合上方图片,得出以下核心代码:

/// <summary>
/// 退化四边形.
/// </summary>
[System.Serializable]
public struct DegradedRectangle  	//(v1、v2、v3_1)(v1、v2、v3_2)为相同一条边的两个三角面,两个三角面即为一个退化四边形.   v3_2为-1,即该四边形相同边是“边界边缘”.
{public int vertex1;             //构成边的顶点1的索引public int vertex2;             //构成边的顶点2的索引public int triangle1_vertex3;   //边所在三角面1的顶点3索引public int triangle2_vertex3;   //边所在三角面2的顶点3索引 
}/// <summary>
/// 绘制模型的网格线框.(直接放在模型身上,初始化时创建)
/// </summary>
public class DynamicDrawWireFrame : MonoBehaviour
{private Transform m_Transform;private MeshFilter m_MeshFilter;private Transform m_drawWireFrameParent;            //描绘物体线框的线 父物体.[Header("退化四边形资源文件")][SerializeField]private DegradedRectangles m_DegradedRectangles;    //退化四边形资源文件,就是DegradedRectangled数组. [Header("LineRenderer预制体")][SerializeField]private GameObject m_Prefab_Line;                   //LineRender预制体.void Start(){if (m_DegradedRectangles == null){Debug.LogError("没有赋值退化四边形.");return;}//查找初始化.m_Transform = gameObject.GetComponent<Transform>();m_MeshFilter = gameObject.GetComponent<MeshFilter>();Mesh mesh = m_MeshFilter.sharedMesh;m_drawWireFrameParent = m_Transform.Find("DrawWireFrameParent");if (m_drawWireFrameParent == null) { m_drawWireFrameParent = new GameObject("DrawWireFrameParent").transform;m_drawWireFrameParent.SetParent(m_Transform, false); }//临时变量.Vector3 v1;Vector3 v2;Vector3 v3_1;Vector3 v3_2;Vector3 vv1;Vector3 vv2;Vector3 vv3;Vector3 face1Normal;Vector3 face2Normal;float angle;List<Vector3> drawList = new List<Vector3>();//循环退化四边形,通过计算,得出网格的边缘线.for (int i = 0; i < m_DegradedRectangles.degraded_rectangles.Count; i++){//获取退化四边形对应的网格顶点坐标.v1 = mesh.vertices[m_DegradedRectangles.degraded_rectangles[i].vertex1];    v2 = mesh.vertices[m_DegradedRectangles.degraded_rectangles[i].vertex2];v3_1 = mesh.vertices[m_DegradedRectangles.degraded_rectangles[i].triangle1_vertex3];if (m_DegradedRectangles.degraded_rectangles[i].triangle2_vertex3 > 0)    //如果是边界边缘,该值为-1.{v3_2 = mesh.vertices[m_DegradedRectangles.degraded_rectangles[i].triangle2_vertex3]; //获取退化四边形对应的网格顶点坐标.//计算出两个相邻三角面的法线向量.vv1 = v2 - v1;      vv2 = v3_1 - v1;vv3 = v3_2 - v1;face1Normal = Vector3.Cross(vv1, vv2).normalized;face2Normal = Vector3.Cross(vv3, vv1).normalized;//点积,计算两个三角面是否平行.angle = Mathf.Acos(Vector3.Dot(face1Normal, face2Normal)) * Mathf.Rad2Deg;  //两条法线相交的角度.//angle = Vector3.Angle(face1Normal, face2Normal);      //只能算到 [0,180] 度.if (angle < -2f || angle > 2f)  //小于或大于.    两个面不平行,该线不是中间线.{Debug.Log("边缘");drawList.Add(v1);drawList.Add(v2);}else    //两个面平行.    不画中间的线.{}}else    //边界边缘.{Debug.Log("边界边");Debug.Log(m_DegradedRectangles.degraded_rectangles[i]);drawList.Add(v1);drawList.Add(v2);}}//循环相框集合,每两个点,生成一根线.GameObject line;LineRenderer line_LineRenderer;for (int i = 0; i < drawList.Count; i += 2){line = GameObject.Instantiate<GameObject>(m_Prefab_Line, m_drawWireFrameParent);line.name = "Line_" + i;line_LineRenderer = line.GetComponent<LineRenderer>();line_LineRenderer.positionCount = 2;line_LineRenderer.SetPosition(0, drawList[i]);line_LineRenderer.SetPosition(1, drawList[i + 1]);}}}


项目链接Github
项目链接csdn

这是以前写下的东西,当时查阅了不少文档,如有雷同,侵删,完毕.

这篇关于Unity 模型描边的几种方法. (Shader、GL、代码生成描绘边)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/918752

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费