首页 > 其他分享 >假阴影,低开销的阴影实现方式

假阴影,低开销的阴影实现方式

时间:2024-07-16 12:07:39浏览次数:16  
标签:开销 方式 阴影 worldPos GroundY float4 Shadow v2f

参考:Unity无光照假阴影Shader实现及常见问题总结 - 简书 (jianshu.com)

 

游戏实现阴影的常见处理方式 (动态人或物,非烘焙)

1.实时光照
实时光照属于真阴影,一般来说效果是最好的,但是开销也是最大的。 Shadow Map(阴影贴图)跟Soft Shadows(软阴影) - JeasonBoy - 博客园 (cnblogs.com)

2.脚底放置阴影面片模拟阴影
一般是无光照小型游戏的常见解决方案,开销较小,表现形式较差,面片是死的,无法根据人物动作变化

3.通过顶点shader变换成面片模拟阴影
如上图所示
优点 : 表现形式上比方案2强,阴影可跟随顶点动画,开销比实时阴影要少
缺点 : 无法在 "非平面" 使用,比如在斜坡上,会穿帮

4.通过 Projector 或者 Decal 来模拟投射阴影
优点 : 表现效果更近一步,也可以在斜面上进行投影了
缺点 : 开销也更近一步

 

方式3实现思路
1.我们通过2个Pass来渲染,第二个Pass正常渲染角色,第一个Pass模拟渲染阴影
2.我们需要将模型的所有 Y 值压到地面高度,这样就形成了一个头顶俯视图的阴影效果
3.我们再对 XZ 方向进行偏移,偏移量根据模型原先 Y 值高度为参考做插值
4.阴影的方向我们规定在 XZ 平面上 (X=0,Z=1) 为初始默认方向,以这个向量为基准进行旋转
5.旋转我们可以通过 二维旋转矩阵 来计算   Shader代码
Shader "loom/fake_shadow_test_pass_order"
{
    Properties
    {
        //材质属性面板
        _MainTex ("主贴图",2D) = "white"{}

        _GroundY ("地面Y高度 (外部传入)",float) = 0
        _Shadow_Color("影子颜色",Color) = (1,1,1,1)
        _Shadow_Length("影子长度",float) = 0
        _Shadow_Rotated("影子旋转角度",range(0,360)) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue" = "Geometry+1"  //注意这里很重要,因为影子是要绘制在地面上,所以地面必须应该先绘制,否则blend混合的时候就是和背后的skybox进行混合了
        }

        pass
        {
            Stencil{

                Ref 1
                //Comp取值依次为  0:Disabled  1:Never  2:Less  3:Equal  4:LessEqual  5:Greater  6:NotEqual  7:GreaterEqual  8:Always
                Comp Greater //或者改成NotEqual
                //Pass取值依次为  0:Keep  1:Zero  2:Replace  3:IncrementSaturate  4:DecrementSaturate  5:Invert  6:IncrementWrap  7:DecrementWrap
                Pass Replace
            }

            Blend SrcAlpha oneMinusSrcAlpha   

            //因为和地面重叠所以做个偏移
            //也可以不做偏移,将传入的地面高度抬高一点即可
            Offset -2,-2

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                //这里worldPos一定是float4,因为vert()中实际是手动两次空间变换如果是float3会导致w分量丢失,透视除法会出错
                //如果不参与变换,只是传到frag()中使用的话,比如进行Blinn-Phong光照计算V向量那么float3就够了
                float4 worldPos : TEXCOORD0;
                //做阴影插值和Clip地面以下阴影用
                float cacheWorldY : TEXCOORD1;
            };

            half _GroundY;
            half4 _Shadow_Color;   
            half _Shadow_Length;     
            half _Shadow_Rotated;
            
            v2f vert(appdata v)
            {
                v2f o = (v2f)0;

                //获取世界空间的位置
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                //缓存世界空间下的y分量,后续两点作用
                //第一点 : 做插值用做计算xz的偏移量的多少
                //第二点 : 防止在地面以下
                o.cacheWorldY = o.worldPos.y;

                //设置世界空间下y的值全部都设置为传入的地面高度值
                o.worldPos.y = _GroundY;

                //根据世界空间下模型y值减去传入的地面高度值_GroundY
                //以这个值为传入 lerp(0,_Shadow_Length) 进行线性插值
                //最后获取到模型y值由低到高的插值lerpVal
                //这个max()函数 假设腿部在地面以下则裁切掉腿部阴影,后续使用clip后无需Max
                //half lerpVal = lerp(0,_Shadow_Length,max(0,o.cacheWorldY-_GroundY));
                half lerpVal = lerp(0,_Shadow_Length,o.cacheWorldY-_GroundY);

                //常量PI
                //const float PI = 3.14159265;
                //角度转换成弧度
                half radian = _Shadow_Rotated / 180.0 * UNITY_PI;

                //旋转矩阵,对(0,1)向量进行旋转,计算旋转后的向量,该向量就是阴影方向
                //2D旋转矩阵如下
                // [x]        [ cosθ , -sinθ ]
                // [ ]  乘以  
                // [y]        [ sinθ , cosθ  ]
                // x' = xcosθ - ysinθ
                // y' = xsinθ + ycosθ
                half2 ratatedAngle = half2((0*cos(radian)-1*sin(radian)),(0*sin(radian)+1*cos(radian)));
                
                //用以y轴高度为参考计算的插值 lerpVal 去 乘以一个旋转后的方向向量,作为阴影的方向
                //最终得到偏移后的阴影位置
                o.worldPos.xz += lerpVal * ratatedAngle;
                
                //变换到裁剪空间
                o.pos = mul(UNITY_MATRIX_VP,o.worldPos);

                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET
            {
                //剔除低于地面部分的片段
                clip(i.cacheWorldY - _GroundY);
                //用作阴影的Pass直接输出颜色即可
                return _Shadow_Color;
            }

            ENDCG
        }

        pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;half4 _MainTex_ST;

            struct appdata{
                float4 vertex : POSITION;
                float2 uv0 : TEXCOORD0;
            };

            struct v2f{
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata v)
            {
                v2f o = (v2f)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv0,_MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET
            {
                return tex2D(_MainTex,i.uv);
            }
            ENDCG
        }
    }
}

 

