UnityShader学习之旅--纹理

纹理

定义 目的 技术 方法
纹理 物体表面的纹路 使用一张图片来控制模型的外观 纹理映射技术 逐纹素地控制模型的颜色

纹理映射坐标

定义 表示 归一化
纹理映射坐标 顶点在纹理中对应的2D坐标 二维变量(U,V)==UV坐标 【0,1】
  • 纹理的应用
  • 使用一张纹理来代替物体的漫反射颜色
/*****************************************************
//单张纹理
//使用单张纹理来作为模拟的颜色 仍使用Blinn-Phong光照模型计算光照
******************************************************/
Shader "SingleTexture"{
     
	Properties{
     
	   _Color("Color Tint",Color) = (1,1,1,1)
	   _MainTex("Main Tex", 2D) = "white"{
     }
	   _Specular("Specular", Color) = (1,1,1,1)
	   _Gloss("Gloss", Range(8,256)) = 10
	}
	SubShader{
     
	  Pass{
     
	  	Tags{
     "LightMode" = "ForwardBase"}
	  	CGPROGRAM
	  	#pragma vertex vert
	  	#pragma fragment frag
	  	#include "Lighting.cginc"
	  	struct a2v{
     
		  float4 vertex : POSITION;
		  float4 normal : NORMAL;
		  float4 texcoord:TEXCOORD0; //模型的第一组纹理坐标存储到该变量中
		};
	  	struct v2f{
     
		  float4 pos :SV_POSITION;
		  float3 worldNormal:TEXCOORD0;
		  float3 worldPos:TEXCOORD1;
		  float2 uv:TEXCOORD2; //纹理坐标 
		};
		float4 _Color;
		float4 _Specular;
		float _Gloss;
		sampler2D _MainTex;
		float4 _MainTex_ST;//ST: scale->translation. ST.xy:缩放值 ST.zw:偏移值	
		v2f vert(a2v v){
     
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.worldNormal = UnityObjectToWorldNormal(v.normal);
			o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			//对顶点纹理坐标进行变换
			o.uv = TRANFORM_TEX(v.texcoord, _MainTex);
			return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
			//法线方向
			fixed3 wNormal = normalize(i.worldNormal);
			//光照方向
			fixed3 wlightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
			//视角方向
			fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			fixed3 halfdir = normalize(wlightdir  + wViewdir );
			fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
			fixed3 ambient = UNITY_LIGHTMODEL_AMBINET.xyz * albedo;
			fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(wNormal, wlightdir));
			fixed3 specular = _LightColor.rgb * _Specular.rgb * pow(max(0, dot(wNormal, halfdir)), _Gloss);
			fixed3 color = ambient + diffuse + specular;
			return fixed4(color, 1);
		}

	  	ENDCG
	   }
	}
	Fallback "Specular"
}
tex2D(_MainTex, IN uv)
/// 
/// 对纹理进行采样
/// 
/// 需要被采样的纹理
/// float2 类型的纹理坐标
///  纹素值 

UnityShader学习之旅--纹理_第1张图片

  • 纹理面板
纹理属性
纹理类型 1.Texture 默认 2. Normal map 3.Cubemap
Wrap Mode 决定当纹理坐标超过[0,1]范围后如何平铺 repeat
去整数取小数,纹理不断重复
clamp
>1取1,<0取0
Filter Mode 当纹理由于变换而发生拉伸时需要的滤波模式
效果一次增强,但性能也变大
会影响放大缩小时得到图片质量
point
最近邻滤波像素1个
Bilinear
线性滤波找4个邻近像素
Trilinear
同Bilinear,与多级渐远纹理混合
纹理缩小 复杂,处理抗锯齿问题 多级渐远纹理(mipmapping)
最大尺寸
Max Size
理想大小为2的n次幂
纹理模式
Format
决定了Unity内部使用哪种格式来存储该纹理 纹理格式精度越高,占用内存越大,效果越好
Create from Grayscale 用于从高度图中生成法线纹理 高度图导入后需要勾选==切线空间下的法线纹理
Bumpiness 控制凹凸程度
Filtering 决定使用哪种方式计算凹凸程度 Smooth
法线纹理会比较平滑
Sharp
Sobel率生成法线
  • 凹凸映射

使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。并不会真的改变模型的顶点位置,只是看起来好像

凹凸映射实现 实现方式 存储的内容 优点 缺陷
高度映射 一张高度纹理模拟表面位移,得到修改后的法线值 高度值,用于表示模型表面局部的海拔高度
颜色越浅表面向外凸起
直观 计算复杂,实时计算时不能直接得到表面法线,需要由像素的灰度值计算,耗性能
法线映射 一张法线纹理直接存储表面法线 表面的法线方向

法线纹理

