1 非真实渲染

法线贴图和凹凸映射中讲述了普通光照的渲染原理,实现的效果比较贴近真实世界(照相写实主义,Photorealism),非真实渲染(Non-Photorealism Rendering,NPR)在照相写实主义的基础上添加了一些风格处理,如:卡通、水彩、素描等风格。

​ 本文完整资源见→Unity3D素描特效

2 素描特效原理

​ 素描特效不直接渲染漫反射效果,而是通过线条的疏密程度表现漫反射效果,即:较亮处线条稀疏,较暗处线条密集。

​ 线条采样方法:先计算顶点对应的漫反射强度,假设为 diffuse,再根据 diffuse 所处的区间在多个素描纹理(如下)中采样。

​ diffuse 计算如下,其中 normal 为顶点对应的单位法线向量,lightDir 为顶点指向光源的单位方向向量。

float diffuse = max(0, dot(normal, lightDir));

​ 为方便划分区间,我们将 diffuse 乘以 7(6 个素描纹理 + 空白纹理),记为 factor,即 factor = diffuse * 7,我们将 factor 均匀划分 7 个区间,假设 factor 值对应的空白纹理和 line1 ~ line6 纹理的权值分别为 w0 ~ w6,则 w0 ~ w6 的计算如下:

fixed w0 = 1; // 白色纹理权值
fixed w1 = 0, w2 = 0, w3 = 0, w4 = 0, w5 = 0, w6 = 0; // line_1~line_6纹理权值
if (factor > 6) { // 区间: (6, 7]
return; // 白色, 直接跳出
} else if (factor > 5) { // 区间: (5, 6]
w1 = factor - 5;
} else if (factor > 4) { // 区间: (4, 5]
w1 = factor - 4;
w2 = 1 - w1;
} else if (factor > 3) { // 区间: (3, 4]
w2 = factor - 3;
w3 = 1 - w2;
} else if (factor > 2) { // 区间: (2, 3]
w3 = factor - 2;
w4 = 1 - w3;
} else if (factor > 1) { // 区间: (1, 2]
w4 = factor - 1;
w5 = 1 - w4;
} else { // 区间: [0, 1]
w5 = factor;
w6 = 1 - w5;
}
w0 = 1 - w1 - w2 - w3 - w4 - w5 - w6;

​ 根据 w0 ~ w6 对白色纹理和 line1 ~ line6 纹理进行加权求和,得到顶点对应的素描颜色。

3 素描特效实现

​ SketchEffect.cs

using UnityEngine;

[DisallowMultipleComponent] // 不允许在同一对象上挂载多个该组件
public class SketchEffect : MonoBehaviour {
private Material sketchMat; // 素描材质 private void Awake() {
sketchMat = Resources.Load<Material>("Sketch/Materials/SketchMat");
} private void OnEnable() {
Renderer[] renderers = GetComponentsInChildren<Renderer>();
foreach (var renderer in renderers) { // 将rneder里的所有材质球都替换为sketchMat材质
Material[] materials = new Material[renderer.sharedMaterials.Length];
for (int i = 0; i < materials.Length; i++) {
materials[i] = sketchMat;
}
renderer.materials = materials;
}
}
}

​ 说明:SketchEffect 脚本组件挂在需要渲染素描特效的对象上。

​ SketchEffect.shader

