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. 音视频八股文(2)--ffmpeg常见命令(1)

    官方文档: https://www.ffmpeg.org/ffplay-all.html https://www.ffmpeg.org/ffmpeg-all.html 01-Windows FFMPE ...

  2. 2023-04-04:使用 Golang 和 ffmpeg-go 库实现 demuxing_decoding.c,轻松掌握音视频分离解码技巧。

    2023-04-04:使用 Golang 和 ffmpeg-go 库实现 demuxing_decoding.c,轻松掌握音视频分离解码技巧. 答案2023-04-05: 使用github/moonf ...

  3. 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] 等于 ...

  4. 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号 ...

  5. 认识 CPU 底层原理(2)——逻辑门

    本文为B站UP主硬件茶谈制作的系列科普<[硬件科普]带你认识CPU>系列的学习笔记,仅作个人学习记录使用,如有侵权,请联系博主删除 上一篇文章我们从最基本的粒子的角度认识了组成CPU的最基 ...

  6. pupstudy的使用

    打开环境 点击管理--打开根目录 把靶场放在www文件夹里 网页打开127.0.0.1/靶场文件名即可

  7. 记一次618军演压测TPS上不去排查及优化

    本文内容主要介绍,618医药供应链质量组一次军演压测发现的问题及排查优化过程.旨在给大家借鉴参考. 背景 本次军演压测背景是,2B业务线及多个业务侧共同和B中台联合军演. 现象 当压测商品卡片接口的时 ...

  8. ENVI5.6 安装教程,新手入门(超详细)附安装包和常见问题

    ENVI是一个完整的遥感图像处理平台,广泛应用于科研.环境保护.气象.农业.林业.地球科学.遥感工程.水利.海洋等领域.目前ENVI已成为遥感影像处理的必备软件,包含辐射定标.大气校正.镶嵌裁剪.分类 ...

  9. LeetCode 双周赛 106(2023/06/10)两道思维题

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 加入知识星球提问. 往期回顾:LeetCode 单周赛第 348 场 · 数位 DP 模版学会了吗? 双周赛 106 ...

  10. C++面试八股文:什么是左值,什么是右值?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第16面: 面试官:什么是左值,什么是右值? 二师兄:简单来说,左值就是可以使用&符号取地址的值,而右值一般不可以使用&符号取地址. ...