【TA】Unity角色二次元风格渲染

news2024/11/18 11:33:05

NRMToonLitSample


Author : 文若
我的Demo地址NRMToonLitSample
学习视频地址Kerry大佬的 技术美术实战培训课程——卡通人物渲染方案


文章目录

  • NRMToonLitSample
    • 1. 模型贴图基本信息
    • 2. 基础渲染效果
      • 2.1 基础shader Toon
      • 2.2 光照模型效果
        • 第一步:光照色阶
        • 第二步:增加ILM贴图
        • 第三步:顶点信息
      • 2.3 当前渲染效果展示
    • 3. 卡通高光
      • 3.1 增加高光效果
      • 3.2 高光颜色优化
    • 4. 描边
      • 4.1 增加描线效果
      • 4.2 使用DetailMap
      • 4.3 描线融合与增强
    • 5. 色彩校正
    • 6. 外描边
      • 6.1 轮廓描边实现
      • 6.2 边缘颜色融合
    • 7. 效果展示

NRM由四张图构成角色渲染,亮部贴图,阴影贴图图,ilm贴图和细节贴图。

1. 模型贴图基本信息

原始模型

亮部贴图RGBA

亮部贴图Alpha
用于区分人物的皮肤区域以及非皮肤区域。

暗部颜色RGBA

暗部贴图Alpha
用于做某些mask使用。

LightMap R通道
控制高光强度

LightMap G通道
偏移光照,越黑部分越接近阴影,越白部分越接近亮部。128中度灰不会对原来光线进行偏移。

LightMap B通道
控制高光范围大小(光滑度)越黑部分高光越小,纯黑区域五高光。

LightMap Alpha通道
一张描线图,记录描线,内描线。

DetailMap RGBA
使用第二套UV采样,也是描线图。但UV分部并不严格。按照第二套UV做点缀使用。

顶点色R通道
遮挡部分颜色较黑,代表环境光的遮挡。剩余通道控制描边的粗细,深度偏移等信息。

2. 基础渲染效果

2.1 基础shader Toon

Shader "Toon"
{
    Properties
    {
        _BaseMap ("Base Map", 2D) = "white" {}
        _SSSMap ("SSS Map", 2D) = "black" {}
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord0 : TEXCOORD0;
                float2 texcoord1 : TEXCOORD1;
                float3 normal : NORMAL;
                float4 color : COLOR;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 pos_world : TEXCOORD1;
                float3 normal_world : TEXCOORD2;
            };

            sampler2D _BaseMap;
            sampler2D _SSSMap;

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.normal_world = UnityObjectToWorldNormal(v.normal);
                o.uv = float4(v.texcoord0, v.texcoord1);
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                half2 uv1 = i.uv.xy;
                half2 uv2 = i.uv.zw;
                half4 base_map = tex2D(_BaseMap, uv1);
                half4 sss_map = tex2D(_SSSMap, uv1);

                return base_map;
            }
            ENDCG
        }
    }
}

2.2 光照模型效果

// 单位向量
float3 normalDir = normalize(i.normal_world);
// 光照方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
// light
half NdotL = dot(normalDir,lightDir);
return NdotL.xxxx;

第一步:光照色阶

模型色阶

half toon_diffuse = step(0.0, NdotL); // 色阶化

在这里插入图片描述
图形色阶化
加入阈值范围和硬度

_ToonThreshold ("ToonThreshold", Range(0,1)) = 0.6 // 阈值范围
_ToonHardness ("ToonHardness",Float) = 50.0 // 过渡的生硬情况
half4 base_map = tex2D(_BaseMap, uv1);
half3 base_color = base_map.rgb;
half NdotL = dot(normalDir, lightDir); // 结果在(-1~1)
half half_lambert = (NdotL + 1.0) * 0.5; // 缩放到0-1之间
// 偏移光照位置
half toon_diffuse = saturate((half_lambert - _ToonThreshold) * _ToonHardness);
// 颜色*base图片的灰度值
half3 final_diffuse = toon_diffuse * base_color;
return float4(final_diffuse, 1.0);


提高阴影面的亮度
1.第一种方法,原有亮度的0.5倍

toon_diffuse = saturate(toon_diffuse + 0.5); // 提亮光照


2.采用阴影面贴图,拿到阴影面rgb和toondiffuse做插值计算

half4 sss_map = tex2D(_SSSMap, uv1);
half3 sss_color = sss_map.rgb;
half NdotL = dot(normalDir, lightDir); 
half half_lambert = (NdotL + 1.0) * 0.5; 
half toon_diffuse = saturate((half_lambert - _ToonThreshold) * _ToonHardness);
half3 final_diffuse = lerp(sss_color, base_color, toon_diffuse);

第二步:增加ILM贴图

// ILM贴图
half4 ilm_map = tex2D(_ILMMap,uv1);
float spec_intensity = ilm_map.r; // 控制高光的强度
float diffuse_control = ilm_map.g * 2.0 - 1.0; // 控制光照偏移,从0-1转换成-1~1
float spec_size = ilm_map.b; // 控制高光的大小
float inner_line = ilm_map.a; // 用来控制内描线

当前头发没有任何阴影,需要为头发末端添加光照阴影。

光照贴图的g通道,提前预设阴影区域,给角色头发做光照偏移的效果。这是一张灰度图,以0.5为分界线,灰度值高于128(0.5)的部分提前变亮,等于128的部分不会对光照进行偏移,低于128的部分变暗。

使用光照贴图优化

half lambert_term = half_lambert + diffuse_control; // 做一个偏移控制
half toon_diffuse = saturate((lambert_term - _ToonThreshold) * _ToonHardness);

第三步:顶点信息

当前效果中裙子内部应该是暗面,利用ao信息来渲染。

float4 vertex_color : TEXCOORD3;
o.vertex_color = v.color;
float ao = i.vertex_color.r;
half lambert_term = half_lambert * ao + diffuse_control;

2.3 当前渲染效果展示

在这里插入图片描述

3. 卡通高光

3.1 增加高光效果

一个完整的效果应该有漫反射和高光反射,上一节完成了漫反射效果。圈中的金属质感部分需要进行高光处理

ILM图的B通道控制高光形状的大小,高光部分越黑越光滑,形状也越小。

使用NotV进行计算,并且为这个值加上偏移结果

// 视觉方向
float3 viewDir = normalize(_WorldSpaceCameraPos - i.pos_world.xyz); 
// 高光处理
float NdotV = (dot(normalDir, viewDir) + 1.0) * 0.5; //拿到NdotV并进行数值范围缩放
float spec_trem = NdotV * ao + diffuse_control; // 光线偏移


增加高光系数

_SpecSize ("Spec Size",Range(0,1)) = 0.1 // 高光系数
// 高光处理 拿到NdotV并进行数值范围缩放
float NdotV = (dot(normalDir, viewDir) + 1.0) * 0.5;
float spec_trem = NdotV * ao + diffuse_control; // 光线偏移
// 当前高光是基于视角的高光 真正高光收到光照方向的影响
spec_trem = half_lambert * 0.9 + spec_trem * 0.1; // 高光权重分配
// 限制边缘
half toon_spec = saturate((spec_trem - (1.0 - spec_size * _SpecSize)) * 500); // 内部数值越大越光滑

在这里插入图片描述
对高光和Base颜色进行叠加,金属部分光线对比之前漫反射渲染效果有了明显变化。

half3 final_spec = toon_spec * base_color * spec_intensity;
half3 final_color = final_diffuse + final_spec;
return float4(final_color, 1.0);

在这里插入图片描述

3.2 高光颜色优化

对高光颜色优化,增加可自定义的高光颜色。

