当前位置: 首页 > news >正文

Unity制作二次元卡通渲染角色材质——4 、内外描边和细节添加

Unity制作二次元材质角色


回到目录

大家好,我是阿赵。
这里继续讲二次元角色材质。这次打算讲一下描边和细节的添加。

一、外描边

外描边的做法也不止一种,比如后处理方法的偏导数ddx/ddy之类的,也能整个屏幕的求出边缘。但一般来说单模型渲染常用的描边方式,是写多一个Pass,这个Pass是Cull Front的,也就是说是剔除了正面的,然后给模型的顶点沿着法线方向稍微扩大一点,填充成黑色,最后把正常颜色的模型放在黑色模型的重叠位置,那么黑色模型就变成了描边了效果了。
在这里插入图片描述

这个就是Cull Front的Pass的效果。
在这里插入图片描述

把2个pass一起渲染,就能得到描边的效果。
值得注意的是,沿着法线放大模型这一步,顶点坐标应该在哪个空间里面来做放大呢?
一般来说,使用世界空间肯定是可以的,就是先求出顶点的世界坐标和世界法线方向,然后世界坐标加上世界法线乘以一个控制大小的值。
但更好的做法,是在观察空间里面做这个扩展。这是因为,有时候我们想让这个Cull Front的Pass渲染的黑色模型,可以沿着我们观察的方向的Z轴做偏移,如果在世界空间坐标里面做法线扩展,明显是很难找到一个轴是可以沿着摄像机方向的。如果我们先把顶点坐标和法线都转换到观察空间,那么在观察空间里面的Z坐标,其实就是离我们观察点的远近了。
这里有2个做法:
1、在法线转换成观察空间之后,把法线的z轴直接固定到一个值,这样模型沿着法线方向扩展时,在z轴会沿着我们想要的方向去放大,比如:

v2f vert(appdata 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) * _OutlineLen*0.001;o.pos = mul(UNITY_MATRIX_P, pos);return o;
}

2、在观察空间扩展完法线之后,用一个值来控制偏移后顶点的z轴偏移,比如:

v2f vert(appdata v)
{v2f o;float4 pos = mul(UNITY_MATRIX_MV, v.vertex);float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);pos = pos + float4(normalize(normal), 0) * _OutlineLen*0.001;pos.z -= v.vertexColor.b;o.pos = mul(UNITY_MATRIX_P, pos);return o;
}

我这里用了一个顶点颜色的B通道去偏移Z轴,是因为这是一般的习惯做法,通过给顶点颜色指定一个通道,作为描边Z轴偏移的量,这样的做法,可以实现整个模型不同部位的描边显示不一样。
由于我手上的这个模型并没有烘焙顶点色,所以没有顶点通道可以控制,所以我就使用第一种方式来处理。

二、内描边

刚才得到的是外描边。正常来说,如果项目要求不高,也勉强够用了。但如果我们想把效果做得更贴近卡通,那么按道理来说,模型除了外部有描边,模型内部应该也会有一些描边的细节。
回头看看之前说的ILM贴图的A通道:
在这里插入图片描述

可以发现,这张贴图提供了模型里面的描边效果。一般来说,在贴图上面画内描线,会遇到一个问题:
在这里插入图片描述

同样一张贴图上,水平或者垂直的线条,会很清晰,但斜线,会起马赛克,会模糊。
在这里插入图片描述

放大我们的这个角色模型,看一下内描线的效果,发现每一内描线,基本都很清晰,没有出现斜线模糊的情况。这是为什么呢?
接下来看一下这张ILM贴图的A通道,可以发现一些东西:
在这里插入图片描述

会发现,这个内描线,基本上都是水平和垂直的线,并没有出现任何斜线。其实这是一种经验和技巧。在展UV的时候,我们尽量把模型的UV展成这张水平和垂直的方向,这样在画这张线条形的贴图时,线条就能非常的清晰。
这种事情最好是在一开始的时候就规划好。如果说实在做不到,因为漫反射贴图已经画好了,没法改,那么,我们也可以通过展UV2,特别为这个内描线展一张水平垂直的UV。
在这里插入图片描述

加上了内描边之后,整体的感觉就丰富了很多了。

三、细节线条

如果想再进一步添加一些小划痕或者痕迹,这里还可以添加一张细节图:
在这里插入图片描述

看得出来,这张图片并没有像内描边一样,讲究水平垂直的画线,整体比较随意。因为这张贴图添加的是一些像手绘的笔触一样的线条,并不需要非常的整齐和明显。
把这张图也加上,那么线条方面的工作就基本完成了:
在这里插入图片描述

四、贴花

最后才说贴花,是因为这个贴花是独立于之前的角色模型贴图以外的。
在这里插入图片描述

本来这个模型是分为了身体和武器(吉他)2个部分的,所以刚才说到的所有图,包括BaseMap、SSSMap、ILMMap,都是身体一套,武器一套的。但这一张贴花的图片,却是身体和武器共用的。
为什么不同的模型可以共用一张贴图呢?这是因为,这张贴花贴图,是使用了UV2的,也就是说,把身体和武器需要贴花的部分展成UV2然后合并在一起。至于不需要贴花的部分,UV基本上是缩小到看不见的。
所以这个部分其实也没什么好说的,直接读取模型的UV2,然后赋予贴图就行了。我自己做了一点小改动,因为吉他弦的部分没有地方控制透明通道,所以我给这张贴花图片做了个透明通道,让吉他弦能正常的显示出来。
在这里插入图片描述

加上了贴花之后,整个模型的显示基本上就完成了。

五、完整Shader

Shader "azhao/ToonBodyOutline"
{Properties{_BaseMap ("BaseMap", 2D) = "white" {}_SSSMap("SSSMap", 2D) = "white" {}_ILMMap("ILMMap", 2D) = "white" {}_DetailMap("DetailMap",2D) = "white"{}_specColor("specColor",Color) = (1,1,1,1)_shininess("shininess", Range(1 , 100)) = 1_SpecAdd("SpecAdd",float) = 1.0_GradationMin("GradationMin",Range(0.0,1.0)) = 0.0_GradationMax("GradationMax",Range(0.0,1.0)) = 1.0_OutlineColor("OutlineColor",Color) = (0,0,0,1)_OutlineLen("_OutlineLen",float) = 2_MatCapTex("MatCapTex", 2D) = "white" {}_MatCapIntensity("MatCapIntensity",Range(0,2)) = 1_MatCapPow("MatCapPow",Range(0,5)) = 1_MatCapUVScale("MatCapUVScale",Range(0,1)) = 1}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#pragma multi_compile_fwdbase#include "AutoLight.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float2 uv2 : TEXCOORD1;float3 normal:NORMAL;};struct v2f{                float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float2 uv2 : TEXCOORD1;float3 worldPos :TEXCOORD2;float3 worldNormal :TEXCOORD3;};sampler2D _BaseMap;float4 _BaseMap_ST;sampler2D _SSSMap;sampler2D _ILMMap;sampler2D _DetailMap;float4 _specColor;float _shininess;float _SpecAdd;float _GradationMin;float _GradationMax;sampler2D _MatCapTex;float _MatCapIntensity;float _MatCapPow;float _MatCapUVScale;//获取HalfLambert漫反射值float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal){float3 lightDir = UnityWorldSpaceLightDir(worldPos);float NDotL = saturate(dot(worldNormal, lightDir));float halfVal = NDotL * 0.5 + 0.5;return halfVal;}//获取BlinnPhong高光float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal){float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));float specDir = max(dot(normalize(worldNormal), halfDir), 0);float specVal = pow(specDir, _shininess);return specVal;}float2 GetMatCapUV(float3 normalWorld){float3 normalView = mul(UNITY_MATRIX_IT_MV, normalWorld);return normalView.xy*0.5 + 0.5;}v2f vert (appdata v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _BaseMap);o.uv2 = TRANSFORM_TEX(v.uv2, _BaseMap);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.worldNormal = UnityObjectToWorldNormal(v.normal);return o;}half4 frag (v2f i) : SV_Target{// sample the texturehalf4 col = tex2D(_BaseMap, i.uv);half4 sssCol = tex2D(_SSSMap, i.uv);half4 ilmCol = tex2D(_ILMMap, i.uv);half4 detailCol = tex2D(_DetailMap, i.uv2);//色阶化half halfLambert = GetHalfLambertDiffuse(i.worldPos, i.worldNormal);half toonVal = smoothstep(_GradationMin, _GradationMax, halfLambert);half specVal = GetBlinnPhongSpec(i.worldPos, i.worldNormal);float2 MatCapUV = GetMatCapUV(i.worldNormal)*_MatCapUVScale;float4 MatCapCol = tex2D(_MatCapTex, MatCapUV)*_MatCapIntensity;MatCapCol = pow(MatCapCol, _MatCapPow);half3 finalRGB = col.rgb*toonVal + sssCol  * (1 - toonVal)+_specColor* specVal*ilmCol.r+ _specColor * specVal*ilmCol.b*_SpecAdd;finalRGB = finalRGB * (1-ilmCol.b) +MatCapCol.rgb*ilmCol.b;finalRGB = finalRGB * ilmCol.a*detailCol.r;half alpha = col.a;return half4(finalRGB,alpha);}ENDCG}Pass{Cull FrontCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#pragma multi_compile_fwdbase#include "AutoLight.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal:NORMAL;};struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float3 worldPos :TEXCOORD1;float3 worldNormal :TEXCOORD2;};float4 _OutlineColor;float _OutlineLen;v2f vert(appdata 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) * _OutlineLen*0.001;o.pos = mul(UNITY_MATRIX_P, pos);return o;}half4 frag(v2f i) : SV_Target{return _OutlineColor;}ENDCG}}
}

