2017-12-18 09:19:04 wangkun1340378 阅读数 5829

unity3D 法线贴图的制作与使用

工具:unity3D 5.4.0


示例:

 首先,获取一张图片用以制作法线贴图


随后,拖入unity3D的project目录


选中图片,在右侧的inspector目录中,texture type选择normal map,filtering选择sharp,并根据需要设置bumpiness的数值,本例中为0.3


随后,点击apply即可生成法线贴图。

利用unity3D搭建一个3d模型,如图所示



最后选中所建立的3d模型,在右侧的inspector目录中,将normal map设置为刚才制作的法线贴图


完成法线贴图后的结果如图所示


2015-03-22 21:19:00 weixin_34364135 阅读数 209

原帖网址http://www.u3dpro.com/read.php?tid=207  感谢jdk900网友的辛苦编写


    我们都知道,一个三维场景的画面的好坏,百分之四十取决于模型,百分之六十取决于贴图,可见贴图在画面中所占的重要性。在这里我将列举一些贴图,并且初步阐述其概念,理解原理的基础上制作贴图,也就顺手多了。
我在这里主要列举几种UNITY3D中常用的贴图,与大家分享,希望对大家有帮助。
01
点击浏览下一页 

首先不得不说的是漫反射贴图:
漫反射贴图diffuse map 

点击浏览下一页 
漫反射贴图在游戏中表现出物体表面的反射和表面颜色。换句话说,它可以表现出物体被光照射到而显出的颜色和强度。我们通过颜色和明暗来绘制一幅漫反射贴图,在这张贴图中,墙的砖缝中因为吸收了比较多的光线,所以比较暗,而墙砖的表面因为反射比较强,所以吸收的光线比较少。上面的这张图可以看出砖块本身是灰色的,而砖块之间的裂缝几乎是黑色的。

刨去那些杂糅的东西,我们只谈明显的,漫反射贴图表现了什么? 列举一下,物体的固有色以及纹理,贴图上的光影。前面的固有色和纹理我们很容易理解,至于后面的光影,我们再绘制漫反射贴图的时候需要区别对待,比如我们做一堵墙,每一块砖都是用模型做出来的,那么我们就没有必要绘制砖缝,因为这个可以通过打灯光来实现。可是我们如果用模型只做了一面墙,上面的砖块是用贴图来实现,那么就得绘制出砖缝了。从美术的角度,砖缝出了事一条单独的材质带外,还有就是砖缝也是承接投影的,所以在漫反射图上,绘制出投影也是很有必要的,如下图:

点击浏览下一页 


没有什么物体能够反射出跟照到它身上相同强度的光。因此,让你的漫反射贴图暗一些是一个不错的想法。通常,光滑的面只有很少的光会散射,所以你的漫反射贴图可以亮一些。

漫反射贴图应用到材质中去是直接通过DiffuseMap的。再命名规范上它通常是再文件的末尾加上“_d”来标记它是漫反射贴图。

点击浏览下一页 
凹凸贴图Bump maps
凸凹贴图可以给贴图增加立体感。它其实并不能改变模型的形状,而是通过影响模型表面的影子来达到凸凹效果的。再游戏中有两种不同类型的凸凹贴图,法线贴图(normalmap)和高度贴图(highmap)。

Normal maps法线贴图

点击浏览下一页 

法线贴图定义了一个表面的倾斜度或者法线。换一种说法,他们改变了我们所看到的表面的倾斜度。


点击浏览下一页 

法线贴图把空间坐标的参数(X,Y,Z)记录在像素中(R,G,B),上面的范例图就是这个意思。



有两种制作法线贴图的方法:

1.从三维的模型渲染出一张法线贴图 (用高模跟低模重叠在一起,把高模上的细节烘焙到低模的UV上,这里需要低模有一个不能重叠的UV

2.转换一张高度贴图成为一个法线贴图。(是用NVIDIAPS插件来转换一张图成为法线贴图



Height maps高度贴图




点击浏览下一页 

什么HeightMap呢?所谓高度图实际上就是一个2D数组。创建地形为什么需要高度图呢?可以这样考虑,地形实际上就是一系列高度不同的网格而已,这样数组中每个元素的索引值刚好可以用来定位不同的网格(xy),而所储存的值就是网格的高度(z)。



我们在这里叙述高度图,其实也是为了更好的绘制法线贴图,很多情况下我们的法线贴图只能在已有的漫反射贴图作为素材进行绘制,这样就是需要由一个HeightMap转换成法线贴图的一个过程,明白了这个原理,做起来也就可以更好的驾驭其效果。

高度贴图是一种黑白的图像,它通过像素来定义模型表面的高度。越亮的地方它的高度就越高,画面越白的地方越高,越黑的地方越低,灰色的在中间,从而表现不同的地形。




当然在UNITY中也是有HightMap出现的,比如在Terrain菜单中,就有导入和导出HightMap的命令。

高度贴图通常是在图形处理软件中绘制的。他们通常没有必要渲染这些,再DOOM3游戏中高度贴图是被转换成法线贴图来使用的。使用高度贴图仅仅是为了适应简单的工作流程。高度贴图通常通过Heightmap函数来调用到3D软件中去的,我们通常再文件名后面加一个"_h"来标示它。





Normal maps vs. height maps



法线贴图和高度贴图
一般来说,Normal Map来自于Height Map。具体生成的方法如下:
Height Map的每个像素和它上面的一个像素相减,得到一个高度差,作为该点法线的x值;
Height Map的每个像素和它右边的一个像素相减,得到一个高度差,作为该点法线的y值;
1作为该点法线的z值。

推导过程如下:
x方向,每个像素和它下面的一个像素相减,得到向量<1, 0, hb  - ha>,其中ha是该像素的高度值,hb是下一行的高度值;
y方向,每个像素和它左边的一个像素相减,得到向量<0, 1, hc  - ha>,其中ha是该像素的高度值,hb是左一列的高度值;
两个向量Cross,得到

简单来说,就是取两个方向的切线向量,对它们做Cross得到该点的法线向量。



还有另外一种做法,是根据每个象素四边的点计算,而该点象素本身不参与计算。
没有试过,不知道哪种好一些。

而且我觉得这种计算只适合于单块的HeightMapNormalMap,像是DOOM3中的NormalMap就无法由HeightMap计算出来了。所以最好还是在美工建模的时候同时生成NormalMapHeightMap而不是利用HeightMap生成NormalMap

DOOM3游戏引擎可以把法线贴图和高度贴图合成在一张凸凹贴图上。

通常我们绘制一张具有足够细节的高度贴图要比建立一个足够细节的模型然后渲染成相应的法线贴图要实际的多。

法线和高度的凸凹贴图可以通过Addnormals函数来合并到一种材质中。

毫无疑问,高度贴图大多数游戏引擎中出现的不多。他们只是给电脑一种方法来计算曲面法线当使用动态灯光的时候。

这说明实际上,一张高度贴图被转换成一张法线贴图,以此可以计算出相邻两块不同高度的位置之间的倾斜面。高度贴图永远不能像法线贴图这样具有足够的细节,这是被肯定的。

很明显只有灰度的高度贴图并不能很好的表现应该有的细节,因为它是黑白的,RGB颜色就会遭到浪费,并且因此你只能只用256层级的强度。

相比较来言法线贴图的每一个图像通道都可以利用到,显而易见,法线贴图能够更好的来表现凸凹。




点击浏览下一页 

Specular maps高光贴图 
点击浏览下一页 


什么是高光贴图?
高光贴图是用来表现当光线照射到模型表面时,其表面属性的.(如金属和皮肤、布、塑料反射不同量的光)从而区分不同材质.

高光贴图再引擎中表现镜面反射和物体表面的高光颜色。



材质的反光程度就越强。(

2019-05-01 15:16:35 weixin_42163773 阅读数 114

有的时候我们会把法线贴图压缩为RG两通道,以复用BA通道存储别的信息。

这种情况下我们需要把法线贴图以

Default类型存储,而不是Normal Map类型

然而,Unity有一个潜规则,对于Shader中定义了属性名称为_BumpMap、_NormalMap、_DetailBumpMap、_DetailNormalMap的材质球,凡被拖到这4种材质球上相应的贴图框内的贴图,均会被引擎判定为法线贴图——参加引擎源码BumpMapSettings.cpp中的PerformBumpMapCheck函数。而这些材质球在被导入(Import)时会触发弹出框询问用户这类被引擎认定的“法线贴图”的Texture Type没有被“正确”标记为Normal map,是否要修复。如下图所示:

对于这类情况,我们一般是点击Ignore来无视它,然而,Unity还有一条潜规则:在以BatchMode运行的情况下并不会弹出这个交互式界面进行询问,而是直接强制将贴图的Texture Type改为Normal Map,产生不符合我们预期(需求)的结果。相关逻辑详见引擎源码BumpMapSettings.cpp中的PerformUnmarkedBumpMapTexturesFixing,这个函数中有个条件分支,如果是BatchMode或者SilentMode,强制处理(为啥不是不做处理?管得太宽了吧。。。),否则的话弹框询问。

那么,这个坑要如何回避呢?其实,简直不要太简单,就是对于有上述通道压缩需求的Shader,其中用于法线贴图的属性(Properties中定义),不要叫上述4个名称中的任何一个,哪怕你就是叫_NormapMap0,也不会触发这个潜规则。对的,就是这么简单。谁让爹妈给你起了个屏蔽字名字呢?活该你的名字没有机会出现在网路上,赶紧改名吧。

最后,总结一条Shader编码规范:对通道作用进行了重定义的法线贴图的自定义Shader,在命名该贴图的属性(变量)名称时,不要使用_BumpMap、_NormalMap、_DetailBumpMap、_DetailNormalMap这四个名字,除此以外都可以。理由见上文。

2016-04-23 02:33:40 cp790621656 阅读数 5706

法线贴图 在之前学习过了,我们使用法线贴图在低分辨率的模型上 模拟 高分辨率的效果。

Unity中 通过 UnpackNormal 函数 来使用法线贴图。


之前学习法线贴图的记录

Unity Shaders and Effects Cookbook (2-5) 如何使用法线贴图

这一节讲的是 在Cubemap 上使用法线贴图。模拟凹凸效果。

最终效果如图


一起来做吧。

首先搭建好场景,和上一节一样。


导入法线贴图



创建材质 、Shader 。

复制上一节的 Shader 就行。然后修改成下面的内容。

Shader "CookBookShaders/Chapt4-4/Cubemap_NormalMap" 
{
	Properties 
	{
		_MainTint("Diffuse Color",Color)=(1,1,1,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}

		_NormalMap("Normal Map",2D) = "bump"{}

		_Cubemap("Cubemap",CUBE)=""{}

		_ReflAmount("Reflection Amount",Range(0,1))=0.5
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert

		float4 _MainTint;
		sampler2D _MainTex;

		sampler2D _NormalMap;

		samplerCUBE _Cubemap;

		float _ReflAmount;



		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

			o.Normal=normals;

			//上面使用法线贴图中的法线数据 替代了 原来的法线数据。

			//法线被修改了,就不能 直接用原来的 内置属性 worldRefl 这个反射向量,而是要通过 WorldReflectionVector(IN,o.Normal)来获取。

			//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
			o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

			o.Albedo = c.rgb * _MainTint;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

和上一节的Shader 相比,修改了如下几处:

1、在 Properties 属性块中添加了法线贴图

_NormalMap("Normal Map",2D) = "bump"{}

然后在 SubShader 中添加对应的变量

sampler2D _NormalMap;


2、在 Input 中 添加了 法线贴图的UV,以及一个奇怪的字段 INTERNAL_DATA

		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

uv_NormalMap 是用来读取 法线贴图数据的。

转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn

INTERNAL_DATA

书上有两点解释

A:通过在Input 中添加 INTERNAL_DATA ,我们就可以访问由法线贴图修改后的表面法线。

这个很奇怪,为什么要添加 INTERNAL_DATA,才可以访问由法线贴图修改后的表面法线?上次学习法线贴图的时候可没有这个东西。

习惯性到 CGIncludes 里面去查找,发现并没有 INTERNAL_DATA 。


在官方文档

http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

找到这一句话

If you want to do reflections that are affected by normal maps, it needs to be slightly more involved: INTERNAL_DATA needs to be added to the Input structure, and WorldReflectionVector function used to compute per-pixel reflection vector after you’ve written the Normal output.


就是说计算世界反射向量的时候 使用了法线贴图就要加上 这个。


谷歌一下,发现网友说在 生成的代码中能看出来,然后就查看生成的代码

Shader "CookBookShaders/Chapt4-4/Cubemap_NormalMap" 
{
	Properties 
	{
		_MainTint("Diffuse Color",Color)=(1,1,1,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}

		_NormalMap("Normal Map",2D) = "bump"{}

		_Cubemap("Cubemap",CUBE)=""{}

		_ReflAmount("Reflection Amount",Range(0,1))=0.5
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		
	// ------------------------------------------------------------
	// Surface shader code generated out of a CGPROGRAM block:
	

	// ---- forward rendering base pass:
	Pass {
		Name "FORWARD"
		Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdbase
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

#define INTERNAL_DATA half3 TtoW0; half3 TtoW1; half3 TtoW2;
#define WorldReflectionVector(data,normal) reflect (data.worldRefl, half3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal)))
#define WorldNormalVector(data,normal) fixed3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal))

// Original surface shader snippet:
#line 18 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif

		//#pragma surface surf Lambert

		float4 _MainTint;
		sampler2D _MainTex;

		sampler2D _NormalMap;

		samplerCUBE _Cubemap;

		float _ReflAmount;



		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

			o.Normal=normals;

			//上面使用法线贴图中的法线数据 替代了 原来的法线数据。

			//法线被修改了,就不能 直接用原来的 内置属性 worldRefl 这个反射向量,而是要通过 WorldReflectionVector(IN,o.Normal)来获取。

			//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
			o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

			o.Albedo = c.rgb * _MainTint;
			o.Alpha = c.a;
		}
		

// vertex-to-fragment interpolation data
#ifdef LIGHTMAP_OFF
struct v2f_surf {
  float4 pos : SV_POSITION;
  float4 pack0 : TEXCOORD0;
  fixed4 TtoW0 : TEXCOORD1;
  fixed4 TtoW1 : TEXCOORD2;
  fixed4 TtoW2 : TEXCOORD3;
  fixed3 lightDir : TEXCOORD4;
  fixed3 vlight : TEXCOORD5;
  LIGHTING_COORDS(6,7)
};
#endif
#ifndef LIGHTMAP_OFF
struct v2f_surf {
  float4 pos : SV_POSITION;
  float4 pack0 : TEXCOORD0;
  fixed4 TtoW0 : TEXCOORD1;
  fixed4 TtoW1 : TEXCOORD2;
  fixed4 TtoW2 : TEXCOORD3;
  float2 lmap : TEXCOORD4;
  LIGHTING_COORDS(5,6)
};
#endif
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
#endif
float4 _MainTex_ST;
float4 _NormalMap_ST;

// vertex shader
v2f_surf vert_surf (appdata_full v) {
  v2f_surf o;
  o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
  o.pack0.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
  float3 viewDir = -ObjSpaceViewDir(v.vertex);
  float3 worldRefl = mul ((float3x3)_Object2World, viewDir);
  TANGENT_SPACE_ROTATION;
  o.TtoW0 = float4(mul(rotation, _Object2World[0].xyz), worldRefl.x)*unity_Scale.w;
  o.TtoW1 = float4(mul(rotation, _Object2World[1].xyz), worldRefl.y)*unity_Scale.w;
  o.TtoW2 = float4(mul(rotation, _Object2World[2].xyz), worldRefl.z)*unity_Scale.w;
  #ifndef LIGHTMAP_OFF
  o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
  #endif
  float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
  float3 lightDir = mul (rotation, ObjSpaceLightDir(v.vertex));
  #ifdef LIGHTMAP_OFF
  o.lightDir = lightDir;
  #endif

  // SH/ambient and vertex lights
  #ifdef LIGHTMAP_OFF
  float3 shlight = ShadeSH9 (float4(worldN,1.0));
  o.vlight = shlight;
  #ifdef VERTEXLIGHT_ON
  float3 worldPos = mul(_Object2World, v.vertex).xyz;
  o.vlight += Shade4PointLights (
    unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
    unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
    unity_4LightAtten0, worldPos, worldN );
  #endif // VERTEXLIGHT_ON
  #endif // LIGHTMAP_OFF

  // pass lighting information to pixel shader
  TRANSFER_VERTEX_TO_FRAGMENT(o);
  return o;
}
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap;
#ifndef DIRLIGHTMAP_OFF
sampler2D unity_LightmapInd;
#endif
#endif

// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
  // prepare and unpack data
  #ifdef UNITY_COMPILER_HLSL
  Input surfIN = (Input)0;
  #else
  Input surfIN;
  #endif
  surfIN.uv_MainTex = IN.pack0.xy;
  surfIN.uv_NormalMap = IN.pack0.zw;
  surfIN.worldRefl = float3(IN.TtoW0.w, IN.TtoW1.w, IN.TtoW2.w);
  surfIN.TtoW0 = IN.TtoW0.xyz;
  surfIN.TtoW1 = IN.TtoW1.xyz;
  surfIN.TtoW2 = IN.TtoW2.xyz;
  #ifdef UNITY_COMPILER_HLSL
  SurfaceOutput o = (SurfaceOutput)0;
  #else
  SurfaceOutput o;
  #endif
  o.Albedo = 0.0;
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;

  // call surface function
  surf (surfIN, o);

  // compute lighting & shadowing factor
  fixed atten = LIGHT_ATTENUATION(IN);
  fixed4 c = 0;

  // realtime lighting: call lighting function
  #ifdef LIGHTMAP_OFF
  c = LightingLambert (o, IN.lightDir, atten);
  #endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
  #ifdef LIGHTMAP_OFF
  c.rgb += o.Albedo * IN.vlight;
  #endif // LIGHTMAP_OFF

  // lightmaps:
  #ifndef LIGHTMAP_OFF
    #ifndef DIRLIGHTMAP_OFF
      // directional lightmaps
      fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
      fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
      half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 1).rgb;
    #else // !DIRLIGHTMAP_OFF
      // single lightmap
      fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
      fixed3 lm = DecodeLightmap (lmtex);
    #endif // !DIRLIGHTMAP_OFF

    // combine lightmaps with realtime shadows
    #ifdef SHADOWS_SCREEN
      #if defined(UNITY_NO_RGBM)
      c.rgb += o.Albedo * min(lm, atten*2);
      #else
      c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
      #endif
    #else // SHADOWS_SCREEN
      c.rgb += o.Albedo * lm;
    #endif // SHADOWS_SCREEN
  c.a = o.Alpha;
  #endif // LIGHTMAP_OFF

  c.rgb += o.Emission;
  return c;
}

ENDCG

}

	// ---- forward rendering additive lights pass:
	Pass {
		Name "FORWARD"
		Tags { "LightMode" = "ForwardAdd" }
		ZWrite Off Blend One One Fog { Color (0,0,0,0) }

CGPROGRAM
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdadd
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDADD
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal

// Original surface shader snippet:
#line 18 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif

		//#pragma surface surf Lambert

		float4 _MainTint;
		sampler2D _MainTex;

		sampler2D _NormalMap;

		samplerCUBE _Cubemap;

		float _ReflAmount;



		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

			o.Normal=normals;

			//上面使用法线贴图中的法线数据 替代了 原来的法线数据。

			//法线被修改了,就不能 直接用原来的 内置属性 worldRefl 这个反射向量,而是要通过 WorldReflectionVector(IN,o.Normal)来获取。

			//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
			o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

			o.Albedo = c.rgb * _MainTint;
			o.Alpha = c.a;
		}
		

// vertex-to-fragment interpolation data
struct v2f_surf {
  float4 pos : SV_POSITION;
  float4 pack0 : TEXCOORD0;
  half3 lightDir : TEXCOORD1;
  LIGHTING_COORDS(2,3)
};
float4 _MainTex_ST;
float4 _NormalMap_ST;

// vertex shader
v2f_surf vert_surf (appdata_full v) {
  v2f_surf o;
  o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
  o.pack0.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
  TANGENT_SPACE_ROTATION;
  float3 lightDir = mul (rotation, ObjSpaceLightDir(v.vertex));
  o.lightDir = lightDir;

  // pass lighting information to pixel shader
  TRANSFER_VERTEX_TO_FRAGMENT(o);
  return o;
}

// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
  // prepare and unpack data
  #ifdef UNITY_COMPILER_HLSL
  Input surfIN = (Input)0;
  #else
  Input surfIN;
  #endif
  surfIN.uv_MainTex = IN.pack0.xy;
  surfIN.uv_NormalMap = IN.pack0.zw;
  #ifdef UNITY_COMPILER_HLSL
  SurfaceOutput o = (SurfaceOutput)0;
  #else
  SurfaceOutput o;
  #endif
  o.Albedo = 0.0;
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;

  // call surface function
  surf (surfIN, o);
  #ifndef USING_DIRECTIONAL_LIGHT
  fixed3 lightDir = normalize(IN.lightDir);
  #else
  fixed3 lightDir = IN.lightDir;
  #endif
  fixed4 c = LightingLambert (o, lightDir, LIGHT_ATTENUATION(IN));
  c.a = 0.0;
  return c;
}

ENDCG

}

	// ---- deferred lighting base geometry pass:
	Pass {
		Name "PREPASS"
		Tags { "LightMode" = "PrePassBase" }
		Fog {Mode Off}

CGPROGRAM
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf

#pragma exclude_renderers flash
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_PREPASSBASE
#include "UnityCG.cginc"
#include "Lighting.cginc"

#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal

// Original surface shader snippet:
#line 18 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif

		//#pragma surface surf Lambert

		float4 _MainTint;
		sampler2D _MainTex;

		sampler2D _NormalMap;

		samplerCUBE _Cubemap;

		float _ReflAmount;



		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

			o.Normal=normals;

			//上面使用法线贴图中的法线数据 替代了 原来的法线数据。

			//法线被修改了,就不能 直接用原来的 内置属性 worldRefl 这个反射向量,而是要通过 WorldReflectionVector(IN,o.Normal)来获取。

			//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
			o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

			o.Albedo = c.rgb * _MainTint;
			o.Alpha = c.a;
		}
		

// vertex-to-fragment interpolation data
struct v2f_surf {
  float4 pos : SV_POSITION;
  float2 pack0 : TEXCOORD0;
  float3 TtoW0 : TEXCOORD1;
  float3 TtoW1 : TEXCOORD2;
  float3 TtoW2 : TEXCOORD3;
};
float4 _NormalMap_ST;

// vertex shader
v2f_surf vert_surf (appdata_full v) {
  v2f_surf o;
  o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  o.pack0.xy = TRANSFORM_TEX(v.texcoord, _NormalMap);
  TANGENT_SPACE_ROTATION;
  o.TtoW0 = mul(rotation, ((float3x3)_Object2World)[0].xyz)*unity_Scale.w;
  o.TtoW1 = mul(rotation, ((float3x3)_Object2World)[1].xyz)*unity_Scale.w;
  o.TtoW2 = mul(rotation, ((float3x3)_Object2World)[2].xyz)*unity_Scale.w;
  return o;
}

// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
  // prepare and unpack data
  #ifdef UNITY_COMPILER_HLSL
  Input surfIN = (Input)0;
  #else
  Input surfIN;
  #endif
  surfIN.uv_NormalMap = IN.pack0.xy;
  #ifdef UNITY_COMPILER_HLSL
  SurfaceOutput o = (SurfaceOutput)0;
  #else
  SurfaceOutput o;
  #endif
  o.Albedo = 0.0;
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;

  // call surface function
  surf (surfIN, o);
  fixed3 worldN;
  worldN.x = dot(IN.TtoW0, o.Normal);
  worldN.y = dot(IN.TtoW1, o.Normal);
  worldN.z = dot(IN.TtoW2, o.Normal);
  o.Normal = worldN;

  // output normal and specular
  fixed4 res;
  res.rgb = o.Normal * 0.5 + 0.5;
  res.a = o.Specular;
  return res;
}

ENDCG

}

	// ---- deferred lighting final pass:
	Pass {
		Name "PREPASS"
		Tags { "LightMode" = "PrePassFinal" }
		ZWrite Off

CGPROGRAM
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_prepassfinal
#pragma exclude_renderers flash
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_PREPASSFINAL
#include "UnityCG.cginc"
#include "Lighting.cginc"

#define INTERNAL_DATA half3 TtoW0; half3 TtoW1; half3 TtoW2;
#define WorldReflectionVector(data,normal) reflect (data.worldRefl, half3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal)))
#define WorldNormalVector(data,normal) fixed3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal))

// Original surface shader snippet:
#line 18 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif

		//#pragma surface surf Lambert

		float4 _MainTint;
		sampler2D _MainTex;

		sampler2D _NormalMap;

		samplerCUBE _Cubemap;

		float _ReflAmount;



		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			INTERNAL_DATA
		};

		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

			o.Normal=normals;

			//上面使用法线贴图中的法线数据 替代了 原来的法线数据。

			//法线被修改了,就不能 直接用原来的 内置属性 worldRefl 这个反射向量,而是要通过 WorldReflectionVector(IN,o.Normal)来获取。

			//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
			o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

			o.Albedo = c.rgb * _MainTint;
			o.Alpha = c.a;
		}
		