// 自定义的高光颜色与原来的颜色进行混合
half spec_color = (_SpecColor.xyz + base_color) * 0.5;
half3 final_spec = toon_spec * spec_color * spec_intensity;

4. 描边

4.1 增加描线效果

光照贴图的alpha通道用来控制内描线

float inner_line = ilm_map.a;

将描线效果叠加到当前颜色上

half3 final_line = inner_line.xxx; // 描线效果
half3 final_color = (final_diffuse + final_spec) * final_line;
return float4(final_color, 1.0);

4.2 使用DetailMap

detail map 的uv更加随意,可以勾勒出一些圆滑的斜线等。但是该贴图非常的大,只有高清二点情况下才能避免模糊。

half3 detail_color = tex2D(_DetailMap, uv2); // 采样detail map 使用第二套uv
half3 final_line = inner_line.xxx * detail_color;
half3 final_color = (final_diffuse + final_spec) * final_line;
return float4(final_color, 1.0);

4.3 描线融合与增强

使用插值将内描线线条增强并与皮肤颜色融合

half3 detail_color = tex2D(_DetailMap, uv2); // 采样detail map 使用第二套uv
detail_color = lerp(base_color * 0.2, float3(1.0, 1.0, 1.0), detail_color);
half3 inner_line_color = lerp(base_color * 0.2, float3(1.0, 1.0, 1.0), inner_line);
half3 final_line = inner_line_color * inner_line_color * detail_color;
half3 final_color = (final_diffuse + final_spec) * final_line;
return float4(final_color, 1.0);

5. 色彩校正

类Tone Mapping做法,将色彩进行微细的调整,稍微对比度压暗了一丢丢

final_color = sqrt(max(exp2(log2(max(final_color, 0.0)) * 2.2), 0.0));

6. 外描边

6.1 轮廓描边实现

外轮廓的描线做法,使用双通道,在Pass2中拿到角色在对应空间坐标上的顶点,根据顶点得到法线,在法线上对顶点进行外拓。

_OutlineWidth ("OutLine Width",Range(0,10)) = 5.0 // 外轮廓宽度

世界空间坐标下外拓实现

float3 pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 normal_world = UnityObjectToWorldNormal(v.normal);
// 顶点外拓 拿到世界坐标
pos_world += normal_world * _OutlineWidth * 0.001;
// 拿到vp矩阵
o.pos = mul(UNITY_MATRIX_VP, float4(pos_world, 1.0));
return float4(0, 0, 0, 1.0); // 黑色描边

观察空间坐标下外拓实现

float3 pos_view = UnityObjectToViewPos(v.vertex);
// 观察空间下的normal方向
float3 normal_world = UnityObjectToWorldNormal(v.normal);
float3 outline_dir = mul((float3x3)UNITY_MATRIX_V, normal_world);
pos_view += outline_dir * _OutlineWidth * 0.001;
o.pos = mul(UNITY_MATRIX_P, float4(pos_view, 1.0));

轮廓描边效果

6.2 边缘颜色融合

根据自定义描边颜色降低对比度,降低饱和度,使得颜色偏暗

_OutlineColor ("Outline Color",Color) = (1,1,1,1) // 轮廓颜色
half4 frag(v2f i) : SV_Target
{
    float3 baseColor = tex2D(_BaseMap, i.uv.xy).xyz;
    half maxComponent = max(max(baseColor.r, baseColor.g), baseColor.b) - 0.004;
    half3 saturatedColor = step(maxComponent.rrr, baseColor) * baseColor;
    saturatedColor = lerp(baseColor.rgb, saturatedColor, 0.6);
    half3 outlineColor = 0.8 * saturatedColor * baseColor * _OutlineColor.xyz;
    return float4(outlineColor, 1.0);
}

7. 效果展示

全身效果
在这里插入图片描述

半身效果
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【从零到一的Raspberry】树莓派踩坑实录(一)系统安装与简单开发

