当前位置: 首页 > 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 …...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...