Part1.效果图

Part2.方法简介

渐变纹理是一种可以用来实现卡通渲染效果的技术,其原理十分简单,使用光照模型计算结果,在一个一维的渐变纹理上进行采样

以半兰伯特模型为例,其表达式为 0.5 * ( Normal · LightDirection) + 0.5,可以看到如果光照方向对于某顶点越接近于直射,其半兰伯特模型计算结果越大,越接近1,对应的在渐变纹理上采样得到的颜色结果也会越深,如果把采样纹理离散化,比如下图的模式

(取值越接近0越靠左,越接近1越靠右)

这样以来,连续的光照计算结果,在经过纹理采样后的颜色值将被离散化,从而模拟卡通风格的块状阴影和高光的美术风格

Part3.代码逐段分析

Properties
{_Color ("Color", Color) = (1,1,1,1)_MainTex("Main Tex",2D) = "white"{}  //主纹理_RampTex("Ramp Tex", 2D) = "white" {}  //渐变纹理_Specular("Specular", Color) = (1,1,1,1)_Gloss("Gloss", Range(8.0,256)) = 20
}

在属性块,需要额外定义一个渐变纹理,不要忘了在CG代码片中也要定义相同的变量

struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;
};

在顶点着色的输入结构体中,我们需要获取顶点位置,顶点法向量以及顶点的纹理uv坐标

struct v2f{float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;float2 uv : TEXCOORD2;
};

在顶点着色器到片元着色器的通信结构体中,我们需要获取裁剪空间下的顶点位置,世界空间下的顶点位置和法向量,以及经过变换后的纹理uv

v2f vert(a2v v){v2f o;//将顶点从模型空间变换到裁剪空间o.pos = UnityObjectToClipPos(v.vertex); //使用内置函数和变换矩阵,将模型空间的法向量和顶点位置变换到世界空间,用于片元着色器的光照计算o.worldNormal  = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//对纹理uv坐标进行尺度变换和偏置o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;
}

顶点着色器的工作相对简单,我们只需要完成一些常规工作即可

fixed4 frag(v2f i) : SV_Target{fixed3 worldNormal = normalize(i.worldNormal);//通过Unity内置函数,获取世界空间下的光照向量,并单位化fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));//对主纹理进行采样,获取像素点颜色fixed3 albedo = tex2D(_MainTex, i.uv)* _Color.rgb;//计算环境光fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;//根据半兰伯特模型计算漫反射项fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;//根据半兰伯特模型计算结果,在渐变纹理上采样,得到被离散化的漫反射颜色fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert,halfLambert)).rgb * albedo * halfLambert;//后面进行正常的光照计算,输出最终颜色,注释从略,可参考之前笔记fixed3 diffuse = _LightColor0.rgb * diffuseColor;fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));fixed3 halfDir = normalize(worldLightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb + pow(max(0,dot(worldNormal,halfDir)),_Gloss);return fixed4(ambient + diffuse + specular, 1.0);
}

在片元着色器中,我们首先需要处理主纹理,即对主纹理采样获得颜色,并用该颜色计算环境光

接下来我们需要获取像素点处半兰伯特模型的光照结果,并根据该结果对渐变纹理进行采样,注意渐变纹理虽然是一维的,但仍然是以2维存储的,其中维度y上的取值均相同,因此我们需要使用一个二维点进行采样,其中两个维度的取值相同即可,因为y轴上没有意义

采样完后,直接与之前计算的主纹理颜色相乘,再与半兰伯特模型结果相乘即可得到离散化的漫反射光照颜色

接下来的工作十分常规化,正常按照Blinn-Phong模型计算高光,输出最终光照结果即可

Part4.一些问题

Q1.为什么要使用半兰伯特模型而不是兰伯特模型?

因为兰伯特模型会出现死黑问题,对于卡通渲染这种颜色鲜艳的风格,死黑问题十分致命

Q2.为什么渐变纹理是一维的,却需要二维的UV坐标去采样?

因为渐变纹理虽然是一维的,但还是按照二维来存储的(可参考Part2给出的样例渐变纹理),但Y维度上取值是完全相同的,因此在Y维度上的采样实际上是没有意义的,代码中简单使用UV相同的采样点采样即可

Part5.完整代码

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'Shader "Chapter7/RampGirl"
{Properties{_Color ("Color", Color) = (1,1,1,1)_MainTex("Main Tex",2D) = "white"{}_RampTex("Ramp Tex", 2D) = "white" {} _Specular("Specular", Color) = (1,1,1,1)_Gloss("Gloss", Range(8.0,256)) = 20}SubShader{Pass{Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Color;sampler2D _MainTex;float4 _MainTex_ST;sampler2D _RampTex;float4 _RampTex_ST;fixed4 _Specular;float _Gloss;struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;};struct v2f{float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;float2 uv : TEXCOORD2;};v2f vert(a2v v){v2f o;o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal  = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}fixed4 frag(v2f i) : SV_Target{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 albedo = tex2D(_MainTex, i.uv)* _Color.rgb;fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert,halfLambert)).rgb * albedo * halfLambert;fixed3 diffuse = _LightColor0.rgb * diffuseColor;fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));fixed3 halfDir = normalize(worldLightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb + pow(max(0,dot(worldNormal,halfDir)),_Gloss);return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Specular"
}

