UnityShader35:光晕光效

news2024/12/28 3:58:46

一、光晕逻辑

光晕的逻辑很简单,就是在屏幕上画上一个一个方形的 Mesh,然后采样带 Alpha 通道的光晕贴图,效果就出来了,其中方形 Mesh 的大小、位置、纹理表现全部都由美术配置,因此效果好坏主要取决于光晕贴图以及是否有一套很好的参数/配置

1.1 Unity URP 光晕

其实 Unity 是支持光晕的,有自带的光晕组件,不过很可惜的是它不能很好的支持 URP:

默认的组件不行的话,就只能自己去实现,不过好在网上已经有人在 HDRP 上实现过了(来源于一个 HDRP Demo):毕竟效果大同小异,直接抄就完事,略微改下就能用,后面的内容也都是在这个基础之上做的分析和优化

1.2 光晕的可见性

光晕(halation)是指在曝光拍摄过程中,强光投射到胶片上时,透过胶片乳剂中在片基表面进行反射,从而致使图像发晕的现象

想要一个科学的光晕效果,需要满足两个条件:

  1. 场景中的太阳可视时,会出现镜头光晕效果
  2. 光晕在屏幕上的位置分布与场景中的太阳方位,和当前视角都有一定关系

场景中的太阳作为主要的平行光源,我们往往只需要其方向,但若想要实现这两个需求,还是要拿到太阳的具体位置,很好办,直接无脑将场景太阳方向乘上一个非常大的值就 OK 了:

half3 D = _ROCLightDir1;
float4 clip = TransformWorldToHClip(GetCameraRelativePositionWS(D * 10000));
float depth = LinearEyeDepth(0, _ZBufferParams);

这一部分是自己做的扩展,原方案的光晕位置是跟着挂载组件的 GameObject 走的

然后就是遮挡判断,需要计算太阳的屏幕空间坐标,并且和当前的摄像机深度缓冲做对比,不过这还有一个问题就是:真实的太阳它不会是一个点,因此我们不能只拿一个世界坐标去进行判断

这个也很好解决,就是在中心点周围随机散点多次,分别采样计算是否被遮挡,然后拿得到的遮挡比率去乘上光晕颜色作为最终的贡献,由于这块是在顶点着色器中做的,而你光晕的贴片顶点很少,因此性能还算 OK 的,当然对于很低端的移动设备不支持在顶点着色器中采样深度贴图的话就不要开光晕了,片段着色器做这个性能爆炸

//thanks, internets
static const uint DEPTH_SAMPLE_COUNT = 32;
static float2 samples[DEPTH_SAMPLE_COUNT] = {
    float2(0.658752441406,-0.0977704077959),
    float2(0.505380451679,-0.862896621227),
    float2(-0.678673446178,0.120453640819),
    //…… 32 组随机数,略
};

float GetOcclusion(float2 screenPos, float depth, float radius, float ratio)
{
    float contrib = 0.0f;
    float sample_Contrib = 1.0 / DEPTH_SAMPLE_COUNT;
    float2 ratioScale = float2(1 / ratio, 1.0);
    for (uint i = 0; i < DEPTH_SAMPLE_COUNT; i++)
    {
        float2 pos = screenPos + (samples[i] * radius * ratioScale);
        pos = pos * 0.5 + 0.5;
        pos.y = 1 - pos.y;
        if (pos.x >= 0 && pos.x <= 1 && pos.y >= 0 && pos.y <= 1)
        {
            float sampledDepth = LinearEyeDepth(SAMPLE_TEXTURE2D_LOD(_CameraDepthTexture, sampler_CameraDepthTexture, pos, 0).r, _ZBufferParams);
            if (sampledDepth >= depth)
                contrib += sample_Contrib;
        }
    }
    return contrib;
}

vert()
{
    float2 screenPos = clip.xy / clip.w;
    float ratio = _ScreenParams.x / _ScreenParams.y;
    float radius = v.worldPosRadius.w;
    float occlusion = GetOcclusion(screenPos, depth, radius, ratio);
}

1.3 Mesh 生成与着色