相关文章:

Unity制作二次元卡通渲染角色材质——4 、内外描边和细节添加

Unity制作二次元材质角色 回到目录 大家好,我是阿赵。 这里继续讲二次元角色材质。这次打算讲一下描边和细节的添加。 一、外描边 外描边的做法也不止一种,比如后处理方法的偏导数ddx/ddy之类的,也能整个屏幕的求出边缘。但一般来说单模型渲…...

Ubuntu安装GCC10

使用包安装的方式安装 sudo apt upgradesudo apt install software-properties-commonsudo add-apt-repository ppa:ubuntu-toolchain-r/test QA: 更新python3.7后出现ModuleNotFoundError: No module named ‘apt_pkg‘错误QA: Cannot import name ‘_gi’ sudo apt updatesu…...

【flutter】Dart 规范2

高效 Dart 语言指南:用法示例 每天在你写的 Dart 代码中都会应用到这些准则。库的使用者可能不需要知道你在其中的一些想法,但是维护者肯定是需要的。 库 这些准则可以帮助你在多个文件编写程序的情况下保证一致性和可维护性。为了让准则简洁&#xf…...

k8s CoreDns详解

一、概述 服务发现是 K8s 的一项很重要的功能。K8s 的服务发现有两种方式,一种是将 svc 的 ClusterIP 以环境变量的方式注入到 pod 中;一种就是 DNS,从 1.13 版本开始,coreDNS 就取代了 kube dns 成为了内置的 DNS 服务器。 Cor…...

c++ 连sqlserver

//要在 C 中连接 SQL Server 数据库,可以使用 Microsoft 提供的 SQL Server Native Client 或者 //ODBC 驱动程序。以下是使用 SQL Server Native Client 连接数据库的基本步骤: //1. 安装 SQL Server Native Client 驱动程序。 //2. 在 C 代码中包含头…...

给钉钉的2个建议

1. 建议.MD文件可以实现在线编辑 .MD文件可以实现在线编辑。 现状:word、excel、txt等文件都可以实现在线编辑,期望.MD文件也可以进行在线编辑,便于喜欢用.MD文旦交流的人使用。 2. 增加群内根据关键词自定义提醒功能 随着个人加入的群聊增多…...

STL之优先级队列(堆)的模拟实现与仿函数(8千字长文详解!)

STL之优先级队列(堆)的模拟实现与仿函数 文章目录 STL之优先级队列(堆)的模拟实现与仿函数优先级队列的概念priority_queue的接口介绍优先级队列的构造函数 priority_queue模拟实现类成员构造函数向下调整算法——正常实现 push向…...

设施管理系统

