【Unity3D】素描特效
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】素描特效的更多相关文章
- 极客标签互动课程系列 - Javascript生成SVG动画素描特效
课程描写叙述:在这个课程中,我们将介绍SVG.而且介绍怎样使用javascript来控制SVG生成素描动画效果 课程地址:http://www.gbtags.com/gb/gbliblist/21.h ...
- Unity3d粒子特效:制作火焰效果
效果 分析 真实的火焰效果,通常包括:火.火光.火星等组成部分,而火焰对周围环境的烘焙,可以通过灯光实现,如点光源. 针对火焰组成部分,我们可以创建对应的粒子系统组件,实现相应的效果,如下图所示: 1 ...
- 【Unity3D】基于粒子系统实现烟花特效
1 需求实现 粒子系统ParticleSystem 中介绍了粒子初始化.粒子发射.发射器形状.渲染器.碰撞.子发射器.拖尾等粒子系统的基本用法,本节将基于粒子系统实现烟花特效. 实现需求如下( ...
- PS 滤镜——素描算法(二)
利用另外一种算法完成素描特效的生成. %%% Sketch clc; clear all; Image=imread('4.jpg'); Image=double(Image); [row,col,l ...
- Python: PS 滤镜--素描
本文用 Python 实现 PS 滤镜中的素描特效,具体的算法原理和效果可以参考之前的博客: http://blog.csdn.net/matrix_space/article/details/386 ...
- Python图像处理丨5种图像处理特效
摘要:本篇文章主要讲解了图像常见的特效处理,从处理效果图.算法原理.代码实现三个步骤进行详细讲解,涉及图像素描特效.怀旧特效.光照特效.流年特效.图像滤镜等. 本文分享自华为云社区<[Pytho ...
- Unity塔防游戏开发
Unity3D塔防开发流程 配置环境及场景搭建编程语言:C#,略懂些许设计模式,如果不了解设计模式,BUG More开发工具:Unity3D编辑器.Visual Studio编译器开发建议:了解Uni ...
- Unity3D特效-场景淡入淡出
最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾 ...
- unity3d 游戏插件 溶解特效插件 - Dissolve Shader
unity3d 游戏插件 溶解特效插件 - Dissolve Shader 链接: https://pan.baidu.com/s/1hr7w39U 密码: 3ed2
- Unity3D之挥动武器产生的剑痕特效
网维教程网 观看很多其它教程 眼下已知3种方法能够做这样的剑痕特效 1.尾随特效 2.程序实现动态面来处理剑痕动画. 3.美术实现剑痕动画,直接坐在模型动画里面 (由于我不会美术所以这个忽略 嘿嘿) ...
随机推荐
- 音视频八股文(2)--ffmpeg常见命令(1)
官方文档: https://www.ffmpeg.org/ffplay-all.html https://www.ffmpeg.org/ffmpeg-all.html 01-Windows FFMPE ...
- 2023-04-04:使用 Golang 和 ffmpeg-go 库实现 demuxing_decoding.c,轻松掌握音视频分离解码技巧。
2023-04-04:使用 Golang 和 ffmpeg-go 库实现 demuxing_decoding.c,轻松掌握音视频分离解码技巧. 答案2023-04-05: 使用github/moonf ...
- 2023-01-06:给定一个只由小写字母组成的字符串str,长度为N, 给定一个只由0、1组成的数组arr,长度为N, arr[i] == 0表示str中i位置的字符不许修改, arr[i] ==
2023-01-06:给定一个只由小写字母组成的字符串str,长度为N, 给定一个只由0.1组成的数组arr,长度为N, arr[i]等于 0 表示str中i位置的字符不许修改, arr[i] 等于 ...
- 2022-01-01:给定int[][] meetings,比如 { {66, 70} 0号会议截止时间66,获得收益70 {25, 90} 1号会议截止时间25,获得收益90
2022-01-01:给定int[][] meetings,比如 { {66, 70} 0号会议截止时间66,获得收益70 {25, 90} 1号会议截止时间25,获得收益90 {50, 30} 2号 ...
- 认识 CPU 底层原理(2)——逻辑门
本文为B站UP主硬件茶谈制作的系列科普<[硬件科普]带你认识CPU>系列的学习笔记,仅作个人学习记录使用,如有侵权,请联系博主删除 上一篇文章我们从最基本的粒子的角度认识了组成CPU的最基 ...
- pupstudy的使用
打开环境 点击管理--打开根目录 把靶场放在www文件夹里 网页打开127.0.0.1/靶场文件名即可
- 记一次618军演压测TPS上不去排查及优化
本文内容主要介绍,618医药供应链质量组一次军演压测发现的问题及排查优化过程.旨在给大家借鉴参考. 背景 本次军演压测背景是,2B业务线及多个业务侧共同和B中台联合军演. 现象 当压测商品卡片接口的时 ...
- ENVI5.6 安装教程,新手入门(超详细)附安装包和常见问题
ENVI是一个完整的遥感图像处理平台,广泛应用于科研.环境保护.气象.农业.林业.地球科学.遥感工程.水利.海洋等领域.目前ENVI已成为遥感影像处理的必备软件,包含辐射定标.大气校正.镶嵌裁剪.分类 ...
- LeetCode 双周赛 106(2023/06/10)两道思维题
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 加入知识星球提问. 往期回顾:LeetCode 单周赛第 348 场 · 数位 DP 模版学会了吗? 双周赛 106 ...
- C++面试八股文:什么是左值,什么是右值?
某日二师兄参加XXX科技公司的C++工程师开发岗位第16面: 面试官:什么是左值,什么是右值? 二师兄:简单来说,左值就是可以使用&符号取地址的值,而右值一般不可以使用&符号取地址. ...