【Unity Shaders】Lighting Models —— 衣服着色器
本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线 ==========================================
写在前面
布料(Cloth)是另一种非常常见的着色需求,在很多实时游戏中都需要它来实现更真实的交互体验。它涉及到如何让布料的纤维合适地分散整个表面的光照,使它看起来像布料一样。布料的渲染非常依赖视角的变化,因此我们将学习一些新的技巧来模拟光扫射到布料上的效果,并且那些细小的纤维还能产生与众不同的边缘光照效果。
这篇将会介绍两种新的概念:细节法线贴图(Detail normal maps)和细节贴图(Detail textures)。通过把这两种法线贴图结合到一起,我们可以得到一种更高层次的细节表现,并且可以存储在一张2048*2048的贴图中。这种技术可以帮助我们模拟表面那种非常细微层次的凹凸不平的感觉,以此来分散整个表面的高光反射。
下面显示了本节最终得到的布料着色器效果:
准备工作
这个Shader需要结合3种不同类型的贴图来模拟布料效果:
- 一张细节法线贴图(Detail Normal map)。这张贴图将会平铺在表面上来模拟细小的缝纫痕迹。
- 一张标准变化贴图(Normal Variation map)。这张贴图将会模拟缝纫的变化,防止所有表面看起来都是一样的,而更像是有岁月磨损的样子。
- 一张细节漫反射贴图(Detail Diffuse map)。我们使用这张贴图去乘以基本颜色来模拟布料的整体颜色,以此来为整体增加更多的深度细节和真实感,并且还能强调布料的缝纫痕迹。
实现
- 首先,老样子添加新的properties。这里主要是为了控制所有的贴图和菲涅耳以及高光反射等。
Properties
{
_MainTint ("Global Tint", Color) = (1,1,1,1)
_BumpMap ("Normal Map", 2D) = "bump" {}
_DetailBump ("Detail Normal Map", 2D) = "bump" {}
_DetailTex ("Fabric Weave", 2D) = "white" {}
_FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
_FresnelPower ("Fresnel Power", Range(0, 12)) = 3
_RimPower ("Rim FallOff", Range(0, 12)) = 3
_SpecIntesity ("Specular Intensiity", Range(0, 1)) = 0.2
_SpecWidth ("Specular Width", Range(0, 1)) = 0.2
}解释:菲涅耳反射,简单来讲,就是当你垂直观察平面时,反射很弱;但当视线与平面越小时,反射越明显。举个例子,当你站在水边观察水面时,水是透明的,反射很弱,但是当你离水面越远时,基本就看不到河面以下的部分了,反射很强。(百度百科)
- 由于我们想要全面控制光照对布料平面的影响,因此我们需要在#pragma语句中声明新的光照模型,并且设置使用Shader model 3.0。
CGPROGRAM
#pragma surface surf Velvet
#pragma target 3.0 - 现在,我们需要建立Properties块和SubShader块的联系。为了使用Properties中的各种数据,我们需要在SubShader中声明同样名字的变量。
sampler2D _BumpMap;
sampler2D _DetailBump;
sampler2D _DetailTex;
float4 _MainTint;
float4 _FresnelColor;
float _FresnelPower;
float _RimPower;
float _SpecIntesity;
float _SpecWidth; - 为了分别控制几种细节贴图的平铺率,我们需要在Input结构中声明它们的UV参数。如果你把uv放在相同的贴图名称的前面,就可以建立UV信息的联系。
struct Input
{
float2 uv_BumpMap;
float2 uv_DetailBump;
float2 uv_DetailTex;
}; - 现在我们需要创建我们的光照模型函数。首先需要创建光照函数结构。我们需要viewDir参数得到视角方向,这是因为布料表面是受视角影响的。
inline fixed4 LightingVelvet (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
} - 永远在一开始就处理好你所有的光照向量(这里指视角方向和光照方向向量,以及它们的衍生向量)。这样可以让你不需要总是标准化你的向量,或担心光照计算的其他部分。因此,在光照模型函数的开头添加光照向量:
//Create lighting vectors here
viewDir = normalize(viewDir);
lightDir = normalize(lightDir);
half3 halfVec = normalize (lightDir + viewDir);
fixed NdotL = max (0, dot (s.Normal, lightDir));
解释:自己画一画就知道,halfVec将lightDir和viewDir结合在一起,主要用于和这两个向量相关的计算中。例如这里的高光反射(高光反射和观察视角以及光照角度都有关系)。NdotL是光照在平面法线方向上的分量,一般用于和光照颜色相乘来得到关于场景里实际灯光的颜色强度。 - 下一步,我们需要计算高光反射(Specular)部分。继续添加下面的代码:
//Create Specular
float NdotH = max (0, dot (s.Normal, halfVec));
float spec = pow (NdotH, s.Specular*128.0) * s.Gloss;布料渲染很大程度上依赖你从什么角度观察这个平面。观察角度越倾斜,就有越多的纤维捕捉到灯光后面的光照,并增强了高光反射。(菲涅耳效应)
//Create Fresnel
float HdotV = pow(1-max(0, dot(halfVec, viewDir)), _FresnelPower);
float NdotE = pow(1-max(0, dot(s.Normal, viewDir)), _RimPower);
float finalSpecMask = NdotE * HdotV - 当大部分计算完成后,我们仅仅需要输出最后的颜色值。添加下面的代码来完成我们的光照模型:
//Output the final color
fixed4 c;
c.rgb = (s.Albedo * NdotL * _LightColor0.rgb)
+ (spec * (finalSpecMask * _FresnelColor)) * (atten * 2);
c.a = 1.0;
return c; - 最后,我们创建surf()函数完成我们的Shader。这里,我们仅仅需要解压法线贴图,并把所有的数据传递给我们SurfaceOutput结构。
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_DetailTex, IN.uv_DetailTex);
fixed3 normals = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)).rgb;
fixed3 detailNormals = UnpackNormal(tex2D(_DetailBump, IN.uv_DetailBump)).rgb;
fixed3 finalNormals = float3(normals.x + detailNormals.x,
normals.y + detailNormals.y,
normals.z + detailNormals.z); o.Normal = normalize(finalNormals);
o.Specular = _SpecWidth;
o.Gloss = _SpecIntesity;
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}解释:在我们的布料着色器中,我们演示的新技术就是如何使用不同的平铺率整合两个法线贴图。基本的线性代数表明,我们可以将两个向量相加得到一个新的位置。因此,我们可以这样操作我们的法线贴图。我们使用UnpackNormal()函数得到标准变化贴图(Normal Variation map)的法线向量,再将其和细节法线贴图(Detail Normal map)的法线向量相加。这样得到了一个新的法线贴图。然后,我们标准化最后的向量,来让它的范围在0到1之间。如果没有这样做,我们的法线贴图就会看起来就是错的。
Shader "Custom/ClothShader" {
Properties
{
_MainTint ("Global Tint", Color) = (1,1,1,1)
_BumpMap ("Normal Map", 2D) = "bump" {}
_DetailBump ("Detail Normal Map", 2D) = "bump" {}
_DetailTex ("Fabric Weave", 2D) = "white" {}
_FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
_FresnelPower ("Fresnel Power", Range(0, 12)) = 3
_RimPower ("Rim FallOff", Range(0, 12)) = 3
_SpecIntesity ("Specular Intensiity", Range(0, 1)) = 0.2
_SpecWidth ("Specular Width", Range(0, 1)) = 0.2
} SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200 CGPROGRAM
#pragma surface surf Velvet
#pragma target 3.0 sampler2D _BumpMap;
sampler2D _DetailBump;
sampler2D _DetailTex;
float4 _MainTint;
float4 _FresnelColor;
float _FresnelPower;
float _RimPower;
float _SpecIntesity;
float _SpecWidth; struct Input
{
float2 uv_BumpMap;
float2 uv_DetailBump;
float2 uv_DetailTex;
}; inline fixed4 LightingVelvet (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
//Create lighting vectors here
viewDir = normalize(viewDir);
lightDir = normalize(lightDir);
half3 halfVec = normalize (lightDir + viewDir);
fixed NdotL = max (0, dot (s.Normal, lightDir)); //Create Specular
float NdotH = max (0, dot (s.Normal, halfVec));
float spec = pow (NdotH, s.Specular*128.0) * s.Gloss; //Create Fresnel
float HdotV = pow(1-max(0, dot(halfVec, viewDir)), _FresnelPower);
float NdotE = pow(1-max(0, dot(s.Normal, viewDir)), _RimPower);
float finalSpecMask = NdotE * HdotV; //Output the final color
fixed4 c;
c.rgb = (s.Albedo * NdotL * _LightColor0.rgb)
+ (spec * (finalSpecMask * _FresnelColor)) * (atten * 2);
c.a = 1.0;
return c;
} void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_DetailTex, IN.uv_DetailTex);
fixed3 normals = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)).rgb;
fixed3 detailNormals = UnpackNormal(tex2D(_DetailBump, IN.uv_DetailBump)).rgb;
fixed3 finalNormals = float3(normals.x + detailNormals.x,
normals.y + detailNormals.y,
normals.z + detailNormals.z); o.Normal = normalize(finalNormals);
o.Specular = _SpecWidth;
o.Gloss = _SpecIntesity;
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
解释
写在最后
【Unity Shaders】Lighting Models —— 衣服着色器的更多相关文章
- 基于Unity实现油画风格的着色器
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Cust ...
- 基于Unity实现像素化风格的着色器
Shader "MyShaderTest/SimplePixelationShader" { Properties { _MainTex ("Base (RGB)&quo ...
- 【Unity Shaders】Lighting Models 介绍
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- Unity 渲染教程(二):着色器基础
转载:https://www.jianshu.com/p/7db167704056 这是关于渲染基础的系列教程的第二部分.这个渲染基础的系列教程的第一部分是有关矩阵的内容.在这篇文章中我们将编写我们的 ...
- Unity Shader着色器优化
https://mp.weixin.qq.com/s?__biz=MzU5MjQ1NTEwOA==&mid=2247493518&idx=1&sn=c51b92e9300bcf ...
- Unity3d之Shader编程:子着色器、通道与标签的写法 & 纹理混合
一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器 ...
- Shader开发之三大着色器
固定功能管线着色器Fixed Function Shaders 固定功能管线着色器的关键代码一般都在Pass的材质设置Material{}和纹理设置SetTexture{}部分. Shader &qu ...
- 【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 & 纹理混合
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1117/120.html 作者:毛星云 ...
- 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
随机推荐
- pytorch_SRU(Simple Recurrent Unit)
导读 本文讨论了最新爆款论文(Training RNNs as Fast as CNNs)提出的LSTM变种SRU(Simple Recurrent Unit),以及基于pytorch实现了SRU,并 ...
- Json数组删除
有一个json数组,{'people':[{'name':'jetty','sex':'男'},{'name':'lily','sex':'女'}]} 有一个json:var aa={'name':' ...
- lgp20151222 java中如何将Object类型转换为int类型
if (object instanceof Integer) { Integer.parseInt(object.toString()); } 很简单是不是?我就想提醒下自己,java有个特殊词 ...
- vue中的eventBus
在vue2中,父子组件传递数据,父组件可以直接传递数据进子组件,而子组件通过调用父组件传递进来的方法,将自己的数据传递回去. 那兄弟组件之间,或者是兄弟组件的子组件之间如何传递呢? 当然vuex是一种 ...
- MyBatis 查询映射自定义枚举
背景 MyBatis查询若想映射枚举类型,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用 ...
- postgresql跨服务器复制数据库
假设名为dbname数据库需要从A服务器拷贝到B服务器 接收服务器B postgres用户 需先重置B服务器postgres系统用户的密码,使之与数据库用户postgres一致: passwd -d ...
- webpack 1.x 配合npm scripts管理多站点
需求场景: 希望通过一个webpack文件夹管理多个站点的打包流程. 假设现在我要为站点domain配置打包流程. npm 添加淘宝镜像 你懂得 vim ~/.npmrc registry = htt ...
- 初始化openresty开发环境
参考链接 https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-14-04 初始化git ...
- myeclipse中修改maven的默认仓库位置
1.本地需要安装Maven.这里假设安装在D:\Program Files\apache-maven-2.2.1 2.修改本地maven库的路径:在D:\Program Files\ap ...
- dynamic initializer和全局变量
"慎用全局变量,包括全局静态变量" 是众所周知的原则,因为全局变量除了会增加程序的维护成本. 如果全局变量是个复杂的对象,并且还使用其他的全局变量,那情况就变得复杂的多.因为全局变 ...