// vertex-to-fragment interpolation data
struct v2f_surf {
  float4 pos : SV_POSITION;
  float4 pack0 : TEXCOORD0;
  float4 screen : TEXCOORD1;
  fixed4 TtoW0 : TEXCOORD2;
  fixed4 TtoW1 : TEXCOORD3;
  fixed4 TtoW2 : TEXCOORD4;
#ifdef LIGHTMAP_OFF
  float3 vlight : TEXCOORD5;
#else
  float2 lmap : TEXCOORD5;
#ifdef DIRLIGHTMAP_OFF
  float4 lmapFadePos : TEXCOORD6;
#endif
#endif
};
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
#endif
float4 _MainTex_ST;
float4 _NormalMap_ST;

// vertex shader
v2f_surf vert_surf (appdata_full v) {
  v2f_surf o;
  o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
  o.pack0.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
  float3 viewDir = -ObjSpaceViewDir(v.vertex);
  float3 worldRefl = mul ((float3x3)_Object2World, viewDir);
  TANGENT_SPACE_ROTATION;
  o.TtoW0 = float4(mul(rotation, _Object2World[0].xyz), worldRefl.x)*unity_Scale.w;
  o.TtoW1 = float4(mul(rotation, _Object2World[1].xyz), worldRefl.y)*unity_Scale.w;
  o.TtoW2 = float4(mul(rotation, _Object2World[2].xyz), worldRefl.z)*unity_Scale.w;
  o.screen = ComputeScreenPos (o.pos);
#ifndef LIGHTMAP_OFF
  o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
  #ifdef DIRLIGHTMAP_OFF
    o.lmapFadePos.xyz = (mul(_Object2World, v.vertex).xyz - unity_ShadowFadeCenterAndType.xyz) * unity_ShadowFadeCenterAndType.w;
    o.lmapFadePos.w = (-mul(UNITY_MATRIX_MV, v.vertex).z) * (1.0 - unity_ShadowFadeCenterAndType.w);
  #endif
#else
  float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
  o.vlight = ShadeSH9 (float4(worldN,1.0));
#endif
  return o;
}
sampler2D _LightBuffer;
#if defined (SHADER_API_XBOX360) && defined (HDR_LIGHT_PREPASS_ON)
sampler2D _LightSpecBuffer;
#endif
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap;
sampler2D unity_LightmapInd;
float4 unity_LightmapFade;
#endif
fixed4 unity_Ambient;

// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
  // prepare and unpack data
  #ifdef UNITY_COMPILER_HLSL
  Input surfIN = (Input)0;
  #else
  Input surfIN;
  #endif
  surfIN.uv_MainTex = IN.pack0.xy;
  surfIN.uv_NormalMap = IN.pack0.zw;
  surfIN.worldRefl = float3(IN.TtoW0.w, IN.TtoW1.w, IN.TtoW2.w);
  surfIN.TtoW0 = IN.TtoW0.xyz;
  surfIN.TtoW1 = IN.TtoW1.xyz;
  surfIN.TtoW2 = IN.TtoW2.xyz;
  #ifdef UNITY_COMPILER_HLSL
  SurfaceOutput o = (SurfaceOutput)0;
  #else
  SurfaceOutput o;
  #endif
  o.Albedo = 0.0;
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;

  // call surface function
  surf (surfIN, o);
  half4 light = tex2Dproj (_LightBuffer, UNITY_PROJ_COORD(IN.screen));
#if defined (SHADER_API_MOBILE)
  light = max(light, half4(0.001));
#endif
#ifndef HDR_LIGHT_PREPASS_ON
  light = -log2(light);
#endif
#if defined (SHADER_API_XBOX360) && defined (HDR_LIGHT_PREPASS_ON)
  light.w = tex2Dproj (_LightSpecBuffer, UNITY_PROJ_COORD(IN.screen)).r;