坐标空间 定义 法线纹理颜色 模-优点1 模优点2 切-优点1 切-优点2 切-优点3 切-优点4
模型空间 模型空间中的表面法线存储在一张纹理中 五颜六色
所有的法线坐标都在同一个模型空间中,法线方向却不一样
实现简单,更加直观
计算少–不需要原始的法线切线等信息
提供平滑的边界
同一坐标系的法线信息,边界处插值得到的法线可以平滑变换
绝对法线信息-仅用于创建它时的那个模型,其他模型失效 得不到效果 同上 每个方向都有可能,必须存储3个方向的值,不可压缩
切线空间 模型顶点的切线空间来存储法线
切线原点–顶点本身、z轴–顶点的法线方向、x轴–顶点的切线方向、y轴–法线与切线的叉积
浅蓝色
表面每个点都有自己的切线空间
要求纹理映射连续 法线信息依靠纹理坐标方向的插值,可能在边缘处有缝合迹象 自由度高
记录的是相对法线信息,可应用于一个完全不同的网格上
UV动画
移动UV坐标实现凹凸移动效果
重用法线纹理 可压缩–法线的Z方向总是正方向,可由xy推导
/******************************************************
切线空间下计算光照模型
1.顶点着色器中把视角方向和光照方向从模型空间变换到切线空间中
2.片元着色器中通过纹理采样得到切线空间下的法线
3.与切线空间下的视角方向、光照方向等进行计算得到光照结果
*******************************************************/
Shader"TangentSpace"{
     
       Properties{
     
        _Color("Color Tint", Color) = (1,1,1,1)
        _MainTex("Main Tex", 2D) = "white"{
     }
        _Specular("Specular", Color)= (1,1,1,1)
        _Gloss("Gloss", Range(8,256)) = 10
        _BumpMap("Normal Map", 2D) = "bump"{
     } //法线纹理
        _BumpScale("Bump Scale", Float) = 1.0 //控制凹凸程度,0:法线纹理不会对光照产生任何影响
       }
       SubeShader{
     
       	Pass{
     
	  Tags{
     "LightMode" = "ForwardBase"}
	  CGPROGRAM
	  #pragma vertex vert
	  #pragma fragment frag
	  #include "Lighting.cginc"
	  
	  struct a2v{
     
	    float4 vertex:POSITION;
	    float3 normal:NORMAL;
	    float4 tangent:TANGENT; //切线,w分量决定切线空间的副切线的方向
	    float4 texcoord:TEXCOORD0; 
	  };
	  struct v2f{
     
	    float4 pos:SV_POSITION;
	    float3 lightdir:TEXCOORD0;//计算切线空间下的光照方向
	    float3 viewdir:TEXCOORD1; //计算切线空间下的视角方向
	    float4 uv:TEXCOORD2; //xy存储_MainTex, zw存储_BumpMap
	  };
	  float4 _Color;
	  sampler2D _MainTex;
	  float4 _MainTex_ST;
	  float4 _Specular;
	  sampler2D _BumpMap;
	  float4 _BumpMap_ST
	  fixed  _BumpScale;
	  float  _Gloss;
	  v2f vert(a2v v){
     
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.uv.xy =  TRANSFORM_TEX(v.texcoord, _MainTex);
		o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
		TANGENT_SPACE_ROTATION;
		o.lightdir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
		o.viewdir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
		return o;
	  }
	  fixed4 frag(v2f i):SV_Target{
     
	  	fixed3 tLightdir = normalize(i.lightdir );
	  	fixed3 tViewdir = normalize(i.viewdir);
	  	fixed3 tHalf = normalize(tLightdir + tViewdir);
		fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); //法线纹理的采样
		fixed3 tNormal = UnpackNormal(packedNormal); //切线空间下的法线方向
		tNormal.xy *= _BumpScale;//
	  	tNormal.z = sqrt(1.0 - saturate(dot(tNormal.xy, tNormal.xy)));//xyz啥关系呢?x^2 + y^2 + z^2 = 1
		
		fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
		fixed3 ambinet = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
		fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tNormal, tLightdir));
		fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tNormal, tHalf)), _Gloss);
		fixed3 color = ambient + diffuse + specular;
		return fixed4(color, 1);
	  }
	  ENDCG
	}
    }
    Fallback "Specular"
}

UnityShader学习之旅--纹理_第2张图片

TANGENT_SPACE_ROTATION 《==》
float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w; //副切线 = 法线 与 切线的叉积
float3x3 rotation = float3x3(v.tangent.xyz, binormal,v.normal);
cross(a, b)
/// < summary>
/// 计算向量积
///

UnpackNormal(packNormal)
/// 
/// 计算正确的法线方向
///  法线纹理设置为Normal Map时,unity会根据平台来选择不同的压缩方法。
/// 
/// 法线经过映射后的像素值,利用tex2D对_BumpMap采样得到
///  法线方向 
若无设置Normal Map需要手动转换 
(packedNormal.xy * 2 - 1)像素[0,1]与法线[-1,1]的映射 ==>pixel = (n + 1)/2 
/******************************************************
在世界空间下计算光照模型
1.顶点着色器中计算从切线空间到世界空间中的转换矩阵
2.片元着色器中把法线方向从切线空间变换到世界空间下
变换矩阵的计算:由顶点的切线、副切线和法线在世界空间下的表示得到
*******************************************************/
Shader"WorldSpace"{
     
	Properties{
     
	  _Color("Color Tint", Color) = (1,1,1,1)
	  _MainTex("Main Tex", 2D) = "white"{
     }
	  _BumpMap("Bump Map") = "bump"{
     } //法线纹理
	  _BumpScale("Bump Scale", Float) = 1.0
	  _Specular("Specular", Color)=(1,1,1,1)
	  _Gloss("Gloss", Range(8, 256)) = 10
	}
	SubShader{
     
		Pass{
     
		  Tags{
     "LightMode" = "ForwardBase"}
		  CGPROGRAM
		  #pragma vertex vert
		  #pragme fragment frag
		  #include "Lighting.cginc"
		  struct a2v{
     
		      float4 vertex:POSITION;
		      float4 tangent:TANGENT;
		      float4 texcoord:TEXCOORD0;
		      float3 normal:NORMAL;	
		  };
		  // 切线空间到世界空间的变换矩阵
		  struct v2f{
     
		     float4 pos:SV_POSITION;
		     float4 TtoW0:TEXCOORD0;
		     float4 TtoW1:TEXCOORD1;
		     float4 TtoW2:TEXCOORD2; 
		     float4 uv:TEXCOORD3;
		  };
		  float4 _Color;
		  float4 _Specular;
		  sampler2D _MainTex;
		  float4 _MainTex_ST;
		  sampler2D _BumpMap;
		  float4 _BumpMap_ST;
		  float _Gloss;
		  v2f vert(a2v v){
     
		     v2f o;
		     o.pos = UnityObjectToClipPos(v.vertex);
		     o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
		     o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
		     
		     float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		     float3 wNormal = UnityObjectToWorldNormal(v.normal);
		     float3 wTangent = UnityObjetToWorldDir(v.tangent.xyz);
		     float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
		     
		     o.TtoW0 = float4(wTangent.x, wBinormal.x, wNormal.x, worldPos.x);
		     o.TtoW1 = float4(wTangent.y, wBinormal.y, wNormal.y, worldPos.y);
		     o.TtoW2 = float4(wTangent.z, wBinormal.z, wNormal.z, worldPos.z);
		     return o;
		  }
		  fixed4 frag(v2f i):SV_Target{
     
		  	float3 wPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
		  	float3 wLightdir = normalize(UnityWorldSpaceLightDir(wPos));
		  	float3 wViewdir = normalize(UnityWorldSpaceViewDir(wPos));
		  	float3 wHalf = normalize(wLightdir + wViewdir);
		  	
		  	fixed3 packNormal = tex2D(_BumpMap, i.uv.zw);
		  	fixed3 bump = UnpackNormal(packNormal);
		  	bump.xy *= _BumpScale;
		  	bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
		  	//将法线从切线空间转换到世界空间中。。矩阵每一行与法线点乘
		  	bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
		  	fixed albedo = tex2D(_MainTex, i.uv.xy);
		  	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
		  	fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(bump, wLightdir));
		  	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump, wHalf)), _Gloss); 
		  	fixed3 color = ambient + diffuse + specular ;
		  	return fixed4(color, 1.0);
		  }
		  ENDCG
		}
	}

}

UnityShader学习之旅--纹理_第3张图片

  • 法线纹理类型

Unity根据不同平台对纹理进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样。

压缩方式 纹理a通道 g通道 r通道 b通道
DXT5nm 法线x分量 y分量 舍弃 舍弃 z分量由xy分量推导
  • 渐变纹理
/******************************************************
使用一张渐变纹理来控制漫反射光照
*******************************************************/
Shader"RampTexture"{
     
  Properties{
     
   _Color("Color Tint", Color) = (1,1,11)
   _RampTex("Ramp Tex", 2D) = "white"{
     } //渐变纹理
   _Specular("Specular", Color) = (1,1,1,1)
   _Gloss("Gloss", Range(8,256)) = 10
  }
  SubShader{
     
  	Pass{
     
  		Tags{
     "LightMode" = "ForwardBase"}
  		CGPROGRAM
  		#pragma vertex vert
  		#pragma fragment frag
  		#include "Lighting.cginc"
  		struct a2v{
     
			float4 vertex:POSITION;
			float3 normal:NORMAL;
			float4 texcoord:TEXCOORD0;
		};
  		struct v2f{
     
  			float4 pos:SV_POSITION;
  			float3 worldNormal:TEXCOORD0;
  			float3 worldPos:TEXCOORD1;
  			float2 uv:TEXCOORD2;
  		};
  		float4 _Color;
  		float4 _Specular;
  		float _Gloss;
  		sampler2D _RampTex;
  		float4 _RampTex_ST;
  		v2f vert(a2v v){
     
		  v2f o;
		  o.pos = UnityObjectToClipPos(v.vertex);
		  o.worldNormal = UnityObjectToWorldNormal(v.normal);
		  o.worldPos = mul(unity_objectToWorld, v.vertex).xyz;
		  o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
		  return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
		  fixed3 wNormal = normalize(i.worldNormal);
		  fixed3 wlightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
		  fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
		  fixed3 whalfdir = normalize(wlightdir + wViewdir);
		  //半兰伯特光照计算[0,1]
		  fixed halfLambert = 0.5*dot(wNormal, wLightdir) + 0.5; 
		  //利用半兰伯特构建的纹理坐标对渐变纹理采样
		  fixed3 diffuseColor = tex2D(_RampTex, half2(halfLambert, halfLambert )).rgb * _Color.rgb;
		  fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		  fixed3 diffuse = _LightColor0.rgb * diffuseColor;
		  fixed3 specular = _LightColor.rgb * _Specular.rgb * pow(saturate(dot(wNormal, whalfdir)), _Gloss);
		  fixed3 color = ambient + diffuse + specular;
		  return fixed4(color, 1.0);
		}
  		ENDCG
  	    }
	}
	Fallback "Specular"
}

UnityShader学习之旅--纹理_第4张图片

  • 遮罩纹理
