Chapter14 非真实感渲染 NPR——Shader入门精要

news2024/12/23 16:22:46

Chapter14 非真实感渲染 NPR

  • 一、卡通风格渲染
    • 1.渲染轮廓线
    • 2.添加高光
    • 3.ToonShadingMat
  • 二、素描风格渲染

一、卡通风格渲染

  • 特点:物体被黑色线条描边,分明的明暗变化等
  • 方法:其中之一就是 基于色调的着色技术

1.渲染轮廓线

  • 方法
    • 基于观察角度和表面法线的轮廓线渲染:使用视角方向和表面法线的点乘结果来得到轮廓线信息,快但效果不好
    • 过程式几何轮廓线渲染:使用两个Pass,第一个Pass渲染背面的面片,使用某些技术让其轮廓可见,第二个Pass再渲染正面面片。快速,但不适合立方体这样平整的模型
    • 基于图像处理的轮廓线渲染:12章13章介绍的,适用于所有类型的模型,但一些深度和法线变化小的就不太行(类似于桌上的纸张)
    • 基于轮廓边检测的轮廓线渲染:可以控制轮廓线的风格渲染,动画连贯性差
      • 检测一条边是否为轮廓线,只需检测这条边相邻的两个三角形面片是否满足: ( 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_{0} n0 n 1 n_{1} n1表示两个三角形面片法向, v v v是从视角到该边上任意顶点的方向
      • 本质在于检查两个三角形是否一个朝正面一个朝背面
    • 混合以上方法的轮廓线渲染
  • 本节使用: 过程式几何轮廓线渲染
    • 第一个Pass中会使用轮廓线颜色渲染整个背面面片,并在是视角空间下把模型顶点 沿着法线方向向外扩张一段距离 —— 让背部轮廓线可见 viewPos = viewPos + viewNormal * _Outline
    • 但是对于内凹的模型,就可能会有背部轮廓遮挡正面的情况
    • 在扩张之前,先对顶点法线z分量进行处理,使它们等于一个定值,再把法线归一化后再扩张 —— 背面更加扁平化,降低了遮挡正面的可能性
viewNormal.z = -0.5;
viewNormal = normalize(viewNormal);
viewPos = viewPos + viewNormal * _Outline;

2.添加高光

  • 卡通风格的高光是一块纯色区域
  • Blinn-Phong 模型实现高光:float spec = pow(max(0,dot(normal, halfDir)),_Gloss);
  • 卡通渲染:要把 n o r m a l ⋅ h a l f D i r normal \cdot halfDir normalhalfDir 与阈值进行比较,小于的高光系数设为0,否则返回1。利用CG里的step函数实现与阈值比较的目的,参数1是参考值,参数2是待比较值,若参数2 > 参数1,就返回1。这种方法可能会有锯齿边缘
float spec = dot(worldNormal, worldHalfDir);
spec = step(threshold,spec);
  • 在上方法的基础上进行抗锯齿平滑处理。
    • 使用了CG的smoothstep函数
    • w是一个很小的值,当spec-threshold < -w 返回0,spec-threshold > w 时返回1,否则在0-1之间插值,实现平滑效果
float spec = dot(worldNormal, worldHalfDir);
spec = lerp(0,1,smoothstep(-w, w, spec-threshold));

3.ToonShadingMat

Properties
{
    _Color ("Color", Color) = (1,1,1,1)
	_MainTex ("Main Tex", 2D) = "white" {}
    _Ramp("Ramp Texture",2D) = "white"{}
    _Outline("Outline" ,Range(0,1)) = 0.1
    _OutlineColor("Outline Color",Color)= (0,0,0,1)
    _Specular("Specular",Color) = (1,1,1,1)
    _SpecularScale("Specular Scale",Range(0,0.1)) = 0.01
}
Pass
{
    NAME "OUTLINE"
    
    Cull Front 
    CGPROGRAM
    #pragma vertex vert 
    #pragma fragment frag

    #include "UnityCG.cginc"
    float _Outline;
    fixed4 _OutlineColor;

    struct a2v{
        float4 vertex: POSITION;
        float3 normal:NORMAL;
    };
    struct v2f{
        float4 pos : SV_POSITION;
    };
  • 这个pass只渲染背面的三角形面片,设置Cull Front 把正面三角形剔除
 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;
 }

 float4 frag(v2f i) : SV_Target
 {
     return float4(_OutlineColor.rgb, 1);
 }
  • 在顶点着色器中,先把顶点和法线变换到视角空间下,这是为了让描边可以在观察空间达到最好的效果
  • 把法线z分量设为定量
  • 把法线归一化后再向外扩张,得到扩张后的顶点坐标 pos
  • 再把顶点变换到裁剪空间
  • 片元着色器只需要用轮廓线颜色渲染整个背面就好
 Pass{
     Tags {"LightMode" = "ForwardBase"}

     Cull Back 
	......
	  struct a2v {
		float4 vertex : POSITION;
		float3 normal : NORMAL;
		float4 texcoord : TEXCOORD0;
		float4 tangent : TANGENT;
	}; 

 struct v2f{
     float4 pos : POSITION;
     float2 uv : TEXCOORD0;
     float3 worldNormal : TEXCOORD1;
     float3 worldPos : TEXCOORD2;
     SHADOW_COORDS(3)
 };
  • #pragma multi_compile_fwdbase:用途是告诉Unity编译器为不同的光照模型和阴影类型生成多个变体(variants)的Shader,使得Shader能够更好地适应前向渲染路径下的不同光照和阴影条件
float4 frag(v2f i) : SV_Target 
{
    fixed3 worldNormal = 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);

    fixed diff = dot(worldNormal, worldLightDir);
    diff = (diff * 0.5 +0.5) * atten;

    fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff,diff)).rgb;

    fixed spec = dot(worldNormal, worldHalfDir);
    fixed w = fwidth(spec) *2.0;
    fixed3 specular = _Specular.rgb * lerp(0,1,smoothstep(-w,w,spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);

    return fixed4(ambient + diffuse + specular, 1.0);
}
  • 首先计算了光照模型中所需的各个方向矢量,并进行归一化处理
  • 计算了材质反射率 albedo(基础颜色)和环境光照 ambient
  • UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);:计算当前世界坐标下的阴影值
  • 又计算了 HalfLambert 漫反射系数 diff,并与阴影值 atten 相乘
  • 用漫反射系数对渐变纹理 _Ramp 采样
  • 又计算了高光反射,与14.1.2方法一致,使用 fwidth 函数对高光区域的边界进行抗锯齿处理
  • 计算高光时还使用了step(0.0001, _SpecularScale),因为为了在_SpecularScale 为0时,可以完全消除高光反射的光照
    在这里插入图片描述