标签:开销,方式,阴影,worldPos,GroundY,float4,Shadow,v2f
From: https://www.cnblogs.com/jeason1997/p/18304894

相关文章

  • 三种交换方式的比较
    一、互联网的核心部分网络核心部分是互联网中最复杂的部分。网络中的核心部分要向周围网络边缘的大量主机提供连通性,使边缘部分任何一个主机都能够向其他主机通信。在网络核心部分起特殊作用的是路由器。路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部......
  • SQL Server中Upsert的三种方式(转载)
    本文介绍了SQLServer中Upsert的三种常见写法以及他们的性能比较。SQLServer并不支持原生的Upsert语句,通常使用组合语句实现upsert功能。 假设有表table_A,各字段如下所示:int型Id为主键。 方法1:先查询,根据查询结果判断使用insert或者updateIFEXISTS(SELECT1FROMtab......
  • 基于XML配置方式组件管理
    基于XML配置方式组件管理1.组件信息声明配置定义XML配置文件,声明组件类信息1.1基于无参构造函数先准备一个普通的类,里面默认包含无参构造packagecom.ztong.ioc_01;​publicclassHappyComponent{​  //默认包含无参构造  publicvoiddowork(){   ......
  • burpsuite暴力破解的四种攻击方式
    Sniper  狙击手模式Batteringram  攻城锤模式Pitchfork  叉子模式Clusterbomb  集束炸弹模式Sniper狙击手模式这种模式适合只爆破一个变量,如果用户名和密码都不知道的情况下不会使用该模式去爆破。他的爆破模式是如果只设置一个变量进行爆破,就按照字典中的顺序一......
  • vscode插件导致c盘内存高电脑变卡的处理方式,更换vscode插件位置
    vscode扩展包默认的安装路径是:C:\Users\用户名\.vscode\extensions,由于C盘存储空间不足,vscode扩展包体积大,需要迁移到D盘。1、关闭vscode;2、打开到D盘,创建文件夹,我的目标文件夹路径是:D:\ProgramFiles\.vscode\extensions 3、点击vscode右键,依次点击“属性”,“快捷方式”,目......
  • 安装docker在线和离线方式
    1、添加阿里云的yum下载源yuminstall-yyum-utilsyum-config-manager--add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo#查看这个新加的yum源中有那些版本包配置yumlistdocker-ce--showduplicates|sort-r#下载安装需要的rpm包yuminstall......
  • java操作Oracle 方式三 ( 全局一个连接,当操作时发现连接断开了,则再次连接,单线程 )
    全局一个连接,当操作时发现连接断开了,则再次连接,单线程这种方式好处是,全局一个连接,不会每次都发启连接,适用于某一时刻,频繁操作数据库,如:每晚同步数据OracleUtil.java基础类代码详见:https://www.cnblogs.com/hailexuexi/p/18302732完整代码dbCdrOneConnect.javapackagecom.Ja......
  • java操作Oracle 方式一 ( 连接-》操作-》断开连接 )
    连接-》操作-》断开连接这种方式的特点是每次都是新的连接,单线程,缺点是网络环境不好时连接oracle比较费时。OracleUtil.java基础类代码详见:https://www.cnblogs.com/hailexuexi/p/18302732完整代码dbCDR.javapackagecom.JavaRabbitMQToDataBase.dbOracle;importjava......
  • java操作Oracle 方式二 ( 多线程 )
    多线程方式 也是 连接-》操作-》断开连接  这样的操作过程,只是采用了多线程这种方式的特点是每次都是新的连接,多线程,解决了网络环境不好时连接oracle比较费时,影响主程序其它功能的响应。OracleUtil.java基础类代码详见:https://www.cnblogs.com/hailexuexi/p/1830273......
  • es5 js函数有哪几种继承方式
    在ES5(ECMAScript5)中,JavaScript函数有几种继承方式,主要是通过原型链实现的。以下是常见的几种继承方式:原型链继承(PrototypeInheritance):原理:通过将子类的原型对象设置为父类的实例来实现继承。特点:可以继承父类的实例方法和属性,但无法实现多继承。示例:functionParent(na......