「UnityShader笔记」08. 基础卡通渲染—渐变纹理相关推荐

  1. 字符串输出为什么第一个没了_「课堂笔记」Python基础语法:变量和输入输出

    学习了视频课程<财务Python基础--财务人的第一个Python程序|输入输出>,小编特为大家整理了本节内容的文字版笔记,一起来温故知新吧~~ 变 量 一.为什么要引入变量? 在信息计算 ...

  2. 「划线高亮」和「插入笔记」—— 不止是前端知识点

    如今前端领域:serverless,low code,全栈化等概念遍布漫天.开发者们热衷于讨论「如何把前端格局做大」,「如何将高高在上的概念落地」.此时,你有没有感受到「还不知道发展方向到底是什么,就 ...

  3. 「学习笔记」移动Web开发之rem适配布局10

    「学习笔记」移动Web开发之rem适配布局10 一.rem单位 1.1 rem 单位 二.媒体查询 2.1 什么是媒体查询 2.2 语法规范 2.2.1 mediatype 查询类型 2.2.2 关键 ...

  4. 「学习笔记」品优购项目-上(页面公共部分 )

    「学习笔记」品优购项目-上 品优购项目-上 目标 品优购项目规划 网站制作流程 品优购项目介绍 品优购项目的学习目的 开发工具以及技术栈 开发工具 技术栈 品优购项目搭建工作 创建的文件夹如下(称为项 ...

  5. 「学习笔记」黑马面面布局开发

    「学习笔记」黑马面面布局开发 黑马面面布局开发 一.目的 1.1 技术方案 1.2 代码规范 1.2 目录规范 二.流程开发 2.1 蓝湖/摹客协作平台 2.2 适配方案 2.3 初始化文件 2.4 ...

  6. 「学习笔记」多项式的蛇皮操作

    文章目录 「学习笔记」多项式的蛇皮操作 前置知识 趋近 自然常数 对数 逆元 导函数 牛顿迭代与泰勒公式 不定积分与定积分 多项式乘法 多项式求逆元 多项式除法/取模 多项式牛顿迭代法 多项式开根 「 ...

  7. 「学习笔记」移动Web开发之flex布局9

    「学习笔记」移动Web开发之flex布局9 一.flex布局体验 1.1 传统布局与flex布局 1.2 初体验 二.flex布局原理 2.1 布局原理 三.flex布局父项常见属性 3.1 常见父项 ...

  8. linux加权_「学员笔记」LINUX随堂笔记(十一):LVS负载均衡群集

    第12章 LVS负载均衡群集 一.群集概述 1.1 群集的类型 无论是哪种群集,都至少包括两台节点服务器,而对外表现为一个整体,只提供一个访问入口(域名或IP地址),相当于一台大型计算机,根据群集所提 ...

  9. 「学习笔记」HTML5CSS3提高6(上)

    「学习笔记」HTML5&CSS3提高6(上) HTML5新特性 概述 语义化标签 (★★) 多媒体标签 视频标签- video(★★★) 基本使用 兼容写法 video 常用属性 音频标签- ...

最新文章

  1. 在Eclipse中导入Java程序
  2. JavaScript判断图片是否加载完成的三种方式
  3. 保镖机器人作文_我的保镖作文500字
  4. dueling dqn 和 double dqn_强化学习(十一)--DQN三个经典的变种
  5. Hive安装中遇到过的坑
  6. python cmdb_Django之入门 CMDB系统 (一) 基础环境
  7. 金蝶eas怎么引出凭证_金蝶专业版如何引入引出凭证
  8. 基于BP神经网络的轨迹跟踪matlab程序
  9. java爬取网页上qq号,邮箱号等
  10. Cocos Creator实现FPS经典瞄准镜+监视器
  11. 中国有多少家银行?(最全名单统计)
  12. 怎么查看计算机簇大小,分区格式与簇的大小讲解
  13. 【机器学习基础】样本类别不平衡的解决办法
  14. 喝酸奶八大误区[zt]
  15. python文件及目录操作(copytree)
  16. VC++ 动态检测串口的热插拔(一)通过遍历实现
  17. 码垛机器人模型图纸分享(附下载)
  18. np.random.seed()函数
  19. 畜牧养殖物联网:物联网在畜牧养殖中的应用
  20. OpenGL环境下PLY三维模型的读入与显示

热门文章

  1. IP地址-B类网络子网划分
  2. PHP与JAVA循环性能比较
  3. 战争磨盘十五:一擒孙有才
  4. 使用python 解罗马数字转整数
  5. 【LeetCode】1697. 检查边长度限制的路径是否存在
  6. 各种动态网页技术的特点与区别
  7. CentOS7或Ubuntu 配置阿里云yum源
  8. css中什么是伪类选择器?伪类选择器的简介
  9. PS中选中图层以及查看单个图层的方法
  10. ios程序员要如何面试