#endif

  // add lighting from lightmaps / vertex / ambient:
  #ifndef LIGHTMAP_OFF
    #ifdef DIRLIGHTMAP_OFF
      // dual lightmaps
      fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
      fixed4 lmtex2 = tex2D(unity_LightmapInd, IN.lmap.xy);
      half lmFade = length (IN.lmapFadePos) * unity_LightmapFade.z + unity_LightmapFade.w;
      half3 lmFull = DecodeLightmap (lmtex);
      half3 lmIndirect = DecodeLightmap (lmtex2);
      half3 lm = lerp (lmIndirect, lmFull, saturate(lmFade));
      light.rgb += lm;
    #else
      // directional lightmaps
      fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
      fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
      half4 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 1);
      light += lm;
    #endif
  #else
    light.rgb += IN.vlight;
  #endif
  half4 c = LightingLambert_PrePass (o, light);
  c.rgb += o.Emission;
  return c;
}

ENDCG

}

	// ---- end of surface shader generated code

#LINE 64

	} 
	FallBack "Diffuse"
}

果然,原来 INTERNAL_DATA 就是一个宏……



而且会发现在生成的代码中不止一个 INTERNAL_DATA。这是因为生成的代码中有多个PASS

forward rendering additive lights pass:
deferred lighting base geometry pass:
deferred lighting final pass:
forward rendering base pass:


