UnityShader源码2017---学习笔记与自我拓展032

源自Alpha-Parallax.shader

关于这个shader最重要的部分就是视差偏移ParallaxOffset这一块了

void surf (Input IN, inout SurfaceOutput o) {
    half h = tex2D (_ParallaxMap, IN.uv_BumpMap).w;
    float2 offset = ParallaxOffset (h, _Parallax, IN.viewDir);
    IN.uv_MainTex += offset;
    IN.uv_BumpMap += offset;

    fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
    o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
// Calculates UV offset for parallax bump mapping
inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{
    h = h * height - height/2.0;
    float3 v = normalize(viewDir);
    v.z += 0.42;
    return h * (v.xy / v.z);
}

这里我们先抛开surface shader,我们自己做一个基础的视差映射的VertexFragment shader。

Shader "ShaderStore/UnitShader2017/CPOthers/Parallax_Study"
{
	Properties
	{
		_Parallax("Height",Range(0.005,0.08))=0.02
		_MainTex ("Texture", 2D) = "white" {}
		_BumpMap("NormalMap",2D) = "bump"{}
		_ParallaxMap("Heightmap(A)",2D) = "black"{}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float4 tangent: TANGENT;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 lightDir:TEXCOORD1;
				float3 viewDir:TEXCOORD2;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			sampler2D _ParallaxMap;
			fixed _Parallax;
			float4 _LightColor0;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				
				float3 binormal = cross(v.normal,v.tangent.xyz)*v.tangent.w;
				float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);

				o.lightDir = normalize(mul(rotation,ObjSpaceLightDir(v.vertex)));
				o.viewDir = normalize(mul(rotation,ObjSpaceViewDir(v.vertex)));

				return o;
			}
			
			//Simple Parallax
			float2 offsetUV_Simple(fixed h,fixed height,fixed3 viewDir)
			{
				return viewDir.xy * h / viewDir.z * height;
			}


			fixed4 frag (v2f i) : SV_Target
			{
				
				float h = tex2D(_ParallaxMap,i.uv).a;
				float2 offset = offsetUV_Simple(h,_Parallax,i.viewDir);

				i.uv += offset;

				float3 normal = UnpackNormal(tex2D(_BumpMap,i.uv));

				float diff = max(0,dot(normal,i.lightDir));

				fixed3 maincol = tex2D(_MainTex, i.uv).rgb;
				fixed3 col = maincol * diff*_LightColor0.rgb;
				return float4(col,1);
			}
			ENDCG
		}
	}
}

接下来我们继续给这个shader添加视差映射部分。(自己的理解,可能有错)

正常情况下,我们观测B点,就在纹理坐标Ta点处,但是,我们想让他根据height map去左右一下,来增强一下bump的视觉效果。所以我们想在Ta出看到A的高度,在没有heightmap做distort前,我们看A直接就看到了Tb’处。

这里的viewDir是tangent space下的,也就是说viewDir.xy的轴向是uv.xy,所以offset其实就是我们将要 求的Voffset在viewDir.xy方向的偏移,Voffset = offset * normalize(viewDir.xy)

像三角形相似还有平行四边形的基本定理什么的就不说了,我们可以得到

offset/len(AB) = len(viewDir.xy)/viewDir.z

而len(AB)是我们heightmap sample出来的值,也就是h,即h = len(AB)

offset = len(viewDir.xy) * h / viewDir.z

这样

Voffset = normalize(ViewDir.xy)*len(viewDir.xy)*h/viewDir.z

整理一下

Voffset = viewDir.xy * h * / viewDir.z

这样我们就找到了偏移后的纹理坐标的

Tb = Ta + Voffset;

我们去代码里code it

fixed4 frag (v2f i) : SV_Target
{
	
	float h = tex2D(_ParallaxMap,i.uv).a;
	float2 offset = i.viewDir.xy * h / i.viewDir.z;
	i.uv += offset*_Parallax;
	float3 normal = UnpackNormal(tex2D(_BumpMap,i.uv));
	float diff = max(0,dot(normal,i.lightDir));
	fixed3 maincol = tex2D(_MainTex, i.uv).rgb;
	fixed3 col = maincol * diff*_LightColor0.rgb;
	return float4(col,1);
}

上图就是在与面片比较平行的视角去观察的截图,明显不如源码里的效果。

所以可以折头看一下源码里方法和黑魔法。

//Simple Parallax
float2 offsetUV_Simple(fixed h,fixed height,fixed3 viewDir)
{
	return viewDir.xy * h / viewDir.z * height;
}
//Builtin Parallax
float2 offsetUV_Builtin(fixed h,fixed height,fixed3 viewDir)
{
	h = h*height - height/2.0;
	viewDir.z += 0.42;
	return viewDir.xy * h /viewDir.z;
}

可以看到Builtin的方法里对采样heightmap得到的h,与可调节参数height进行了一种remap的操作,因为我们的可调节的参数height(properties里的_Parallax)的范围很小(【0.005,0.08】),这个操作相当于将h(范围是【0,1】)的值域的跨度变小了,然后又将viewDir(单位化的)的Z分量往正值方向偏移了0.42(Magic Num?)

然后,我开始google之旅。。。找到了这篇。。。。研究一下这个和上面那个一样的?这个这个这个这个这个还有这个

把研究的过程,记录一下吧。后来又找到了一个翻译版

视差映射的根本:利用高度图偏移了采样颜色和法线贴图的纹理坐标。

视差映射的目的:精确计算viewDir与高度图定义出来的表面的交点。

视差映射的效果:增强normal map带来的bump的效果。(所以视差映射的计算与normal的计算是一样的,都在切线空间下)

Parallax mapping 与 Parallax Mapping with Offset Limiting

因为视差映射的计算是在切线空间下的,所以,很自然的我们就把viewDir转到了切线空间下了,而切线空间中,xy是顺着uv坐标的方向,也就是说,viewDir.xy其实就可相当于uv的偏移量。如果viewDir.xy/viewDir.z作为uv的偏移量的话,这就是传统的Parallax mapping,如果不除以z分量就是Parallax Mapping with Offset Limiting

//Simple Parallax
float2 offsetUV_Simple(fixed h,fixed height,fixed3 viewDir)
{
	return viewDir.xy * h * height / viewDir.z;
}
//Parallax Mapping with Offset Limiting
float2 offsetUV_Limiting(fixed h,fixed height,fixed3 viewDir)
{
	return viewDir.xy * h * height;
}

这两种方法的优点就是,只做了一次对heightmap的采样(h的获取就是对Heightmap的采样),所以性能是不错的。

缺点也挺明显的,viewDir.xy * h * height或者(viewDir.xy * h * height / viewDir.z)这个得到的uv的偏移量并不是准确的值。也就是说并不是viewDir与heightmap定义表面的准确交点。

Steep Parallax Mapping

float2 offsetUV_Steep(sampler2D Hmap, float2 uv,fixed height,fixed3 viewDir,out float parallaxHeight)
{
	const float minLayers = 5;
	const float maxLayers = 15;
	float numLayers = lerp(maxLayers,minLayers,abs(dot(float3(0,0,1),viewDir)));
	//height per layer
	float layerHeight = 1.0/numLayers;
	float currentHeight = 0;
	float2 dtex = height * viewDir.xy/viewDir.z * layerHeight;
	float2 currentUV = uv;
	float h = tex2D(Hmap,uv).a;
	[unroll(16)]
	while(h > currentHeight)
	{
		currentHeight += layerHeight;
		currentUV -= dtex;
		h = tex2D(Hmap,currentUV).a;
	}
	parallaxHeight = currentHeight;
	return currentUV;
}

Relief Parallax Mapping 和 Parallax Occlusion Mapping (POM)以及Parallax Mapping and self-shadowing

就不写了,手游上一时还用不到这个,一般情况下都是trick了

Shader "ShaderStore/UnitShader2017/CPOthers/Parallax_Study"
{
	Properties
	{
		_Parallax("Height",Range(0.005,0.08))=0.02
		_MainTex ("Texture", 2D) = "white" {}
		_BumpMap("NormalMap",2D) = "bump"{}
		_ParallaxMap("Heightmap(A)",2D) = "black"{}
		[KeywordEnum(Builtin,Normal, Limiting, Steep,Relief,Occlusion)]_Type("Parallax Type",float) = 0
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma shader_feature _TYPE_BUILTIN _TYPE_NORMAL _TYPE_LIMITING _TYPE_STEEP _TYPE_RELIEF _TYPE_OCCLUSION
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float4 tangent: TANGENT;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 lightDir:TEXCOORD1;
				float3 viewDir:TEXCOORD2;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			sampler2D _ParallaxMap;
			fixed _Parallax;
			float4 _LightColor0;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				
				float3 binormal = cross(v.normal,v.tangent.xyz)*v.tangent.w;
				float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);

				o.lightDir = normalize(mul(rotation,ObjSpaceLightDir(v.vertex)));
				o.viewDir = normalize(mul(rotation,ObjSpaceViewDir(v.vertex)));

				return o;
			}
			
			//Simple Parallax
			float2 offsetUV_Simple(sampler2D Hmap, float2 uv,fixed height,fixed3 viewDir)
			{
				float h = tex2D(Hmap,uv).a;
				return uv + viewDir.xy * h * height / viewDir.z;
			}
			//Parallax Mapping with Offset Limiting
			float2 offsetUV_Limiting(sampler2D Hmap, float2 uv,fixed height,fixed3 viewDir)
			{
				float h = tex2D(Hmap,uv).a;
				return uv + viewDir.xy * h * height;
			}
			//Steep
			float2 offsetUV_Steep(sampler2D Hmap, float2 uv,fixed height,fixed3 viewDir,out float parallaxHeight)
			{
				const float minLayers = 5;
				const float maxLayers = 15;
				float numLayers = lerp(maxLayers,minLayers,abs(dot(float3(0,0,1),viewDir)));

				//height per layer
				float layerHeight = 1.0/numLayers;

				float currentHeight = 0;

				float2 dtex = height * viewDir.xy/viewDir.z * layerHeight;

				float2 currentUV = uv;

				float h = tex2D(Hmap,uv).a;

				[unroll(16)]
				while(h > currentHeight)
				{
					currentHeight += layerHeight;
					currentUV -= dtex;
					h = tex2D(Hmap,currentUV).a;
				}

				parallaxHeight = currentHeight;
				return currentUV;

			}

			//Relief
			float2 offsetUV_Relief(sampler2D Hmap,in float2 T,fixed height, in float3 V, out float parallaxHeight)
			{
				// determine required number of layers
				const float minLayers = 10;
				const float maxLayers = 15;
				float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0, 0, 1), V)));

				// height of each layer
				float layerHeight = 1.0 / numLayers;
				// depth of current layer
				float currentLayerHeight = 0;
				// shift of texture coordinates for each iteration
				float2 dtex = height * V.xy / V.z / numLayers;

				// current texture coordinates
				float2 currentTextureCoords = T;

				// depth from heightmap
				float heightFromTexture = tex2D(Hmap, currentTextureCoords).r;

				// while point is above surface
				[unroll(16)]
				while(heightFromTexture > currentLayerHeight) 
				{
					// go to the next layer
					currentLayerHeight += layerHeight; 
					// shift texture coordinates along V
					currentTextureCoords -= dtex;
					// new depth from heightmap
					heightFromTexture = tex2D(Hmap, currentTextureCoords).r;
				}

				///////////////////////////////////////////////////////////
				// Start of Relief Parallax Mapping

				// decrease shift and height of layer by half
				float2 deltaTexCoord = dtex / 2;
				float deltaHeight = layerHeight / 2;

				// return to the mid point of previous layer
				currentTextureCoords += deltaTexCoord;
				currentLayerHeight -= deltaHeight;

				// binary search to increase precision of Steep Paralax Mapping
				const int numSearches = 5;
				for(int i=0; i<numSearches; i++)
				{
					// decrease shift and height of layer by half
					deltaTexCoord /= 2;
					deltaHeight /= 2;

					// new depth from heightmap
					heightFromTexture = tex2D(Hmap, currentTextureCoords).r;

					// shift along or agains vector V
					if(heightFromTexture > currentLayerHeight) // below the surface
					{
						currentTextureCoords -= deltaTexCoord;
						currentLayerHeight += deltaHeight;
					}
					else // above the surface
					{
						currentTextureCoords += deltaTexCoord;
						currentLayerHeight -= deltaHeight;
					}
				}

				// return results
				parallaxHeight = currentLayerHeight;    
				return currentTextureCoords;
			}
			//Occlusion 
			float2 offsetUV_Occlusion(sampler2D Hmap,in float2 T, fixed height,in float3 V, out float parallaxHeight)
			{
				// determine optimal number of layers
				const float minLayers = 10;
				const float maxLayers = 15;
				float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0, 0, 1), V)));

				// height of each layer
				float layerHeight = 1.0 / numLayers;
				// current depth of the layer
				float curLayerHeight = 0;
				// shift of texture coordinates for each layer
				float2 dtex = height * V.xy / V.z / numLayers;

				// current texture coordinates
				float2 currentTextureCoords = T;

				// depth from heightmap
				float heightFromTexture = tex2D(Hmap, currentTextureCoords).r;

				// while point is above the surface
				[unroll(16)]
				while(heightFromTexture > curLayerHeight) 
				{
					// to the next layer
					curLayerHeight += layerHeight; 
					// shift of texture coordinates
					currentTextureCoords -= dtex;
					// new depth from heightmap
					heightFromTexture = tex2D(Hmap, currentTextureCoords).r;
				}

				///////////////////////////////////////////////////////////

				// previous texture coordinates
				float2 prevTCoords = currentTextureCoords + dtex;

				// heights for linear interpolation
				float nextH    = heightFromTexture - curLayerHeight;
				float prevH    = tex2D(Hmap, prevTCoords).r
										- curLayerHeight + layerHeight;

				// proportions for linear interpolation
				float weight = nextH / (nextH - prevH);

				// interpolation of texture coordinates
				float2 finalTexCoords = prevTCoords * weight + currentTextureCoords * (1.0-weight);

				// interpolation of depth values
				parallaxHeight = curLayerHeight + prevH * weight + nextH * (1.0 - weight);

				// return result
				return finalTexCoords;
			}

			//Builtin Parallax
			float2 offsetUV_Builtin(sampler2D Hmap, float2 uv,fixed height,fixed3 viewDir)
			{
				float h = tex2D(Hmap,uv).a;
				h = h*height - height/2.0;
				viewDir.z += 0.42;
				return uv + viewDir.xy * h / viewDir.z;
			}

			float parallaxSoftShadowMultiplier(sampler2D Hmap,in float2 initialTexCoord, fixed height,in fixed3 L,in float initialHeight)
			{
				float shadowMultiplier = 1;

				const float minLayers = 15;
				const float maxLayers = 30;

				// calculate lighting only for surface oriented to the light source
				if(dot(fixed3(0, 0, 1), L) > 0)
				{
					// calculate initial parameters
					float numSamplesUnderSurface    = 0;
					shadowMultiplier    = 0;
					float numLayers    = lerp(maxLayers, minLayers, abs(dot(fixed3(0, 0, 1), L)));
					float layerHeight    = initialHeight / numLayers;
					float2 texStep    = height * L.xy / L.z / numLayers;

					// current parameters
					float currentLayerHeight    = initialHeight - layerHeight;
					float2 currentTextureCoords    = initialTexCoord + texStep;
					float heightFromTexture    = tex2D(Hmap, currentTextureCoords).r;
					int stepIndex    = 1;

					// while point is below depth 0.0 )
					[unroll(16)]
					while(currentLayerHeight > 0)
					{
						// if point is under the surface
						if(heightFromTexture < currentLayerHeight)
						{
							// calculate partial shadowing factor
							numSamplesUnderSurface    += 1;
							float newShadowMultiplier    = (currentLayerHeight - heightFromTexture) *
															(1.0 - stepIndex / numLayers);
							shadowMultiplier    = max(shadowMultiplier, newShadowMultiplier);
						}

						// offset to the next layer
						stepIndex    += 1;
						currentLayerHeight    -= layerHeight;
						currentTextureCoords    += texStep;
						heightFromTexture    = tex2D(Hmap, currentTextureCoords).r;
					}

					// Shadowing factor should be 1 if there were no points under the surface
					if(numSamplesUnderSurface < 1)
					{
						shadowMultiplier = 1;
					}
					else
					{
						shadowMultiplier = 1.0 - shadowMultiplier;
					}
				}
				return shadowMultiplier;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				
				float pH = 1;
				float shadowMultiplier = 1;
				#if _TYPE_NORMAL
					i.uv = offsetUV_Simple(_ParallaxMap,i.uv,_Parallax,i.viewDir);
				#elif _TYPE_LIMITING
					i.uv = offsetUV_Limiting(_ParallaxMap,i.uv,_Parallax,i.viewDir);
				#elif _TYPE_STEEP
					i.uv = offsetUV_Steep(_ParallaxMap,i.uv,_Parallax,i.viewDir,pH);
					shadowMultiplier = parallaxSoftShadowMultiplier(_ParallaxMap,i.uv,_Parallax,i.lightDir,pH - 0.05);
				#elif _TYPE_RELIEF
					i.uv = offsetUV_Relief(_ParallaxMap,i.uv,_Parallax,i.viewDir,pH);
					shadowMultiplier = parallaxSoftShadowMultiplier(_ParallaxMap,i.uv,_Parallax,i.lightDir,pH - 0.05);
				#elif _TYPE_BUILTIN
					i.uv = offsetUV_Builtin(_ParallaxMap,i.uv,_Parallax,i.viewDir);
				#elif _TYPE_OCCLUSION
					i.uv = offsetUV_Occlusion(_ParallaxMap,i.uv,_Parallax,i.viewDir,pH);
					shadowMultiplier = parallaxSoftShadowMultiplier(_ParallaxMap,i.uv,_Parallax,i.lightDir,pH - 0.05);
				#endif

				float3 normal = UnpackNormal(tex2D(_BumpMap,i.uv));

				float diff = max(0,dot(normal,i.lightDir));

				fixed3 maincol = tex2D(_MainTex, i.uv).rgb;
				fixed3 col = maincol * diff*_LightColor0.rgb * pow(shadowMultiplier, 4);
				return float4(col,1);
			}
			ENDCG
		}
	}
}

猜你喜欢

转载自blog.csdn.net/u012871784/article/details/81069874
今日推荐