Shader "MyShader/SketchEffect" {
Properties {
_Color ("Color", Color) = (1, 1, 1, 1) // 背景颜色
_Tilling ("Tilling", Float) = 1 // 纹理缩放, 值越大线条越密集
_Line1 ("Line 1", 2D) = "white" {} // 素描纹理1
_Line2 ("Line 2", 2D) = "white" {} // 素描纹理2
_Line3 ("Line 3", 2D) = "white" {} // 素描纹理3
_Line4 ("Line 4", 2D) = "white" {} // 素描纹理4
_Line5 ("Line 5", 2D) = "white" {} // 素描纹理5
_Line6 ("Line 6", 2D) = "white" {} // 素描纹理6
} SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"} Pass {
Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" fixed4 _Color; // 背景颜色
float _Tilling; // 纹理缩放, 值越大线条越密集
sampler2D _Line1; // 素描纹理1
sampler2D _Line2; // 素描纹理2
sampler2D _Line3; // 素描纹理3
sampler2D _Line4; // 素描纹理4
sampler2D _Line5; // 素描纹理5
sampler2D _Line6; // 素描纹理6 struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
fixed4 w1 : TEXCOORD1; // 前三张素描纹理的权值(w维存储白色)
fixed3 w2 : TEXCOORD2; // 后三张素描纹理的权值
}; void getLineWeights(float diffuse, out fixed4 w1, out fixed3 w2) { // 根据漫反射值获取6张素描纹理的权重
float factor = diffuse * 7.0;
w1 = fixed4(0, 0, 0, 1);
w2 = fixed3(0, 0, 0);
if (factor > 6) { // 区间: (6, 7]
return; // 白色, 直接跳出
} else if (factor > 5) { // 区间: (5, 6]
w1.x = factor - 5;
} else if (factor > 4) { // 区间: (4, 5]
w1.x = factor - 4;
w1.y = 1 - w1.x;
} else if (factor > 3) { // 区间: (3, 4]
w1.y = factor - 3;
w1.z = 1 - w1.y;
} else if (factor > 2) { // 区间: (2, 3]
w1.z = factor - 2;
w2.x = 1 - w1.z;
} else if (factor > 1) { // 区间: (1, 2]
w2.x = factor - 1;
w2.y = 1 - w2.x;
} else { // 区间: [0, 1]
w2.y = factor;
w2.z = 1 - w2.y;
}
w1.w = 1 - w1.x - w1.y - w1.z - w2.x - w2.y - w2.z; // w维存储白色
} v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
o.uv = v.texcoord * _Tilling; // 对uv坐标进行缩放, 用于调整素描图像的稀疏和宽窄程度
fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex)); // 计算世界空间中顶点指向光源的向量
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量
fixed diffuse = max(0, dot(worldLightDir, worldNormal)); // 计算漫反射值
getLineWeights(diffuse, o.w1, o.w2); // 根据漫反射值获取6张素描纹理的权重
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed4 lineTex1 = tex2D(_Line1, i.uv) * i.w1.x;
fixed4 lineTex2 = tex2D(_Line2, i.uv) * i.w1.y;
fixed4 lineTex3 = tex2D(_Line3, i.uv) * i.w1.z;
fixed4 lineTex4 = tex2D(_Line4, i.uv) * i.w2.x;
fixed4 lineTex5 = tex2D(_Line5, i.uv) * i.w2.y;
fixed4 lineTex6 = tex2D(_Line6, i.uv) * i.w2.z;
fixed4 whiteColor = fixed4(1, 1, 1, 1) * i.w1.w;
fixed4 sketchColor = lineTex1 + lineTex2 + lineTex3 + lineTex4 + lineTex5 + lineTex6 + whiteColor;
return fixed4(sketchColor.rgb * _Color.rgb, 1.0);
} ENDCG
}
} FallBack "Diffuse"
}

​ 说明:在 Assets/Resources/Sketch/Materials 目录下创建 Material,重命名为 SketchMat ,将 SketchEffect.shader 赋给 SketchMat 材质,并将 line1 ~ line6 纹理拖拽到 SketchMat 材质中对应位置,调整 Tilling 属性的值。

4 运行效果

1)原图

2)素描特效

​ 声明:本文转自【Unity3D】素描特效

