永远相信美好的事情即将发生 😊!

Unity Shader 入门精要(冯乐乐著)学习笔记(9)——高级纹理

学习 Mavis 30℃ 0评论

立方体纹理

对立方体纹理采样需要一个三维的纹理坐标。尽量对凸面体使用立方体纹理(凹面体会反射自身,但是立方体纹理不能模拟多次反射的结果)。

立方体纹理的两大应用:天空盒子+环境映射

天空盒子

创建一个skybox材质即可,然后赋值给天空盒子:

步骤:创建材质,shader选择内置的skybox/6 sided

赋值:

其他设置:将天空盒子材质中的六张纹理正确赋值后,还需要把这六张纹理的wrap mode

改成clamp。材质中tint color控制材质整体颜色,exposure调整天空盒子的亮度,rotation调正天空盒子沿+y方向的旋转角度。为了让摄像机正常显示天空盒子,camera组件的clear flags设置为skybox:

若要相机显示不同的天空盒子,可对相机单独添加skybox组件来覆盖全局的设置,unity中天空盒子是在所有不透明物体之后渲染,使用的网格是一个立方体或细分后的球体。

环境映射纹理准备

纹理的准备有三种方式:由一些特殊布局的纹理创建、手动创建cubemap资源,再将六张图赋值、由脚本生成。

由一些特殊布局的纹理创建:提供一张具有特殊布局的纹理,例如立方体展开图的交叉布局、全景布局等,将纹理的texture type设置为cubemap即可,在基于物理的渲染中,通常使用一张HDR图像来生成高质量的cubemap。推荐使用这种,可以对纹理数据进行压缩,还支持边缘修正、光滑反射和HDR等功能。

手动创建cubemap资源,再将六张图赋值:创建一个cubemap,将纹理赋值。

由脚本生成:希望根据物体在场景中不同的位置,生成各自不同的立方体纹理,利用Camera.RenderToCubemap函数实现:

使用环境映射技术

反射:

通过入射光线和法线计算反射方向,再用反射方向对cubemap采样即可。

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" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		Pass { 
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			#pragma multi_compile_fwdbase
			#pragma vertex vert
			#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;
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				// Mix the diffuse color with the reflected color
				fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
				return fixed4(color, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Reflective/VertexLit"
}

效果:

折射:

实际渲染中,需要计算两次折射,但实时渲染中,通常仅模拟第一次折射就可以得到还不错的效果。

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
        // 入射光线和法线必须归一化,第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值。例如光从空气到水=1/1.5
				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;
				return fixed4(color, 1.0);
			}
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"
}

效果:

菲涅尔反射:

使用菲涅尔反射来根据视角方向控制反射程度,反射的光和入射的光存在一定的比率关系。使用schlick菲涅尔近似公式

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;
				return fixed4(color, 1.0);
			}
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"
}

效果:

渲染纹理

多重渲染目标(Multiple Render Target,MRT)技术允许把场景同时渲染到多个渲染目标纹理(render target texture,RTT)中,而不再为每个渲染目标纹理单独渲染完整的场景。

使用渲染纹理有两种方式:

  1. 创建一个渲染纹理,然后将某摄像机的渲染目标设置为该渲染纹理,摄像机的渲染结果就会实时更新到渲染纹理中,而不会显示在屏幕上。可以选择渲染纹理的分辨率、滤波模式等属性。
  2. 在屏幕后处理是时使用GrabPass命令或OnRenderImage函数获取当前屏幕图像,把这个图放到渲染纹理中,在自定义的Pass中可以将其当成普通纹理来处理,从而实现各种屏幕特效。

镜子效果

创建渲染纹理:

创建一个相机,调整它的位置,裁剪平面,视角等,使它显示的图像是我们希望的镜子图像:

MirrorTexture是一张创建的渲染纹理,将这个纹理作为镜子shader的输入即可,将渲染纹理在水平方向上翻转后显示到物体上即可完成镜子效果(镜像-左右翻转):

Shader "Unity Shaders Book/Chapter 10/Mirror" {
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		Pass {
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			sampler2D _MainTex;
			struct a2v {
				float4 vertex : POSITION;
				float3 texcoord : TEXCOORD0;
			};
			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
			};
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				// Mirror needs to filp x
				o.uv.x = 1 - o.uv.x;
				return o;
			}
			fixed4 frag(v2f i) : SV_Target {
				return tex2D(_MainTex, i.uv);
			}
			ENDCG
		}
	} 
 	FallBack Off
}

玻璃效果

使用特殊的Pass来获取屏幕图像:GrabPass。通常用来实现诸如玻璃等透明材质的模拟。

需要小心物体的渲染队列,通常用于渲染透明物体,所以要将渲染队列设置为透明队列。

实现玻璃效果:法线纹理更改法线信息、通过cubemap实现反射、使用grabpass获取玻璃后面的屏幕图像实现折射(使用切线空间下的法线对屏幕纹理坐标偏移后,再对屏幕图像进行采样来模拟近似的折射效果):

