纹理映射技术就是把一张图粘贴在模形的表面上
(一)用一张纹理来代替物体的漫反射颜色
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
Properties {
_Color ("Color Tint ",Color) = (1,1,1,1)
_MainTex ("Main Tex",2D) = "white"{}//2D 是纹理属性
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 Color;
sampler2D _MainTex;
float4 _MainTex_ST;//纹理名_ST方式声明某个纹理的属性 _MainTex_ST.xy 存储的是缩放值,_MainTex_ST.zw存储的是偏移值
fixed4 _Specular;
float _Gloss;
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;// 存储纹理的坐标
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//或者用o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);进行纹理坐标变换
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
//tex2D 用来对纹理进行采样,第一个参数_MainTex为纹理,i.uv为该点的纹素值,
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
纹理的类型:Texture, Normal map, Cubemap等
Alpha from Grayscale:设置透明通道的
Wrap Mode:可以设置为Repeat ,也可以设置为Tiling平铺
Filter Mode :滤波,Point Bilinear Trilinear三种,滤波效果一次提升,耗费也会变大
多级渐远纹理:提前用滤波技术得到很多小的图像,形成一个金字塔形,每一层都是对上一层采样的结果。当摄像机距离较远时,会使用小的纹理。这种技术会占用很多内存,以空间换取时间。
Advance类型的纹理,可以设置 Generate Mip Maps多级渐远纹理
导入的纹理可以是非正方形的,但是长宽的大小应该是2的幂,例如2,4,8等。如果非2的幂,会占用更多的内存空间,GPU读取纹理的速度也会下降。
Format决定了压缩的格式
(二)凹凸映射
凹凸映射目的是用一张纹理来修改模型表面的法线,为模型提供更多的细节。基本思想是通过改变表面法线,而不是使用纹理来改变光照方程中的颜色分量
实现凹凸映射有两种方法:
高度映射:利用高度纹理来模拟表面位移,得到一个修改后的法线值。
法线映射:使用一张法线纹理来直接存储表面法线,这种方法为法线映射。
1.高度纹理:用一张高度图来实现,主要是颜色不同,浅色表明向外凸起,深色为凹陷
2.法线纹理:法线纹理存储的就是表面的法线方向,由于法线的方向分量范围在[-1,1],而像素的分量范围在[0,1],需要做一个映射。
pixel = (normal+1)/2 normal = pixel * 2 -1
模型空间中的表面法线存储在一张纹理中,为模型空间法线纹理。实际应用中常用模型顶点的切线空间来存储法线,称为切线空间的法线纹理。
(1)切线空间下光照模型
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}//bump是Unity自带法线纹理,没有提供任何法线纹理时。表示对应了模型自带的法线纹理
_BumpScale ("Bump Scale", Float) = 1.0//控制凹凸程度的
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;//利用tangent的第四个变量tangent.w来确定切线空间中的第三个坐标轴,副切线的方向
float4 texcoord : TEXCOORD0;//第一组纹理坐标
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
// Unity doesn't support the 'inverse' function in native shader
// so we write one by our own
// Note: this function is just a demonstration, not too confident on the math or the speed
// Reference: http://answers.unity3d.com/questions/218333/shader-inversefloat4x4-function.html
float4x4 inverse(float4x4 input) {
#define minor(a,b,c) determinant(float3x3(input.a, input.b, input.c))
float4x4 cofactors = float4x4(
minor(_22_23_24, _32_33_34, _42_43_44),
-minor(_21_23_24, _31_33_34, _41_43_44),
minor(_21_22_24, _31_32_34, _41_42_44),
-minor(_21_22_23, _31_32_33, _41_42_43),
-minor(_12_13_14, _32_33_34, _42_43_44),
minor(_11_13_14, _31_33_34, _41_43_44),
-minor(_11_12_14, _31_32_34, _41_42_44),
minor(_11_12_13, _31_32_33, _41_42_43),
minor(_12_13_14, _22_23_24, _42_43_44),
-minor(_11_13_14, _21_23_24, _41_43_44),
minor(_11_12_14, _21_22_24, _41_42_44),
-minor(_11_12_13, _21_22_23, _41_42_43),
-minor(_12_13_14, _22_23_24, _32_33_34),
minor(_11_13_14, _21_23_24, _31_33_34),
-minor(_11_12_14, _21_22_24, _31_32_34),
minor(_11_12_13, _21_22_23, _31_32_33)
);
#undef minor
return transpose(cofactors) / determinant(input);
}
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//进行纹理坐标变换
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
///
/// Note that the code below can handle both uniform and non-uniform scales
///
// Construct a matrix that transforms a point/vector from tangent space to world space
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);//切线转换到世界空间
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //因为与切线和法线都垂直的方向有两个,因此需要确定哪个方向
/*
float4x4 tangentToWorld = float4x4(worldTangent.x, worldBinormal.x, worldNormal.x, 0.0,
worldTangent.y, worldBinormal.y, worldNormal.y, 0.0,
worldTangent.z, worldBinormal.z, worldNormal.z, 0.0,
0.0, 0.0, 0.0, 1.0);
// The matrix that transforms from world space to tangent space is inverse of tangentToWorld
float3x3 worldToTangent = inverse(tangentToWorld);
*/
//wToT = the inverse of tToW = the transpose of tToW as long as tToW is an orthogonal matrix.
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
//TARGENT_SPACE_ROTATION 可以直接得到ration的变换矩阵
// Transform the light and view dir from world space to tangent space
o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));
///
/// Note that the code below can only handle uniform scales, not including non-uniform scales
///
// Compute the binormal
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
// // Construct a matrix which transform vectors from object space to tangent space
// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
// Or just use the built-in macro
// TANGENT_SPACE_ROTATION;
//
// // Transform the light direction from object space to tangent space
// o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz;
// // Transform the view direction from object space to tangent space
// o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
// Get the texel in the normal map
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);//纹理采样
fixed3 tangentNormal;
// If the texture is not marked as "Normal map"如果纹理没有设置为法线贴图,需要映射回法线方向
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// Or mark the texture as "Normal map", and use the built-in funciton
tangentNormal = UnpackNormal(packedNormal);//计算得到正确的法线方向
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
当把法线纹理的纹理标识成Normal Map,使用Unity内置的函数 UnpackNormal来得到正确的法线方向。
Create from Grayscale用于从高度图中生成法线纹理。高度图本身记录的是相对高度,是一张灰度图,白色表示相对更高,黑色表示相对更低。
(三)渐变纹理
渐变纹理是利用
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
Properties {
_Color ("Color Tint ",Color) = (1,1,1,1)
_RampTex ("Ramp Tex",2D) = "white"{}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 Color;
sampler2D _RampTex;
float4 _RampTex_ST;//纹理名_ST方式声明某个纹理的属性 _MainTex_ST.xy 存储的是缩放值,_MainTex_ST.zw存储的是偏移值
fixed4 _Specular;
float _Gloss;
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;
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UINTY_MATRIX_MVP,v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_ObjectToWorld,v.vertex).xyz;
o.uv = TRANSFROM_TEX(v.texcoord,_RampTex);//计算平铺和偏移后的纹理坐标
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed halfLambert = 0.5 * dot(worldNormal , worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex , fixed2(halfLambert,halfLambert)).rgb * _Color.rgb;//利用半兰伯特模型实现漫反射光照,利用halfLambert纹理坐标对渐变纹理_RampTex进行采样,_RampTex实际就是一个一维纹理,纵轴方向上颜色不变,因此纹理坐标u v方向都适用halfLambert,然后从渐变纹理采样得到的颜色和材质颜色相乘,得到漫反射的颜色
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir+viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return (ambient + diffuse + specular,1.0);
}
ENDCG
}
}
FallBack "Specular"
}
把渐变纹理的Wrap Mode的Clamp模式可以防止对纹理进行采样是由于浮点数精度而造成的问题。
(四)遮罩纹理
在创建世界地图时,做地形的材质需要混合多张图片,如草地,石子,土地等,用遮罩纹理可以控制如何混合这些纹理
遮罩纹理一般通过采样得到遮罩纹理,然后使用其中某个通道的值来与某种表面属性进行相乘,当该通道值为0时可以保护表面不受该属性影响。
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
Properties {
_Color ("Color Tint ",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) ="white"{}
_BumpMap("Normal Map",2D) = "bump"{}//决定凹凸性的纹理,决定的是漫反射
_BumpScale("Bump Scale",Float) = 1.0
_SpecularMask("Specualr Mask",2D) = "white"//决定遮罩的纹理,决定的是高光反射
_SpecularScale("Specular Scale",float) = 1.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 Color;
sampler2D _MainTex;
float _MainTex_ST;
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;//将第一组纹理坐标存储在这个变量中
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float viewDir : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UINTY_MATRIX_MVP,v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
TANFENT_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 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv));
tangentNormal.xy * = _BumpScale;
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHRMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(tangentNormal,tangetLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specularMask = tex2D(_SpecularMask,i.uv).r * _SpecularScale;//对SpecularMask进行采样,这里利用r来计算掩码值
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(tangetNormal,halfDir)),_Gloss) * speculatMask;
return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
FallBack "Specular"
}
(五)立方体纹理
(1)反射
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
#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)//SHADOW_COORDS这个宏后面的参数是指第几个通道,不要和其他的出现冲突,也就是要改变投影的颜色话必须要占用一个通道
};
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"
}
(2)折射
与上面基本相同,只是顶点着色器中计算的是
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);//入射光线,表面法线,折射率来计算折射
片元着色器中计算的是:
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
(3)菲涅耳反射
菲涅耳反射:描述了一种光学显现,当光线照射到物体表面时,一部分发生反射,一部分发生折射或散射,被反射的光和入射的光存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。例如,站在湖边,你会发现脚边的水是几乎透明的,而远处的水面只能看见远处。Shader基本与前面一样,顶点着色器中计算的是反射,片元着色器:
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;
(六)渲染纹理
渲染纹理非常重要。一般来说,一个摄像机的渲染结果汇输出到颜色缓冲区中,并显示在屏幕上,现代GPU允许把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(RTT),而不是传统的帧缓冲或者后备缓冲。多重渲染目标技术(MRT)指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景,延迟渲染就是用的多重渲染目标技术。
(1)镜子
镜子的实现:镜子的实现原理很简单,需要再创建一个摄像机,调整位置,裁剪平面视角等,使他的显示图像时我们希望的镜子图像。该摄像机不需要直接显示在屏幕上,而是用于渲染到纹理。Unity中创建一个渲染纹理 Render Texture,并把该渲染纹理在水平方向上翻转后直接显示到物体上即可。
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);
}
(2)玻璃
Unity中使用一种特殊的Pass来实现获取屏幕图像的目的,就是GrabPass,会把当前屏幕的图像绘制在一张纹理中,以便在后续的Pass中访问。使用GrabPass可以让我们对该物体后面的图像进行更复杂的处理,例如使用法线模拟折射效果,而不再是简单的与原屏幕颜色进行混合。GrabPass通常用于渲染透明物体,因此注意渲染队列的设置。"Queue"="Transparent"才能保证当渲染该物体时,所有的不透明的物体都已经被绘制在屏幕上。
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//反射效果
}
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,定义的字符串决定了会把抓取到的图像存在哪个纹理中
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);//计算_MainTex的采样坐标
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);//计算_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;
//由于要在片元着色器把法线从切线空间转换到世界空间,以便对CubeMap进行采样,因此需要计算从切线空间到世界空间的变换矩阵
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));//UnpackNormal对法线纹理进行采样和解码
// 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;//利用反射方向对_Cubemap进行采样,并和主纹理方向进行相乘得到反射颜色
fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
return fixed4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
GrabPass和渲染纹理+额外摄像机都可以抓取屏幕图像。GrabPass效率差一些。
GrabPass{"TextureName"}后续的Pass会利用TextureName来访问屏幕图像,每帧最多抓取一次。如果不说明图像,场景中多个物体使用的话会抓取多次,很昂贵。每帧进行多次抓取操作。
(7)深度纹理和法线纹理
深度纹理和法线纹理上进行采样等操作,会使物体不受外部光照因素影响,保存的是当前渲染物体的真实信息,类似于边缘检测等会更可靠。
深度纹理:就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。这些深度值来自于顶点变换后得到的归一化的设备坐标(NDC)。变换到NDC时,z坐标是从-1 , 1之间。为了可以存储在一张图中,我们将其转换:d = 0.5 *Zndc + 0.5。d对应了深度纹理中的像素值。
在Unity中,如果使用延迟渲染,自然会把深度纹理存储在G缓冲区中。如果没有使用延迟渲染,Unity中可以设置RenderType标签为Opaque的物体,判断它们使用的渲染队列是否小于2500,如果满足就渲染到深度和法线纹理中。
Unity获取深度纹理和法线纹理
camera.depthTextureMode = DepthTextureMode.Depth
camera.depthTextureMode = DepthTextureMode.DepthNormals
将=换为|=即可获取两种纹理
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv)