几何着色器在不增加drawcall的前提下完成假阴影
程序员文章站
2022-03-18 17:03:16
...
项目之前想要做一个假阴影,一般有几种做法:
第一种是直接把角色的顶点的世界坐标的y坐标映射到地面高度的位置,用另一个pass渲染他,让他的颜色可以控制就好。这样能产生一个顶部灯光的效果,但是角色有多少部件就会有多多少个drawcall。二点顶点数也是翻倍的。
第二种是直接用shadowmap,其实本质上也会多一个pass去渲染他的投射阴影,所以dc没减少。
第三种是后处理,得到屏幕贴图后叠加在角色底部。这样其实效率也不高。dc其实也一点没减少。
第四种就是我们在考虑opengl3.2以上的机型的情况下可以考虑用几何着色器来做。
几何着色器可以为输入的图元附加其它信息,同时还可以访问图元的邻接顶点信息。
具体就是在顶点后做一次几何着色器让他的三角形多一倍,放到脚下就好了。
Shader "xxxxx/xxxxx" {
Properties{
_MainTex("Albedo Tex", 2D) = "white" {}
_TintColor("TintColor",color) = (1,1,1,1)
[HDR]_Emission("EmissionColor(MainTex.a)",Color)=(0,0,0,0)
_HighlightPow("Highlight Power", Range(0,4)) = 2
_HighlightAmt("Highlight Strength", Range(0,4)) = 2
[HDR]_RimColor("RimColor", Color) = (1,0,0,1)
_RimPower("RimPower", Range(0, 8)) = 4.5
_EdgeWidth("EdgeWidth",Range(0,0.5)) = 0.1
_EdgeColor_1("EdgeColor_1",Color) = (1,0,0,1)
_EdgeColor_2("EdgeColor_2",Color) = (1,1,0,1)
_DissolveTex("DissolveTex",2D) = "white" {}
_Dissolve("Dissolve",Range(0,1)) = 0
_ShadowColor("ShadowColor", color) = (0,0,0,1)
_ShadowXOffset("ShadowXOffset", Range(0,5)) = 0.6
_ShadowYOffset("ShadowYOffset", Range(0,5)) = 2.0
_ShadowZOffset("ShadowZOffset", Range(0,5)) = 0.0
_ShadowWidth("ShadowWidth", Range(0,5)) = 1.0
}
Subshader{
Fog{ Mode Off }
Lighting Off
Cull Back
Tags{ "RenderType" = "Opaque" "Queue" = "Geometry" "IgnoreProjector" = "true" }
Pass
{
CGPROGRAM
#pragma target 3.5
#include "UnityCG.cginc"
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
// =========================================================
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform fixed4 _Emission;
uniform half _HighlightAmt;
uniform half _HighlightPow;
uniform float3 _LightPos;
uniform float4 _LightColor;
uniform float _LightRangeMin;
uniform float _LightRangeMax;
uniform half _lightBrightness;
uniform float4 _ShadeColor;
uniform float _shadeBrightness;
uniform fixed4 _TintColor;
uniform float _RimPower;
uniform fixed4 _RimColor;
uniform fixed4 _ShadowColor;
uniform fixed _ShadowXOffset;
uniform fixed _ShadowYOffset;
uniform fixed _ShadowZOffset;
uniform fixed _ShadowWidth;
fixed _EdgeWidth;
fixed4 _EdgeColor_1;
fixed4 _EdgeColor_2;
sampler2D _DissolveTex;
float4 _DissolveTex_ST;
fixed _Dissolve;
//-------顶点向几何阶段传递数据
struct v2g {
float4 pos:SV_POSITION;
float4 texture_uv:TEXCOORD0;
float3 world_nrm : TEXCOORD1;
float3 light_dir : TEXCOORD2;
float worldPos : TEXCOORD3;
fixed RimFresnel : TEXCOORD4;
};
//-------几何阶段向片元阶段传递数据
struct g2f
{
float4 vertex : SV_POSITION;
float4 texture_uv : TEXCOORD0;
float3 world_nrm : TEXCOORD1;
float3 light_dir : TEXCOORD2;
float worldPos : TEXCOORD3;
fixed RimFresnel : TEXCOORD4;
float isShadow : TEXCOORD5;
};
// =========================================================
v2g vert(appdata_tan v)
{
v2g o;
o.pos = v.vertex;
//o.texture_uv = TRANSFORM_TEX(v.texcoord, _MainTex);
// todo: 原本中的区别
o.texture_uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.texture_uv.zw = TRANSFORM_TEX(v.texcoord,_DissolveTex);
o.world_nrm = UnityObjectToWorldNormal(v.normal);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.light_dir = normalize(_LightPos - worldPos);
//o.light_dst = smoothstep(_LightRangeMax, _LightRangeMin, distance(worldPos, _LightPos));
o.worldPos = worldPos;
float3 worldViewDir = normalize(WorldSpaceViewDir(v.vertex));
o.RimFresnel = pow( 1.0 - dot(o.world_nrm, worldViewDir ), _RimPower );
return o;
}
g2f AddVertex(v2g input)
{
g2f o = (g2f)0;
o.vertex = UnityObjectToClipPos(input.pos);
o.texture_uv = input.texture_uv;
o.world_nrm = input.world_nrm;
o.light_dir = input.light_dir;
//o.light_dst = input.light_dst;
o.RimFresnel = input.RimFresnel;
o.isShadow = 0;
return o;
}
g2f AddShadowVertex(v2g input)
{
g2f o = (g2f)0;
o.vertex = UnityObjectToClipPos(float4(-(input.pos.x * _ShadowWidth + _ShadowXOffset), (input.pos.y - _ShadowYOffset), input.pos.z + _ShadowZOffset, input.pos.w));
o.texture_uv = input.texture_uv;
o.world_nrm = input.world_nrm;
o.light_dir = input.light_dir;
//o.light_dst = input.light_dst;
o.RimFresnel = input.RimFresnel;
o.isShadow = 1;
return o;
}
//-------静态制定单个调用的最大顶点个数
[maxvertexcount(6)]
void geom(triangle v2g input[3], inout TriangleStream<g2f> outStream) {
//for (int i = 0; i < 3; i++) {
g2f o1 = AddVertex(input[0]);
g2f o2 = AddVertex(input[1]);
g2f o3 = AddVertex(input[2]);
//-----将一个顶点添加到输出流列表
outStream.Append(o1);
outStream.Append(o2);
outStream.Append(o3);
//}
//-------restart strip可以模拟一个primitives list
outStream.RestartStrip();
g2f on1 = AddShadowVertex(input[0]);
g2f on2 = AddShadowVertex(input[1]);
g2f on3 = AddShadowVertex(input[2]);
outStream.Append(on1);
outStream.Append(on2);
outStream.Append(on3);
outStream.RestartStrip();
}
fixed4 frag(g2f i) : COLOR
{
fixed4 colortex = tex2D(_MainTex, i.texture_uv);
fixed4 outcolor = colortex * _TintColor;
fixed3 nrm = i.world_nrm;
fixed dotProd = dot(nrm, i.light_dir);
fixed dotLit = 1 + pow(max(0, dotProd), _HighlightPow) * _HighlightAmt;
fixed dotDark = 0.5 * max(0, -dotProd);
outcolor *= dotLit - dotDark;
half light_dst = smoothstep(_LightRangeMax, _LightRangeMin, distance(i.worldPos, _LightPos));
outcolor *= lerp(_ShadeColor, _LightColor, light_dst);
// RimColor部分
fixed3 EmissionCol = colortex.a * _Emission.rgb * _Emission.a;
outcolor.rgb += _RimColor.rgb*i.RimFresnel*_RimColor.a + EmissionCol;
fixed dissolve = tex2D(_DissolveTex,i.texture_uv.zw).r - _Dissolve;
fixed t =(1 - smoothstep(0,_EdgeWidth,dissolve))* step(0.0001,_Dissolve);
fixed3 EdgeColor = pow(lerp(_EdgeColor_1,_EdgeColor_2,t),5);
outcolor.rgb=lerp(outcolor.rgb,EdgeColor,t);
clip(dissolve);
outcolor.rgb = i.isShadow == 0 ? outcolor.rgb : _ShadowColor.rgb;
return outcolor;
}
ENDCG
}
}
}
主要可以看geom这个部分。而且要注意顶点到几何,几何到片源的结构传递。
随便摆一个位置来看效果
可以看到dc没有因为阴影而增加。
但是也因为他支持的opengl比较高。所以要谨慎使用。