Shader "Unity Shaders Book/Chapter 10/Glass Refraction" {
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}//材质纹理
		_BumpMap ("Normal Map", 2D) = "bump" {}//法线纹理
		_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}//环境映射——反射
		_Distortion ("Distortion", Range(0, 100)) = 10//控制模拟折射时图像的扭曲程度
		_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0//控制反射程度,0:只反射。1:只折射
	}
	SubShader {
		// We must be transparent, so other objects are drawn before this one.
		Tags { "Queue"="Transparent" "RenderType"="Opaque" }
		// This pass grabs the screen behind the object into a texture.
		// We can access the result in the next pass as _RefractionTex
		GrabPass { "_RefractionTex" }
		Pass {		
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			samplerCUBE _Cubemap;
			float _Distortion;
			fixed _RefractAmount;
			sampler2D _RefractionTex;
			float4 _RefractionTex_TexelSize;
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT; 
				float2 texcoord: TEXCOORD0;
			};
			struct v2f {
				float4 pos : SV_POSITION;
				float4 scrPos : TEXCOORD0;
				float4 uv : TEXCOORD1;
				float4 TtoW0 : TEXCOORD2;  
			    float4 TtoW1 : TEXCOORD3;  
			    float4 TtoW2 : TEXCOORD4; 
			};
			v2f vert (a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.scrPos = ComputeGrabScreenPos(o.pos);
				o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
				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 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
				o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
				o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
				return o;
			}
			fixed4 frag (v2f i) : SV_Target {		
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				// Get the normal in tangent space
				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));	
				// Compute the offset in tangent space
				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;//透视除法得到真正的屏幕坐标,计算折射
				// Convert the normal to world space
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
				fixed3 reflDir = reflect(-worldViewDir, bump);
				fixed4 texColor = tex2D(_MainTex, i.uv.xy);//材质纹理
				fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;//反射
				fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
				return fixed4(finalColor, 1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

效果:

渲染纹理和GrabPass

效率上,使用渲染纹理效率高于grabpass,尤其是移动设备上,而使用grabpass获取到的图像分辨率和显示屏幕一样,在高分辨率的设备上会造成严重的带宽影响。unity还有命令缓冲(command buffers)允许扩展渲染流水线,通过这样的方式可以得到类似抓屏的效果。

实现简单的程序纹理

为需要程序纹理的物体创建新脚本:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//该脚本可以在编辑器模式下运行
[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {
	//声明材质,这个材质将使用该脚本生成的程序纹理
	public Material material = null;
	//声明程序纹理使用的参数
	#region Material properties
	//纹理的大小,通常是2的整数幂
	[SerializeField, SetProperty("textureWidth")]
	private int m_textureWidth = 512;
	public int textureWidth {
		get {
			return m_textureWidth;
		}
		set {
			m_textureWidth = value;
			_UpdateMaterial();//为了在面板上修改属性仍可以执行set,使用开源插件SetProperty,用该函数来生成新的程序纹理
		}
	}
	//纹理的背景颜色
	[SerializeField, SetProperty("backgroundColor")]
	private Color m_backgroundColor = Color.white;
	public Color backgroundColor {
		get {
			return m_backgroundColor;
		}
		set {
			m_backgroundColor = value;
			_UpdateMaterial();
		}
	}
	//圆点的颜色
	[SerializeField, SetProperty("circleColor")]
	private Color m_circleColor = Color.yellow;
	public Color circleColor {
		get {
			return m_circleColor;
		}
		set {
			m_circleColor = value;
			_UpdateMaterial();
		}
	}
	//模糊因子,用于模糊圆形边界
	[SerializeField, SetProperty("blurFactor")]
	private float m_blurFactor = 2.0f;
	public float blurFactor {
		get {
			return m_blurFactor;
		}
		set {
			m_blurFactor = value;
			_UpdateMaterial();
		}
	}
	#endregion
	private Texture2D m_generatedTexture = null;//用于保存生成的程序纹理
	// Use this for initialization
	void Start () {
		if (material == null) {
			Renderer renderer = gameObject.GetComponent<Renderer>();
			if (renderer == null) {
				Debug.LogWarning("Cannot find a renderer.");
				return;
			}
			material = renderer.sharedMaterial;
		}
		_UpdateMaterial();
	}
	private void _UpdateMaterial() {
		if (material != null) {
			m_generatedTexture = _GenerateProceduralTexture();
			material.SetTexture("_MainTex", m_generatedTexture);//把生成的纹理赋值给材质,材质中需要一个名为_MainTex的纹理属性
		}
	}
	private Color _MixColor(Color color0, Color color1, float mixFactor) {
		Color mixColor = Color.white;
		mixColor.r = Mathf.Lerp(color0.r, color1.r, mixFactor);
		mixColor.g = Mathf.Lerp(color0.g, color1.g, mixFactor);
		mixColor.b = Mathf.Lerp(color0.b, color1.b, mixFactor);
		mixColor.a = Mathf.Lerp(color0.a, color1.a, mixFactor);
		return mixColor;
	}
	private Texture2D _GenerateProceduralTexture() {
		Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);
		// The interval between circles
		float circleInterval = textureWidth / 4.0f;
		// The radius of circles
		float radius = textureWidth / 10.0f;
		// The blur factor
		float edgeBlur = 1.0f / blurFactor;
		for (int w = 0; w < textureWidth; w++) {
			for (int h = 0; h < textureWidth; h++) {
				// Initalize the pixel with background color
				Color pixel = backgroundColor;
				// Draw nine circles one by one
				for (int i = 0; i < 3; i++) {
					for (int j = 0; j < 3; j++) {
						// Compute the center of current circle
						Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));
						// Compute the distance between the pixel and the center
						float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;
						// Blur the edge of the circle
						Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));
						// Mix the current color with the previous color
						pixel = _MixColor(pixel, color, color.a);
					}
				}
				proceduralTexture.SetPixel(w, h, pixel);
			}
		}
		proceduralTexture.Apply();
		return proceduralTexture;
	}
}

效果:

程序材质

程序材质使用Substance Designer软件在unity外部生成,,可以导入unity中,导入后,unity就会生成一个程序纹理资源,Unity2018及以上或版本靠近2018在Substance Designer制作材质导入后不能识别,需要在Unity商城下载Substance in Unity插件,如果Unity低版本直接支持就不需下载插件;


Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Unity Shader 入门精要(冯乐乐著)学习笔记(9)——高级纹理
喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址