Unity shader入门精要笔记(十四)~(十五)
1.1.1 卡通风格的渲染
游戏渲染一般是以照相写实主义(photorealism)作为主要目标,但也有许多游戏使用了非真实感渲染(Non – Photorealistic Rendering, NPR)的方法来渲染游戏画面。非真实感渲染的一个主要目的是:使用一些渲染方法来使得画面达到和某些特殊的绘画风格相似的效果,如卡通、水彩风格。
卡通风格都有一些共同的特点,如:物体被黑色的线条描边,以及分明的明暗变化等。而实现卡通渲染可以基于色调的着色技术(tone-based shading)。卡通风格中,模型的高光往往是一块分界明显的纯色区域。
渲染轮廓线:
渲染模型轮廓线的方法可以划分为以下5种类型:
[1]基于观察角度和表面法线的轮廓线渲染
[2]过程式几何轮廓渲染线
[3]基于图像处理的轮廓线渲染
[4]基于轮廓边检测的轮廓线渲染
[5]混合以上几种渲染方法
实现步骤:
[1]创建场景,创建材质并附上shader,并把该材质赋给所需要的物体
[2]调节参数并观察
shader代码:
Shader "Unity Shaders Book/Chapter 14/Toon Shading" { Properties { //声明各属性 _Color ("Color Tint", Color) = (1, 1, 1, 1) _MainTex ("Main Tex", 2D) = "white" {} _Ramp ("Ramp Texture", 2D) = "white" {} //控制漫反射色调的渐变纹理 _Outline ("Outline", Range(0, 1)) = 0.1 //控制轮廓线宽度 _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1) //轮廓线颜色 _Specular ("Specular", Color) = (1, 1, 1, 1) //高光反射颜色 _SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01 //计算高光反射时使用的阈值 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry"} //定义对应标签 Pass { NAME "OUTLINE" //该Pass只渲染背面的三角面片,而用NAME命令为该pass定义了名称,因为描边在非真实感渲染中非常常见,为该pass定义名称可以让开发者在后面的使用中不需要再重复编写该pass,只需要调用它的名字即可 Cull Front //把正面的三角面片剔除掉,只渲染背面 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float _Outline; fixed4 _OutlineColor; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert (a2v v) { //顶点着色器 v2f o; float4 pos = mul(UNITY_MATRIX_MV, v.vertex); float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); //计算法线 normal.z = -0.5; //为了可以让背面的轮廓线遮挡正面,将顶点延起方向扩张 pos = pos + float4(normalize(normal), 0) * _Outline; //计算轮廓线 o.pos = mul(UNITY_MATRIX_P, pos); //投影到裁剪空间中 return o; } float4 frag(v2f i) : SV_Target { //片元着色器 return float4(_OutlineColor.rgb, 1); //渲染着整个背面即可 } ENDCG } Pass { Tags { "LightMode"="ForwardBase" } //前向渲染标签,光照模型需要Unity提供光照等信息,从而要对pass进行相应的设置 Cull Back //剔除背面 CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" #include "UnityShaderVariables.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _Ramp; fixed4 _Specular; fixed _SpecularScale; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; SHADOW_COORDS(3) }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos( v.vertex); //裁剪空间坐标 o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); //计算世界坐标下的法线坐标 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //计算世界坐标下的顶点坐标 TRANSFER_SHADOW(o); return o; } float4 frag(v2f i) : SV_Target { //归一化 fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); fixed4 c = tex2D (_MainTex, i.uv); //材质 fixed3 albedo = c.rgb * _Color.rgb; //计算材质的反射率 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; //计算环境光 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //利用内置宏计算当前世界坐标下的阴影值 fixed diff = dot(worldNormal, worldLightDir); diff = (diff * 0.5 + 0.5) * atten; fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb; //漫反射光照 fixed spec = dot(worldNormal, worldHalfDir); fixed w = fwidth(spec) * 2.0; //抗锯齿操作 fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale); //而0.0001是为了在 _SpecularScale为0时完全消除高光反射的光照 return fixed4(ambient + diffuse + specular, 1.0); //环境光、漫反射和高光反射相加的结果 } ENDCG } } FallBack "Diffuse" }
1.1.2 素描风格的渲染
通过提前生成的素描纹理来实现实时的素描风格渲染,这些纹理组成了一个色调艺术映射(Tonal Art Map, TAM)。随着笔触的增多,从而用于模拟不同光照下的漫反射效果。而多渐层纹理的生成并不是简单的对上一层纹理进行降采样,而是需要保持笔触之间的间距,从而更真实的模拟素描效果。
[1]创建场景,创建材质并赋予对应的shader,后把材质拖拽给模型
[2]材质球中给他附上对应的贴图,并添加图片作为背景
shader代码:
Shader "Unity Shaders Book/Chapter 14/Hatching" { Properties { _Color ("Color Tint", Color) = (1, 1, 1, 1) //模型的颜色 _TileFactor ("Tile Factor", Float) = 1 //纹理的平铺系数 _Outline ("Outline", Range(0, 1)) = 0.1 //描边的粗细 _Hatch0 ("Hatch 0", 2D) = "white" {} //渲染时的6张素描纹理, 其线条密度依次增加 _Hatch1 ("Hatch 1", 2D) = "white" {} _Hatch2 ("Hatch 2", 2D) = "white" {} _Hatch3 ("Hatch 3", 2D) = "white" {} _Hatch4 ("Hatch 4", 2D) = "white" {} _Hatch5 ("Hatch 5", 2D) = "white" {} } SubShader Tags { "RenderType"="Opaque" "Queue"="Geometry"} //定义合适标签 UsePass "Unity Shaders Book/Chapter 14/Toon Shading/OUTLINE" //使用卡通渲染的pass进行渲染轮廓 Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" #include "UnityShaderVariables.cginc" fixed4 _Color; //创建对应的属性 float _TileFactor; sampler2D _Hatch0; sampler2D _Hatch1; sampler2D _Hatch2; sampler2D _Hatch3; sampler2D _Hatch4; sampler2D _Hatch5; struct a2v { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; fixed3 hatchWeights0 : TEXCOORD1; //声明6张纹理,从而需要6个混合权重,从而把其存储在两个fixed3类型的变量中(hatchWeights0和hatchWeights1) fixed3 hatchWeights1 : TEXCOORD2; float3 worldPos : TEXCOORD3; //为添加阴影,从而需要声明worldPos变量 SHADOW_COORDS(4) //使用SHADOW_COORDS从而声明阴影纹理的采样坐标 }; v2f vert(a2v v) { //关键的顶点坐标 v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy * _TileFactor; //通过_TileFactor得到纹理采样坐标 fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex)); //归一化光照方向 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); //法线方向 fixed diff = max(0, dot(worldLightDir, worldNormal)); //通过法线方向和光线方向从而得到漫反射系数diff o.hatchWeights0 = fixed3(0, 0, 0); //把权重值初始化定位0 o.hatchWeights1 = fixed3(0, 0, 0); float hatchFactor = diff * 7.0; //把diff缩放到[0,7]得到hatchFactor,通过hatchFactor来计算其所处的自区间来计算对应的纹理混合权重 if (hatchFactor > 6.0) { // Pure white, do nothing } else if (hatchFactor > 5.0) { o.hatchWeights0.x = hatchFactor - 5.0; } else if (hatchFactor > 4.0) { o.hatchWeights0.x = hatchFactor - 4.0; o.hatchWeights0.y = 1.0 - o.hatchWeights0.x; } else if (hatchFactor > 3.0) { o.hatchWeights0.y = hatchFactor - 3.0; o.hatchWeights0.z = 1.0 - o.hatchWeights0.y; } else if (hatchFactor > 2.0) { o.hatchWeights0.z = hatchFactor - 2.0; o.hatchWeights1.x = 1.0 - o.hatchWeights0.z; } else if (hatchFactor > 1.0) { o.hatchWeights1.x = hatchFactor - 1.0; o.hatchWeights1.y = 1.0 - o.hatchWeights1.x; } else { o.hatchWeights1.y = hatchFactor; o.hatchWeights1.z = 1.0 - o.hatchWeights1.y; } o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //计算顶点的世界坐标 TRANSFER_SHADOW(o); //计算阴影纹理的采样坐标 return o; } fixed4 frag(v2f i) : SV_Target { fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x; //混合权重,对每张纹理进行采样,并和他们对应的权重值相乘得到每张纹理的采样颜色 fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y; fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z; fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x; fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y; fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z; fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z); //计算纯白在渲染中的贡献度 fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor; //混合各个颜色 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //得到阴影值 return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0); //混合相乘各个颜色值 } ENDCG } } FallBack "Diffuse" }
1.2 Unity shader入门精要笔记(十五)
1.2.1 消融效果
消融(dissolve)效果常见于游戏中的角色死亡、地图烧毁等效果。而该效果的原理为:噪声纹理+透明度测试。而对噪声纹理采样的结果和某个控制消融程度的阈值比较,如果小于阈值,就使用clip函数把它对应的像素裁剪掉,这些部分就对应了图中被“烧毁”的区域。而镂空边缘的烧焦效果则是将两种颜色混合,再使用pow函数处理后,与原纹理颜色混合后的效果。
实践步骤:
[1]布置场景,创建材质与对应的shader,并给该材质赋于对应的贴图
[2]运行查看效果
shader代码:
Shader "Unity Shaders Book/Chapter 15/Dissolve" { Properties { _BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0 //控制燃烧的效果,即少了多少面块 _LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1 //控制燃烧的边界粗细 _MainTex ("Base (RGB)", 2D) = "white" {} //主纹理 _BumpMap ("Normal Map", 2D) = "bump" {} //法线纹理 _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1) //燃烧边界的第一种颜色 _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1) //燃烧边界的渐变的第二种颜色 _BurnMap("Burn Map", 2D) = "white"{} //对应的噪声纹理 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry"} //定义合适的标签 Pass { Tags { "LightMode"="ForwardBase" } //前向渲染 Cull Off //关闭面片剔除 CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag //创建对应的属性 fixed _BurnAmount; fixed _LineWidth; sampler2D _MainTex; sampler2D _BumpMap; fixed4 _BurnFirstColor; fixed4 _BurnSecondColor; sampler2D _BurnMap; float4 _MainTex_ST; //对应的纹理的缩放系数 float4 _BumpMap_ST; float4 _BurnMap_ST; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uvMainTex : TEXCOORD0; float2 uvBumpMap : TEXCOORD1; float2 uvBurnMap : TEXCOORD2; float3 lightDir : TEXCOORD3; float3 worldPos : TEXCOORD4; SHADOW_COORDS(5) //阴影纹理 }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //顶点着色器得到关于该点的纹理信息,计算三张纹理对应的纹理坐标 o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap); o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); //把光源信息从模型空间变换到切线空间 TANGENT_SPACE_ROTATION; o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_SHADOW(o); //得到阴影信息 return o; } fixed4 frag(v2f i) : SV_Target { fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; //对噪声纹理进行采样 clip(burn.r - _BurnAmount); //将采样结果和用于控制消融效果的属性 _BurnAmount相减并传递给clip函数,如果结果小于1则该像素将会被剔除,从而不会显示到屏幕上,而如果通过了测试,则将进行正常的光照效果 //如果通过了测试,则进行正常的光照计算 float3 tangentLightDir = normalize(i.lightDir); //归一化 fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap)); fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb; //根据漫反射纹理得到材质的反射率albedo fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; //环境光 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); //漫反射 //计算燃烧颜色burncolor fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount); //使用smoothstep函数计算混合系数t,当t为1时,表明该像素位于消融的边界,当t为0时,表明该像素为正常的模型颜色 fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t); //使用t来混合两种火焰颜色 burnColor = pow(burnColor, 5); //pow让其更加接近烧焦效果 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount)); //使用t来混合正常的光照颜色(环境光+漫反射)和烧焦颜色,而step是保证_BurnAmount为0时不显示任何消融效果 return fixed4(finalColor, 1); //返回混合后的颜色值finalColor } ENDCG } // Pass to render object as a shadow caster //要把被剔除的区域取消其阴影投射 Pass { Tags { "LightMode" = "ShadowCaster" } //用于投射阴影的pass的lightmode被设置为shadowcaster CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster //指明所需要的编译指令 #include "UnityCG.cginc" fixed _BurnAmount; sampler2D _BurnMap; float4 _BurnMap_ST; struct v2f { V2F_SHADOW_CASTER; //得到定义阴影投射所需要定义的变量 float2 uvBurnMap : TEXCOORD1; }; v2f vert(appdata_base v) { //顶点着色器 v2f o; TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) //填充V2F_SHADOW_CASTER;背后声明的一些变量,而我们只需要在顶点着色器中关注自定义的计算部分 o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); //需要计算噪声纹理的采样坐标 return o; } fixed4 frag(v2f i) : SV_Target { fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; clip(burn.r - _BurnAmount); //利用噪声纹理的采样结果uvBurnMap来剔除片元 SHADOW_CASTER_FRAGMENT(i) //完成投射部分 } ENDCG } } FallBack "Diffuse" }
1.2.2 水波效果
而模拟实时水面的过程中,往往也会使用噪声纹理。噪声纹理通常会用作一个高度图,以不断修改水面的法线方向。而为了模拟水不断流动的效果,通常会使用和时间相关的变量来对噪声纹理进行采样,当得到法线纹理信息后,在进行正常的反射+折射计算,得到最后的水面波动效果。
在本节中将会使用一个由噪声纹理得到的法线贴图,实现一个包含菲涅尔反射的水面效果。
使用一张立方体纹理(Cubemap)作为环境纹理,模拟反射;为了模拟折射效果,使用GrabPass来获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标进行偏移,再使用该坐标对渲染纹理进行屏幕采样,从而模拟近似的折射效果。而水波的法线纹理是由一张噪声纹理生成而得,而且会随着时间变化不断评议,模拟波光粼粼的效果。而同时没有使用一个定植来混合反射和折射颜色,而是使用之前提到的菲涅尔系数来动态决定混合系数。
计算菲涅尔系数公式:
而和是视角方向和法线方向,它们的夹角越小,fresnel值越小,反射值越弱,折射越强。菲涅尔系数经常会用于边缘光照的计算中。
实践步骤:
[1]构建场景,设立一个平面后创建材质赋给它,并把对应的shader赋给该材质,后给材质赋上对应的贴图纹理
[2]运行查看效果
shader代码:
Shader "Unity Shaders Book/Chapter 15/Water Wave" { Properties { //声明属性 _Color ("Main Color", Color) = (0, 0.15, 0.115, 1) //控制水面颜色 _MainTex ("Base (RGB)", 2D) = "white" {} //主纹理 _WaveMap ("Wave Map", 2D) = "bump" {} //由噪声纹理生成的法线纹理 _Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} //模拟反射的立方体纹理 _WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 //法线纹理在x方向上的平移速度 _WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01 //法线纹理在y方向上的平移速度 _Distortion ("Distortion", Range(0, 100)) = 10 //模拟折射时图像的扭曲程度 } SubShader { // We must be transparent, so other objects are drawn before this one. Tags { "Queue"="Transparent" "RenderType"="Opaque" } //对应的标签,Queue设置成Transparent可以确保该物体渲染时,其他所有不透明物体都已经被渲染到屏幕上,否则可能无法正确得到“透过水面看到的图像”;而设置为RenderType则是为了在使用着色器替换时,该物体可以在需要时被正确渲染 // This pass grabs the screen behind the object into a texture. // We can access the result in the next pass as _RefractionTex GrabPass { "_RefractionTex" } //GrabPass定义了一个抓去屏幕图像的Pass,在该Pass中定义一个字符串,该字符串内部的名称决定了抓去得到的屏幕图像将会被存入哪个纹理中 Pass { Tags { "LightMode"="ForwardBase" } //前向渲染 CGPROGRAM #include "UnityCG.cginc" #include "Lighting.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag //定义对应属性 fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _WaveMap; float4 _WaveMap_ST; samplerCUBE _Cubemap; fixed _WaveXSpeed; fixed _WaveYSpeed; float _Distortion; sampler2D _RefractionTex; //前面GrabPass定义的抓去屏幕图像并存储到该纹理中 float4 _RefractionTex_TexelSize; //可以得到该纹理的纹素大小,例如一个大小为256x512的纹理,它的像素大小为(1/256,1/512);对屏幕图像的采样坐标进行偏移时使用该变量 struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 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); //通过调用ComputeGrabScreenPos来得到对应被抓取屏幕图像的采样坐标 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); //计算 _MainTex采样坐标 o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap); //计算 _WaveMap采样坐标 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进行采样,从而需要在这里计算该顶点对应的从切线空间到世界空间的变换矩阵,并把该矩阵的每一行分别存储到TtoW0、TtoW1、TtoW2的xyz分量中 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); //得到切线空间下的3个坐标轴(x、y、z轴分别对应了切线、副切线、法线方向)在世界空间下的表示,再依次按列组成一个变换矩阵即可。 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); //通过变量w得到世界坐标 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); //得到片元着色器对应的视角方向 float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed); //通过_Time.y变量和_WaveXSpeed、_WaveYSpeed计算法线纹理当前偏移量 // Get the normal in tangent space fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb; //利用该值对法线纹理进行两次采样从而模拟两层交叉的水面波动的效果 fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb; fixed3 bump = normalize(bump1 + bump2); //归一化得到切线空间下的法线方向 // Compute the offset in tangent space float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; //利用该值和其他属性来对屏幕图像的采样坐标进行偏移,模拟折射效果,offset越大,水面扭曲程度越大 i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; //选择切线空间下的法线方向来进行偏移,因为该空间下的法线可以反应顶点局部空间下的法线方向,在计算偏移后的屏幕坐标,需要把偏移量和屏幕坐标的z分量相乘,从而模拟深度变大, fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb; //对scrPos进行透视除法,再使用该坐标对抓取的屏幕图像_RefractionTex进行采样 // Convert the normal to world space bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump))); //把法线方向从切线空间变换到世界空间下,并分别和法线方向点乘,构建新的法线方向 fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed); fixed3 reflDir = reflect(-viewDir, bump); //,并据此得到视觉方向相对于法线方向的反射方向 fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb; //使用Cubemap进行采样并把结果和主纹理颜色相乘后得到反射颜色 fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4); //计算菲涅尔系数 fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel); //混合折射和反射颜色,并作为最终的输出颜色 return fixed4(finalColor, 1); //最终输出颜色输出 } ENDCG } } // Do not cast shadow FallBack Off }
1.2.3 再谈全局雾效
1.13.3中使用深度纹理来实现基于屏幕后处理的全局雾效是由深度纹理重建每个像素在世界空间下的位置,再使用一个基于高度的公式来计算雾效的混合系数,最后使用该系数来混合雾的颜色和原屏幕颜色。其实现的是一个基于高度的均匀雾效,即便在同一个高度上,雾的浓度是相同的。
而通过一张噪声纹理可以时雾的浓度是相同的,同时让雾看起来更加飘渺。
实践步骤:
[1]和13.3的屏幕后处理的全局雾效差不多,创建场景后,给摄像机添加对应的脚本后赋给合适的shader与贴图给脚本
[2]观察效果
脚本代码:
using UnityEngine; using System.Collections; public class FogWithNoise : PostEffectsBase { public Shader fogShader; private Material fogMaterial = null; public Material material { get { fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial); return fogMaterial; } } private Camera myCamera; public Camera camera { get { if (myCamera == null) { myCamera = GetComponent<Camera>(); } return myCamera; } } private Transform myCameraTransform; public Transform cameraTransform { get { if (myCameraTransform == null) { myCameraTransform = camera.transform; } return myCameraTransform; } } [Range(0.1f, 3.0f)] public float fogDensity = 1.0f; public Color fogColor = Color.white; public float fogStart = 0.0f; public float fogEnd = 2.0f; public Texture noiseTexture; [Range(-0.5f, 0.5f)] public float fogXSpeed = 0.1f; [Range(-0.5f, 0.5f)] public float fogYSpeed = 0.1f; [Range(0.0f, 3.0f)] //控制噪声的程度 public float noiseAmount = 1.0f; void OnEnable() { GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth; //得到摄像机的深度纹理 } void OnRenderImage (RenderTexture src, RenderTexture dest) { if (material != null) { Matrix4x4 frustumCorners = Matrix4x4.identity; //得到计算近裁剪平面的4个角对应的向量 //赋值 //下面计算都是关于裁剪平面四个角的计算 float fov = camera.fieldOfView; float near = camera.nearClipPlane; float aspect = camera.aspect; float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); Vector3 toRight = cameraTransform.right * halfHeight * aspect; Vector3 toTop = cameraTransform.up * halfHeight; Vector3 topLeft = cameraTransform.forward * near + toTop - toRight; float scale = topLeft.magnitude / near; topLeft.Normalize(); topLeft *= scale; Vector3 topRight = cameraTransform.forward * near + toRight + toTop; topRight.Normalize(); topRight *= scale; Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight; bottomLeft.Normalize(); bottomLeft *= scale; Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop; bottomRight.Normalize(); bottomRight *= scale; frustumCorners.SetRow(0, bottomLeft); frustumCorners.SetRow(1, bottomRight); frustumCorners.SetRow(2, topRight); frustumCorners.SetRow(3, topLeft); material.SetMatrix("_FrustumCornersRay", frustumCorners); material.SetFloat("_FogDensity", fogDensity); material.SetColor("_FogColor", fogColor); material.SetFloat("_FogStart", fogStart); material.SetFloat("_FogEnd", fogEnd); material.SetTexture("_NoiseTex", noiseTexture); material.SetFloat("_FogXSpeed", fogXSpeed); material.SetFloat("_FogYSpeed", fogYSpeed); material.SetFloat("_NoiseAmount", noiseAmount); //调用Graphics.Blit (src, dest, material);把渲染结果传递给屏幕上 Graphics.Blit (src, dest, material); } else { Graphics.Blit(src, dest); } } }
shader代码:
Shader "Unity Shaders Book/Chapter 15/Fog With Noise" { Properties { //声明属性 _MainTex ("Base (RGB)", 2D) = "white" {} _FogDensity ("Fog Density", Float) = 1.0 _FogColor ("Fog Color", Color) = (1, 1, 1, 1) _FogStart ("Fog Start", Float) = 0.0 _FogEnd ("Fog End", Float) = 1.0 _NoiseTex ("Noise Texture", 2D) = "white" {} _FogXSpeed ("Fog Horizontal Speed", Float) = 0.1 _FogYSpeed ("Fog Vertical Speed", Float) = 0.1 _NoiseAmount ("Noise Amount", Float) = 1 } SubShader { CGINCLUDE #include "UnityCG.cginc" float4x4 _FrustumCornersRay; sampler2D _MainTex; half4 _MainTex_TexelSize; sampler2D _CameraDepthTexture; //摄像机的深度纹理 half _FogDensity; //雾的大小 fixed4 _FogColor; float _FogStart; float _FogEnd; sampler2D _NoiseTex; half _FogXSpeed; //雾x方向的移动 half _FogYSpeed; //y方向的移动 half _NoiseAmount; //噪声强度 struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 uv_depth : TEXCOORD1; float4 interpolatedRay : TEXCOORD2; }; v2f vert(appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; o.uv_depth = v.texcoord; #if UNITY_UV_STARTS_AT_TOP //适配不同平台 if (_MainTex_TexelSize.y < 0) o.uv_depth.y = 1 - o.uv_depth.y; #endif int index = 0; if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) { index = 0; } else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) { index = 1; } else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) { index = 2; } else { index = 3; } #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0) index = 3 - index; #endif o.interpolatedRay = _FrustumCornersRay[index]; return o; } fixed4 frag(v2f i) : SV_Target { float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth)); //根据深度纹理构建该像素在世界空间中的位置 float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed); //计算雾的偏移量 float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount; //对噪声纹理进行采样从而得到噪声值 float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));//计算雾效混合系数 fixed4 finalColor = tex2D(_MainTex, i.uv); finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity); //混合最终颜色 return finalColor; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } FallBack Off