/******************************************************
使用高光遮罩纹理,逐像素的控制模型表面的高光反射强度
1.采样得到遮罩纹理的纹素值
2.使用其中某个通道的值来与某找那个表面属性进行相乘
当通道的值为0时,可以保护表面不受该属性的影响
*******************************************************/
Shader "Mask"{
     
	Properties{
     
	  _Color("Color Tint", Color) = (1,1,1,1)
	  _MainTex("Main Tex", 2D) = "white" {
     }
	  _BumpMap("Bump Map", 2D) = "bump" {
     } //法线纹理
	  _BumpScale("Bump Scale", Float) = 1.0
	  _SpecularMask("Specular Mask", 2D) = "white"{
     }
	  _SpecularScale("Specular Scale", Float) = 1.0
	  _Specular("Specular", Color) = (1,1,1,1)
	  _Gloss("Gloss", Range(8, 256)) = 10
	}
	SubShader{
     
		Pass{
     
		   Tags{
     "LightMode" = "ForwardBase"}
		   CGPROGRAM
		   #pragma vertex vert
		   #pragma fragment frag
		   #include "Lighting.cginc"
			
		   struct a2v{
     
		   	float4 vertex : POSITION;
		   	float3 normal :NORMAL;
		   	float4 tangent :TANGENT;
		   	float4 texcoord:TEXCOOR0;
		   };
		   struct v2f{
     
		   	float4 pos:SV_POSITION;
		   	float3 tViewdir:TEXCOORD0;
		   	float3t tLightDir:TEXCOORD1;
		   	float3 uv:TEXCOORD2;
		   };
		   float4 _Color;
		   sampler2D _MainTex;
		   float4 _MainTex_ST;
		   sampler2D _BumpMap;
		   sampler2D _SpecularMask;
		   fixed _BumpScale;
		   float _Gloss;
		   float4 _Specular;
		   fixed _SpecularScale;
		   v2f vert(a2v v){
      //模型空间转换到切线空间
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
			TANGENT_SPACE_ROTATION;
			o.tViewdir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
			o.tLightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
			return o;
		    }
		    fixed4 frag(v2f i):SV_Target{
     
		    	fixed3 tViewdir = normalize(i.tViewdir);
		    	fixed3 tLightdir = normalize(i.tLightDir);
		    	fixed3 tHalfdir = normalize(tViewdir+tLightdir);
			//获取切线空间的法线
			fixed3 tNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
			tNormal.xy *= _BumpScale;
			tNormal.z = sqrt(1- saturate(dot(tNormal.xy, tNormal.xy)));
			
			fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
			fixed3 ambinet = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
			fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tNormal, tLightdir));
			//遮罩纹理采样,来控制高光反射的强度
			fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
			fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tNormal, tHalfdir)), _Gloss) * specularMask;
			fixed3 color = ambient + diffuse + specular;
			return fixed4(color, 1.0)
		    }
		   ENDCG 
		}
	}
	Fallback "Specular"
}

UnityShader学习之旅--纹理_第5张图片

  • 立方体纹理

是环境映射的一种实现方法。环境映射可以模拟物体周围的环境,使用了环境映射的物体可以反射出周围的环境
立方体一个有6张图像,对应了一个立方体的6张图像

环境映射立方体纹理的方法 方法
特殊布局的纹理创建 1.特殊纹理类似立方体展开图的交叉布局等
2.纹理设置为Cubemap
可以对纹理数据进行压缩,支持修正、光滑反射和HDR等 HDR
手动创建一个Cubemap资源 项目中创建一个Cubemap,再赋值6张纹理
脚本生成 Camera.RenderToCubmap函数实现 不需要提前准备好立方体纹理,可以从任意位置观察到的场景图像存储到6张图像中,从而创建
环境映射应用
立方体纹理 模拟出金属质感的材质
反射 物体看起来像镀了层金属 1.入射光线的方向和表面法线方向计算反射方向
2.反射方向对立方体纹理采样
折射 传播方向发生改变 1.一种介质斜射到另一种介质中(折射率不同)
N1sin = N2sin1
  • 反射
/******************************************************
模拟反射效果
1.通过入射光线的方向和表面法线的方向来计算反射方向 
2.利用反射方向对立方体纹理采样
*******************************************************/
Shader "Reflection"{
     
	Properties{
     
	  _Color("Color Tint", Color) = (1,1,1,1)
	  _ReflectColor("Reflect Color", Color) = (1,1,1,1) //控制反射颜色
	  _ReflectAmount("Reflect Amount", Range(0,1)) = 1  //控制材质的反射程度
	  _CubeMap("Cube Map", Cube) = "_Skybox"{
     }          //模拟反射的环境映射纹理
	}
	SubShader{
     
		Tags {
      "RenderType"="Opaque" "Queue"="Geometry"}
		Pass{
     
		Tags{
     "LightMode" = "ForwardBase"}
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "Lighting.cginc"
		#include "AutoLight.cginc"
		struct a2v{
     
		  float4 vertex:POSITION;
		  float3 normal:NORMAL;	
		};
		struct v2f{
     
		  float4 pos:SV_POSITION;
		  float3 worldPos:TEXCOORD0;
		  float3 worldNormal:TEXCOORD1;
		  SHADOW_COORDS(2)
		};
		float4 _Color;
		float4 _ReflectColor;
		fixed _ReflectAmount;
		samplerCUBE _CubeMap;
		v2f vert(a2v v){
     
		  v2f o;
		  o.pos = UnityObjectToClipPos(v.vertex);
		  o.worldNormal = UnityObjectToWorldNormal(v.normal);
		  o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		  TRANSFER_SHADOW(o);
		  return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
		  fixed3 wNormal = normalize(i.worldNormal);
		  fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
		  fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
		  fixed3 reflectdir = reflect(-wViewdir, wNormal);
		  fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		  fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
		  fixed3 reflection = texCUBE(_CubeMap, reflectdir).rgb * _ReflectColor.rgb;
		  UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
		  //漫反射与反射插值 * 衰减
		  fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
		  return fixed4(color, 1.0);
		}
		ENDCG
	  }
	}
	FallBack"Reflective/VertexLit"
}

UnityShader学习之旅--纹理_第6张图片

    texCUBE(_Cubemap, wRefl)
/// 
/// 对立方体纹理的采样
/// 
/// 需要被采样的立方纹理
/// 反射方向
///  纹素值 
  • 折射
/******************************************************
模拟折射效果
1.通过入射光线的方向和表面法线的方向来计算折射方向 
2.利用折射方向对立方体纹理采样
*******************************************************/
Shader"Refract"{
     
	Prpperties{
     
	_Color("Color Tint", Color) = (1,1,1,1)
	_RefractColor("Refract Color", Color) = (1,1,1,1) //折射颜色
	_RefractAmount("Refract Amount", Range(0,1)) = 1.0
	_RefractRate("Refract Rate", Range(0.1, 1)) = 0.5
	_Cubemap("Cube Map", Cube)="_Skybox"{
     }
	}
	SubPass{
     
	 	Tags{
     "Render" = "Opaque" "Queue"="Geometry"}
		Pass{
     
			Tags{
     "LightMode"="ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			struct a2v{
     
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};
			struct v2f{
     
				float4 pos:SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos :TEXCOORD1;
				float3 worldRefract:TEXCOORD2;
				float3 worldViewdir:TEXCOORD3;
				SHADOW_COORDS(4)
			};
			float4 _Color;
			float4 _RefractColor;
			fixed _RefractAmount;
			fixed _RefractRate;
			samplerCUBE _Cubemap;
			v2f vert(a2v v){
     
			  v2f o;
			  o.pos = UnityObjectToClipPos(v.vertex);
			  o.worldNormal = UnityObjectToWorldNormal(o.normal);
			  o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			  o.worldViewdir = UnityWorldSpaceViewDir(o.worldPos);
			  o.worldRefract = refract(normalize(o.worldViewdir), normalize(o.worldNormal), _RefractRate);
			  TRANSFER_SHADOW(o)
			  return o;
			}
			fixed4 frag(v2f i):SV_Target{
     
			  fixed3 wNormal = normlaize(i.worldNormal);
			  fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
			  
			  fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
			  fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
			  
			  fixed3 refraction = texCUBE(_Cubemap, o.worldRefract).rgb * _RefractColor.rgb;
			  UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			  fixed3 color = ambient + lerp(diffuse, refraction,  _RefractAmount) * atten;
			  return fixed4(color, 1);
			}
		
			ENDCG
		}
	}
	FallBack"Reflective/VertexLit"
}

UnityShader学习之旅--纹理_第7张图片

    refract(wLightdir, wNormal, Ratio)
/// 
/// 计算折射方向
/// 
/// 入射光线的方向,必须归一化
/// 表面法线,必须归一化
 /// 入射与折射的介质折射率之比
///  折射方向,模==入射光线的模
  • 菲涅耳反射

描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或者散射。被反射的光和入射光之间存在一定的比率关系,==>菲涅耳等式可以计算UnityShader学习之旅--纹理_第8张图片

/******************************************************
使用Schlick 菲涅尔近似模拟菲涅尔反射
1.利用Schlick计算fresnel变量
2.使用Fresnel来混合漫反射光照和反射光照
*******************************************************/
Shader"Frenel"{
     
	Properties{
     
	 _Color("Color Tint", Color) = (1,1,1,1)
	 _FresnelScale("Fresnel Scale", Range(0,1)) = 0.5
	 _Cubemap("Cube Map", Cube) = "_Skybox"{
     }
	}	
	SubShader{
     
		Tags{
     "RenderType"="Opaque" "Queue"="Geometry"}
		Pass{
     
		 Tags{
     "LightMode" = "ForwardBase"}
		 CGPROGRAM
		 #pragma vertex vert
		 #pragma fragment frag
		 #pragma multi_compile_fwdbase
		 #include "Lighting.cginc"
		 #include "AutoLight.cginc"
		 struct a2v{
     
		  float4 vertex:POSITION;
		  float3 normal:NORMAL;
		};
		struct v2f{
     
		  float4 pos:SV_POSITION;
		  float3 worldNormal:TEXCOORD0;
		  float3 worldPos:TEXCOORD1;
		  float3 worldReflect:TEXCOORD2;
		  float3 worldView:TEXCOORD3;
		  SHADOW_COORDS(4)
		};
		float4 _Color;
		fixed _FresnelScale;
		samplerCUBE _Cubemap;
		v2f vert(a2v v){
     
		  a2v o;
		  o.pos = UnityObjectToClipPos(v.vertex);
		  o.worldNormal = UnityObjectToWorldNormal(v.normal);
		  o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		  o.worldView = UnityWorldSpaceViewDir(o.worldPos);
		  o.worldReflect = reflect(-o.worldView, o.worldNormal);
		  TRANSFER_SHADOW(o);
		  return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
		  fixed3 wNormal = normalize(i.worldNormal);	
		  fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
		  fixed3 wViewdir = normlaize(i.);
		  fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		  fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
		  fixed fresnel = _FresnelScale +  (1-_FresnelScale)*(pow((1 -dot(wViewdir , wNormal)), 5));
		  fixed3 reflectdir = texCUBE(_Cubemap, i.worldReflect).rgb;
		  UNITY_LIGHT_ATTENUATION(atten, i, i.wordlPos);
		  fixed3 color = ambient + lerp(diffuse, reflectdir , saturate(fresnel ))*atten;
		  return fixed4(color, 1);
		}
		 ENDCG
		}
	}
	Fallback "Reflective/VertexLit"
}

