[Unity]实时阴影技术方案总结

一,Planar Shadow

原理就是将模型压扁之后绘制在需要接受阴影的物体上,这种方式十分高效,消耗很低。具体实现过程参考Unity Shader - Planar Shadow - 平面阴影。具按照自己的理解,其实就是根据光照方向计算片元在接受阴影的平面上的投影位置,然后绘制即可,这种方式还是只适合在平面上绘制阴影。
PlanarShadow.shader

Shader "Custom/PlanarShadow"
{
    Properties
    {
        _Tint("_Tint", Color) = (1,1,1,1)
        _MainTex("_MainTex (albedo)", 2D) = "white" {}

        [Header(Alpha)]
	[Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1
        _Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5 // alpha clip threshold
        
        [Header(Shadow)]
        // _GroundHeight("_GroundHeight", Range(-100, 100)) = 0
        _GroundHeight("_GroundHeight", Float) = 0
        _ShadowColor("_ShadowColor", Color) = (0,0,0,1)
	_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05

        // Blending state
        [HideInInspector] _SrcBlend("__src", Float) = 1.0
        [HideInInspector] _DstBlend("__dst", Float) = 0.0
        [HideInInspector] _ZWrite("__zw", Float) = 1.0
        [HideInInspector] _Cull("__cull", Float) = 2.0
    }
    SubShader
    {
	Pass {
		// 其他Pass请自行实现
	}
        
        // Planar Shadows平面阴影
        Pass
        {
            Name "PlanarShadow"

            //用使用模板测试以保证alpha显示正确
            Stencil
            {
                Ref 0
                Comp equal
                Pass incrWrap
                Fail keep
                ZFail keep
            }

            Cull Off

            //透明混合模式
            Blend SrcAlpha OneMinusSrcAlpha

            //关闭深度写入
            ZWrite off

            //深度稍微偏移防止阴影与地面穿插
            Offset -1 , 0

            CGPROGRAM
            #pragma shader_feature _CLIPPING
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _ALPHAPREMULTIPLY_ON

            #include "UnityCG.cginc"

            #pragma vertex vert
            #pragma fragment frag
            
            float _GroundHeight;
            float4 _ShadowColor;
            float _ShadowFalloff;
            half4 _Tint;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Clipping;
            half _Cutoff;

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 color : COLOR;
                float2 uv : TEXCOORD0;
            };

            float3 ShadowProjectPos(float4 vertPos)
            {
                float3 shadowPos;

                //得到顶点的世界空间坐标
                float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;

                //灯光方向
                // float3 lightDir = normalize(_LightDir.xyz);
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //阴影的世界空间坐标(低于地面的部分不做改变)
                shadowPos.y = min(worldPos .y , _GroundHeight);
                shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y; 

                return shadowPos;
            }

            float GetAlpha (v2f i) {
                float alpha = _Tint.a * tex2D(_MainTex, i.uv.xy).a;
                return alpha;
            }

            v2f vert (appdata v)
            {
                v2f o;

                //得到阴影的世界空间坐标
                float3 shadowPos = ShadowProjectPos(v.vertex);

                //转换到裁切空间
                o.vertex = UnityWorldToClipPos(shadowPos);

                //得到中心点世界坐标
                float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);
                //计算阴影衰减
                float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);

                //阴影颜色
                o.color = _ShadowColor;
                o.color.a *= falloff;
                
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                if (_Clipping)
                {
                    float alpha = GetAlpha(i);
                    i.color.a *= step(_Cutoff, alpha);
                }
                return i.color;
            }
            ENDCG
        }
    }
    // FallBack "Diffuse"
}

二,Projector Shadow

Projector Shadow是常用的实时阴影实现方式,其基本原理是通过摄像机将需要显示阴影的物体,渲染到一张RenderTexture(RT)上,记录下物体的颜色值(可设置为自定义颜色),并将RT关联到Projector组件的材质上;然后通过Projector组件将需要接收阴影的物体以Projector组件的材质再渲染一遍来实现阴影的显示。
在这里插入图片描述
可下载这个插件DynamicShadowProjector

