【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)。我们使用这张贴图去乘以基本颜色来模拟布料的整体颜色,以此来为整体增加更多的深度细节和真实感,并且还能强调布料的缝纫痕迹。

下面展示了本节中需要的三张贴图。你可以在本书资源(见最上方)中找到它们。

同时,你当然还需要像以前一样,新建一个场景,一个平行光,以及一个物体(本节使用自带的布料模型)。最后,新建一个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 —— 衣服着色器,布布扣,bubuko.com

时间: 2024-10-20 17:32:16

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

【Unity Shaders】Lighting Models 介绍

本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源(当然你也可以从官网下载). ========================================== 分割线 ========================================== 本章介绍 在前面几章中,我们一直在尝试使用Surface Shading中的不同部分来构建Shade

第4章:缓冲区、着色器、GLSL

原文链接: http://www.rastertek.com/gl40tut04.html Tutorial 4: Buffers, Shaders, and GLSL This tutorial will be the introduction to writing vertex and pixel shaders in OpenGL 4.0. It will also be the introduction to using vertex and index buffers in OpenG

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

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/41175585 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 邮箱: [email protected] 本文介绍了Unity中子着色器.通道和标签相关的详细概念与写法,以及纹理的设置方法,基本的纹理混合写法,写了5个Shader作为本文Shader讲解的实战内容,最后创建了一个梦幻的光之

Shader开发之三大着色器

固定功能管线着色器Fixed Function Shaders 固定功能管线着色器的关键代码一般都在Pass的材质设置Material{}和纹理设置SetTexture{}部分. Shader "Custom/VertexList" { Properties { _Color("Main Color",Color) = (0,1,1,0.5) _SpecColor("Spec Color",Color) = (1,1,1,1) _Emission

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

一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器定义了一个渲染通道的列表,并可选是否为所有通道初始化所需要的通用状态.子着色器的写法如下: Subshader{ [Tags] [CommonState] Passdef [Passdef ...] } 也就是通过可选标签,通用状态 和 一个Pass 定义的列表构成了子着色器. 当Unity选择用于

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

本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源(当然你也可以从官网下载). ========================================== 分割线 ========================================== 写在前面 照亮的球体(Lit Sphere,翻译过来很怪)类型的光照模型是一种非常有趣的基于图像的光

Unity Shaders and Effects Cookbook (6-1) 使用 alpha 参数的 半透明着色器

对于游戏项目,透明是很消耗资源的一个操作,在Unity的Profile 中可以很直观的看到透明所消耗的系统资源. 在Unity的表面着色器 Surface Shader 中,我们可以很方便的创建一种全透明的效果.如草的表面.这一节学习 通过在 #pragma 语句中添加 alpha 参数 来使用透明功能. 这一节我使用了下面这张贴图作为球 的表面纹理. 这张 512x 512 的图片分为了 4个色块区域.分别是 Green .Red.White.Blue. Green:就是 G通道为1,R.B

Unity Shaders and Effects Cookbook (6-2) 透明裁剪着色器

上一节中,利用纹理的 RGB 通道值 作为灰度,来控制像素的透明度,这种方式叫做半透明着色器. 这一节学习透明裁剪着色器,仍然是读取 RGB 通道值,赋值给 alpha,然后指定一个固定值作为标尺,如果 alpha < 标尺,就抛弃这个片段,也就是裁减掉不显示. 这一次实现的效果如下图 适合作为怪物死亡的效果. 转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn 下面开始学习. 搭建好场景,新建Material,新建Shader.

Unity 内置着色器(转)

Unity包括超过40种内置的shader. 标准着色器家族 Normal Shader Family 这些着色器都是Unity基本的着色器.适用于大多数的不透明物体,如果想要物体有透明.发光效果等,它们并不适用. Vertex Lit 顶点光照着色器 Assets needed 资源需要: One Base texture, no alpha channel required  一个基本纹理,无alpha通道 Diffuse 漫反射着色器 Assets needed 资源需要: One Bas