每个PASS 都有一个,所以有好几个。


那么,不使用  INTERNAL_DATA,而是直接使用 具体的内容,可以吗?来试一下。

首先,从Shader 中删掉 INTERNAL_DATA。

Unity 报了以下错误

Shader error in 'CookBookShaders/Chapt4-4/Cubemap_NormalMap': invalid subscript 'TtoW0' at line 59

是这一行的错

o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;

因为从上面 Shader 编译后的代码看到,WorldReflectionVector 也是一个宏

#define WorldReflectionVector(data,normal) reflect (data.worldRefl, half3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal)))


其实就是说,在WorldReflectionVector 这个函数里找不到 TtoW0 这个参数了。


然后,在 Input 结构体中添加 INTERNAL_DATA 的具体内容看看

		struct Input 
		{
			float2 uv_MainTex;

			float2 uv_NormalMap;

			float3 worldRefl;

			half3 TtoW0; half3 TtoW1; half3 TtoW2;
			
		};

法线 Shader 已经正常工作啦




B:在Input 中 添加 float3 worldRefl 和 INTERNAL_DATA ,给o.Normal 赋值的话,就可以 用 WorldReflectionVector(IN,o.Normal) 获取到 法线贴图 计算后的 反射向量。

原文是这样写的

在Input 结构体中还有更多的内置函数,其中一部分如下:

float3 viewDir :

Will contain view direction, for computing Parallax effects, rimlighting, and so on.

float4 COLOR :

Will contain interpolated per-vertex color.

float4 screenPos:

Will contain screen-space position for reflection effects. Used by WetStreet shader in Dark Unity, for example.

float3 worldPos :

Will contain world space position.

float3 worldRefl :

Will contain world reflection vector if Surface Shader does not write to o.Normal. See Reflect-Diffuse shader for example.

float3 worldNormal :

Will contain world normal vector if Surface Shader does not write to o.Normal.

float3 worldRef;INTERNAL_DATA:

Will contain world reflection vector if Surface Shader writes to o.Normal. To get the reflection vector based on per-pixel normal map, use WorldReflectionVector (IN,o.Normal). See Reflect-Bumped shader for example.

float3 worldNormal;INTERNAL_DATA:
Will contain world normal vector if Surface Shader writes to o.Normal. To get the normal vector based on per-pixel normal map, use WorldNormalVector (IN, o.Normal).



3、在 surf 函数中 添加了 读取 法线贴图的代码

//从法线贴图中提取法线信息,UnpackNormal这个函数在 CGInclude 文件夹中的Lighting中。
			float3 normals=UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));

4、上一节中直接使用 Input 中的 worldRefl 这个反射向量来获取立方图 采样。这一次需要使用上面说的 WorldReflectionVector 来计算世界反射向量。