UnityShader学习之旅--纹理_第9张图片

  • 渲染纹理
    使用渲染纹理的方式
方式 做法 优点
1 1.Project目录下创建一个渲染纹理
2.把某个摄像机的渲染目标设置成渲染纹理
3.渲染结果会实时更新到渲染纹理中,而不会显示在屏幕上
可以选择渲染纹理的分辨率、滤波模式等纹理属性
2 1.屏幕后处理时使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像
2.屏幕图像放在一张和屏幕分辨率等同的渲染纹理中
3.在自定义的Pass中把它们当成普通的纹理处理,实现屏幕特效
/******************************************************
使用渲染纹理模拟镜子效果
*******************************************************/
Shader"Mirror"{
     
	Properties{
     	
   	 _MainTex("Main Tex", 2D) = "white"{
     }
	}
	SubShader{
     
	Tags{
     "RenderType"="Opaque" "Queue"="Geometry"}
	 Pass{
     
	 CGPROGRAM
	 #pragma vertex vert
	 #pragma fragment frag
	 
	 struct a2v{
     
		float4 vertex:POSITION;
		float3 texcoord:TEXCOOR0;
	 };
	 struct v2f{
     
	 	float4 pos:SV_POSITION;
	 	float3 uv:TEXCOORD0;
	 };
	 sampler2D _MainTex;
	 float4 _MainTex_ST;
	 v2f vert(a2v v){
     
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.uv = v.texcoord;
		o.uv.x = 1- v.texcoodr.x;
		return o;
	 }
	 fixed4 frag(v2f i):SV_Target{
     
		return tex2D(_MainTex, i.uv);
	 }
	 ENDCG
	 }
	}
	
	Fallback Off
}
  • 玻璃效果
/******************************************************
使用渲染纹理模拟玻璃效果
1.使用一张法线纹理来修改模型的法线信息
2.通过一个Cubemap来模拟玻璃的反射。
3.使用GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移
4.对屏幕图像的采样来模拟近似的折射效果
*******************************************************/
Shader "GlassRefrction"{
     
	Properties{
     	
	_MainTex("MainTex", 2D) ="white"{
     }     		// 玻璃的材质纹理
	_BumpMap("Normal Map", 2D)="white"{
     }		// 玻璃的法线纹理
	_CubeMap("Environment CubeMap", Cube) = "_Skybox"{
     } //模拟反射的环境纹理
	_Distortion("Distortion", Range(0,100)) = 10	// 控制模拟反射的扭曲程度
	_RefractAmount("Refract Amount", Range(0.0,1.0)) = 1.0  //控制折射程度
      }
	SubShader{
     
	    Tags{
     "RenderType" = "Opaque" "Queue"="Transparent"} //玻璃是透明的,需要把所有不透明的先渲染
	    GrabPass{
     "_RefractionTex"} //抓取屏幕图像的Pass
	    Pass{
     
	     	Tags{
     "LightMode" = "ForwardBase"}
	     	CGPROGRAM
	     	#pragma vertex vert
	     	#pragma fragment frag
	     	struct a2v{
     
		  float4 vertex : POSITION;
		  float3 normal : NORMAL;
		  float4 tangent : TANGENT;
		  float3 texcoord:TEXCOORD0;
		};
	     	struct v2f{
     
		  float4 pos: SV_POSITION;
		  float4 iToW0:TEXCOOR0;
		  float4 iToW1:TEXCOOR1;
		  float4 iToW2:TEXCOOR2;
		  float4 uv:TEXCOORD3;
		  float4 scrPos:TEXCOORD4;
		};
		float4 _MainTex_ST ;
		sampler2D _MainTex;
		sampler2D _BumpMap;
		float4 _BumpMap_ST;
		sampler2D _RefractionTex;
		float4 _RefractionTex_TexelSize; //纹理的纹素大小
		samplerCUBE _CubeMap;
		float _Distortion;
		fixed _RefractAmount;
		v2f vert(a2v v){
     
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			float3 worldNormal = UnityObjectToWorldNormal(v.normal);
			float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
			float3 worldBinal = cross(worldNormal, worldTangent) * v.tangent.w;
			o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
			o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
			//存储顺序:副切线、切线、法线
			o.iToW0 = float4(worldTangent.x, worldBinal.x, worldNormal.x, worldPos.x);
			o.iToW1 = float4(worldTangent.y, worldBinal.y, worldNormal.y, worldPos.y);
			o.iToW2 = float4(worldTangent.z, worldBinal.z, worldNormal.z, worldPos.z);
			o.scrPos = ComputerGrapScreenPos(o.pos);
			return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
			fixed3 worldPos = fixed3(i.iToW0.w, i.iToW1.w, i.iToW2.w);
			fixed3 lightdir = Normalize(UnityWorldSpaceLightDir(worldPos));
			fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
			float2 offest = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
			i.scrPos.xy = offset +i.scrPos.xy;
			//透视除法获取真正的屏幕坐标,采样、模拟折射颜色
			fixed3 refraColor = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb; 
			bump = normalize(half3(dot(i.iToW0.xyz, bump), dot(i.iToW1.xyz, bump), dot(i.iToW2.xyz, bump)));
			fixed3 refldir = reflect(-lightdir, bump );
			//反射颜色
			float4 texColor = tex2D(_MainTex, i.uv.xy);
			fixed3 reflCor = texCUBE(_CubeMap, refldir).rgb *texColor.rgb;
			fixed3 final =reflCor * (1-_RefractAmount) + refraColor * _RefractAmount;
			return fixed4(final, 1.0);
		}
	     	ENDCG
	    }
	}
}
ComputerGrapScreenPos(pos)
///抓取屏幕图像的采样坐标
/// 顶点变换后的坐标
GrabPass 后续Pass访问屏幕图像 不同点
GrabPass{} _GrabTexture 多个物体抓取时,性能消耗大,每一个物体单独进行 每个物体的得到不同的屏幕图像
GrabPass{“Texture”} 自定义的Texture 每一帧只会执行一次抓取 所有物体使用同一张屏幕图像
  • 水波效果