二、素描风格渲染

  • 本节中不考虑多级渐变纹理的生成,直接使用6张像素纹理进行渲染
  • 首先在顶点着色器阶段计算 逐顶点光照 ,根据光照结果来决定6张纹理的权重,并传递给片元着色器
  • 在片元着色器中根据权重来混合6张图的采样结果

    Properties
    {
       _Color ("Color Tint", Color) = (1,1,1,1)
       _TileFactor ("Tile Factor",Float) = 1
       _Outline ("Outline", Range(0,1)) = 0.1
       _Hatch0 ("Hatch 0",2D) = "white"{}
       _Hatch1 ("Hatch 1",2D) = "white"{}
       _Hatch2 ("Hatch 2",2D) = "white"{}
       _Hatch3 ("Hatch 3",2D) = "white"{}
       _Hatch4 ("Hatch 4",2D) = "white"{}
       _Hatch5 ("Hatch 5",2D) = "white"{}
    }
  • _Color 用于控制模型的颜色
  • _TileFactor 是纹理的平铺系数,数越大,模型上的线条越密
  • _Hatch0-_Hatch5 是渲染时使用的六张纹理
 SubShader
 {
     Tags { "RenderType"="Opaque" "Queue" = "Geometry"}
     UsePass "Custom/Chapter14-ToonShading/OUTLINE"
  • 由于需要描边,就使用第一节的描边shader
 struct a2v{
     float4 vertex : POSITION;
     float3 normal : NORMAL;
     float4 tangent : TANGENT;
     float2 texcoord : TEXCOORD0;
 };
 struct v2f{
     float4 pos : SV_POSITION;
     float2 uv : TEXCOORD0;
     fixed3 hatchWeights0 : TEXCOORD1;
     fixed3 hatchWeights1 : TEXCOORD2;
     float3 worldPos : TEXCOORD3;
     SHADOW_COORDS(4)
 };
  • 由于声明了六张纹理,所以需要6个混合权重,所以存储在两个fixed3 变量 hatchWeights0 和 hatchWeights1 中
  • 为了添加阴影效果,还声明了 worldPos 变量,并使用 SHADOW_COORDS(4) 声明了阴影采样坐标
v2f vert(a2v v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord.xy *_TileFactor;

    fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    fixed diff = max(0,dot(worldLightDir,worldNormal));

    o.hatchWeights0 = fixed3(0,0,0);
    o.hatchWeights1 = fixed3(0,0,0);

    float hatchFactor = diff * 7.0;

    if (hatchFactor > 6.0) {
		// Pure white, do nothing
	} else if (hatchFactor > 5.0) {
		o.hatchWeights0.x = hatchFactor - 5.0;
	} else if (hatchFactor > 4.0) {
		o.hatchWeights0.x = hatchFactor - 4.0;
		o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
	} else if (hatchFactor > 3.0) {
		o.hatchWeights0.y = hatchFactor - 3.0;
		o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
	} else if (hatchFactor > 2.0) {
		o.hatchWeights0.z = hatchFactor - 2.0;
		o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
	} else if (hatchFactor > 1.0) {
		o.hatchWeights1.x = hatchFactor - 1.0;
		o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
	} else {
		o.hatchWeights1.y = hatchFactor;
		o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
	}
	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
	
	TRANSFER_SHADOW(o);
	
	return o; 
}
  • 先对顶点进行基本坐标变换
  • 使用_TileFactor 得到纹理采样坐标
  • 在计算6张纹理的混合权重之前,要先进行 逐顶点光照
  • 使用世界空间下的光照方向和法线方向得到漫反射系数 diff
  • 把diff 缩放到 [0,7]之间,得到 hatchFactor
  • 把[0,7] 区间均匀划分为7个子区间,通过判断 hatchFactor 所处的子区间来计算对应的纹理权重
fixed4 frag(v2f i) : SV_Target 
{
    fixed4 hatchTex0 = tex2D(_Hatch0,i.uv) * i.hatchWeights0.x;
    fixed4 hatchTex1 = tex2D(_Hatch1,i.uv) * i.hatchWeights0.y;
    fixed4 hatchTex2 = tex2D(_Hatch2,i.uv) * i.hatchWeights0.z;
    fixed4 hatchTex3 = tex2D(_Hatch3,i.uv) * i.hatchWeights1.x;
    fixed4 hatchTex4 = tex2D(_Hatch4,i.uv) * i.hatchWeights1.y;
    fixed4 hatchTex5 = tex2D(_Hatch5,i.uv) * i.hatchWeights1.z;

   fixed4 whiteColor = fixed4(1,1,1,1) * (1-i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z-i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);
   fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
   UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

   return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
}
  • 得到每张图的权重后,就可以对其进行纹理映射,得到采样颜色
  • 还计算了留白的权重 (1-i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z-i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z)
    在这里插入图片描述

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

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

相关文章

零基础入门鸿蒙开发 HarmonyOS NEXT星河版开发学习

今天开始带大家零基础入门鸿蒙开发&#xff0c;也就是你没有任何编程基础的情况下就可以跟着石头哥零基础学习鸿蒙开发。 目录 一&#xff0c;为什么要学习鸿蒙 1-1&#xff0c;鸿蒙介绍 1-2&#xff0c;为什么要学习鸿蒙 1-3&#xff0c;鸿蒙各个版本介绍 1-4&#xff0…

MYSQL2

1.建库以及建表&#xff1a; mysql> create database mydb8_woker; mysql> use mydb8_woker; mysql> create table t_worker( -> department_id int(11) not null comment 部门号, -> worker_id int(11) primary key not null comment 职工号, -&…

Java中的Heap(堆)(如果想知道Java中有关堆的知识点,那么只看这一篇就足够了!)

前言&#xff1a;&#xff08;Heap&#xff09;是一种特殊的完全二叉树&#xff0c;它在诸多算法中有着广泛的应用&#xff0c;本文将详细介绍Java中的堆。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 先让我们看一下本文大…

《Milvus Cloud向量数据库指南》——开源许可证的开放度:塑造AI开发合作与创新的双刃剑

在人工智能(AI)技术日新月异的今天,开源软件作为推动技术创新的重要力量,其许可证的开放度成为了影响AI开发合作、创新模式乃至整个行业生态的关键因素。不同的开源许可证模型,以其各自独特的开放程度,不仅决定了软件项目的可访问性和可定制性,还深刻影响着AI领域内的合…

springboot 实体类加注解校验入参数据

导入的是springboot自身的依赖包 import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid;

R语言进行K折交叉验证问题

在使用R语言进行模型参数评估优化时候&#xff0c;会使用K折交叉验证&#xff0c;其中会遇到各种各样问题&#xff1a; 错误: C5.0 models require a factor outcome > (1-mean(E0));(1-mean(E1)) [1] 1 [1] 1 报错说明C5.0模型需要因子变量输出&#xff0c;源代码如下&am…

还在为电脑录屏困扰吗?试试这4个方法,瞬间解决

现在很多人用手机进行日常操作都知道录屏的功能怎么操作&#xff0c;但是电脑录屏怎么录呢&#xff1f;如果你需要使用电脑进行录屏操作的时候就可以看看这篇文章。 1.福晰录屏大师 这个工具是一个专业的录屏软件。可以控制录制的区域范围&#xff0c;也能控制音频来源&#…

鸿蒙仓颉语言【匹配match】

模式匹配match match特性是现代编程语言中常见的特性&#xff0c;它们在不同的编程语言中有类似的概念和语法&#xff0c;但在细节上可能有一些差异。它们都可以提高代码的灵活性和可重用性&#xff0c;但用法和语法可能会因编程语言而异。 仓颉的match 支持通过箭头函数直接…

python用selenium网页模拟时xpath无法定位元素解决方法2

有时我们在使用python selenium xpath时&#xff0c;无法定位元素&#xff0c;红字显示no such element。上一篇文章写了1种情况&#xff0c;是包含iframe的&#xff0c;详见https://blog.csdn.net/Sixth5/article/details/140342929。 本篇写第2种情况&#xff0c;就是xpath定…

怎样对 PostgreSQL 中的慢查询进行分析和优化?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样对 PostgreSQL 中的慢查询进行分析和优化&#xff1f;一、理解慢查询的危害二、找出慢查询&#x…

恶补,先验分布,后验分布 ,似然估计

恶补&#xff0c;打一遍增加印象 先验分布后验分布&#xff0c;似然估计 声明&#xff1a;仅记录个人学习&#xff0c;并无其他用途。 先验分布 后验分布&#xff0c; 似然估计 隔壁小哥的故事&#xff1a; 隔壁小哥要去15公里外的一个公园里玩&#xff0c;小哥可以选择步行…

使用 Elasticsearch 和 OpenAI 为你的客户成功应用程序构建对话式搜索

作者&#xff1a;来自 Elastic Lionel Palacin 在此博客中&#xff0c;我们将探讨如何通过利用大型语言模型 (LLM) 和检索增强生成 (RAG) 等技术实施对话式搜索来增强你的客户成功应用程序。 你将了解对话式搜索在客户成功应用程序环境中的优势&#xff0c;以及如何使用 Elast…

复制配置,多个端口号一起开启。

选择 输入&#xff1a;-Dserver.port8082

安卓原生聊天面板开发(一)整体规划

系列文章 安卓原生聊天面板开发&#xff08;一&#xff09;整体规划 安卓原生聊天面板开发&#xff08;二&#xff09;emoji功能实现 安卓原生聊天面板开发&#xff08;三&#xff09;录音交互实现 安卓原生聊天面板开发&#xff08;四&#xff09;整体交互实现 背景 产品喝…

Android 小白菜鸟从入门到精通教程

前言 Android一词最早出现于法国作家利尔亚当&#xff08;Auguste Villiers de l’Isle-Adam&#xff09;在1886年发表的科幻小说《未来的夏娃》&#xff08;L’ve future&#xff09;中。他将外表像人的机器起名为Android。从初学者的角度出发&#xff0c;通过通俗易懂的语言…

记录通过SSH连接Linux(VM)过程

记录通过SSH连接Linux&#xff08;VM&#xff09;过程 打开VM的虚拟网络编辑器&#xff0c; 点击NAT 设置&#xff0c;记录一下子网和网关 进入虚拟机&#xff0c;输入&#xff1a;vim /etc/sysconfig/network-scripts/ifcfg-ens*; 网关改成和上面一样的网关&#xff0c;IP…

Zabbix介绍和架构

目录 一.Zabbix简介 1.为什么需要监控 2.需要监控什么 3.常见的监控工具 4.Zabbix使用场景及系统概述 5.Zabbix 架构 6.Zabbix工作流程 7.Zabbix 术语 二. 部署安装zabbix 三.zabbix 配置文件 一.Zabbix简介 1.为什么需要监控 运维行业有句话:“无监控、不运维”&am…

朋友圈运营分享干货

朋友圈运营技巧对于提升个人或品牌的曝光度、增强用户粘性以及实现营销目标至关重要。 本篇是一些清晰、实用的朋友圈运营技巧~ 目标定位 明确目标群体&#xff1a;了解目标用户的年龄、兴趣、需求等特征以便精准定位内容。 竞争对手分析&#xff1a;观察和分析竞争对手的运…

echarts图表

记一个简单的关于轴线和toolip图表配置 getEcharts(xAxisData, analysisData) {// console.log(analysisData,"数据");var chart this.$echarts.init(this.$refs.classEcharts)let option {tooltip: {trigger: axis,className: echarts-tooltip,backgroundColor:…