【UnityShader入门精要学习笔记】第十四章 非真实感渲染

news2024/11/15 11:46:39

在这里插入图片描述
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:

  • 书本中句子照抄 + 个人批注
  • 项目源码
  • 一堆新手会犯的错误
  • 潜在的太监断更,有始无终

我的GitHub仓库

总之适用于同样开始学习Shader的同学们进行有取舍的参考。


文章目录

  • 卡通风格渲染
    • 渲染轮廓线
    • 添加高光
    • 实现
    • 素描风格的渲染


卡通风格渲染

卡通风格的渲染特点往往是:黑色线条的描边,分明的明暗变化(色调变换不是渐变的,而是不同的颜色)

要实现卡通风格的渲染,其中之一就是使用基于色调的着色技术,在7.3部分我们使用渐变纹理实现了这样的效果。卡通的高光效果也和我们之前学习的光照不同,在卡通风格中,模型的高光往往是一块块分界明显的纯色区域。

之前我们是直接通过屏幕后处理卷积计算边缘并进行描边,现在,我们将学习如何基于模型进行描边。

渲染轮廓线

在RTR3中,作者将轮廓线的渲染分成了5类:

  • 基于观察角度和表面法线的轮廓线渲染。这种方法使用视角方向和表面法线的点乘结果来得到轮廓线的信息,
  • 过程式几何轮廓线渲染,使用两个Pass渲染,第一个Pass渲染背面的面片,并用某些技术使其轮廓可见,第二个Pass再正常渲染正面的面片,这种方法的优点在于快速有效。并且适用于绝大多数表面平滑的模型,但它的缺点是不适合类似于立方体这样平整的模型
  • 基于图像处理的轮廓线渲染,这种方法可以适用于任何种类的模型,但是一些深度和法线变化很小的轮廓无法被检测。
  • 基于轮廓边缘检测的轮廓线渲染,上述这些方法的缺点在于无法控制轮廓线的风格渲染,如果我们想要渲染出特殊风格的轮廓线,例如水墨风格等。就需要检测出精确的轮廓边,然后直接渲染它们。检测一条边是否是轮廓边的公式很简单,我们只需要检查和这条边相邻的两个三角形面片是否满足以下条件:
    ( n 0 ⋅ v > 0 ) ≠ ( n 1 ⋅ v > 0 ) (n_0 \cdot v >0) \neq (n_1 \cdot v > 0) (n0v>0)=(n1v>0)

其中 n 0 & n 1 n_0 \& n_1 n0&n1分别代表了两个相邻三角形面片的法向,v是从视角到该边上任意顶点的方向。上述公式的本质在于检测两个相邻的三角形面片是否一个朝向正面,一个朝向背面。我们可以在几何着色器的帮助下实现上面的检测过程。缺点是实现相对复杂,且由于是逐帧单独提取轮廓,所以帧与帧之间会出现跳跃性,会导致动画连贯性的问题。

  • 最后一种方法混合了上述几种的渲染方法,例如先找到精确的轮廓边,把模型和轮廓边渲染到纹理中,再使用图像处理的方法识别出轮廓线,并在图像空间下进行风格化渲染,我们将使用两个Pass——第一个Pass使用轮廓色渲染背面面片,并在视角空间下把模型顶点沿着法线方向向外扩张一段距离,以此让背部轮廓线可见:
    viewPos += viewNormal * _Outline;随后用正面面片遮住不必要显示的背面,保留的部分就是轮廓线了

但是如果直接使用顶点法线进行拓展,对于一些内凹的模型,就可能产生背面面片遮挡正面面片的情况
为了尽可能防止出现这样的情况,在扩张背面顶点之前,我们首先对顶点法线的z分量进行处理,使其等于一个定值,然后把法线归一化之后再对顶点进行扩张,好处在于,拓展后的背面更加扁平化,从而降低了遮挡正面面片的可能:

viewNormal.z = -0.5;
viewNormal = normalize(viewNormal);
viewPos = viewPos + viewNormal * _Outline;

添加高光

卡通的高光往往是模型上一块块分界明显的纯色区域,为了实现这种效果,我们就不能再使用之前学习的光照模型。由于变成一块纯色区域了,因此光照模型显然不合适,我们可以通过阈值比较来判断,小于阈值则反光系数为0,否则为1。

float spec = dot(worldNormal,worldHalfDir);
spec = step(threshold,spec);

step函数用于比较,第一个参数为参考值,第二个参数为待比较的值,若第二个参数大于等于第一个参数则返回1,否则返回0(这个step可以代替if语句判断,只需作为乘积相乘比较结果即可)

spec = lerp(0,1,smoothstep(-w,w,spec - threshold));

这个函数smoothstep,作用是当spec - threshold小于第一个参考值返回0,大于第二个参考值返回1,否则在[0,1]之间进行插值。这样就可以保证边缘处的过渡不会突兀,实现边缘抗锯齿。

实现

Shader "Custom/ToonShading_Copy"
{
    Properties
    {
        _Color("Tint Color",Color) = (1,1,1,1)
        _MainTex("Map Tex",2D)="white"{}
        _RampTex("Ramp Tex",2D) = "grey"{}
        _Outline("Outline",Range(0,1)) = 0.1
        _OutlineColor("Outline Color",Color) = (1,1,1,1)
        _Specular("Specular",Color) = (1,1,1,1)
        _SpecularScale("Specular Scale",Float) =1.0
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }
        CGINCLUDE
        #include "UnityCG.cginc"
        fixed4 _Color, _OutlineColor, _Specular;
        fixed _Outline;
        float _SpecularScale;
        sampler2D _MainTex, _RampTex;
        half4 _MainTex_ST;
        ENDCG
        // 背面渲染一次并根据法线方向挤出顶点
        Pass
        {
            Name "OUTLINE"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                // 此处直接按照法线方向挤出顶点,描边效果稍微不同
                v.vertex.xyz += v.normal * _Outline;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                return fixed4(_OutlineColor.rgb, 1);
            }
            ENDCG
        }
        // 正面渲染
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            Cull Back
            CGPROGRAM
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct v2f
            {
                float4 pos : POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
            };

            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                TRANSFER_SHADOW(o);

                return o;
            }

            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 worldNormalDir = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

                fixed4 c = tex2D(_MainTex, i.uv);
                fixed3 albedo = c.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

                // 计算与阴影方向,并从标准坐标系[-1,1]映射到[0,1]以采样渐变纹理
                fixed diff = dot(worldNormalDir, worldLightDir);
                diff = (diff * 0.5 + 0.5) * atten;
                fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_RampTex, float2(diff, diff)).rgb;
                // 计算高光方向
                fixed spec = dot(worldNormalDir, worldHalfDir);
                //fwidth(v) = abs(ddx(v))+ abs(ddy(v)) 以2x2像素为单位,ddx为右边界-左边界,ddy为下边界-上边界
                // fwidth的返回值表明UV值在该点和临近像素之间的变化,这个值帮助我们判断模糊的大小范围
                // 总之是用于smoothstep对像素进行模糊的
                fixed w = fwidth(spec) * 2.0;
                // smoothstep柔和过渡避免锯齿
                fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(
                    0.0001, _SpecularScale);

                return fixed4(ambient + diffuse + specular, 1.0);
            }
            ENDCG

        }
    }
    FallBack "Diffuse"

}

效果:直接根据法线挤出顶点并描边,可以把面部的轮廓线也描绘出来

在这里插入图片描述

书中对背面面片渲染的代码如下:

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

通过在顶点着色器中将模型顶点转换到view空间下,从而使得描边在观察空间获得最好的效果,并对法线设置z分量后归一化,将顶点沿着法线方向凸出,最终转换到裁剪空间获得描边的效果
在这里插入图片描述
相比之下一些正对视角的顶点边缘是没有描绘的,效果见仁见智吧