结构是这样的
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/288345.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解卡尔曼滤波(Kalman Filter)

1. 从维纳滤波到卡尔曼滤波 黑盒(Black Box)思想最早由维纳(Wiener)在1939年提出,即假定我们对从数据到估计中间的映射过程一无所知,仅仅用线性估计(我们知道在高斯背景下,线性估计…

计算机创新协会冬令营——暴力枚举题目01

首先是欢迎大家参加此次的冬令营,我们协会欢迎所有志同道合的同学们。话不多说,先来看看今天的题目吧。 题目 力扣题号:2351. 第一个出现两次的字母 注:下述题目和示例均来自力扣 题目 给你一个由小写英文字母组成的字符串 s &…

RocketMQ5.0Pop消费模式

前言 RocketMQ 5.0 消费者引入了一种新的消费模式:Pop 消费模式,目的是解决 Push 消费模式的一些痛点。 RocketMQ 4.x 之前,消费模式分为两种: Pull:拉模式,消费者自行拉取消息、上报消费结果Push&#x…

探索Allure Report:提升自动化测试效率的秘密武器

亲爱的小伙伴们,由于微信公众号改版,打乱了发布时间,为了保证大家可以及时收到文章的推送,可以点击上方蓝字关注测试工程师成长之路,并设为星标就可以第一时间收到推送哦! 一.使用 Allure2 运行方式-Python…

【操作系统xv6】学习记录4 -CPU上下文:进程上下文、线程上下文、中断上下文

什么是cpu上下文 CPU 寄存器和程序计数器就是 CPU 上下文,因为它们都是 CPU 在运行任何任务前,必须的依赖环境。 什么是 CPU 上下文切换 先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后…

equals()比较字符串和MySQL中=比较结果不一致

问题: 普通车辆入园统计结果数量和普通车辆统计列表数量不一致? 列子:数量:967,列表:974 解决问题步骤 对比统计数量和统计列表的统计方法 统计数量代码实现 一:查询出车辆滞留表数据List 二:查询出…

112. 雷达设备(贪心/逆向思考)

题目&#xff1a; 112. 雷达设备 - AcWing题库 输入样例&#xff1a; 3 2 1 2 -3 1 2 1输出样例&#xff1a; 2 思路&#xff1a; 代码&#xff1a; #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include<…

海外住宅IP代理的工作原理和应用场景分析,新手必看

海外住宅IP代理作为一种技术解决方案&#xff0c;为用户提供了访问全球网络资源和维护隐私安全的方法。本文将介绍海外住宅IP代理的工作原理和应用场景&#xff0c;帮助读者更好地理解和利用这一技术。 一、工作原理 海外住宅IP代理的工作原理基于代理服务器和IP地址的转发。它…

【springboot配置文件加载源码分析】

在Spring Boot的源码中&#xff0c;配置文件的加载是在应用程序启动的早期阶段进行的。具体来说&#xff0c;配置文件加载的主要步骤发生在SpringApplication类的run()方法中的prepareEnvironment方法中&#xff0c;真正读取我们的配置文件还是PropertySourceLoader。 本篇博客…

Docker安装Flarum(开源论坛)

Flarum介绍安装命令 #---------------------------------------------------------- mkdir -p /opt/flarum && cd /opt/flarum #---------------------------------------------------------- docker run -p 8888:8888 --name flarum \ --restartalways \ -v /opt/flar…

靠着这份年终总结,我涨薪8K,成为领导眼中最闪亮的星~

2023 年即将接近尾声&#xff0c;各大公司的“测试媛/猿”们又到了提交年终总结报告的时候了。 每年到这个时候都是抓耳挠腮、冥思苦想的时候&#xff0c;猛然一想&#xff0c;今年跟去年做的事情好像差不多&#xff0c;那么年终总结可以敷衍了事么&#xff1f; 当然是不可以…

chatGPT带你学习设计模式 (二)抽象工厂模式(创建型模式) GURU

深入理解抽象工厂模式 引言 在面向对象编程中&#xff0c;对象的创建是一个常见且关键的挑战。尤其在需要管理一系列相关对象的创建时&#xff0c;传统的对象创建方法&#xff08;如直接使用 new 关键字&#xff09;可能导致代码的高耦合和低灵活性。这时&#xff0c;抽象工厂…

rime中州韵小狼毫 中英互绎 滤镜

英文在日常生活中已经随处可见&#xff0c;我们一般中英互译需要使用专业的翻译软件来实现。但如果我们在输入法中&#xff0c;在输入中文的时候&#xff0c;可以顺便瞟一眼对应的英文词汇&#xff0c;或者在输入英文的时候可以顺便了解对应的中文词汇&#xff0c;那将为我们的…

【Qt第三方库】QXlsx库——对 Excel 文件进行相关操作

0 前言 关键词&#xff1a;Qt&#xff1b;Excel&#xff1b;QXlsx&#xff1b;QInt 简介&#xff1a; QXlsx 是第三方开源的库&#xff0c;能够对 Excel 文件进行相关操作&#xff08;读写等&#xff09; 地址&#xff1a; QXlsx官网 QXlsx的Github主页 1 快速上手 对于第一次…

设置代理IP地址对网络有什么影响?爬虫代理IP主要有哪些作用?

在互联网的广泛应用下&#xff0c;代理IP地址成为了一种常见的网络技术。代理IP地址可以改变用户的上网行为&#xff0c;进而影响网络访问的速度和安全性。本篇文章将探讨设置代理IP地址对网络的影响&#xff0c;以及爬虫代理IP的主要作用。 首先&#xff0c;让我们来了解一下代…

【Java】实验三 抽象类与接口

实验名称 实验三 抽象类与接口 实验目的 1. 深刻理解抽象类、接口的意义。 2. 熟练掌握抽象类和接口的定义、继承抽象类以及实现接口的方法。 3. 理解和掌握多态。 实验内容 &#xff08;一&#xff09;抽象类实验&#xff1a;项目源码中新建一个ahpu.shape的包&a…

Transformer从菜鸟到新手(一)

引言 这是从Transformer到LLM(大语言模型)系列的第一篇文章&#xff0c;几乎所有的大语言模型都是基于Transformer结构&#xff0c;因此本文回顾一下Transformer的原理与实现细节&#xff0c;包括分词算法BPE的实现。最终利用从零实现的Transformer模型进行英中翻译。 本文主…

IOS:Safari无法播放MP4(H.264编码)

一、问题描述 MP4使用H.264编码通常具有良好的兼容性&#xff0c;因为H.264是一种广泛支持的视频编码标准。它可以在许多设备和平台上播放&#xff0c;包括电脑、移动设备和流媒体设备。 使用caniuse查询H.264兼容性&#xff0c;看似确实具有良好的兼容性&#xff1a; 然而…

Windows系统镜像检测修复建议

当通过镜像检测功能检测出Windows操作系统磁盘上有残留驱动项、系统中存在残留Xen驱动或者存在禁止安装驱动属性设置等异常检测项时&#xff0c;您可以参考本文的操作指导进行修复。 清理注册表残留驱动 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control注册表树包含了控…

c++基础(对c的扩展)

文章目录 命令空间引用基本本质引用作为参数引用的使用场景 内联函数引出基本概念 函数补充默认参数函数重载c中函数重载定义条件函数重载的原理 命令空间 定义 namespace是单独的作用域 两者不会相互干涉 namespace 名字 { //变量 函数 等等 }eg namespace nameA {int num;v…