本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

写在前面

布料(Cloth)是另一种非常常见的着色需求,在很多实时游戏中都需要它来实现更真实的交互体验。它涉及到如何让布料的纤维合适地分散整个表面的光照,使它看起来像布料一样。布料的渲染非常依赖视角的变化,因此我们将学习一些新的技巧来模拟光扫射到布料上的效果,并且那些细小的纤维还能产生与众不同的边缘光照效果。

这篇将会介绍两种新的概念:细节法线贴图(Detail normal maps)和细节贴图(Detail textures)。通过把这两种法线贴图结合到一起,我们可以得到一种更高层次的细节表现,并且可以存储在一张2048*2048的贴图中。这种技术可以帮助我们模拟表面那种非常细微层次的凹凸不平的感觉,以此来分散整个表面的高光反射。

下面显示了本节最终得到的布料着色器效果:

准备工作

这个Shader需要结合3种不同类型的贴图来模拟布料效果:

  • 一张细节法线贴图(Detail Normal map)。这张贴图将会平铺在表面上来模拟细小的缝纫痕迹。
  • 一张标准变化贴图(Normal Variation map)。这张贴图将会模拟缝纫的变化,防止所有表面看起来都是一样的,而更像是有岁月磨损的样子。
  • 一张细节漫反射贴图(Detail Diffuse map)。我们使用这张贴图去乘以基本颜色来模拟布料的整体颜色,以此来为整体增加更多的深度细节和真实感,并且还能强调布料的缝纫痕迹。
下面展示了本节中需要的三张贴图。你可以在本书资源(见最上方)中找到它们。
同时,你当然还需要像以前一样,新建一个场景,一个平行光,以及一个物体(本节使用自带的布料模型)。最后,新建一个Shader和Material,并命名为ClothShader。

实现

  • 首先,老样子添加新的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"
}
下面显示了我们的布料着色器的效果:

解释

实际上我们的Shader并不复杂,无非进行了一些基本的光照计算,但是有时候这些计算就足够了。在你想要用Shader模拟某种表面时,把它分成几个部分,然后再在某一时刻把它们整合到一起。最关键的部分就是你怎样整合不同部分,这就像在Photoshop中混合不同的layers一样。
最后,我们整合菲涅耳和高光反射的计算,这让我们创建了那些微小纤维也可以反射光的视觉效果(这里的我的理解是倾斜的时候就会看到布料的表面越粗糙,那些纤维的细节就越明显)。

写在最后

感觉这一篇原文作者解释的很简单,但是看起来还是有点吃力的。尤其是光照模型中最后关于颜色赋值方面的计算,感觉很多计算实际是靠经验和视觉来进行整合的。
呼。。。今天先写到这里,希望多看看可以有更多的理解。

【Unity Shaders】Lighting Models —— 衣服着色器的更多相关文章

  1. 基于Unity实现油画风格的着色器

    // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Cust ...

  2. 基于Unity实现像素化风格的着色器

    Shader "MyShaderTest/SimplePixelationShader" { Properties { _MainTex ("Base (RGB)&quo ...

  3. 【Unity Shaders】Lighting Models 介绍

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  4. Unity 渲染教程(二):着色器基础

    转载:https://www.jianshu.com/p/7db167704056 这是关于渲染基础的系列教程的第二部分.这个渲染基础的系列教程的第一部分是有关矩阵的内容.在这篇文章中我们将编写我们的 ...

  5. Unity Shader着色器优化

    https://mp.weixin.qq.com/s?__biz=MzU5MjQ1NTEwOA==&mid=2247493518&idx=1&sn=c51b92e9300bcf ...

  6. Unity3d之Shader编程:子着色器、通道与标签的写法 & 纹理混合

    一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器 ...

  7. Shader开发之三大着色器

    固定功能管线着色器Fixed Function Shaders 固定功能管线着色器的关键代码一般都在Pass的材质设置Material{}和纹理设置SetTexture{}部分. Shader &qu ...

  8. 【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 &amp; 纹理混合

    本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1117/120.html 作者:毛星云 ...

  9. 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. Python与C的简单比较(Python3.0)

    Python可以说是目前最火的语言之一了,人工智能的兴起让Python一夜之间变得家喻户晓,Python号称目前最最简单易学的语言,现在有不少高校开始将Python作为大一新生的入门语言.本萌新也刚开 ...

  2. Delphi7.0常用函数-属性-事件

    abort 函数 引起放弃的意外处理 addexitproc 函数 将一过程添加到运行时库的结束过程表中 addr 函数 返回指定对象的地址 adjustlinebreaks 函数 将给定字符串的行分 ...

  3. Debugging TensorFlow models 调试 TensorFlow 模型

    Debugging TensorFlow models Symbolic nature of TensorFlow makes it relatively more difficult to debu ...

  4. 如何查看cisco 生成树状态

    如何查看cisco 生成树状态  Role(角色): 1.Desg(指定端口)   2.Root(根端口)    3.Altn(替换端口) Sts(状态): 1.FWD(转发)      2.BLK( ...

  5. sea.js及三种加载方式的异同

      一.前言     浏览器本身并不提供模块管理的机制,过去网页开发中,为了使用各种模块,不得不在加入一大堆script标签.这样就使得网页体积臃肿,难以维护,还产生大量的HTTP请求,拖慢显示速度, ...

  6. vscode 常见插件及配置 备忘

    配置 // 以下解决格式化js自动添加分号 "prettier.singleQuote": true, "prettier.semi": false, // 以 ...

  7. centos 6安装opencv

    昨天装好的,今天有些细节已经记不起来里,大致写一下吧. 首先,从opencv官网下载linux的opencv-2.4.9安装包,下载地址:http://jaist.dl.sourceforge.net ...

  8. Spring-cloud(六) Hystrix入门

    前提 一个可用的Eureka注册中心(文中以之前博客中双节点注册中心,不重要) 一个连接到这个注册中心的服务提供者 快速入门 项目搭建 搭建一个新maven项目,artifactid为Ribbon-c ...

  9. python脚本批量生成数据

    在平时的工作中,经常会遇到造数据,特别是性能测试的时候更是需要大量的数据.如果一条条的插入数据库或者一条条的创建数据,效率未免有点低.如何快速的造大量的测试数据呢?在不熟悉存储过程的情况下,今天给大家 ...

  10. ACM Wooden Stricks

    有一堆n根木棍.每根棍子的长度和重量是预先知道的. 这些棒子将被木工机器逐一加工..它需要一些时间,称为安装时间,用于机器准备加工棒.设置时间与机器中的清洁操作和更换工具和形状相关联.木工机械的安装时 ...