//使用WorldReflectionVector 获得 基于法线贴图中的反射向量的 世界反射向量
o.Emission = texCUBE(_Cubemap,WorldReflectionVector(IN,o.Normal)).rgb * _ReflAmount;


示例项目打包下载:

http://pan.baidu.com/s/1c1TcSgS


2018-08-10 18:10:40 qq_38061677 阅读数 1863

法线贴图

法线贴图是比较常用的一种贴图,作用是使一些面数比较少的模型的纹理更加的精致和逼真。这样在大幅度减少了运行的性能消耗,也能达到比较好的游戏画面。unity3d中有比较简单的法线贴图,就是看起来与3D效果无异的2D贴图。如果做3D模型的话,就会浪费显示芯片,使游戏性能下降,便会用法线贴图,既不影响玩家体验游戏又不影响游戏性能.
下面让我们一制作一张简单逼真的纹理贴图,让它作用在一个游戏对象上。

效果图

这里写图片描述

第一首先在Project栏里面创建一个TextPicture文件夹来放置图片,我在网上找的一张砖头的图片,鼠标拖入项目Assets/Texture文件夹中。多复制一张,修改TextType属性

这里写图片描述

TextType是纹理的类型
Default:纹理
Normal map:法线贴图
Editor GUI and Legacy GUI:编辑器GUI和传统GUI。Sprite(2D and UI):精灵,用于2D对象和UGUI贴图。
Cursor:自定义光标。
Cookie:场景光的Cookie。
Lightmap:光照贴图,将贴图编码成特定的格式,并对纹理数据进行后处理。
SingleChannel:单通道。
选择Normal Map
Create from Grayscale勾上
Create from Grayscale:从灰度高度图(Heightmap)创建。
有两个属性可以调节
Bumpiness:崎岖度。
Filtering:滤波算法。
1.Smooth:平滑,标准前向差分算法。
2.Sharp:尖锐,Sobel滤波器。
崎岖度根据需要,自由调节,滤波算法,我们选择尖锐Sharp
这里写图片描述
选择完后,记得点击Apply。

第二步,制作Metrial。创建一个Metrial文件夹,在里面创建一个Metrial。点击Project的Create来创建

这里写图片描述

Material里面最重要的是Shader,Shader(着色器):Shader负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。

材质(Meterial):将输入的贴图或者颜色,加上对应的Shader,以及对Shader的特定的参数设置,将这些打包在一起就是一个材质了。我们便可以将材质赋予合适的renderer(渲染器)来进行渲染。
单击Meterial的Inspecter面板,在Albedo属性上选择多复制的那张正常图片,然后在Normal Map上选择制作好的法线贴图。一个带有法线贴图的Meteral做好了,重命名wall.
这里写图片描述

第三步,新建一个cude,把我们建好的材质包鼠标托上去就建好了。

这里写图片描述
这里写图片描述

光线烘培

当我们进行游戏时,有些场面避免不了使用大量灯光渲染。大量的灯光渲染对于计算机来说无疑是个负担,为了尽量节省计算机的显卡资源,对于一些静态的不动的物体,可以采用烘培的方法,把灯光的效果,制作成游戏物体纹理的一部分,这样即使没有灯光的渲染。该游戏物品也会呈现一样的视觉效果。烘培非常的常见,请注意需要烘培的的游戏物品一定时静止的
下面做一个实例

第一步 在场景中创键一个cude和一个plane,为了效果更佳,把直射光源的强度稍微调小一点。

这里写图片描述

第二步、把所有要渲染的游戏对象全选,在Inspecto面板里面单击Static选项,选择Lightmap static。

这里写图片描述
创建一个点光源做为要渲染的的光源,为了着重看出效果。我把光源的颜色改成紫色。在实际的需求中,大家可以根据自己的需求进行改变。将需要烘焙的光源的Mode模式改为Baked
这里写图片描述

第三步、选择菜单栏的window选项里面的Lighting的Settings面板,在Object Maps选项中讲勾选的Auto Generate按钮去掉后,单击Generate Lighting按钮。场景就在烘焙中,右下角会出现进度条,当进度条完成后。烘培完成。完成烘焙后,即使删掉了光源,游戏物品依然具有之前的纹理。

这里写图片描述这里写图片描述
这里写图片描述

最后完成。神奇的一幕来了,把目标光源删除掉后,纹理依旧
这里写图片描述

没有更多推荐了,返回首页