素描风格的渲染

在这里插入图片描述

素描风格的渲染本质上是根据光照结果来进行素描纹理权重混合的渲染。这种渲染风格可能一次需要多种不同的纹理贴图,使用笔触越多的纹理来模拟越深的阴影。
在这里插入图片描述

我们将使用六张纹理图来渲染光照以实现上面这种效果

ToonStyle的渲染在现在二次元手游这么多的情况下显得越来越重要了。

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

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

相关文章

2024年5月24日 十二生肖 今日运势

小运播报:2024年5月24日,星期五,农历四月十七 (甲辰年己巳月戊子日),法定工作日。 红榜生肖:龙、牛、猴 需要注意:兔、羊、马 喜神方位:东南方 财神方位:…

10个顶级的论文降重指令,让你的论文降重至1.9%

10个顶级的论文降重指令,本硕博写论文必备! 在ChatGPT4o对话框中输入:写一个Spring BootVue实现的车位管理系统的论文大纲,并对其具体章节进行详细描述。 几小时即可完成一份1万字论文的编写 在GPTS中搜索论文降重,使…

[Redis]基本全局命令

Redis存储方式介绍 在 Redis 中数据是以键值对的凡事存储的,键(Key)和值(Value)是基本的数据存储单元。以下是对 Redis 键值对的详细讲解: 键(Key): 类型:…

论文精读--InstructGPT

模型效果取决于数据效果,但在精细度上控制不够,只是大力出奇迹,这样有很大的问题: (1)数据量太多或者没有这方面的数据,模型学不会怎么办 (2)安全性问题,模…

c语言:利用随机函数产生20个[120, 834] 之间互不相等的随机数, 并利用选择排序法将其从小到大排序后输出(每行输出5个)

利用随机函数产生20个[120, 834] 之间互不相等的随机数&#xff0c; 并利用选择排序法将其从小到大排序后输出&#xff08;每行输出5个&#xff09; 代码如下&#xff1a; #include <stdio.h> #include <time.h> #include <stdlib.h> int shenchen(int a[…

信息系统项目管理师0126:输入(8项目整合管理—8.6管理项目知识—8.6.1输入)

点击查看专栏目录 文章目录 8.6 管理项目知识8.6.1 输入8.6 管理项目知识 管理项目知识是使用现有知识并生成新知识,以实现项目目标并且帮助组织学习的过程。管理项目过程的主要作用: 利用已有的组织知识来创造或改进项目成果;使当前项目创造的知识可用于支持组织运营和未来…

解决Vscode打开新文件会覆盖旧文件

现象&原因 现象&#xff1a;Vscode左侧点击新文件&#xff0c;右侧重用预览编辑器&#xff0c;新文件会替换旧文件原因&#xff1a; 默认单击是预览编辑器显示&#xff0c;双击是保持打开状态 解决方案 以下两种都可以 设置里搜索 Enable Preview 默认是勾选状态&#x…

京东科技市场与平台运营中心PMO负责人徐雪娇受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 京东集团京东科技市场与平台运营中心PMO负责人徐雪娇女士受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“AI数字人项目全过程管理实践分享”。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&…

springboot vue 开源 会员收银系统 (4) 分类及商品模块开发

前言 完整版演示 前面我们对会员系统 门店模块开发的开发 完成了门店的基础管理 并与会员相关联 下面我们将开发门店的分类及商品管理 我们分析以下几个重点 分类可以随时禁用不用单独下架某个商品便于管理商品添加应该有图片上传商品设置会员价和散客价便于营销商品应该参与…

解决updateByExample时属性值异常的问题(部分属性值没有使用占位符?进行占位,而是变成了属性的名称)