随着经济的快速发展,各种基础设施都在更新,在企事业单位中各种设施也都难以管理,以往传统的管理模式已经无法适应现代社会的需求,设备管理的滞后反而会影响设施设备的使用效果,因此设施设备管理系统必不可少。那么什么…...

JavaScript:获取当前日期、星期、时间 | Data对象

文章目录 1 Date对象2 代码示例3 获取 yyyy-MM-dd 格式的日期 1 Date对象 JavaScript 中的 Date 对象表示日期和时间。Date 对象基于自 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)以来的毫秒数。以下是 Date 对象的一些常用方法和属性。 getFullYear…...

Cadence原理图快速查找元器件的方法

1.Cadence原理图快速查找元器件的方法 ①在红框中输入元器件编号,点击望远镜的图标在底下的状态栏可看到查找到的相关元器件,点击元器件可自动定位当前元器件的位置。 ②点击hierarchy(层)可自主查找,找到后点击序号即…...

科目二 调整座椅

靠背倾角 座椅高低 座椅前后用手抬起座椅前的横杠,让座椅向后移动方便上车 靠背左侧,向后扳扳杠调整倾角 座椅左侧,上下扳动调整高低头顶距车顶有一拳的距离 座椅前横杠一只手提起横杠另一只手握住方向盘前拉、后推调整到合适位置&#xff0c…...

02.加载GDT表,进入保护模式

加载GDT表,进入保护模式 加载GDT表,实现操作系统从实模式进入保护模式 参考 操作系统学习 — 启动操作系统:进入保护模式 保护模式与实模式 GDT、GDTR、LDT、LDTR 调用门与特权级 趣谈 Linux 操作系统 在01.硬盘启动盘,加载操作系…...

MySQL(进阶篇3.0)

锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算机资源(CPU、RAM、I/O)的争用之外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&…...

2023.6.8小记——嵌入式系统初识、什么是ARM架构?

今天还挺充实的,早上在图书馆本来想学一下notion,结果看李沐老师的动手深度学习看到十点半,在电脑上配置了李沐老师的d2l和jupyter,等后续有时间的时候再继续学。 下午看了一下notion的使用方法,这玩意初学者用起来是…...

分布式运用之ELK企业级日志分析系统

1.ELK的相关知识 1.1 ELK的概念与组件 ELK平台是一套完整的日志集中处理解决方案,将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用, 完成更强大的用户对日志的查询、排序、统计需求。 ElasticSearch: 是基于Lucene&#xff08…...

【华为OD机试真题 C语言】8、停车场车辆统计 | 机试真题+思路参考+代码解析

文章目录 一、题目🎃题目描述🎃输入输出🎃样例1🎃样例2 二、思路参考三、代码参考🏆C语言 作者:KJ.JK 🍂个人博客首页: KJ.JK 🍂专栏介绍: 华为OD机试真题汇…...

c++ MES 对接(XML、JSON、SOAP)

&#x1f5d1;️ 清空 //MES系统对接可以使用多种协议&#xff0c;包括XML、JSON和SOAP等。 //以下是使用C语言进行MES系统对接的示例代码&#xff1a; //1. XML协议对接&#xff1a; //c #include <iostream> #include <string> #include <vector> #incl…...

idea导入java web项目带jar

可参考&#xff1a;idea导入Javaweb项目_小黑cc的博客-CSDN博客 配置tomcat 加载项目jar依赖 最后点ok&#xff0c;tomcat启动 jsp页面的项目&#xff0c;必须要加载这两个jar包...

【第55天|● 392.判断子序列 ● 115.不同的子序列 】

392.判断子序列 class Solution { public:bool isSubsequence(string s, string t) {if(s.size()0)return true;if(t.size()0) return false;vector<bool> dp(t.size()1, true);for(int i0; i<s.size(); i){for(int jt.size(); j>0; j--){if(s[i]t[j-1]&&…...

Dockerfile创建镜像

一、Docker镜像的创建 创建镜像有三种方法&#xff0c;分别为【基于已有镜像创建】、【基于本地模板创建】以及【基于Dockerfile创建】。 1.1 基于现有镜像创建 &#xff08;1&#xff09;首先启动一个镜像&#xff0c;在容器里做修改docker run -it centos:7 /bin/bash …...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...