模拟实时水面 高度图,不断修改水面的法线方向
模拟水流 时间相关变量对噪声纹理采样,利用法线信息,进行正常的反射+折射

菲涅尔系数

/******************************************************
 *水波效果
 1.立方体纹理作为环境纹理,模拟反射
 2.使用GrapPass获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标偏移
 3.对渲染纹理进行屏幕采样--模拟近似的折射效果
 * WaterWave.Shader
 *****************************************************/
 Shader "WaterWave"{
     
 	Properties{
     
		_Color("Main Tint", Color) = (0,1,0,1)  //控制水面颜色
		_MainTex("Main Tex", 2D) = "white"{
     }	//水面波纹材质纹理
		_WaveMap("Wave Map", 2D)="bump"{
     }	//噪声纹理生成的法线纹理
		_CubeMap("Cube Map", Cube) = "_Skybox"{
     }	//环境纹理
		_WaveXSpeed("Wave X Speed", Range(-0.1,0.1)) = 0.01 	//法线纹理在X方向上的平移速度
		_WaveYSpeed("Wave Y Speed", Range(-0.1,0.1)) = 0.01 	//法线纹理在Y方向上的平移速度
		_Distortion("Distortion", Range("0, 100")) 10		//控制模拟折射时图像的扭曲程度
	}
	SubShader{
     
		//设置渲染队列
		Tags{
     "Queue" = "Transparent" "RenderType"="Opaque"}
		//抓取屏幕图像
		GrabPass{
     "_RefractionTex"}
		Pass
		{
     
		CGPROGRAM
		#include "UnityCG.cginc"
		fixed4 _Color;
		sampler2D _MainTex;
		float4 _MainTex_ST;
		sampler2D _WaveMap;
		float4 _WaveMap_ST;
		samplerCUBE _CubeMap;
		fixed _WaveXSpeed;
		fixed _WaveYSpeed;
		float _Distortion;
		sampler2D _RefractionTex;
		float4 _RefractionTex_TexelSize;
		struct v2f{
     
			float4 pos : SV_POSITION;
			float4 scrPos :TEXCOORD0;
			float4 uv :TEXCOORD1;
			float4 TtoW0 : TEXCOORD2;
			float4 TtoW1 : TEXCOORD3;
			float4 TtoW2 : TEXCOORD4;
		};
		v2f vert(appdata_base v){
     
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
			o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
			//获取抓取屏幕图像的采样坐标
			o.scrPos = ComputeGrabScreenPos(o.pos);
			float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
			fixed3 worldTangent = UnityObjectToWorldDir(v.tangent,xyz);
			fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
			//法线 切线 副切线
			o.TtoW0 = fixed4(worldNormal.x, worldTangent.x, worldBinormal.x, worldPos.x);
			o.TtoW1 = fixed4(worldNormal.y, worldTangent.y, worldBinormal.y, worldPos.y);
			o.TtoW2 = fixed4(worldNormal.z, worldTangent.z, worldBinormal.z, worldPos.z);
			return o;
		}
		fixed4 frag(v2f i):SV_Target{
     
			float3 worldPos = float3(i.TtoW0.x, i.TtoW1.y, i.TtoW2.z);
			float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
			float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
			
			//获取切线空间的法线
			fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
			fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
			fixed3 bump = normalize(bump1 + bump2);
			
			float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
			i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
			//透视除法,模拟折射颜色
			fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
			//将法线方向从切线空间转换到世界坐标中
			bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
			fixed4 texColor = tex3D(_MainTex, i.uv.xy + speed);
			//反射方向
			fixed3 refDir = reflect(-viewDir, bump);
			fixed3 relCol = texCUBE(_CubMap, reflDir).rgb * texColor.rgb *_Color.rgb;
    			//菲涅尔系数
    			fixed fresnel = pow(1-saturate(dot(viewDir, bump)), 4);
    			fixed3 finalColor = refrCol * fresnel + refrCol * (1 - fresnel);
    return fixed4(finalColor, 1.0); 
		}
		ENDCG
	    }
 	}
 	Fallback Off
 }