目录 场景简介代码片断实体类 报错信息排查原因解决测试过程解决方案 场景简介 1、程序将mybatis框架升级为3.5.9版本后执行updateByExample方法时报错 代码片断 Condition condition new Condition(MbCcsSessionConfig.class); condition.createCriteria().andEqualTo(&quo…

[STM32-HAL库]Flash库-HAL库-复杂数据读写-STM32CUBEMX开发-HAL库开发系列-主控STM32F103C6T6

目录 一、前言 二、实现步骤 1.STM32CUBEMX配置 2.导入Flash库 3.分析地址范围 4.找到可用的地址 5.写入读取普通数据 6.写入读取字符串 6.1 存储相关信息 6.2 存取多个参数 三、总结及源码 一、前言 在面对需要持久化存储的数据时&#xff0c;除了挂载TF卡&#xff0c;我们…

【线段图案】

描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的线段图案。 输入描述&#xff1a; 多组输入&#xff0c;一个整数&#xff08;1~100&#xff09;&#xff0c;表示线段长度&#xff0c;即“*”的数量。 输出描述…

.NET Core Web Api Swagger运行异常

遇到的问题 因为新增了一个控制器方法&#xff0c;从而导致在运行Swagger的时候直接报错&#xff0c;异常如下&#xff1a; SwaggerGeneratorException: Conflicting method/path combination "POST api/UserOperationExample" for actions - WebApi.Controllers.Us…

HCIP-Datacom-ARST自选题库__MPLS简答【4道题】

1.如图所示&#xff0c;R1、R2、R3、R4处于同一个MPLS域&#xff0c;且设备之间采用LDP分配MPLS标签&#xff0c;R4为4.4.4.0/24这条FEC的EgressLSR。若想实现R1访问4.4.4.0/24时&#xff0c;R4不需要查询标签表但能够了解该数据的转发优先级&#xff0c;则R3对于该FEC的出标签…

Linux中vim的基本使用

目录 vim中的三种模式以及基本操作命令模式(默认模式)插入模式底行模式 命令模式下的命令底行模式下的命令 vim是Linux和Unix环境下最基本的文本编辑器&#xff0c;类似于windows上的记事本 vim和Visual studio相比&#xff0c;vim并不集成&#xff0c;vim只能用来写代码 VS把写…

第六节 自动装配源码理解

tips&#xff1a;不同版本代码实现有差异。 前面两章了解的流程&#xff0c;就是 SpringBoot 自动转配的核心。 一、自动装配 1.1 什么是 SpringBoot 自动装配? 自动装配是 Spring 框架用来减少配置的显式需求而引入的一个特性&#xff0c;该特性通过 Autowired或者Resource…

Unity Terrain Adjust插件使用教程

一、Terrain Adjust插件介绍 二、插件下载以及导入 1、官方下载地址&#xff1a;Terrain Adjust 2、积分下载地址&#xff1a;Terrain Adjust 下载好之后&#xff0c;回到Unity当中&#xff0c;导入下载好之后的unitypackage包 三、插件使用 1、在使用之前一定要在场景中新…

KuberSphere 安装kubernates

准备机器 最少3台机器 centos Linux 内核&#xff1a;官方建议 3.10 以上 uname -rcontrol 一台 配置 2c 4g worker 二台 配置 2c 4g 3台设备需要用不同的hostname&#xff0c;需要提前修改 hostnamectl set-hostname <新的主机名> # 修改后执行一下命令刷新一下 su -…

【Android】联系人列表补充

真布局--叠起来垂直管 效果展示 部分代码&#xff08;在activity_main&#xff09;里面 <FrameLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

自动驾驶决策规划——坐标转换

以下内容来自b站up主忠厚老实的老王&#xff0c;视频链接&#xff1a;自动驾驶决策规划算法序章 总纲与大致目录_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1hP4y1p7es/?spm_id_from333.999.0.0&vd_sourced36e625f376908cfa88ef5ecf2fb0ed8侵删。 决策规划算法…