【译】Unity3D Shader 新手教程(3/6) —— 更加真实的积雪
本文为翻译,附上原文链接。
转载请注明出处——polobymulberry-博客园。
如果你满足以下条件,我建议你阅读这篇教程:
- 你想知道如何在表面着色器中进行混色(blend colour)
- 你想实现一个更加真实的积雪效果
引论
我觉得有雪区域向无雪区域过渡的有些突兀,感觉更像白色的油漆涂在了岩石上,而不是积雪!为了使我们积雪shader的效果更加完美所以下一步需要做的是允许积雪和岩石纹理同时进行渲染,从而达到混色的效果。
我们只要对表面着色器的pixel处理方式进行一些修改就可以达到很好的积雪效果,而且这也将证明saturate函数是非常有用的。
准备工作
教程第二部分中的_SnowLevel(表示积雪程度的变量)决定了该像素是否应该赋以积雪的颜色,此处我们也会使用_SnowLevel来使积雪的边缘颜色变为半透明的白色。而不是之前完全的白色。当该像素的法向与雪落下的方向越一致(即两者夹角越小),则该像素的不透明度越高,换句话说,就是此处积雪更多,直到完全不透明,此处的像素颜色值将变为白色(事实上就是变成了积雪的颜色)。
实现该Shader
和教程第二部分的shader最大的差别在于,第二部分的shader中,每个像素的颜色值要不是积雪颜色,要不就是纹理颜色,而此部分shader将此非黑即白的判断改进成颜色值的变化过程。这导致我们将重写了shader的逻辑部分,并使用数学计算的方法来代替if的条件判断。
首先我们需要一个属性值(Property)来表示我们混合积雪颜色的程度。我们给该属性值取名为_Wetness(湿润度,如果该值越大,则混色中积雪颜色所占比例越低,这表明积雪越湿润,则雪的颜色越少,都化成水了):
_Wetness ("Wetness", Range(0, 0.5)) = 0.3
接着我们需要一个变量在shader中表示该属性值
float _Wetness;
我们使用下面这个公式计算difference变量。
float difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow);
其实第二部分的代码中也可以使用difference变量(比如当difference大于0就将该区域赋以积雪颜色,小于0就使用原先纹理颜色),关键在于对于difference的处理和应用不同。
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Normal = UnpackNormal (tex2D (_Bump, IN.uv_Bump));
float difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow);;
difference = saturate(difference / _Wetness);
o.Albedo = difference*_SnowColor.rgb + (1-difference) *c;
o.Alpha = c.a;
}
我们可以看到计算完difference后,首先将difference除以_Wetness(此处我们可以一窥_Wetness的具体用途),然后对difference使用saturate函数。saturate函数的具体用处如下:
- saturate函数将给定的值规范到0~1之间(大于1的置为1,小于0的置为0,其他不变)
- 所以当difference小于0时,即此处无积雪,我们通过saturate函数置difference为0。
- 如果_Wetness的默认值为0.3,并且法向和落雪方向夹角在73度~90度之间,则difference的值健在0~1之间,大于90度,则为0,小于73度则为1.
一个余弦值的范围是1到-1,差距为2,代表了0度到180度的变化(一直从同向变化到反向),所以要是difference在0~1之间,则点乘的结果(即cos值)必须在0~0.3之间,所以对应夹角为73度~90度(cos73=0.3,cos90=1)。
然后我们用difference乘以积雪颜色,difference代表积雪颜色在最终颜色所占的比例。我们再设置纹理本身颜色的比重为1-difference,相加得到最终颜色。其中夹角小于73度的区域将全部填充为积雪颜色,在73度~90度之间(也就是积雪的边缘)有一积雪过渡效果,大于90度的将使用纹理原来颜色。
o.Albedo = difference*_SnowColor.rgb + (1-difference) *c;
修正顶点数据
如果我们的的雪十分湿润(wet),那么只使用原先的shader的话,我们会发现积雪少的区域,模型也会增厚,根本原因是我们的顶点变化方式没有随积雪混色方式(主要是添加了_Wetness参数)变化而变化,产生的效果将不真实。所以我们应该将参数_Wetness应用到计算中,使模型的增厚效果随_Wetness变化。
void vert (inout appdata_full v) {
if(dot(v.normal, _SnowDirection.xyz) >= lerp(1,-1, ((1-_Wetness) * _Snow*2)/3))
{
v.vertex.xyz += (_SnowDirection.xyz + v.normal) * _SnowDepth * _Snow;
}
}
可以看到,我们决定是否增厚模型的判断变成了是否大于
lerp(1,-1, ((1-_Wetness) * _Snow*2)/3))
如果_Wetness为0,则一切都没变化,如果_Wetness变到了其最大范围0.5,则_Snow所占的比例不再是上文的2/3,而是1/3了 - 使模型增厚范围增加了50%。
下面是最终的效果图:

源代码
资源和代码在这里。
Shader "Custom/Realistic Snow" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Bump ("Bump", 2D) = "bump" {}
_Snow ("Snow Level", Range(0,1) ) = 0
_SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0)
_SnowDirection ("Snow Direction", Vector) = (0,1,0)
_SnowDepth ("Snow Depth", Range(0,0.2)) = 0.1
_Wetness ("Wetness", Range(0, 0.5)) = 0.3
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert
sampler2D _MainTex;
sampler2D _Bump;
float _Snow;
float4 _SnowColor;
float4 _SnowDirection;
float _SnowDepth;
float _Wetness;
struct Input {
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
void vert (inout appdata_full v) {
//将_SnowDirection转化到模型局部坐标系下
float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection);
if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow*2)/3))
{
v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
}
}
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Normal = UnpackNormal (tex2D (_Bump, IN.uv_Bump));
half difference = dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) - lerp(1,-1,_Snow);
difference = saturate(difference / _Wetness);
o.Albedo = difference*_SnowColor.rgb + (1-difference) *c;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
【译】Unity3D Shader 新手教程(3/6) —— 更加真实的积雪的更多相关文章
- 【译】Unity3D Shader 新手教程(1/6)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 刚开始接触Unity3D Shader编程时,你会发现有关shader的文档相当散,这也造成初学者对Unity3D ...
- 【译】Unity3D Shader 新手教程(2/6) —— 积雪Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的 ...
- 【译】Unity3D Shader 新手教程(6/6) —— 更好的卡通Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 动机 如果你想了解以下几件事,我建议你阅读以下这篇教程: 想知道如何写一个multipass的toon shade ...
- 【译】Unity3D Shader 新手教程(5/6) —— Bumped Diffuse Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 动机 如果你满足以下条件,我建议你阅读这篇教程: 你想学习片段着色器(Fragment Shader). 你想实现 ...
- 【译】Unity3D Shader 新手教程(4/6) —— 卡通shader(入门版)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 暗黑系 动机 如果你满足以下条件,我建议你阅读这篇教程: 你想了解更多有关表面着色器的细节知识. 你想实现一个入门 ...
- Unity3D Shader基础教程
原文地址:http://bbs.9ria.com/thread-212557-1-1.html 此教程将指引你如何建立自己的Shaders,让你的游戏场景看起来更好.Unity配备了强大的阴影和材料的 ...
- Unity3D Shader官方教程翻译(十九)----Shader语法,编写表面着色器
Writing Surface Shaders Writing shaders that interact with lighting is complex. There are different ...
- 【译】Meteor 新手教程:在排行榜上添加新特性
原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...
- 【浅墨Unity3D Shader编程】之二 雪山飞狐篇:Unity的基本Shader框架写法&颜色、光照与材质
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40955607 作者:毛星云(浅墨) ...
随机推荐
- 故障重现(内存篇2),JAVA内存不足导致频繁回收和swap引起的性能问题
背景起因: 记起以前的另一次也是关于内存的调优分享下 有个系统平时运行非常稳定运行(没经历过大并发考验),然而在一次活动后,人数并发一上来后,系统开始卡. 我按经验开始调优,在每个关键步骤的加入如 ...
- 再讲IQueryable<T>,揭开表达式树的神秘面纱
接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...
- hash表长度优化证明
hash表冲突的解决方法一般有两个方向: 一个是倾向于空间换时间,使用向量加链表可以最大程度的在节省空间的前提下解决冲突. 另外一个倾向于时间换空间,下面是关于这种思路的一种合适表长度的证明过程: 这 ...
- ubuntu系统下如何修改host
Ubuntu系统的Hosts只需修改/etc/hosts文件,在目录中还有一个hosts.conf文件,刚开始还以为只需要修改这个就可以了,结果发现是需要修改hosts.修改完之后要重启网络.具体过程 ...
- 以项目谈WebGIS中Web制图的设计和实现
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景介绍 一般WebGIS项目中,前端展示数据的流程基本是先做数据入 ...
- C++ 11 多线程--线程管理
说到多线程编程,那么就不得不提并行和并发,多线程是实现并发(并行)的一种手段.并行是指两个或多个独立的操作同时进行.注意这里是同时进行,区别于并发,在一个时间段内执行多个操作.在单核时代,多个线程是并 ...
- Java多态性——分派
一.基本概念 Java是一门面向对象的程序设计语言,因为Java具备面向对象的三个基本特征:封装.继承和多态.这三个特征并不是各自独立的,从一定角度上看,封装和继承几乎都是为多态而准备的.多态性主要体 ...
- 【JQ基础】数组
each() 方法规定为每个匹配元素规定运行的函数.
- Node.js使用PM2的集群将变得更加容易
介绍 众所周知,Node.js运行在Chrome的JavaScript运行时平台上,我们把该平台优雅地称之为V8引擎.不论是V8引擎,还是之后的Node.js,都是以单线程的方式运行的,因此,在多核心 ...
- 中文 iOS/Mac 开发博客列表
中文 iOS/Mac 开发博客列表 博客地址 RSS地址 OneV's Den http://onevcat.com/atom.xml 一只魔法师的工坊 http://blog.ibireme.com ...