unity3d 网格shader,unity网格怎么去掉
墨初 知识笔记 66阅读
文章目录 前言一、网格阴影原理1、在世界空间下把角色模型在Y轴上压缩成一个面片把修改成像影子的颜色2、把压缩后的面片移动到合适的位置把模型和阴影面片错开3、实现距离脚进的阴影偏移少距离脚远的阴影偏移多 二、网格阴影的优缺点优点缺点 三、模型网格阴影的实现1、在 LOD 400 的 SubShader 的第一个 Pass 中我们只保留最基础的模型渲染效果2、然后继续加一个Pass用来实现模型网格阴影
前言

Unity中Shader的模型网格阴影,一般用于 低配设置 情况下模拟影子的样子节省性能。适用于地面比较平缓的游戏
一、网格阴影原理 1、在世界空间下把角色模型在Y轴上压缩成一个面片把修改成像影子的颜色 2、把压缩后的面片移动到合适的位置把模型和阴影面片错开 3、实现距离脚进的阴影偏移少距离脚远的阴影偏移多 二、网格阴影的优缺点 优点1.实现简单
2.效果实时清晰

1.需要两个Pass来渲染
2.在地面起伏较大时容易穿帮
我们继续使用上一篇文章中的Shader来进行测试
Unity中Shader的ShaderLOD我们在中配的Shader中实现网格阴影
需要两个Pass,一个Pass实现基本的渲染另一个Pass实现模型网格阴影
1、在 LOD 400 的 SubShader 的第一个 Pass 中我们只保留最基础的模型渲染效果Pass { //Tags{LightModeForwardBase} CGPROGRAM #pragma vertex vert #pragma fragment frag //#pragma multi_compile_fwdbase //剔除无用的变体 //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON //自己定义阴影需要使用的变体 #include UnityCG.cginc #include AutoLight.cginc sampler2D _MainTex; float _Clip; sampler2D _DissolveTex; //这个四维向量xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST float4 _DissolveTex_ST; //因为 在使用渐变纹理时只使用了 渐变纹理的 u 坐标所以把 sampler2D 换为 sampler sampler _RampTex; struct appdata { float4 vertex : POSITION; float4 uv : TEXCOORD0; }; struct v2f { float4 uv : TEXCOORD0; float4 pos : SV_POSITION; float4 worldPos :TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); //为了减少传入的值 所以就不创建新变量来存储而是把 uv 改为 四维向量 来用 //使用 o.uv 的 xy 来存放 原人物贴图 //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值 o.uv.xy v.uv.xy; //o.uv.zw v.uv * _DissolveTex_ST.xy _DissolveTex_ST.zw; o.uv.zw TRANSFORM_TEX(v.uv,_DissolveTex); TRANSFER_SHADOW(o) //把顶点转化到世界空间下 o.worldPos mul(unity_ObjectToWorld,v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv.xy); //外部获取的 纹理 使用前都需要采样 fixed4 dissolveTex tex2D(_DissolveTex,i.uv.zw); //片段的取舍 clip(dissolveTex.r - _Clip); //进行归一化 fixed4 dissolveValue saturate((dissolveTex.r - _Clip) / (_Clip 0.1 - _Clip)); fixed4 rampTex tex1D(_RampTex,dissolveValue.r); //col rampTex; return col; } ENDCG }
2、然后继续加一个Pass用来实现模型网格阴影 因为我们需要修改的东西只跟顶点有关所以在应用程序传入 和 顶点传入片元 时只用传入顶点信息即可 struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};
把模型压缩成一个面为了防止面片角度乱动所以需要转化到世界空间下来压缩
压缩的时候修改世界空间下的 y 值即可
把顶点信息从 世界空间 下转化到裁剪空间使用 UNITY_MATRIX_VP
v2f vert(appdata v)
{
v2f o;
//在世界空间下 压缩模型顶点防止压缩后角度乱动
float4 worldPos mul(unity_ObjectToWorld,v.vertex);
worldPos.y 0.001;
//转化为裁剪空间内的坐标转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间
o.pos mul(UNITY_MATRIX_VP,worldPos);
return o;
}
_Shadow(“Shadow”,Float) 0.001 用该值来替代 上面的 0.001
替代后效果可以修改影子生成的位置了
worldPos.xz float2(-1,2);
在偏移后发现偏移值并没有离脚近偏移少离脚远偏移多实现该效果只需要让加减的偏移值乘以角色以脚为坐标原点的 y 值即可
但是我们的模型原点是在模型中央的那么要获取从脚为坐标原点的 y 值
只需要使用角色的 y 值 减去 之前影子所在的 y 值即可
worldPos.xz float2(-1,2) * (worldPosY - _Shadow);
实现后效果
使用四维向量的原因节省性能。要让影子透明需要修改混合模式后才看得到效果
属性栏中
_Shadow(“Offset(XZ) Height(Y) Alpha(W)”,Vector) (-1,0.001,1,0)
SubShader中
Blend SrcAlpha OneMinusSrcAlpha
顶点着色器 和 片元着色器中
v2f vert(appdata v) { v2f o; //在世界空间下 压缩模型顶点后防止压缩后角度乱动 float4 worldPos mul(unity_ObjectToWorld,v.vertex); float worldPosY worldPos.y; worldPos.y _Shadow.y; worldPos.xz _Shadow.xz * (worldPosY - _Shadow.y); //转化为裁剪空间内的坐标转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间 o.pos mul(UNITY_MATRIX_VP,worldPos); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col 0; col.a _Shadow.w; return col; }
修改后效果
但是我们会发现 使影子变透明后压缩后的模型会出现 模型另一面的效果这个情况和之前模板测试时一模一样使用模板测试就可解决
//使用模板测试 让 透明后的模型别看见后面的效果(对比后不等于的直接替换)
Stencil
{
//这个值目前是随便取的
Ref 100
//不等于上面的值的话
Comp NotEqual
//替代
Pass Replace
}
最终代码
//网格阴影原理Shader MyShader/P1_7_6{ Properties { [Enum(Off,0,On,1)]_ZWrite(ZWrite,int) 0 [Enum(UnityEngine.Rendering.CompareFunction)]_ZTest(ZTest,int) 0 //使用这个标签可以使外部暴露属性有标题 [Header(Base)] [NoScaleOffset]_MainTex (Texture, 2D) white {} _Clip(Clip,Range(0,1)) 0 //使用这个标签可以 在两行暴露属性之间加 间隙 [Space(10)] [Header(Dissolve)] _DissolveTex(DissolveTex,2D) black{} [NoScaleOffset]_RampTex(RampTex(RGB),2D) black {} [Header(Shadow)] _Shadow(Offset(XZ) Height(Y) Alpha(W),Vector) (-1,0.001,1,0) } SubShader { Tags{Queue Geometry} LOD 600 Blend Off Cull Back /*ZWrite [_ZWrite] ZTest [_ZTest]*/ Offset -1,-1 UsePass MyShader/P1_6_4/XRay Pass { //Tags{LightModeForwardBase} CGPROGRAM #pragma vertex vert #pragma fragment frag //#pragma multi_compile_fwdbase //剔除无用的变体 //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON //自己定义阴影需要使用的变体 #pragma multi_compile DIRECTIONAL SHADOWS_SCREEN #include UnityCG.cginc #include AutoLight.cginc sampler2D _MainTex; float _Clip; sampler2D _DissolveTex; //这个四维向量xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST float4 _DissolveTex_ST; //因为 在使用渐变纹理时只使用了 渐变纹理的 u 坐标所以把 sampler2D 换为 sampler sampler _RampTex; struct appdata { float4 vertex : POSITION; float4 uv : TEXCOORD0; }; //1.在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量用作阴影的采样坐标. struct v2f { float4 uv : TEXCOORD0; float4 pos : SV_POSITION; UNITY_SHADOW_COORDS(1) float4 worldPos :TEXCOORD2; }; //2.在顶点着色器中添加TRANSFER_SHADOW(o)用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标为采样阴影纹理使用. v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); //为了减少传入的值 所以就不创建新变量来存储而是把 uv 改为 四维向量 来用 //使用 o.uv 的 xy 来存放 原人物贴图 //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值 o.uv.xy v.uv.xy; //o.uv.zw v.uv * _DissolveTex_ST.xy _DissolveTex_ST.zw; o.uv.zw TRANSFORM_TEX(v.uv,_DissolveTex); TRANSFER_SHADOW(o) //把顶点转化到世界空间下 o.worldPos mul(unity_ObjectToWorld,v.vertex); return o; } //3.在片断着色器中添加UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)其中atten即存储了采样后的阴影. fixed4 frag (v2f i) : SV_Target { UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) fixed4 col tex2D(_MainTex, i.uv.xy); //把阴影 和 纹理相乘 col * atten; //外部获取的 纹理 使用前都需要采样 fixed4 dissolveTex tex2D(_DissolveTex,i.uv.zw); //片段的取舍 clip(dissolveTex.r - _Clip); //进行归一化 fixed4 dissolveValue saturate((dissolveTex.r - _Clip) / (_Clip 0.1 - _Clip)); fixed4 rampTex tex1D(_RampTex,dissolveValue.r); //col rampTex; return col; } ENDCG } //阴影的投射 Pass { //1、设置 LightMode ShadowCaster Tags{LightMode ShadowCaster} CGPROGRAM #pragma vertex vert #pragma fragment frag //需要添加一个 Unity变体 #pragma multi_compile_shadowcaster #include UnityCG.cginc //声明消融使用的变量 float _Clip; sampler2D _DissolveTex; float4 _DissolveTex_ST; //2、appdata中声明float4 vertex:POSITION;和half3 normal:NORMAL;这是生成阴影所需要的语义. //注意在appdata部分我们几乎不要去修改名字 和 对应的类型。 //因为在Unity中封装好的很多方法都是使用这些标准的名字 struct appdata { float4 vertex:POSITION; half3 normal:NORMAL; float4 uv:TEXCOORD; }; //3、v2f中添加V2F_SHADOW_CASTER;用于声明需要传送到片断的数据. struct v2f { float4 uv : TEXCOORD; V2F_SHADOW_CASTER; }; //4、在顶点着色器中添加TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象. v2f vert(appdata v) { v2f o; o.uv.zw TRANSFORM_TEX(v.uv,_DissolveTex); TRANSFER_SHADOW_CASTER_NORMALOFFSET(o); return o; } //5、在片断着色器中添加SHADOW_CASTER_FRAGMENT(i) fixed4 frag(v2f i) : SV_Target { //外部获取的 纹理 使用前都需要采样 fixed4 dissolveTex tex2D(_DissolveTex,i.uv.zw); //片段的取舍 clip(dissolveTex.r - _Clip); SHADOW_CASTER_FRAGMENT(i); } ENDCG } } SubShader { Blend SrcAlpha OneMinusSrcAlpha LOD 400 //使用模板测试 让 透明后的模型别看见后面的效果(对比后不等于的直接替换) Stencil { //这个值目前是随便取的 Ref 100 //不等于上面的值的话 Comp NotEqual //替代 Pass Replace } Pass { //Tags{LightModeForwardBase} CGPROGRAM #pragma vertex vert #pragma fragment frag //#pragma multi_compile_fwdbase //剔除无用的变体 //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON //自己定义阴影需要使用的变体 #include UnityCG.cginc #include AutoLight.cginc sampler2D _MainTex; float _Clip; sampler2D _DissolveTex; //这个四维向量xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST float4 _DissolveTex_ST; //因为 在使用渐变纹理时只使用了 渐变纹理的 u 坐标所以把 sampler2D 换为 sampler sampler _RampTex; struct appdata { float4 vertex : POSITION; float4 uv : TEXCOORD0; }; struct v2f { float4 uv : TEXCOORD0; float4 pos : SV_POSITION; float4 worldPos :TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); //为了减少传入的值 所以就不创建新变量来存储而是把 uv 改为 四维向量 来用 //使用 o.uv 的 xy 来存放 原人物贴图 //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值 o.uv.xy v.uv.xy; //o.uv.zw v.uv * _DissolveTex_ST.xy _DissolveTex_ST.zw; o.uv.zw TRANSFORM_TEX(v.uv,_DissolveTex); TRANSFER_SHADOW(o) //把顶点转化到世界空间下 o.worldPos mul(unity_ObjectToWorld,v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv.xy); //外部获取的 纹理 使用前都需要采样 fixed4 dissolveTex tex2D(_DissolveTex,i.uv.zw); //片段的取舍 clip(dissolveTex.r - _Clip); //进行归一化 fixed4 dissolveValue saturate((dissolveTex.r - _Clip) / (_Clip 0.1 - _Clip)); fixed4 rampTex tex1D(_RampTex,dissolveValue.r); //col rampTex; return col; } ENDCG } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc float4 _Shadow; struct appdata { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert(appdata v) { v2f o; //在世界空间下 压缩模型顶点后防止压缩后角度乱动 float4 worldPos mul(unity_ObjectToWorld,v.vertex); float worldPosY worldPos.y; worldPos.y _Shadow.y; worldPos.xz _Shadow.xz * (worldPosY - _Shadow.y); //转化为裁剪空间内的坐标转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间 o.pos mul(UNITY_MATRIX_VP,worldPos); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col 0; col.a _Shadow.w; return col; } ENDCG } } // Fallback Legacy Shaders/VertexLit}
最终效果