Cg Programming/Unity/Toon Shading卡通着色

2024-03-11 09:08

本文主要是介绍Cg Programming/Unity/Toon Shading卡通着色,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本教程涵盖了卡通着色(也叫做卡通渲染)作为非真实感渲染技术的一个实例。
这里写图片描述奶牛图标。本节中的所有图片都由Mariana Ruiz提供。
这是几个关于光照教程中的其中之一,这个光照超出了Phong反射模型的范围。但是,它是基于用章节“光滑镜面高光”中描述的Phong反射模型实现的逐像素光照。如果你还没有阅读过那章,建议你最好先读一下它。

非真实感渲染是计算机图形学中的一个非常宽泛的术语,它涵盖了所有的渲染技术,以及明显不同于实物照片外观的视觉风格。例子包括:孵化、概述、线性透视扭曲、粗抖动、粗颜色量化等。

卡通着色(也叫卡通渲染)是任何被用来实现一个卡通或手绘外观三维模型的非真实感绘制技术的子集。

一个指定视觉的着色器

这里写图片描述
皮克斯的John Lasseter曾在接受采访时说:“艺术挑战技术,技术激发艺术。”传统上用于描述三维对象的许多视觉风格和绘图技术实际上很难在着色器中实现。但是,我们根本没有理由不去尝试它。

当为指定视觉风格实现一个或多个着色器时,首先应该确定风格的特性哪些必须被实现。这是精确分析视觉风格例子的主要任务。没有这些例子,通常很难确认风格的特征。即使掌握某种风格的艺术家也往往无法恰当地描述这些特征;举个例子,因为他们不再意识到某些特征,或者认为某些特征是不必要的缺陷,不值得一提。

对于每一个特性,它应该被确定是否以及如何准确地实现它们。有些特性很容易实现,有些则很难通过程序员来实现或通过GPU来计算。因此,以John Lasseter的名言为精神的着色器程序员和(技术)艺术家之间的讨论通常是非常值得的,以决定哪些特性包括以及如何准确地重现它们。

非写实的镜面高光

这里写图片描述
跟在章节“光滑镜面高光”中实现的Phong反射模型相比较,本节图像中的镜面高光显然是白色的,没有任何其它颜色的添加。此外,它们还有一个非常锐化的边界。

通过计算Phong着色模型的高光项,以及设置片元颜色为镜面反射颜色乘以光源颜色(无衰减),如果镜面反射项大于某一阈值,比如最大强度的一半,我们可以实现这种程序式的镜面高光。

但是如果没有任何高光呢?通常,用户会为这种情况指定一个黑色镜面反射颜色;但是,用我们的方法会导致黑色的高光。解决这个问题的一个方法是考虑镜面反射的颜色,并且根据镜面颜色的不透明度将高光颜色和其它颜色“混合”。Alpha混合作为片元着色器的操作已经在章节“透明度”中描述过了。但是,如果所有的颜色都在片元着色器中已知,那么它也可以在片元着色器中计算。

