1.立方体纹理
用于实现环境映射。模拟物体映射周围的环境。立方体纹理包含六张贴图(前后左右上下)。
局限:场景发生变化(物体,光源增减,物体位置变化),立方体纹理就得重新生产。
1.1 立方体纹理创建方式
- 直接使用带特殊布局的纹理。然后设置TextureTpye = Cubemap即可。Unity官方推荐使用
- 创建一个Cubemap,然后提供前后左右上下六张贴图。
- 调用Camera.RenderToCubemap将当前摄像机观察到的图像渲染存储到Cubemap中。
1.2 利用立方体纹理实现物体反射场景环境效果
原理:
图中向量I表示观察方向(摄像机到顶点的方向),N表示顶点对应的法向量,R表示反射向量。
用反射向量R对立方体纹理进行采样(反射向量和立方体纹理其中的一张贴图相交,采样相交点的纹理。),然后叠加到原来的纹理上,形成反射周边环境的效果。
反射向量计算如下图:
Shader "Chan/Chapter10_Reflection" {
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_ReflectColor("Reflect Color",Color) = (1,1,1,1)
_ReflectAmout("Reflect Amout",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;
fixed4 _ReflectColor;
float _ReflectAmout;
samplerCUBE _Cubemap;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:Texcoord0;
fixed3 worldPos: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;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(worldNormal,worldLightDir));
//用反射方向向量对立方体纹理进行采样
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb * _ReflectColor.rgb;
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合
fixed3 color = ambient + lerp(diffuse,reflection,_ReflectAmout) * atten;
return fixed4(color,1);
}
ENDCG
}
}
Fallback "Reflective/VertexLit"
}
实现思路:
- 世界坐标系下求得顶点的反射方向(CG函数 reflect),然后拿着反射方向去跟立方体纹理进行采样。
- 用反射方向向量对立方体纹理进行采样,使用CG方法texCUBE。
- 设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合。
1.3 利用立方体纹理实现物体折射场景环境效果
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Chan/Chapter10_Refraction" {
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_RefractColor("Refract Color",Color) = (1,1,1,1)
_RefractAmout("Refract Amout",Range(0,1)) = 0.5
_RefractRation("Refract Ration",Range(0,1)) = 0.5
_Cubemap("Refraction Cubemap",Cube) = "_Skybox"{}
}
SubShader
{
Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma vertex vert
#pragma fragment frag
float4 _Color;
fixed3 _RefractColor;
float _RefractAmout;
float _RefractRation;
samplerCUBE _Cubemap;
struct a2v
{
float4 vert:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:Texcoord0;
fixed3 worldPos:Texcoord1;
fixed3 worldViewDir:Texcoord2;
fixed3 worldRefl:Texcoord3;
SHADOW_COORDS(4)
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vert);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vert).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//CG refract函数计算折射方向,传入参数 1.入射光线方向,2.表面法线向量,3.入射光线所在介质和折射光线所在介质的折射率之比
o.worldRefl = refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRation);
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));
fixed3 refraction = texCUBE(_Cubemap,i.worldRefl).rgb * _RefractColor.rgb;
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
fixed3 color = ambient + lerp(diffuse,refraction,_RefractAmout) * atten;
return fixed4(color,1);
}
ENDCG
}
}
Fallback ""
}
实现思路:
- 世界坐标系下求得顶点的折射方向(CG函数 refract),然后拿着折射方向去跟立方体纹理进行采样。
- 用折射方向向量对立方体纹理进行采样,使用CG方法texCUBE。
- 设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合。
菲涅尔反射
当视线与物体表面夹角越小(越平行于表面),反射越明显。入射光线和反射光线之间存在一定的比率关系。
Schlick 菲涅尔近似等式:
其中是反射系数,用于控制菲涅尔反射强度。v是视角方向,n是表面法线。
Empricial 菲涅尔近似等式:
bias,scale,poser都是控制参数
Shader "Chan/Chapter10_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;
float _FresnelScale;
samplerCUBE _Cubemap;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:Texcoord0;
fixed3 worldViewDir:Texcoord1;
fixed3 worldPos: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);
//利用texCUBE函数,传入参数反射方向向量对立方体纹理进行采样
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb;
//Schlick 菲涅尔近似等式计算入射光线和反射光线比率大小的值 fresnel
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"
}
Schlick 菲涅尔近似等式计算入射光线和反射光线比率大小的值 fresnel。
然后用Schlick对漫反射光照和反射光照用fresnel进行插值。
2.渲染纹理
渲染目标纹理:现代GPU允许我们,把整个场景渲染到一个中间缓冲区渲染目标纹理(Render Target)中,而不是传统的的帧缓冲或者后备缓冲区(back buffer),然后显示到屏幕上。Unity中有专门的渲染目标纹理类型 - 渲染纹理(Render Texture)
多重渲染目标纹理:利用这种技术,可以实现多重渲染目标(Multiple Render Target,MRT)。这种技术允许我们把场景同事渲染到多个渲染目标纹理中,不再需要每个渲染目标单独渲染整个场景。
2.1 使用渲染纹理(Render Texture)
- 在Unity中直接通过菜单创建(Create-Render Texture),然后把某个摄像机的渲染目标设置成该渲染纹理。
- 在屏幕后处理时使用GrabPass命令或者OnRenderImage函数来获取当前屏幕图像。Unity会把当前屏幕图像存放到一张和屏幕分辨率等同的渲染纹理中。在后续的自定义Pass中就可当做普通的纹理来处理,实现各种屏幕特效。
镜子效果:
Shader "Chan/Chapter10_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;
//镜面,左右相反 因此翻转uv的x分量
o.uv.x = 1 - o.uv.x;
return o;
}
fixed4 frag(v2f i):SV_Target
{
return tex2D(_MainTex,i.uv);
}
ENDCG
}
}
Fallback Off
}
实现步骤:
- 创建一个摄像机,调整摄像机参数,用于照射镜面效果所需要的画面。
- 创建一个渲染纹理(Render Texture),设置摄像机的Target Texture为渲染纹理。把摄像机所照到的图面渲染存储到渲染纹理中。
- 创建一个四边形Quad,调整大小坐标角度参数。作为镜子使用。设置渲染纹理为四边形材质的Main Texture。
玻璃效果:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Chan/Chapter10_GlassRefraction" {
Properties
{
_MainTex("Main Tex",2D) = "white"{}
_BumpMap("Normal Map",2D) = "white"{}
_CubeMap("Cube Map",Cube) = "_Skybox"{}
//控制折射时候图像的扭曲程度
_Distortion("Distortion",Range(0,100)) = 10
//控制折射程度
_RefractAmount("Refract Amount",Range(0.0,1.0)) = 1.0
}
SubShader
{
//GrabPass 通常用于渲染透明物体,因此渲染队列设置为Transparent
Tags{"Queue" = "Transparent" "RenderType" = "Opaque"}
//GrabPass 抓取屏幕图像,存到一张纹理,给这个纹理取个名字叫 _RefractionTex
GrabPass{"_RefractionTex"}
Pass
{
Tags{"LightMode" = "Forwardbase"}
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members TtoW0,TtoW0,TtoW0)
#pragma exclude_renderers d3d11
#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;
//_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);
//得到抓取的屏幕图像_RefractionTex 的采样坐标
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));
//对法线纹理进行采样,得到切线空间下的法线方向 (默认就是切线空间下)
fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
//用切线空间下的法线方向,对屏幕图像的采样坐标进行偏移,模拟折射效果
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset + i.scrPos.xy;
//i.scrPos.xy/i.scrPos.w = 透视除法得到真正的屏幕坐标
//用真正的屏幕坐标对抓取的屏幕图像进行采样
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)));
//得到视角方向相对于法线方向的反射方向
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
}
}
}
实现思路:
- Tags{"Queue" = "Transparent" "RenderType" = "Opaque"} GrabPass{"_RefractionTex"} 在所有不透明物体渲染完成后,抓取一张屏幕图像。
- 把法线方向从切线空间转换到世界空间下,然后得到视角方向相对于法线方向的反射方向
- 用反射方向对立方体纹理采样。
- 根据参数混合,得到最终的输出。
渲染纹理(Render Texture) + 摄像机 和 GrabPass 都可以抓取屏幕图像
- 渲染纹理(Render Texture) 效率高,可以自定义抓取的纹理大小,摄像机可选择渲染对象
- GrabPass 实现简单,效率低。抓取纹理大小就是屏幕尺寸。
程序纹理
程序纹理-计算机生成的图像。由于是程序生成,因此可以由程序控制,实现各种动画,视觉效果。
Unity的程序材质
专门使用程序纹理的材质,叫做程序材质。用Substance Designer是非常出色的纹理生成工具。它会生成.sbsar后缀的文件,当把文件导入Unity后,Unity就会自动生成包含一个或多个程序材质的的程序纹理资源。
程序材质使用和普通材质使用一样。程序材质通过调整程序纹理的多个属性来控制纹理外观。
参考:
https://blog.csdn.net/v_xchen_v/article/details/79474193
https://blog.csdn.net/wangdingqiaoit/article/details/52557544
https://blog.csdn.net/v_xchen_v/article/details/79692726?utm_source=blogxgwz9