shader入门精要读书笔记22 高级纹理-立方体纹理-环境映射反射折射

一、前言

立方体纹理是环境映射的一种实现方法,立方体纹理共包含6张图像。
采样是通过立方体中心出发向外部延伸,与6张纹理之一发生相交,采样结果就是由交点计算来的。

立方体纹理好处:简单快速、鲜果比较好,缺点:当场景发生变化需要重新生成立方体纹理,而且不能进行自身反射,所以我们需要尽量使用凸面体。

二、天空盒

天空盒使用的就是立方体纹理映射技术。

游戏中模拟背景的一种方法,它的Skybox材质,中使用6张纹理,这6张纹理的Wrap Mode需要设置为Clamp,需要截取边界值,防止出现不匹配的现象。

还有3个属性:Tint Color用于控制材质整体颜色,Exposure用于调整天空盒子的亮度,Rotation用于调整天空盒子沿+y轴方向的旋转角度。

天空盒子是在所有的不透明物体之后进行渲染的,其背后使用的网格是一个立方体或一个细分的球体。

三、创建用于环境映射的立方体纹理

立方体纹理在环境映射时通常用来模拟金属质感的材质。

创建用于环境映射的立方体纹理方法有三种:

1.直接由一些特殊布局的纹理创建

这时我们需要提供一张具有特殊布局的纹理,例如立方体展开图的交叉布局、全景布局等。然后我们把纹理的Texture Type设置为Cubemap即可,在基于物理的渲染中,我们通常会使用一张HDR图像来生成高质量的Cubemap。
官方推荐使用这种方法,可以对纹理数据进行压缩,又支持边缘修正、光滑反射、HDR等功能。

2.手动创建一个Cubemap资源,再把6张图赋值给他

3.由脚本生成

public class RenderCubemapWizard : ScriptableWizard {
	
	public Transform renderFromPosition;  //在这个指定的位置动态创建摄像机
	public Cubemap cubemap;  //获取得出的cubemap
	
	void OnWizardUpdate () {      //编辑器更新时调用
		helpString = "Select transform to render from and cubemap to render into";   //提示
		isValid = (renderFromPosition != null) && (cubemap != null);  //设置按钮是否可以点击
	}
	
	void OnWizardCreate () {   //当向导被打开或只要用户在向导改变了某些东西时被调用。
		// create temporary camera for rendering
		GameObject go = new GameObject( "CubemapCamera");
		go.AddComponent<Camera>();
		// place it on the object
		go.transform.position = renderFromPosition.position;
		// render into cubemap		
		go.GetComponent<Camera>().RenderToCubemap(cubemap);  //渲染出cubemap
		
		// destroy temporary camera
		DestroyImmediate( go );//销毁摄像机
	}
	
	[MenuItem("GameObject/Render into Cubemap")]
	static void RenderCubemap () {
		ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
			"Render cubemap", "Render!");
	}
}

之后我们在场景中创建GameObject,并赋值给代码弄出的菜单栏条目界面中的renderFromPosition。
再创建一个Cubemap立方体纹理(Create>Legacy>Cubemap),为了让脚本可以顺利将图像渲染到立方体纹理中,我们需要勾选Readable选项。

使用上面的窗口代码工具渲染得到纹理,里面Face size选项越大,渲染得立方体纹理分辨率越大专用的内存也多。

四、反射

反射可以得到类似于镀层金属一样的效果,思想大概是通过视角方向和法线获取视角反射方向得到入射光源方向,在用这个方向对纹理采样进行相关计算。

Shader "Unity Shaders Book/Chapter 10/Reflection" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)	
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)	 //控制反射颜色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1			//控制材质的反射程度
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}		//环境映射纹理,就是那个cubemap立方体纹理,在材质中进行替换赋值
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}   //用于不透明的几何物体。。。
		
		Pass { 
			Tags { "LightMode"="ForwardBase" }
			
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			fixed4 _ReflectColor;
			fixed _ReflectAmount;
			samplerCUBE _Cubemap;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				fixed3 worldNormal : TEXCOORD1;
				fixed3 worldViewDir : TEXCOORD2;
				fixed3 worldRefl : TEXCOORD3;
				SHADOW_COORDS(4)  //用于衰减和阴影计算,三剑客之一
			};
			
			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.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
				
				// Compute the reflect dir in world space
				o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); //使用视角方向的反射方向计算光方向
				
				TRANSFER_SHADOW(o); //三剑客之二
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));		
				fixed3 worldViewDir = normalize(i.worldViewDir);		
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
				
				// Use the reflect dir in world space to access the cubemap
				fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; //这里i.worldRefl不需要及逆行归一化,因为只取方向
				
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);  //统一管理那个,算是三剑客之三。。
				
				// Mix the diffuse color with the reflected color
				fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten; //这里使用_ReflectAmount进行取值(混合漫发射颜色和反射颜色)
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Reflective/VertexLit"
}

之后再把cubemap立方体纹理拖到我们在Shader中设置材质的Reflection Cubemap中。

五、折射

折射是另外一种环境映射的常见应用。

折射-------斯涅尔定律用来计算反射角------n1 sinθ1=n2 sinθ2,(角度都是与法线夹角;n1、n2是两个介质的折射率,真空是1,玻璃是1.5)

Shader "Unity Shaders Book/Chapter 10/Refraction" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)   //折射颜色设置
		_RefractAmount ("Refraction Amount", Range(0, 1)) = 1     //透射程度
		_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5   //透射比
		_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}	//立方体纹理
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}  //用于不透明的几何物体。。。
		
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			fixed4 _RefractColor;
			float _RefractAmount;
			fixed _RefractRatio;
			samplerCUBE _Cubemap;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				fixed3 worldNormal : TEXCOORD1;
				fixed3 worldViewDir : TEXCOORD2;
				fixed3 worldRefr : TEXCOORD3;
				SHADOW_COORDS(4)
			};
			
			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.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
				
				// Compute the refract dir in world space
				o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio); 
				//计算折射方向(第一个参数入射光线方向,第二个是表面法线,这两个都要归一化,第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率的比值)
				
				TRANSFER_SHADOW(o);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(i.worldViewDir);
								
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
				
				// Use the refract dir in world space to access the cubemap
				fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;  //使用折射方向对立方体纹理进行采样获取折射颜色
				
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				// Mix the diffuse color with the refract color
				fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;  //使用_RefractAmount透射程度对漫反射和折射颜色进行混合取值
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"
}

之后再把cubemap立方体纹理拖到我们在Shader中设置材质的Refraction Cubemap中(书上又写错了hhh~)。

六、菲涅尔反射

菲涅尔反射可以根据视角方向控制反射程度,这样可以产生一种光学现象,
(现象例子:站在湖边,脚下的水几乎透明,而远处的水面只能看到反射现象)
即当光线照射到物体表面时,一部分发生反射一部分发生折射或者散射,反射和折射的光存在一定的比例关系,比例关系就是通过菲涅尔等式来计算的。

菲涅尔等式:F(v,n)=F0+(1-F0) [ (1-v·n) 的5次方]

使用这个可以在边界处模拟反射光强和折射光强/漫反射之间的变化,在很多车漆水面等材质中,经常使用。

Shader "Unity Shaders Book/Chapter 10/Fresnel" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5    //调整影响大小的
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			fixed _FresnelScale;
			samplerCUBE _Cubemap;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
  				fixed3 worldNormal : TEXCOORD1;
  				fixed3 worldViewDir : TEXCOORD2;
  				fixed3 worldRefl : TEXCOORD3;
 	 			SHADOW_COORDS(4)
			};
			
			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.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
				
				o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);   //求出反射方向
				
				TRANSFER_SHADOW(o);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(i.worldViewDir);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;   //使用反射方向采样
				
				fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);   //计算菲涅尔反射公式。
				
				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;  //使用菲涅尔公式计算出的fresnel对漫反射和反射进行混合取值
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"

有的实现也会直接把fresnel和反射光照相乘叠加到漫反射光照上,模拟边缘光照效果。

Fresnel Scale为0时,是一个具有边缘光照效果的漫反射物体,为1时,完全反射立方体纹理中的图像。

上一篇:图形 4 Unity Shader的简单了解(四)·逐顶点的漫反射光照


下一篇:光照着色器---漫反射