在以下的代码片段中,假定fragmentColor已经分配了一个颜色,比如基于漫射照明。镜面颜色_SpecColor乘以光源颜色_LightColor0,然后在镜面颜色的不透明度_SpecColor.a上混合fragmentColor

 if (dot(normalDirection, lightDirection) > 0.0 // light source on the right side?&& attenuation *  pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), _Shininess) > 0.5) // more than half highlight intensity? {fragmentColor = _SpecColor.a * _LightColor0.rgb * _SpecColor.rgb+ (1.0 - _SpecColor.a) * fragmentColor;}

这就足够了吗?如果你仔细观察上图中公牛的眼睛,你将会看到一对镜面高光,即有一个以上的光源导致了镜面高光。在大多数教程中,我们通过有加性混合的第二个渲染通道把额外的光源考虑进来。但是,如果镜面高光的颜色不应该被添加到其它颜色上的话,加性混合就不应该被使用。相反,有镜面高光不透明颜色(通常)和其它片元的透明片元的的alpha混合将是一个可行的解决方案。(查看章节“透明度”中关于alpha混合的描述。)

非写实漫射照明

这里写图片描述
上面公牛图像中的漫射光照只包含了两种颜色:照亮毛皮的浅棕色和无光毛皮的深棕色。公牛其它部分的颜色与灯光无关。

实现这个的一种方法就是不管Phong反射模型的漫反射项是否达到了某个阈值都使用完整的漫反射颜色,比如大于0以及第二个颜色小于0。对于公牛的毛皮,这两种颜色将会是不一样的;对于其它部分,它们将会是一样的,这样在有光和无光区域就没有视觉差别了。对于阈值_DiffuseThreshold从深色_UnlitColor转换到浅色_Color(乘以光源颜色_LightColor0)的实现可能看起来像这样:

 float3 fragmentColor = _UnlitColor.rgb; 
if (attenuation * max(0.0, dot(normalDirection, lightDirection))>= _DiffuseThreshold)
{fragmentColor = _LightColor0.rgb * _Color.rgb; 
}

这就是上面图像中关于非写实照明的所有内容吗?仔细看一下就会发现在深棕色和浅棕色之间有一条浅的不规则的线。实际上,情况甚至会更复杂,深棕色有时并不包括上述技术所覆盖的区域,并且有时它包括了更多甚至超越了黑色轮廓。它在视觉风格上添加了丰富的细节并创建了手工绘制的外观。另一方面,在着色器中很难重现这一点。

轮廓

很多卡通着色器的特征之一是沿着模型的轮廓以一种指定的颜色勾勒(通常是黑色的,但也会是其它颜色,可以看看上图中的牛)。

在着色器中有不同的技术来达到这种效果。Unity 3.3在标准资源中附带了这样一个着色器,它通过在轮廓的颜色渲染放大模型的背面来呈现这些轮廓(通过移动表面法向量上的顶点坐标来放大),然后在上面绘制正面。这里我们用另一基于章节“轮廓增强”中技术:如果一个片元被确定为接近轮廓(原文为silhouette),它就被设置为轮廓线(原文为outline)的颜色。这个只对光滑表面有用,并且它会生成不同厚度的轮廓线(略厚或略薄取决于视觉风格)。但是,至少轮廓的总厚度应该由着色器的属性来控制。

这里写图片描述

我们做完了吗?如果你仔细看一下上图中的驴,你会看到在它腹部和耳朵的轮廓线比其它轮廓要厚得多。这样表示无光的区域;但是,厚度上的改变是连续的。模拟这种效果的一个方法是让用户指定两种总的轮廓线厚度:一个用于光照充足的区域,一个用作无光的区域(根据Phong反射模型中的漫反射项)。在这两个极端之间,厚度的参数应该被插值(也是根据Phong反射模型中的漫反射项)。但是,这样会使得轮廓线依赖于指定的光源;因此,以下的着色器只渲染第一个光源的轮廓线和漫射光照,这个光源通常是光源中最重要的一个。其它所有的光源只是渲染镜面高光。

以下的实现必须在_UnlitOutlineThickness(如果漫反射项的点乘结果小于等于0)和_LitOutlineThickness(如果点乘结果为1)之间进行插值。对于用0到1之间的参数x把值a线性变换到值b,Cg提供了内置函数lerp(a, b, x)。被插值的值随后会被用作阈值以决定一个点是否足够接近轮廓。如果它接近轮廓,它的片元颜色就被设置为轮廓线的颜色_OutlineColor

            if (dot(viewDirection, normalDirection) < lerp(_UnlitOutlineThickness, _LitOutlineThickness, max(0.0, dot(normalDirection, lightDirection)))){fragmentColor = _LightColor0.rgb * _OutlineColor.rgb; }

完整的着色器代码

目前为止应该很清楚的是,即使是上面的一些图像也会给忠实实现带来非常大的困难。因此,以下的着色器只实现了上面描述的一些特性而忽略了许多其它特性。注意不同颜色的贡献值(漫射照明、轮廓线、高光)是根据互相之间的遮挡来指定不同的优先级。你也可以把这些优先级考虑为不同的层放在彼此之上。

Shader "Cg shader for toon shading" {Properties {_Color ("Diffuse Color", Color) = (1,1,1,1) _UnlitColor ("Unlit Diffuse Color", Color) = (0.5,0.5,0.5,1) _DiffuseThreshold ("Threshold for Diffuse Colors", Range(0,1)) = 0.1 _OutlineColor ("Outline Color", Color) = (0,0,0,1)_LitOutlineThickness ("Lit Outline Thickness", Range(0,1)) = 0.1_UnlitOutlineThickness ("Unlit Outline Thickness", Range(0,1)) = 0.4_SpecColor ("Specular Color", Color) = (1,1,1,1) _Shininess ("Shininess", Float) = 10}SubShader {Pass {      Tags { "LightMode" = "ForwardBase" } // pass for ambient light and first light sourceCGPROGRAM#pragma vertex vert  #pragma fragment frag #include "UnityCG.cginc"uniform float4 _LightColor0; // color of light source (from "Lighting.cginc")// User-specified propertiesuniform float4 _Color; uniform float4 _UnlitColor;uniform float _DiffuseThreshold;uniform float4 _OutlineColor;uniform float _LitOutlineThickness;uniform float _UnlitOutlineThickness;uniform float4 _SpecColor; uniform float _Shininess;struct vertexInput {float4 vertex : POSITION;float3 normal : NORMAL;};struct vertexOutput {float4 pos : SV_POSITION;float4 posWorld : TEXCOORD0;float3 normalDir : TEXCOORD1;};vertexOutput vert(vertexInput input) {vertexOutput output;float4x4 modelMatrix = _Object2World;float4x4 modelMatrixInverse = _World2Object; output.posWorld = mul(modelMatrix, input.vertex);output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);output.pos = mul(UNITY_MATRIX_MVP, input.vertex);return output;}float4 frag(vertexOutput input) : COLOR{float3 normalDirection = normalize(input.normalDir);float3 viewDirection = normalize(_WorldSpaceCameraPos - input.posWorld.xyz);float3 lightDirection;float attenuation;if (0.0 == _WorldSpaceLightPos0.w) // directional light?{attenuation = 1.0; // no attenuationlightDirection = normalize(_WorldSpaceLightPos0.xyz);} else // point or spot light{float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - input.posWorld.xyz;float distance = length(vertexToLightSource);attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource);}// default: unlit float3 fragmentColor = _UnlitColor.rgb; // low priority: diffuse illuminationif (attenuation * max(0.0, dot(normalDirection, lightDirection)) >= _DiffuseThreshold){fragmentColor = _LightColor0.rgb * _Color.rgb; }// higher priority: outlineif (dot(viewDirection, normalDirection) < lerp(_UnlitOutlineThickness, _LitOutlineThickness, max(0.0, dot(normalDirection, lightDirection)))){fragmentColor = _LightColor0.rgb * _OutlineColor.rgb; }// highest priority: highlightsif (dot(normalDirection, lightDirection) > 0.0 // light source on the right side?&& attenuation *  pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), _Shininess) > 0.5) // more than half highlight intensity? {fragmentColor = _SpecColor.a * _LightColor0.rgb * _SpecColor.rgb+ (1.0 - _SpecColor.a) * fragmentColor;}return float4(fragmentColor, 1.0);}ENDCG}Pass {      Tags { "LightMode" = "ForwardAdd" } // pass for additional light sourcesBlend SrcAlpha OneMinusSrcAlpha // blend specular highlights over framebufferCGPROGRAM#pragma vertex vert  #pragma fragment frag #include "UnityCG.cginc"uniform float4 _LightColor0; // color of light source (from "Lighting.cginc")// User-specified propertiesuniform float4 _Color; uniform float4 _UnlitColor;uniform float _DiffuseThreshold;uniform float4 _OutlineColor;uniform float _LitOutlineThickness;uniform float _UnlitOutlineThickness;uniform float4 _SpecColor; uniform float _Shininess;struct vertexInput {float4 vertex : POSITION;float3 normal : NORMAL;};struct vertexOutput {float4 pos : SV_POSITION;float4 posWorld : TEXCOORD0;float3 normalDir : TEXCOORD1;};vertexOutput vert(vertexInput input) {vertexOutput output;float4x4 modelMatrix = _Object2World;float4x4 modelMatrixInverse = _World2Object;output.posWorld = mul(modelMatrix, input.vertex);output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).rgb);output.pos = mul(UNITY_MATRIX_MVP, input.vertex);return output;}float4 frag(vertexOutput input) : COLOR{float3 normalDirection = normalize(input.normalDir);float3 viewDirection = normalize(_WorldSpaceCameraPos - input.posWorld.rgb);float3 lightDirection;float attenuation;if (0.0 == _WorldSpaceLightPos0.w) // directional light?{attenuation = 1.0; // no attenuationlightDirection = normalize(_WorldSpaceLightPos0.xyz);} else // point or spot light{float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - input.posWorld.xyz;float distance = length(vertexToLightSource);attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource);}float4 fragmentColor = float4(0.0, 0.0, 0.0, 0.0);if (dot(normalDirection, lightDirection) > 0.0 // light source on the right side?&& attenuation *  pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), _Shininess) > 0.5) // more than half highlight intensity? {fragmentColor = float4(_LightColor0.rgb, 1.0) * _SpecColor;}return fragmentColor;}ENDCG}} Fallback "Specular"
}

总结

恭喜,你完成了本章的学习。我们看到了:

  • 什么是卡通着色,卡通渲染,以及非真实感渲染。
  • 非真实感渲染中的一些技术是如何被使用到卡通着色中去的。
  • 如何在着色器中实现这些技术。

扩展阅读

  • 关于Phong反射模型和逐像素光照,你应该阅读章节“光滑镜面高光”。
  • 关于轮廓的计算,你应该阅读章节“轮廓增强”。
  • 关于混合,你应该阅读章节“透明度”。
  • 关于非真实感渲染技术,你应该阅读图书“OpenGL着色器语言”(第三版)中的第18章。

这篇关于Cg Programming/Unity/Toon Shading卡通着色的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Deferred shading技术简介

参考文章:http://blog.sina.com.cn/s/blog_458f871201017i06.html Deferred shading是这样一种技术:将光照/渲染计算推迟到第二步进行计算。我们这样做的目的是为了避免多次(超过1次)渲染同一个像素。 其基本思想如下: 1、在第一步中,我们渲染场景,但是与通常情况下应用反射模型计算片断颜色不同的是,我们只是简单的将几何信息(位置

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

在Unity环境中使用UTF-8编码

为什么要讨论这个问题         为了避免乱码和更好的跨平台         我刚开始开发时是使用VS开发,Unity自身默认使用UTF-8 without BOM格式,但是在Unity中创建一个脚本,使用VS打开,VS自身默认使用GB2312(它应该是对应了你电脑的window版本默认选取了国标编码,或者是因为一些其他的原因)读取脚本,默认是看不到在VS中的编码格式,下面我介绍一种简单快

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)-CSDN博客 做完了数据结构类,该做一个存储类了,也就是生成一个字典类(只是声明)  实现和上一节的数据结构类的方式大同小异,所

【Unity小技巧】URP管线遮挡高亮效果

前言 在URP渲染管线环境下实现物体遮挡高亮显示效果,效果如下: Unity URP遮挡高亮 实现步骤 创建层级,为需要显示高亮效果的物体添加层级,比如Player 创建一个材质球,也就是高亮效果显示的材质球找到Universal Renderer Data Assets 4.在Assets上添加两个Render Objects组件 第一个做如下三处设置 指定遮挡层级指