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

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...

redis和redission的区别

Redis 和 Redisson 是两个密切相关但又本质不同的技术&#xff0c;它们扮演着完全不同的角色&#xff1a; Redis: 内存数据库/数据结构存储 本质&#xff1a; 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能&#xff1a; 提供丰…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...

华为云Flexus+DeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手

华为云FlexusDeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手 一、构建知识库问答助手引言二、构建知识库问答助手环境2.1 基于FlexusX实例的Dify平台2.2 基于MaaS的模型API商用服务 三、构建知识库问答助手实战3.1 配置Dify环境3.2 创建知识库问答助手3.3 使用知…...