这一块完全根据配置决定,几乎没有计算量,太简单了,可以直接看代码

不过还有一点要提:原方案由于可以调节的参数比较多,它们要传入 shader,都需要通过 mesh 的额外通道 uv1、uv2、uv3,并且格式要开 float4,这样就可能会出现通道不够用的情况,特别是有的设备可能都不支持额外的 uv 数据使用 float4,因此有些参数就考虑不要了

struct appdata
{
    float4 vertex: POSITION;
    float2 uv: TEXCOORD0;
    float4 color: COLOR;

    // LensFlare Data : 
    //      * X = RayPos 
    //      * Y = Rotation (< 0 = Auto)
    //      * ZW = Size (Width, Height) in Screen Height Ratio
    nointerpolation float4 lensflare_data: TEXCOORD1;
    // World Position (XYZ) and Radius(W) : 
    nointerpolation float4 worldPosRadius: TEXCOORD2;
    // LensFlare FadeData : 
    //      * X = Near Start Distance
    //      * Y = Near End Distance
    //      * Z = Far Start Distance
    //      * W = Far End Distance
    nointerpolation float4 lensflare_fadeData: TEXCOORD3;
};

很明显,如果我们确保光晕的方向一定跟着场景光源方向走,那么我们就不太需要去传递世界坐标到 shader 中,只需要按照前面的方案无脑拿平行光方向乘上一个很大的数就可以了,这样第二组参数就可以省下来

除此之外,第三个参数用于实现光源距离越近,光晕效果越强的渐进效果,其实也可以不要,目前来看效果是 OK 的,毕竟我们完全可以把太阳当作在无限远的位置,给个固定值就可以了

这样就能砍掉一半的顶点数据

1.4 后续性能优化方案

目前实现是用了多个独立的 subMesh 来绘制多个面片,但其实也可以使用分 UV 的方式,把多张 Flare 贴图合成一张图,并且在一个 Mesh 上进行绘制,这样可以减少到一次 Drawcall

除此之外,目前光晕和画质无关,所有画质下都是默认开启的,理论上低配中配可以不给开

二、手机各平台兼容 

很可惜,如果你和我一样是基于前面 HDRP Demo 的光晕效果,在此基础之上优化修改的,那么它还有一个问题,那就是其不能很好的支持的 Andriod 平台(当然如果你不考虑除了 Window 以外的其它平台就当我没说)

问题就出现在文章中的 2.2 光晕光效的可见性部分,当主光源被遮挡时,就不会有光晕的现象,这个判断很简单,因为太阳的位置可以被视作无限远,因此我们只要以太阳中心为圆心,多次随机采样周围屏幕坐标对应的深度信息中有没有被写入数据就可以了,如果有就说明这个屏幕点不可视

但是在 Andriod 平台上,这个深度检测它貌似失效了:即怎么检测都通过,因此无论光源是否可见,光晕效果永远存在,这明显是不对的

2.1 是否当前的手机平台不支持顶点着色器采样纹理

查了下资料,OpenGL 3.0 及以上,DX9 以上都支持顶点着色器中采样纹理,而事实上,现在极大多数设备平台都满足这个要求,因此不是这个原因

  • 但仍然需要注意的是:Unity 若要在顶点着色器中采样纹理,需要指定 mipmap 等级,这就意味着不能使用常规的 API 例如 SAMPLE_TEXTURE2D(…),需要用 SAMPLE_TEXTURE2D_LOD(…, 0) 代替,这是因为 mipmap 的级别的选择需要在片段着色阶段才能获取

2.2 是否是因为指定的着色器模型,默认编译目标设置过高

Shader 中设置的默认编译目标为 5.0,这基本上是能设置的最高等级了:

#pragma target 5.0

后来查了下 Unity3D 对应的官方手册:着色器编译:针对着色器模型和 GPU 功能,其中也说明

如果真如此,那么显然目前几乎所有的主流设备都不支持编译这么高的版本,必然有问题

不过也很奇怪,如果真的不支持整个 shader 都应该不生效才对,不应该只是深度测试有问题,因此后面又查了下资料,确保 Unity 在这块的设置是默认向后兼容的,而且你没有用像曲面细分着色器这种必须高版本才支持的功能/特性的话,编译目标版本确实低一些也没关系

后面修改编译目标,打包测试了下,排除了这个原因

2.3 疑似屏幕坐标计算错误

参考其它 shader:通过屏幕坐标采样 depthTexture,都会对 clipPos 做一步 ComputeScreenPos 的操作,再除一个 w,而出现问题的光晕着色器中,并没有这一步,最终我们用于采样的坐标是手算的,所以怀疑是否有算错

o.pos = TransformObjectToHClip(v.pos.xyz);
o.screenUV = ComputeScreenPos(o.pos);
half2 screenUV = i.screenUV.xy / i.screenUV.w;
viewSpaceDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_PointClamp, screenUV);

需要排查这个原因,需要先知道 Unity 的 ComputeScreenPos 到底为我们做了什么事

2.3.1 ComputeScreenPos

直接看源码:

float4 ComputeScreenPos(float4 positionCS)
{
    float4 o = positionCS * 0.5f;
    o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w;
    o.zw = positionCS.zw;
    return o;
}

对于我们的输入 positionCS,也就是经过 TransformObjectToHClip 转换后的坐标是处于裁剪空间中的坐标,其 x, y 分量的范围为 [-w, w],假设我们最终屏幕的宽高分别为 width 和 height,那么真实屏幕空间坐标的计算方法应该是:

screenUV.x = ((pos.x / w) * 0.5 + 0.5) * width;
screenUV.y = ((pos.y / w) * 0.5 + 0.5) * height;

这就是一个标准的 [-w, w] 映射到 [0, 1] 范围的操作,再乘上我们的宽和高

而此时你再看 ComputeScreenPos 方法内的操作,你就会发现,它的主体其实就是上面的代码乘上 w(先不考虑其中的 _ProjectionParams.x),也就是将 [-w, w] 映射到 [0, w] 范围而非 [0, 1]

Unity 为什么要这样做呢?即为什么不直接帮我们直接映射到 [0, 1]?

  1. 考虑 tex2Dproj 指令,其会在对纹理采样前帮我们把参数除上一个 w 分量,这样你在 ComputeScreenPos 之后直接套用这个方法就是正确的了,只不过我们很少用这个 tex2Dproj 指令
  2. 如果你要在片段着色器中采样,非常不建议在顶点着色器中就提前除以 w 分量,此会导致经过插值到片段着色器后,得到的插值结果不准确,因此最好是先插值再归一化,这是因为投影空间不是线性空间

而对于 _ProjectionParams.x,则是判断我们的投影矩阵是否为翻转矩阵,如果使用了翻转投影矩阵,那么我们同时也要翻转 y 的坐标值,关于是否使用翻转投影矩阵,这个就和平台有关

  • 如果是 Direct3D-like 平台(UNITY_UV_STARTS_AT_TOP = 1),就需要进行翻转
  • 如果是 OpenGL-like 平台(UNITY_UV_STARTS_AT_TOP = 0),则无需翻转

因此使用 ComputeScreenPos 就不需要关心这一部分平台差异了

2.4 Reversed-Z

在做深度测试的时候,我们把太阳的位置当作一个无限远的位置,但是不同平台下,深度最值不一样,这个是个很重要的问题,之前有篇文章也专门介绍过 Reversed-Z

看下原始代码,逻辑中给太阳设定的深度值如下:

float depth = LinearEyeDepth(0, _ZBufferParams);

关于 LinearEyeDepth,即传入深度纹理中的深度值以计算出实际的深度值,这就不详细介绍

很明显,只有在深度发生反转的平台上(DirectX),最值深度才为0,OpenGL 平台没有 Reversed-Z,其深度范围为 [0, 1],最值为1,因此正确的考虑了平台差异的代码应如下:

#if UNITY_REVERSED_Z
    float depth = LinearEyeDepth(0, _ZBufferParams);
#else
    float depth = LinearEyeDepth(1, _ZBufferParams);
#endif

好了,搞定!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP8266点亮 0.96 英寸 OLED 显示屏,基于Arduino IDE