【Unity3D】素描特效的更多相关文章

  1. 极客标签互动课程系列 - Javascript生成SVG动画素描特效

    课程描写叙述:在这个课程中,我们将介绍SVG.而且介绍怎样使用javascript来控制SVG生成素描动画效果 课程地址:http://www.gbtags.com/gb/gbliblist/21.h ...

  2. Unity3d粒子特效:制作火焰效果

    效果 分析 真实的火焰效果,通常包括:火.火光.火星等组成部分,而火焰对周围环境的烘焙,可以通过灯光实现,如点光源. 针对火焰组成部分,我们可以创建对应的粒子系统组件,实现相应的效果,如下图所示: 1 ...

  3. 【Unity3D】基于粒子系统实现烟花特效

    1 需求实现 ​ 粒子系统ParticleSystem 中介绍了粒子初始化.粒子发射.发射器形状.渲染器.碰撞.子发射器.拖尾等粒子系统的基本用法,本节将基于粒子系统实现烟花特效. ​ 实现需求如下( ...

  4. PS 滤镜——素描算法(二)

    利用另外一种算法完成素描特效的生成. %%% Sketch clc; clear all; Image=imread('4.jpg'); Image=double(Image); [row,col,l ...

  5. Python: PS 滤镜--素描

    本文用 Python 实现 PS 滤镜中的素描特效,具体的算法原理和效果可以参考之前的博客: http://blog.csdn.net/matrix_space/article/details/386 ...

  6. Python图像处理丨5种图像处理特效

    摘要:本篇文章主要讲解了图像常见的特效处理,从处理效果图.算法原理.代码实现三个步骤进行详细讲解,涉及图像素描特效.怀旧特效.光照特效.流年特效.图像滤镜等. 本文分享自华为云社区<[Pytho ...

  7. Unity塔防游戏开发

    Unity3D塔防开发流程 配置环境及场景搭建编程语言:C#,略懂些许设计模式,如果不了解设计模式,BUG More开发工具:Unity3D编辑器.Visual Studio编译器开发建议:了解Uni ...

  8. Unity3D特效-场景淡入淡出

    最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾 ...

  9. unity3d 游戏插件 溶解特效插件 - Dissolve Shader

    unity3d 游戏插件 溶解特效插件 - Dissolve Shader   链接: https://pan.baidu.com/s/1hr7w39U 密码: 3ed2

  10. Unity3D之挥动武器产生的剑痕特效

    网维教程网 观看很多其它教程 眼下已知3种方法能够做这样的剑痕特效 1.尾随特效 2.程序实现动态面来处理剑痕动画. 3.美术实现剑痕动画,直接坐在模型动画里面 (由于我不会美术所以这个忽略 嘿嘿) ...

随机推荐

  1. 2020-09-23:TCP头部信息有哪些?

    福哥答案2020-09-23:#福大大架构师每日一题# 福哥口诀法:T源目序缺首保 紧确推和复同终 窗校紧选数(TCP格式:源端口,目的端口,序号,确认号,首部长度,保留,紧急位URG,确认位ACK, ...

  2. 2022-04-23:给定一个长度为4的整数数组 cards 。你有 4 张卡片,每张卡片上都包含一个范围在 [1,9] 的数字。您应该使用运算符 [‘+‘, ‘-‘, ‘*‘, ‘/‘] 和括号 ‘

    2022-04-23:给定一个长度为4的整数数组 cards .你有 4 张卡片,每张卡片上都包含一个范围在 [1,9] 的数字.您应该使用运算符 ['+', '-', '*', '/'] 和括号 ' ...

  3. 2021-02-18:给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文。arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来。返回需要至少多少张贴纸可以完成这个任务。例子:str= "babac",arr = {"ba","c","abcd"}。a + ba + c 3 abcd + abcd 2 abcd+ba 2。所以返回2。

    2021-02-18:给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文.arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来.返回需要至少多少张贴 ...

  4. 2021-03-22:小虎去买苹果,商店只提供两种类型的塑料袋,每种类型都有任意数量。1.能装下6个苹果的袋子,2.能装下8个苹果的袋子。小虎可以自由使用两种袋子来装苹果,但是小虎有强迫症,他要求自己使用的袋子数量必须最少,且使用的每个袋子必须装满。给定一个正整数N,返回至少使用多少袋子。如果N无法让使用的每个袋子必须装满,返回-1。

    2021-03-22:小虎去买苹果,商店只提供两种类型的塑料袋,每种类型都有任意数量.1.能装下6个苹果的袋子,2.能装下8个苹果的袋子.小虎可以自由使用两种袋子来装苹果,但是小虎有强迫症,他要求自己 ...

  5. 2022-04-01:有n个人,m个任务,任务之间有依赖记录在int[][] depends里。 比如: depends[i] = [a, b],表示a任务依赖b任务的完成, 其中 0 <= a <

    2022-04-01:有n个人,m个任务,任务之间有依赖记录在int[][] depends里. 比如: depends[i] = [a, b],表示a任务依赖b任务的完成, 其中 0 <= a ...

  6. 2021-12-14:根据身高重建队列。 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个

    2021-12-14:根据身高重建队列. 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序).每个 people[i] = [hi, ki] 表示第 i 个 ...

  7. 2021-10-28:打家劫舍 II。你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装

    2021-10-28:打家劫舍 II.你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金.这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的.同时,相邻的房屋装 ...

  8. Django-账号用户密码修改

    Django账号密码修改命令: python manage.py changepassword python manage.py changepassword 实操分析: 第一次修改失败是因为违反了密 ...

  9. 2023.5.16 总结 AT_abc260_g

    atcoder AT_abc260_g 题意 一个点 O 可以影响到其它点,能影响到的点的坐标满足:(\((u, v)\) 为当前点的坐标,\((x, y) 为能影响到的点的坐标\)) \(u\le ...

  10. 【使用git之旅】

    前言 在学习各种语言的时候我总喜欢把例子改成有自己想法并且有趣的程序, 但是时间一长,我发现在本地管理很麻烦,于是乎想到了github和gitee, 然后昨晚一时兴起,就开始了学习,开个博客记录一下我 ...