UnityShader学习之旅--纹理_第10张图片

  • 程序纹理

由计算机生成的图像。由特定的算法来创建个性化图像或非常真实的自然元素。
好处:可以使用各种参数来控制纹理的外观,可以得到更加丰富的动画和视觉效果。

/**********************************
程序纹理
生成波点纹理
***********************************/
[ExecuteInEditMode]
public class TextureGeneration:MonoBehaviour{
     
	public Material material = null;
	private  Texture2D _generateTexture = null;
	#region  定义材质的属性
	private float _textureWidth = 512f;
	public  float TextureWidth{
     
	 get {
     return _textureWidth;} set{
     _textureWidth = value;}
	}
	//背景颜色
	private Color _backcolor = Color.white;
	public Color  BackColor {
     
	 get{
     return _backcolor;} set{
     _backcolor = value;}
	}
	//波点颜色
	private Color _circlecolor = Color.black;
	public Color CircleColor{
     
	  get{
     return _circlecolor;}set{
     _circlecolor  = value;}
	}
	//模糊系数
	private float _blurFactor = 2.0f;
	public float BlurFactor{
     
	 get{
     return _blurFactor;} set{
     _blurFactor  = value;}
	}
	#endregion
	protected void Start(){
     
	  if(material == null){
     
		Renderer render = gameObject.GetComponent<Renderer>();
		if(render == null) return;
		material = render.sharedMaterial;
	  }
	  UpdateMaterial();
	}
	public void UpdateMaterial(){
     
	   if (null == material) return ;
	   _generateTexture  = GenerateProcedureTexture();
	   material.SetTexture("_MainTex", _generateTexture);
	}
	publuc Texture2D GenerateProcedureTexture(){
     
	  //贴图
	  Texture2D texture = new Texture2D(TextureWidth, TextureWidth);
	  //圆与圆之间的距离
	  float circleInterval = TextureWidth/5.0f;
	  float radius = TextureWidth/10.f;
	  for(int w = 0; w < TextureWidth; ++w){
     
	    for(int h = 0; h < TextureWidth; ++h )
	     {
     
	     	Color pixel = BackColor ;
	     	for(int i = 0; i < 4; i++){
     
		  for (int j = 0; j < 4 ; j++){
     
			//计算圆心的位置
			Vector2D circleCenter = new Vector2D(circleInterval*(i + 1), circleInterval*(j + 1));
			//计算当前像素与圆心的距离-半径
			float dist = Vector2D.Distance(new(w,h), circleCenter) - radius;
			Color mixColor = MixColor(CircleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1f, dist * BlurFactor));
			//与之前的颜色进行混合
			pixel = MixColor (pixel, mixColor, mixColor.a);
		  }
		}
		texture.SetPixel(w,h,pixel);
	     }
	  }
	  //像素值写入纹理中
	  texture.Apply();
	  return texture;
	}
	public Color MixColor(Color circle, Color Color2, float mixFactor){
     
	  Color mixColor = Color.white;
	  mixColor.r = Mathf.Lerp(circle.r, color2.r, mixFactor); 
	  mixColor.g = Mathf.Lerp(circle.r, color2.r, mixFactor); 
	  mixColor.b = Mathf.Lerp(circle.r, color2.r, mixFactor); 
	  mixColor.a = Mathf.Lerp(circle.r, color2.r, mixFactor); 
	  return mixColor;
	}
}

UnityShader学习之旅--纹理_第11张图片

Mathf.SmoothStep(from : float,to : float, t : float) : float
/// 
///在最小和最大值之间的插值,并在限制处渐入渐出
/// 
///最小值 
/// 最大值
///插值 

Unity Mathf 数学运算(C#)

  • Unity的程序材质

软件 Substance Designer
总结:

效果 实现方式 优化 缺陷
镜子效果 渲染纹理 图像模糊不清时可使用更高的分辨率或抗锯齿采样 更高的分辨率会影响带宽跟性能
纹理 采样 缺点
凹凸纹理 修改模型表面的法线 二维
渐变纹理 1.控制漫反射效果
2.插画风格的渲染效果
二维
遮罩纹理 1.控制模型表面的光照效果
2.混合多张纹理
可以充分利用遮罩纹理中的每个颜色通道存储不同的表面属性 二维
立方体纹理 环境映射的实现方法 实现简单快速,效果好 三维的纹理坐标2 1.引入新物体或物体发生移动时,需要重新生成立方体纹理
2.不能模拟多次反射结果,仅反射环境
渲染纹理

  1. 斯涅尔定律
    UnityShader学习之旅--纹理_第12张图片
    在这里插入图片描述 ↩︎

  2. 表示了我们世界空间下的一个3D方向。这个方向的矢量从立方体的中心除法,当它向外延伸时就会和立方体的6个纹理之一发生相交,采样得到的结果就是由该交点计算。 ↩︎

你可能感兴趣的:(UnityShader)