本指南介绍如何使用 Arduino IDE 将 0.96 英寸 SSD1306 OLED 显示屏与 ESP8266 结合使用。我们将向您展示如何编写文本、设置不同的字体、绘制形状和显示位图图像。安装 SSD1306 OLED 库 – ESP8266有几个库可用于使用 ESP8266 控制 OLED 显示屏。在本教程中&#xff0c;我们将…

IP地址与用户行为

IP地址能够解决网络风险和提高网络安全的原因是&#xff1a;所有的网络请求都会带有IP信息&#xff0c;是访问者的独立标识&#xff0c;另外ip地址的分配和管理比较严格&#xff0c;难以造假。另外ip属于网络层&#xff0c;可以轻松的对其进行阻断。现有的各种网络安全、负载均…

操作系统开发:BIOS/MBR基础与调试

这里在实验之前需要下载 Bochs-win32-2.6.11 作者使用的是Linux版本的&#xff0c;在Linux写代码不太舒服&#xff0c;所以最好在Windows上做实验&#xff0c;下载好虚拟机以后还需要下载Nasm汇编器&#xff0c;以及GCC编译器&#xff0c;为了能够使用DD命令实现磁盘拷贝&#…

树莓派 安装 宝塔linux面板5.9. 2023-2-14

一.环境 1.硬件环境: 树莓派3b , 8GB tf卡 ,micro usb电源 2.网络环境: 网线直连路由器 , 可访问互联网 3.软件环境: 树莓派操作系统 CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-2009-sda(linux) 系统刻录工具 Win32DiskImager (win) ip扫描工具 Advanced IP Scanne…

公司招聘:33岁以上的和两年一跳的不要,开出工资我还以为看错了...

导读&#xff1a;对于公司来说&#xff0c;肯定是希望花最少的钱招到最优秀的员工&#xff0c;但事实上这个想法是不太现实的&#xff0c;虽然如今互联网不太好找工作&#xff0c;但要员工降薪去入职&#xff0c;相信还是有很大难度的&#xff0c;很多人宁可在家休息&#xff0…

【Linux】进程的虚拟地址空间

文章目录现象引入进程地址空间进程地址空间的描述进程地址空间是怎么产生的进程地址空间的好处对开篇问题的解释现象引入 我们运行下面一段代码&#xff1a; #include <stdio.h> #include <unistd.h>int global_val 100;int main() {pid_t id fork();int count…

根据 Jupyter-lab 源码实现 notebook(.ipynb)在页面中的渲染

前言 最近因为工作项目的需要&#xff0c;要在项目中尽可能的还原notebook渲染效果。由于网上没找到相关的指导文章&#xff0c;所以只能生啃JupyterLab源码&#xff0c;独自摸索实现。经过一段时间“跌跌撞撞”的摸索尝试&#xff0c;总算勉强实现了。 因此编写此文章做一下…

转转微服务容量管理实践

1 背景2 容量管理的目标3 发展阶段4 容量管理4.1 容量水位4.2 资源容量优化4.3 集群容量4.4 压测指标4.5 压测标准5 扩容、缩容6 总结1 背景 随着转转业务的不断发展和用户不断增长&#xff0c;公司持续增加对硬件和基础设施的投入&#xff0c;用于满足业务发展的需要&#xff…

计算机网络8-在浏览器中输入URL后会发生什么

参考&#xff1a; 在浏览器中输入URL并按下回车后会发生什么&#xff1f; DNS域名详细解析过程 1.URL解析拿到域名 当用户输入URL并回车后&#xff0c;浏览器对拿到的URL进行识别&#xff0c;抽取出域名字段&#xff0c;比如https://www.baidu.com,它的域名就是www.baidu.com…

SQL数据库根据需求发送邮件

一、启用数据库邮件 手动启用数据库邮件功能&#xff0c;需执行以下脚本&#xff1a; exec sp_configure show advanced options,1 RECONFIGURE exec sp_configure Database Mail XPs,1 RECONFIGURE With Override 二、邮件服务器设置 1.邮箱启用设置-POP3/IMAP/SMTP/Exch…