写在前面 本系列作为树莓派上手记录,同时将本人的踩坑以及参考进行记录汇总,必要时罗列出小组分工,作为《嵌入式软开》小组参考文件。 0 硬件准备 名称描述树莓派3B支持wifi,包含了散热器、外壳、电源线等配件网线感谢王emo同学…

【小程序websocket前后端交互】uniapp写微信小程序聊天功能功能,websocket交互功能,心跳重连【详细注释,复制即用】

前言 这几天在做的一个需求,就是要写一个小程序端的页面,用于跟客服聊天。 然后就用到了websocket技术,以前我做过网页版的,但是做小程序后发现网页版的逻辑放过来没问题,但是很多的方法和api是不生效的,所…

立方体的表面积 长方体的表面积 公里转换为米 温度对照

立方体的表面积 难度&#xff1a;青铜 时间限制&#xff1a;1秒 占用内存&#xff1a;64 M 输入立方体边长&#xff0c;输出立方体的表面积。不考虑非法输入。格式 输入格式&#xff1a;输入实型 输出格式&#xff1a;输出实型 #include<bits/stdc.h> using namespace s…

万字长文的CSS与JavaScript简易学习

近期学习web笔记&#xff0c;可供参考 目录 css: css导入方式&#xff1a; css选择器&#xff1a; javascript: javascript介绍&#xff1a; js引入方式&#xff1a; js书写语法&#xff1a; js变量&#xff1a; 5种原始类型&#xff1a; 运算符&#xff1a; JavaScr…

推荐一款基于.Net Core开发简约漂亮的 WPF UI库

今天给大家推荐一个开源WPF UI库。 项目简介 这是一款使用简单、UI评论的WPF UI库&#xff0c;借鉴了多个开源框架。UI简单清晰、大气。 技术架构 1、跨平台&#xff1a;这是基于.Net Core开发的系统&#xff0c;可以部署在Docker, Windows, Linux, Mac。 2、开发环境&…

微视网媒:沃尔沃质量有什么魅力 让大佬罗永浩、樊登纷纷翻牌S90?

在消费市场&#xff0c;选对代言人&#xff0c;产品就成功了一半&#xff0c;这话可是一点都没说错。 从一定程度上来说&#xff0c;代言人就是产品对外形象的展示&#xff0c;甚至有不少消费者还会因为代言人激情下单。 当然&#xff0c;成也代言&#xff0c;败也代言&#xf…

拉格朗日对偶问题的一些介绍

文章目录参考前言拉格朗日函数例1例2拉格朗日函数的对偶问题参考 “拉格朗日对偶问题”如何直观理解&#xff1f;“KKT条件” “Slater条件” “凸优化”打包理解 感觉有时间看视频的还是看视频比较好&#xff0c;本文只是记录一下以防以后忘记。 前言 还记得SVM里用到拉格朗…

Python代码的编写运行方式简介

Python代码的编写运行方式简介 Python编写方式 Python 是一种解释型的脚本编程语言&#xff0c;支持两种代码编写方式&#xff1a;交互命令行方式和运行.py代码文件方式。 Python的交互命令行方式和直接运行.py代码文件方式有什么区别呢&#xff1f; 交互模式&#xff0c;相当…

Python爬虫|采集开源众包的悬赏任务,自动翻页

前言 现在互联网,有很多网站提供一些接单外派的形式,提供给有能力的人或者团队去接单。比如说,很多人熟悉的猪八戒,程序员客栈,CODING 码市,开源众包等等平台,相信很多同学也都知道。 如果要第一时间了解某个接单平台发布的第一手悬赏任务,选择爬虫也是非常不错的选择…

websocket接口自动化集成pytest测试框架

01、websocket协议 1、介绍 WebSocket是一种在单个TCP通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455&#xff0c;并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向…

【2020-10-26】JS逆向之同盾滑块

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言总结前言 用官网的做个例子吧&#xff1a;https://sec.xiaodun.com/onlineExperience/slidingPuzzle?Rbdjj?sourcetongdun&keywordtongdun 简单分析下这个…

项目整体管理

项目整体管理 1.1 项目整体管理概述 1.项目整体管理知识领域包括识别、确定、结合、统一与协调各项目管理过程组内不同过程与项目管理活动所需进行的各种过程和活动。 2.“整体管理”的基本任务就是为了按照实施组织确定的程序实现项目目标&#xff0c;将项目管理过程组中需要…

CalBioreagents 艾美捷芽孢杆菌多克隆抗体说明书

艾美捷CalBioreagents 芽孢杆菌多克隆抗体英文说明&#xff1a; CATALOG NUMBER: P043 PRODUCT DESCRIPTION: Rabbit anti-Bacillus polyclonal antibody. IMMUNOGEN: Immunogen was prepared from a pool of purified spores from Bacillus subtilis and Bacillus cereus.…

一句话需求,让你去抄人家产品,你遇到了咋办

打工者联盟为了抵抗996、拖欠工资、黑心老板、恶心公司&#xff0c;让我们组成打工者联盟。客观评价自己任职过的公司情况&#xff0c;为其他求职者竖起一座引路的明灯。https://book.employleague.cn/话说&#xff1a;工地有个包工头&#xff0c;找来两个瓦匠要承包工程。 瓦…

ZooKeeper基础

一、概念 架构&#xff1a; Client/ServerEnsemble&#xff08;集群&#xff0c;ZK服务器组&#xff09;&#xff0c;最小节点数为3ZK LeaderaZK Follower ZooKeeper数据模型 znode&#xff1a;用于存储数据&#xff0c;分为持久的&#xff08;default&#xff09;、临时的…

一次服务器非法重启后导致的故障排查记录

作者&#xff1a;JackTian 来源&#xff1a;公众号「杰哥的IT之旅」 ID&#xff1a;Jake_Internet 转载请联系授权&#xff08;微信ID&#xff1a;Hc220088&#xff09; 原文地址&#xff1a;一次服务器非法重启后导致的故障排查记录 大家好&#xff0c;我是杰哥。 前段时间遇…

贪婪算法(Huffman编码)

如果一个算法分阶段的工作&#xff0c;并且在每一个阶段都认为所做的决定是最好的&#xff0c;而不考虑将来的后果&#xff0c;这样的算法就叫做贪婪算法。贪婪算法只考虑当前局部的最优解&#xff0c;而不去考虑全局的情况&#xff0c;如果最终得到的结果是全局最优的&#xf…

麻雀优化CNN超参数用于回归MATLAB

在CNN模型的构建中&#xff0c;涉及到特别多的超参数&#xff0c;比如&#xff1a;学习率、训练次数、batchsize、各个卷积层的卷积核大小与卷积核数量&#xff08;feature map数&#xff09;&#xff0c;全连接层的节点数等。直接选择的话&#xff0c;很难选到一组满意的参数&…

如何理解关系型数据库的常见设计范式?

目录理清基础概念实体属性元组分组函数依赖完全函数依赖部分函数依赖传递函数依赖码全码理解六范式第一范式&#xff08;1NF&#xff09;第二范式&#xff08;2NF&#xff09;第三范式&#xff08;3NF&#xff09;巴斯-科德范式&#xff08;BCNF&#xff0c;Boyce-Codd Normal …

基于STM32的智能家居系统设计

目录 第1章 前言 1 1.1 课题研究的背景和实际意义 1 1.1.1课题背景 1 1.1.2实际意义 1 1.2国内外发展现状、存在问题以及前景 2 1.2.1发展现状 2 1.2.2存在问题 2 1.2.3发展前景 2 1.3 主要工作、内容安排及预期成果 3 1.3.1主要研究工作 3 1.3.2预期成果 3 第2章 总体设计方案…