DAMA数据管理知识体系指南之数据质量管理

第12章 数据质量管理 12.1 简介 数据质量管理是组织变革管理中一项关键的支撑流程。业务重点的变化、公司的业务整合战略&#xff0c;以及并购与合作&#xff0c;都对IT职能提出了更高要求&#xff0c;包括整合数据源、创建一致的数据副本、交互提供数据或整合数据。与遗留系…

SpringAOP理解实现方式

Aop 什么是Aop&#xff1f; AOP就是面向切面编程&#xff0c;通过预编译方式以及运行期间的动态代理技术来实现程序的统一维护功能。 什么是切面&#xff0c;我理解的切面就是两个方法之间&#xff0c;两个对象之间&#xff0c;两个模块之间就是一个切面。假设在两个模块之间…

9.手动部署Java应用

Jenkins部署Java应用什么java应用手动部署java环境、手动进行代码发布过程1.环境准备配制负载均衡配制webserver&#xff08;tomcat&#xff09;集群本地做域名劫持查看效果2.模拟开发提交Java代码-->推送至gitlab上传代码至gitlab3.运维克隆代码&#xff0c;然后通过maven手…

Yolo-fastestv2训练自己的数据集记录

Yolo-fastestv2训练自己的数据集记录 第一节&#xff1a;代码来源 本机环境&#xff1a;ubuntu20&#xff0c;cuda,cudnn,pytorch1.11.0 代码来源&#xff1a;https://github.com/dog-qiuqiu/Yolo-FastestV2 配置环境后先测试一下环境 终端输入&#xff1a; python3 test.py…

Vue入门介绍

一、背景 目前前端主流框架有Vue、react、Angular等&#xff0c;其中Vue简单易学&#xff0c;只要稍微会点HTML、CSS、JavaScript基础就能很快上手Vue&#xff0c;其门槛低&#xff0c;上手速度快的特点&#xff0c;深受测试开发同学喜爱&#xff0c;已逐渐成为测开必备的前端…

spring回显方式在代码层面的复现(内存马系列篇十四)

前言 在前面的一章中&#xff0c;主要在理论上进行了各种内存马的实现&#xff0c;这里就做为上一篇的补充&#xff0c;自己搭建反序列化的漏洞环境来进行上文中理论上内存马的注入实践。 这是内存马系列文章的第十四篇。 环境搭建 可以使用我用的漏洞环境 https://github…

一款基于java的超级棒的开源支付系统(用来毕设也不错),国内首款开源的互联网支付系统

最近就快要到年末了&#xff0c;小编想着应该会有很多公司开始冲年度的业绩了&#xff0c;既然是冲业绩&#xff0c;就离不开我们的支付系统&#xff0c;所以小编就去网上给大家找到了一款超级棒的开源支付系统&#xff01;帮助大家从头到尾了解清楚这其中的逻辑&#xff01;所…

蓝牙 - 芯片制造商的代号编制以及在Windows上查看

在蓝牙技术的规范中&#xff0c;对很多信息都进行了整理和代号分配&#xff0c;比如生产蓝牙芯片的厂商&#xff0c;也进行了数字编号。 有一个专门的“Assigned Numbers”的PDF文档&#xff0c;记录了蓝牙规范中的各种类型数字所表示的含义。 本文介绍的数字类型&#xff0c…

JavaScript Window - 浏览器对象模型

JavaScript Window - 浏览器对象模型 浏览器对象模型 (BOM) BOM&#xff1a;Browser Object Model 是浏览器对象模型&#xff0c;BOM由多个对象构成&#xff0c;其中代表浏览器窗口的window对象是BOM的顶层对象也是核心对象&#xff0c;其他对象都是该对象的子对象。 BOM对象…

IB-PYP幼儿十大素质培养目标

作为IB候选学校&#xff0c;一直秉承IB教育的核心目标&#xff0c;贯彻在幼儿的学习生活中。IB教育之所以成为当今国际教育的领跑者&#xff0c;最主要的原因是IB教育是切切实实的“全人”教育&#xff0c;“素质”教育&#xff0c;拥有一套完整的教学服务体系。当我们走进IB“…